From c73db02271a67e9ceb42115dde6be997ec4b89ef Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 10 Aug 2016 15:48:20 -0400 Subject: split up grp-{hostname,locale}, grp-resolve/systemd-resolve{,d}; shell completion --- src/grp-hostname/.gitignore | 1 - src/grp-hostname/Makefile | 53 +- src/grp-hostname/hostnamectl.c | 529 ----- src/grp-hostname/hostnamectl/Makefile | 45 + src/grp-hostname/hostnamectl/hostnamectl.c | 529 +++++ .../hostnamectl/hostnamectl.completion.bash | 64 + .../hostnamectl/hostnamectl.completion.zsh | 80 + src/grp-hostname/hostnamed.c | 745 ------- src/grp-hostname/org.freedesktop.hostname1.conf | 27 - .../org.freedesktop.hostname1.policy.in | 50 - src/grp-hostname/org.freedesktop.hostname1.service | 12 - src/grp-hostname/systemd-hostnamed/.gitignore | 1 + src/grp-hostname/systemd-hostnamed/Makefile | 65 + src/grp-hostname/systemd-hostnamed/hostnamed.c | 745 +++++++ .../org.freedesktop.hostname1.conf | 27 + .../org.freedesktop.hostname1.policy.in | 50 + .../org.freedesktop.hostname1.service | 12 + .../systemd-detect-virt.completion.zsh | 11 + .../grp-remote/systemd-journal-remote/Makefile | 9 +- .../systemd-cat/systemd-cat.completion.zsh | 12 + src/grp-locale/.gitignore | 1 - src/grp-locale/Makefile | 67 +- src/grp-locale/kbd-model-map | 68 - src/grp-locale/language-fallback-map | 13 - src/grp-locale/localectl.c | 682 ------ src/grp-locale/localectl/Makefile | 45 + src/grp-locale/localectl/localectl.c | 682 ++++++ src/grp-locale/localectl/localectl.completion.bash | 92 + src/grp-locale/localectl/localectl.completion.zsh | 93 + src/grp-locale/localed.c | 1389 ------------ src/grp-locale/org.freedesktop.locale1.conf | 27 - src/grp-locale/org.freedesktop.locale1.policy.in | 40 - src/grp-locale/org.freedesktop.locale1.service | 12 - src/grp-locale/systemd-localed/.gitignore | 1 + src/grp-locale/systemd-localed/Makefile | 79 + src/grp-locale/systemd-localed/kbd-model-map | 68 + .../systemd-localed/language-fallback-map | 13 + src/grp-locale/systemd-localed/localed.c | 1389 ++++++++++++ .../systemd-localed/org.freedesktop.locale1.conf | 27 + .../org.freedesktop.locale1.policy.in | 40 + .../org.freedesktop.locale1.service | 12 + src/grp-resolve/Makefile | 3 +- src/grp-resolve/libbasic-dns/Makefile | 115 + src/grp-resolve/libbasic-dns/dns-type.c | 324 +++ src/grp-resolve/libbasic-dns/dns-type.h | 161 ++ src/grp-resolve/libbasic-dns/resolved-def.h | 38 + src/grp-resolve/libbasic-dns/resolved-dns-answer.c | 859 ++++++++ src/grp-resolve/libbasic-dns/resolved-dns-answer.h | 144 ++ src/grp-resolve/libbasic-dns/resolved-dns-dnssec.c | 2199 +++++++++++++++++++ src/grp-resolve/libbasic-dns/resolved-dns-dnssec.h | 103 + src/grp-resolve/libbasic-dns/resolved-dns-packet.c | 2256 ++++++++++++++++++++ src/grp-resolve/libbasic-dns/resolved-dns-packet.h | 270 +++ .../libbasic-dns/resolved-dns-question.c | 469 ++++ .../libbasic-dns/resolved-dns-question.h | 70 + src/grp-resolve/libbasic-dns/resolved-dns-rr.c | 1595 ++++++++++++++ src/grp-resolve/libbasic-dns/resolved-dns-rr.h | 343 +++ .../test-data/_443._tcp.fedoraproject.org.pkts | Bin 0 -> 169 bytes .../test-data/_openpgpkey.fedoraproject.org.pkts | Bin 0 -> 986 bytes .../libbasic-dns/test-data/fake-caa.pkts | Bin 0 -> 196 bytes .../libbasic-dns/test-data/fedoraproject.org.pkts | Bin 0 -> 1483 bytes .../libbasic-dns/test-data/gandi.net.pkts | Bin 0 -> 1010 bytes .../libbasic-dns/test-data/google.com.pkts | Bin 0 -> 747 bytes .../libbasic-dns/test-data/kyhwana.org.pkts | Bin 0 -> 1803 bytes src/grp-resolve/libbasic-dns/test-data/root.pkts | Bin 0 -> 1061 bytes ...sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts | Bin 0 -> 330 bytes .../libbasic-dns/test-data/teamits.com.pkts | Bin 0 -> 1021 bytes .../test-data/zbyszek@fedoraproject.org.pkts | Bin 0 -> 2533 bytes src/grp-resolve/libbasic-dns/test-dns-packet.c | 115 + src/grp-resolve/libbasic-dns/test-dnssec-complex.c | 237 ++ src/grp-resolve/libbasic-dns/test-dnssec.c | 344 +++ src/grp-resolve/libbasic-dns/test-resolve-tables.c | 65 + src/grp-resolve/systemd-resolve/Makefile | 52 + src/grp-resolve/systemd-resolve/gcrypt-util.c | 1 + src/grp-resolve/systemd-resolve/gcrypt-util.h | 1 + src/grp-resolve/systemd-resolve/resolve-tool.c | 1485 +++++++++++++ .../systemd-resolve.completion.bash | 64 + .../systemd-resolve/systemd-resolve.completion.zsh | 64 + src/grp-resolve/systemd-resolved/Makefile | 109 +- src/grp-resolve/systemd-resolved/dns-type.c | 324 --- src/grp-resolve/systemd-resolved/dns-type.h | 161 -- src/grp-resolve/systemd-resolved/resolve-tool.c | 1485 ------------- src/grp-resolve/systemd-resolved/resolved-def.h | 38 - .../systemd-resolved/resolved-dns-answer.c | 859 -------- .../systemd-resolved/resolved-dns-answer.h | 144 -- .../systemd-resolved/resolved-dns-dnssec.c | 2199 ------------------- .../systemd-resolved/resolved-dns-dnssec.h | 103 - .../systemd-resolved/resolved-dns-packet.c | 2256 -------------------- .../systemd-resolved/resolved-dns-packet.h | 270 --- .../systemd-resolved/resolved-dns-question.c | 469 ---- .../systemd-resolved/resolved-dns-question.h | 70 - src/grp-resolve/systemd-resolved/resolved-dns-rr.c | 1595 -------------- src/grp-resolve/systemd-resolved/resolved-dns-rr.h | 343 --- .../test-data/_443._tcp.fedoraproject.org.pkts | Bin 169 -> 0 bytes .../test-data/_openpgpkey.fedoraproject.org.pkts | Bin 986 -> 0 bytes .../systemd-resolved/test-data/fake-caa.pkts | Bin 196 -> 0 bytes .../test-data/fedoraproject.org.pkts | Bin 1483 -> 0 bytes .../systemd-resolved/test-data/gandi.net.pkts | Bin 1010 -> 0 bytes .../systemd-resolved/test-data/google.com.pkts | Bin 747 -> 0 bytes .../systemd-resolved/test-data/kyhwana.org.pkts | Bin 1803 -> 0 bytes .../systemd-resolved/test-data/root.pkts | Bin 1061 -> 0 bytes ...sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts | Bin 330 -> 0 bytes .../systemd-resolved/test-data/teamits.com.pkts | Bin 1021 -> 0 bytes .../test-data/zbyszek@fedoraproject.org.pkts | Bin 2533 -> 0 bytes src/grp-resolve/systemd-resolved/test-dns-packet.c | 115 - .../systemd-resolved/test-dnssec-complex.c | 237 -- src/grp-resolve/systemd-resolved/test-dnssec.c | 344 --- .../systemd-resolved/test-resolve-tables.c | 65 - src/grp-system/systemctl/.gitignore | 2 + .../systemctl/systemctl.completion.bash.in | 284 +++ .../systemctl/systemctl.completion.zsh.in | 391 ++++ .../systemd-notify/systemd-notify.completion.zsh | 12 + .../systemd-path/systemd-path.completion.zsh | 18 + .../systemd-socket-activate.completion.bash | 0 .../systemd-socket-activate.completion.zsh | 0 .../systemd-ask-password.completion.zsh | 12 + src/systemd-cgls/systemd-cgls.completion.zsh | 12 + src/systemd-cgtop/systemd-cgtop.completion.zsh | 17 + .../systemd-machine-id-setup.completion.zsh | 8 + .../systemd-stdio-bridge.completion.bash | 0 .../systemd-stdio-bridge.completion.zsh | 0 .../systemd-tty-ask-password-agent.completion.zsh | 14 + 121 files changed, 16335 insertions(+), 14905 deletions(-) delete mode 100644 src/grp-hostname/.gitignore delete mode 100644 src/grp-hostname/hostnamectl.c create mode 100644 src/grp-hostname/hostnamectl/Makefile create mode 100644 src/grp-hostname/hostnamectl/hostnamectl.c create mode 100644 src/grp-hostname/hostnamectl/hostnamectl.completion.bash create mode 100644 src/grp-hostname/hostnamectl/hostnamectl.completion.zsh delete mode 100644 src/grp-hostname/hostnamed.c delete mode 100644 src/grp-hostname/org.freedesktop.hostname1.conf delete mode 100644 src/grp-hostname/org.freedesktop.hostname1.policy.in delete mode 100644 src/grp-hostname/org.freedesktop.hostname1.service create mode 100644 src/grp-hostname/systemd-hostnamed/.gitignore create mode 100644 src/grp-hostname/systemd-hostnamed/Makefile create mode 100644 src/grp-hostname/systemd-hostnamed/hostnamed.c create mode 100644 src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.conf create mode 100644 src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.policy.in create mode 100644 src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.service create mode 100644 src/grp-initutils/systemd-detect-virt/systemd-detect-virt.completion.zsh create mode 100644 src/grp-journal/systemd-cat/systemd-cat.completion.zsh delete mode 100644 src/grp-locale/.gitignore delete mode 100644 src/grp-locale/kbd-model-map delete mode 100644 src/grp-locale/language-fallback-map delete mode 100644 src/grp-locale/localectl.c create mode 100644 src/grp-locale/localectl/Makefile create mode 100644 src/grp-locale/localectl/localectl.c create mode 100644 src/grp-locale/localectl/localectl.completion.bash create mode 100644 src/grp-locale/localectl/localectl.completion.zsh delete mode 100644 src/grp-locale/localed.c delete mode 100644 src/grp-locale/org.freedesktop.locale1.conf delete mode 100644 src/grp-locale/org.freedesktop.locale1.policy.in delete mode 100644 src/grp-locale/org.freedesktop.locale1.service create mode 100644 src/grp-locale/systemd-localed/.gitignore create mode 100644 src/grp-locale/systemd-localed/Makefile create mode 100644 src/grp-locale/systemd-localed/kbd-model-map create mode 100644 src/grp-locale/systemd-localed/language-fallback-map create mode 100644 src/grp-locale/systemd-localed/localed.c create mode 100644 src/grp-locale/systemd-localed/org.freedesktop.locale1.conf create mode 100644 src/grp-locale/systemd-localed/org.freedesktop.locale1.policy.in create mode 100644 src/grp-locale/systemd-localed/org.freedesktop.locale1.service create mode 100644 src/grp-resolve/libbasic-dns/Makefile create mode 100644 src/grp-resolve/libbasic-dns/dns-type.c create mode 100644 src/grp-resolve/libbasic-dns/dns-type.h create mode 100644 src/grp-resolve/libbasic-dns/resolved-def.h create mode 100644 src/grp-resolve/libbasic-dns/resolved-dns-answer.c create mode 100644 src/grp-resolve/libbasic-dns/resolved-dns-answer.h create mode 100644 src/grp-resolve/libbasic-dns/resolved-dns-dnssec.c create mode 100644 src/grp-resolve/libbasic-dns/resolved-dns-dnssec.h create mode 100644 src/grp-resolve/libbasic-dns/resolved-dns-packet.c create mode 100644 src/grp-resolve/libbasic-dns/resolved-dns-packet.h create mode 100644 src/grp-resolve/libbasic-dns/resolved-dns-question.c create mode 100644 src/grp-resolve/libbasic-dns/resolved-dns-question.h create mode 100644 src/grp-resolve/libbasic-dns/resolved-dns-rr.c create mode 100644 src/grp-resolve/libbasic-dns/resolved-dns-rr.h create mode 100644 src/grp-resolve/libbasic-dns/test-data/_443._tcp.fedoraproject.org.pkts create mode 100644 src/grp-resolve/libbasic-dns/test-data/_openpgpkey.fedoraproject.org.pkts create mode 100644 src/grp-resolve/libbasic-dns/test-data/fake-caa.pkts create mode 100644 src/grp-resolve/libbasic-dns/test-data/fedoraproject.org.pkts create mode 100644 src/grp-resolve/libbasic-dns/test-data/gandi.net.pkts create mode 100644 src/grp-resolve/libbasic-dns/test-data/google.com.pkts create mode 100644 src/grp-resolve/libbasic-dns/test-data/kyhwana.org.pkts create mode 100644 src/grp-resolve/libbasic-dns/test-data/root.pkts create mode 100644 src/grp-resolve/libbasic-dns/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts create mode 100644 src/grp-resolve/libbasic-dns/test-data/teamits.com.pkts create mode 100644 src/grp-resolve/libbasic-dns/test-data/zbyszek@fedoraproject.org.pkts create mode 100644 src/grp-resolve/libbasic-dns/test-dns-packet.c create mode 100644 src/grp-resolve/libbasic-dns/test-dnssec-complex.c create mode 100644 src/grp-resolve/libbasic-dns/test-dnssec.c create mode 100644 src/grp-resolve/libbasic-dns/test-resolve-tables.c create mode 100644 src/grp-resolve/systemd-resolve/Makefile create mode 120000 src/grp-resolve/systemd-resolve/gcrypt-util.c create mode 120000 src/grp-resolve/systemd-resolve/gcrypt-util.h create mode 100644 src/grp-resolve/systemd-resolve/resolve-tool.c create mode 100644 src/grp-resolve/systemd-resolve/systemd-resolve.completion.bash create mode 100644 src/grp-resolve/systemd-resolve/systemd-resolve.completion.zsh delete mode 100644 src/grp-resolve/systemd-resolved/dns-type.c delete mode 100644 src/grp-resolve/systemd-resolved/dns-type.h delete mode 100644 src/grp-resolve/systemd-resolved/resolve-tool.c delete mode 100644 src/grp-resolve/systemd-resolved/resolved-def.h delete mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-answer.c delete mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-answer.h delete mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-dnssec.c delete mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-dnssec.h delete mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-packet.c delete mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-packet.h delete mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-question.c delete mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-question.h delete mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-rr.c delete mode 100644 src/grp-resolve/systemd-resolved/resolved-dns-rr.h delete mode 100644 src/grp-resolve/systemd-resolved/test-data/_443._tcp.fedoraproject.org.pkts delete mode 100644 src/grp-resolve/systemd-resolved/test-data/_openpgpkey.fedoraproject.org.pkts delete mode 100644 src/grp-resolve/systemd-resolved/test-data/fake-caa.pkts delete mode 100644 src/grp-resolve/systemd-resolved/test-data/fedoraproject.org.pkts delete mode 100644 src/grp-resolve/systemd-resolved/test-data/gandi.net.pkts delete mode 100644 src/grp-resolve/systemd-resolved/test-data/google.com.pkts delete mode 100644 src/grp-resolve/systemd-resolved/test-data/kyhwana.org.pkts delete mode 100644 src/grp-resolve/systemd-resolved/test-data/root.pkts delete mode 100644 src/grp-resolve/systemd-resolved/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts delete mode 100644 src/grp-resolve/systemd-resolved/test-data/teamits.com.pkts delete mode 100644 src/grp-resolve/systemd-resolved/test-data/zbyszek@fedoraproject.org.pkts delete mode 100644 src/grp-resolve/systemd-resolved/test-dns-packet.c delete mode 100644 src/grp-resolve/systemd-resolved/test-dnssec-complex.c delete mode 100644 src/grp-resolve/systemd-resolved/test-dnssec.c delete mode 100644 src/grp-resolve/systemd-resolved/test-resolve-tables.c create mode 100644 src/grp-system/systemctl/.gitignore create mode 100644 src/grp-system/systemctl/systemctl.completion.bash.in create mode 100644 src/grp-system/systemctl/systemctl.completion.zsh.in create mode 100644 src/grp-utils/systemd-notify/systemd-notify.completion.zsh create mode 100644 src/grp-utils/systemd-path/systemd-path.completion.zsh create mode 100644 src/grp-utils/systemd-socket-activate/systemd-socket-activate.completion.bash create mode 100644 src/grp-utils/systemd-socket-activate/systemd-socket-activate.completion.zsh create mode 100644 src/systemd-ask-password/systemd-ask-password.completion.zsh create mode 100644 src/systemd-cgls/systemd-cgls.completion.zsh create mode 100644 src/systemd-cgtop/systemd-cgtop.completion.zsh create mode 100644 src/systemd-machine-id-setup/systemd-machine-id-setup.completion.zsh create mode 100644 src/systemd-stdio-bridge/systemd-stdio-bridge.completion.bash create mode 100644 src/systemd-stdio-bridge/systemd-stdio-bridge.completion.zsh create mode 100644 src/systemd-tty-ask-password-agent/systemd-tty-ask-password-agent.completion.zsh (limited to 'src') diff --git a/src/grp-hostname/.gitignore b/src/grp-hostname/.gitignore deleted file mode 100644 index 1ff281b231..0000000000 --- a/src/grp-hostname/.gitignore +++ /dev/null @@ -1 +0,0 @@ -org.freedesktop.hostname1.policy diff --git a/src/grp-hostname/Makefile b/src/grp-hostname/Makefile index 911188454c..319c1d2c8e 100644 --- a/src/grp-hostname/Makefile +++ b/src/grp-hostname/Makefile @@ -23,57 +23,6 @@ include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk include $(topsrcdir)/build-aux/Makefile.head.mk -ifneq ($(ENABLE_HOSTNAMED),) -systemd_hostnamed_SOURCES = \ - src/hostname/hostnamed.c - -systemd_hostnamed_LDADD = \ - libshared.la - -rootlibexec_PROGRAMS += \ - systemd-hostnamed - -nodist_systemunit_DATA += \ - units/systemd-hostnamed.service - -dist_systemunit_DATA_busnames += \ - units/org.freedesktop.hostname1.busname - -dist_dbuspolicy_DATA += \ - src/hostname/org.freedesktop.hostname1.conf - -dist_dbussystemservice_DATA += \ - src/hostname/org.freedesktop.hostname1.service - -polkitpolicy_files += \ - src/hostname/org.freedesktop.hostname1.policy - -SYSTEM_UNIT_ALIASES += \ - systemd-hostnamed.service dbus-org.freedesktop.hostname1.service - -BUSNAMES_TARGET_WANTS += \ - org.freedesktop.hostname1.busname - -hostnamectl_SOURCES = \ - src/hostname/hostnamectl.c - -hostnamectl_LDADD = \ - libshared.la - -bin_PROGRAMS += \ - hostnamectl - -dist_bashcompletion_data += \ - shell-completion/bash/hostnamectl - -dist_zshcompletion_data += \ - shell-completion/zsh/_hostnamectl -endif # ENABLE_HOSTNAMED - -polkitpolicy_in_files += \ - src/hostname/org.freedesktop.hostname1.policy.in - -EXTRA_DIST += \ - units/systemd-hostnamed.service.in +nested.subdirs += systemd-hostnamed hostnamectl include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-hostname/hostnamectl.c b/src/grp-hostname/hostnamectl.c deleted file mode 100644 index 8a24813934..0000000000 --- a/src/grp-hostname/hostnamectl.c +++ /dev/null @@ -1,529 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include -#include -#include - -#include -#include - -#include "basic/alloc-util.h" -#include "basic/architecture.h" -#include "basic/hostname-util.h" -#include "basic/util.h" -#include "sd-bus/bus-error.h" -#include "shared/bus-util.h" -#include "shared/spawn-polkit-agent.h" - -static bool arg_ask_password = true; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_transient = false; -static bool arg_pretty = false; -static bool arg_static = 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(); -} - -typedef struct StatusInfo { - char *hostname; - char *static_hostname; - char *pretty_hostname; - char *icon_name; - char *chassis; - char *deployment; - char *location; - char *kernel_name; - char *kernel_release; - char *os_pretty_name; - char *os_cpe_name; - char *virtualization; - char *architecture; -} StatusInfo; - -static void print_status_info(StatusInfo *i) { - sd_id128_t mid = {}, bid = {}; - int r; - - assert(i); - - printf(" Static hostname: %s\n", strna(i->static_hostname)); - - if (!isempty(i->pretty_hostname) && - !streq_ptr(i->pretty_hostname, i->static_hostname)) - printf(" Pretty hostname: %s\n", i->pretty_hostname); - - if (!isempty(i->hostname) && - !streq_ptr(i->hostname, i->static_hostname)) - printf("Transient hostname: %s\n", i->hostname); - - if (!isempty(i->icon_name)) - printf(" Icon name: %s\n", - strna(i->icon_name)); - - if (!isempty(i->chassis)) - printf(" Chassis: %s\n", - strna(i->chassis)); - - if (!isempty(i->deployment)) - printf(" Deployment: %s\n", i->deployment); - - if (!isempty(i->location)) - printf(" Location: %s\n", i->location); - - r = sd_id128_get_machine(&mid); - if (r >= 0) - printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(mid)); - - r = sd_id128_get_boot(&bid); - if (r >= 0) - printf(" Boot ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(bid)); - - if (!isempty(i->virtualization)) - printf(" Virtualization: %s\n", i->virtualization); - - if (!isempty(i->os_pretty_name)) - printf(" Operating System: %s\n", i->os_pretty_name); - - if (!isempty(i->os_cpe_name)) - printf(" CPE OS Name: %s\n", i->os_cpe_name); - - if (!isempty(i->kernel_name) && !isempty(i->kernel_release)) - printf(" Kernel: %s %s\n", i->kernel_name, i->kernel_release); - - if (!isempty(i->architecture)) - printf(" Architecture: %s\n", i->architecture); - -} - -static int show_one_name(sd_bus *bus, const char* attr) { - _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 *s; - int r; - - r = sd_bus_get_property( - bus, - "org.freedesktop.hostname1", - "/org/freedesktop/hostname1", - "org.freedesktop.hostname1", - attr, - &error, &reply, "s"); - if (r < 0) { - log_error("Could not get property: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "s", &s); - if (r < 0) - return bus_log_parse_error(r); - - printf("%s\n", s); - - return 0; -} - -static int show_all_names(sd_bus *bus) { - StatusInfo info = {}; - - static const struct bus_properties_map hostname_map[] = { - { "Hostname", "s", NULL, offsetof(StatusInfo, hostname) }, - { "StaticHostname", "s", NULL, offsetof(StatusInfo, static_hostname) }, - { "PrettyHostname", "s", NULL, offsetof(StatusInfo, pretty_hostname) }, - { "IconName", "s", NULL, offsetof(StatusInfo, icon_name) }, - { "Chassis", "s", NULL, offsetof(StatusInfo, chassis) }, - { "Deployment", "s", NULL, offsetof(StatusInfo, deployment) }, - { "Location", "s", NULL, offsetof(StatusInfo, location) }, - { "KernelName", "s", NULL, offsetof(StatusInfo, kernel_name) }, - { "KernelRelease", "s", NULL, offsetof(StatusInfo, kernel_release) }, - { "OperatingSystemPrettyName", "s", NULL, offsetof(StatusInfo, os_pretty_name) }, - { "OperatingSystemCPEName", "s", NULL, offsetof(StatusInfo, os_cpe_name) }, - {} - }; - - static const struct bus_properties_map manager_map[] = { - { "Virtualization", "s", NULL, offsetof(StatusInfo, virtualization) }, - { "Architecture", "s", NULL, offsetof(StatusInfo, architecture) }, - {} - }; - - int r; - - r = bus_map_all_properties(bus, - "org.freedesktop.hostname1", - "/org/freedesktop/hostname1", - hostname_map, - &info); - if (r < 0) - goto fail; - - bus_map_all_properties(bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - manager_map, - &info); - - print_status_info(&info); - -fail: - free(info.hostname); - free(info.static_hostname); - free(info.pretty_hostname); - free(info.icon_name); - free(info.chassis); - free(info.deployment); - free(info.location); - free(info.kernel_name); - free(info.kernel_release); - free(info.os_pretty_name); - free(info.os_cpe_name); - free(info.virtualization); - free(info.architecture); - - return r; -} - -static int show_status(sd_bus *bus, char **args, unsigned n) { - assert(args); - - if (arg_pretty || arg_static || arg_transient) { - const char *attr; - - if (!!arg_static + !!arg_pretty + !!arg_transient > 1) { - log_error("Cannot query more than one name type at a time"); - return -EINVAL; - } - - attr = arg_pretty ? "PrettyHostname" : - arg_static ? "StaticHostname" : "Hostname"; - - return show_one_name(bus, attr); - } else - return show_all_names(bus); -} - -static int set_simple_string(sd_bus *bus, const char *method, const char *value) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r = 0; - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.hostname1", - "/org/freedesktop/hostname1", - "org.freedesktop.hostname1", - method, - &error, NULL, - "sb", value, arg_ask_password); - if (r < 0) - log_error("Could not set property: %s", bus_error_message(&error, -r)); - return r; -} - -static int set_hostname(sd_bus *bus, char **args, unsigned n) { - _cleanup_free_ char *h = NULL; - char *hostname = args[1]; - int r; - - assert(args); - assert(n == 2); - - if (!arg_pretty && !arg_static && !arg_transient) - arg_pretty = arg_static = arg_transient = true; - - if (arg_pretty) { - const char *p; - - /* If the passed hostname is already valid, then - * assume the user doesn't know anything about pretty - * hostnames, so let's unset the pretty hostname, and - * just set the passed hostname as static/dynamic - * hostname. */ - - if (arg_static && hostname_is_valid(hostname, true)) { - p = ""; - /* maybe get rid of trailing dot */ - hostname = hostname_cleanup(hostname); - } else { - p = h = strdup(hostname); - if (!p) - return log_oom(); - - hostname_cleanup(hostname); - } - - r = set_simple_string(bus, "SetPrettyHostname", p); - if (r < 0) - return r; - } - - if (arg_static) { - r = set_simple_string(bus, "SetStaticHostname", hostname); - if (r < 0) - return r; - } - - if (arg_transient) { - r = set_simple_string(bus, "SetHostname", hostname); - if (r < 0) - return r; - } - - return 0; -} - -static int set_icon_name(sd_bus *bus, char **args, unsigned n) { - assert(args); - assert(n == 2); - - return set_simple_string(bus, "SetIconName", args[1]); -} - -static int set_chassis(sd_bus *bus, char **args, unsigned n) { - assert(args); - assert(n == 2); - - return set_simple_string(bus, "SetChassis", args[1]); -} - -static int set_deployment(sd_bus *bus, char **args, unsigned n) { - assert(args); - assert(n == 2); - - return set_simple_string(bus, "SetDeployment", args[1]); -} - -static int set_location(sd_bus *bus, char **args, unsigned n) { - assert(args); - assert(n == 2); - - return set_simple_string(bus, "SetLocation", args[1]); -} - -static void help(void) { - printf("%s [OPTIONS...] COMMAND ...\n\n" - "Query or change system hostname.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --transient Only set transient hostname\n" - " --static Only set static hostname\n" - " --pretty Only set pretty hostname\n\n" - "Commands:\n" - " status Show current hostname settings\n" - " set-hostname NAME Set system hostname\n" - " set-icon-name NAME Set icon name for host\n" - " set-chassis NAME Set chassis type for host\n" - " set-deployment NAME Set deployment environment for host\n" - " set-location NAME Set location for host\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_ASK_PASSWORD, - ARG_TRANSIENT, - ARG_STATIC, - ARG_PRETTY - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "transient", no_argument, NULL, ARG_TRANSIENT }, - { "static", no_argument, NULL, ARG_STATIC }, - { "pretty", no_argument, NULL, ARG_PRETTY }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - {} - }; - - int 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 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_TRANSIENT: - arg_transient = true; - break; - - case ARG_PRETTY: - arg_pretty = true; - break; - - case ARG_STATIC: - arg_static = true; - break; - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int hostnamectl_main(sd_bus *bus, int argc, char *argv[]) { - - static const struct { - const char* verb; - const enum { - MORE, - LESS, - EQUAL - } argc_cmp; - const int argc; - int (* const dispatch)(sd_bus *bus, char **args, unsigned n); - } verbs[] = { - { "status", LESS, 1, show_status }, - { "set-hostname", EQUAL, 2, set_hostname }, - { "set-icon-name", EQUAL, 2, set_icon_name }, - { "set-chassis", EQUAL, 2, set_chassis }, - { "set-deployment", EQUAL, 2, set_deployment }, - { "set-location", EQUAL, 2, set_location }, - }; - - int left; - unsigned i; - - assert(argc >= 0); - assert(argv); - - left = argc - optind; - - if (left <= 0) - /* Special rule: no arguments means "status" */ - i = 0; - else { - if (streq(argv[optind], "help")) { - help(); - return 0; - } - - for (i = 0; i < ELEMENTSOF(verbs); i++) - if (streq(argv[optind], verbs[i].verb)) - break; - - if (i >= ELEMENTSOF(verbs)) { - log_error("Unknown operation %s", argv[optind]); - return -EINVAL; - } - } - - switch (verbs[i].argc_cmp) { - - case EQUAL: - if (left != verbs[i].argc) { - log_error("Invalid number of arguments."); - return -EINVAL; - } - - break; - - case MORE: - if (left < verbs[i].argc) { - log_error("Too few arguments."); - return -EINVAL; - } - - break; - - case LESS: - if (left > verbs[i].argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - break; - - default: - assert_not_reached("Unknown comparison operator."); - } - - return verbs[i].dispatch(bus, argv + optind, left); -} - -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; - } - - r = hostnamectl_main(bus, argc, argv); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/grp-hostname/hostnamectl/Makefile b/src/grp-hostname/hostnamectl/Makefile new file mode 100644 index 0000000000..f684bde523 --- /dev/null +++ b/src/grp-hostname/hostnamectl/Makefile @@ -0,0 +1,45 @@ +# -*- 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_HOSTNAMED),) + +hostnamectl_SOURCES = \ + src/hostname/hostnamectl.c + +hostnamectl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + hostnamectl + +dist_bashcompletion_data += \ + shell-completion/bash/hostnamectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_hostnamectl + +endif + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-hostname/hostnamectl/hostnamectl.c b/src/grp-hostname/hostnamectl/hostnamectl.c new file mode 100644 index 0000000000..8a24813934 --- /dev/null +++ b/src/grp-hostname/hostnamectl/hostnamectl.c @@ -0,0 +1,529 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include + +#include +#include + +#include "basic/alloc-util.h" +#include "basic/architecture.h" +#include "basic/hostname-util.h" +#include "basic/util.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-util.h" +#include "shared/spawn-polkit-agent.h" + +static bool arg_ask_password = true; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_transient = false; +static bool arg_pretty = false; +static bool arg_static = 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(); +} + +typedef struct StatusInfo { + char *hostname; + char *static_hostname; + char *pretty_hostname; + char *icon_name; + char *chassis; + char *deployment; + char *location; + char *kernel_name; + char *kernel_release; + char *os_pretty_name; + char *os_cpe_name; + char *virtualization; + char *architecture; +} StatusInfo; + +static void print_status_info(StatusInfo *i) { + sd_id128_t mid = {}, bid = {}; + int r; + + assert(i); + + printf(" Static hostname: %s\n", strna(i->static_hostname)); + + if (!isempty(i->pretty_hostname) && + !streq_ptr(i->pretty_hostname, i->static_hostname)) + printf(" Pretty hostname: %s\n", i->pretty_hostname); + + if (!isempty(i->hostname) && + !streq_ptr(i->hostname, i->static_hostname)) + printf("Transient hostname: %s\n", i->hostname); + + if (!isempty(i->icon_name)) + printf(" Icon name: %s\n", + strna(i->icon_name)); + + if (!isempty(i->chassis)) + printf(" Chassis: %s\n", + strna(i->chassis)); + + if (!isempty(i->deployment)) + printf(" Deployment: %s\n", i->deployment); + + if (!isempty(i->location)) + printf(" Location: %s\n", i->location); + + r = sd_id128_get_machine(&mid); + if (r >= 0) + printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(mid)); + + r = sd_id128_get_boot(&bid); + if (r >= 0) + printf(" Boot ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(bid)); + + if (!isempty(i->virtualization)) + printf(" Virtualization: %s\n", i->virtualization); + + if (!isempty(i->os_pretty_name)) + printf(" Operating System: %s\n", i->os_pretty_name); + + if (!isempty(i->os_cpe_name)) + printf(" CPE OS Name: %s\n", i->os_cpe_name); + + if (!isempty(i->kernel_name) && !isempty(i->kernel_release)) + printf(" Kernel: %s %s\n", i->kernel_name, i->kernel_release); + + if (!isempty(i->architecture)) + printf(" Architecture: %s\n", i->architecture); + +} + +static int show_one_name(sd_bus *bus, const char* attr) { + _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 *s; + int r; + + r = sd_bus_get_property( + bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + attr, + &error, &reply, "s"); + if (r < 0) { + log_error("Could not get property: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &s); + if (r < 0) + return bus_log_parse_error(r); + + printf("%s\n", s); + + return 0; +} + +static int show_all_names(sd_bus *bus) { + StatusInfo info = {}; + + static const struct bus_properties_map hostname_map[] = { + { "Hostname", "s", NULL, offsetof(StatusInfo, hostname) }, + { "StaticHostname", "s", NULL, offsetof(StatusInfo, static_hostname) }, + { "PrettyHostname", "s", NULL, offsetof(StatusInfo, pretty_hostname) }, + { "IconName", "s", NULL, offsetof(StatusInfo, icon_name) }, + { "Chassis", "s", NULL, offsetof(StatusInfo, chassis) }, + { "Deployment", "s", NULL, offsetof(StatusInfo, deployment) }, + { "Location", "s", NULL, offsetof(StatusInfo, location) }, + { "KernelName", "s", NULL, offsetof(StatusInfo, kernel_name) }, + { "KernelRelease", "s", NULL, offsetof(StatusInfo, kernel_release) }, + { "OperatingSystemPrettyName", "s", NULL, offsetof(StatusInfo, os_pretty_name) }, + { "OperatingSystemCPEName", "s", NULL, offsetof(StatusInfo, os_cpe_name) }, + {} + }; + + static const struct bus_properties_map manager_map[] = { + { "Virtualization", "s", NULL, offsetof(StatusInfo, virtualization) }, + { "Architecture", "s", NULL, offsetof(StatusInfo, architecture) }, + {} + }; + + int r; + + r = bus_map_all_properties(bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + hostname_map, + &info); + if (r < 0) + goto fail; + + bus_map_all_properties(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + manager_map, + &info); + + print_status_info(&info); + +fail: + free(info.hostname); + free(info.static_hostname); + free(info.pretty_hostname); + free(info.icon_name); + free(info.chassis); + free(info.deployment); + free(info.location); + free(info.kernel_name); + free(info.kernel_release); + free(info.os_pretty_name); + free(info.os_cpe_name); + free(info.virtualization); + free(info.architecture); + + return r; +} + +static int show_status(sd_bus *bus, char **args, unsigned n) { + assert(args); + + if (arg_pretty || arg_static || arg_transient) { + const char *attr; + + if (!!arg_static + !!arg_pretty + !!arg_transient > 1) { + log_error("Cannot query more than one name type at a time"); + return -EINVAL; + } + + attr = arg_pretty ? "PrettyHostname" : + arg_static ? "StaticHostname" : "Hostname"; + + return show_one_name(bus, attr); + } else + return show_all_names(bus); +} + +static int set_simple_string(sd_bus *bus, const char *method, const char *value) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r = 0; + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + method, + &error, NULL, + "sb", value, arg_ask_password); + if (r < 0) + log_error("Could not set property: %s", bus_error_message(&error, -r)); + return r; +} + +static int set_hostname(sd_bus *bus, char **args, unsigned n) { + _cleanup_free_ char *h = NULL; + char *hostname = args[1]; + int r; + + assert(args); + assert(n == 2); + + if (!arg_pretty && !arg_static && !arg_transient) + arg_pretty = arg_static = arg_transient = true; + + if (arg_pretty) { + const char *p; + + /* If the passed hostname is already valid, then + * assume the user doesn't know anything about pretty + * hostnames, so let's unset the pretty hostname, and + * just set the passed hostname as static/dynamic + * hostname. */ + + if (arg_static && hostname_is_valid(hostname, true)) { + p = ""; + /* maybe get rid of trailing dot */ + hostname = hostname_cleanup(hostname); + } else { + p = h = strdup(hostname); + if (!p) + return log_oom(); + + hostname_cleanup(hostname); + } + + r = set_simple_string(bus, "SetPrettyHostname", p); + if (r < 0) + return r; + } + + if (arg_static) { + r = set_simple_string(bus, "SetStaticHostname", hostname); + if (r < 0) + return r; + } + + if (arg_transient) { + r = set_simple_string(bus, "SetHostname", hostname); + if (r < 0) + return r; + } + + return 0; +} + +static int set_icon_name(sd_bus *bus, char **args, unsigned n) { + assert(args); + assert(n == 2); + + return set_simple_string(bus, "SetIconName", args[1]); +} + +static int set_chassis(sd_bus *bus, char **args, unsigned n) { + assert(args); + assert(n == 2); + + return set_simple_string(bus, "SetChassis", args[1]); +} + +static int set_deployment(sd_bus *bus, char **args, unsigned n) { + assert(args); + assert(n == 2); + + return set_simple_string(bus, "SetDeployment", args[1]); +} + +static int set_location(sd_bus *bus, char **args, unsigned n) { + assert(args); + assert(n == 2); + + return set_simple_string(bus, "SetLocation", args[1]); +} + +static void help(void) { + printf("%s [OPTIONS...] COMMAND ...\n\n" + "Query or change system hostname.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-ask-password Do not prompt for password\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --transient Only set transient hostname\n" + " --static Only set static hostname\n" + " --pretty Only set pretty hostname\n\n" + "Commands:\n" + " status Show current hostname settings\n" + " set-hostname NAME Set system hostname\n" + " set-icon-name NAME Set icon name for host\n" + " set-chassis NAME Set chassis type for host\n" + " set-deployment NAME Set deployment environment for host\n" + " set-location NAME Set location for host\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_ASK_PASSWORD, + ARG_TRANSIENT, + ARG_STATIC, + ARG_PRETTY + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "transient", no_argument, NULL, ARG_TRANSIENT }, + { "static", no_argument, NULL, ARG_STATIC }, + { "pretty", no_argument, NULL, ARG_PRETTY }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + {} + }; + + int 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 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_TRANSIENT: + arg_transient = true; + break; + + case ARG_PRETTY: + arg_pretty = true; + break; + + case ARG_STATIC: + arg_static = true; + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int hostnamectl_main(sd_bus *bus, int argc, char *argv[]) { + + static const struct { + const char* verb; + const enum { + MORE, + LESS, + EQUAL + } argc_cmp; + const int argc; + int (* const dispatch)(sd_bus *bus, char **args, unsigned n); + } verbs[] = { + { "status", LESS, 1, show_status }, + { "set-hostname", EQUAL, 2, set_hostname }, + { "set-icon-name", EQUAL, 2, set_icon_name }, + { "set-chassis", EQUAL, 2, set_chassis }, + { "set-deployment", EQUAL, 2, set_deployment }, + { "set-location", EQUAL, 2, set_location }, + }; + + int left; + unsigned i; + + assert(argc >= 0); + assert(argv); + + left = argc - optind; + + if (left <= 0) + /* Special rule: no arguments means "status" */ + i = 0; + else { + if (streq(argv[optind], "help")) { + help(); + return 0; + } + + for (i = 0; i < ELEMENTSOF(verbs); i++) + if (streq(argv[optind], verbs[i].verb)) + break; + + if (i >= ELEMENTSOF(verbs)) { + log_error("Unknown operation %s", argv[optind]); + return -EINVAL; + } + } + + switch (verbs[i].argc_cmp) { + + case EQUAL: + if (left != verbs[i].argc) { + log_error("Invalid number of arguments."); + return -EINVAL; + } + + break; + + case MORE: + if (left < verbs[i].argc) { + log_error("Too few arguments."); + return -EINVAL; + } + + break; + + case LESS: + if (left > verbs[i].argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + break; + + default: + assert_not_reached("Unknown comparison operator."); + } + + return verbs[i].dispatch(bus, argv + optind, left); +} + +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; + } + + r = hostnamectl_main(bus, argc, argv); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-hostname/hostnamectl/hostnamectl.completion.bash b/src/grp-hostname/hostnamectl/hostnamectl.completion.bash new file mode 100644 index 0000000000..6a252188ea --- /dev/null +++ b/src/grp-hostname/hostnamectl/hostnamectl.completion.bash @@ -0,0 +1,64 @@ +# hostnamectl(1) completion -*- shell-script -*- +# +# This file is part of systemd. +# +# Copyright 2010 Ran Benita +# +# 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 +# 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 . + +__contains_word () { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +_hostnamectl() { + local i verb comps + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + local OPTS='-h --help --version --transient --static --pretty + --no-ask-password -H --host --machine' + + if [[ $cur = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + local -A VERBS=( + [STANDALONE]='status' + [ICONS]='set-icon-name' + [NAME]='set-hostname set-deployment set-location' + [CHASSIS]='set-chassis' + ) + + for ((i=0; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]}; then + verb=${COMP_WORDS[i]} + break + fi + done + + if [[ -z $verb ]]; then + comps=${VERBS[*]} + elif __contains_word "$verb" ${VERBS[CHASSIS]}; then + comps='desktop laptop server tablet handset watch embedded vm container' + elif __contains_word "$verb" ${VERBS[STANDALONE]} ${VERBS[ICONS]} ${VERBS[NAME]}; then + comps='' + fi + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _hostnamectl hostnamectl diff --git a/src/grp-hostname/hostnamectl/hostnamectl.completion.zsh b/src/grp-hostname/hostnamectl/hostnamectl.completion.zsh new file mode 100644 index 0000000000..7528e0649d --- /dev/null +++ b/src/grp-hostname/hostnamectl/hostnamectl.completion.zsh @@ -0,0 +1,80 @@ +#compdef hostnamectl + +_hostnamectl_set-hostname() { + if (( CURRENT <= 3 )); then + _message "new hostname" + else + _message "no more options" + fi +} + +_hostnamectl_set-icon-name() { + if (( CURRENT <= 3 )); then + _message "new icon name" + else + _message "no more options" + fi +} + +_hostnamectl_set-chassis() { + if (( CURRENT <= 3 )); then + _chassis=( desktop laptop server tablet handset watch embedded vm container ) + _describe chassis _chassis + else + _message "no more options" + fi +} + +_hostnamectl_set-deployment() { + if (( CURRENT <= 3 )); then + _message "new environment" + else + _message "no more options" + fi +} + +_hostnamectl_set-location() { + if (( CURRENT <= 3 )); then + _message "new location" + else + _message "no more options" + fi +} + +_hostnamectl_command() { + local -a _hostnamectl_cmds + _hostnamectl_cmds=( + "status:Show current hostname settings" + "set-hostname:Set system hostname" + "set-icon-name:Set icon name for host" + "set-chassis:Set chassis type for host" + "set-deployment:Set deployment environment for host" + "set-location:Set location for host" + ) + if (( CURRENT == 1 )); then + _describe -t commands 'hostnamectl commands' _hostnamectl_cmds || compadd "$@" + else + local curcontext="$curcontext" + cmd="${${_hostnamectl_cmds[(r)$words[1]:*]%%:*}}" + if (( $#cmd )); then + if [[ $cmd == status ]]; then + _message "no options" + else + _hostnamectl_$cmd + fi + else + _message "unknown hostnamectl command: $words[1]" + fi + fi +} + +_arguments -s \ + {-h,--help}'[Show this help]' \ + '--version[Show package version]' \ + '--transient[Only set transient hostname]' \ + '--static[Only set static hostname]' \ + '--pretty[Only set pretty hostname]' \ + '--no-ask-password[Do not prompt for password]' \ + {-H+,--host=}'[Operate on remote host]:userathost:_sd_hosts_or_user_at_host' \ + {-M+,--machine=}'[Operate on local container]:machines:_sd_machines' \ + '*::hostnamectl commands:_hostnamectl_command' diff --git a/src/grp-hostname/hostnamed.c b/src/grp-hostname/hostnamed.c deleted file mode 100644 index 22eabc469b..0000000000 --- a/src/grp-hostname/hostnamed.c +++ /dev/null @@ -1,745 +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 - -#include "basic/alloc-util.h" -#include "basic/def.h" -#include "basic/env-util.h" -#include "basic/fileio-label.h" -#include "basic/hostname-util.h" -#include "basic/parse-util.h" -#include "basic/path-util.h" -#include "basic/selinux-util.h" -#include "basic/strv.h" -#include "basic/user-util.h" -#include "basic/util.h" -#include "basic/virt.h" -#include "shared/bus-util.h" - -#define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:") - -enum { - PROP_HOSTNAME, - PROP_STATIC_HOSTNAME, - PROP_PRETTY_HOSTNAME, - PROP_ICON_NAME, - PROP_CHASSIS, - PROP_DEPLOYMENT, - PROP_LOCATION, - PROP_KERNEL_NAME, - PROP_KERNEL_RELEASE, - PROP_KERNEL_VERSION, - PROP_OS_PRETTY_NAME, - PROP_OS_CPE_NAME, - _PROP_MAX -}; - -typedef struct Context { - char *data[_PROP_MAX]; - Hashmap *polkit_registry; -} Context; - -static void context_reset(Context *c) { - int p; - - assert(c); - - for (p = 0; p < _PROP_MAX; p++) - c->data[p] = mfree(c->data[p]); -} - -static void context_free(Context *c) { - assert(c); - - context_reset(c); - bus_verify_polkit_async_registry_free(c->polkit_registry); -} - -static int context_read_data(Context *c) { - int r; - struct utsname u; - - assert(c); - - context_reset(c); - - assert_se(uname(&u) >= 0); - c->data[PROP_KERNEL_NAME] = strdup(u.sysname); - c->data[PROP_KERNEL_RELEASE] = strdup(u.release); - c->data[PROP_KERNEL_VERSION] = strdup(u.version); - if (!c->data[PROP_KERNEL_NAME] || !c->data[PROP_KERNEL_RELEASE] || - !c->data[PROP_KERNEL_VERSION]) - return -ENOMEM; - - c->data[PROP_HOSTNAME] = gethostname_malloc(); - if (!c->data[PROP_HOSTNAME]) - return -ENOMEM; - - r = read_hostname_config("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]); - if (r < 0 && r != -ENOENT) - return r; - - r = parse_env_file("/etc/machine-info", NEWLINE, - "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME], - "ICON_NAME", &c->data[PROP_ICON_NAME], - "CHASSIS", &c->data[PROP_CHASSIS], - "DEPLOYMENT", &c->data[PROP_DEPLOYMENT], - "LOCATION", &c->data[PROP_LOCATION], - NULL); - if (r < 0 && r != -ENOENT) - return r; - - r = parse_env_file("/etc/os-release", NEWLINE, - "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME], - "CPE_NAME", &c->data[PROP_OS_CPE_NAME], - NULL); - if (r == -ENOENT) - r = parse_env_file("/usr/lib/os-release", NEWLINE, - "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME], - "CPE_NAME", &c->data[PROP_OS_CPE_NAME], - NULL); - - if (r < 0 && r != -ENOENT) - return r; - - return 0; -} - -static bool valid_chassis(const char *chassis) { - assert(chassis); - - return nulstr_contains( - "vm\0" - "container\0" - "desktop\0" - "laptop\0" - "server\0" - "tablet\0" - "handset\0" - "watch\0" - "embedded\0", - chassis); -} - -static bool valid_deployment(const char *deployment) { - assert(deployment); - - return in_charset(deployment, VALID_DEPLOYMENT_CHARS); -} - -static const char* fallback_chassis(void) { - int r; - char *type; - unsigned t; - int v; - - v = detect_virtualization(); - - if (VIRTUALIZATION_IS_VM(v)) - return "vm"; - if (VIRTUALIZATION_IS_CONTAINER(v)) - return "container"; - - r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type); - if (r < 0) - goto try_dmi; - - r = safe_atou(type, &t); - free(type); - if (r < 0) - goto try_dmi; - - /* We only list the really obvious cases here as the ACPI data - * is not really super reliable. - * - * See the ACPI 5.0 Spec Section 5.2.9.1 for details: - * - * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf - */ - - switch(t) { - - case 1: - case 3: - case 6: - return "desktop"; - - case 2: - return "laptop"; - - case 4: - case 5: - case 7: - return "server"; - - case 8: - return "tablet"; - } - -try_dmi: - r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type); - if (r < 0) - return NULL; - - r = safe_atou(type, &t); - free(type); - if (r < 0) - return NULL; - - /* We only list the really obvious cases here. The DMI data is - unreliable enough, so let's not do any additional guesswork - on top of that. - - See the SMBIOS Specification 3.0 section 7.4.1 for - details about the values listed here: - - https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf - */ - - switch (t) { - - case 0x3: - case 0x4: - case 0x6: - case 0x7: - return "desktop"; - - case 0x8: - case 0x9: - case 0xA: - case 0xE: - return "laptop"; - - case 0xB: - return "handset"; - - case 0x11: - case 0x1C: - case 0x1D: - return "server"; - - case 0x1E: - return "tablet"; - } - - return NULL; -} - -static char* context_fallback_icon_name(Context *c) { - const char *chassis; - - assert(c); - - if (!isempty(c->data[PROP_CHASSIS])) - return strappend("computer-", c->data[PROP_CHASSIS]); - - chassis = fallback_chassis(); - if (chassis) - return strappend("computer-", chassis); - - return strdup("computer"); -} - - -static bool hostname_is_useful(const char *hn) { - return !isempty(hn) && !is_localhost(hn); -} - -static int context_update_kernel_hostname(Context *c) { - const char *static_hn; - const char *hn; - - assert(c); - - static_hn = c->data[PROP_STATIC_HOSTNAME]; - - /* /etc/hostname with something other than "localhost" - * has the highest preference ... */ - if (hostname_is_useful(static_hn)) - hn = static_hn; - - /* ... the transient host name, (ie: DHCP) comes next ... */ - else if (!isempty(c->data[PROP_HOSTNAME])) - hn = c->data[PROP_HOSTNAME]; - - /* ... fallback to static "localhost.*" ignored above ... */ - else if (!isempty(static_hn)) - hn = static_hn; - - /* ... and the ultimate fallback */ - else - hn = "localhost"; - - if (sethostname_idempotent(hn) < 0) - return -errno; - - return 0; -} - -static int context_write_data_static_hostname(Context *c) { - - assert(c); - - if (isempty(c->data[PROP_STATIC_HOSTNAME])) { - - if (unlink("/etc/hostname") < 0) - return errno == ENOENT ? 0 : -errno; - - return 0; - } - return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]); -} - -static int context_write_data_machine_info(Context *c) { - - static const char * const name[_PROP_MAX] = { - [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME", - [PROP_ICON_NAME] = "ICON_NAME", - [PROP_CHASSIS] = "CHASSIS", - [PROP_DEPLOYMENT] = "DEPLOYMENT", - [PROP_LOCATION] = "LOCATION", - }; - - _cleanup_strv_free_ char **l = NULL; - int r, p; - - assert(c); - - r = load_env_file(NULL, "/etc/machine-info", NULL, &l); - if (r < 0 && r != -ENOENT) - return r; - - for (p = PROP_PRETTY_HOSTNAME; p <= PROP_LOCATION; p++) { - _cleanup_free_ char *t = NULL; - char **u; - - assert(name[p]); - - if (isempty(c->data[p])) { - strv_env_unset(l, name[p]); - continue; - } - - t = strjoin(name[p], "=", c->data[p], NULL); - if (!t) - return -ENOMEM; - - u = strv_env_set(l, t); - if (!u) - return -ENOMEM; - - strv_free(l); - l = u; - } - - if (strv_isempty(l)) { - if (unlink("/etc/machine-info") < 0) - return errno == ENOENT ? 0 : -errno; - - return 0; - } - - return write_env_file_label("/etc/machine-info", l); -} - -static int property_get_icon_name( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_free_ char *n = NULL; - Context *c = userdata; - const char *name; - - if (isempty(c->data[PROP_ICON_NAME])) - name = n = context_fallback_icon_name(c); - else - name = c->data[PROP_ICON_NAME]; - - if (!name) - return -ENOMEM; - - return sd_bus_message_append(reply, "s", name); -} - -static int property_get_chassis( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Context *c = userdata; - const char *name; - - if (isempty(c->data[PROP_CHASSIS])) - name = fallback_chassis(); - else - name = c->data[PROP_CHASSIS]; - - return sd_bus_message_append(reply, "s", name); -} - -static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) { - Context *c = userdata; - const char *name; - int interactive; - char *h; - int r; - - assert(m); - assert(c); - - r = sd_bus_message_read(m, "sb", &name, &interactive); - if (r < 0) - return r; - - if (isempty(name)) - name = c->data[PROP_STATIC_HOSTNAME]; - - if (isempty(name)) - name = "localhost"; - - if (!hostname_is_valid(name, false)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name); - - if (streq_ptr(name, c->data[PROP_HOSTNAME])) - return sd_bus_reply_method_return(m, NULL); - - r = bus_verify_polkit_async( - m, - CAP_SYS_ADMIN, - "org.freedesktop.hostname1.set-hostname", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - h = strdup(name); - if (!h) - return -ENOMEM; - - free(c->data[PROP_HOSTNAME]); - c->data[PROP_HOSTNAME] = h; - - r = context_update_kernel_hostname(c); - if (r < 0) { - log_error_errno(r, "Failed to set host name: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r)); - } - - log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME])); - - (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL); - - return sd_bus_reply_method_return(m, NULL); -} - -static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) { - Context *c = userdata; - const char *name; - int interactive; - int r; - - assert(m); - assert(c); - - r = sd_bus_message_read(m, "sb", &name, &interactive); - if (r < 0) - return r; - - if (isempty(name)) - name = NULL; - - if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME])) - return sd_bus_reply_method_return(m, NULL); - - r = bus_verify_polkit_async( - m, - CAP_SYS_ADMIN, - "org.freedesktop.hostname1.set-static-hostname", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - if (isempty(name)) { - c->data[PROP_STATIC_HOSTNAME] = mfree(c->data[PROP_STATIC_HOSTNAME]); - } else { - char *h; - - if (!hostname_is_valid(name, false)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name); - - h = strdup(name); - if (!h) - return -ENOMEM; - - free(c->data[PROP_STATIC_HOSTNAME]); - c->data[PROP_STATIC_HOSTNAME] = h; - } - - r = context_update_kernel_hostname(c); - if (r < 0) { - log_error_errno(r, "Failed to set host name: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r)); - } - - r = context_write_data_static_hostname(c); - if (r < 0) { - log_error_errno(r, "Failed to write static host name: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r)); - } - - log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME])); - - (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL); - - return sd_bus_reply_method_return(m, NULL); -} - -static int set_machine_info(Context *c, sd_bus_message *m, int prop, sd_bus_message_handler_t cb, sd_bus_error *error) { - int interactive; - const char *name; - int r; - - assert(c); - assert(m); - - r = sd_bus_message_read(m, "sb", &name, &interactive); - if (r < 0) - return r; - - if (isempty(name)) - name = NULL; - - if (streq_ptr(name, c->data[prop])) - return sd_bus_reply_method_return(m, NULL); - - /* Since the pretty hostname should always be changed at the - * same time as the static one, use the same policy action for - * both... */ - - r = bus_verify_polkit_async( - m, - CAP_SYS_ADMIN, - prop == PROP_PRETTY_HOSTNAME ? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - if (isempty(name)) { - c->data[prop] = mfree(c->data[prop]); - } else { - char *h; - - /* The icon name might ultimately be used as file - * name, so better be safe than sorry */ - - if (prop == PROP_ICON_NAME && !filename_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name); - if (prop == PROP_PRETTY_HOSTNAME && string_has_cc(name, NULL)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name); - if (prop == PROP_CHASSIS && !valid_chassis(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name); - if (prop == PROP_DEPLOYMENT && !valid_deployment(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid deployment '%s'", name); - if (prop == PROP_LOCATION && string_has_cc(name, NULL)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid location '%s'", name); - - h = strdup(name); - if (!h) - return -ENOMEM; - - free(c->data[prop]); - c->data[prop] = h; - } - - r = context_write_data_machine_info(c); - if (r < 0) { - log_error_errno(r, "Failed to write machine info: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r)); - } - - log_info("Changed %s to '%s'", - prop == PROP_PRETTY_HOSTNAME ? "pretty host name" : - prop == PROP_DEPLOYMENT ? "deployment" : - prop == PROP_LOCATION ? "location" : - prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop])); - - (void) sd_bus_emit_properties_changed( - sd_bus_message_get_bus(m), - "/org/freedesktop/hostname1", - "org.freedesktop.hostname1", - prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" : - prop == PROP_DEPLOYMENT ? "Deployment" : - prop == PROP_LOCATION ? "Location" : - prop == PROP_CHASSIS ? "Chassis" : "IconName" , NULL); - - return sd_bus_reply_method_return(m, NULL); -} - -static int method_set_pretty_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) { - return set_machine_info(userdata, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error); -} - -static int method_set_icon_name(sd_bus_message *m, void *userdata, sd_bus_error *error) { - return set_machine_info(userdata, m, PROP_ICON_NAME, method_set_icon_name, error); -} - -static int method_set_chassis(sd_bus_message *m, void *userdata, sd_bus_error *error) { - return set_machine_info(userdata, m, PROP_CHASSIS, method_set_chassis, error); -} - -static int method_set_deployment(sd_bus_message *m, void *userdata, sd_bus_error *error) { - return set_machine_info(userdata, m, PROP_DEPLOYMENT, method_set_deployment, error); -} - -static int method_set_location(sd_bus_message *m, void *userdata, sd_bus_error *error) { - return set_machine_info(userdata, m, PROP_LOCATION, method_set_location, error); -} - -static const sd_bus_vtable hostname_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Deployment", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_DEPLOYMENT, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Location", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_LOCATION, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetDeployment", "sb", NULL, method_set_deployment, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetLocation", "sb", NULL, method_set_location, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END, -}; - -static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - assert(c); - assert(event); - assert(_bus); - - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to get system bus connection: %m"); - - r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c); - if (r < 0) - return log_error_errno(r, "Failed to register object: %m"); - - r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0); - if (r < 0) - return log_error_errno(r, "Failed to register name: %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"); - - *_bus = bus; - bus = NULL; - - return 0; -} - -int main(int argc, char *argv[]) { - Context context = {}; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - mac_selinux_init(); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto finish; - } - - r = sd_event_default(&event); - if (r < 0) { - log_error_errno(r, "Failed to allocate event loop: %m"); - goto finish; - } - - sd_event_set_watchdog(event, true); - - r = connect_bus(&context, event, &bus); - if (r < 0) - goto finish; - - r = context_read_data(&context); - if (r < 0) { - log_error_errno(r, "Failed to read hostname and machine information: %m"); - goto finish; - } - - r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } - -finish: - context_free(&context); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/grp-hostname/org.freedesktop.hostname1.conf b/src/grp-hostname/org.freedesktop.hostname1.conf deleted file mode 100644 index 46b4aadc83..0000000000 --- a/src/grp-hostname/org.freedesktop.hostname1.conf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/grp-hostname/org.freedesktop.hostname1.policy.in b/src/grp-hostname/org.freedesktop.hostname1.policy.in deleted file mode 100644 index c32c1d4fda..0000000000 --- a/src/grp-hostname/org.freedesktop.hostname1.policy.in +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - The systemd Project - http://www.freedesktop.org/wiki/Software/systemd - - - <_description>Set host name - <_message>Authentication is required to set the local host name. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - - - - <_description>Set static host name - <_message>Authentication is required to set the statically configured local host name, as well as the pretty host name. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - org.freedesktop.hostname1.set-hostname org.freedesktop.hostname1.set-machine-info - - - - <_description>Set machine information - <_message>Authentication is required to set local machine information. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - - - diff --git a/src/grp-hostname/org.freedesktop.hostname1.service b/src/grp-hostname/org.freedesktop.hostname1.service deleted file mode 100644 index 6041ed60ca..0000000000 --- a/src/grp-hostname/org.freedesktop.hostname1.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.hostname1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.hostname1.service diff --git a/src/grp-hostname/systemd-hostnamed/.gitignore b/src/grp-hostname/systemd-hostnamed/.gitignore new file mode 100644 index 0000000000..1ff281b231 --- /dev/null +++ b/src/grp-hostname/systemd-hostnamed/.gitignore @@ -0,0 +1 @@ +org.freedesktop.hostname1.policy diff --git a/src/grp-hostname/systemd-hostnamed/Makefile b/src/grp-hostname/systemd-hostnamed/Makefile new file mode 100644 index 0000000000..171bce55a4 --- /dev/null +++ b/src/grp-hostname/systemd-hostnamed/Makefile @@ -0,0 +1,65 @@ +# -*- 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_HOSTNAMED),) +systemd_hostnamed_SOURCES = \ + src/hostname/hostnamed.c + +systemd_hostnamed_LDADD = \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-hostnamed + +nodist_systemunit_DATA += \ + units/systemd-hostnamed.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.hostname1.busname + +dist_dbuspolicy_DATA += \ + src/hostname/org.freedesktop.hostname1.conf + +dist_dbussystemservice_DATA += \ + src/hostname/org.freedesktop.hostname1.service + +polkitpolicy_files += \ + src/hostname/org.freedesktop.hostname1.policy + +SYSTEM_UNIT_ALIASES += \ + systemd-hostnamed.service dbus-org.freedesktop.hostname1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.hostname1.busname + +endif # ENABLE_HOSTNAMED + +polkitpolicy_in_files += \ + src/hostname/org.freedesktop.hostname1.policy.in + +EXTRA_DIST += \ + units/systemd-hostnamed.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-hostname/systemd-hostnamed/hostnamed.c b/src/grp-hostname/systemd-hostnamed/hostnamed.c new file mode 100644 index 0000000000..22eabc469b --- /dev/null +++ b/src/grp-hostname/systemd-hostnamed/hostnamed.c @@ -0,0 +1,745 @@ +/*** + 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 + +#include "basic/alloc-util.h" +#include "basic/def.h" +#include "basic/env-util.h" +#include "basic/fileio-label.h" +#include "basic/hostname-util.h" +#include "basic/parse-util.h" +#include "basic/path-util.h" +#include "basic/selinux-util.h" +#include "basic/strv.h" +#include "basic/user-util.h" +#include "basic/util.h" +#include "basic/virt.h" +#include "shared/bus-util.h" + +#define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:") + +enum { + PROP_HOSTNAME, + PROP_STATIC_HOSTNAME, + PROP_PRETTY_HOSTNAME, + PROP_ICON_NAME, + PROP_CHASSIS, + PROP_DEPLOYMENT, + PROP_LOCATION, + PROP_KERNEL_NAME, + PROP_KERNEL_RELEASE, + PROP_KERNEL_VERSION, + PROP_OS_PRETTY_NAME, + PROP_OS_CPE_NAME, + _PROP_MAX +}; + +typedef struct Context { + char *data[_PROP_MAX]; + Hashmap *polkit_registry; +} Context; + +static void context_reset(Context *c) { + int p; + + assert(c); + + for (p = 0; p < _PROP_MAX; p++) + c->data[p] = mfree(c->data[p]); +} + +static void context_free(Context *c) { + assert(c); + + context_reset(c); + bus_verify_polkit_async_registry_free(c->polkit_registry); +} + +static int context_read_data(Context *c) { + int r; + struct utsname u; + + assert(c); + + context_reset(c); + + assert_se(uname(&u) >= 0); + c->data[PROP_KERNEL_NAME] = strdup(u.sysname); + c->data[PROP_KERNEL_RELEASE] = strdup(u.release); + c->data[PROP_KERNEL_VERSION] = strdup(u.version); + if (!c->data[PROP_KERNEL_NAME] || !c->data[PROP_KERNEL_RELEASE] || + !c->data[PROP_KERNEL_VERSION]) + return -ENOMEM; + + c->data[PROP_HOSTNAME] = gethostname_malloc(); + if (!c->data[PROP_HOSTNAME]) + return -ENOMEM; + + r = read_hostname_config("/etc/hostname", &c->data[PROP_STATIC_HOSTNAME]); + if (r < 0 && r != -ENOENT) + return r; + + r = parse_env_file("/etc/machine-info", NEWLINE, + "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME], + "ICON_NAME", &c->data[PROP_ICON_NAME], + "CHASSIS", &c->data[PROP_CHASSIS], + "DEPLOYMENT", &c->data[PROP_DEPLOYMENT], + "LOCATION", &c->data[PROP_LOCATION], + NULL); + if (r < 0 && r != -ENOENT) + return r; + + r = parse_env_file("/etc/os-release", NEWLINE, + "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME], + "CPE_NAME", &c->data[PROP_OS_CPE_NAME], + NULL); + if (r == -ENOENT) + r = parse_env_file("/usr/lib/os-release", NEWLINE, + "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME], + "CPE_NAME", &c->data[PROP_OS_CPE_NAME], + NULL); + + if (r < 0 && r != -ENOENT) + return r; + + return 0; +} + +static bool valid_chassis(const char *chassis) { + assert(chassis); + + return nulstr_contains( + "vm\0" + "container\0" + "desktop\0" + "laptop\0" + "server\0" + "tablet\0" + "handset\0" + "watch\0" + "embedded\0", + chassis); +} + +static bool valid_deployment(const char *deployment) { + assert(deployment); + + return in_charset(deployment, VALID_DEPLOYMENT_CHARS); +} + +static const char* fallback_chassis(void) { + int r; + char *type; + unsigned t; + int v; + + v = detect_virtualization(); + + if (VIRTUALIZATION_IS_VM(v)) + return "vm"; + if (VIRTUALIZATION_IS_CONTAINER(v)) + return "container"; + + r = read_one_line_file("/sys/firmware/acpi/pm_profile", &type); + if (r < 0) + goto try_dmi; + + r = safe_atou(type, &t); + free(type); + if (r < 0) + goto try_dmi; + + /* We only list the really obvious cases here as the ACPI data + * is not really super reliable. + * + * See the ACPI 5.0 Spec Section 5.2.9.1 for details: + * + * http://www.acpi.info/DOWNLOADS/ACPIspec50.pdf + */ + + switch(t) { + + case 1: + case 3: + case 6: + return "desktop"; + + case 2: + return "laptop"; + + case 4: + case 5: + case 7: + return "server"; + + case 8: + return "tablet"; + } + +try_dmi: + r = read_one_line_file("/sys/class/dmi/id/chassis_type", &type); + if (r < 0) + return NULL; + + r = safe_atou(type, &t); + free(type); + if (r < 0) + return NULL; + + /* We only list the really obvious cases here. The DMI data is + unreliable enough, so let's not do any additional guesswork + on top of that. + + See the SMBIOS Specification 3.0 section 7.4.1 for + details about the values listed here: + + https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf + */ + + switch (t) { + + case 0x3: + case 0x4: + case 0x6: + case 0x7: + return "desktop"; + + case 0x8: + case 0x9: + case 0xA: + case 0xE: + return "laptop"; + + case 0xB: + return "handset"; + + case 0x11: + case 0x1C: + case 0x1D: + return "server"; + + case 0x1E: + return "tablet"; + } + + return NULL; +} + +static char* context_fallback_icon_name(Context *c) { + const char *chassis; + + assert(c); + + if (!isempty(c->data[PROP_CHASSIS])) + return strappend("computer-", c->data[PROP_CHASSIS]); + + chassis = fallback_chassis(); + if (chassis) + return strappend("computer-", chassis); + + return strdup("computer"); +} + + +static bool hostname_is_useful(const char *hn) { + return !isempty(hn) && !is_localhost(hn); +} + +static int context_update_kernel_hostname(Context *c) { + const char *static_hn; + const char *hn; + + assert(c); + + static_hn = c->data[PROP_STATIC_HOSTNAME]; + + /* /etc/hostname with something other than "localhost" + * has the highest preference ... */ + if (hostname_is_useful(static_hn)) + hn = static_hn; + + /* ... the transient host name, (ie: DHCP) comes next ... */ + else if (!isempty(c->data[PROP_HOSTNAME])) + hn = c->data[PROP_HOSTNAME]; + + /* ... fallback to static "localhost.*" ignored above ... */ + else if (!isempty(static_hn)) + hn = static_hn; + + /* ... and the ultimate fallback */ + else + hn = "localhost"; + + if (sethostname_idempotent(hn) < 0) + return -errno; + + return 0; +} + +static int context_write_data_static_hostname(Context *c) { + + assert(c); + + if (isempty(c->data[PROP_STATIC_HOSTNAME])) { + + if (unlink("/etc/hostname") < 0) + return errno == ENOENT ? 0 : -errno; + + return 0; + } + return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]); +} + +static int context_write_data_machine_info(Context *c) { + + static const char * const name[_PROP_MAX] = { + [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME", + [PROP_ICON_NAME] = "ICON_NAME", + [PROP_CHASSIS] = "CHASSIS", + [PROP_DEPLOYMENT] = "DEPLOYMENT", + [PROP_LOCATION] = "LOCATION", + }; + + _cleanup_strv_free_ char **l = NULL; + int r, p; + + assert(c); + + r = load_env_file(NULL, "/etc/machine-info", NULL, &l); + if (r < 0 && r != -ENOENT) + return r; + + for (p = PROP_PRETTY_HOSTNAME; p <= PROP_LOCATION; p++) { + _cleanup_free_ char *t = NULL; + char **u; + + assert(name[p]); + + if (isempty(c->data[p])) { + strv_env_unset(l, name[p]); + continue; + } + + t = strjoin(name[p], "=", c->data[p], NULL); + if (!t) + return -ENOMEM; + + u = strv_env_set(l, t); + if (!u) + return -ENOMEM; + + strv_free(l); + l = u; + } + + if (strv_isempty(l)) { + if (unlink("/etc/machine-info") < 0) + return errno == ENOENT ? 0 : -errno; + + return 0; + } + + return write_env_file_label("/etc/machine-info", l); +} + +static int property_get_icon_name( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *n = NULL; + Context *c = userdata; + const char *name; + + if (isempty(c->data[PROP_ICON_NAME])) + name = n = context_fallback_icon_name(c); + else + name = c->data[PROP_ICON_NAME]; + + if (!name) + return -ENOMEM; + + return sd_bus_message_append(reply, "s", name); +} + +static int property_get_chassis( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Context *c = userdata; + const char *name; + + if (isempty(c->data[PROP_CHASSIS])) + name = fallback_chassis(); + else + name = c->data[PROP_CHASSIS]; + + return sd_bus_message_append(reply, "s", name); +} + +static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = userdata; + const char *name; + int interactive; + char *h; + int r; + + assert(m); + assert(c); + + r = sd_bus_message_read(m, "sb", &name, &interactive); + if (r < 0) + return r; + + if (isempty(name)) + name = c->data[PROP_STATIC_HOSTNAME]; + + if (isempty(name)) + name = "localhost"; + + if (!hostname_is_valid(name, false)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name); + + if (streq_ptr(name, c->data[PROP_HOSTNAME])) + return sd_bus_reply_method_return(m, NULL); + + r = bus_verify_polkit_async( + m, + CAP_SYS_ADMIN, + "org.freedesktop.hostname1.set-hostname", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + h = strdup(name); + if (!h) + return -ENOMEM; + + free(c->data[PROP_HOSTNAME]); + c->data[PROP_HOSTNAME] = h; + + r = context_update_kernel_hostname(c); + if (r < 0) { + log_error_errno(r, "Failed to set host name: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r)); + } + + log_info("Changed host name to '%s'", strna(c->data[PROP_HOSTNAME])); + + (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL); + + return sd_bus_reply_method_return(m, NULL); +} + +static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = userdata; + const char *name; + int interactive; + int r; + + assert(m); + assert(c); + + r = sd_bus_message_read(m, "sb", &name, &interactive); + if (r < 0) + return r; + + if (isempty(name)) + name = NULL; + + if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME])) + return sd_bus_reply_method_return(m, NULL); + + r = bus_verify_polkit_async( + m, + CAP_SYS_ADMIN, + "org.freedesktop.hostname1.set-static-hostname", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + if (isempty(name)) { + c->data[PROP_STATIC_HOSTNAME] = mfree(c->data[PROP_STATIC_HOSTNAME]); + } else { + char *h; + + if (!hostname_is_valid(name, false)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name); + + h = strdup(name); + if (!h) + return -ENOMEM; + + free(c->data[PROP_STATIC_HOSTNAME]); + c->data[PROP_STATIC_HOSTNAME] = h; + } + + r = context_update_kernel_hostname(c); + if (r < 0) { + log_error_errno(r, "Failed to set host name: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %s", strerror(-r)); + } + + r = context_write_data_static_hostname(c); + if (r < 0) { + log_error_errno(r, "Failed to write static host name: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %s", strerror(-r)); + } + + log_info("Changed static host name to '%s'", strna(c->data[PROP_STATIC_HOSTNAME])); + + (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL); + + return sd_bus_reply_method_return(m, NULL); +} + +static int set_machine_info(Context *c, sd_bus_message *m, int prop, sd_bus_message_handler_t cb, sd_bus_error *error) { + int interactive; + const char *name; + int r; + + assert(c); + assert(m); + + r = sd_bus_message_read(m, "sb", &name, &interactive); + if (r < 0) + return r; + + if (isempty(name)) + name = NULL; + + if (streq_ptr(name, c->data[prop])) + return sd_bus_reply_method_return(m, NULL); + + /* Since the pretty hostname should always be changed at the + * same time as the static one, use the same policy action for + * both... */ + + r = bus_verify_polkit_async( + m, + CAP_SYS_ADMIN, + prop == PROP_PRETTY_HOSTNAME ? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + if (isempty(name)) { + c->data[prop] = mfree(c->data[prop]); + } else { + char *h; + + /* The icon name might ultimately be used as file + * name, so better be safe than sorry */ + + if (prop == PROP_ICON_NAME && !filename_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name); + if (prop == PROP_PRETTY_HOSTNAME && string_has_cc(name, NULL)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty host name '%s'", name); + if (prop == PROP_CHASSIS && !valid_chassis(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name); + if (prop == PROP_DEPLOYMENT && !valid_deployment(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid deployment '%s'", name); + if (prop == PROP_LOCATION && string_has_cc(name, NULL)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid location '%s'", name); + + h = strdup(name); + if (!h) + return -ENOMEM; + + free(c->data[prop]); + c->data[prop] = h; + } + + r = context_write_data_machine_info(c); + if (r < 0) { + log_error_errno(r, "Failed to write machine info: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %s", strerror(-r)); + } + + log_info("Changed %s to '%s'", + prop == PROP_PRETTY_HOSTNAME ? "pretty host name" : + prop == PROP_DEPLOYMENT ? "deployment" : + prop == PROP_LOCATION ? "location" : + prop == PROP_CHASSIS ? "chassis" : "icon name", strna(c->data[prop])); + + (void) sd_bus_emit_properties_changed( + sd_bus_message_get_bus(m), + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + prop == PROP_PRETTY_HOSTNAME ? "PrettyHostname" : + prop == PROP_DEPLOYMENT ? "Deployment" : + prop == PROP_LOCATION ? "Location" : + prop == PROP_CHASSIS ? "Chassis" : "IconName" , NULL); + + return sd_bus_reply_method_return(m, NULL); +} + +static int method_set_pretty_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) { + return set_machine_info(userdata, m, PROP_PRETTY_HOSTNAME, method_set_pretty_hostname, error); +} + +static int method_set_icon_name(sd_bus_message *m, void *userdata, sd_bus_error *error) { + return set_machine_info(userdata, m, PROP_ICON_NAME, method_set_icon_name, error); +} + +static int method_set_chassis(sd_bus_message *m, void *userdata, sd_bus_error *error) { + return set_machine_info(userdata, m, PROP_CHASSIS, method_set_chassis, error); +} + +static int method_set_deployment(sd_bus_message *m, void *userdata, sd_bus_error *error) { + return set_machine_info(userdata, m, PROP_DEPLOYMENT, method_set_deployment, error); +} + +static int method_set_location(sd_bus_message *m, void *userdata, sd_bus_error *error) { + return set_machine_info(userdata, m, PROP_LOCATION, method_set_location, error); +} + +static const sd_bus_vtable hostname_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Deployment", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_DEPLOYMENT, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Location", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_LOCATION, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("KernelName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_NAME, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KernelRelease", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_RELEASE, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KernelVersion", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_KERNEL_VERSION, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_METHOD("SetHostname", "sb", NULL, method_set_hostname, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetStaticHostname", "sb", NULL, method_set_static_hostname, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetPrettyHostname", "sb", NULL, method_set_pretty_hostname, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetIconName", "sb", NULL, method_set_icon_name, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetChassis", "sb", NULL, method_set_chassis, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetDeployment", "sb", NULL, method_set_deployment, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLocation", "sb", NULL, method_set_location, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END, +}; + +static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + assert(c); + assert(event); + assert(_bus); + + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to get system bus connection: %m"); + + r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/hostname1", "org.freedesktop.hostname1", hostname_vtable, c); + if (r < 0) + return log_error_errno(r, "Failed to register object: %m"); + + r = sd_bus_request_name(bus, "org.freedesktop.hostname1", 0); + if (r < 0) + return log_error_errno(r, "Failed to register name: %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"); + + *_bus = bus; + bus = NULL; + + return 0; +} + +int main(int argc, char *argv[]) { + Context context = {}; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + mac_selinux_init(); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + r = sd_event_default(&event); + if (r < 0) { + log_error_errno(r, "Failed to allocate event loop: %m"); + goto finish; + } + + sd_event_set_watchdog(event, true); + + r = connect_bus(&context, event, &bus); + if (r < 0) + goto finish; + + r = context_read_data(&context); + if (r < 0) { + log_error_errno(r, "Failed to read hostname and machine information: %m"); + goto finish; + } + + r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + +finish: + context_free(&context); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.conf b/src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.conf new file mode 100644 index 0000000000..46b4aadc83 --- /dev/null +++ b/src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.conf @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.policy.in b/src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.policy.in new file mode 100644 index 0000000000..c32c1d4fda --- /dev/null +++ b/src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.policy.in @@ -0,0 +1,50 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Set host name + <_message>Authentication is required to set the local host name. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Set static host name + <_message>Authentication is required to set the statically configured local host name, as well as the pretty host name. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + org.freedesktop.hostname1.set-hostname org.freedesktop.hostname1.set-machine-info + + + + <_description>Set machine information + <_message>Authentication is required to set local machine information. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + diff --git a/src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.service b/src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.service new file mode 100644 index 0000000000..6041ed60ca --- /dev/null +++ b/src/grp-hostname/systemd-hostnamed/org.freedesktop.hostname1.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.hostname1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.hostname1.service diff --git a/src/grp-initutils/systemd-detect-virt/systemd-detect-virt.completion.zsh b/src/grp-initutils/systemd-detect-virt/systemd-detect-virt.completion.zsh new file mode 100644 index 0000000000..a0c7df727c --- /dev/null +++ b/src/grp-initutils/systemd-detect-virt/systemd-detect-virt.completion.zsh @@ -0,0 +1,11 @@ +#compdef systemd-detect-virt + +local curcontext="$curcontext" state lstate line +_arguments \ + {-h,--help}'[Show this help]' \ + '--version[Show package version]' \ + {-c,--container}'[Only detect whether we are run in a container]' \ + {-v,--vm}'[Only detect whether we are run in a VM]' \ + {-q,--quiet}"[Don't output anything, just set return value]" + +#vim: set ft=zsh sw=4 ts=4 et diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/Makefile b/src/grp-journal/grp-remote/systemd-journal-remote/Makefile index 05af76afc2..b75eeedae8 100644 --- a/src/grp-journal/grp-remote/systemd-journal-remote/Makefile +++ b/src/grp-journal/grp-remote/systemd-journal-remote/Makefile @@ -66,12 +66,9 @@ dist_systemunit_DATA += \ nodist_systemunit_DATA += \ units/systemd-journal-remote.service -journal-remote-install-hook: journal-install-hook - -$(MKDIR_P) $(DESTDIR)/var/log/journal/remote - -chown 0:0 $(DESTDIR)/var/log/journal/remote - -chmod 755 $(DESTDIR)/var/log/journal/remote - -INSTALL_EXEC_HOOKS += journal-remote-install-hook +files.sys.all += /var/log/journal/remote +$(DESTDIR)/var/log/journal/remote: + $(MKDIR_P) $@ nodist_pkgsysconf_DATA += \ src/journal-remote/journal-remote.conf diff --git a/src/grp-journal/systemd-cat/systemd-cat.completion.zsh b/src/grp-journal/systemd-cat/systemd-cat.completion.zsh new file mode 100644 index 0000000000..7487b00ee8 --- /dev/null +++ b/src/grp-journal/systemd-cat/systemd-cat.completion.zsh @@ -0,0 +1,12 @@ +#compdef systemd-cat + +local curcontext="$curcontext" state lstate line +_arguments \ + {-h,--help}'[Show this help]' \ + '--version[Show package version.]' \ + {-t+,--identifier=}'[Set syslog identifier.]:syslog identifier:' \ + {-p+,--priority=}'[Set priority value.]:value:({0..7})' \ + '--level-prefix=[Control whether level prefix shall be parsed.]:boolean:(1 0)' \ + ':Message' + +#vim: set ft=zsh sw=4 ts=4 et diff --git a/src/grp-locale/.gitignore b/src/grp-locale/.gitignore deleted file mode 100644 index b1e0ba755e..0000000000 --- a/src/grp-locale/.gitignore +++ /dev/null @@ -1 +0,0 @@ -org.freedesktop.locale1.policy diff --git a/src/grp-locale/Makefile b/src/grp-locale/Makefile index 013c5289a5..6dc11dc2d8 100644 --- a/src/grp-locale/Makefile +++ b/src/grp-locale/Makefile @@ -23,71 +23,6 @@ include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk include $(topsrcdir)/build-aux/Makefile.head.mk -ifneq ($(ENABLE_LOCALED),) -systemd_localed_SOURCES = \ - src/locale/localed.c - -systemd_localed_LDADD = \ - libshared.la \ - -ldl - -systemd_localed_CFLAGS = \ - $(AM_CFLAGS) \ - $(XKBCOMMON_CFLAGS) - -nodist_systemunit_DATA += \ - units/systemd-localed.service - -dist_systemunit_DATA_busnames += \ - units/org.freedesktop.locale1.busname - -rootlibexec_PROGRAMS += \ - systemd-localed - -dist_dbuspolicy_DATA += \ - src/locale/org.freedesktop.locale1.conf - -dist_dbussystemservice_DATA += \ - src/locale/org.freedesktop.locale1.service - -polkitpolicy_files += \ - src/locale/org.freedesktop.locale1.policy - -SYSTEM_UNIT_ALIASES += \ - systemd-localed.service dbus-org.freedesktop.locale1.service - -BUSNAMES_TARGET_WANTS += \ - org.freedesktop.locale1.busname - -dist_pkgdata_DATA = \ - src/locale/kbd-model-map \ - src/locale/language-fallback-map - -localectl_SOURCES = \ - src/locale/localectl.c - -localectl_LDADD = \ - libshared.la - -bin_PROGRAMS += \ - localectl - -dist_bashcompletion_data += \ - shell-completion/bash/localectl - -dist_zshcompletion_data += \ - shell-completion/zsh/_localectl -endif # ENABLE_LOCALED - -.PHONY: update-kbd-model-map - -polkitpolicy_in_files += \ - src/locale/org.freedesktop.locale1.policy.in - -EXTRA_DIST += \ - units/systemd-localed.service.in - -sd.CPPFLAGS += -DSYSTEMD_KBD_MODEL_MAP=\"$(pkgdatadir)/kbd-model-map\" -sd.CPPFLAGS += -DSYSTEMD_LANGUAGE_FALLBACK_MAP=\"$(pkgdatadir)/language-fallback-map\" +nested.subdirs += systemd-localed localectl include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-locale/kbd-model-map b/src/grp-locale/kbd-model-map deleted file mode 100644 index 8fa984f83b..0000000000 --- a/src/grp-locale/kbd-model-map +++ /dev/null @@ -1,68 +0,0 @@ -# Generated from system-config-keyboard's model list -# consolelayout xlayout xmodel xvariant xoptions -sg ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp -nl nl pc105 - terminate:ctrl_alt_bksp -mk-utf mk,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -trq tr pc105 - terminate:ctrl_alt_bksp -uk gb pc105 - terminate:ctrl_alt_bksp -is-latin1 is pc105 - terminate:ctrl_alt_bksp -de de pc105 - terminate:ctrl_alt_bksp -la-latin1 latam pc105 - terminate:ctrl_alt_bksp -us us pc105+inet - terminate:ctrl_alt_bksp -ko kr pc105 - terminate:ctrl_alt_bksp -ro-std ro pc105 std terminate:ctrl_alt_bksp -de-latin1 de pc105 - terminate:ctrl_alt_bksp -slovene si pc105 - terminate:ctrl_alt_bksp -hu101 hu pc105 qwerty terminate:ctrl_alt_bksp -jp106 jp jp106 - terminate:ctrl_alt_bksp -croat hr pc105 - terminate:ctrl_alt_bksp -it2 it pc105 - terminate:ctrl_alt_bksp -hu hu pc105 - terminate:ctrl_alt_bksp -sr-latin rs pc105 latin terminate:ctrl_alt_bksp -fi fi pc105 - terminate:ctrl_alt_bksp -fr_CH ch pc105 fr terminate:ctrl_alt_bksp -dk-latin1 dk pc105 - terminate:ctrl_alt_bksp -fr fr pc105 - terminate:ctrl_alt_bksp -it it pc105 - terminate:ctrl_alt_bksp -ua-utf ua,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -fr-latin1 fr pc105 - terminate:ctrl_alt_bksp -sg-latin1 ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp -be-latin1 be pc105 - terminate:ctrl_alt_bksp -dk dk pc105 - terminate:ctrl_alt_bksp -fr-pc fr pc105 - terminate:ctrl_alt_bksp -bg_pho-utf8 bg,us pc105 ,phonetic terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -it-ibm it pc105 - terminate:ctrl_alt_bksp -cz-us-qwertz cz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -br-abnt2 br abnt2 - terminate:ctrl_alt_bksp -ro ro pc105 - terminate:ctrl_alt_bksp -us-acentos us pc105 intl terminate:ctrl_alt_bksp -pt-latin1 pt pc105 - terminate:ctrl_alt_bksp -ro-std-cedilla ro pc105 std_cedilla terminate:ctrl_alt_bksp -tj_alt-UTF8 tj pc105 - terminate:ctrl_alt_bksp -de-latin1-nodeadkeys de pc105 nodeadkeys terminate:ctrl_alt_bksp -no no pc105 - terminate:ctrl_alt_bksp -bg_bds-utf8 bg,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -dvorak us pc105 dvorak terminate:ctrl_alt_bksp -dvorak us pc105 dvorak-alt-intl terminate:ctrl_alt_bksp -ru ru,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -cz-lat2 cz pc105 qwerty terminate:ctrl_alt_bksp -pl2 pl pc105 - terminate:ctrl_alt_bksp -es es pc105 - terminate:ctrl_alt_bksp -ro-cedilla ro pc105 cedilla terminate:ctrl_alt_bksp -ie ie pc105 - terminate:ctrl_alt_bksp -et ee pc105 - terminate:ctrl_alt_bksp -sk-qwerty sk pc105 - terminate:ctrl_alt_bksp,qwerty -sk-qwertz sk pc105 - terminate:ctrl_alt_bksp -fr-latin9 fr pc105 latin9 terminate:ctrl_alt_bksp -fr_CH-latin1 ch pc105 fr terminate:ctrl_alt_bksp -cf ca pc105 - terminate:ctrl_alt_bksp -sv-latin1 se pc105 - terminate:ctrl_alt_bksp -sr-cy rs pc105 - terminate:ctrl_alt_bksp -gr gr,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -by by,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -il il pc105 - terminate:ctrl_alt_bksp -kazakh kz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -lt.baltic lt pc105 - terminate:ctrl_alt_bksp -lt.l4 lt pc105 - terminate:ctrl_alt_bksp -lt lt pc105 - terminate:ctrl_alt_bksp -khmer kh,us pc105 - terminate:ctrl_alt_bksp diff --git a/src/grp-locale/language-fallback-map b/src/grp-locale/language-fallback-map deleted file mode 100644 index d0b02a6b98..0000000000 --- a/src/grp-locale/language-fallback-map +++ /dev/null @@ -1,13 +0,0 @@ -csb_PL csb:pl -en_AU en_AU:en_GB -en_IE en_IE:en_GB -en_NZ en_NZ:en_GB -en_ZA en_ZA:en_GB -fr_BE fr_BE:fr_FR -fr_CA fr_CA:fr_FR -fr_CH fr_CH:fr_FR -fr_LU fr_LU:fr_FR -it_CH it_CH:it_IT -mai_IN mai:hi -nds_DE nds:de -szl_PL szl:pl diff --git a/src/grp-locale/localectl.c b/src/grp-locale/localectl.c deleted file mode 100644 index 72ac1c0303..0000000000 --- a/src/grp-locale/localectl.c +++ /dev/null @@ -1,682 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Lennart Poettering - Copyright 2013 Kay Sievers - - 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 "basic/def.h" -#include "basic/fd-util.h" -#include "basic/fileio.h" -#include "basic/locale-util.h" -#include "basic/set.h" -#include "basic/strv.h" -#include "basic/util.h" -#include "basic/virt.h" -#include "sd-bus/bus-error.h" -#include "shared/bus-util.h" -#include "shared/pager.h" -#include "shared/spawn-polkit-agent.h" - -static bool arg_no_pager = false; -static bool arg_ask_password = true; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_convert = true; - -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(); -} - -typedef struct StatusInfo { - char **locale; - char *vconsole_keymap; - char *vconsole_keymap_toggle; - char *x11_layout; - char *x11_model; - char *x11_variant; - char *x11_options; -} StatusInfo; - -static void status_info_clear(StatusInfo *info) { - if (info) { - strv_free(info->locale); - free(info->vconsole_keymap); - free(info->vconsole_keymap_toggle); - free(info->x11_layout); - free(info->x11_model); - free(info->x11_variant); - free(info->x11_options); - zero(*info); - } -} - -static void print_overridden_variables(void) { - int r; - char *variables[_VARIABLE_LC_MAX] = {}; - LocaleVariable j; - bool print_warning = true; - - if (detect_container() > 0 || arg_host) - return; - - r = parse_env_file("/proc/cmdline", WHITESPACE, - "locale.LANG", &variables[VARIABLE_LANG], - "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE], - "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE], - "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], - "locale.LC_TIME", &variables[VARIABLE_LC_TIME], - "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE], - "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY], - "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], - "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER], - "locale.LC_NAME", &variables[VARIABLE_LC_NAME], - "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], - "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], - "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], - "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], - NULL); - - if (r < 0 && r != -ENOENT) { - log_warning_errno(r, "Failed to read /proc/cmdline: %m"); - goto finish; - } - - for (j = 0; j < _VARIABLE_LC_MAX; j++) - if (variables[j]) { - if (print_warning) { - log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n" - " Command Line: %s=%s", locale_variable_to_string(j), variables[j]); - - print_warning = false; - } else - log_warning(" %s=%s", locale_variable_to_string(j), variables[j]); - } - finish: - for (j = 0; j < _VARIABLE_LC_MAX; j++) - free(variables[j]); -} - -static void print_status_info(StatusInfo *i) { - assert(i); - - if (strv_isempty(i->locale)) - puts(" System Locale: n/a"); - else { - char **j; - - printf(" System Locale: %s\n", i->locale[0]); - STRV_FOREACH(j, i->locale + 1) - printf(" %s\n", *j); - } - - printf(" VC Keymap: %s\n", strna(i->vconsole_keymap)); - if (!isempty(i->vconsole_keymap_toggle)) - printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle); - - printf(" X11 Layout: %s\n", strna(i->x11_layout)); - if (!isempty(i->x11_model)) - printf(" X11 Model: %s\n", i->x11_model); - if (!isempty(i->x11_variant)) - printf(" X11 Variant: %s\n", i->x11_variant); - if (!isempty(i->x11_options)) - printf(" X11 Options: %s\n", i->x11_options); -} - -static int show_status(sd_bus *bus, char **args, unsigned n) { - _cleanup_(status_info_clear) StatusInfo info = {}; - static const struct bus_properties_map map[] = { - { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) }, - { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) }, - { "VConsoleKeymapToggle", "s", NULL, offsetof(StatusInfo, vconsole_keymap_toggle) }, - { "X11Layout", "s", NULL, offsetof(StatusInfo, x11_layout) }, - { "X11Model", "s", NULL, offsetof(StatusInfo, x11_model) }, - { "X11Variant", "s", NULL, offsetof(StatusInfo, x11_variant) }, - { "X11Options", "s", NULL, offsetof(StatusInfo, x11_options) }, - { "Locale", "as", NULL, offsetof(StatusInfo, locale) }, - {} - }; - int r; - - assert(bus); - - r = bus_map_all_properties(bus, - "org.freedesktop.locale1", - "/org/freedesktop/locale1", - map, - &info); - if (r < 0) - return log_error_errno(r, "Could not get properties: %m"); - - print_overridden_variables(); - print_status_info(&info); - - return r; -} - -static int set_locale(sd_bus *bus, char **args, unsigned n) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(args); - - polkit_agent_open_if_enabled(); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.locale1", - "/org/freedesktop/locale1", - "org.freedesktop.locale1", - "SetLocale"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, args + 1); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "b", arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, NULL); - if (r < 0) { - log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int list_locales(sd_bus *bus, char **args, unsigned n) { - _cleanup_strv_free_ char **l = NULL; - int r; - - assert(args); - - r = get_locales(&l); - if (r < 0) - return log_error_errno(r, "Failed to read list of locales: %m"); - - pager_open(arg_no_pager, false); - strv_print(l); - - return 0; -} - -static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *map, *toggle_map; - int r; - - assert(bus); - assert(args); - - if (n > 3) { - log_error("Too many arguments."); - return -EINVAL; - } - - polkit_agent_open_if_enabled(); - - map = args[1]; - toggle_map = n > 2 ? args[2] : ""; - - r = sd_bus_call_method( - bus, - "org.freedesktop.locale1", - "/org/freedesktop/locale1", - "org.freedesktop.locale1", - "SetVConsoleKeyboard", - &error, - NULL, - "ssbb", map, toggle_map, arg_convert, arg_ask_password); - if (r < 0) - log_error("Failed to set keymap: %s", bus_error_message(&error, -r)); - - return r; -} - -static Set *keymaps = NULL; - -static int nftw_cb( - const char *fpath, - const struct stat *sb, - int tflag, - struct FTW *ftwbuf) { - - char *p, *e; - int r; - - if (tflag != FTW_F) - return 0; - - if (!endswith(fpath, ".map") && - !endswith(fpath, ".map.gz")) - return 0; - - p = strdup(basename(fpath)); - if (!p) - return log_oom(); - - e = endswith(p, ".map"); - if (e) - *e = 0; - - e = endswith(p, ".map.gz"); - if (e) - *e = 0; - - r = set_consume(keymaps, p); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Can't add keymap: %m"); - - return 0; -} - -static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) { - _cleanup_strv_free_ char **l = NULL; - const char *dir; - - keymaps = set_new(&string_hash_ops); - if (!keymaps) - return log_oom(); - - NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) - nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS); - - l = set_get_strv(keymaps); - if (!l) { - set_free_free(keymaps); - return log_oom(); - } - - set_free(keymaps); - - if (strv_isempty(l)) { - log_error("Couldn't find any console keymaps."); - return -ENOENT; - } - - strv_sort(l); - - pager_open(arg_no_pager, false); - - strv_print(l); - - return 0; -} - -static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *layout, *model, *variant, *options; - int r; - - assert(bus); - assert(args); - - if (n > 5) { - log_error("Too many arguments."); - return -EINVAL; - } - - polkit_agent_open_if_enabled(); - - layout = args[1]; - model = n > 2 ? args[2] : ""; - variant = n > 3 ? args[3] : ""; - options = n > 4 ? args[4] : ""; - - r = sd_bus_call_method( - bus, - "org.freedesktop.locale1", - "/org/freedesktop/locale1", - "org.freedesktop.locale1", - "SetX11Keyboard", - &error, - NULL, - "ssssbb", layout, model, variant, options, - arg_convert, arg_ask_password); - if (r < 0) - log_error("Failed to set keymap: %s", bus_error_message(&error, -r)); - - return r; -} - -static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_strv_free_ char **list = NULL; - char line[LINE_MAX]; - enum { - NONE, - MODELS, - LAYOUTS, - VARIANTS, - OPTIONS - } state = NONE, look_for; - int r; - - if (n > 2) { - log_error("Too many arguments."); - return -EINVAL; - } - - f = fopen("/usr/share/X11/xkb/rules/base.lst", "re"); - if (!f) - return log_error_errno(errno, "Failed to open keyboard mapping list. %m"); - - if (streq(args[0], "list-x11-keymap-models")) - look_for = MODELS; - else if (streq(args[0], "list-x11-keymap-layouts")) - look_for = LAYOUTS; - else if (streq(args[0], "list-x11-keymap-variants")) - look_for = VARIANTS; - else if (streq(args[0], "list-x11-keymap-options")) - look_for = OPTIONS; - else - assert_not_reached("Wrong parameter"); - - FOREACH_LINE(line, f, break) { - char *l, *w; - - l = strstrip(line); - - if (isempty(l)) - continue; - - if (l[0] == '!') { - if (startswith(l, "! model")) - state = MODELS; - else if (startswith(l, "! layout")) - state = LAYOUTS; - else if (startswith(l, "! variant")) - state = VARIANTS; - else if (startswith(l, "! option")) - state = OPTIONS; - else - state = NONE; - - continue; - } - - if (state != look_for) - continue; - - w = l + strcspn(l, WHITESPACE); - - if (n > 1) { - char *e; - - if (*w == 0) - continue; - - *w = 0; - w++; - w += strspn(w, WHITESPACE); - - e = strchr(w, ':'); - if (!e) - continue; - - *e = 0; - - if (!streq(w, args[1])) - continue; - } else - *w = 0; - - r = strv_extend(&list, l); - if (r < 0) - return log_oom(); - } - - if (strv_isempty(list)) { - log_error("Couldn't find any entries."); - return -ENOENT; - } - - strv_sort(list); - strv_uniq(list); - - pager_open(arg_no_pager, false); - - strv_print(list); - return 0; -} - -static void help(void) { - printf("%s [OPTIONS...] COMMAND ...\n\n" - "Query or change system locale and keyboard settings.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not prompt for password\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --no-convert Don't convert keyboard mappings\n\n" - "Commands:\n" - " status Show current locale settings\n" - " set-locale LOCALE... Set system locale\n" - " list-locales Show known locales\n" - " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n" - " list-keymaps Show known virtual console keyboard mappings\n" - " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n" - " Set X11 and console keyboard mappings\n" - " list-x11-keymap-models Show known X11 keyboard mapping models\n" - " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n" - " list-x11-keymap-variants [LAYOUT]\n" - " Show known X11 keyboard mapping variants\n" - " list-x11-keymap-options Show known X11 keyboard mapping options\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_CONVERT, - ARG_NO_ASK_PASSWORD - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "no-convert", no_argument, NULL, ARG_NO_CONVERT }, - {} - }; - - int 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_NO_CONVERT: - arg_convert = false; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - 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 '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int localectl_main(sd_bus *bus, int argc, char *argv[]) { - - static const struct { - const char* verb; - const enum { - MORE, - LESS, - EQUAL - } argc_cmp; - const int argc; - int (* const dispatch)(sd_bus *bus, char **args, unsigned n); - } verbs[] = { - { "status", LESS, 1, show_status }, - { "set-locale", MORE, 2, set_locale }, - { "list-locales", EQUAL, 1, list_locales }, - { "set-keymap", MORE, 2, set_vconsole_keymap }, - { "list-keymaps", EQUAL, 1, list_vconsole_keymaps }, - { "set-x11-keymap", MORE, 2, set_x11_keymap }, - { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps }, - { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps }, - { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps }, - { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps }, - }; - - int left; - unsigned i; - - assert(argc >= 0); - assert(argv); - - left = argc - optind; - - if (left <= 0) - /* Special rule: no arguments means "status" */ - i = 0; - else { - if (streq(argv[optind], "help")) { - help(); - return 0; - } - - for (i = 0; i < ELEMENTSOF(verbs); i++) - if (streq(argv[optind], verbs[i].verb)) - break; - - if (i >= ELEMENTSOF(verbs)) { - log_error("Unknown operation %s", argv[optind]); - return -EINVAL; - } - } - - switch (verbs[i].argc_cmp) { - - case EQUAL: - if (left != verbs[i].argc) { - log_error("Invalid number of arguments."); - return -EINVAL; - } - - break; - - case MORE: - if (left < verbs[i].argc) { - log_error("Too few arguments."); - return -EINVAL; - } - - break; - - case LESS: - if (left > verbs[i].argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - break; - - default: - assert_not_reached("Unknown comparison operator."); - } - - return verbs[i].dispatch(bus, argv + optind, left); -} - -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; - } - - r = localectl_main(bus, argc, argv); - -finish: - pager_close(); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/grp-locale/localectl/Makefile b/src/grp-locale/localectl/Makefile new file mode 100644 index 0000000000..d0023df124 --- /dev/null +++ b/src/grp-locale/localectl/Makefile @@ -0,0 +1,45 @@ +# -*- 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_LOCALED),) + +localectl_SOURCES = \ + src/locale/localectl.c + +localectl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + localectl + +dist_bashcompletion_data += \ + shell-completion/bash/localectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_localectl + +endif # ENABLE_LOCALED + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-locale/localectl/localectl.c b/src/grp-locale/localectl/localectl.c new file mode 100644 index 0000000000..72ac1c0303 --- /dev/null +++ b/src/grp-locale/localectl/localectl.c @@ -0,0 +1,682 @@ +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2013 Kay Sievers + + 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 "basic/def.h" +#include "basic/fd-util.h" +#include "basic/fileio.h" +#include "basic/locale-util.h" +#include "basic/set.h" +#include "basic/strv.h" +#include "basic/util.h" +#include "basic/virt.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-util.h" +#include "shared/pager.h" +#include "shared/spawn-polkit-agent.h" + +static bool arg_no_pager = false; +static bool arg_ask_password = true; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_convert = true; + +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(); +} + +typedef struct StatusInfo { + char **locale; + char *vconsole_keymap; + char *vconsole_keymap_toggle; + char *x11_layout; + char *x11_model; + char *x11_variant; + char *x11_options; +} StatusInfo; + +static void status_info_clear(StatusInfo *info) { + if (info) { + strv_free(info->locale); + free(info->vconsole_keymap); + free(info->vconsole_keymap_toggle); + free(info->x11_layout); + free(info->x11_model); + free(info->x11_variant); + free(info->x11_options); + zero(*info); + } +} + +static void print_overridden_variables(void) { + int r; + char *variables[_VARIABLE_LC_MAX] = {}; + LocaleVariable j; + bool print_warning = true; + + if (detect_container() > 0 || arg_host) + return; + + r = parse_env_file("/proc/cmdline", WHITESPACE, + "locale.LANG", &variables[VARIABLE_LANG], + "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE], + "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE], + "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], + "locale.LC_TIME", &variables[VARIABLE_LC_TIME], + "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE], + "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY], + "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], + "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER], + "locale.LC_NAME", &variables[VARIABLE_LC_NAME], + "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], + "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], + "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], + "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], + NULL); + + if (r < 0 && r != -ENOENT) { + log_warning_errno(r, "Failed to read /proc/cmdline: %m"); + goto finish; + } + + for (j = 0; j < _VARIABLE_LC_MAX; j++) + if (variables[j]) { + if (print_warning) { + log_warning("Warning: Settings on kernel command line override system locale settings in /etc/locale.conf.\n" + " Command Line: %s=%s", locale_variable_to_string(j), variables[j]); + + print_warning = false; + } else + log_warning(" %s=%s", locale_variable_to_string(j), variables[j]); + } + finish: + for (j = 0; j < _VARIABLE_LC_MAX; j++) + free(variables[j]); +} + +static void print_status_info(StatusInfo *i) { + assert(i); + + if (strv_isempty(i->locale)) + puts(" System Locale: n/a"); + else { + char **j; + + printf(" System Locale: %s\n", i->locale[0]); + STRV_FOREACH(j, i->locale + 1) + printf(" %s\n", *j); + } + + printf(" VC Keymap: %s\n", strna(i->vconsole_keymap)); + if (!isempty(i->vconsole_keymap_toggle)) + printf("VC Toggle Keymap: %s\n", i->vconsole_keymap_toggle); + + printf(" X11 Layout: %s\n", strna(i->x11_layout)); + if (!isempty(i->x11_model)) + printf(" X11 Model: %s\n", i->x11_model); + if (!isempty(i->x11_variant)) + printf(" X11 Variant: %s\n", i->x11_variant); + if (!isempty(i->x11_options)) + printf(" X11 Options: %s\n", i->x11_options); +} + +static int show_status(sd_bus *bus, char **args, unsigned n) { + _cleanup_(status_info_clear) StatusInfo info = {}; + static const struct bus_properties_map map[] = { + { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) }, + { "VConsoleKeymap", "s", NULL, offsetof(StatusInfo, vconsole_keymap) }, + { "VConsoleKeymapToggle", "s", NULL, offsetof(StatusInfo, vconsole_keymap_toggle) }, + { "X11Layout", "s", NULL, offsetof(StatusInfo, x11_layout) }, + { "X11Model", "s", NULL, offsetof(StatusInfo, x11_model) }, + { "X11Variant", "s", NULL, offsetof(StatusInfo, x11_variant) }, + { "X11Options", "s", NULL, offsetof(StatusInfo, x11_options) }, + { "Locale", "as", NULL, offsetof(StatusInfo, locale) }, + {} + }; + int r; + + assert(bus); + + r = bus_map_all_properties(bus, + "org.freedesktop.locale1", + "/org/freedesktop/locale1", + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + print_overridden_variables(); + print_status_info(&info); + + return r; +} + +static int set_locale(sd_bus *bus, char **args, unsigned n) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(args); + + polkit_agent_open_if_enabled(); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.locale1", + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "SetLocale"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, args + 1); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "b", arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, NULL); + if (r < 0) { + log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int list_locales(sd_bus *bus, char **args, unsigned n) { + _cleanup_strv_free_ char **l = NULL; + int r; + + assert(args); + + r = get_locales(&l); + if (r < 0) + return log_error_errno(r, "Failed to read list of locales: %m"); + + pager_open(arg_no_pager, false); + strv_print(l); + + return 0; +} + +static int set_vconsole_keymap(sd_bus *bus, char **args, unsigned n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *map, *toggle_map; + int r; + + assert(bus); + assert(args); + + if (n > 3) { + log_error("Too many arguments."); + return -EINVAL; + } + + polkit_agent_open_if_enabled(); + + map = args[1]; + toggle_map = n > 2 ? args[2] : ""; + + r = sd_bus_call_method( + bus, + "org.freedesktop.locale1", + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "SetVConsoleKeyboard", + &error, + NULL, + "ssbb", map, toggle_map, arg_convert, arg_ask_password); + if (r < 0) + log_error("Failed to set keymap: %s", bus_error_message(&error, -r)); + + return r; +} + +static Set *keymaps = NULL; + +static int nftw_cb( + const char *fpath, + const struct stat *sb, + int tflag, + struct FTW *ftwbuf) { + + char *p, *e; + int r; + + if (tflag != FTW_F) + return 0; + + if (!endswith(fpath, ".map") && + !endswith(fpath, ".map.gz")) + return 0; + + p = strdup(basename(fpath)); + if (!p) + return log_oom(); + + e = endswith(p, ".map"); + if (e) + *e = 0; + + e = endswith(p, ".map.gz"); + if (e) + *e = 0; + + r = set_consume(keymaps, p); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Can't add keymap: %m"); + + return 0; +} + +static int list_vconsole_keymaps(sd_bus *bus, char **args, unsigned n) { + _cleanup_strv_free_ char **l = NULL; + const char *dir; + + keymaps = set_new(&string_hash_ops); + if (!keymaps) + return log_oom(); + + NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) + nftw(dir, nftw_cb, 20, FTW_MOUNT|FTW_PHYS); + + l = set_get_strv(keymaps); + if (!l) { + set_free_free(keymaps); + return log_oom(); + } + + set_free(keymaps); + + if (strv_isempty(l)) { + log_error("Couldn't find any console keymaps."); + return -ENOENT; + } + + strv_sort(l); + + pager_open(arg_no_pager, false); + + strv_print(l); + + return 0; +} + +static int set_x11_keymap(sd_bus *bus, char **args, unsigned n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *layout, *model, *variant, *options; + int r; + + assert(bus); + assert(args); + + if (n > 5) { + log_error("Too many arguments."); + return -EINVAL; + } + + polkit_agent_open_if_enabled(); + + layout = args[1]; + model = n > 2 ? args[2] : ""; + variant = n > 3 ? args[3] : ""; + options = n > 4 ? args[4] : ""; + + r = sd_bus_call_method( + bus, + "org.freedesktop.locale1", + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "SetX11Keyboard", + &error, + NULL, + "ssssbb", layout, model, variant, options, + arg_convert, arg_ask_password); + if (r < 0) + log_error("Failed to set keymap: %s", bus_error_message(&error, -r)); + + return r; +} + +static int list_x11_keymaps(sd_bus *bus, char **args, unsigned n) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **list = NULL; + char line[LINE_MAX]; + enum { + NONE, + MODELS, + LAYOUTS, + VARIANTS, + OPTIONS + } state = NONE, look_for; + int r; + + if (n > 2) { + log_error("Too many arguments."); + return -EINVAL; + } + + f = fopen("/usr/share/X11/xkb/rules/base.lst", "re"); + if (!f) + return log_error_errno(errno, "Failed to open keyboard mapping list. %m"); + + if (streq(args[0], "list-x11-keymap-models")) + look_for = MODELS; + else if (streq(args[0], "list-x11-keymap-layouts")) + look_for = LAYOUTS; + else if (streq(args[0], "list-x11-keymap-variants")) + look_for = VARIANTS; + else if (streq(args[0], "list-x11-keymap-options")) + look_for = OPTIONS; + else + assert_not_reached("Wrong parameter"); + + FOREACH_LINE(line, f, break) { + char *l, *w; + + l = strstrip(line); + + if (isempty(l)) + continue; + + if (l[0] == '!') { + if (startswith(l, "! model")) + state = MODELS; + else if (startswith(l, "! layout")) + state = LAYOUTS; + else if (startswith(l, "! variant")) + state = VARIANTS; + else if (startswith(l, "! option")) + state = OPTIONS; + else + state = NONE; + + continue; + } + + if (state != look_for) + continue; + + w = l + strcspn(l, WHITESPACE); + + if (n > 1) { + char *e; + + if (*w == 0) + continue; + + *w = 0; + w++; + w += strspn(w, WHITESPACE); + + e = strchr(w, ':'); + if (!e) + continue; + + *e = 0; + + if (!streq(w, args[1])) + continue; + } else + *w = 0; + + r = strv_extend(&list, l); + if (r < 0) + return log_oom(); + } + + if (strv_isempty(list)) { + log_error("Couldn't find any entries."); + return -ENOENT; + } + + strv_sort(list); + strv_uniq(list); + + pager_open(arg_no_pager, false); + + strv_print(list); + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...] COMMAND ...\n\n" + "Query or change system locale and keyboard settings.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-ask-password Do not prompt for password\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --no-convert Don't convert keyboard mappings\n\n" + "Commands:\n" + " status Show current locale settings\n" + " set-locale LOCALE... Set system locale\n" + " list-locales Show known locales\n" + " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n" + " list-keymaps Show known virtual console keyboard mappings\n" + " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n" + " Set X11 and console keyboard mappings\n" + " list-x11-keymap-models Show known X11 keyboard mapping models\n" + " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n" + " list-x11-keymap-variants [LAYOUT]\n" + " Show known X11 keyboard mapping variants\n" + " list-x11-keymap-options Show known X11 keyboard mapping options\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_CONVERT, + ARG_NO_ASK_PASSWORD + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "no-convert", no_argument, NULL, ARG_NO_CONVERT }, + {} + }; + + int 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_NO_CONVERT: + arg_convert = false; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + 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 '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int localectl_main(sd_bus *bus, int argc, char *argv[]) { + + static const struct { + const char* verb; + const enum { + MORE, + LESS, + EQUAL + } argc_cmp; + const int argc; + int (* const dispatch)(sd_bus *bus, char **args, unsigned n); + } verbs[] = { + { "status", LESS, 1, show_status }, + { "set-locale", MORE, 2, set_locale }, + { "list-locales", EQUAL, 1, list_locales }, + { "set-keymap", MORE, 2, set_vconsole_keymap }, + { "list-keymaps", EQUAL, 1, list_vconsole_keymaps }, + { "set-x11-keymap", MORE, 2, set_x11_keymap }, + { "list-x11-keymap-models", EQUAL, 1, list_x11_keymaps }, + { "list-x11-keymap-layouts", EQUAL, 1, list_x11_keymaps }, + { "list-x11-keymap-variants", LESS, 2, list_x11_keymaps }, + { "list-x11-keymap-options", EQUAL, 1, list_x11_keymaps }, + }; + + int left; + unsigned i; + + assert(argc >= 0); + assert(argv); + + left = argc - optind; + + if (left <= 0) + /* Special rule: no arguments means "status" */ + i = 0; + else { + if (streq(argv[optind], "help")) { + help(); + return 0; + } + + for (i = 0; i < ELEMENTSOF(verbs); i++) + if (streq(argv[optind], verbs[i].verb)) + break; + + if (i >= ELEMENTSOF(verbs)) { + log_error("Unknown operation %s", argv[optind]); + return -EINVAL; + } + } + + switch (verbs[i].argc_cmp) { + + case EQUAL: + if (left != verbs[i].argc) { + log_error("Invalid number of arguments."); + return -EINVAL; + } + + break; + + case MORE: + if (left < verbs[i].argc) { + log_error("Too few arguments."); + return -EINVAL; + } + + break; + + case LESS: + if (left > verbs[i].argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + break; + + default: + assert_not_reached("Unknown comparison operator."); + } + + return verbs[i].dispatch(bus, argv + optind, left); +} + +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; + } + + r = localectl_main(bus, argc, argv); + +finish: + pager_close(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-locale/localectl/localectl.completion.bash b/src/grp-locale/localectl/localectl.completion.bash new file mode 100644 index 0000000000..e0c06a794e --- /dev/null +++ b/src/grp-locale/localectl/localectl.completion.bash @@ -0,0 +1,92 @@ +# localectl(1) completion -*- shell-script -*- +# +# This file is part of systemd. +# +# Copyright 2010 Ran Benita +# +# 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 +# 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 . + +__contains_word () { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +__locale_fields=( LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME \ + LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER \ + LC_NAME LC_ADDRESS LC_TELEPHONE \ + LC_MEASUREMENT LC_IDENTIFICATION ) +# LC_ALL is omitted on purpose + +_localectl() { + local i verb comps locale_vals + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + local OPTS='-h --help --version --no-convert --no-pager --no-ask-password + -H --host --machine' + + if __contains_word "$prev" $OPTS; then + case $prev in + --host|-H) + comps='' + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + if [[ $cur = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + local -A VERBS=( + [STANDALONE]='status list-locales list-keymaps' + [LOCALES]='set-locale' + [KEYMAPS]='set-keymap' + [X11]='set-x11-keymap' + ) + + for ((i=0; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]}; then + verb=${COMP_WORDS[i]} + break + fi + done + + if [[ -z $verb ]]; then + comps=${VERBS[*]} + elif __contains_word "$verb" ${VERBS[LOCALES]}; then + if [[ $cur = *=* ]]; then + mapfile -t locale_vals < <(command localectl list-locales 2>/dev/null) + COMPREPLY=( $(compgen -W '${locale_vals[*]}' -- "${cur#=}") ) + elif [[ $prev = "=" ]]; then + mapfile -t locale_vals < <(command localectl list-locales 2>/dev/null) + COMPREPLY=( $(compgen -W '${locale_vals[*]}' -- "$cur") ) + else + compopt -o nospace + COMPREPLY=( $(compgen -W '${__locale_fields[*]}' -S= -- "$cur") ) + fi + return 0 + elif __contains_word "$verb" ${VERBS[KEYMAPS]}; then + comps=$(command localectl list-keymaps) + elif __contains_word "$verb" ${VERBS[STANDALONE]} ${VERBS[X11]}; then + comps='' + fi + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _localectl localectl diff --git a/src/grp-locale/localectl/localectl.completion.zsh b/src/grp-locale/localectl/localectl.completion.zsh new file mode 100644 index 0000000000..d8af4d1863 --- /dev/null +++ b/src/grp-locale/localectl/localectl.completion.zsh @@ -0,0 +1,93 @@ +#compdef localectl + +_localectl_set-locale() { + local -a _locales locale_fields + locale_fields=(LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME \ + LC_COLLATE LC_MONETARY LC_MESSAGES LC_PAPER \ + LC_NAME LC_ADDRESS LC_TELEPHONE \ + LC_MEASUREMENT LC_IDENTIFICATION) + # LC_ALL is omitted on purpose + + local expl suf + _locales=( ${(f)"$(_call_program locales "$service" list-locales)"} ) + compset -P1 '*=' + if [[ -prefix 1 *\= ]]; then + local conf=${PREFIX%%\=*} + _wanted locales expl "locales configs" \ + _combination localeconfs confs=$conf locales "$@" - + else + compadd -S '=' $locale_fields + fi +} + +_localectl_set-keymap() { + local -a _keymaps + if (( CURRENT <= 3 )); then + _keymaps=( ${(f)"$(_call_program locales "$service" list-keymaps)"} ) + _describe keymaps _keymaps + else + _message "no more options" + fi +} + +_localectl_set-x11-keymap() { + if (( $+commands[pkg-config] )); then + local -a _file _layout _model _variant _options + local _xorg_lst + _xorg_lst=${"$($commands[pkg-config] xkeyboard-config --variable=xkb_base)"} + _file=( ${(ps:\n\!:)"$(<$_xorg_lst/rules/xorg.lst)"} ) + _layout=( ${${${(M)${(f)_file[1]}:# *}# }%% *} ) + _model=( ${${${(M)${(f)_file[2]}:# *}# }%% *} ) + _variant=( ${${${(M)${(f)_file[3]}:# *}# }%% *} ) + _options=( ${${${(M)${(f)_file[4]}:# *}# }%% *} ) + #_layout=( ${(f)"$( echo $_file[1] | awk '/^ / {print $1}' )"} ) + #_model=( ${(f)"$(echo $_file[2] | awk '/^ / {print $1}')"} ) + #_variant=( ${(f)"$(echo $_file[3] | awk '/^ / {print $1}')"} ) + #_options=( ${(f)"$(echo ${_file[4]//:/\\:} | awk '/^ / {print $1}')"} ) + + case $CURRENT in + 2) _describe layouts _layout ;; + 3) _describe models _model;; + 4) _describe variants _variant;; + 5) _describe options _options;; + *) _message "no more options" + esac + fi +} + +_localectl_command() { + local -a _localectl_cmds + _localectl_cmds=( + 'status:Show current locale settings' + 'set-locale:Set system locale' + 'list-locales:Show known locales' + 'set-keymap:Set virtual console keyboard mapping' + 'list-keymaps:Show known virtual console keyboard mappings' + 'set-x11-keymap:Set X11 keyboard mapping' + 'list-x11-keymap-models:Show known X11 keyboard mapping models' + 'list-x11-keymap-layouts:Show known X11 keyboard mapping layouts' + 'list-x11-keymap-variants:Show known X11 keyboard mapping variants' + 'list-x11-keymap-options:Show known X11 keyboard mapping options' + ) + if (( CURRENT == 1 )); then + _describe -t commands 'localectl command' _localectl_cmds + else + local curcontext="$curcontext" + cmd="${${_localectl_cmds[(r)$words[1]:*]%%:*}}" + if (( $+functions[_localectl_$cmd] )); then + _localectl_$cmd + else + _message "unknown localectl command: $words[1]" + fi + fi +} + +_arguments \ + {-h,--help}'[Show this help]' \ + '--version[Show package version]' \ + "--no-convert[Don't convert keyboard mappings]" \ + '--no-pager[Do not pipe output into a pager]' \ + '--no-ask-password[Do not prompt for password]' \ + {-H+,--host=}'[Operate on remote host]:userathost:_sd_hosts_or_user_at_host' \ + {-M+,--machine=}'[Operate on local container]:machine' \ + '*::localectl commands:_localectl_command' diff --git a/src/grp-locale/localed.c b/src/grp-locale/localed.c deleted file mode 100644 index 7bb7f1cf34..0000000000 --- a/src/grp-locale/localed.c +++ /dev/null @@ -1,1389 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - Copyright 2013 Kay Sievers - - 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 - -#ifdef HAVE_XKBCOMMON -#include -#include -#endif - -#include - -#include "basic/alloc-util.h" -#include "basic/def.h" -#include "basic/env-util.h" -#include "basic/fd-util.h" -#include "basic/fileio-label.h" -#include "basic/fileio.h" -#include "basic/locale-util.h" -#include "basic/mkdir.h" -#include "basic/path-util.h" -#include "basic/selinux-util.h" -#include "basic/strv.h" -#include "basic/user-util.h" -#include "basic/util.h" -#include "sd-bus/bus-error.h" -#include "sd-bus/bus-message.h" -#include "shared/bus-util.h" - -enum { - /* We don't list LC_ALL here on purpose. People should be - * using LANG instead. */ - LOCALE_LANG, - LOCALE_LANGUAGE, - LOCALE_LC_CTYPE, - LOCALE_LC_NUMERIC, - LOCALE_LC_TIME, - LOCALE_LC_COLLATE, - LOCALE_LC_MONETARY, - LOCALE_LC_MESSAGES, - LOCALE_LC_PAPER, - LOCALE_LC_NAME, - LOCALE_LC_ADDRESS, - LOCALE_LC_TELEPHONE, - LOCALE_LC_MEASUREMENT, - LOCALE_LC_IDENTIFICATION, - _LOCALE_MAX -}; - -static const char * const names[_LOCALE_MAX] = { - [LOCALE_LANG] = "LANG", - [LOCALE_LANGUAGE] = "LANGUAGE", - [LOCALE_LC_CTYPE] = "LC_CTYPE", - [LOCALE_LC_NUMERIC] = "LC_NUMERIC", - [LOCALE_LC_TIME] = "LC_TIME", - [LOCALE_LC_COLLATE] = "LC_COLLATE", - [LOCALE_LC_MONETARY] = "LC_MONETARY", - [LOCALE_LC_MESSAGES] = "LC_MESSAGES", - [LOCALE_LC_PAPER] = "LC_PAPER", - [LOCALE_LC_NAME] = "LC_NAME", - [LOCALE_LC_ADDRESS] = "LC_ADDRESS", - [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE", - [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT", - [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION" -}; - -typedef struct Context { - char *locale[_LOCALE_MAX]; - - char *x11_layout; - char *x11_model; - char *x11_variant; - char *x11_options; - - char *vc_keymap; - char *vc_keymap_toggle; - - Hashmap *polkit_registry; -} Context; - -static const char* nonempty(const char *s) { - return isempty(s) ? NULL : s; -} - -static bool startswith_comma(const char *s, const char *prefix) { - const char *t; - - return s && (t = startswith(s, prefix)) && (*t == ','); -} - -static void context_free_x11(Context *c) { - c->x11_layout = mfree(c->x11_layout); - c->x11_options = mfree(c->x11_options); - c->x11_model = mfree(c->x11_model); - c->x11_variant = mfree(c->x11_variant); -} - -static void context_free_vconsole(Context *c) { - c->vc_keymap = mfree(c->vc_keymap); - c->vc_keymap_toggle = mfree(c->vc_keymap_toggle); -} - -static void context_free_locale(Context *c) { - int p; - - for (p = 0; p < _LOCALE_MAX; p++) - c->locale[p] = mfree(c->locale[p]); -} - -static void context_free(Context *c) { - context_free_locale(c); - context_free_x11(c); - context_free_vconsole(c); - - bus_verify_polkit_async_registry_free(c->polkit_registry); -}; - -static void locale_simplify(Context *c) { - int p; - - for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++) - if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p])) - c->locale[p] = mfree(c->locale[p]); -} - -static int locale_read_data(Context *c) { - int r; - - context_free_locale(c); - - r = parse_env_file("/etc/locale.conf", NEWLINE, - "LANG", &c->locale[LOCALE_LANG], - "LANGUAGE", &c->locale[LOCALE_LANGUAGE], - "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE], - "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC], - "LC_TIME", &c->locale[LOCALE_LC_TIME], - "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE], - "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY], - "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES], - "LC_PAPER", &c->locale[LOCALE_LC_PAPER], - "LC_NAME", &c->locale[LOCALE_LC_NAME], - "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS], - "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE], - "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT], - "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION], - NULL); - - if (r == -ENOENT) { - int p; - - /* Fill in what we got passed from systemd. */ - for (p = 0; p < _LOCALE_MAX; p++) { - assert(names[p]); - - r = free_and_strdup(&c->locale[p], - nonempty(getenv(names[p]))); - if (r < 0) - return r; - } - - r = 0; - } - - locale_simplify(c); - return r; -} - -static int vconsole_read_data(Context *c) { - int r; - - context_free_vconsole(c); - - r = parse_env_file("/etc/vconsole.conf", NEWLINE, - "KEYMAP", &c->vc_keymap, - "KEYMAP_TOGGLE", &c->vc_keymap_toggle, - NULL); - - if (r < 0 && r != -ENOENT) - return r; - - return 0; -} - -static int x11_read_data(Context *c) { - _cleanup_fclose_ FILE *f; - char line[LINE_MAX]; - bool in_section = false; - int r; - - context_free_x11(c); - - f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re"); - if (!f) - return errno == ENOENT ? 0 : -errno; - - while (fgets(line, sizeof(line), f)) { - char *l; - - char_array_0(line); - l = strstrip(line); - - if (l[0] == 0 || l[0] == '#') - continue; - - if (in_section && first_word(l, "Option")) { - _cleanup_strv_free_ char **a = NULL; - - r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES); - if (r < 0) - return r; - - if (strv_length(a) == 3) { - char **p = NULL; - - if (streq(a[1], "XkbLayout")) - p = &c->x11_layout; - else if (streq(a[1], "XkbModel")) - p = &c->x11_model; - else if (streq(a[1], "XkbVariant")) - p = &c->x11_variant; - else if (streq(a[1], "XkbOptions")) - p = &c->x11_options; - - if (p) { - free(*p); - *p = a[2]; - a[2] = NULL; - } - } - - } else if (!in_section && first_word(l, "Section")) { - _cleanup_strv_free_ char **a = NULL; - - r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES); - if (r < 0) - return -ENOMEM; - - if (strv_length(a) == 2 && streq(a[1], "InputClass")) - in_section = true; - - } else if (in_section && first_word(l, "EndSection")) - in_section = false; - } - - return 0; -} - -static int context_read_data(Context *c) { - int r, q, p; - - r = locale_read_data(c); - q = vconsole_read_data(c); - p = x11_read_data(c); - - return r < 0 ? r : q < 0 ? q : p; -} - -static int locale_write_data(Context *c, char ***settings) { - int r, p; - _cleanup_strv_free_ char **l = NULL; - - /* Set values will be returned as strv in *settings on success. */ - - r = load_env_file(NULL, "/etc/locale.conf", NULL, &l); - if (r < 0 && r != -ENOENT) - return r; - - for (p = 0; p < _LOCALE_MAX; p++) { - _cleanup_free_ char *t = NULL; - char **u; - - assert(names[p]); - - if (isempty(c->locale[p])) { - l = strv_env_unset(l, names[p]); - continue; - } - - if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0) - return -ENOMEM; - - u = strv_env_set(l, t); - if (!u) - return -ENOMEM; - - strv_free(l); - l = u; - } - - if (strv_isempty(l)) { - if (unlink("/etc/locale.conf") < 0) - return errno == ENOENT ? 0 : -errno; - - return 0; - } - - r = write_env_file_label("/etc/locale.conf", l); - if (r < 0) - return r; - - *settings = l; - l = NULL; - return 0; -} - -static int locale_update_system_manager(Context *c, sd_bus *bus) { - _cleanup_free_ char **l_unset = NULL; - _cleanup_strv_free_ char **l_set = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - sd_bus_error error = SD_BUS_ERROR_NULL; - unsigned c_set, c_unset, p; - int r; - - assert(bus); - - l_unset = new0(char*, _LOCALE_MAX); - if (!l_unset) - return -ENOMEM; - - l_set = new0(char*, _LOCALE_MAX); - if (!l_set) - return -ENOMEM; - - for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) { - assert(names[p]); - - if (isempty(c->locale[p])) - l_unset[c_set++] = (char*) names[p]; - else { - char *s; - - if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0) - return -ENOMEM; - - l_set[c_unset++] = s; - } - } - - assert(c_set + c_unset == _LOCALE_MAX); - r = sd_bus_message_new_method_call(bus, &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnsetAndSetEnvironment"); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(m, l_unset); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(m, l_set); - if (r < 0) - return r; - - r = sd_bus_call(bus, m, 0, &error, NULL); - if (r < 0) - log_error_errno(r, "Failed to update the manager environment: %m"); - - return 0; -} - -static int vconsole_write_data(Context *c) { - int r; - _cleanup_strv_free_ char **l = NULL; - - r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l); - if (r < 0 && r != -ENOENT) - return r; - - if (isempty(c->vc_keymap)) - l = strv_env_unset(l, "KEYMAP"); - else { - _cleanup_free_ char *s = NULL; - char **u; - - s = strappend("KEYMAP=", c->vc_keymap); - if (!s) - return -ENOMEM; - - u = strv_env_set(l, s); - if (!u) - return -ENOMEM; - - strv_free(l); - l = u; - } - - if (isempty(c->vc_keymap_toggle)) - l = strv_env_unset(l, "KEYMAP_TOGGLE"); - else { - _cleanup_free_ char *s = NULL; - char **u; - - s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle); - if (!s) - return -ENOMEM; - - u = strv_env_set(l, s); - if (!u) - return -ENOMEM; - - strv_free(l); - l = u; - } - - if (strv_isempty(l)) { - if (unlink("/etc/vconsole.conf") < 0) - return errno == ENOENT ? 0 : -errno; - - return 0; - } - - return write_env_file_label("/etc/vconsole.conf", l); -} - -static int x11_write_data(Context *c) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *temp_path = NULL; - int r; - - if (isempty(c->x11_layout) && - isempty(c->x11_model) && - isempty(c->x11_variant) && - isempty(c->x11_options)) { - - if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) - return errno == ENOENT ? 0 : -errno; - - return 0; - } - - mkdir_p_label("/etc/X11/xorg.conf.d", 0755); - - r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path); - if (r < 0) - return r; - - fchmod(fileno(f), 0644); - - fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n" - "# manually too freely.\n" - "Section \"InputClass\"\n" - " Identifier \"system-keyboard\"\n" - " MatchIsKeyboard \"on\"\n", f); - - if (!isempty(c->x11_layout)) - fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout); - - if (!isempty(c->x11_model)) - fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model); - - if (!isempty(c->x11_variant)) - fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant); - - if (!isempty(c->x11_options)) - fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options); - - fputs("EndSection\n", f); - - r = fflush_and_check(f); - if (r < 0) - goto fail; - - if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) { - r = -errno; - goto fail; - } - - return 0; - -fail: - (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf"); - - if (temp_path) - (void) unlink(temp_path); - - return r; -} - -static int vconsole_reload(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - - r = sd_bus_call_method(bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "RestartUnit", - &error, - NULL, - "ss", "systemd-vconsole-setup.service", "replace"); - - if (r < 0) - log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); - return r; -} - -static const char* strnulldash(const char *s) { - return isempty(s) || streq(s, "-") ? NULL : s; -} - -static int read_next_mapping(const char* filename, - unsigned min_fields, unsigned max_fields, - FILE *f, unsigned *n, char ***a) { - assert(f); - assert(n); - assert(a); - - for (;;) { - char line[LINE_MAX]; - char *l, **b; - int r; - size_t length; - - errno = 0; - if (!fgets(line, sizeof(line), f)) { - - if (ferror(f)) - return errno > 0 ? -errno : -EIO; - - return 0; - } - - (*n)++; - - l = strstrip(line); - if (l[0] == 0 || l[0] == '#') - continue; - - r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES); - if (r < 0) - return r; - - length = strv_length(b); - if (length < min_fields || length > max_fields) { - log_error("Invalid line %s:%u, ignoring.", filename, *n); - strv_free(b); - continue; - - } - - *a = b; - return 1; - } -} - -static int vconsole_convert_to_x11(Context *c, sd_bus *bus) { - bool modified = false; - - assert(bus); - - if (isempty(c->vc_keymap)) { - - modified = - !isempty(c->x11_layout) || - !isempty(c->x11_model) || - !isempty(c->x11_variant) || - !isempty(c->x11_options); - - context_free_x11(c); - } else { - _cleanup_fclose_ FILE *f = NULL; - unsigned n = 0; - - f = fopen(SYSTEMD_KBD_MODEL_MAP, "re"); - if (!f) - return -errno; - - for (;;) { - _cleanup_strv_free_ char **a = NULL; - int r; - - r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a); - if (r < 0) - return r; - if (r == 0) - break; - - if (!streq(c->vc_keymap, a[0])) - continue; - - if (!streq_ptr(c->x11_layout, strnulldash(a[1])) || - !streq_ptr(c->x11_model, strnulldash(a[2])) || - !streq_ptr(c->x11_variant, strnulldash(a[3])) || - !streq_ptr(c->x11_options, strnulldash(a[4]))) { - - if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 || - free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 || - free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 || - free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0) - return -ENOMEM; - - modified = true; - } - - break; - } - } - - if (modified) { - int r; - - r = x11_write_data(c); - if (r < 0) - return log_error_errno(r, "Failed to set X11 keyboard layout: %m"); - - log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'", - strempty(c->x11_layout), - strempty(c->x11_model), - strempty(c->x11_variant), - strempty(c->x11_options)); - - sd_bus_emit_properties_changed(bus, - "/org/freedesktop/locale1", - "org.freedesktop.locale1", - "X11Layout", "X11Model", "X11Variant", "X11Options", NULL); - } else - log_debug("X11 keyboard layout was not modified."); - - return 0; -} - -static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) { - const char *dir; - _cleanup_free_ char *n; - - if (x11_variant) - n = strjoin(x11_layout, "-", x11_variant, NULL); - else - n = strdup(x11_layout); - if (!n) - return -ENOMEM; - - NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) { - _cleanup_free_ char *p = NULL, *pz = NULL; - bool uncompressed; - - p = strjoin(dir, "xkb/", n, ".map", NULL); - pz = strjoin(dir, "xkb/", n, ".map.gz", NULL); - if (!p || !pz) - return -ENOMEM; - - uncompressed = access(p, F_OK) == 0; - if (uncompressed || access(pz, F_OK) == 0) { - log_debug("Found converted keymap %s at %s", - n, uncompressed ? p : pz); - - *new_keymap = n; - n = NULL; - return 1; - } - } - - return 0; -} - -static int find_legacy_keymap(Context *c, char **new_keymap) { - _cleanup_fclose_ FILE *f; - unsigned n = 0; - unsigned best_matching = 0; - int r; - - f = fopen(SYSTEMD_KBD_MODEL_MAP, "re"); - if (!f) - return -errno; - - for (;;) { - _cleanup_strv_free_ char **a = NULL; - unsigned matching = 0; - - r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a); - if (r < 0) - return r; - if (r == 0) - break; - - /* Determine how well matching this entry is */ - if (streq_ptr(c->x11_layout, a[1])) - /* If we got an exact match, this is best */ - matching = 10; - else { - /* We have multiple X layouts, look for an - * entry that matches our key with everything - * but the first layout stripped off. */ - if (startswith_comma(c->x11_layout, a[1])) - matching = 5; - else { - char *x; - - /* If that didn't work, strip off the - * other layouts from the entry, too */ - x = strndupa(a[1], strcspn(a[1], ",")); - if (startswith_comma(c->x11_layout, x)) - matching = 1; - } - } - - if (matching > 0) { - if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) { - matching++; - - if (streq_ptr(c->x11_variant, a[3])) { - matching++; - - if (streq_ptr(c->x11_options, a[4])) - matching++; - } - } - } - - /* The best matching entry so far, then let's save that */ - if (matching >= MAX(best_matching, 1u)) { - log_debug("Found legacy keymap %s with score %u", - a[0], matching); - - if (matching > best_matching) { - best_matching = matching; - - r = free_and_strdup(new_keymap, a[0]); - if (r < 0) - return r; - } - } - } - - if (best_matching < 10 && c->x11_layout) { - /* The best match is only the first part of the X11 - * keymap. Check if we have a converted map which - * matches just the first layout. - */ - char *l, *v = NULL, *converted; - - l = strndupa(c->x11_layout, strcspn(c->x11_layout, ",")); - if (c->x11_variant) - v = strndupa(c->x11_variant, strcspn(c->x11_variant, ",")); - r = find_converted_keymap(l, v, &converted); - if (r < 0) - return r; - if (r > 0) { - free(*new_keymap); - *new_keymap = converted; - } - } - - return 0; -} - -static int find_language_fallback(const char *lang, char **language) { - _cleanup_fclose_ FILE *f = NULL; - unsigned n = 0; - - assert(language); - - f = fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP, "re"); - if (!f) - return -errno; - - for (;;) { - _cleanup_strv_free_ char **a = NULL; - int r; - - r = read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP, 2, 2, f, &n, &a); - if (r <= 0) - return r; - - if (streq(lang, a[0])) { - assert(strv_length(a) == 2); - *language = a[1]; - a[1] = NULL; - return 1; - } - } - - assert_not_reached("should not be here"); -} - -static int x11_convert_to_vconsole(Context *c, sd_bus *bus) { - bool modified = false; - int r; - - assert(bus); - - if (isempty(c->x11_layout)) { - - modified = - !isempty(c->vc_keymap) || - !isempty(c->vc_keymap_toggle); - - context_free_x11(c); - } else { - char *new_keymap = NULL; - - r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap); - if (r < 0) - return r; - else if (r == 0) { - r = find_legacy_keymap(c, &new_keymap); - if (r < 0) - return r; - } - - if (!streq_ptr(c->vc_keymap, new_keymap)) { - free(c->vc_keymap); - c->vc_keymap = new_keymap; - c->vc_keymap_toggle = mfree(c->vc_keymap_toggle); - modified = true; - } else - free(new_keymap); - } - - if (modified) { - r = vconsole_write_data(c); - if (r < 0) - log_error_errno(r, "Failed to set virtual console keymap: %m"); - - log_info("Changed virtual console keymap to '%s' toggle '%s'", - strempty(c->vc_keymap), strempty(c->vc_keymap_toggle)); - - sd_bus_emit_properties_changed(bus, - "/org/freedesktop/locale1", - "org.freedesktop.locale1", - "VConsoleKeymap", "VConsoleKeymapToggle", NULL); - - return vconsole_reload(bus); - } else - log_debug("Virtual console keymap was not modified."); - - return 0; -} - -static int property_get_locale( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Context *c = userdata; - _cleanup_strv_free_ char **l = NULL; - int p, q; - - l = new0(char*, _LOCALE_MAX+1); - if (!l) - return -ENOMEM; - - for (p = 0, q = 0; p < _LOCALE_MAX; p++) { - char *t; - - if (isempty(c->locale[p])) - continue; - - if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0) - return -ENOMEM; - - l[q++] = t; - } - - return sd_bus_message_append_strv(reply, l); -} - -static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) { - Context *c = userdata; - _cleanup_strv_free_ char **l = NULL; - char **i; - const char *lang = NULL; - int interactive; - bool modified = false; - bool have[_LOCALE_MAX] = {}; - int p; - int r; - - assert(m); - assert(c); - - r = bus_message_read_strv_extend(m, &l); - if (r < 0) - return r; - - r = sd_bus_message_read_basic(m, 'b', &interactive); - if (r < 0) - return r; - - /* Check whether a variable changed and if it is valid */ - STRV_FOREACH(i, l) { - bool valid = false; - - for (p = 0; p < _LOCALE_MAX; p++) { - size_t k; - - k = strlen(names[p]); - if (startswith(*i, names[p]) && - (*i)[k] == '=' && - locale_is_valid((*i) + k + 1)) { - valid = true; - have[p] = true; - - if (p == LOCALE_LANG) - lang = (*i) + k + 1; - - if (!streq_ptr(*i + k + 1, c->locale[p])) - modified = true; - - break; - } - } - - if (!valid) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data."); - } - - /* If LANG was specified, but not LANGUAGE, check if we should - * set it based on the language fallback table. */ - if (have[LOCALE_LANG] && !have[LOCALE_LANGUAGE]) { - _cleanup_free_ char *language = NULL; - - assert(lang); - - (void) find_language_fallback(lang, &language); - if (language) { - log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language); - if (!streq_ptr(language, c->locale[LOCALE_LANGUAGE])) { - r = strv_extendf(&l, "LANGUAGE=%s", language); - if (r < 0) - return r; - - have[LOCALE_LANGUAGE] = true; - modified = true; - } - } - } - - /* Check whether a variable is unset */ - if (!modified) - for (p = 0; p < _LOCALE_MAX; p++) - if (!isempty(c->locale[p]) && !have[p]) { - modified = true; - break; - } - - if (modified) { - _cleanup_strv_free_ char **settings = NULL; - - r = bus_verify_polkit_async( - m, - CAP_SYS_ADMIN, - "org.freedesktop.locale1.set-locale", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - STRV_FOREACH(i, l) - for (p = 0; p < _LOCALE_MAX; p++) { - size_t k; - - k = strlen(names[p]); - if (startswith(*i, names[p]) && (*i)[k] == '=') { - r = free_and_strdup(&c->locale[p], *i + k + 1); - if (r < 0) - return r; - break; - } - } - - for (p = 0; p < _LOCALE_MAX; p++) { - if (have[p]) - continue; - - c->locale[p] = mfree(c->locale[p]); - } - - locale_simplify(c); - - r = locale_write_data(c, &settings); - if (r < 0) { - log_error_errno(r, "Failed to set locale: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r)); - } - - locale_update_system_manager(c, sd_bus_message_get_bus(m)); - - if (settings) { - _cleanup_free_ char *line; - - line = strv_join(settings, ", "); - log_info("Changed locale to %s.", strnull(line)); - } else - log_info("Changed locale to unset."); - - (void) sd_bus_emit_properties_changed( - sd_bus_message_get_bus(m), - "/org/freedesktop/locale1", - "org.freedesktop.locale1", - "Locale", NULL); - } else - log_debug("Locale settings were not modified."); - - - return sd_bus_reply_method_return(m, NULL); -} - -static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) { - Context *c = userdata; - const char *keymap, *keymap_toggle; - int convert, interactive; - int r; - - assert(m); - assert(c); - - r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive); - if (r < 0) - return r; - - if (isempty(keymap)) - keymap = NULL; - - if (isempty(keymap_toggle)) - keymap_toggle = NULL; - - if (!streq_ptr(keymap, c->vc_keymap) || - !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) { - - if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) || - (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle)))) - return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data"); - - r = bus_verify_polkit_async( - m, - CAP_SYS_ADMIN, - "org.freedesktop.locale1.set-keyboard", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - if (free_and_strdup(&c->vc_keymap, keymap) < 0 || - free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0) - return -ENOMEM; - - r = vconsole_write_data(c); - if (r < 0) { - log_error_errno(r, "Failed to set virtual console keymap: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r)); - } - - log_info("Changed virtual console keymap to '%s' toggle '%s'", - strempty(c->vc_keymap), strempty(c->vc_keymap_toggle)); - - r = vconsole_reload(sd_bus_message_get_bus(m)); - if (r < 0) - log_error_errno(r, "Failed to request keymap reload: %m"); - - (void) sd_bus_emit_properties_changed( - sd_bus_message_get_bus(m), - "/org/freedesktop/locale1", - "org.freedesktop.locale1", - "VConsoleKeymap", "VConsoleKeymapToggle", NULL); - - if (convert) { - r = vconsole_convert_to_x11(c, sd_bus_message_get_bus(m)); - if (r < 0) - log_error_errno(r, "Failed to convert keymap data: %m"); - } - } - - return sd_bus_reply_method_return(m, NULL); -} - -#ifdef HAVE_XKBCOMMON - -_printf_(3, 0) -static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) { - const char *fmt; - - fmt = strjoina("libxkbcommon: ", format); - log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args); -} - -#define LOAD_SYMBOL(symbol, dl, name) \ - ({ \ - (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \ - (symbol) ? 0 : -EOPNOTSUPP; \ - }) - -static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { - - /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge - * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function - * pointers to the shared library are below: */ - - struct xkb_context* (*symbol_xkb_context_new)(enum xkb_context_flags flags) = NULL; - void (*symbol_xkb_context_unref)(struct xkb_context *context) = NULL; - void (*symbol_xkb_context_set_log_fn)(struct xkb_context *context, void (*log_fn)(struct xkb_context *context, enum xkb_log_level level, const char *format, va_list args)) = NULL; - struct xkb_keymap* (*symbol_xkb_keymap_new_from_names)(struct xkb_context *context, const struct xkb_rule_names *names, enum xkb_keymap_compile_flags flags) = NULL; - void (*symbol_xkb_keymap_unref)(struct xkb_keymap *keymap) = NULL; - - const struct xkb_rule_names rmlvo = { - .model = model, - .layout = layout, - .variant = variant, - .options = options, - }; - struct xkb_context *ctx = NULL; - struct xkb_keymap *km = NULL; - void *dl; - int r; - - /* Compile keymap from RMLVO information to check out its validity */ - - dl = dlopen("libxkbcommon.so.0", RTLD_LAZY); - if (!dl) - return -EOPNOTSUPP; - - r = LOAD_SYMBOL(symbol_xkb_context_new, dl, "xkb_context_new"); - if (r < 0) - goto finish; - - r = LOAD_SYMBOL(symbol_xkb_context_unref, dl, "xkb_context_unref"); - if (r < 0) - goto finish; - - r = LOAD_SYMBOL(symbol_xkb_context_set_log_fn, dl, "xkb_context_set_log_fn"); - if (r < 0) - goto finish; - - r = LOAD_SYMBOL(symbol_xkb_keymap_new_from_names, dl, "xkb_keymap_new_from_names"); - if (r < 0) - goto finish; - - r = LOAD_SYMBOL(symbol_xkb_keymap_unref, dl, "xkb_keymap_unref"); - if (r < 0) - goto finish; - - ctx = symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES); - if (!ctx) { - r = -ENOMEM; - goto finish; - } - - symbol_xkb_context_set_log_fn(ctx, log_xkb); - - km = symbol_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS); - if (!km) { - r = -EINVAL; - goto finish; - } - - r = 0; - -finish: - if (symbol_xkb_keymap_unref && km) - symbol_xkb_keymap_unref(km); - - if (symbol_xkb_context_unref && ctx) - symbol_xkb_context_unref(ctx); - - (void) dlclose(dl); - return r; -} - -#else - -static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { - return 0; -} - -#endif - -static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) { - Context *c = userdata; - const char *layout, *model, *variant, *options; - int convert, interactive; - int r; - - assert(m); - assert(c); - - r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive); - if (r < 0) - return r; - - if (isempty(layout)) - layout = NULL; - - if (isempty(model)) - model = NULL; - - if (isempty(variant)) - variant = NULL; - - if (isempty(options)) - options = NULL; - - if (!streq_ptr(layout, c->x11_layout) || - !streq_ptr(model, c->x11_model) || - !streq_ptr(variant, c->x11_variant) || - !streq_ptr(options, c->x11_options)) { - - if ((layout && !string_is_safe(layout)) || - (model && !string_is_safe(model)) || - (variant && !string_is_safe(variant)) || - (options && !string_is_safe(options))) - return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data"); - - r = bus_verify_polkit_async( - m, - CAP_SYS_ADMIN, - "org.freedesktop.locale1.set-keyboard", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = verify_xkb_rmlvo(model, layout, variant, options); - if (r < 0) { - log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m", - strempty(model), strempty(layout), strempty(variant), strempty(options)); - - if (r == -EOPNOTSUPP) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Local keyboard configuration not supported on this system."); - - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Specified keymap cannot be compiled, refusing as invalid."); - } - - if (free_and_strdup(&c->x11_layout, layout) < 0 || - free_and_strdup(&c->x11_model, model) < 0 || - free_and_strdup(&c->x11_variant, variant) < 0 || - free_and_strdup(&c->x11_options, options) < 0) - return -ENOMEM; - - r = x11_write_data(c); - if (r < 0) { - log_error_errno(r, "Failed to set X11 keyboard layout: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r)); - } - - log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'", - strempty(c->x11_layout), - strempty(c->x11_model), - strempty(c->x11_variant), - strempty(c->x11_options)); - - (void) sd_bus_emit_properties_changed( - sd_bus_message_get_bus(m), - "/org/freedesktop/locale1", - "org.freedesktop.locale1", - "X11Layout", "X11Model", "X11Variant", "X11Options", NULL); - - if (convert) { - r = x11_convert_to_vconsole(c, sd_bus_message_get_bus(m)); - if (r < 0) - log_error_errno(r, "Failed to convert keymap data: %m"); - } - } - - return sd_bus_reply_method_return(m, NULL); -} - -static const sd_bus_vtable locale_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - assert(c); - assert(event); - assert(_bus); - - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to get system bus connection: %m"); - - r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c); - if (r < 0) - return log_error_errno(r, "Failed to register object: %m"); - - r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0); - if (r < 0) - return log_error_errno(r, "Failed to register name: %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"); - - *_bus = bus; - bus = NULL; - - return 0; -} - -int main(int argc, char *argv[]) { - _cleanup_(context_free) Context context = {}; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - mac_selinux_init(); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto finish; - } - - r = sd_event_default(&event); - if (r < 0) { - log_error_errno(r, "Failed to allocate event loop: %m"); - goto finish; - } - - sd_event_set_watchdog(event, true); - - r = connect_bus(&context, event, &bus); - if (r < 0) - goto finish; - - r = context_read_data(&context); - if (r < 0) { - log_error_errno(r, "Failed to read locale data: %m"); - goto finish; - } - - r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/grp-locale/org.freedesktop.locale1.conf b/src/grp-locale/org.freedesktop.locale1.conf deleted file mode 100644 index 79d0ecd2bb..0000000000 --- a/src/grp-locale/org.freedesktop.locale1.conf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/grp-locale/org.freedesktop.locale1.policy.in b/src/grp-locale/org.freedesktop.locale1.policy.in deleted file mode 100644 index df63845e9b..0000000000 --- a/src/grp-locale/org.freedesktop.locale1.policy.in +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - The systemd Project - http://www.freedesktop.org/wiki/Software/systemd - - - <_description>Set system locale - <_message>Authentication is required to set the system locale. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - org.freedesktop.locale1.set-keyboard - - - - <_description>Set system keyboard settings - <_message>Authentication is required to set the system keyboard settings. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - - - diff --git a/src/grp-locale/org.freedesktop.locale1.service b/src/grp-locale/org.freedesktop.locale1.service deleted file mode 100644 index 025f9a0fc2..0000000000 --- a/src/grp-locale/org.freedesktop.locale1.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.locale1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.locale1.service diff --git a/src/grp-locale/systemd-localed/.gitignore b/src/grp-locale/systemd-localed/.gitignore new file mode 100644 index 0000000000..6d9d6089c0 --- /dev/null +++ b/src/grp-locale/systemd-localed/.gitignore @@ -0,0 +1 @@ +/org.freedesktop.locale1.policy diff --git a/src/grp-locale/systemd-localed/Makefile b/src/grp-locale/systemd-localed/Makefile new file mode 100644 index 0000000000..de38bb8576 --- /dev/null +++ b/src/grp-locale/systemd-localed/Makefile @@ -0,0 +1,79 @@ +# -*- 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_LOCALED),) +systemd_localed_SOURCES = \ + src/locale/localed.c + +systemd_localed_LDADD = \ + libshared.la \ + -ldl + +systemd_localed_CFLAGS = \ + $(AM_CFLAGS) \ + $(XKBCOMMON_CFLAGS) + +nodist_systemunit_DATA += \ + units/systemd-localed.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.locale1.busname + +rootlibexec_PROGRAMS += \ + systemd-localed + +dist_dbuspolicy_DATA += \ + src/locale/org.freedesktop.locale1.conf + +dist_dbussystemservice_DATA += \ + src/locale/org.freedesktop.locale1.service + +polkitpolicy_files += \ + src/locale/org.freedesktop.locale1.policy + +SYSTEM_UNIT_ALIASES += \ + systemd-localed.service dbus-org.freedesktop.locale1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.locale1.busname + +dist_pkgdata_DATA = \ + src/locale/kbd-model-map \ + src/locale/language-fallback-map + +endif # ENABLE_LOCALED + +.PHONY: update-kbd-model-map + +polkitpolicy_in_files += \ + src/locale/org.freedesktop.locale1.policy.in + +EXTRA_DIST += \ + units/systemd-localed.service.in + +sd.CPPFLAGS += -DSYSTEMD_KBD_MODEL_MAP=\"$(pkgdatadir)/kbd-model-map\" +sd.CPPFLAGS += -DSYSTEMD_LANGUAGE_FALLBACK_MAP=\"$(pkgdatadir)/language-fallback-map\" + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-locale/systemd-localed/kbd-model-map b/src/grp-locale/systemd-localed/kbd-model-map new file mode 100644 index 0000000000..8fa984f83b --- /dev/null +++ b/src/grp-locale/systemd-localed/kbd-model-map @@ -0,0 +1,68 @@ +# Generated from system-config-keyboard's model list +# consolelayout xlayout xmodel xvariant xoptions +sg ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp +nl nl pc105 - terminate:ctrl_alt_bksp +mk-utf mk,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +trq tr pc105 - terminate:ctrl_alt_bksp +uk gb pc105 - terminate:ctrl_alt_bksp +is-latin1 is pc105 - terminate:ctrl_alt_bksp +de de pc105 - terminate:ctrl_alt_bksp +la-latin1 latam pc105 - terminate:ctrl_alt_bksp +us us pc105+inet - terminate:ctrl_alt_bksp +ko kr pc105 - terminate:ctrl_alt_bksp +ro-std ro pc105 std terminate:ctrl_alt_bksp +de-latin1 de pc105 - terminate:ctrl_alt_bksp +slovene si pc105 - terminate:ctrl_alt_bksp +hu101 hu pc105 qwerty terminate:ctrl_alt_bksp +jp106 jp jp106 - terminate:ctrl_alt_bksp +croat hr pc105 - terminate:ctrl_alt_bksp +it2 it pc105 - terminate:ctrl_alt_bksp +hu hu pc105 - terminate:ctrl_alt_bksp +sr-latin rs pc105 latin terminate:ctrl_alt_bksp +fi fi pc105 - terminate:ctrl_alt_bksp +fr_CH ch pc105 fr terminate:ctrl_alt_bksp +dk-latin1 dk pc105 - terminate:ctrl_alt_bksp +fr fr pc105 - terminate:ctrl_alt_bksp +it it pc105 - terminate:ctrl_alt_bksp +ua-utf ua,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +fr-latin1 fr pc105 - terminate:ctrl_alt_bksp +sg-latin1 ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp +be-latin1 be pc105 - terminate:ctrl_alt_bksp +dk dk pc105 - terminate:ctrl_alt_bksp +fr-pc fr pc105 - terminate:ctrl_alt_bksp +bg_pho-utf8 bg,us pc105 ,phonetic terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +it-ibm it pc105 - terminate:ctrl_alt_bksp +cz-us-qwertz cz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +br-abnt2 br abnt2 - terminate:ctrl_alt_bksp +ro ro pc105 - terminate:ctrl_alt_bksp +us-acentos us pc105 intl terminate:ctrl_alt_bksp +pt-latin1 pt pc105 - terminate:ctrl_alt_bksp +ro-std-cedilla ro pc105 std_cedilla terminate:ctrl_alt_bksp +tj_alt-UTF8 tj pc105 - terminate:ctrl_alt_bksp +de-latin1-nodeadkeys de pc105 nodeadkeys terminate:ctrl_alt_bksp +no no pc105 - terminate:ctrl_alt_bksp +bg_bds-utf8 bg,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +dvorak us pc105 dvorak terminate:ctrl_alt_bksp +dvorak us pc105 dvorak-alt-intl terminate:ctrl_alt_bksp +ru ru,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +cz-lat2 cz pc105 qwerty terminate:ctrl_alt_bksp +pl2 pl pc105 - terminate:ctrl_alt_bksp +es es pc105 - terminate:ctrl_alt_bksp +ro-cedilla ro pc105 cedilla terminate:ctrl_alt_bksp +ie ie pc105 - terminate:ctrl_alt_bksp +et ee pc105 - terminate:ctrl_alt_bksp +sk-qwerty sk pc105 - terminate:ctrl_alt_bksp,qwerty +sk-qwertz sk pc105 - terminate:ctrl_alt_bksp +fr-latin9 fr pc105 latin9 terminate:ctrl_alt_bksp +fr_CH-latin1 ch pc105 fr terminate:ctrl_alt_bksp +cf ca pc105 - terminate:ctrl_alt_bksp +sv-latin1 se pc105 - terminate:ctrl_alt_bksp +sr-cy rs pc105 - terminate:ctrl_alt_bksp +gr gr,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +by by,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +il il pc105 - terminate:ctrl_alt_bksp +kazakh kz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll +lt.baltic lt pc105 - terminate:ctrl_alt_bksp +lt.l4 lt pc105 - terminate:ctrl_alt_bksp +lt lt pc105 - terminate:ctrl_alt_bksp +khmer kh,us pc105 - terminate:ctrl_alt_bksp diff --git a/src/grp-locale/systemd-localed/language-fallback-map b/src/grp-locale/systemd-localed/language-fallback-map new file mode 100644 index 0000000000..d0b02a6b98 --- /dev/null +++ b/src/grp-locale/systemd-localed/language-fallback-map @@ -0,0 +1,13 @@ +csb_PL csb:pl +en_AU en_AU:en_GB +en_IE en_IE:en_GB +en_NZ en_NZ:en_GB +en_ZA en_ZA:en_GB +fr_BE fr_BE:fr_FR +fr_CA fr_CA:fr_FR +fr_CH fr_CH:fr_FR +fr_LU fr_LU:fr_FR +it_CH it_CH:it_IT +mai_IN mai:hi +nds_DE nds:de +szl_PL szl:pl diff --git a/src/grp-locale/systemd-localed/localed.c b/src/grp-locale/systemd-localed/localed.c new file mode 100644 index 0000000000..7bb7f1cf34 --- /dev/null +++ b/src/grp-locale/systemd-localed/localed.c @@ -0,0 +1,1389 @@ +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + Copyright 2013 Kay Sievers + + 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 + +#ifdef HAVE_XKBCOMMON +#include +#include +#endif + +#include + +#include "basic/alloc-util.h" +#include "basic/def.h" +#include "basic/env-util.h" +#include "basic/fd-util.h" +#include "basic/fileio-label.h" +#include "basic/fileio.h" +#include "basic/locale-util.h" +#include "basic/mkdir.h" +#include "basic/path-util.h" +#include "basic/selinux-util.h" +#include "basic/strv.h" +#include "basic/user-util.h" +#include "basic/util.h" +#include "sd-bus/bus-error.h" +#include "sd-bus/bus-message.h" +#include "shared/bus-util.h" + +enum { + /* We don't list LC_ALL here on purpose. People should be + * using LANG instead. */ + LOCALE_LANG, + LOCALE_LANGUAGE, + LOCALE_LC_CTYPE, + LOCALE_LC_NUMERIC, + LOCALE_LC_TIME, + LOCALE_LC_COLLATE, + LOCALE_LC_MONETARY, + LOCALE_LC_MESSAGES, + LOCALE_LC_PAPER, + LOCALE_LC_NAME, + LOCALE_LC_ADDRESS, + LOCALE_LC_TELEPHONE, + LOCALE_LC_MEASUREMENT, + LOCALE_LC_IDENTIFICATION, + _LOCALE_MAX +}; + +static const char * const names[_LOCALE_MAX] = { + [LOCALE_LANG] = "LANG", + [LOCALE_LANGUAGE] = "LANGUAGE", + [LOCALE_LC_CTYPE] = "LC_CTYPE", + [LOCALE_LC_NUMERIC] = "LC_NUMERIC", + [LOCALE_LC_TIME] = "LC_TIME", + [LOCALE_LC_COLLATE] = "LC_COLLATE", + [LOCALE_LC_MONETARY] = "LC_MONETARY", + [LOCALE_LC_MESSAGES] = "LC_MESSAGES", + [LOCALE_LC_PAPER] = "LC_PAPER", + [LOCALE_LC_NAME] = "LC_NAME", + [LOCALE_LC_ADDRESS] = "LC_ADDRESS", + [LOCALE_LC_TELEPHONE] = "LC_TELEPHONE", + [LOCALE_LC_MEASUREMENT] = "LC_MEASUREMENT", + [LOCALE_LC_IDENTIFICATION] = "LC_IDENTIFICATION" +}; + +typedef struct Context { + char *locale[_LOCALE_MAX]; + + char *x11_layout; + char *x11_model; + char *x11_variant; + char *x11_options; + + char *vc_keymap; + char *vc_keymap_toggle; + + Hashmap *polkit_registry; +} Context; + +static const char* nonempty(const char *s) { + return isempty(s) ? NULL : s; +} + +static bool startswith_comma(const char *s, const char *prefix) { + const char *t; + + return s && (t = startswith(s, prefix)) && (*t == ','); +} + +static void context_free_x11(Context *c) { + c->x11_layout = mfree(c->x11_layout); + c->x11_options = mfree(c->x11_options); + c->x11_model = mfree(c->x11_model); + c->x11_variant = mfree(c->x11_variant); +} + +static void context_free_vconsole(Context *c) { + c->vc_keymap = mfree(c->vc_keymap); + c->vc_keymap_toggle = mfree(c->vc_keymap_toggle); +} + +static void context_free_locale(Context *c) { + int p; + + for (p = 0; p < _LOCALE_MAX; p++) + c->locale[p] = mfree(c->locale[p]); +} + +static void context_free(Context *c) { + context_free_locale(c); + context_free_x11(c); + context_free_vconsole(c); + + bus_verify_polkit_async_registry_free(c->polkit_registry); +}; + +static void locale_simplify(Context *c) { + int p; + + for (p = LOCALE_LANG+1; p < _LOCALE_MAX; p++) + if (isempty(c->locale[p]) || streq_ptr(c->locale[LOCALE_LANG], c->locale[p])) + c->locale[p] = mfree(c->locale[p]); +} + +static int locale_read_data(Context *c) { + int r; + + context_free_locale(c); + + r = parse_env_file("/etc/locale.conf", NEWLINE, + "LANG", &c->locale[LOCALE_LANG], + "LANGUAGE", &c->locale[LOCALE_LANGUAGE], + "LC_CTYPE", &c->locale[LOCALE_LC_CTYPE], + "LC_NUMERIC", &c->locale[LOCALE_LC_NUMERIC], + "LC_TIME", &c->locale[LOCALE_LC_TIME], + "LC_COLLATE", &c->locale[LOCALE_LC_COLLATE], + "LC_MONETARY", &c->locale[LOCALE_LC_MONETARY], + "LC_MESSAGES", &c->locale[LOCALE_LC_MESSAGES], + "LC_PAPER", &c->locale[LOCALE_LC_PAPER], + "LC_NAME", &c->locale[LOCALE_LC_NAME], + "LC_ADDRESS", &c->locale[LOCALE_LC_ADDRESS], + "LC_TELEPHONE", &c->locale[LOCALE_LC_TELEPHONE], + "LC_MEASUREMENT", &c->locale[LOCALE_LC_MEASUREMENT], + "LC_IDENTIFICATION", &c->locale[LOCALE_LC_IDENTIFICATION], + NULL); + + if (r == -ENOENT) { + int p; + + /* Fill in what we got passed from systemd. */ + for (p = 0; p < _LOCALE_MAX; p++) { + assert(names[p]); + + r = free_and_strdup(&c->locale[p], + nonempty(getenv(names[p]))); + if (r < 0) + return r; + } + + r = 0; + } + + locale_simplify(c); + return r; +} + +static int vconsole_read_data(Context *c) { + int r; + + context_free_vconsole(c); + + r = parse_env_file("/etc/vconsole.conf", NEWLINE, + "KEYMAP", &c->vc_keymap, + "KEYMAP_TOGGLE", &c->vc_keymap_toggle, + NULL); + + if (r < 0 && r != -ENOENT) + return r; + + return 0; +} + +static int x11_read_data(Context *c) { + _cleanup_fclose_ FILE *f; + char line[LINE_MAX]; + bool in_section = false; + int r; + + context_free_x11(c); + + f = fopen("/etc/X11/xorg.conf.d/00-keyboard.conf", "re"); + if (!f) + return errno == ENOENT ? 0 : -errno; + + while (fgets(line, sizeof(line), f)) { + char *l; + + char_array_0(line); + l = strstrip(line); + + if (l[0] == 0 || l[0] == '#') + continue; + + if (in_section && first_word(l, "Option")) { + _cleanup_strv_free_ char **a = NULL; + + r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES); + if (r < 0) + return r; + + if (strv_length(a) == 3) { + char **p = NULL; + + if (streq(a[1], "XkbLayout")) + p = &c->x11_layout; + else if (streq(a[1], "XkbModel")) + p = &c->x11_model; + else if (streq(a[1], "XkbVariant")) + p = &c->x11_variant; + else if (streq(a[1], "XkbOptions")) + p = &c->x11_options; + + if (p) { + free(*p); + *p = a[2]; + a[2] = NULL; + } + } + + } else if (!in_section && first_word(l, "Section")) { + _cleanup_strv_free_ char **a = NULL; + + r = strv_split_extract(&a, l, WHITESPACE, EXTRACT_QUOTES); + if (r < 0) + return -ENOMEM; + + if (strv_length(a) == 2 && streq(a[1], "InputClass")) + in_section = true; + + } else if (in_section && first_word(l, "EndSection")) + in_section = false; + } + + return 0; +} + +static int context_read_data(Context *c) { + int r, q, p; + + r = locale_read_data(c); + q = vconsole_read_data(c); + p = x11_read_data(c); + + return r < 0 ? r : q < 0 ? q : p; +} + +static int locale_write_data(Context *c, char ***settings) { + int r, p; + _cleanup_strv_free_ char **l = NULL; + + /* Set values will be returned as strv in *settings on success. */ + + r = load_env_file(NULL, "/etc/locale.conf", NULL, &l); + if (r < 0 && r != -ENOENT) + return r; + + for (p = 0; p < _LOCALE_MAX; p++) { + _cleanup_free_ char *t = NULL; + char **u; + + assert(names[p]); + + if (isempty(c->locale[p])) { + l = strv_env_unset(l, names[p]); + continue; + } + + if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0) + return -ENOMEM; + + u = strv_env_set(l, t); + if (!u) + return -ENOMEM; + + strv_free(l); + l = u; + } + + if (strv_isempty(l)) { + if (unlink("/etc/locale.conf") < 0) + return errno == ENOENT ? 0 : -errno; + + return 0; + } + + r = write_env_file_label("/etc/locale.conf", l); + if (r < 0) + return r; + + *settings = l; + l = NULL; + return 0; +} + +static int locale_update_system_manager(Context *c, sd_bus *bus) { + _cleanup_free_ char **l_unset = NULL; + _cleanup_strv_free_ char **l_set = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + unsigned c_set, c_unset, p; + int r; + + assert(bus); + + l_unset = new0(char*, _LOCALE_MAX); + if (!l_unset) + return -ENOMEM; + + l_set = new0(char*, _LOCALE_MAX); + if (!l_set) + return -ENOMEM; + + for (p = 0, c_set = 0, c_unset = 0; p < _LOCALE_MAX; p++) { + assert(names[p]); + + if (isempty(c->locale[p])) + l_unset[c_set++] = (char*) names[p]; + else { + char *s; + + if (asprintf(&s, "%s=%s", names[p], c->locale[p]) < 0) + return -ENOMEM; + + l_set[c_unset++] = s; + } + } + + assert(c_set + c_unset == _LOCALE_MAX); + r = sd_bus_message_new_method_call(bus, &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnsetAndSetEnvironment"); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(m, l_unset); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(m, l_set); + if (r < 0) + return r; + + r = sd_bus_call(bus, m, 0, &error, NULL); + if (r < 0) + log_error_errno(r, "Failed to update the manager environment: %m"); + + return 0; +} + +static int vconsole_write_data(Context *c) { + int r; + _cleanup_strv_free_ char **l = NULL; + + r = load_env_file(NULL, "/etc/vconsole.conf", NULL, &l); + if (r < 0 && r != -ENOENT) + return r; + + if (isempty(c->vc_keymap)) + l = strv_env_unset(l, "KEYMAP"); + else { + _cleanup_free_ char *s = NULL; + char **u; + + s = strappend("KEYMAP=", c->vc_keymap); + if (!s) + return -ENOMEM; + + u = strv_env_set(l, s); + if (!u) + return -ENOMEM; + + strv_free(l); + l = u; + } + + if (isempty(c->vc_keymap_toggle)) + l = strv_env_unset(l, "KEYMAP_TOGGLE"); + else { + _cleanup_free_ char *s = NULL; + char **u; + + s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle); + if (!s) + return -ENOMEM; + + u = strv_env_set(l, s); + if (!u) + return -ENOMEM; + + strv_free(l); + l = u; + } + + if (strv_isempty(l)) { + if (unlink("/etc/vconsole.conf") < 0) + return errno == ENOENT ? 0 : -errno; + + return 0; + } + + return write_env_file_label("/etc/vconsole.conf", l); +} + +static int x11_write_data(Context *c) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *temp_path = NULL; + int r; + + if (isempty(c->x11_layout) && + isempty(c->x11_model) && + isempty(c->x11_variant) && + isempty(c->x11_options)) { + + if (unlink("/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) + return errno == ENOENT ? 0 : -errno; + + return 0; + } + + mkdir_p_label("/etc/X11/xorg.conf.d", 0755); + + r = fopen_temporary("/etc/X11/xorg.conf.d/00-keyboard.conf", &f, &temp_path); + if (r < 0) + return r; + + fchmod(fileno(f), 0644); + + fputs("# Read and parsed by systemd-localed. It's probably wise not to edit this file\n" + "# manually too freely.\n" + "Section \"InputClass\"\n" + " Identifier \"system-keyboard\"\n" + " MatchIsKeyboard \"on\"\n", f); + + if (!isempty(c->x11_layout)) + fprintf(f, " Option \"XkbLayout\" \"%s\"\n", c->x11_layout); + + if (!isempty(c->x11_model)) + fprintf(f, " Option \"XkbModel\" \"%s\"\n", c->x11_model); + + if (!isempty(c->x11_variant)) + fprintf(f, " Option \"XkbVariant\" \"%s\"\n", c->x11_variant); + + if (!isempty(c->x11_options)) + fprintf(f, " Option \"XkbOptions\" \"%s\"\n", c->x11_options); + + fputs("EndSection\n", f); + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, "/etc/X11/xorg.conf.d/00-keyboard.conf") < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + (void) unlink("/etc/X11/xorg.conf.d/00-keyboard.conf"); + + if (temp_path) + (void) unlink(temp_path); + + return r; +} + +static int vconsole_reload(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + + r = sd_bus_call_method(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "RestartUnit", + &error, + NULL, + "ss", "systemd-vconsole-setup.service", "replace"); + + if (r < 0) + log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); + return r; +} + +static const char* strnulldash(const char *s) { + return isempty(s) || streq(s, "-") ? NULL : s; +} + +static int read_next_mapping(const char* filename, + unsigned min_fields, unsigned max_fields, + FILE *f, unsigned *n, char ***a) { + assert(f); + assert(n); + assert(a); + + for (;;) { + char line[LINE_MAX]; + char *l, **b; + int r; + size_t length; + + errno = 0; + if (!fgets(line, sizeof(line), f)) { + + if (ferror(f)) + return errno > 0 ? -errno : -EIO; + + return 0; + } + + (*n)++; + + l = strstrip(line); + if (l[0] == 0 || l[0] == '#') + continue; + + r = strv_split_extract(&b, l, WHITESPACE, EXTRACT_QUOTES); + if (r < 0) + return r; + + length = strv_length(b); + if (length < min_fields || length > max_fields) { + log_error("Invalid line %s:%u, ignoring.", filename, *n); + strv_free(b); + continue; + + } + + *a = b; + return 1; + } +} + +static int vconsole_convert_to_x11(Context *c, sd_bus *bus) { + bool modified = false; + + assert(bus); + + if (isempty(c->vc_keymap)) { + + modified = + !isempty(c->x11_layout) || + !isempty(c->x11_model) || + !isempty(c->x11_variant) || + !isempty(c->x11_options); + + context_free_x11(c); + } else { + _cleanup_fclose_ FILE *f = NULL; + unsigned n = 0; + + f = fopen(SYSTEMD_KBD_MODEL_MAP, "re"); + if (!f) + return -errno; + + for (;;) { + _cleanup_strv_free_ char **a = NULL; + int r; + + r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a); + if (r < 0) + return r; + if (r == 0) + break; + + if (!streq(c->vc_keymap, a[0])) + continue; + + if (!streq_ptr(c->x11_layout, strnulldash(a[1])) || + !streq_ptr(c->x11_model, strnulldash(a[2])) || + !streq_ptr(c->x11_variant, strnulldash(a[3])) || + !streq_ptr(c->x11_options, strnulldash(a[4]))) { + + if (free_and_strdup(&c->x11_layout, strnulldash(a[1])) < 0 || + free_and_strdup(&c->x11_model, strnulldash(a[2])) < 0 || + free_and_strdup(&c->x11_variant, strnulldash(a[3])) < 0 || + free_and_strdup(&c->x11_options, strnulldash(a[4])) < 0) + return -ENOMEM; + + modified = true; + } + + break; + } + } + + if (modified) { + int r; + + r = x11_write_data(c); + if (r < 0) + return log_error_errno(r, "Failed to set X11 keyboard layout: %m"); + + log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'", + strempty(c->x11_layout), + strempty(c->x11_model), + strempty(c->x11_variant), + strempty(c->x11_options)); + + sd_bus_emit_properties_changed(bus, + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "X11Layout", "X11Model", "X11Variant", "X11Options", NULL); + } else + log_debug("X11 keyboard layout was not modified."); + + return 0; +} + +static int find_converted_keymap(const char *x11_layout, const char *x11_variant, char **new_keymap) { + const char *dir; + _cleanup_free_ char *n; + + if (x11_variant) + n = strjoin(x11_layout, "-", x11_variant, NULL); + else + n = strdup(x11_layout); + if (!n) + return -ENOMEM; + + NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) { + _cleanup_free_ char *p = NULL, *pz = NULL; + bool uncompressed; + + p = strjoin(dir, "xkb/", n, ".map", NULL); + pz = strjoin(dir, "xkb/", n, ".map.gz", NULL); + if (!p || !pz) + return -ENOMEM; + + uncompressed = access(p, F_OK) == 0; + if (uncompressed || access(pz, F_OK) == 0) { + log_debug("Found converted keymap %s at %s", + n, uncompressed ? p : pz); + + *new_keymap = n; + n = NULL; + return 1; + } + } + + return 0; +} + +static int find_legacy_keymap(Context *c, char **new_keymap) { + _cleanup_fclose_ FILE *f; + unsigned n = 0; + unsigned best_matching = 0; + int r; + + f = fopen(SYSTEMD_KBD_MODEL_MAP, "re"); + if (!f) + return -errno; + + for (;;) { + _cleanup_strv_free_ char **a = NULL; + unsigned matching = 0; + + r = read_next_mapping(SYSTEMD_KBD_MODEL_MAP, 5, UINT_MAX, f, &n, &a); + if (r < 0) + return r; + if (r == 0) + break; + + /* Determine how well matching this entry is */ + if (streq_ptr(c->x11_layout, a[1])) + /* If we got an exact match, this is best */ + matching = 10; + else { + /* We have multiple X layouts, look for an + * entry that matches our key with everything + * but the first layout stripped off. */ + if (startswith_comma(c->x11_layout, a[1])) + matching = 5; + else { + char *x; + + /* If that didn't work, strip off the + * other layouts from the entry, too */ + x = strndupa(a[1], strcspn(a[1], ",")); + if (startswith_comma(c->x11_layout, x)) + matching = 1; + } + } + + if (matching > 0) { + if (isempty(c->x11_model) || streq_ptr(c->x11_model, a[2])) { + matching++; + + if (streq_ptr(c->x11_variant, a[3])) { + matching++; + + if (streq_ptr(c->x11_options, a[4])) + matching++; + } + } + } + + /* The best matching entry so far, then let's save that */ + if (matching >= MAX(best_matching, 1u)) { + log_debug("Found legacy keymap %s with score %u", + a[0], matching); + + if (matching > best_matching) { + best_matching = matching; + + r = free_and_strdup(new_keymap, a[0]); + if (r < 0) + return r; + } + } + } + + if (best_matching < 10 && c->x11_layout) { + /* The best match is only the first part of the X11 + * keymap. Check if we have a converted map which + * matches just the first layout. + */ + char *l, *v = NULL, *converted; + + l = strndupa(c->x11_layout, strcspn(c->x11_layout, ",")); + if (c->x11_variant) + v = strndupa(c->x11_variant, strcspn(c->x11_variant, ",")); + r = find_converted_keymap(l, v, &converted); + if (r < 0) + return r; + if (r > 0) { + free(*new_keymap); + *new_keymap = converted; + } + } + + return 0; +} + +static int find_language_fallback(const char *lang, char **language) { + _cleanup_fclose_ FILE *f = NULL; + unsigned n = 0; + + assert(language); + + f = fopen(SYSTEMD_LANGUAGE_FALLBACK_MAP, "re"); + if (!f) + return -errno; + + for (;;) { + _cleanup_strv_free_ char **a = NULL; + int r; + + r = read_next_mapping(SYSTEMD_LANGUAGE_FALLBACK_MAP, 2, 2, f, &n, &a); + if (r <= 0) + return r; + + if (streq(lang, a[0])) { + assert(strv_length(a) == 2); + *language = a[1]; + a[1] = NULL; + return 1; + } + } + + assert_not_reached("should not be here"); +} + +static int x11_convert_to_vconsole(Context *c, sd_bus *bus) { + bool modified = false; + int r; + + assert(bus); + + if (isempty(c->x11_layout)) { + + modified = + !isempty(c->vc_keymap) || + !isempty(c->vc_keymap_toggle); + + context_free_x11(c); + } else { + char *new_keymap = NULL; + + r = find_converted_keymap(c->x11_layout, c->x11_variant, &new_keymap); + if (r < 0) + return r; + else if (r == 0) { + r = find_legacy_keymap(c, &new_keymap); + if (r < 0) + return r; + } + + if (!streq_ptr(c->vc_keymap, new_keymap)) { + free(c->vc_keymap); + c->vc_keymap = new_keymap; + c->vc_keymap_toggle = mfree(c->vc_keymap_toggle); + modified = true; + } else + free(new_keymap); + } + + if (modified) { + r = vconsole_write_data(c); + if (r < 0) + log_error_errno(r, "Failed to set virtual console keymap: %m"); + + log_info("Changed virtual console keymap to '%s' toggle '%s'", + strempty(c->vc_keymap), strempty(c->vc_keymap_toggle)); + + sd_bus_emit_properties_changed(bus, + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "VConsoleKeymap", "VConsoleKeymapToggle", NULL); + + return vconsole_reload(bus); + } else + log_debug("Virtual console keymap was not modified."); + + return 0; +} + +static int property_get_locale( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Context *c = userdata; + _cleanup_strv_free_ char **l = NULL; + int p, q; + + l = new0(char*, _LOCALE_MAX+1); + if (!l) + return -ENOMEM; + + for (p = 0, q = 0; p < _LOCALE_MAX; p++) { + char *t; + + if (isempty(c->locale[p])) + continue; + + if (asprintf(&t, "%s=%s", names[p], c->locale[p]) < 0) + return -ENOMEM; + + l[q++] = t; + } + + return sd_bus_message_append_strv(reply, l); +} + +static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = userdata; + _cleanup_strv_free_ char **l = NULL; + char **i; + const char *lang = NULL; + int interactive; + bool modified = false; + bool have[_LOCALE_MAX] = {}; + int p; + int r; + + assert(m); + assert(c); + + r = bus_message_read_strv_extend(m, &l); + if (r < 0) + return r; + + r = sd_bus_message_read_basic(m, 'b', &interactive); + if (r < 0) + return r; + + /* Check whether a variable changed and if it is valid */ + STRV_FOREACH(i, l) { + bool valid = false; + + for (p = 0; p < _LOCALE_MAX; p++) { + size_t k; + + k = strlen(names[p]); + if (startswith(*i, names[p]) && + (*i)[k] == '=' && + locale_is_valid((*i) + k + 1)) { + valid = true; + have[p] = true; + + if (p == LOCALE_LANG) + lang = (*i) + k + 1; + + if (!streq_ptr(*i + k + 1, c->locale[p])) + modified = true; + + break; + } + } + + if (!valid) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid Locale data."); + } + + /* If LANG was specified, but not LANGUAGE, check if we should + * set it based on the language fallback table. */ + if (have[LOCALE_LANG] && !have[LOCALE_LANGUAGE]) { + _cleanup_free_ char *language = NULL; + + assert(lang); + + (void) find_language_fallback(lang, &language); + if (language) { + log_debug("Converted LANG=%s to LANGUAGE=%s", lang, language); + if (!streq_ptr(language, c->locale[LOCALE_LANGUAGE])) { + r = strv_extendf(&l, "LANGUAGE=%s", language); + if (r < 0) + return r; + + have[LOCALE_LANGUAGE] = true; + modified = true; + } + } + } + + /* Check whether a variable is unset */ + if (!modified) + for (p = 0; p < _LOCALE_MAX; p++) + if (!isempty(c->locale[p]) && !have[p]) { + modified = true; + break; + } + + if (modified) { + _cleanup_strv_free_ char **settings = NULL; + + r = bus_verify_polkit_async( + m, + CAP_SYS_ADMIN, + "org.freedesktop.locale1.set-locale", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + STRV_FOREACH(i, l) + for (p = 0; p < _LOCALE_MAX; p++) { + size_t k; + + k = strlen(names[p]); + if (startswith(*i, names[p]) && (*i)[k] == '=') { + r = free_and_strdup(&c->locale[p], *i + k + 1); + if (r < 0) + return r; + break; + } + } + + for (p = 0; p < _LOCALE_MAX; p++) { + if (have[p]) + continue; + + c->locale[p] = mfree(c->locale[p]); + } + + locale_simplify(c); + + r = locale_write_data(c, &settings); + if (r < 0) { + log_error_errno(r, "Failed to set locale: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to set locale: %s", strerror(-r)); + } + + locale_update_system_manager(c, sd_bus_message_get_bus(m)); + + if (settings) { + _cleanup_free_ char *line; + + line = strv_join(settings, ", "); + log_info("Changed locale to %s.", strnull(line)); + } else + log_info("Changed locale to unset."); + + (void) sd_bus_emit_properties_changed( + sd_bus_message_get_bus(m), + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "Locale", NULL); + } else + log_debug("Locale settings were not modified."); + + + return sd_bus_reply_method_return(m, NULL); +} + +static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = userdata; + const char *keymap, *keymap_toggle; + int convert, interactive; + int r; + + assert(m); + assert(c); + + r = sd_bus_message_read(m, "ssbb", &keymap, &keymap_toggle, &convert, &interactive); + if (r < 0) + return r; + + if (isempty(keymap)) + keymap = NULL; + + if (isempty(keymap_toggle)) + keymap_toggle = NULL; + + if (!streq_ptr(keymap, c->vc_keymap) || + !streq_ptr(keymap_toggle, c->vc_keymap_toggle)) { + + if ((keymap && (!filename_is_valid(keymap) || !string_is_safe(keymap))) || + (keymap_toggle && (!filename_is_valid(keymap_toggle) || !string_is_safe(keymap_toggle)))) + return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keymap data"); + + r = bus_verify_polkit_async( + m, + CAP_SYS_ADMIN, + "org.freedesktop.locale1.set-keyboard", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + if (free_and_strdup(&c->vc_keymap, keymap) < 0 || + free_and_strdup(&c->vc_keymap_toggle, keymap_toggle) < 0) + return -ENOMEM; + + r = vconsole_write_data(c); + if (r < 0) { + log_error_errno(r, "Failed to set virtual console keymap: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to set virtual console keymap: %s", strerror(-r)); + } + + log_info("Changed virtual console keymap to '%s' toggle '%s'", + strempty(c->vc_keymap), strempty(c->vc_keymap_toggle)); + + r = vconsole_reload(sd_bus_message_get_bus(m)); + if (r < 0) + log_error_errno(r, "Failed to request keymap reload: %m"); + + (void) sd_bus_emit_properties_changed( + sd_bus_message_get_bus(m), + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "VConsoleKeymap", "VConsoleKeymapToggle", NULL); + + if (convert) { + r = vconsole_convert_to_x11(c, sd_bus_message_get_bus(m)); + if (r < 0) + log_error_errno(r, "Failed to convert keymap data: %m"); + } + } + + return sd_bus_reply_method_return(m, NULL); +} + +#ifdef HAVE_XKBCOMMON + +_printf_(3, 0) +static void log_xkb(struct xkb_context *ctx, enum xkb_log_level lvl, const char *format, va_list args) { + const char *fmt; + + fmt = strjoina("libxkbcommon: ", format); + log_internalv(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, fmt, args); +} + +#define LOAD_SYMBOL(symbol, dl, name) \ + ({ \ + (symbol) = (typeof(symbol)) dlvsym((dl), (name), "V_0.5.0"); \ + (symbol) ? 0 : -EOPNOTSUPP; \ + }) + +static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { + + /* We dlopen() the library in order to make the dependency soft. The library (and what it pulls in) is huge + * after all, hence let's support XKB maps when the library is around, and refuse otherwise. The function + * pointers to the shared library are below: */ + + struct xkb_context* (*symbol_xkb_context_new)(enum xkb_context_flags flags) = NULL; + void (*symbol_xkb_context_unref)(struct xkb_context *context) = NULL; + void (*symbol_xkb_context_set_log_fn)(struct xkb_context *context, void (*log_fn)(struct xkb_context *context, enum xkb_log_level level, const char *format, va_list args)) = NULL; + struct xkb_keymap* (*symbol_xkb_keymap_new_from_names)(struct xkb_context *context, const struct xkb_rule_names *names, enum xkb_keymap_compile_flags flags) = NULL; + void (*symbol_xkb_keymap_unref)(struct xkb_keymap *keymap) = NULL; + + const struct xkb_rule_names rmlvo = { + .model = model, + .layout = layout, + .variant = variant, + .options = options, + }; + struct xkb_context *ctx = NULL; + struct xkb_keymap *km = NULL; + void *dl; + int r; + + /* Compile keymap from RMLVO information to check out its validity */ + + dl = dlopen("libxkbcommon.so.0", RTLD_LAZY); + if (!dl) + return -EOPNOTSUPP; + + r = LOAD_SYMBOL(symbol_xkb_context_new, dl, "xkb_context_new"); + if (r < 0) + goto finish; + + r = LOAD_SYMBOL(symbol_xkb_context_unref, dl, "xkb_context_unref"); + if (r < 0) + goto finish; + + r = LOAD_SYMBOL(symbol_xkb_context_set_log_fn, dl, "xkb_context_set_log_fn"); + if (r < 0) + goto finish; + + r = LOAD_SYMBOL(symbol_xkb_keymap_new_from_names, dl, "xkb_keymap_new_from_names"); + if (r < 0) + goto finish; + + r = LOAD_SYMBOL(symbol_xkb_keymap_unref, dl, "xkb_keymap_unref"); + if (r < 0) + goto finish; + + ctx = symbol_xkb_context_new(XKB_CONTEXT_NO_ENVIRONMENT_NAMES); + if (!ctx) { + r = -ENOMEM; + goto finish; + } + + symbol_xkb_context_set_log_fn(ctx, log_xkb); + + km = symbol_xkb_keymap_new_from_names(ctx, &rmlvo, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!km) { + r = -EINVAL; + goto finish; + } + + r = 0; + +finish: + if (symbol_xkb_keymap_unref && km) + symbol_xkb_keymap_unref(km); + + if (symbol_xkb_context_unref && ctx) + symbol_xkb_context_unref(ctx); + + (void) dlclose(dl); + return r; +} + +#else + +static int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options) { + return 0; +} + +#endif + +static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = userdata; + const char *layout, *model, *variant, *options; + int convert, interactive; + int r; + + assert(m); + assert(c); + + r = sd_bus_message_read(m, "ssssbb", &layout, &model, &variant, &options, &convert, &interactive); + if (r < 0) + return r; + + if (isempty(layout)) + layout = NULL; + + if (isempty(model)) + model = NULL; + + if (isempty(variant)) + variant = NULL; + + if (isempty(options)) + options = NULL; + + if (!streq_ptr(layout, c->x11_layout) || + !streq_ptr(model, c->x11_model) || + !streq_ptr(variant, c->x11_variant) || + !streq_ptr(options, c->x11_options)) { + + if ((layout && !string_is_safe(layout)) || + (model && !string_is_safe(model)) || + (variant && !string_is_safe(variant)) || + (options && !string_is_safe(options))) + return sd_bus_error_set_errnof(error, -EINVAL, "Received invalid keyboard data"); + + r = bus_verify_polkit_async( + m, + CAP_SYS_ADMIN, + "org.freedesktop.locale1.set-keyboard", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = verify_xkb_rmlvo(model, layout, variant, options); + if (r < 0) { + log_error_errno(r, "Cannot compile XKB keymap for new x11 keyboard layout ('%s' / '%s' / '%s' / '%s'): %m", + strempty(model), strempty(layout), strempty(variant), strempty(options)); + + if (r == -EOPNOTSUPP) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Local keyboard configuration not supported on this system."); + + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Specified keymap cannot be compiled, refusing as invalid."); + } + + if (free_and_strdup(&c->x11_layout, layout) < 0 || + free_and_strdup(&c->x11_model, model) < 0 || + free_and_strdup(&c->x11_variant, variant) < 0 || + free_and_strdup(&c->x11_options, options) < 0) + return -ENOMEM; + + r = x11_write_data(c); + if (r < 0) { + log_error_errno(r, "Failed to set X11 keyboard layout: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to set X11 keyboard layout: %s", strerror(-r)); + } + + log_info("Changed X11 keyboard layout to '%s' model '%s' variant '%s' options '%s'", + strempty(c->x11_layout), + strempty(c->x11_model), + strempty(c->x11_variant), + strempty(c->x11_options)); + + (void) sd_bus_emit_properties_changed( + sd_bus_message_get_bus(m), + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + "X11Layout", "X11Model", "X11Variant", "X11Options", NULL); + + if (convert) { + r = x11_convert_to_vconsole(c, sd_bus_message_get_bus(m)); + if (r < 0) + log_error_errno(r, "Failed to convert keymap data: %m"); + } + } + + return sd_bus_reply_method_return(m, NULL); +} + +static const sd_bus_vtable locale_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Locale", "as", property_get_locale, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("X11Layout", "s", NULL, offsetof(Context, x11_layout), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("X11Model", "s", NULL, offsetof(Context, x11_model), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("X11Variant", "s", NULL, offsetof(Context, x11_variant), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("X11Options", "s", NULL, offsetof(Context, x11_options), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("VConsoleKeymap", "s", NULL, offsetof(Context, vc_keymap), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("VConsoleKeymapToggle", "s", NULL, offsetof(Context, vc_keymap_toggle), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_METHOD("SetLocale", "asb", NULL, method_set_locale, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetVConsoleKeyboard", "ssbb", NULL, method_set_vc_keyboard, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetX11Keyboard", "ssssbb", NULL, method_set_x11_keyboard, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + assert(c); + assert(event); + assert(_bus); + + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to get system bus connection: %m"); + + r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/locale1", "org.freedesktop.locale1", locale_vtable, c); + if (r < 0) + return log_error_errno(r, "Failed to register object: %m"); + + r = sd_bus_request_name(bus, "org.freedesktop.locale1", 0); + if (r < 0) + return log_error_errno(r, "Failed to register name: %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"); + + *_bus = bus; + bus = NULL; + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(context_free) Context context = {}; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + mac_selinux_init(); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + r = sd_event_default(&event); + if (r < 0) { + log_error_errno(r, "Failed to allocate event loop: %m"); + goto finish; + } + + sd_event_set_watchdog(event, true); + + r = connect_bus(&context, event, &bus); + if (r < 0) + goto finish; + + r = context_read_data(&context); + if (r < 0) { + log_error_errno(r, "Failed to read locale data: %m"); + goto finish; + } + + r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/grp-locale/systemd-localed/org.freedesktop.locale1.conf b/src/grp-locale/systemd-localed/org.freedesktop.locale1.conf new file mode 100644 index 0000000000..79d0ecd2bb --- /dev/null +++ b/src/grp-locale/systemd-localed/org.freedesktop.locale1.conf @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/grp-locale/systemd-localed/org.freedesktop.locale1.policy.in b/src/grp-locale/systemd-localed/org.freedesktop.locale1.policy.in new file mode 100644 index 0000000000..df63845e9b --- /dev/null +++ b/src/grp-locale/systemd-localed/org.freedesktop.locale1.policy.in @@ -0,0 +1,40 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Set system locale + <_message>Authentication is required to set the system locale. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + org.freedesktop.locale1.set-keyboard + + + + <_description>Set system keyboard settings + <_message>Authentication is required to set the system keyboard settings. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + diff --git a/src/grp-locale/systemd-localed/org.freedesktop.locale1.service b/src/grp-locale/systemd-localed/org.freedesktop.locale1.service new file mode 100644 index 0000000000..025f9a0fc2 --- /dev/null +++ b/src/grp-locale/systemd-localed/org.freedesktop.locale1.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.locale1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.locale1.service diff --git a/src/grp-resolve/Makefile b/src/grp-resolve/Makefile index 4cea7de4c6..7969cc1298 100644 --- a/src/grp-resolve/Makefile +++ b/src/grp-resolve/Makefile @@ -23,6 +23,7 @@ include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk include $(topsrcdir)/build-aux/Makefile.head.mk -nested.subdirs += systemd-resolved nss-resolve +nested.subdirs += libbasic-dns +nested.subdirs += systemd-resolved systemd-resolve nss-resolve include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-resolve/libbasic-dns/Makefile b/src/grp-resolve/libbasic-dns/Makefile new file mode 100644 index 0000000000..3c9fe3e783 --- /dev/null +++ b/src/grp-resolve/libbasic-dns/Makefile @@ -0,0 +1,115 @@ +# -*- 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +$(outdir)/dns_type-list.txt: $(srcdir)/dns-type.h + $(AM_V_GEN)$(SED) -n -r 's/.* DNS_TYPE_(\w+).*/\1/p' <$< >$@ + +$(outdir)/dns_type-to-name.h: $(outdir)/dns_type-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "const char *dns_type_to_string(int type) {\n\tswitch(type) {" } {printf " case DNS_TYPE_%s: return ", $$1; sub(/_/, "-"); printf "\"%s\";\n", $$1 } END{ print " default: return NULL;\n\t}\n}\n" }' <$< >$@ + +$(outdir)/dns_type-from-name.gperf: $(outdir)/dns_type-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct dns_type_name { const char* name; int id; };"; print "%null-strings"; print "%%";} { s=$$1; sub(/_/, "-", s); printf "%s, ", $$s; printf "DNS_TYPE_%s\n", $$1 }' <$< >$@ + +basic_dns_sources = +libbasic_dns_la_SOURCES = \ + 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 + + +tests += \ + test-dns-packet \ + test-resolve-tables \ + test-dnssec + +manual_tests += \ + test-dnssec-complex + +test_resolve_tables_SOURCES = \ + src/resolve/test-resolve-tables.c \ + src/resolve/dns_type-from-name.h \ + src/resolve/dns_type-to-name.h \ + $(basic_dns_sources) \ + src/shared/test-tables.h + +test_resolve_tables_LDADD = \ + libbasic-dns.la \ + libshared.la + +test_dns_packet_SOURCES = \ + src/resolve/test-dns-packet.c \ + $(basic_dns_sources) + +test_dns_packet_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DRESOLVE_TEST_DIR=\"$(abs_top_srcdir)/src/resolve/test-data\" + +test_dns_packet_LDADD = \ + libbasic-dns.la \ + libshared.la + +EXTRA_DIST += \ + src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts \ + src/resolve/test-data/fedoraproject.org.pkts \ + src/resolve/test-data/gandi.net.pkts \ + src/resolve/test-data/google.com.pkts \ + src/resolve/test-data/root.pkts \ + src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts \ + src/resolve/test-data/teamits.com.pkts \ + src/resolve/test-data/zbyszek@fedoraproject.org.pkts \ + src/resolve/test-data/_443._tcp.fedoraproject.org.pkts \ + src/resolve/test-data/kyhwana.org.pkts \ + src/resolve/test-data/fake-caa.pkts + +test_dnssec_SOURCES = \ + src/resolve/test-dnssec.c \ + $(basic_dns_sources) + +test_dnssec_LDADD = \ + libbasic-dns.la \ + 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 + +noinst_LTLIBRARIES = libbasic-dns.la +libbasic_dns_la_LIBADD = libshared.la +$(outdir)/dns-type.lo: $(outdir)/dns_type-from-name.h $(outdir)/dns_type-to-name.h + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-resolve/libbasic-dns/dns-type.c b/src/grp-resolve/libbasic-dns/dns-type.c new file mode 100644 index 0000000000..6d5b9d616f --- /dev/null +++ b/src/grp-resolve/libbasic-dns/dns-type.c @@ -0,0 +1,324 @@ +/*** + 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 "basic/parse-util.h" +#include "basic/string-util.h" + +#include "dns-type.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); +} + +bool dns_type_needs_authentication(uint16_t type) { + + /* Returns true for all (non-obsolete) RR types where records are not useful if they aren't + * authenticated. I.e. everything that contains crypto keys. */ + + return IN_SET(type, + DNS_TYPE_CERT, + DNS_TYPE_SSHFP, + DNS_TYPE_IPSECKEY, + DNS_TYPE_DS, + DNS_TYPE_DNSKEY, + DNS_TYPE_TLSA, + DNS_TYPE_CDNSKEY, + DNS_TYPE_OPENPGPKEY, + DNS_TYPE_CAA); +} + +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/grp-resolve/libbasic-dns/dns-type.h b/src/grp-resolve/libbasic-dns/dns-type.h new file mode 100644 index 0000000000..a8ee105e16 --- /dev/null +++ b/src/grp-resolve/libbasic-dns/dns-type.h @@ -0,0 +1,161 @@ +#pragma once + +/*** + 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 "basic/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 +}; + +#define _DNS_CLASS_STRING_MAX (sizeof "CLASS" + DECIMAL_STR_MAX(uint16_t)) +#define _DNS_TYPE_STRING_MAX (sizeof "CLASS" + DECIMAL_STR_MAX(uint16_t)) + +bool dns_type_is_pseudo(uint16_t type); +bool dns_type_is_valid_query(uint16_t type); +bool dns_type_is_valid_rr(uint16_t type); +bool dns_type_may_redirect(uint16_t type); +bool dns_type_is_dnssec(uint16_t type); +bool dns_type_is_obsolete(uint16_t type); +bool dns_type_may_wildcard(uint16_t type); +bool dns_type_apex_only(uint16_t type); +bool dns_type_needs_authentication(uint16_t type); +int dns_type_to_af(uint16_t type); + +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 class); +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); + +/* https://tools.ietf.org/html/rfc6844#section-5.1 */ +#define CAA_FLAG_CRITICAL (1u << 7) diff --git a/src/grp-resolve/libbasic-dns/resolved-def.h b/src/grp-resolve/libbasic-dns/resolved-def.h new file mode 100644 index 0000000000..c4c1915b18 --- /dev/null +++ b/src/grp-resolve/libbasic-dns/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/grp-resolve/libbasic-dns/resolved-dns-answer.c b/src/grp-resolve/libbasic-dns/resolved-dns-answer.c new file mode 100644 index 0000000000..1ade0507db --- /dev/null +++ b/src/grp-resolve/libbasic-dns/resolved-dns-answer.c @@ -0,0 +1,859 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/string-util.h" +#include "shared/dns-domain.h" + +#include "resolved-dns-answer.h" +#include "resolved-dns-dnssec.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 + * possible, 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/grp-resolve/libbasic-dns/resolved-dns-answer.h b/src/grp-resolve/libbasic-dns/resolved-dns-answer.h new file mode 100644 index 0000000000..92557a410a --- /dev/null +++ b/src/grp-resolve/libbasic-dns/resolved-dns-answer.h @@ -0,0 +1,144 @@ +#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 "basic/macro.h" + +typedef struct DnsAnswer DnsAnswer; +typedef struct DnsAnswerItem DnsAnswerItem; + +#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 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/grp-resolve/libbasic-dns/resolved-dns-dnssec.c b/src/grp-resolve/libbasic-dns/resolved-dns-dnssec.c new file mode 100644 index 0000000000..39afbada25 --- /dev/null +++ b/src/grp-resolve/libbasic-dns/resolved-dns-dnssec.c @@ -0,0 +1,2199 @@ +/*** + 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 "basic/alloc-util.h" +#include "shared/dns-domain.h" +#include "shared/gcrypt-util.h" +#include "basic/hexdecoct.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-packet.h" +#include "basic/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 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 that the RRSet matches the specified "key" in "a", + * using the signature "rrsig" and the key "dnskey". It's + * assumed that 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(false); + + 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(false); + + 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(false); + + 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); + 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 "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/grp-resolve/libbasic-dns/resolved-dns-dnssec.h b/src/grp-resolve/libbasic-dns/resolved-dns-dnssec.h new file mode 100644 index 0000000000..81879e287f --- /dev/null +++ b/src/grp-resolve/libbasic-dns/resolved-dns-dnssec.h @@ -0,0 +1,103 @@ +#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 "shared/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/grp-resolve/libbasic-dns/resolved-dns-packet.c b/src/grp-resolve/libbasic-dns/resolved-dns-packet.c new file mode 100644 index 0000000000..37c0244b7e --- /dev/null +++ b/src/grp-resolve/libbasic-dns/resolved-dns-packet.c @@ -0,0 +1,2256 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/string-table.h" +#include "basic/strv.h" +#include "basic/unaligned.h" +#include "basic/utf8.h" +#include "basic/util.h" +#include "shared/dns-domain.h" + +#include "resolved-dns-packet.h" + +#define EDNS0_OPT_DO (1<<15) + +typedef struct DnsPacketRewinder { + DnsPacket *packet; + size_t saved_rindex; +} DnsPacketRewinder; + +static void rewind_dns_packet(DnsPacketRewinder *rewinder) { + if (rewinder->packet) + dns_packet_rewind(rewinder->packet, rewinder->saved_rindex); +} + +#define INIT_REWINDER(rewinder, p) do { rewinder.packet = p; rewinder.saved_rindex = p->rindex; } while (0) +#define CANCEL_REWINDER(rewinder) do { rewinder.packet = NULL; } while (0) + +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; + + memcpy_safe(((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_CAA: + r = dns_packet_append_uint8(p, rr->caa.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->caa.tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->caa.value, rr->caa.value_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) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + const void *d; + char *t; + uint8_t c; + int r; + + assert(p); + INIT_REWINDER(rewinder, p); + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + return r; + + r = dns_packet_read(p, c, &d, NULL); + if (r < 0) + return r; + + if (memchr(d, 0, c)) + return -EBADMSG; + + t = strndup(d, c); + if (!t) + return -ENOMEM; + + if (!utf8_is_valid(t)) { + free(t); + return -EBADMSG; + } + + *ret = t; + + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + uint8_t c; + int r; + + assert(p); + INIT_REWINDER(rewinder, p); + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + return r; + + r = dns_packet_read(p, c, ret, NULL); + if (r < 0) + return r; + + if (size) + *size = c; + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +int dns_packet_read_name( + DnsPacket *p, + char **_ret, + bool allow_compression, + size_t *start) { + + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + size_t 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); + INIT_REWINDER(rewinder, p); + jump_barrier = p->rindex; + + if (p->refuse_compression) + allow_compression = false; + + for (;;) { + uint8_t c, d; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + return r; + + 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) + return r; + + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (first) + first = false; + else + ret[n++] = '.'; + + r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + 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) + return r; + + ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; + if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) + return -EBADMSG; + + 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 + return -EBADMSG; + } + + if (!GREEDY_REALLOC(ret, allocated, n + 1)) + return -ENOMEM; + + ret[n] = 0; + + if (after_rindex != 0) + p->rindex= after_rindex; + + *_ret = ret; + ret = NULL; + + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +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; + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + int r; + + assert(p); + assert(types); + INIT_REWINDER(rewinder, p); + + r = bitmap_ensure_allocated(types); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &window, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &length, NULL); + if (r < 0) + return r; + + if (length == 0 || length > 32) + return -EBADMSG; + + r = dns_packet_read(p, length, (const void **)&bitmap, NULL); + if (r < 0) + return r; + + 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) + return r; + } + + bit++; + bitmask >>= 1; + } + } + + if (!found) + return -EBADMSG; + + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + int r; + + INIT_REWINDER(rewinder, p); + + while (p->rindex < rewinder.saved_rindex + size) { + r = dns_packet_read_type_window(p, types, NULL); + if (r < 0) + return r; + + /* don't read past end of current RR */ + if (p->rindex > rewinder.saved_rindex + size) + return -EBADMSG; + } + + if (p->rindex != rewinder.saved_rindex + size) + return -EBADMSG; + + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + _cleanup_free_ char *name = NULL; + bool cache_flush = false; + uint16_t class, type; + DnsResourceKey *key; + int r; + + assert(p); + assert(ret); + INIT_REWINDER(rewinder, p); + + r = dns_packet_read_name(p, &name, true, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, &type, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, &class, NULL); + if (r < 0) + return r; + + 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) + return -ENOMEM; + + name = NULL; + *ret = key; + + if (ret_cache_flush) + *ret_cache_flush = cache_flush; + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +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; + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; + size_t offset; + uint16_t rdlength; + bool cache_flush; + int r; + + assert(p); + assert(ret); + + INIT_REWINDER(rewinder, p); + + r = dns_packet_read_key(p, &key, &cache_flush, NULL); + if (r < 0) + return r; + + if (!dns_class_is_valid_rr(key->class) || !dns_type_is_valid_rr(key->type)) + return -EBADMSG; + + rr = dns_resource_record_new(key); + if (!rr) + return -ENOMEM; + + r = dns_packet_read_uint32(p, &rr->ttl, NULL); + if (r < 0) + return r; + + /* 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) + return r; + + if (p->rindex + rdlength > p->size) + return -EBADMSG; + + offset = p->rindex; + + switch (rr->key->type) { + + case DNS_TYPE_SRV: + r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); + if (r < 0) + return r; + r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); + if (r < 0) + return r; + r = dns_packet_read_uint16(p, &rr->srv.port, NULL); + if (r < 0) + return r; + 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) + return r; + + 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) + return r; + + r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); + if (r < 0) + return r; + + 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) + return r; + + 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) + return r; + + if (t == 0) { + rr->loc.version = t; + + r = dns_packet_read_uint8(p, &rr->loc.size, NULL); + if (r < 0) + return r; + + if (!loc_size_ok(rr->loc.size)) + return -EBADMSG; + + r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); + if (r < 0) + return r; + + if (!loc_size_ok(rr->loc.horiz_pre)) + return -EBADMSG; + + r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); + if (r < 0) + return r; + + if (!loc_size_ok(rr->loc.vert_pre)) + return -EBADMSG; + + r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); + if (r < 0) + return r; + + 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) + return r; + + r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL); + if (r < 0) + return r; + + r = dns_packet_read_memdup(p, rdlength - 4, + &rr->ds.digest, &rr->ds.digest_size, + NULL); + if (r < 0) + return r; + + 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 */ + return -EBADMSG; + + break; + + case DNS_TYPE_SSHFP: + r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); + if (r < 0) + return r; + + 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 */ + return -EBADMSG; + + break; + + case DNS_TYPE_DNSKEY: + r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); + if (r < 0) + return r; + + 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 */ + return -EBADMSG; + + break; + + case DNS_TYPE_RRSIG: + r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); + if (r < 0) + return r; + + r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); + if (r < 0) + return r; + + 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 */ + return -EBADMSG; + + 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) + return r; + + r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); + + /* 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) + return r; + + r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL); + if (r < 0) + return r; + + /* this may be zero */ + r = dns_packet_read_uint8(p, &size, NULL); + if (r < 0) + return r; + + r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &size, NULL); + if (r < 0) + return r; + + if (size <= 0) + return -EBADMSG; + + r = dns_packet_read_memdup(p, size, + &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, + NULL); + if (r < 0) + return r; + + r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL); + + /* 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) + return r; + + r = dns_packet_read_uint8(p, &rr->tlsa.selector, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint8(p, &rr->tlsa.matching_type, NULL); + if (r < 0) + return r; + + 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 */ + return -EBADMSG; + + break; + + case DNS_TYPE_CAA: + r = dns_packet_read_uint8(p, &rr->caa.flags, NULL); + if (r < 0) + return r; + + r = dns_packet_read_string(p, &rr->caa.tag, NULL); + if (r < 0) + return r; + + r = dns_packet_read_memdup(p, + rdlength + offset - p->rindex, + &rr->caa.value, &rr->caa.value_size, NULL); + + 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); + + break; + } + if (r < 0) + return r; + if (p->rindex != offset + rdlength) + return -EBADMSG; + + *ret = rr; + rr = NULL; + + if (ret_cache_flush) + *ret_cache_flush = cache_flush; + if (start) + *start = rewinder.saved_rindex; + CANCEL_REWINDER(rewinder); + + return 0; +} + +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; + _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {}; + unsigned n, i; + int r; + + if (p->extracted) + return 0; + + INIT_REWINDER(rewinder, p); + dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); + + n = DNS_PACKET_QDCOUNT(p); + if (n > 0) { + question = dns_question_new(n); + if (!question) + return -ENOMEM; + + 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) + return r; + + if (cache_flush) + return -EBADMSG; + + if (!dns_type_is_valid_query(key->type)) + return -EBADMSG; + + r = dns_question_add(question, key); + if (r < 0) + return r; + } + } + + 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) + return -ENOMEM; + + 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) + return r; + + /* 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) + return r; + } + + /* 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; + + /* no CANCEL, always rewind */ + return 0; +} + +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/grp-resolve/libbasic-dns/resolved-dns-packet.h b/src/grp-resolve/libbasic-dns/resolved-dns-packet.h new file mode 100644 index 0000000000..2e0eba83b6 --- /dev/null +++ b/src/grp-resolve/libbasic-dns/resolved-dns-packet.h @@ -0,0 +1,270 @@ +#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 "basic/hashmap.h" +#include "basic/in-addr-util.h" +#include "basic/macro.h" +#include "basic/sparse-endian.h" + +typedef struct DnsPacket DnsPacket; +typedef struct DnsPacketHeader DnsPacketHeader; + +#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 f|(family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4); + + default: + return f; + } +} diff --git a/src/grp-resolve/libbasic-dns/resolved-dns-question.c b/src/grp-resolve/libbasic-dns/resolved-dns-question.c new file mode 100644 index 0000000000..ee53dbff9d --- /dev/null +++ b/src/grp-resolve/libbasic-dns/resolved-dns-question.c @@ -0,0 +1,469 @@ +/*** + 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 "basic/alloc-util.h" +#include "shared/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/grp-resolve/libbasic-dns/resolved-dns-question.h b/src/grp-resolve/libbasic-dns/resolved-dns-question.h new file mode 100644 index 0000000000..320bf53488 --- /dev/null +++ b/src/grp-resolve/libbasic-dns/resolved-dns-question.h @@ -0,0 +1,70 @@ +#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 "basic/macro.h" + +typedef struct DnsQuestion DnsQuestion; + +#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/grp-resolve/libbasic-dns/resolved-dns-rr.c b/src/grp-resolve/libbasic-dns/resolved-dns-rr.c new file mode 100644 index 0000000000..c6a60b65b7 --- /dev/null +++ b/src/grp-resolve/libbasic-dns/resolved-dns-rr.c @@ -0,0 +1,1595 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/escape.h" +#include "basic/hexdecoct.h" +#include "basic/string-table.h" +#include "basic/string-util.h" +#include "basic/strv.h" +#include "basic/terminal-util.h" +#include "shared/dns-domain.h" + +#include "dns-type.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-rr.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; +} + +const char* dns_resource_key_name(const DnsResourceKey *key) { + const char *name; + + if (!key) + return NULL; + + if (key->_name) + name = key->_name; + else + name = (char*) key + sizeof(DnsResourceKey); + + if (dns_name_is_root(name)) + return "."; + else + return name; +} + +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 +}; + +char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size) { + const char *c, *t; + char *ans = buf; + + /* 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); + t = dns_type_to_string(key->type); + + snprintf(buf, buf_size, "%s %s%s%.0u %s%s%.0u", + dns_resource_key_name(key), + c ?: "", c ? "" : "CLASS", c ? 0 : key->class, + t ?: "", t ? "" : "TYPE", t ? 0 : key->class); + + return ans; +} + +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_CAA: + free(rr->caa.tag); + free(rr->caa.value); + 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); + + case DNS_TYPE_CAA: + return a->caa.flags == b->caa.flags && + streq(a->caa.tag, b->caa.tag) && + FIELD_EQUAL(a->caa, b->caa, value); + + case DNS_TYPE_OPENPGPKEY: + 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 *t = NULL; + char *s, k[DNS_RESOURCE_KEY_STRING_MAX]; + int r; + + assert(rr); + + if (rr->to_string) + return rr->to_string; + + dns_resource_key_to_string(rr->key, k, sizeof(k)); + + 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; + 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 %u %u %s %n", + k, + 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" + " -- Flags:%s%s%s\n" + " -- Key tag: %u", + s, + rr->dnskey.flags & DNSKEY_FLAG_SEP ? " SEP" : "", + rr->dnskey.flags & DNSKEY_FLAG_REVOKE ? " REVOKE" : "", + rr->dnskey.flags & DNSKEY_FLAG_ZONE_KEY ? " ZONE_KEY" : "", + 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; + + 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); + + t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); + if (!t) + return NULL; + + r = asprintf(&s, + "%s %u %u %u %s\n" + " -- Cert. usage: %s\n" + " -- Selector: %s\n" + " -- Matching type: %s", + k, + rr->tlsa.cert_usage, + rr->tlsa.selector, + rr->tlsa.matching_type, + t, + cert_usage, + selector, + matching_type); + if (r < 0) + return NULL; + + break; + } + + case DNS_TYPE_CAA: { + _cleanup_free_ char *value; + + value = octescape(rr->caa.value, rr->caa.value_size); + if (!value) + return NULL; + + r = asprintf(&s, "%s %u %s \"%s\"%s%s%s%.0u", + k, + rr->caa.flags, + rr->caa.tag, + value, + rr->caa.flags ? "\n -- Flags:" : "", + rr->caa.flags & CAA_FLAG_CRITICAL ? " critical" : "", + rr->caa.flags & ~CAA_FLAG_CRITICAL ? " " : "", + rr->caa.flags & ~CAA_FLAG_CRITICAL); + if (r < 0) + return NULL; + + 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; +} + +ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out) { + assert(rr); + assert(out); + + switch(rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + case DNS_TYPE_SRV: + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + case DNS_TYPE_HINFO: + case DNS_TYPE_SPF: + case DNS_TYPE_TXT: + case DNS_TYPE_A: + case DNS_TYPE_AAAA: + case DNS_TYPE_SOA: + case DNS_TYPE_MX: + case DNS_TYPE_LOC: + case DNS_TYPE_DS: + case DNS_TYPE_DNSKEY: + case DNS_TYPE_RRSIG: + case DNS_TYPE_NSEC: + case DNS_TYPE_NSEC3: + return -EINVAL; + + case DNS_TYPE_SSHFP: + *out = rr->sshfp.fingerprint; + return rr->sshfp.fingerprint_size; + + case DNS_TYPE_TLSA: + *out = rr->tlsa.data; + return rr->tlsa.data_size; + + + case DNS_TYPE_OPENPGPKEY: + default: + *out = rr->generic.data; + return rr->generic.data_size; + } +} + +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; +} + +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_CAA: + siphash24_compress(&rr->caa.flags, sizeof(rr->caa.flags), state); + string_hash_func(rr->caa.tag, state); + siphash24_compress(rr->caa.value, rr->caa.value_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/grp-resolve/libbasic-dns/resolved-dns-rr.h b/src/grp-resolve/libbasic-dns/resolved-dns-rr.h new file mode 100644 index 0000000000..daf9c8c210 --- /dev/null +++ b/src/grp-resolve/libbasic-dns/resolved-dns-rr.h @@ -0,0 +1,343 @@ +#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 "basic/bitmap.h" +#include "basic/hashmap.h" +#include "basic/in-addr-util.h" +#include "basic/list.h" +#include "basic/string-util.h" + +#include "dns-type.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 directly, 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; + + /* https://tools.ietf.org/html/rfc6844 */ + struct { + uint8_t flags; + char *tag; + void *value; + size_t value_size; + } caa; + }; +}; + +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); +const char* dns_resource_key_name(const 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); + +/* _DNS_{CLASS,TYPE}_STRING_MAX include one byte for NUL, which we use for space instead below. + * DNS_HOSTNAME_MAX does not include the NUL byte, so we need to add 1. */ +#define DNS_RESOURCE_KEY_STRING_MAX (_DNS_CLASS_STRING_MAX + _DNS_TYPE_STRING_MAX + DNS_HOSTNAME_MAX + 1) + +char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size); +ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out); + +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); + +void dns_resource_record_hash_func(const void *i, struct siphash *state); + +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/grp-resolve/libbasic-dns/test-data/_443._tcp.fedoraproject.org.pkts b/src/grp-resolve/libbasic-dns/test-data/_443._tcp.fedoraproject.org.pkts new file mode 100644 index 0000000000..a383c6286d Binary files /dev/null and b/src/grp-resolve/libbasic-dns/test-data/_443._tcp.fedoraproject.org.pkts differ diff --git a/src/grp-resolve/libbasic-dns/test-data/_openpgpkey.fedoraproject.org.pkts b/src/grp-resolve/libbasic-dns/test-data/_openpgpkey.fedoraproject.org.pkts new file mode 100644 index 0000000000..15de02e997 Binary files /dev/null and b/src/grp-resolve/libbasic-dns/test-data/_openpgpkey.fedoraproject.org.pkts differ diff --git a/src/grp-resolve/libbasic-dns/test-data/fake-caa.pkts b/src/grp-resolve/libbasic-dns/test-data/fake-caa.pkts new file mode 100644 index 0000000000..1c3ecc5491 Binary files /dev/null and b/src/grp-resolve/libbasic-dns/test-data/fake-caa.pkts differ diff --git a/src/grp-resolve/libbasic-dns/test-data/fedoraproject.org.pkts b/src/grp-resolve/libbasic-dns/test-data/fedoraproject.org.pkts new file mode 100644 index 0000000000..17874844d9 Binary files /dev/null and b/src/grp-resolve/libbasic-dns/test-data/fedoraproject.org.pkts differ diff --git a/src/grp-resolve/libbasic-dns/test-data/gandi.net.pkts b/src/grp-resolve/libbasic-dns/test-data/gandi.net.pkts new file mode 100644 index 0000000000..5ef51e0c8e Binary files /dev/null and b/src/grp-resolve/libbasic-dns/test-data/gandi.net.pkts differ diff --git a/src/grp-resolve/libbasic-dns/test-data/google.com.pkts b/src/grp-resolve/libbasic-dns/test-data/google.com.pkts new file mode 100644 index 0000000000..f98c4cd855 Binary files /dev/null and b/src/grp-resolve/libbasic-dns/test-data/google.com.pkts differ diff --git a/src/grp-resolve/libbasic-dns/test-data/kyhwana.org.pkts b/src/grp-resolve/libbasic-dns/test-data/kyhwana.org.pkts new file mode 100644 index 0000000000..e28a725c9a Binary files /dev/null and b/src/grp-resolve/libbasic-dns/test-data/kyhwana.org.pkts differ diff --git a/src/grp-resolve/libbasic-dns/test-data/root.pkts b/src/grp-resolve/libbasic-dns/test-data/root.pkts new file mode 100644 index 0000000000..54ba668c75 Binary files /dev/null and b/src/grp-resolve/libbasic-dns/test-data/root.pkts differ diff --git a/src/grp-resolve/libbasic-dns/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts b/src/grp-resolve/libbasic-dns/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts new file mode 100644 index 0000000000..a854249532 Binary files /dev/null and b/src/grp-resolve/libbasic-dns/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts differ diff --git a/src/grp-resolve/libbasic-dns/test-data/teamits.com.pkts b/src/grp-resolve/libbasic-dns/test-data/teamits.com.pkts new file mode 100644 index 0000000000..11deb39677 Binary files /dev/null and b/src/grp-resolve/libbasic-dns/test-data/teamits.com.pkts differ diff --git a/src/grp-resolve/libbasic-dns/test-data/zbyszek@fedoraproject.org.pkts b/src/grp-resolve/libbasic-dns/test-data/zbyszek@fedoraproject.org.pkts new file mode 100644 index 0000000000..f0a6f982df Binary files /dev/null and b/src/grp-resolve/libbasic-dns/test-data/zbyszek@fedoraproject.org.pkts differ diff --git a/src/grp-resolve/libbasic-dns/test-dns-packet.c b/src/grp-resolve/libbasic-dns/test-dns-packet.c new file mode 100644 index 0000000000..18d04b930d --- /dev/null +++ b/src/grp-resolve/libbasic-dns/test-dns-packet.c @@ -0,0 +1,115 @@ +/*** + This file is part of systemd + + Copyright 2016 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "basic/alloc-util.h" +#include "basic/fileio.h" +#include "basic/glob-util.h" +#include "basic/log.h" +#include "basic/macro.h" +#include "basic/string-util.h" +#include "basic/strv.h" + +#include "resolved-dns-packet.h" +#include "resolved-dns-rr.h" + +#define HASH_KEY SD_ID128_MAKE(d3,1e,48,90,4b,fa,4c,fe,af,9d,d5,a1,d7,2e,8a,b1) + +static uint64_t hash(DnsResourceRecord *rr) { + struct siphash state; + + siphash24_init(&state, HASH_KEY.bytes); + dns_resource_record_hash_func(rr, &state); + return siphash24_finalize(&state); +} + +static void test_packet_from_file(const char* filename, bool canonical) { + _cleanup_free_ char *data = NULL; + size_t data_size, packet_size, offset; + + assert_se(read_full_file(filename, &data, &data_size) >= 0); + assert_se(data); + assert_se(data_size > 8); + + log_info("============== %s %s==============", filename, canonical ? "canonical " : ""); + + for (offset = 0; offset < data_size; offset += 8 + packet_size) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL, *p2 = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL, *rr2 = NULL; + const char *s, *s2; + uint64_t hash1, hash2; + + packet_size = le64toh( *(uint64_t*)(data + offset) ); + assert_se(packet_size > 0); + assert_se(offset + 8 + packet_size <= data_size); + + assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, 0) >= 0); + + assert_se(dns_packet_append_blob(p, data + offset + 8, packet_size, NULL) >= 0); + assert_se(dns_packet_read_rr(p, &rr, NULL, NULL) >= 0); + + s = dns_resource_record_to_string(rr); + assert_se(s); + puts(s); + + hash1 = hash(rr); + + assert_se(dns_resource_record_to_wire_format(rr, canonical) >= 0); + + assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, 0) >= 0); + assert_se(dns_packet_append_blob(p2, rr->wire_format, rr->wire_format_size, NULL) >= 0); + assert_se(dns_packet_read_rr(p2, &rr2, NULL, NULL) >= 0); + + s2 = dns_resource_record_to_string(rr); + assert_se(s2); + assert_se(streq(s, s2)); + + hash2 = hash(rr); + assert_se(hash1 == hash2); + } +} + +int main(int argc, char **argv) { + int i, N; + _cleanup_globfree_ glob_t g = {}; + char **fnames; + + log_parse_environment(); + + if (argc >= 2) { + N = argc - 1; + fnames = argv + 1; + } else { + assert_se(glob(RESOLVE_TEST_DIR "/*.pkts", GLOB_NOSORT, NULL, &g) == 0); + N = g.gl_pathc; + fnames = g.gl_pathv; + } + + for (i = 0; i < N; i++) { + test_packet_from_file(fnames[i], false); + puts(""); + test_packet_from_file(fnames[i], true); + if (i + 1 < N) + puts(""); + } + + return EXIT_SUCCESS; +} diff --git a/src/grp-resolve/libbasic-dns/test-dnssec-complex.c b/src/grp-resolve/libbasic-dns/test-dnssec-complex.c new file mode 100644 index 0000000000..ef78cd1ea5 --- /dev/null +++ b/src/grp-resolve/libbasic-dns/test-dnssec-complex.c @@ -0,0 +1,237 @@ +/*** + 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 + +#include "basic/af-list.h" +#include "basic/alloc-util.h" +#include "basic/random-util.h" +#include "basic/string-util.h" +#include "basic/time-util.h" +#include "sd-bus/bus-common-errors.h" + +#include "dns-type.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/grp-resolve/libbasic-dns/test-dnssec.c b/src/grp-resolve/libbasic-dns/test-dnssec.c new file mode 100644 index 0000000000..1f05196d8e --- /dev/null +++ b/src/grp-resolve/libbasic-dns/test-dnssec.c @@ -0,0 +1,344 @@ +/*** + 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 "basic/alloc-util.h" +#include "basic/hexdecoct.h" +#include "basic/string-util.h" + +#include "resolved-dns-dnssec.h" +#include "resolved-dns-rr.h" + +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); +} + +#ifdef HAVE_GCRYPT + +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_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_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_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); +} + +#endif + +int main(int argc, char*argv[]) { + + test_dnssec_canonicalize(); + +#ifdef HAVE_GCRYPT + test_dnssec_verify_dns_key(); + test_dnssec_verify_rrset(); + test_dnssec_verify_rrset2(); + test_dnssec_nsec3_hash(); +#endif + + return 0; +} diff --git a/src/grp-resolve/libbasic-dns/test-resolve-tables.c b/src/grp-resolve/libbasic-dns/test-resolve-tables.c new file mode 100644 index 0000000000..0eaab70687 --- /dev/null +++ b/src/grp-resolve/libbasic-dns/test-resolve-tables.c @@ -0,0 +1,65 @@ +/*** + 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 "shared/test-tables.h" + +#include "dns-type.h" + +int main(int argc, char **argv) { + uint16_t i; + + test_table_sparse(dns_type, DNS_TYPE); + + log_info("/* DNS_TYPE */"); + for (i = 0; i < _DNS_TYPE_MAX; i++) { + const char *s; + + s = dns_type_to_string(i); + assert_se(s == NULL || strlen(s) < _DNS_TYPE_STRING_MAX); + + if (s) + log_info("%-*s %s%s%s%s%s%s%s%s%s", + (int) _DNS_TYPE_STRING_MAX - 1, s, + dns_type_is_pseudo(i) ? "pseudo " : "", + dns_type_is_valid_query(i) ? "valid_query " : "", + dns_type_is_valid_rr(i) ? "is_valid_rr " : "", + dns_type_may_redirect(i) ? "may_redirect " : "", + dns_type_is_dnssec(i) ? "dnssec " : "", + dns_type_is_obsolete(i) ? "obsolete " : "", + dns_type_may_wildcard(i) ? "wildcard " : "", + dns_type_apex_only(i) ? "apex_only " : "", + dns_type_needs_authentication(i) ? "needs_authentication" : ""); + } + + log_info("/* DNS_CLASS */"); + for (i = 0; i < _DNS_CLASS_MAX; i++) { + const char *s; + + s = dns_class_to_string(i); + assert_se(s == NULL || strlen(s) < _DNS_CLASS_STRING_MAX); + + if (s) + log_info("%-*s %s%s", + (int) _DNS_CLASS_STRING_MAX - 1, s, + dns_class_is_pseudo(i) ? "is_pseudo " : "", + dns_class_is_valid_rr(i) ? "is_valid_rr " : ""); + } + + return EXIT_SUCCESS; +} diff --git a/src/grp-resolve/systemd-resolve/Makefile b/src/grp-resolve/systemd-resolve/Makefile new file mode 100644 index 0000000000..46322830ac --- /dev/null +++ b/src/grp-resolve/systemd-resolve/Makefile @@ -0,0 +1,52 @@ +# -*- 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +basic_dns_sources = + +systemd_resolve_SOURCES = \ + src/resolve/resolve-tool.c \ + $(basic_dns_sources) \ + src/shared/gcrypt-util.c \ + src/shared/gcrypt-util.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 + +dist_bashcompletion_data += \ + shell-completion/bash/systemd-resolve + +dist_zshcompletion_data += \ + shell-completion/zsh/_systemd-resolve + +systemd_resolve_LDADD += libbasic-dns.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-resolve/systemd-resolve/gcrypt-util.c b/src/grp-resolve/systemd-resolve/gcrypt-util.c new file mode 120000 index 0000000000..5b2fae92e2 --- /dev/null +++ b/src/grp-resolve/systemd-resolve/gcrypt-util.c @@ -0,0 +1 @@ +../../libshared/src/gcrypt-util.c \ No newline at end of file diff --git a/src/grp-resolve/systemd-resolve/gcrypt-util.h b/src/grp-resolve/systemd-resolve/gcrypt-util.h new file mode 120000 index 0000000000..e1ad70b84f --- /dev/null +++ b/src/grp-resolve/systemd-resolve/gcrypt-util.h @@ -0,0 +1 @@ +../../libshared/include/shared/gcrypt-util.h \ No newline at end of file diff --git a/src/grp-resolve/systemd-resolve/resolve-tool.c b/src/grp-resolve/systemd-resolve/resolve-tool.c new file mode 100644 index 0000000000..d9f28576b7 --- /dev/null +++ b/src/grp-resolve/systemd-resolve/resolve-tool.c @@ -0,0 +1,1485 @@ +/*** + 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 + +#include "basic/af-list.h" +#include "basic/alloc-util.h" +#include "basic/escape.h" +#include "basic/in-addr-util.h" +#include "basic/parse-util.h" +#include "basic/terminal-util.h" +#include "sd-bus/bus-error.h" +#include "shared/bus-util.h" +#include "shared/gcrypt-util.h" + +#include "resolved-def.h" +#include "resolved-dns-packet.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; + +typedef enum ServiceFamily { + SERVICE_FAMILY_TCP, + SERVICE_FAMILY_UDP, + SERVICE_FAMILY_SCTP, + _SERVICE_FAMILY_INVALID = -1, +} ServiceFamily; +static ServiceFamily arg_service_family = SERVICE_FAMILY_TCP; + +typedef enum RawType { + RAW_NONE, + RAW_PAYLOAD, + RAW_PACKET, +} RawType; +static RawType arg_raw = RAW_NONE; + +static enum { + MODE_RESOLVE_HOST, + MODE_RESOLVE_RECORD, + MODE_RESOLVE_SERVICE, + MODE_RESOLVE_OPENPGP, + MODE_RESOLVE_TLSA, + MODE_STATISTICS, + MODE_RESET_STATISTICS, +} arg_mode = MODE_RESOLVE_HOST; + +static ServiceFamily service_family_from_string(const char *s) { + if (s == NULL || streq(s, "tcp")) + return SERVICE_FAMILY_TCP; + if (streq(s, "udp")) + return SERVICE_FAMILY_UDP; + if (streq(s, "sctp")) + return SERVICE_FAMILY_SCTP; + return _SERVICE_FAMILY_INVALID; +} + +static const char* service_family_to_string(ServiceFamily service) { + switch(service) { + case SERVICE_FAMILY_TCP: + return "_tcp"; + case SERVICE_FAMILY_UDP: + return "_udp"; + case SERVICE_FAMILY_SCTP: + return "_sctp"; + default: + assert_not_reached("invalid service"); + } +} + +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 output_rr_packet(const void *d, size_t l, int ifindex) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + char ifname[IF_NAMESIZE] = ""; + + 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"); + + if (arg_raw == RAW_PAYLOAD) { + void *data; + ssize_t k; + + k = dns_resource_record_payload(rr, &data); + if (k < 0) + return log_error_errno(k, "Cannot dump RR: %m"); + fwrite(data, 1, k, stdout); + } else { + const char *s; + + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); + + 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); + } + + 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; + bool needs_authentication = false; + + 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) { + 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); + + if (arg_raw == RAW_PACKET) { + uint64_t u64 = htole64(l); + + fwrite(&u64, sizeof(u64), 1, stdout); + fwrite(d, 1, l, stdout); + } else { + r = output_rr_packet(d, l, ifindex); + if (r < 0) + return r; + } + + if (dns_type_needs_authentication(t)) + needs_authentication = true; + + 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); + + if ((flags & SD_RESOLVED_AUTHENTICATED) == 0 && needs_authentication) { + fflush(stdout); + + fprintf(stderr, "\n%s" + "WARNING: The resources shown contain cryptographic key data which could not be\n" + " authenticated. It is not suitable to authenticate any communication.\n" + " This is usually indication that DNSSEC authentication was not enabled\n" + " or is not available for the selected protocol or DNS servers.%s\n", + ansi_highlight_red(), + ansi_normal()); + } + + 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 (class == 0) + class = arg_class ?: DNS_CLASS_IN; + if (type == 0) + type = arg_type ?: DNS_TYPE_A; + + 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 resolve_openpgp(sd_bus *bus, const char *address) { + const char *domain, *full; + int r; + _cleanup_free_ char *hashed = NULL; + + assert(bus); + assert(address); + + domain = strrchr(address, '@'); + if (!domain) { + log_error("Address does not contain '@': \"%s\"", address); + return -EINVAL; + } else if (domain == address || domain[1] == '\0') { + log_error("Address starts or ends with '@': \"%s\"", address); + return -EINVAL; + } + domain++; + + r = string_hashsum_sha224(address, domain - 1 - address, &hashed); + if (r < 0) + return log_error_errno(r, "Hashing failed: %m"); + + full = strjoina(hashed, "._openpgpkey.", domain); + log_debug("Looking up \"%s\".", full); + + return resolve_record(bus, full, + arg_class ?: DNS_CLASS_IN, + arg_type ?: DNS_TYPE_OPENPGPKEY); +} + +static int resolve_tlsa(sd_bus *bus, const char *address) { + const char *port; + uint16_t port_num = 443; + _cleanup_free_ char *full = NULL; + int r; + + assert(bus); + assert(address); + + port = strrchr(address, ':'); + if (port) { + r = safe_atou16(port + 1, &port_num); + if (r < 0 || port_num == 0) + return log_error_errno(r, "Invalid port \"%s\".", port + 1); + + address = strndupa(address, port - address); + } + + r = asprintf(&full, "_%u.%s.%s", + port_num, + service_family_to_string(arg_service_family), + address); + if (r < 0) + return log_oom(); + + log_debug("Looking up \"%s\".", full); + + return resolve_record(bus, full, + arg_class ?: DNS_CLASS_IN, + arg_type ?: DNS_TYPE_TLSA); +} + +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("%1$s [OPTIONS...] HOSTNAME|ADDRESS...\n" + "%1$s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n" + "%1$s [OPTIONS...] --openpgp EMAIL@DOMAIN...\n" + "%1$s [OPTIONS...] --statistics\n" + "%1$s [OPTIONS...] --reset-statistics\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=PROTO|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 Resolve address for services (default: yes)\n" + " --service-txt=BOOL Resolve TXT records for services (default: yes)\n" + " --openpgp Query OpenPGP public key\n" + " --tlsa Query TLS public key\n" + " --cname=BOOL Follow CNAME redirects (default: yes)\n" + " --search=BOOL Use search domains for single-label names\n" + " (default: yes)\n" + " --raw[=payload|packet] Dump the answer as binary data\n" + " --legend=BOOL Print headers and additional info (default: yes)\n" + " --statistics Show resolver statistics\n" + " --reset-statistics Reset resolver statistics\n" + , 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_OPENPGP, + ARG_TLSA, + ARG_RAW, + 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 }, + { "openpgp", no_argument, NULL, ARG_OPENPGP }, + { "tlsa", optional_argument, NULL, ARG_TLSA }, + { "raw", optional_argument, NULL, ARG_RAW }, + { "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_OPENPGP: + arg_mode = MODE_RESOLVE_OPENPGP; + break; + + case ARG_TLSA: + arg_mode = MODE_RESOLVE_TLSA; + arg_service_family = service_family_from_string(optarg); + if (arg_service_family < 0) { + log_error("Unknown service family \"%s\".", optarg); + return -EINVAL; + } + break; + + case ARG_RAW: + if (on_tty()) { + log_error("Refusing to write binary data to tty."); + return -ENOTTY; + } + + if (optarg == NULL || streq(optarg, "payload")) + arg_raw = RAW_PAYLOAD; + else if (streq(optarg, "packet")) + arg_raw = RAW_PACKET; + else { + log_error("Unknown --raw specifier \"%s\".", optarg); + return -EINVAL; + } + + arg_legend = false; + break; + + case ARG_CNAME: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --cname= argument."); + SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); + break; + + case ARG_SERVICE_ADDRESS: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --service-address= argument."); + SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); + break; + + case ARG_SERVICE_TXT: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --service-txt= argument."); + SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); + break; + + case ARG_SEARCH: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --search argument."); + SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); + 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_SERVICE) { + 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_RESOLVE_OPENPGP: + if (argc < optind + 1) { + log_error("E-mail address required."); + r = -EINVAL; + goto finish; + + } + + r = 0; + while (optind < argc) { + int k; + + k = resolve_openpgp(bus, argv[optind++]); + if (k < 0) + r = k; + } + break; + + case MODE_RESOLVE_TLSA: + if (argc < optind + 1) { + log_error("Domain name required."); + r = -EINVAL; + goto finish; + + } + + r = 0; + while (optind < argc) { + int k; + + k = resolve_tlsa(bus, argv[optind++]); + if (k < 0) + r = k; + } + 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/grp-resolve/systemd-resolve/systemd-resolve.completion.bash b/src/grp-resolve/systemd-resolve/systemd-resolve.completion.bash new file mode 100644 index 0000000000..0c501c9405 --- /dev/null +++ b/src/grp-resolve/systemd-resolve/systemd-resolve.completion.bash @@ -0,0 +1,64 @@ +# systemd-resolve(1) completion -*- shell-script -*- +# +# This file is part of systemd. +# +# Copyright 2016 Zbigniew Jędrzejewski-Szmek +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# 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 . + +__contains_word () { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +__get_interfaces(){ + { cd /sys/class/net && echo *; } | \ + while read -d' ' -r name; do + [[ "$name" != "lo" ]] && echo "$name" + done +} + +_systemd-resolve() { + local i comps + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + local -A OPTS=( + [STANDALONE]='-h --help --version -4 -6 + --service --openpgp --tlsa --statistics --reset-statistics + --service-address=no --service-txt=no + --cname=no --search=no --legend=no' + [ARG]='-i --interface -p --protocol -t --type -c --class' + ) + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --interface|-i) + comps=$( __get_interfaces ) + ;; + --protocol|-p|--type|-t|--class|-c) + comps=$( systemd-resolve --legend=no "$prev" help; echo help ) + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi +} + +complete -F _systemd-resolve systemd-resolve diff --git a/src/grp-resolve/systemd-resolve/systemd-resolve.completion.zsh b/src/grp-resolve/systemd-resolve/systemd-resolve.completion.zsh new file mode 100644 index 0000000000..c318ab50f1 --- /dev/null +++ b/src/grp-resolve/systemd-resolve/systemd-resolve.completion.zsh @@ -0,0 +1,64 @@ +#compdef systemd-resolve + +# +# This file is part of systemd. +# +# Copyright 2016 Zbigniew Jędrzejewski-Szmek +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# 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 . + +_dns_protocol() { + local -a _protocol + _protocol=( $(_call_program protocol ${service} --legend=no --protocol help; echo help) ) + _values 'protocol' "$_protocol[@]" +} + +_dns_type() { + local -a _type + _type=( $(_call_program type ${service} --legend=no --type help; echo help) ) + _values 'type' "$_type[@]" +} + +_dns_class() { + local -a _class + _class=( $(_call_program class ${service} --legend=no --class help; echo help) ) + _values 'class' "$_class[@]" +} + +_systemd-resolve_none() { + _alternative : \ + 'domain:DNS address:' \ + 'address:email address:' +} + +_arguments \ + {-h,--help}'[Print a short help text and exit]' \ + '--version[Print a short version string and exit]' \ + '--legend=no[Do not show headers and footers]' \ + '-4[Resolve IPv4 addresses]' \ + '-6[Resolve IPv6 addresses]' \ + {-i+,--interface=}'[Look on interface]:interface:_net_interfaces' \ + {-p+,--protocol=}'[Look via protocol]:protocol:_dns_protocol' \ + {-t+,--type=}'[Query RR with DNS type]:type:_dns_type' \ + {-c+,--class=}'[Query RR with DNS class]:class:_dns_class' \ + '--service[Resolve services]' \ + '--service-address=no[Do not resolve address for services]' \ + '--service-txt=no[Do not resolve TXT records for services]' \ + '--openpgp[Query OpenPGP public key]' \ + '--tlsa[Query TLS public key]' \ + '--cname=no[Do not follow CNAME redirects]' \ + '--search=no[Do not use search domains]' \ + '--statistics[Show resolver statistics]' \ + '--reset-statistics[Reset resolver statistics]' \ + '*::default: _systemd-resolve_none' diff --git a/src/grp-resolve/systemd-resolved/Makefile b/src/grp-resolve/systemd-resolved/Makefile index 8574d3f71a..7a2cff2f2e 100644 --- a/src/grp-resolve/systemd-resolved/Makefile +++ b/src/grp-resolve/systemd-resolved/Makefile @@ -23,31 +23,7 @@ include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk include $(topsrcdir)/build-aux/Makefile.head.mk - -$(outdir)/dns_type-list.txt: $(srcdir)/dns-type.h - $(AM_V_GEN)$(SED) -n -r 's/.* DNS_TYPE_(\w+).*/\1/p' <$< >$@ - -$(outdir)/dns_type-to-name.h: $(outdir)/dns_type-list.txt - $(AM_V_GEN)$(AWK) 'BEGIN{ print "const char *dns_type_to_string(int type) {\n\tswitch(type) {" } {printf " case DNS_TYPE_%s: return ", $$1; sub(/_/, "-"); printf "\"%s\";\n", $$1 } END{ print " default: return NULL;\n\t}\n}\n" }' <$< >$@ - -$(outdir)/dns_type-from-name.gperf: $(outdir)/dns_type-list.txt - $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct dns_type_name { const char* name; int id; };"; print "%null-strings"; print "%%";} { s=$$1; sub(/_/, "-", s); printf "%s, ", $$s; printf "DNS_TYPE_%s\n", $$1 }' <$< >$@ - -ifneq ($(ENABLE_RESOLVED),) - -basic_dns_sources = \ - 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 +basic_dns_sources = systemd_resolved_SOURCES = \ src/resolve/resolved.c \ @@ -130,87 +106,6 @@ GENERAL_ALIASES += \ nodist_pkgsysconf_DATA += \ src/resolve/resolved.conf -systemd_resolve_SOURCES = \ - src/resolve/resolve-tool.c \ - $(basic_dns_sources) \ - src/shared/gcrypt-util.c \ - src/shared/gcrypt-util.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 - -dist_bashcompletion_data += \ - shell-completion/bash/systemd-resolve - -dist_zshcompletion_data += \ - shell-completion/zsh/_systemd-resolve - -tests += \ - test-dns-packet \ - test-resolve-tables \ - test-dnssec - -manual_tests += \ - test-dnssec-complex - -test_resolve_tables_SOURCES = \ - src/resolve/test-resolve-tables.c \ - src/resolve/dns_type-from-name.h \ - src/resolve/dns_type-to-name.h \ - $(basic_dns_sources) \ - src/shared/test-tables.h - -test_resolve_tables_LDADD = \ - libshared.la - -test_dns_packet_SOURCES = \ - src/resolve/test-dns-packet.c \ - $(basic_dns_sources) - -test_dns_packet_CPPFLAGS = \ - $(AM_CPPFLAGS) \ - -DRESOLVE_TEST_DIR=\"$(abs_top_srcdir)/src/resolve/test-data\" - -test_dns_packet_LDADD = \ - libshared.la - -EXTRA_DIST += \ - src/resolve/test-data/_openpgpkey.fedoraproject.org.pkts \ - src/resolve/test-data/fedoraproject.org.pkts \ - src/resolve/test-data/gandi.net.pkts \ - src/resolve/test-data/google.com.pkts \ - src/resolve/test-data/root.pkts \ - src/resolve/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts \ - src/resolve/test-data/teamits.com.pkts \ - src/resolve/test-data/zbyszek@fedoraproject.org.pkts \ - src/resolve/test-data/_443._tcp.fedoraproject.org.pkts \ - src/resolve/test-data/kyhwana.org.pkts \ - src/resolve/test-data/fake-caa.pkts - -test_dnssec_SOURCES = \ - src/resolve/test-dnssec.c \ - $(basic_dns_sources) - -test_dnssec_LDADD = \ - libshared.la - -test_dnssec_complex_SOURCES = \ - src/resolve/test-dnssec-complex.c \ - src/resolve/dns-type.c \ - src/resolve/dns-type.h - -test_dnssec_complex_LDADD = \ - libshared.la - -endif # ENABLE_RESOLVED - gperf_txt_sources += \ src/resolve/dns_type-list.txt @@ -221,8 +116,8 @@ EXTRA_DIST += \ units/systemd-resolved.service.m4.in \ src/resolve/resolved.conf.in +systemd_resolved_LDADD += libbasic-dns.la -$(outdir)/dns-type.o: $(outdir)/dns_type-from-name.h $(outdir)/dns_type-to-name.h sd.CPPFLAGS += -DPKGSYSCONFDIR=\"$(pkgsysconfdir)\" include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-resolve/systemd-resolved/dns-type.c b/src/grp-resolve/systemd-resolved/dns-type.c deleted file mode 100644 index 6d5b9d616f..0000000000 --- a/src/grp-resolve/systemd-resolved/dns-type.c +++ /dev/null @@ -1,324 +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 "basic/parse-util.h" -#include "basic/string-util.h" - -#include "dns-type.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); -} - -bool dns_type_needs_authentication(uint16_t type) { - - /* Returns true for all (non-obsolete) RR types where records are not useful if they aren't - * authenticated. I.e. everything that contains crypto keys. */ - - return IN_SET(type, - DNS_TYPE_CERT, - DNS_TYPE_SSHFP, - DNS_TYPE_IPSECKEY, - DNS_TYPE_DS, - DNS_TYPE_DNSKEY, - DNS_TYPE_TLSA, - DNS_TYPE_CDNSKEY, - DNS_TYPE_OPENPGPKEY, - DNS_TYPE_CAA); -} - -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/grp-resolve/systemd-resolved/dns-type.h b/src/grp-resolve/systemd-resolved/dns-type.h deleted file mode 100644 index a8ee105e16..0000000000 --- a/src/grp-resolve/systemd-resolved/dns-type.h +++ /dev/null @@ -1,161 +0,0 @@ -#pragma once - -/*** - 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 "basic/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 -}; - -#define _DNS_CLASS_STRING_MAX (sizeof "CLASS" + DECIMAL_STR_MAX(uint16_t)) -#define _DNS_TYPE_STRING_MAX (sizeof "CLASS" + DECIMAL_STR_MAX(uint16_t)) - -bool dns_type_is_pseudo(uint16_t type); -bool dns_type_is_valid_query(uint16_t type); -bool dns_type_is_valid_rr(uint16_t type); -bool dns_type_may_redirect(uint16_t type); -bool dns_type_is_dnssec(uint16_t type); -bool dns_type_is_obsolete(uint16_t type); -bool dns_type_may_wildcard(uint16_t type); -bool dns_type_apex_only(uint16_t type); -bool dns_type_needs_authentication(uint16_t type); -int dns_type_to_af(uint16_t type); - -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 class); -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); - -/* https://tools.ietf.org/html/rfc6844#section-5.1 */ -#define CAA_FLAG_CRITICAL (1u << 7) diff --git a/src/grp-resolve/systemd-resolved/resolve-tool.c b/src/grp-resolve/systemd-resolved/resolve-tool.c deleted file mode 100644 index d9f28576b7..0000000000 --- a/src/grp-resolve/systemd-resolved/resolve-tool.c +++ /dev/null @@ -1,1485 +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 - -#include "basic/af-list.h" -#include "basic/alloc-util.h" -#include "basic/escape.h" -#include "basic/in-addr-util.h" -#include "basic/parse-util.h" -#include "basic/terminal-util.h" -#include "sd-bus/bus-error.h" -#include "shared/bus-util.h" -#include "shared/gcrypt-util.h" - -#include "resolved-def.h" -#include "resolved-dns-packet.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; - -typedef enum ServiceFamily { - SERVICE_FAMILY_TCP, - SERVICE_FAMILY_UDP, - SERVICE_FAMILY_SCTP, - _SERVICE_FAMILY_INVALID = -1, -} ServiceFamily; -static ServiceFamily arg_service_family = SERVICE_FAMILY_TCP; - -typedef enum RawType { - RAW_NONE, - RAW_PAYLOAD, - RAW_PACKET, -} RawType; -static RawType arg_raw = RAW_NONE; - -static enum { - MODE_RESOLVE_HOST, - MODE_RESOLVE_RECORD, - MODE_RESOLVE_SERVICE, - MODE_RESOLVE_OPENPGP, - MODE_RESOLVE_TLSA, - MODE_STATISTICS, - MODE_RESET_STATISTICS, -} arg_mode = MODE_RESOLVE_HOST; - -static ServiceFamily service_family_from_string(const char *s) { - if (s == NULL || streq(s, "tcp")) - return SERVICE_FAMILY_TCP; - if (streq(s, "udp")) - return SERVICE_FAMILY_UDP; - if (streq(s, "sctp")) - return SERVICE_FAMILY_SCTP; - return _SERVICE_FAMILY_INVALID; -} - -static const char* service_family_to_string(ServiceFamily service) { - switch(service) { - case SERVICE_FAMILY_TCP: - return "_tcp"; - case SERVICE_FAMILY_UDP: - return "_udp"; - case SERVICE_FAMILY_SCTP: - return "_sctp"; - default: - assert_not_reached("invalid service"); - } -} - -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 output_rr_packet(const void *d, size_t l, int ifindex) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - char ifname[IF_NAMESIZE] = ""; - - 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"); - - if (arg_raw == RAW_PAYLOAD) { - void *data; - ssize_t k; - - k = dns_resource_record_payload(rr, &data); - if (k < 0) - return log_error_errno(k, "Cannot dump RR: %m"); - fwrite(data, 1, k, stdout); - } else { - const char *s; - - s = dns_resource_record_to_string(rr); - if (!s) - return log_oom(); - - 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); - } - - 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; - bool needs_authentication = false; - - 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) { - 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); - - if (arg_raw == RAW_PACKET) { - uint64_t u64 = htole64(l); - - fwrite(&u64, sizeof(u64), 1, stdout); - fwrite(d, 1, l, stdout); - } else { - r = output_rr_packet(d, l, ifindex); - if (r < 0) - return r; - } - - if (dns_type_needs_authentication(t)) - needs_authentication = true; - - 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); - - if ((flags & SD_RESOLVED_AUTHENTICATED) == 0 && needs_authentication) { - fflush(stdout); - - fprintf(stderr, "\n%s" - "WARNING: The resources shown contain cryptographic key data which could not be\n" - " authenticated. It is not suitable to authenticate any communication.\n" - " This is usually indication that DNSSEC authentication was not enabled\n" - " or is not available for the selected protocol or DNS servers.%s\n", - ansi_highlight_red(), - ansi_normal()); - } - - 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 (class == 0) - class = arg_class ?: DNS_CLASS_IN; - if (type == 0) - type = arg_type ?: DNS_TYPE_A; - - 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 resolve_openpgp(sd_bus *bus, const char *address) { - const char *domain, *full; - int r; - _cleanup_free_ char *hashed = NULL; - - assert(bus); - assert(address); - - domain = strrchr(address, '@'); - if (!domain) { - log_error("Address does not contain '@': \"%s\"", address); - return -EINVAL; - } else if (domain == address || domain[1] == '\0') { - log_error("Address starts or ends with '@': \"%s\"", address); - return -EINVAL; - } - domain++; - - r = string_hashsum_sha224(address, domain - 1 - address, &hashed); - if (r < 0) - return log_error_errno(r, "Hashing failed: %m"); - - full = strjoina(hashed, "._openpgpkey.", domain); - log_debug("Looking up \"%s\".", full); - - return resolve_record(bus, full, - arg_class ?: DNS_CLASS_IN, - arg_type ?: DNS_TYPE_OPENPGPKEY); -} - -static int resolve_tlsa(sd_bus *bus, const char *address) { - const char *port; - uint16_t port_num = 443; - _cleanup_free_ char *full = NULL; - int r; - - assert(bus); - assert(address); - - port = strrchr(address, ':'); - if (port) { - r = safe_atou16(port + 1, &port_num); - if (r < 0 || port_num == 0) - return log_error_errno(r, "Invalid port \"%s\".", port + 1); - - address = strndupa(address, port - address); - } - - r = asprintf(&full, "_%u.%s.%s", - port_num, - service_family_to_string(arg_service_family), - address); - if (r < 0) - return log_oom(); - - log_debug("Looking up \"%s\".", full); - - return resolve_record(bus, full, - arg_class ?: DNS_CLASS_IN, - arg_type ?: DNS_TYPE_TLSA); -} - -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("%1$s [OPTIONS...] HOSTNAME|ADDRESS...\n" - "%1$s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n" - "%1$s [OPTIONS...] --openpgp EMAIL@DOMAIN...\n" - "%1$s [OPTIONS...] --statistics\n" - "%1$s [OPTIONS...] --reset-statistics\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=PROTO|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 Resolve address for services (default: yes)\n" - " --service-txt=BOOL Resolve TXT records for services (default: yes)\n" - " --openpgp Query OpenPGP public key\n" - " --tlsa Query TLS public key\n" - " --cname=BOOL Follow CNAME redirects (default: yes)\n" - " --search=BOOL Use search domains for single-label names\n" - " (default: yes)\n" - " --raw[=payload|packet] Dump the answer as binary data\n" - " --legend=BOOL Print headers and additional info (default: yes)\n" - " --statistics Show resolver statistics\n" - " --reset-statistics Reset resolver statistics\n" - , 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_OPENPGP, - ARG_TLSA, - ARG_RAW, - 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 }, - { "openpgp", no_argument, NULL, ARG_OPENPGP }, - { "tlsa", optional_argument, NULL, ARG_TLSA }, - { "raw", optional_argument, NULL, ARG_RAW }, - { "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_OPENPGP: - arg_mode = MODE_RESOLVE_OPENPGP; - break; - - case ARG_TLSA: - arg_mode = MODE_RESOLVE_TLSA; - arg_service_family = service_family_from_string(optarg); - if (arg_service_family < 0) { - log_error("Unknown service family \"%s\".", optarg); - return -EINVAL; - } - break; - - case ARG_RAW: - if (on_tty()) { - log_error("Refusing to write binary data to tty."); - return -ENOTTY; - } - - if (optarg == NULL || streq(optarg, "payload")) - arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) - arg_raw = RAW_PACKET; - else { - log_error("Unknown --raw specifier \"%s\".", optarg); - return -EINVAL; - } - - arg_legend = false; - break; - - case ARG_CNAME: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --cname= argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); - break; - - case ARG_SERVICE_ADDRESS: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-address= argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); - break; - - case ARG_SERVICE_TXT: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-txt= argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); - break; - - case ARG_SEARCH: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --search argument."); - SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); - 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_SERVICE) { - 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_RESOLVE_OPENPGP: - if (argc < optind + 1) { - log_error("E-mail address required."); - r = -EINVAL; - goto finish; - - } - - r = 0; - while (optind < argc) { - int k; - - k = resolve_openpgp(bus, argv[optind++]); - if (k < 0) - r = k; - } - break; - - case MODE_RESOLVE_TLSA: - if (argc < optind + 1) { - log_error("Domain name required."); - r = -EINVAL; - goto finish; - - } - - r = 0; - while (optind < argc) { - int k; - - k = resolve_tlsa(bus, argv[optind++]); - if (k < 0) - r = k; - } - 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/grp-resolve/systemd-resolved/resolved-def.h b/src/grp-resolve/systemd-resolved/resolved-def.h deleted file mode 100644 index c4c1915b18..0000000000 --- a/src/grp-resolve/systemd-resolved/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/grp-resolve/systemd-resolved/resolved-dns-answer.c b/src/grp-resolve/systemd-resolved/resolved-dns-answer.c deleted file mode 100644 index 1ade0507db..0000000000 --- a/src/grp-resolve/systemd-resolved/resolved-dns-answer.c +++ /dev/null @@ -1,859 +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 "basic/alloc-util.h" -#include "basic/string-util.h" -#include "shared/dns-domain.h" - -#include "resolved-dns-answer.h" -#include "resolved-dns-dnssec.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 - * possible, 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/grp-resolve/systemd-resolved/resolved-dns-answer.h b/src/grp-resolve/systemd-resolved/resolved-dns-answer.h deleted file mode 100644 index 92557a410a..0000000000 --- a/src/grp-resolve/systemd-resolved/resolved-dns-answer.h +++ /dev/null @@ -1,144 +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 "basic/macro.h" - -typedef struct DnsAnswer DnsAnswer; -typedef struct DnsAnswerItem DnsAnswerItem; - -#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 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/grp-resolve/systemd-resolved/resolved-dns-dnssec.c b/src/grp-resolve/systemd-resolved/resolved-dns-dnssec.c deleted file mode 100644 index 39afbada25..0000000000 --- a/src/grp-resolve/systemd-resolved/resolved-dns-dnssec.c +++ /dev/null @@ -1,2199 +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 "basic/alloc-util.h" -#include "shared/dns-domain.h" -#include "shared/gcrypt-util.h" -#include "basic/hexdecoct.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "basic/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 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 that the RRSet matches the specified "key" in "a", - * using the signature "rrsig" and the key "dnskey". It's - * assumed that 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(false); - - 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(false); - - 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(false); - - 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); - 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 "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/grp-resolve/systemd-resolved/resolved-dns-dnssec.h b/src/grp-resolve/systemd-resolved/resolved-dns-dnssec.h deleted file mode 100644 index 81879e287f..0000000000 --- a/src/grp-resolve/systemd-resolved/resolved-dns-dnssec.h +++ /dev/null @@ -1,103 +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 "shared/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/grp-resolve/systemd-resolved/resolved-dns-packet.c b/src/grp-resolve/systemd-resolved/resolved-dns-packet.c deleted file mode 100644 index 37c0244b7e..0000000000 --- a/src/grp-resolve/systemd-resolved/resolved-dns-packet.c +++ /dev/null @@ -1,2256 +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 "basic/alloc-util.h" -#include "basic/string-table.h" -#include "basic/strv.h" -#include "basic/unaligned.h" -#include "basic/utf8.h" -#include "basic/util.h" -#include "shared/dns-domain.h" - -#include "resolved-dns-packet.h" - -#define EDNS0_OPT_DO (1<<15) - -typedef struct DnsPacketRewinder { - DnsPacket *packet; - size_t saved_rindex; -} DnsPacketRewinder; - -static void rewind_dns_packet(DnsPacketRewinder *rewinder) { - if (rewinder->packet) - dns_packet_rewind(rewinder->packet, rewinder->saved_rindex); -} - -#define INIT_REWINDER(rewinder, p) do { rewinder.packet = p; rewinder.saved_rindex = p->rindex; } while (0) -#define CANCEL_REWINDER(rewinder) do { rewinder.packet = NULL; } while (0) - -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; - - memcpy_safe(((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_CAA: - r = dns_packet_append_uint8(p, rr->caa.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_string(p, rr->caa.tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->caa.value, rr->caa.value_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) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - const void *d; - char *t; - uint8_t c; - int r; - - assert(p); - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - return r; - - r = dns_packet_read(p, c, &d, NULL); - if (r < 0) - return r; - - if (memchr(d, 0, c)) - return -EBADMSG; - - t = strndup(d, c); - if (!t) - return -ENOMEM; - - if (!utf8_is_valid(t)) { - free(t); - return -EBADMSG; - } - - *ret = t; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - uint8_t c; - int r; - - assert(p); - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - return r; - - r = dns_packet_read(p, c, ret, NULL); - if (r < 0) - return r; - - if (size) - *size = c; - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -int dns_packet_read_name( - DnsPacket *p, - char **_ret, - bool allow_compression, - size_t *start) { - - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - size_t 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); - INIT_REWINDER(rewinder, p); - jump_barrier = p->rindex; - - if (p->refuse_compression) - allow_compression = false; - - for (;;) { - uint8_t c, d; - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - return r; - - 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) - return r; - - if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; - - if (first) - first = false; - else - ret[n++] = '.'; - - r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - 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) - return r; - - ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; - if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) - return -EBADMSG; - - 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 - return -EBADMSG; - } - - if (!GREEDY_REALLOC(ret, allocated, n + 1)) - return -ENOMEM; - - ret[n] = 0; - - if (after_rindex != 0) - p->rindex= after_rindex; - - *_ret = ret; - ret = NULL; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -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; - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - int r; - - assert(p); - assert(types); - INIT_REWINDER(rewinder, p); - - r = bitmap_ensure_allocated(types); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &window, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &length, NULL); - if (r < 0) - return r; - - if (length == 0 || length > 32) - return -EBADMSG; - - r = dns_packet_read(p, length, (const void **)&bitmap, NULL); - if (r < 0) - return r; - - 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) - return r; - } - - bit++; - bitmask >>= 1; - } - } - - if (!found) - return -EBADMSG; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - int r; - - INIT_REWINDER(rewinder, p); - - while (p->rindex < rewinder.saved_rindex + size) { - r = dns_packet_read_type_window(p, types, NULL); - if (r < 0) - return r; - - /* don't read past end of current RR */ - if (p->rindex > rewinder.saved_rindex + size) - return -EBADMSG; - } - - if (p->rindex != rewinder.saved_rindex + size) - return -EBADMSG; - - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - _cleanup_free_ char *name = NULL; - bool cache_flush = false; - uint16_t class, type; - DnsResourceKey *key; - int r; - - assert(p); - assert(ret); - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_name(p, &name, true, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &type, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &class, NULL); - if (r < 0) - return r; - - 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) - return -ENOMEM; - - name = NULL; - *ret = key; - - if (ret_cache_flush) - *ret_cache_flush = cache_flush; - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -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; - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder; - size_t offset; - uint16_t rdlength; - bool cache_flush; - int r; - - assert(p); - assert(ret); - - INIT_REWINDER(rewinder, p); - - r = dns_packet_read_key(p, &key, &cache_flush, NULL); - if (r < 0) - return r; - - if (!dns_class_is_valid_rr(key->class) || !dns_type_is_valid_rr(key->type)) - return -EBADMSG; - - rr = dns_resource_record_new(key); - if (!rr) - return -ENOMEM; - - r = dns_packet_read_uint32(p, &rr->ttl, NULL); - if (r < 0) - return r; - - /* 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) - return r; - - if (p->rindex + rdlength > p->size) - return -EBADMSG; - - offset = p->rindex; - - switch (rr->key->type) { - - case DNS_TYPE_SRV: - r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); - if (r < 0) - return r; - r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); - if (r < 0) - return r; - r = dns_packet_read_uint16(p, &rr->srv.port, NULL); - if (r < 0) - return r; - 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) - return r; - - 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) - return r; - - r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); - if (r < 0) - return r; - - 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) - return r; - - 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) - return r; - - if (t == 0) { - rr->loc.version = t; - - r = dns_packet_read_uint8(p, &rr->loc.size, NULL); - if (r < 0) - return r; - - if (!loc_size_ok(rr->loc.size)) - return -EBADMSG; - - r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); - if (r < 0) - return r; - - if (!loc_size_ok(rr->loc.horiz_pre)) - return -EBADMSG; - - r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); - if (r < 0) - return r; - - if (!loc_size_ok(rr->loc.vert_pre)) - return -EBADMSG; - - r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); - if (r < 0) - return r; - - 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) - return r; - - r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, rdlength - 4, - &rr->ds.digest, &rr->ds.digest_size, - NULL); - if (r < 0) - return r; - - 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 */ - return -EBADMSG; - - break; - - case DNS_TYPE_SSHFP: - r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); - if (r < 0) - return r; - - 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 */ - return -EBADMSG; - - break; - - case DNS_TYPE_DNSKEY: - r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); - if (r < 0) - return r; - - 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 */ - return -EBADMSG; - - break; - - case DNS_TYPE_RRSIG: - r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); - if (r < 0) - return r; - - r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); - if (r < 0) - return r; - - 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 */ - return -EBADMSG; - - 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) - return r; - - r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); - - /* 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) - return r; - - r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL); - if (r < 0) - return r; - - /* this may be zero */ - r = dns_packet_read_uint8(p, &size, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &size, NULL); - if (r < 0) - return r; - - if (size <= 0) - return -EBADMSG; - - r = dns_packet_read_memdup(p, size, - &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, - NULL); - if (r < 0) - return r; - - r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL); - - /* 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) - return r; - - r = dns_packet_read_uint8(p, &rr->tlsa.selector, NULL); - if (r < 0) - return r; - - r = dns_packet_read_uint8(p, &rr->tlsa.matching_type, NULL); - if (r < 0) - return r; - - 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 */ - return -EBADMSG; - - break; - - case DNS_TYPE_CAA: - r = dns_packet_read_uint8(p, &rr->caa.flags, NULL); - if (r < 0) - return r; - - r = dns_packet_read_string(p, &rr->caa.tag, NULL); - if (r < 0) - return r; - - r = dns_packet_read_memdup(p, - rdlength + offset - p->rindex, - &rr->caa.value, &rr->caa.value_size, NULL); - - 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); - - break; - } - if (r < 0) - return r; - if (p->rindex != offset + rdlength) - return -EBADMSG; - - *ret = rr; - rr = NULL; - - if (ret_cache_flush) - *ret_cache_flush = cache_flush; - if (start) - *start = rewinder.saved_rindex; - CANCEL_REWINDER(rewinder); - - return 0; -} - -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; - _cleanup_(rewind_dns_packet) DnsPacketRewinder rewinder = {}; - unsigned n, i; - int r; - - if (p->extracted) - return 0; - - INIT_REWINDER(rewinder, p); - dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); - - n = DNS_PACKET_QDCOUNT(p); - if (n > 0) { - question = dns_question_new(n); - if (!question) - return -ENOMEM; - - 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) - return r; - - if (cache_flush) - return -EBADMSG; - - if (!dns_type_is_valid_query(key->type)) - return -EBADMSG; - - r = dns_question_add(question, key); - if (r < 0) - return r; - } - } - - 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) - return -ENOMEM; - - 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) - return r; - - /* 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) - return r; - } - - /* 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; - - /* no CANCEL, always rewind */ - return 0; -} - -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/grp-resolve/systemd-resolved/resolved-dns-packet.h b/src/grp-resolve/systemd-resolved/resolved-dns-packet.h deleted file mode 100644 index 2e0eba83b6..0000000000 --- a/src/grp-resolve/systemd-resolved/resolved-dns-packet.h +++ /dev/null @@ -1,270 +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 "basic/hashmap.h" -#include "basic/in-addr-util.h" -#include "basic/macro.h" -#include "basic/sparse-endian.h" - -typedef struct DnsPacket DnsPacket; -typedef struct DnsPacketHeader DnsPacketHeader; - -#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 f|(family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4); - - default: - return f; - } -} diff --git a/src/grp-resolve/systemd-resolved/resolved-dns-question.c b/src/grp-resolve/systemd-resolved/resolved-dns-question.c deleted file mode 100644 index ee53dbff9d..0000000000 --- a/src/grp-resolve/systemd-resolved/resolved-dns-question.c +++ /dev/null @@ -1,469 +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 "basic/alloc-util.h" -#include "shared/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/grp-resolve/systemd-resolved/resolved-dns-question.h b/src/grp-resolve/systemd-resolved/resolved-dns-question.h deleted file mode 100644 index 320bf53488..0000000000 --- a/src/grp-resolve/systemd-resolved/resolved-dns-question.h +++ /dev/null @@ -1,70 +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 "basic/macro.h" - -typedef struct DnsQuestion DnsQuestion; - -#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/grp-resolve/systemd-resolved/resolved-dns-rr.c b/src/grp-resolve/systemd-resolved/resolved-dns-rr.c deleted file mode 100644 index c6a60b65b7..0000000000 --- a/src/grp-resolve/systemd-resolved/resolved-dns-rr.c +++ /dev/null @@ -1,1595 +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 "basic/alloc-util.h" -#include "basic/escape.h" -#include "basic/hexdecoct.h" -#include "basic/string-table.h" -#include "basic/string-util.h" -#include "basic/strv.h" -#include "basic/terminal-util.h" -#include "shared/dns-domain.h" - -#include "dns-type.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-rr.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; -} - -const char* dns_resource_key_name(const DnsResourceKey *key) { - const char *name; - - if (!key) - return NULL; - - if (key->_name) - name = key->_name; - else - name = (char*) key + sizeof(DnsResourceKey); - - if (dns_name_is_root(name)) - return "."; - else - return name; -} - -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 -}; - -char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size) { - const char *c, *t; - char *ans = buf; - - /* 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); - t = dns_type_to_string(key->type); - - snprintf(buf, buf_size, "%s %s%s%.0u %s%s%.0u", - dns_resource_key_name(key), - c ?: "", c ? "" : "CLASS", c ? 0 : key->class, - t ?: "", t ? "" : "TYPE", t ? 0 : key->class); - - return ans; -} - -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_CAA: - free(rr->caa.tag); - free(rr->caa.value); - 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); - - case DNS_TYPE_CAA: - return a->caa.flags == b->caa.flags && - streq(a->caa.tag, b->caa.tag) && - FIELD_EQUAL(a->caa, b->caa, value); - - case DNS_TYPE_OPENPGPKEY: - 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 *t = NULL; - char *s, k[DNS_RESOURCE_KEY_STRING_MAX]; - int r; - - assert(rr); - - if (rr->to_string) - return rr->to_string; - - dns_resource_key_to_string(rr->key, k, sizeof(k)); - - 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; - 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 %u %u %s %n", - k, - 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" - " -- Flags:%s%s%s\n" - " -- Key tag: %u", - s, - rr->dnskey.flags & DNSKEY_FLAG_SEP ? " SEP" : "", - rr->dnskey.flags & DNSKEY_FLAG_REVOKE ? " REVOKE" : "", - rr->dnskey.flags & DNSKEY_FLAG_ZONE_KEY ? " ZONE_KEY" : "", - 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; - - 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); - - t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); - if (!t) - return NULL; - - r = asprintf(&s, - "%s %u %u %u %s\n" - " -- Cert. usage: %s\n" - " -- Selector: %s\n" - " -- Matching type: %s", - k, - rr->tlsa.cert_usage, - rr->tlsa.selector, - rr->tlsa.matching_type, - t, - cert_usage, - selector, - matching_type); - if (r < 0) - return NULL; - - break; - } - - case DNS_TYPE_CAA: { - _cleanup_free_ char *value; - - value = octescape(rr->caa.value, rr->caa.value_size); - if (!value) - return NULL; - - r = asprintf(&s, "%s %u %s \"%s\"%s%s%s%.0u", - k, - rr->caa.flags, - rr->caa.tag, - value, - rr->caa.flags ? "\n -- Flags:" : "", - rr->caa.flags & CAA_FLAG_CRITICAL ? " critical" : "", - rr->caa.flags & ~CAA_FLAG_CRITICAL ? " " : "", - rr->caa.flags & ~CAA_FLAG_CRITICAL); - if (r < 0) - return NULL; - - 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; -} - -ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out) { - assert(rr); - assert(out); - - switch(rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - case DNS_TYPE_SRV: - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - case DNS_TYPE_HINFO: - case DNS_TYPE_SPF: - case DNS_TYPE_TXT: - case DNS_TYPE_A: - case DNS_TYPE_AAAA: - case DNS_TYPE_SOA: - case DNS_TYPE_MX: - case DNS_TYPE_LOC: - case DNS_TYPE_DS: - case DNS_TYPE_DNSKEY: - case DNS_TYPE_RRSIG: - case DNS_TYPE_NSEC: - case DNS_TYPE_NSEC3: - return -EINVAL; - - case DNS_TYPE_SSHFP: - *out = rr->sshfp.fingerprint; - return rr->sshfp.fingerprint_size; - - case DNS_TYPE_TLSA: - *out = rr->tlsa.data; - return rr->tlsa.data_size; - - - case DNS_TYPE_OPENPGPKEY: - default: - *out = rr->generic.data; - return rr->generic.data_size; - } -} - -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; -} - -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_CAA: - siphash24_compress(&rr->caa.flags, sizeof(rr->caa.flags), state); - string_hash_func(rr->caa.tag, state); - siphash24_compress(rr->caa.value, rr->caa.value_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/grp-resolve/systemd-resolved/resolved-dns-rr.h b/src/grp-resolve/systemd-resolved/resolved-dns-rr.h deleted file mode 100644 index daf9c8c210..0000000000 --- a/src/grp-resolve/systemd-resolved/resolved-dns-rr.h +++ /dev/null @@ -1,343 +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 "basic/bitmap.h" -#include "basic/hashmap.h" -#include "basic/in-addr-util.h" -#include "basic/list.h" -#include "basic/string-util.h" - -#include "dns-type.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 directly, 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; - - /* https://tools.ietf.org/html/rfc6844 */ - struct { - uint8_t flags; - char *tag; - void *value; - size_t value_size; - } caa; - }; -}; - -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); -const char* dns_resource_key_name(const 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); - -/* _DNS_{CLASS,TYPE}_STRING_MAX include one byte for NUL, which we use for space instead below. - * DNS_HOSTNAME_MAX does not include the NUL byte, so we need to add 1. */ -#define DNS_RESOURCE_KEY_STRING_MAX (_DNS_CLASS_STRING_MAX + _DNS_TYPE_STRING_MAX + DNS_HOSTNAME_MAX + 1) - -char* dns_resource_key_to_string(const DnsResourceKey *key, char *buf, size_t buf_size); -ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out); - -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); - -void dns_resource_record_hash_func(const void *i, struct siphash *state); - -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/grp-resolve/systemd-resolved/test-data/_443._tcp.fedoraproject.org.pkts b/src/grp-resolve/systemd-resolved/test-data/_443._tcp.fedoraproject.org.pkts deleted file mode 100644 index a383c6286d..0000000000 Binary files a/src/grp-resolve/systemd-resolved/test-data/_443._tcp.fedoraproject.org.pkts and /dev/null differ diff --git a/src/grp-resolve/systemd-resolved/test-data/_openpgpkey.fedoraproject.org.pkts b/src/grp-resolve/systemd-resolved/test-data/_openpgpkey.fedoraproject.org.pkts deleted file mode 100644 index 15de02e997..0000000000 Binary files a/src/grp-resolve/systemd-resolved/test-data/_openpgpkey.fedoraproject.org.pkts and /dev/null differ diff --git a/src/grp-resolve/systemd-resolved/test-data/fake-caa.pkts b/src/grp-resolve/systemd-resolved/test-data/fake-caa.pkts deleted file mode 100644 index 1c3ecc5491..0000000000 Binary files a/src/grp-resolve/systemd-resolved/test-data/fake-caa.pkts and /dev/null differ diff --git a/src/grp-resolve/systemd-resolved/test-data/fedoraproject.org.pkts b/src/grp-resolve/systemd-resolved/test-data/fedoraproject.org.pkts deleted file mode 100644 index 17874844d9..0000000000 Binary files a/src/grp-resolve/systemd-resolved/test-data/fedoraproject.org.pkts and /dev/null differ diff --git a/src/grp-resolve/systemd-resolved/test-data/gandi.net.pkts b/src/grp-resolve/systemd-resolved/test-data/gandi.net.pkts deleted file mode 100644 index 5ef51e0c8e..0000000000 Binary files a/src/grp-resolve/systemd-resolved/test-data/gandi.net.pkts and /dev/null differ diff --git a/src/grp-resolve/systemd-resolved/test-data/google.com.pkts b/src/grp-resolve/systemd-resolved/test-data/google.com.pkts deleted file mode 100644 index f98c4cd855..0000000000 Binary files a/src/grp-resolve/systemd-resolved/test-data/google.com.pkts and /dev/null differ diff --git a/src/grp-resolve/systemd-resolved/test-data/kyhwana.org.pkts b/src/grp-resolve/systemd-resolved/test-data/kyhwana.org.pkts deleted file mode 100644 index e28a725c9a..0000000000 Binary files a/src/grp-resolve/systemd-resolved/test-data/kyhwana.org.pkts and /dev/null differ diff --git a/src/grp-resolve/systemd-resolved/test-data/root.pkts b/src/grp-resolve/systemd-resolved/test-data/root.pkts deleted file mode 100644 index 54ba668c75..0000000000 Binary files a/src/grp-resolve/systemd-resolved/test-data/root.pkts and /dev/null differ diff --git a/src/grp-resolve/systemd-resolved/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts b/src/grp-resolve/systemd-resolved/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts deleted file mode 100644 index a854249532..0000000000 Binary files a/src/grp-resolve/systemd-resolved/test-data/sw1a1aa-sw1a2aa-sw1a2ab-sw1a2ac.find.me.uk.pkts and /dev/null differ diff --git a/src/grp-resolve/systemd-resolved/test-data/teamits.com.pkts b/src/grp-resolve/systemd-resolved/test-data/teamits.com.pkts deleted file mode 100644 index 11deb39677..0000000000 Binary files a/src/grp-resolve/systemd-resolved/test-data/teamits.com.pkts and /dev/null differ diff --git a/src/grp-resolve/systemd-resolved/test-data/zbyszek@fedoraproject.org.pkts b/src/grp-resolve/systemd-resolved/test-data/zbyszek@fedoraproject.org.pkts deleted file mode 100644 index f0a6f982df..0000000000 Binary files a/src/grp-resolve/systemd-resolved/test-data/zbyszek@fedoraproject.org.pkts and /dev/null differ diff --git a/src/grp-resolve/systemd-resolved/test-dns-packet.c b/src/grp-resolve/systemd-resolved/test-dns-packet.c deleted file mode 100644 index 18d04b930d..0000000000 --- a/src/grp-resolve/systemd-resolved/test-dns-packet.c +++ /dev/null @@ -1,115 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2016 Zbigniew Jędrzejewski-Szmek - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include - -#include "basic/alloc-util.h" -#include "basic/fileio.h" -#include "basic/glob-util.h" -#include "basic/log.h" -#include "basic/macro.h" -#include "basic/string-util.h" -#include "basic/strv.h" - -#include "resolved-dns-packet.h" -#include "resolved-dns-rr.h" - -#define HASH_KEY SD_ID128_MAKE(d3,1e,48,90,4b,fa,4c,fe,af,9d,d5,a1,d7,2e,8a,b1) - -static uint64_t hash(DnsResourceRecord *rr) { - struct siphash state; - - siphash24_init(&state, HASH_KEY.bytes); - dns_resource_record_hash_func(rr, &state); - return siphash24_finalize(&state); -} - -static void test_packet_from_file(const char* filename, bool canonical) { - _cleanup_free_ char *data = NULL; - size_t data_size, packet_size, offset; - - assert_se(read_full_file(filename, &data, &data_size) >= 0); - assert_se(data); - assert_se(data_size > 8); - - log_info("============== %s %s==============", filename, canonical ? "canonical " : ""); - - for (offset = 0; offset < data_size; offset += 8 + packet_size) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL, *p2 = NULL; - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL, *rr2 = NULL; - const char *s, *s2; - uint64_t hash1, hash2; - - packet_size = le64toh( *(uint64_t*)(data + offset) ); - assert_se(packet_size > 0); - assert_se(offset + 8 + packet_size <= data_size); - - assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, 0) >= 0); - - assert_se(dns_packet_append_blob(p, data + offset + 8, packet_size, NULL) >= 0); - assert_se(dns_packet_read_rr(p, &rr, NULL, NULL) >= 0); - - s = dns_resource_record_to_string(rr); - assert_se(s); - puts(s); - - hash1 = hash(rr); - - assert_se(dns_resource_record_to_wire_format(rr, canonical) >= 0); - - assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, 0) >= 0); - assert_se(dns_packet_append_blob(p2, rr->wire_format, rr->wire_format_size, NULL) >= 0); - assert_se(dns_packet_read_rr(p2, &rr2, NULL, NULL) >= 0); - - s2 = dns_resource_record_to_string(rr); - assert_se(s2); - assert_se(streq(s, s2)); - - hash2 = hash(rr); - assert_se(hash1 == hash2); - } -} - -int main(int argc, char **argv) { - int i, N; - _cleanup_globfree_ glob_t g = {}; - char **fnames; - - log_parse_environment(); - - if (argc >= 2) { - N = argc - 1; - fnames = argv + 1; - } else { - assert_se(glob(RESOLVE_TEST_DIR "/*.pkts", GLOB_NOSORT, NULL, &g) == 0); - N = g.gl_pathc; - fnames = g.gl_pathv; - } - - for (i = 0; i < N; i++) { - test_packet_from_file(fnames[i], false); - puts(""); - test_packet_from_file(fnames[i], true); - if (i + 1 < N) - puts(""); - } - - return EXIT_SUCCESS; -} diff --git a/src/grp-resolve/systemd-resolved/test-dnssec-complex.c b/src/grp-resolve/systemd-resolved/test-dnssec-complex.c deleted file mode 100644 index ef78cd1ea5..0000000000 --- a/src/grp-resolve/systemd-resolved/test-dnssec-complex.c +++ /dev/null @@ -1,237 +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 - -#include "basic/af-list.h" -#include "basic/alloc-util.h" -#include "basic/random-util.h" -#include "basic/string-util.h" -#include "basic/time-util.h" -#include "sd-bus/bus-common-errors.h" - -#include "dns-type.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/grp-resolve/systemd-resolved/test-dnssec.c b/src/grp-resolve/systemd-resolved/test-dnssec.c deleted file mode 100644 index 1f05196d8e..0000000000 --- a/src/grp-resolve/systemd-resolved/test-dnssec.c +++ /dev/null @@ -1,344 +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 "basic/alloc-util.h" -#include "basic/hexdecoct.h" -#include "basic/string-util.h" - -#include "resolved-dns-dnssec.h" -#include "resolved-dns-rr.h" - -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); -} - -#ifdef HAVE_GCRYPT - -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_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_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_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); -} - -#endif - -int main(int argc, char*argv[]) { - - test_dnssec_canonicalize(); - -#ifdef HAVE_GCRYPT - test_dnssec_verify_dns_key(); - test_dnssec_verify_rrset(); - test_dnssec_verify_rrset2(); - test_dnssec_nsec3_hash(); -#endif - - return 0; -} diff --git a/src/grp-resolve/systemd-resolved/test-resolve-tables.c b/src/grp-resolve/systemd-resolved/test-resolve-tables.c deleted file mode 100644 index 0eaab70687..0000000000 --- a/src/grp-resolve/systemd-resolved/test-resolve-tables.c +++ /dev/null @@ -1,65 +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 "shared/test-tables.h" - -#include "dns-type.h" - -int main(int argc, char **argv) { - uint16_t i; - - test_table_sparse(dns_type, DNS_TYPE); - - log_info("/* DNS_TYPE */"); - for (i = 0; i < _DNS_TYPE_MAX; i++) { - const char *s; - - s = dns_type_to_string(i); - assert_se(s == NULL || strlen(s) < _DNS_TYPE_STRING_MAX); - - if (s) - log_info("%-*s %s%s%s%s%s%s%s%s%s", - (int) _DNS_TYPE_STRING_MAX - 1, s, - dns_type_is_pseudo(i) ? "pseudo " : "", - dns_type_is_valid_query(i) ? "valid_query " : "", - dns_type_is_valid_rr(i) ? "is_valid_rr " : "", - dns_type_may_redirect(i) ? "may_redirect " : "", - dns_type_is_dnssec(i) ? "dnssec " : "", - dns_type_is_obsolete(i) ? "obsolete " : "", - dns_type_may_wildcard(i) ? "wildcard " : "", - dns_type_apex_only(i) ? "apex_only " : "", - dns_type_needs_authentication(i) ? "needs_authentication" : ""); - } - - log_info("/* DNS_CLASS */"); - for (i = 0; i < _DNS_CLASS_MAX; i++) { - const char *s; - - s = dns_class_to_string(i); - assert_se(s == NULL || strlen(s) < _DNS_CLASS_STRING_MAX); - - if (s) - log_info("%-*s %s%s", - (int) _DNS_CLASS_STRING_MAX - 1, s, - dns_class_is_pseudo(i) ? "is_pseudo " : "", - dns_class_is_valid_rr(i) ? "is_valid_rr " : ""); - } - - return EXIT_SUCCESS; -} diff --git a/src/grp-system/systemctl/.gitignore b/src/grp-system/systemctl/.gitignore new file mode 100644 index 0000000000..89c7427edd --- /dev/null +++ b/src/grp-system/systemctl/.gitignore @@ -0,0 +1,2 @@ +/systemctl.completion.bash +/systemctl.completion.zsh diff --git a/src/grp-system/systemctl/systemctl.completion.bash.in b/src/grp-system/systemctl/systemctl.completion.bash.in new file mode 100644 index 0000000000..6f2b3f122c --- /dev/null +++ b/src/grp-system/systemctl/systemctl.completion.bash.in @@ -0,0 +1,284 @@ +# systemctl(1) completion -*- shell-script -*- +# +# This file is part of systemd. +# +# Copyright 2010 Ran Benita +# +# 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 +# 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 . + +__systemctl() { + local mode=$1; shift 1 + systemctl $mode --full --no-legend "$@" +} + +__systemd_properties() { + local mode=$1 + { __systemctl $mode show --all; + @rootlibexecdir@/systemd --dump-configuration-items; } | + while IFS='=' read -r key value; do + [[ $value ]] && echo "$key" + done +} + +__contains_word () { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +__filter_units_by_property () { + local mode=$1 property=$2 value=$3 ; shift 3 + local units=("$@") + local props + IFS=$'\n' read -rd '' -a props < \ + <(__systemctl $mode show --property "$property" -- "${units[@]}") + for ((i=0; $i < ${#units[*]}; i++)); do + if [[ "${props[i]}" = "$property=$value" ]]; then + echo " ${units[i]}" + fi + done +} + +__get_all_units () { { __systemctl $1 list-unit-files; __systemctl $1 list-units --all; } \ + | { while read -r a b; do [[ $a =~ @\. ]] || echo " $a"; done; }; } +__get_template_names () { __systemctl $1 list-unit-files \ + | { while read -r a b; do [[ $a =~ @\. ]] && echo " ${a%%@.*}@"; done; }; } + +__get_active_units () { __systemctl $1 list-units \ + | { while read -r a b; do echo " $a"; done; }; } +__get_startable_units () { + # find startable inactive units + __filter_units_by_property $mode ActiveState inactive $( + __filter_units_by_property $mode CanStart yes $( + __systemctl $mode list-unit-files --state enabled,disabled,static | \ + { while read -r a b; do [[ $a =~ @\. ]] || echo " $a"; done; } + __systemctl $mode list-units --state inactive,failed | \ + { while read -r a b; do echo " $a"; done; } )) +} +__get_restartable_units () { + # filter out masked and not-found + __filter_units_by_property $mode CanStart yes $( + __systemctl $mode list-unit-files --state enabled,disabled,static | \ + { while read -r a b; do [[ $a =~ @\. ]] || echo " $a"; done; } + __systemctl $mode list-units | \ + { while read -r a b; do echo " $a"; done; } ) +} +__get_failed_units () { __systemctl $1 list-units \ + | { while read -r a b c d; do [[ $c == "failed" ]] && echo " $a"; done; }; } +__get_enabled_units () { __systemctl $1 list-unit-files \ + | { while read -r a b c ; do [[ $b == "enabled" ]] && echo " $a"; done; }; } +__get_disabled_units () { __systemctl $1 list-unit-files \ + | { while read -r a b c ; do [[ $b == "disabled" ]] && echo " $a"; done; }; } +__get_masked_units () { __systemctl $1 list-unit-files \ + | { while read -r a b c ; do [[ $b == "masked" ]] && echo " $a"; done; }; } +__get_all_unit_files () { { __systemctl $1 list-unit-files; } | { while read -r a b; do echo " $a"; done; }; } + +__get_machines() { + local a b + { machinectl list-images --no-legend --no-pager; machinectl list --no-legend --no-pager; } | \ + { while read a b; do echo " $a"; done; } +} + +_systemctl () { + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + local i verb comps mode + + local -A OPTS=( + [STANDALONE]='--all -a --reverse --after --before --defaults --force -f --full -l --global + --help -h --no-ask-password --no-block --no-legend --no-pager --no-reload --no-wall + --quiet -q --privileged -P --system --user --version --runtime --recursive -r --firmware-setup + --show-types -i --ignore-inhibitors --plain' + [ARG]='--host -H --kill-who --property -p --signal -s --type -t --state --job-mode --root + --preset-mode -n --lines -o --output -M --machine' + ) + + if __contains_word "--user" ${COMP_WORDS[*]}; then + mode=--user + elif __contains_word "--global" ${COMP_WORDS[*]}; then + mode=--user + else + mode=--system + fi + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --signal|-s) + _signals + return + ;; + --type|-t) + comps=$(__systemctl $mode -t help) + ;; + --state) + comps=$(__systemctl $mode --state=help) + ;; + --job-mode) + comps='fail replace replace-irreversibly isolate + ignore-dependencies ignore-requirements flush' + ;; + --kill-who) + comps='all control main' + ;; + --root) + comps=$(compgen -A directory -- "$cur" ) + compopt -o filenames + ;; + --host|-H) + comps=$(compgen -A hostname) + ;; + --property|-p) + comps=$(__systemd_properties $mode) + ;; + --preset-mode) + comps='full enable-only disable-only' + ;; + --output|-o) + comps='short short-iso short-precise short-monotonic verbose export json + json-pretty json-sse cat' + ;; + --machine|-M) + comps=$( __get_machines ) + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + local -A VERBS=( + [ALL_UNITS]='is-active is-failed is-enabled status show cat mask preset help list-dependencies edit set-property' + [ENABLED_UNITS]='disable' + [DISABLED_UNITS]='enable' + [REENABLABLE_UNITS]='reenable' + [FAILED_UNITS]='reset-failed' + [STARTABLE_UNITS]='start' + [STOPPABLE_UNITS]='stop condstop kill try-restart condrestart' + [ISOLATABLE_UNITS]='isolate' + [RELOADABLE_UNITS]='reload condreload try-reload-or-restart force-reload' + [RESTARTABLE_UNITS]='restart reload-or-restart' + [TARGET_AND_UNITS]='add-wants add-requires' + [MASKED_UNITS]='unmask' + [JOBS]='cancel' + [ENVS]='set-environment unset-environment' + [STANDALONE]='daemon-reexec daemon-reload default + emergency exit halt hibernate hybrid-sleep kexec list-jobs + list-sockets list-timers list-units list-unit-files poweroff + reboot rescue show-environment suspend get-default + is-system-running' + [FILE]='link switch-root' + [TARGETS]='set-default' + ) + + for ((i=0; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} && + ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then + verb=${COMP_WORDS[i]} + break + fi + done + + if [[ -z $verb ]]; then + comps="${VERBS[*]}" + + elif __contains_word "$verb" ${VERBS[ALL_UNITS]}; then + comps=$( __get_all_units $mode ) + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[ENABLED_UNITS]}; then + comps=$( __get_enabled_units $mode ) + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[DISABLED_UNITS]}; then + comps=$( __get_disabled_units $mode; + __get_template_names $mode) + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[REENABLABLE_UNITS]}; then + comps=$( __get_disabled_units $mode; + __get_enabled_units $mode; + __get_template_names $mode) + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[STARTABLE_UNITS]}; then + comps=$( __get_startable_units $mode; + __get_template_names $mode) + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[RESTARTABLE_UNITS]}; then + comps=$( __get_restartable_units $mode; + __get_template_names $mode) + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[STOPPABLE_UNITS]}; then + comps=$( __filter_units_by_property $mode CanStop yes \ + $( __get_active_units $mode ) ) + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[RELOADABLE_UNITS]}; then + comps=$( __filter_units_by_property $mode CanReload yes \ + $( __get_active_units $mode ) ) + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[ISOLATABLE_UNITS]}; then + comps=$( __filter_units_by_property $mode AllowIsolate yes \ + $( __get_all_units $mode ) ) + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[FAILED_UNITS]}; then + comps=$( __get_failed_units $mode ) + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[MASKED_UNITS]}; then + comps=$( __get_masked_units $mode ) + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[TARGET_AND_UNITS]}; then + if __contains_word "$prev" ${VERBS[TARGET_AND_UNITS]} \ + || __contains_word "$prev" ${OPTS[STANDALONE]}; then + comps=$( __systemctl $mode list-unit-files --type target --all \ + | { while read -r a b; do echo " $a"; done; } ) + else + comps=$( __get_all_unit_files $mode ) + fi + compopt -o filenames + + elif __contains_word "$verb" ${VERBS[STANDALONE]}; then + comps='' + + elif __contains_word "$verb" ${VERBS[JOBS]}; then + comps=$( __systemctl $mode list-jobs | { while read -r a b; do echo " $a"; done; } ) + + elif __contains_word "$verb" ${VERBS[ENVS]}; then + comps=$( __systemctl $mode show-environment \ + | while read -r line; do echo " ${line%%=*}=";done ) + compopt -o nospace + + elif __contains_word "$verb" ${VERBS[FILE]}; then + comps=$( compgen -A file -- "$cur" ) + compopt -o filenames + elif __contains_word "$verb" ${VERBS[TARGETS]}; then + comps=$( __systemctl $mode list-unit-files --type target --full --all \ + | { while read -r a b; do echo " $a"; done; } ) + fi + + COMPREPLY=( $(compgen -o filenames -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _systemctl systemctl diff --git a/src/grp-system/systemctl/systemctl.completion.zsh.in b/src/grp-system/systemctl/systemctl.completion.zsh.in new file mode 100644 index 0000000000..44c31b7833 --- /dev/null +++ b/src/grp-system/systemctl/systemctl.completion.zsh.in @@ -0,0 +1,391 @@ +#compdef systemctl + +(( $+functions[_systemctl_command] )) || _systemctl_command() +{ + local -a _systemctl_cmds + _systemctl_cmds=( + "list-sockets:List sockets" + "list-timers:List timers" + "list-units:List units" + "start:Start (activate) one or more units" + "stop:Stop (deactivate) one or more units" + "reload:Reload one or more units" + "restart:Start or restart one or more units" + "condrestart:Restart one or more units if active" + "try-restart:Restart one or more units if active" + "reload-or-restart:Reload one or more units if possible, otherwise start or restart" + "force-reload:Reload one or more units if possible, otherwise restart if active" + "hibernate:Hibernate the system" + "hybrid-sleep:Hibernate and suspend the system" + "try-reload-or-restart:Reload one or more units if possible, otherwise restart if active" + "isolate:Start one unit and stop all others" + "kill:Send signal to processes of a unit" + "is-active:Check whether units are active" + "is-failed:Check whether units are failed" + "status:Show runtime status of one or more units" + "show:Show properties of one or more units/jobs or the manager" + "cat:Show the source unit files and drop-ins" + "reset-failed:Reset failed state for all, one, or more units" + "list-unit-files:List installed unit files" + "enable:Enable one or more unit files" + "disable:Disable one or more unit files" + "reenable:Reenable one or more unit files" + "preset:Enable/disable one or more unit files based on preset configuration" + "set-default:Set the default target" + "get-default:Query the default target" + "edit:Edit one or more unit files" + "is-system-running:Query overall status of the system" + "help:Show documentation for specified units" + "list-dependencies:Show unit dependency tree" + "mask:Mask one or more units" + "unmask:Unmask one or more units" + "link:Link one or more units files into the search path" + "is-enabled:Check whether unit files are enabled" + "list-jobs:List jobs" + "cancel:Cancel all, one, or more jobs" + "show-environment:Dump environment" + "set-environment:Set one or more environment variables" + "unset-environment:Unset one or more environment variables" + "daemon-reload:Reload systemd manager configuration" + "daemon-reexec:Reexecute systemd manager" + "default:Enter system default mode" + "rescue:Enter system rescue mode" + "emergency:Enter system emergency mode" + "halt:Shut down and halt the system" + "suspend:Suspend the system" + "poweroff:Shut down and power-off the system" + "reboot:Shut down and reboot the system" + "kexec:Shut down and reboot the system with kexec" + "exit:Ask for user instance termination" + "switch-root:Change root directory" + ) + + if (( CURRENT == 1 )); then + _describe -t commands 'systemctl command' _systemctl_cmds || compadd "$@" + else + local curcontext="$curcontext" expl + + cmd="${${_systemctl_cmds[(r)$words[1]:*]%%:*}}" + # Deal with any aliases + case $cmd in + condrestart) cmd="try-restart";; + force-reload) cmd="try-reload-or-restart";; + esac + + if (( $#cmd )); then + curcontext="${curcontext%:*:*}:systemctl-${cmd}:" + + local update_policy + zstyle -s ":completion:${curcontext}:" cache-policy update_policy + if [[ -z "$update_policy" ]]; then + zstyle ":completion:${curcontext}:" cache-policy _systemctl_caching_policy + fi + + _call_function ret _systemctl_$cmd || _message 'no more arguments' + else + _message "unknown systemctl command: $words[1]" + fi + return ret + fi +} + +__systemctl() +{ + systemctl $_sys_service_mgr --full --no-legend --no-pager "$@" +} + + +# Fills the unit list +_systemctl_all_units() +{ + if ( [[ ${+_sys_all_units} -eq 0 ]] || _cache_invalid SYS_ALL_UNITS ) && + ! _retrieve_cache SYS_ALL_UNITS; + then + _sys_all_units=( ${${(f)"$(__systemctl list-units --all)"}%% *} ) + _store_cache SYS_ALL_UNITS _sys_all_units + fi +} + +# Fills the unit list including all file units +_systemctl_really_all_units() +{ + local -a all_unit_files; + local -a really_all_units; + if ( [[ ${+_sys_really_all_units} -eq 0 ]] || _cache_invalid SYS_REALLY_ALL_UNITS ) && + ! _retrieve_cache SYS_REALLY_ALL_UNITS; + then + all_unit_files=( ${${(f)"$(__systemctl list-unit-files)"}%% *} ) + _systemctl_all_units + really_all_units=($_sys_all_units $all_unit_files) + _sys_really_all_units=(${(u)really_all_units}) + _store_cache SYS_REALLY_ALL_UNITS _sys_really_all_units + fi +} + +_filter_units_by_property() { + local property=$1 value=$2 ; shift ; shift + local -a units ; units=($*) + local props + for props in ${(ps:\n\n:)"$(_call_program units "$service show --no-pager --property="Id,$property" -- ${units} 2>/dev/null")"}; do + props=(${(f)props}) + if [[ "${props[2]}" = "$property=$value" ]]; then + echo -E - " ${props[1]#Id=}" + fi + done +} + +_systemctl_get_template_names() { echo -E - ${^${(M)${(f)"$(__systemctl list-unit-files)"}##*@.[^[:space:]]##}%%@.*}\@ } + + +_systemctl_active_units() {_sys_active_units=( ${${(f)"$(__systemctl list-units)"}%% *} )} + +_systemctl_startable_units(){ + _sys_startable_units=( $( _filter_units_by_property ActiveState inactive $( + _filter_units_by_property CanStart yes $( + __systemctl $mode list-unit-files --state enabled,disabled,static | \ + { while read -r a b; do [[ $a =~ @\. ]] || echo -E - " $a"; done; } + __systemctl $mode list-units --state inactive,failed | \ + { while read -r a b; do echo -E - " $a"; done; } )) ) ) +} + +_systemctl_restartable_units(){ + _sys_restartable_units=( $(_filter_units_by_property CanStart yes $( + __systemctl $mode list-unit-files --state enabled,disabled,static | \ + { while read -r a b; do [[ $a =~ @\. ]] || echo -E - " $a"; done; } + __systemctl $mode list-units | \ + { while read -r a b; do echo -E - " $a"; done; } )) ) +} + +_systemctl_failed_units() {_sys_failed_units=( ${${(f)"$(__systemctl list-units --state=failed)"}%% *} ) } +_systemctl_unit_state() { typeset -gA _sys_unit_state; _sys_unit_state=( $(__systemctl list-unit-files) ) } + +local fun +# Completion functions for ALL_UNITS +for fun in is-active is-failed is-enabled status show cat mask preset help list-dependencies edit ; do + (( $+functions[_systemctl_$fun] )) || _systemctl_$fun() + { + _systemctl_really_all_units + _wanted systemd-units expl unit \ + compadd "$@" -a - _sys_really_all_units + } +done + +# Completion functions for ENABLED_UNITS +(( $+functions[_systemctl_disable] )) || _systemctl_disable() +{ + local _sys_unit_state; _systemctl_unit_state + _wanted systemd-units expl 'enabled unit' \ + compadd "$@" - ${(k)_sys_unit_state[(R)enabled]} +} + +(( $+functions[_systemctl_reenable] )) || _systemctl_reenable() +{ + local _sys_unit_state; _systemctl_unit_state + _wanted systemd-units expl 'enabled/disabled unit' \ + compadd "$@" - ${(k)_sys_unit_state[(R)(enabled|disabled)]} $(_systemctl_get_template_names) +} + +# Completion functions for DISABLED_UNITS +(( $+functions[_systemctl_enable] )) || _systemctl_enable() +{ + local _sys_unit_state; _systemctl_unit_state + _wanted systemd-units expl 'disabled unit' \ + compadd "$@" - ${(k)_sys_unit_state[(R)disabled]} $(_systemctl_get_template_names) +} + +# Completion functions for FAILED_UNITS +(( $+functions[_systemctl_reset-failed] )) || _systemctl_reset-failed() +{ + local _sys_failed_units; _systemctl_failed_units + _wanted systemd-units expl 'failed unit' \ + compadd "$@" -a - _sys_failed_units || _message "no failed unit found" +} + +# Completion functions for STARTABLE_UNITS +(( $+functions[_systemctl_start] )) || _systemctl_start() +{ + local _sys_startable_units; _systemctl_startable_units + _wanted systemd-units expl 'startable unit' \ + compadd "$@" - ${_sys_startable_units[*]} $(_systemctl_get_template_names) +} + +# Completion functions for STOPPABLE_UNITS +for fun in stop kill try-restart condrestart ; do + (( $+functions[_systemctl_$fun] )) || _systemctl_$fun() + { + local _sys_active_units; _systemctl_active_units + _wanted systemd-units expl 'stoppable unit' \ + compadd "$@" - $( _filter_units_by_property CanStop yes \ + ${_sys_active_units[*]} ) + } +done + +# Completion functions for ISOLATABLE_UNITS +(( $+functions[_systemctl_isolate] )) || _systemctl_isolate() +{ + _systemctl_all_units + _wanted systemd-units expl 'isolatable unit' \ + compadd "$@" - $( _filter_units_by_property AllowIsolate yes \ + ${_sys_all_units[*]} ) +} + +# Completion functions for RELOADABLE_UNITS +for fun in reload try-reload-or-restart force-reload ; do + (( $+functions[_systemctl_$fun] )) || _systemctl_$fun() + { + local _sys_active_units; _systemctl_active_units + _wanted systemd-units expl 'reloadable unit' \ + compadd "$@" - $( _filter_units_by_property CanReload yes \ + ${_sys_active_units[*]} ) + } +done + +# Completion functions for RESTARTABLE_UNITS +for fun in restart reload-or-restart ; do + (( $+functions[_systemctl_$fun] )) || _systemctl_$fun() + { + local _sys_restartable_units; _systemctl_restartable_units + _wanted systemd-units expl 'restartable unit' \ + compadd "$@" - ${_sys_restartable_units[*]} $(_systemctl_get_template_names) + } +done + +# Completion functions for MASKED_UNITS +(( $+functions[_systemctl_unmask] )) || _systemctl_unmask() +{ + local _sys_unit_state; _systemctl_unit_state + _wanted systemd-units expl 'masked unit' \ + compadd "$@" - ${(k)_sys_unit_state[(R)masked]} || _message "no masked units found" +} + +# Completion functions for JOBS +(( $+functions[_systemctl_cancel] )) || _systemctl_cancel() +{ + _wanted systemd-jobs expl job \ + compadd "$@" - ${${(f)"$(__systemctl list-jobs)"}%% *} || + _message "no jobs found" +} + +# Completion functions for TARGETS +(( $+functions[_systemctl_set-default] )) || _systemctl_set-default() +{ + _wanted systemd-targets expl target \ + compadd "$@" - ${${(f)"$(__systemctl list-unit-files --type target --all)"}%% *} || + _message "no targets found" +} + +# Completion functions for ENVS +for fun in set-environment unset-environment ; do + (( $+functions[_systemctl_$fun] )) || _systemctl_$fun() + { + local fun=$0 ; fun=${fun##_systemctl_} + local suf + if [[ "${fun}" = "set-environment" ]]; then + suf='-S=' + fi + _wanted systemd-environment expl 'environment variable' \ + compadd "$@" ${suf} - ${${(f)"$(systemctl show-environment)"}%%=*} + } +done + +(( $+functions[_systemctl_link] )) || _systemctl_link() { + _sd_unit_files +} + +(( $+functions[_systemctl_switch-root] )) || _systemctl_switch-root() { + _files +} + +# no systemctl completion for: +# [STANDALONE]='daemon-reexec daemon-reload default +# emergency exit halt kexec list-jobs list-units +# list-unit-files poweroff reboot rescue show-environment' + +_systemctl_caching_policy() +{ + local _sysunits + local -a oldcache + + # rebuild if cache is more than a day old + oldcache=( "$1"(mh+1) ) + (( $#oldcache )) && return 0 + + _sysunits=(${${(f)"$(__systemctl --all)"}%% *}) + + if (( $#_sysunits )); then + for unit in $_sysunits; do + [[ "$unit" -nt "$1" ]] && return 0 + done + fi + + return 1 +} + +_unit_states() { + local -a _states + _states=("${(fo)$(__systemctl --state=help)}") + _values -s , "${_states[@]}" +} + +_unit_types() { + local -a _types + _types=("${(fo)$(__systemctl -t help)}") + _values -s , "${_types[@]}" +} + +_unit_properties() { + if ( [[ ${+_sys_all_properties} -eq 0 ]] || _cache_invalid SYS_ALL_PROPERTIES ) && + ! _retrieve_cache SYS_ALL_PROPERTIES; + then + _sys_all_properties=( ${${(M)${(f)"$(__systemctl show --all; + @rootlibexecdir@/systemd --dump-configuration-items)"}##[[:alnum:]]##=*}%%=*} + ) + _store_cache SYS_ALL_PROPRTIES _sys_all_properties + fi + _values -s , "${_sys_all_properties[@]}" +} + +_job_modes() { + local -a _modes + _modes=(fail replace replace-irreversibly isolate ignore-dependencies ignore-requirements flush) + _values -s , "${_modes[@]}" +} + +local -a _modes; _modes=("--user" "--system") +local _sys_service_mgr=${${words:*_modes}[(R)(${(j.|.)_modes})]:---system} +_arguments -s \ + {-h,--help}'[Show help]' \ + '--version[Show package version]' \ + {-t+,--type=}'[List only units of a particular type]:unit type:_unit_types' \ + '--state=[Display units in the specified state]:unit state:_unit_states' \ + '--job-mode=[Specify how to deal with other jobs]:mode:_job_modes' \ + {-p+,--property=}'[Show only properties by specific name]:unit property:_unit_properties' \ + {-a,--all}'[Show all units/properties, including dead/empty ones]' \ + '--reverse[Show reverse dependencies]' \ + '--after[Show units ordered after]' \ + '--before[Show units ordered before]' \ + {-l,--full}"[Don't ellipsize unit names on output]" \ + '--show-types[When showing sockets, show socket type]' \ + {-i,--ignore-inhibitors}'[When executing a job, ignore jobs dependencies]' \ + {-q,--quiet}'[Suppress output]' \ + '--no-block[Do not wait until operation finished]' \ + '--no-legend[Do not print a legend, i.e. the column headers and the footer with hints]' \ + '--no-pager[Do not pipe output into a pager]' \ + '--system[Connect to system manager]' \ + '--user[Connect to user service manager]' \ + "--no-wall[Don't send wall message before halt/power-off/reboot]" \ + '--global[Enable/disable unit files globally]' \ + "--no-reload[When enabling/disabling unit files, don't reload daemon configuration]" \ + '--no-ask-password[Do not ask for system passwords]' \ + '--kill-who=[Who to send signal to]:killwho:(main control all)' \ + {-s+,--signal=}'[Which signal to send]:signal:_signals' \ + {-f,--force}'[When enabling unit files, override existing symlinks. When shutting down, execute action immediately]' \ + '--root=[Enable unit files in the specified root directory]:directory:_directories' \ + '--runtime[Enable unit files only temporarily until next reboot]' \ + {-H+,--host=}'[Operate on remote host]:userathost:_sd_hosts_or_user_at_host' \ + {-P,--privileged}'[Acquire privileges before execution]' \ + {-n+,--lines=}'[Journal entries to show]:number of entries' \ + {-o+,--output=}'[Change journal output mode]:modes:_sd_outputmodes' \ + '--firmware-setup[Tell the firmware to show the setup menu on next boot]' \ + '--plain[When used with list-dependencies, print output as a list]' \ + '*::systemctl command:_systemctl_command' diff --git a/src/grp-utils/systemd-notify/systemd-notify.completion.zsh b/src/grp-utils/systemd-notify/systemd-notify.completion.zsh new file mode 100644 index 0000000000..910ddfa34c --- /dev/null +++ b/src/grp-utils/systemd-notify/systemd-notify.completion.zsh @@ -0,0 +1,12 @@ +#compdef systemd-notify + +local curcontext="$curcontext" state lstate line +_arguments \ + {-h,--help}'[Show this help]' \ + '--version[Show package version]' \ + '--ready[Inform the init system about service start-up completion.]' \ + '--pid=[Inform the init system about the main PID of the daemon]:daemon main PID:_pids' \ + '--status=[Send a free-form status string for the daemon to the init systemd]:status string:' \ + '--booted[Returns 0 if the system was booted up with systemd]' + +#vim: set ft=zsh sw=4 ts=4 et diff --git a/src/grp-utils/systemd-path/systemd-path.completion.zsh b/src/grp-utils/systemd-path/systemd-path.completion.zsh new file mode 100644 index 0000000000..4919cf4250 --- /dev/null +++ b/src/grp-utils/systemd-path/systemd-path.completion.zsh @@ -0,0 +1,18 @@ +#compdef systemd-path + +__get_names() { + systemd-path | { while IFS=: read -r a b; do echo " $a"; done; } +} + +__names() { + local -a _names + _names=(${(fo)"$(__get_names)"}) + typeset -U _names + _describe 'names' _names +} + +_arguments \ + {-h,--help}'[Show help message]' \ + '--version[Show package version]' \ + '--host=[Sufix to append to paths]:suffix' \ + '*:name:__names' diff --git a/src/grp-utils/systemd-socket-activate/systemd-socket-activate.completion.bash b/src/grp-utils/systemd-socket-activate/systemd-socket-activate.completion.bash new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/grp-utils/systemd-socket-activate/systemd-socket-activate.completion.zsh b/src/grp-utils/systemd-socket-activate/systemd-socket-activate.completion.zsh new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/systemd-ask-password/systemd-ask-password.completion.zsh b/src/systemd-ask-password/systemd-ask-password.completion.zsh new file mode 100644 index 0000000000..fa68159256 --- /dev/null +++ b/src/systemd-ask-password/systemd-ask-password.completion.zsh @@ -0,0 +1,12 @@ +#compdef systemd-ask-password + +local curcontext="$curcontext" state lstate line +_arguments \ + {-h,--help}'[Show this help]' \ + '--icon=[Icon name]:icon name:' \ + '--timeout=[Timeout in sec]:timeout (seconds):' \ + '--no-tty[Ask question via agent even on TTY]' \ + '--accept-cached[Accept cached passwords]' \ + '--multiple[List multiple passwords if available]' + +#vim: set ft=zsh sw=4 ts=4 et diff --git a/src/systemd-cgls/systemd-cgls.completion.zsh b/src/systemd-cgls/systemd-cgls.completion.zsh new file mode 100644 index 0000000000..c8f93fa732 --- /dev/null +++ b/src/systemd-cgls/systemd-cgls.completion.zsh @@ -0,0 +1,12 @@ +#compdef systemd-cgls + +local curcontext="$curcontext" state lstate line +_arguments \ + {-h,--help}'[Show this help]' \ + '--version[Show package version]' \ + '--no-pager[Do not pipe output into a pager]' \ + {-a,--all}'[Show all groups, including empty]' \ + '-k[Include kernel threads in output]' \ + ':cgroups:(cpuset cpu cpuacct memory devices freezer blkio)' + +#vim: set ft=zsh sw=4 ts=4 et diff --git a/src/systemd-cgtop/systemd-cgtop.completion.zsh b/src/systemd-cgtop/systemd-cgtop.completion.zsh new file mode 100644 index 0000000000..f6e1b2422a --- /dev/null +++ b/src/systemd-cgtop/systemd-cgtop.completion.zsh @@ -0,0 +1,17 @@ +#compdef systemd-cgtop + +local curcontext="$curcontext" state lstate line +_arguments \ + {-h,--help}'[Show this help]' \ + '--version[Print version and exit]' \ + '(-c -m -i -t)-p[Order by path]' \ + '(-c -p -m -i)-t[Order by number of tasks]' \ + '(-m -p -i -t)-c[Order by CPU load]' \ + '(-c -p -i -t)-m[Order by memory load]' \ + '(-c -m -p -t)-i[Order by IO load]' \ + {-d+,--delay=}'[Specify delay]:delay:' \ + {-n+,--iterations=}'[Run for N iterations before exiting]:number of iterations:' \ + {-b,--batch}'[Run in batch mode, accepting no input]' \ + '--depth=[Maximum traversal depth]:maximum depth:' + +#vim: set ft=zsh sw=4 ts=4 et diff --git a/src/systemd-machine-id-setup/systemd-machine-id-setup.completion.zsh b/src/systemd-machine-id-setup/systemd-machine-id-setup.completion.zsh new file mode 100644 index 0000000000..d575649394 --- /dev/null +++ b/src/systemd-machine-id-setup/systemd-machine-id-setup.completion.zsh @@ -0,0 +1,8 @@ +#compdef systemd-machine-id-setup + +local curcontext="$curcontext" state lstate line +_arguments \ + {-h,--help}'[Show this help]' \ + '--version[Show package version]' + +#vim: set ft=zsh sw=4 ts=4 et diff --git a/src/systemd-stdio-bridge/systemd-stdio-bridge.completion.bash b/src/systemd-stdio-bridge/systemd-stdio-bridge.completion.bash new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/systemd-stdio-bridge/systemd-stdio-bridge.completion.zsh b/src/systemd-stdio-bridge/systemd-stdio-bridge.completion.zsh new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/systemd-tty-ask-password-agent/systemd-tty-ask-password-agent.completion.zsh b/src/systemd-tty-ask-password-agent/systemd-tty-ask-password-agent.completion.zsh new file mode 100644 index 0000000000..e7c0684996 --- /dev/null +++ b/src/systemd-tty-ask-password-agent/systemd-tty-ask-password-agent.completion.zsh @@ -0,0 +1,14 @@ +#compdef systemd-tty-ask-password-agent + +local curcontext="$curcontext" state lstate line +_arguments \ + {-h,--help}'[Prints a short help text and exits.]' \ + '--version[Prints a short version string and exits.]' \ + '--list[Lists all currently pending system password requests.]' \ + '--query[Process all currently pending system password requests by querying the user on the calling TTY.]' \ + '--watch[Continuously process password requests.]' \ + '--wall[Forward password requests to wall(1).]' \ + '--plymouth[Ask question with plymouth(8).]' \ + '--console[Ask question on /dev/console.]' + +#vim: set ft=zsh sw=4 ts=4 et -- cgit v1.2.3-54-g00ecf