diff --git a/man/systemd-resolve.xml b/man/resolvectl.xml similarity index 60% rename from man/systemd-resolve.xml rename to man/resolvectl.xml index 21d2901203..96b3e8dad3 100644 --- a/man/systemd-resolve.xml +++ b/man/resolvectl.xml @@ -10,11 +10,11 @@ Copyright 2016 Lennart Poettering --> - - systemd-resolve + resolvectl systemd @@ -28,118 +28,29 @@ - systemd-resolve + resolvectl 1 - systemd-resolve + resolvectl resolvconf Resolve domain names, IPV4 and IPv6 addresses, DNS resource records, and services; introspect and reconfigure the DNS resolver - systemd-resolve + resolvectl OPTIONS - HOSTNAME + COMMAND + NAME - - - systemd-resolve - OPTIONS - ADDRESS - - - - systemd-resolve - OPTIONS - --type=TYPE - DOMAIN - - - - systemd-resolve - OPTIONS - --service - NAME - TYPE DOMAIN - - - - systemd-resolve - OPTIONS - --openpgp - USER@DOMAIN - - - - systemd-resolve - OPTIONS - --tlsa - DOMAIN:PORT - - - - systemd-resolve - OPTIONS - --statistics - - - - systemd-resolve - OPTIONS - --reset-statistics - - - - systemd-resolve - OPTIONS - --flush-caches - - - - systemd-resolve - OPTIONS - --reset-server-features - - - - systemd-resolve - OPTIONS - --status - - - - systemd-resolve - OPTIONS - --set-dns=SERVER --set-domain=DOMAIN --set-llmnr=MODE --set-mdns=MODE --set-dnssec=MODE --set-nta=DOMAIN - - - - systemd-resolve - OPTIONS - --revert - - - - resolvconf - OPTIONS - -a INTERFACE < FILE - - - - resolvconf - OPTIONS - -d INTERFACE - - Description - systemd-resolve may be used to resolve domain names, IPv4 and IPv6 addresses, DNS resource + resolvectl may be used to resolve domain names, IPv4 and IPv6 addresses, DNS resource records and services with the systemd-resolved.service8 resolver service. By default, the specified list of parameters will be resolved as hostnames, retrieving their IPv4 @@ -151,36 +62,6 @@ authenticated. All data for which local DNSSEC validation succeeds is considered authenticated. Moreover all data originating from local, trusted sources is also reported authenticated, including resolution of the local host name, the localhost host name or all data from /etc/hosts. - - The switch may be used to specify a DNS resource record type (A, AAAA, SOA, MX, …) in - order to request a specific DNS resource record, instead of the address or reverse address lookups. - The special value help may be used to list known values. - - The switch may be used to resolve SRV and DNS-SD services (see below). In this mode, between one and three - arguments are required. If three parameters are passed the first is assumed to be the DNS-SD service name, the - second the SRV service type, and the third the domain to search in. In this case a full DNS-SD style SRV and TXT - lookup is executed. If only two parameters are specified, the first is assumed to be the SRV service type, and the - second the domain to look in. In this case no TXT RR is requested. Finally, if only one parameter is specified, it - is assumed to be a domain name, that is already prefixed with an SRV type, and an SRV lookup is done (no - TXT). - - The switch may be used to query PGP keys stored as - OPENPGPKEY resource records. - When this option is specified one or more e-mail address must be specified. - - The switch maybe be used to query TLS public - keys stored as - TLSA resource records. - When this option is specified one or more domain names must be specified. - - The switch may be used to show resolver statistics, including information about - the number of successful and failed DNSSEC validations. - - The may be used to reset various statistics counters maintained the - resolver, including those shown in the output. This operation requires root - privileges. @@ -239,13 +120,6 @@ - - - - Enables service resolution. This enables DNS-SD and simple SRV service resolution, depending - on the specified list of parameters (see above). - - BOOL @@ -260,28 +134,6 @@ the TXT service metadata record is resolved as well. - - - - Enables OPENPGPKEY resource record resolution (see above). Specified e-mail - addresses are converted to the corresponding DNS domain name, and any OPENPGPKEY keys are - printed. - - - - - - Enables TLSA resource record resolution (see above). - A query will be performed for each of the specified names prefixed with - the port and family - (_port._family.domain). - The port number may be specified after a colon - (:), otherwise 443 will be used - by default. The family may be specified as an argument after - , otherwise tcp will be - used. - - BOOL @@ -315,21 +167,75 @@ query response are shown. Otherwise, this output is suppressed. - - + + + + + - If specified general resolver statistics are shown, including information whether DNSSEC is + + Commands + + + + + + Resolve domain names, IPv4 and IPv6 addresses. + + + + + + Resolve DNS-SD and + SRV services, depending on the specified list of parameters. + If three parameters are passed the first is assumed to be the DNS-SD service name, the second the SRV service type, + and the third the domain to search in. In this case a full DNS-SD style SRV and TXT lookup is executed. If only two + parameters are specified, the first is assumed to be the SRV service type, and the second the domain to look in. In + this case no TXT RR is requested. Finally, if only one parameter is specified, it is assumed to be a domain name, + that is already prefixed with an SRV type, and an SRV lookup is done (no TXT). + + + + + + Query PGP keys stored as OPENPGPKEY + resource records. Specified e-mail addresses are converted to the corresponding DNS domain name, and any + OPENPGPKEY keys are printed. + + + + + + Query TLS public keys stored as TLSA + resource records. A query will be performed for each of the specified names prefixed with the port and family + (_port._family.domain). + The port number may be specified after a colon (:), otherwise 443 will be used + by default. The family may be specified as the first argument, otherwise tcp will be used. + + + + + + Shows the global and per-link DNS settings in currently in effect. If no command is specified, + this is the implied default. + + + + + + Shows general resolver statistics, including information whether DNSSEC is enabled and available, as well as resolution and validation statistics. - + - Resets the statistics counters shown in to zero. + Resets the statistics counters shown in to zero. + This operation requires root privileges. - + Flushes all DNS resource record caches the service maintains locally. This is mostly equivalent to sending the SIGUSR2 to the systemd-resolved @@ -337,7 +243,7 @@ - + Flushes all feature level information the resolver learnt about specific servers, and ensures that the server feature probing logic is started from the beginning with the next look-up request. This is @@ -346,68 +252,52 @@ - + + + + + + - Shows the global and per-link DNS settings in currently in effect. - - - - - - - - - - - Set per-interface DNS configuration. These switches may be used to configure various DNS + Get/set per-interface DNS configuration. These commands may be used to configure various DNS settings for network interfaces that aren't managed by systemd-networkd.service8. (These commands will fail when used on interfaces that are managed by systemd-networkd, please - configure their DNS settings directly inside the .network files instead.) These switches + configure their DNS settings directly inside the .network files instead.) These commands may be used to inform systemd-resolved about per-interface DNS configuration determined - through external means. Multiple of these switches may be passed on a single invocation of - systemd-resolve in order to set multiple configuration options at once. If any of these - switches is used, it must be combined with to indicate the network interface the - new DNS configuration belongs to. The option expects an IPv4 or IPv6 address - specification of a DNS server to use, and may be used multiple times to define multiple servers for the same - interface. The option expects a valid DNS domain, possibly prefixed with - ~, and configures a per-interface search or route-only domain. It may be used multiple times - to configure multiple such domains. The , and - options may be used to configure the per-interface LLMNR, MulticastDNS and - DNSSEC settings. Finally, may be used to configure additional per-interface DNSSEC - NTA domains and may also be used multiple times. For details about these settings, their possible values and - their effect, see the corresponding options in + through external means. The command expects IPv4 or IPv6 address specifications of DNS + servers to use. The command expects valid DNS domains, possibly prefixed with + ~, and configures a per-interface search or route-only domain. The , + and commands may be used to configure the per-interface LLMNR, + MulticastDNS and DNSSEC settings. Finally, command may be used to configure additional + per-interface DNSSEC NTA domains. For details about these settings, their possible values and their effect, + see the corresponding options in systemd.network5. - + - Revert the per-interface DNS configuration. This option must be combined with - to indicate the network interface the DNS configuration shall be reverted on. If - the DNS configuration is reverted all per-interface DNS setting are reset to their defaults, undoing all - effects of , , , - , , . Note that when a - network interface disappears all configuration is lost automatically, an explicit reverting is not necessary in - that case. + Revert the per-interface DNS configuration. If the DNS configuration is reverted all + per-interface DNS setting are reset to their defaults, undoing all effects of , + , , , , + . Note that when a network interface disappears all configuration is lost automatically, + an explicit reverting is not necessary in that case. - - - Compatibility with <citerefentry><refentrytitle>resolvconf</refentrytitle><manvolnum>8</manvolnum></citerefentry> - systemd-resolve is a multi-call binary. When invoked as resolvconf - (generally achieved by means of a symbolic link of this name to the systemd-resolve binary) it + resolvectl is a multi-call binary. When invoked as resolvconf + (generally achieved by means of a symbolic link of this name to the resolvectl binary) it is run in a limited resolvconf8 compatibility mode. It accepts mostly the same arguments and pushes all data into systemd-resolved.service8, - similar to how and operate. Note that + similar to how and commands operate. Note that systemd-resolved.service is the only supported backend, which is different from other implementations of this command. Note that not all operations supported by other implementations are supported natively. Specifically: @@ -420,15 +310,14 @@ resolv.conf5 compatible DNS configuration data from its standard input. Relevant fields are nameserver and domain/search. This command is mostly identical to invoking - systemd-resolve with a combination of and - . + resolvectl with a combination of and + commands. Unregisters per-interface DNS configuration data with systemd-resolved. This - command is mostly identical to invoking systemd-resolve with - . + command is mostly identical to invoking resolvectl revert. @@ -481,7 +370,7 @@ Retrieve the addresses of the <literal>www.0pointer.net</literal> domain - $ systemd-resolve www.0pointer.net + $ resolvectl www.0pointer.net www.0pointer.net: 2a01:238:43ed:c300:10c3:bcf3:3266:da74 85.214.157.71 @@ -493,7 +382,7 @@ www.0pointer.net: 2a01:238:43ed:c300:10c3:bcf3:3266:da74 Retrieve the domain of the <literal>85.214.157.71</literal> IP address - $ systemd-resolve 85.214.157.71 + $ resolvectl 85.214.157.71 85.214.157.71: gardel.0pointer.net -- Information acquired via protocol DNS in 1.2997s. @@ -504,7 +393,7 @@ www.0pointer.net: 2a01:238:43ed:c300:10c3:bcf3:3266:da74 Retrieve the MX record of the <literal>yahoo.com</literal> domain - $ systemd-resolve -t MX yahoo.com --legend=no + $ resolvectl -t MX yahoo.com --legend=no yahoo.com. IN MX 1 mta7.am0.yahoodns.net yahoo.com. IN MX 1 mta6.am0.yahoodns.net yahoo.com. IN MX 1 mta5.am0.yahoodns.net @@ -514,7 +403,7 @@ yahoo.com. IN MX 1 mta5.am0.yahoodns.net Resolve an SRV service - $ systemd-resolve --service _xmpp-server._tcp gmail.com + $ resolvectl service _xmpp-server._tcp gmail.com _xmpp-server._tcp/gmail.com: alt1.xmpp-server.l.google.com:5269 [priority=20, weight=0] 173.194.210.125 alt4.xmpp-server.l.google.com:5269 [priority=20, weight=0] @@ -526,7 +415,7 @@ _xmpp-server._tcp/gmail.com: alt1.xmpp-server.l.google.com:5269 [priority=20, we Retrieve a PGP key - $ systemd-resolve --openpgp zbyszek@fedoraproject.org + $ resolvectl openpgp zbyszek@fedoraproject.org d08ee310438ca124a6149ea5cc21b6313b390dce485576eff96f8722._openpgpkey.fedoraproject.org. IN OPENPGPKEY mQINBFBHPMsBEACeInGYJCb+7TurKfb6wGyTottCDtiSJB310i37/6ZYoeIay/5soJjlMyf MFQ9T2XNT/0LM6gTa0MpC1st9LnzYTMsT6tzRly1D1UbVI6xw0g0vE5y2Cjk3xUwAynCsSs @@ -535,10 +424,10 @@ d08ee310438ca124a6149ea5cc21b6313b390dce485576eff96f8722._openpgpkey.fedoraproje - Retrieve a TLS key (<literal>=tcp</literal> and + <title>Retrieve a TLS key (<literal>tcp</literal> and <literal>:443</literal> could be skipped) - $ systemd-resolve --tlsa=tcp fedoraproject.org:443 + $ resolvectl tlsa tcp fedoraproject.org:443 _443._tcp.fedoraproject.org IN TLSA 0 0 1 19400be5b7a31fb733917700789d2f0a2471c0c9d506c0e504c06c16d7cb17c0 -- Cert. usage: CA constraint -- Selector: Full Certificate diff --git a/man/rules/meson.build b/man/rules/meson.build index 9b57f10cc0..b0919b9d44 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -43,6 +43,7 @@ manpages = [ ['nss-systemd', '8', ['libnss_systemd.so.2'], 'ENABLE_NSS_SYSTEMD'], ['os-release', '5', [], ''], ['pam_systemd', '8', [], 'HAVE_PAM'], + ['resolvectl', '1', ['resolvconf'], 'ENABLE_RESOLVE'], ['resolved.conf', '5', ['resolved.conf.d'], 'ENABLE_RESOLVE'], ['runlevel', '8', [], 'ENABLE_UTMP'], ['sd-boot', '7', [], 'ENABLE_EFI'], @@ -614,7 +615,6 @@ manpages = [ 'ENABLE_RANDOMSEED'], ['systemd-rc-local-generator', '8', [], ''], ['systemd-remount-fs.service', '8', ['systemd-remount-fs'], ''], - ['systemd-resolve', '1', ['resolvconf'], 'ENABLE_RESOLVE'], ['systemd-resolved.service', '8', ['systemd-resolved'], 'ENABLE_RESOLVE'], ['systemd-rfkill.service', '8', diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index fcc39a554e..9ca1133fed 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -216,7 +216,7 @@ systemd-resolved will flush all caches it maintains. Note that it should normally not be necessary to request this explicitly – except for debugging purposes – as systemd-resolved flushes the caches automatically anyway any time the host's network configuration changes. Sending this signal - to systemd-resolved is equivalent to the systemd-resolve --flush-caches + to systemd-resolved is equivalent to the resolvectl --flush-caches command, however the latter is recommended since it operates in a synchronous way. @@ -230,7 +230,7 @@ should normally not be necessary to request this explicitly – except for debugging purposes – as systemd-resolved automatically forgets learnt information any time the DNS server configuration changes. Sending this signal to systemd-resolved is equivalent to the - systemd-resolve --reset-server-features command, however the latter is recommended since it + resolvectl --reset-server-features command, however the latter is recommended since it operates in a synchronous way. @@ -244,7 +244,7 @@ resolved.conf5, dnssec-trust-anchors.d5, nss-resolve8, - systemd-resolve1, + resolvectl1, resolv.conf5, hosts5, systemd.network5, diff --git a/man/systemd.dnssd.xml b/man/systemd.dnssd.xml index 12ab78c395..ae439f0886 100644 --- a/man/systemd.dnssd.xml +++ b/man/systemd.dnssd.xml @@ -201,10 +201,10 @@ TxtText=path=/stats/index.html t=temperature_sensor This makes the http server running on the host discoverable in the local network given MulticastDNS is enabled on the network interface. - Now the utility systemd-resolve should be able to resolve the + Now the utility resolvectl should be able to resolve the service to the host's name: - $ systemd-resolve --service meteo._http._tcp.local + $ resolvectl service meteo._http._tcp.local meteo._http._tcp.local: meteo.local:80 [priority=0, weight=0] 169.254.208.106%senp0s21f0u2u4 fe80::213:3bff:fe49:8aa%senp0s21f0u2u4 @@ -238,7 +238,8 @@ meteo._http._tcp.local: meteo.local:80 [priority=0, weight=0] See Also systemd1, - systemd-resolved.service8 + systemd-resolved.service8, + resolvectl1 diff --git a/meson.build b/meson.build index 6b633dfb6d..734a289755 100644 --- a/meson.build +++ b/meson.build @@ -1593,8 +1593,8 @@ if conf.get('ENABLE_RESOLVE') == 1 install : true, install_dir : rootlibexecdir) - exe = executable('systemd-resolve', - systemd_resolve_sources, + exe = executable('resolvectl', + resolvectl_sources, include_directories : includes, link_with : [libshared, libbasic_gcrypt, @@ -1608,8 +1608,12 @@ if conf.get('ENABLE_RESOLVE') == 1 public_programs += [exe] meson.add_install_script(meson_make_symlink, - join_paths(bindir, 'systemd-resolve'), + join_paths(bindir, 'resolvectl'), join_paths(rootsbindir, 'resolvconf')) + + meson.add_install_script(meson_make_symlink, + join_paths(bindir, 'resolvectl'), + join_paths(bindir, 'systemd-resolve')) endif if conf.get('ENABLE_LOGIND') == 1 diff --git a/shell-completion/bash/meson.build b/shell-completion/bash/meson.build index 4ed82de1bb..3705adccf5 100644 --- a/shell-completion/bash/meson.build +++ b/shell-completion/bash/meson.build @@ -42,6 +42,7 @@ if bashcompletiondir != 'no' ['machinectl', 'ENABLE_MACHINED'], ['networkctl', 'ENABLE_NETWORKD'], ['systemd-resolve', 'ENABLE_RESOLVE'], + ['resolvectl', 'ENABLE_RESOLVE'], ['timedatectl', 'ENABLE_TIMEDATED'], ] diff --git a/shell-completion/bash/resolvectl b/shell-completion/bash/resolvectl new file mode 100644 index 0000000000..9ffbbbcf1e --- /dev/null +++ b/shell-completion/bash/resolvectl @@ -0,0 +1,165 @@ +# resolvectl(1) completion -*- shell-script -*- +# SPDX-License-Identifier: LGPL-2.1+ +# +# This file is part of systemd. +# +# Copyright 2018 Yu Watanabe +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that 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 +} + +_resolvectl() { + local i comps verb name + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + local -A OPTS=( + [STANDALONE]='-h --help --version --no-pager -4 -6 + --service-address=no --service-txt=no + --cname=no --search=no --legend=no' + [ARG]='-i --interface -p --protocol -t --type -c --class --raw' + ) + local -A VERBS=( + [DOMAIN]='query service openpgp' + [FAMILY]='tlsa' + [STATUS]='status' + [LINK]='revert dns domain nta' + [RESOLVE]='llmnr mdns' + [DNSSEC]='dnssec' + [STANDALONE]='statistics reset-statistics flush-caches reset-server-features' + ) + local -A ARGS=( + [FAMILY]='tcp udp sctp' + [RESOLVE]='yes no resolve' + [DNSSEC]='yes no allow-downgrade' + ) + local interfaces=$( __get_interfaces ) + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --interface|-i) + comps="$interfaces" + ;; + --protocol|-p|--type|-t|--class|-c) + comps=$( resolvectl --legend=no "$prev" help; echo help ) + ;; + --raw) + comps="payload packet" + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + 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[STANDALONE]} ${VERBS[DOMAIN]}; then + comps='' + + elif __contains_word "$verb" ${VERBS[STATUS]}; then + comps="$interfaces" + + elif __contains_word "$verb" ${VERBS[FAMILY]}; then + for ((i++; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${ARGS[FAMILY]} && + ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then + name=${COMP_WORDS[i]} + break; + fi + done + if [[ -z $name ]]; then + comps=${ARGS[FAMILY]} + else + comps="" + fi + + elif __contains_word "$verb" ${VERBS[LINK]} ${VERBS[RESOLVE]} ${VERBS[DNSSEC]}; then + for ((i++; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" $interfaces && + ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then + name=${COMP_WORDS[i]} + break; + fi + done + + if [[ -z $name ]]; then + comps="$interfaces" + + elif __contains_word "$verb" ${VERBS[RESOLVE]}; then + name= + for ((i++; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${ARGS[RESOLVE]} && + ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then + name=${COMP_WORDS[i]} + break; + fi + done + + if [[ -z $name ]]; then + comps=${ARGS[RESOLVE]} + else + comps='' + fi + + elif __contains_word "$verb" ${VERBS[DNSSEC]}; then + name= + for ((i++; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" ${ARGS[DNSSEC]} && + ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then + name=${COMP_WORDS[i]} + break; + fi + done + + if [[ -z $name ]]; then + comps=${ARGS[DNSSEC]} + else + comps='' + fi + + else + comps='' + fi + fi + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _resolvectl resolvectl diff --git a/src/resolve/meson.build b/src/resolve/meson.build index f56ac451c6..a6157743ef 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -67,11 +67,11 @@ systemd_resolved_sources = files(''' resolved-etc-hosts.c '''.split()) -systemd_resolve_sources = files(''' +resolvectl_sources = files(''' resolvconf-compat.c resolvconf-compat.h - resolve-tool.c - resolve-tool.h + resolvectl.c + resolvectl.h '''.split()) ############################################################ diff --git a/src/resolve/resolvconf-compat.c b/src/resolve/resolvconf-compat.c index 1342e7b1f4..a6b327e2f0 100644 --- a/src/resolve/resolvconf-compat.c +++ b/src/resolve/resolvconf-compat.c @@ -10,7 +10,7 @@ #include "fileio.h" #include "parse-util.h" #include "resolvconf-compat.h" -#include "resolve-tool.h" +#include "resolvectl.h" #include "resolved-def.h" #include "string-util.h" #include "strv.h" @@ -44,8 +44,6 @@ static int parse_nameserver(const char *string) { for (;;) { _cleanup_free_ char *word = NULL; - struct in_addr_data data, *n; - int ifindex = 0; r = extract_first_word(&string, &word, NULL, 0); if (r < 0) @@ -53,27 +51,8 @@ static int parse_nameserver(const char *string) { if (r == 0) break; - r = in_addr_ifindex_from_string_auto(word, &data.family, &data.address, &ifindex); - if (r < 0) - return log_error_errno(r, "Failed to parse name server '%s': %m", word); - - if (ifindex > 0 && ifindex != arg_ifindex) { - log_error("Name server interface '%s' does not match selected interface: %m", word); - return -EINVAL; - } - - /* Some superficial filtering */ - if (in_addr_is_null(data.family, &data.address)) - continue; - if (data.family == AF_INET && data.address.in.s_addr == htobe32(INADDR_DNS_STUB)) /* resolved's own stub? */ - continue; - - n = reallocarray(arg_set_dns, arg_n_set_dns + 1, sizeof(struct in_addr_data)); - if (!n) + if (strv_push(&arg_set_dns, word) < 0) return log_oom(); - arg_set_dns = n; - - arg_set_dns[arg_n_set_dns++] = data; } return 0; @@ -93,14 +72,6 @@ static int parse_search_domain(const char *string) { if (r == 0) break; - r = dns_name_is_valid(word); - if (r < 0) - return log_error_errno(r, "Failed to validate specified domain '%s': %m", word); - if (r == 0) { - log_error("Domain not valid: %s", word); - return -EINVAL; - } - if (strv_push(&arg_set_domain, word) < 0) return log_oom(); @@ -251,6 +222,7 @@ int resolvconf_parse_argv(int argc, char *argv[]) { } arg_ifindex = ifi; + arg_ifname = iface; } if (arg_mode == MODE_SET_LINK) { @@ -301,7 +273,7 @@ int resolvconf_parse_argv(int argc, char *argv[]) { } else if (type == TYPE_PRIVATE) log_debug("Private DNS server data not supported, ignoring."); - if (arg_n_set_dns == 0) { + if (!arg_set_dns) { log_error("No DNS servers specified, refusing operation."); return -EINVAL; } diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolvectl.c similarity index 66% rename from src/resolve/resolve-tool.c rename to src/resolve/resolvectl.c index 7b583fbe91..1b7c64a83a 100644 --- a/src/resolve/resolve-tool.c +++ b/src/resolve/resolvectl.c @@ -24,14 +24,16 @@ #include "pager.h" #include "parse-util.h" #include "resolvconf-compat.h" -#include "resolve-tool.h" +#include "resolvectl.h" #include "resolved-def.h" #include "resolved-dns-packet.h" #include "strv.h" #include "terminal-util.h" +#include "verbs.h" static int arg_family = AF_UNSPEC; int arg_ifindex = 0; +const char *arg_ifname = NULL; static uint16_t arg_type = 0; static uint16_t arg_class = 0; static bool arg_legend = true; @@ -45,7 +47,7 @@ typedef enum ServiceFamily { SERVICE_FAMILY_SCTP, _SERVICE_FAMILY_INVALID = -1, } ServiceFamily; -static ServiceFamily arg_service_family = SERVICE_FAMILY_TCP; +static const char *arg_service_family = NULL; typedef enum RawType { RAW_NONE, @@ -56,14 +58,37 @@ static RawType arg_raw = RAW_NONE; ExecutionMode arg_mode = MODE_RESOLVE_HOST; -struct in_addr_data *arg_set_dns = NULL; -size_t arg_n_set_dns = 0; +char **arg_set_dns = NULL; char **arg_set_domain = NULL; -static char *arg_set_llmnr = NULL; -static char *arg_set_mdns = NULL; -static char *arg_set_dnssec = NULL; +static const char *arg_set_llmnr = NULL; +static const char *arg_set_mdns = NULL; +static const char *arg_set_dnssec = NULL; static char **arg_set_nta = NULL; +typedef enum StatusMode { + STATUS_ALL, + STATUS_DNS, + STATUS_DOMAIN, + STATUS_LLMNR, + STATUS_MDNS, + STATUS_DNSSEC, + STATUS_NTA, +} StatusMode; + +static int parse_ifindex_with_warn(const char *s) { + int ifi; + + assert(s); + + if (parse_ifindex(s, &ifi) < 0) { + ifi = if_nametoindex(s); + if (ifi <= 0) + return log_error_errno(errno, "Unknown interface %s: %m", s); + } + + return ifi; +} + static ServiceFamily service_family_from_string(const char *s) { if (!s || streq(s, "tcp")) return SERVICE_FAMILY_TCP; @@ -117,7 +142,6 @@ static void print_source(uint64_t flags, usec_t rtt) { } 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; @@ -616,6 +640,39 @@ invalid: return -EINVAL; } +static int verb_query(int argc, char **argv, void *userdata) { + sd_bus *bus = userdata; + char **p; + int q, r = 0; + + if (arg_type != 0) + STRV_FOREACH(p, argv + 1) { + q = resolve_record(bus, *p, arg_class, arg_type, true); + if (q < 0) + r = q; + } + + else + STRV_FOREACH(p, argv + 1) { + if (startswith(*p, "dns:")) + q = resolve_rfc4501(bus, *p); + else { + int family, ifindex; + union in_addr_union a; + + q = in_addr_ifindex_from_string_auto(*p, &family, &a, &ifindex); + if (q >= 0) + q = resolve_address(bus, family, &a, ifindex); + else + q = resolve_host(bus, *p); + } + if (q < 0) + r = q; + } + + return r; +} + 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; @@ -811,6 +868,17 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons return 0; } +static int verb_service(int argc, char **argv, void *userdata) { + sd_bus *bus = userdata; + + if (argc == 2) + return resolve_service(bus, NULL, NULL, argv[1]); + else if (argc == 3) + return resolve_service(bus, NULL, argv[1], argv[2]); + else + return resolve_service(bus, argv[1], argv[2], argv[3]); +} + static int resolve_openpgp(sd_bus *bus, const char *address) { const char *domain, *full; int r; @@ -859,7 +927,21 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { return r; } -static int resolve_tlsa(sd_bus *bus, const char *address) { +static int verb_openpgp(int argc, char **argv, void *userdata) { + sd_bus *bus = userdata; + char **p; + int q, r = 0; + + STRV_FOREACH(p, argv + 1) { + q = resolve_openpgp(bus, *p); + if (q < 0) + r = q; + } + + return r; +} + +static int resolve_tlsa(sd_bus *bus, ServiceFamily family, const char *address) { const char *port; uint16_t port_num = 443; _cleanup_free_ char *full = NULL; @@ -879,7 +961,7 @@ static int resolve_tlsa(sd_bus *bus, const char *address) { r = asprintf(&full, "_%u.%s.%s", port_num, - service_family_to_string(arg_service_family), + service_family_to_string(family), address); if (r < 0) return log_oom(); @@ -891,9 +973,31 @@ static int resolve_tlsa(sd_bus *bus, const char *address) { arg_type ?: DNS_TYPE_TLSA, true); } -static int show_statistics(sd_bus *bus) { +static int verb_tlsa(int argc, char **argv, void *userdata) { + sd_bus *bus = userdata; + ServiceFamily family; + char **p, **args = argv + 1; + int q, r = 0; + + family = service_family_from_string(argv[1]); + if (family < 0) + family = SERVICE_FAMILY_TCP; + else + args++; + + STRV_FOREACH(p, args) { + q = resolve_tlsa(bus, family, *p); + if (q < 0) + r = q; + } + + return r; +} + +static int show_statistics(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + sd_bus *bus = userdata; 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; @@ -1008,8 +1112,9 @@ static int show_statistics(sd_bus *bus) { return 0; } -static int reset_statistics(sd_bus *bus) { +static int reset_statistics(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; int r; r = sd_bus_call_method(bus, @@ -1026,8 +1131,9 @@ static int reset_statistics(sd_bus *bus) { return 0; } -static int flush_caches(sd_bus *bus) { +static int flush_caches(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; int r; r = sd_bus_call_method(bus, @@ -1044,8 +1150,9 @@ static int flush_caches(sd_bus *bus) { return 0; } -static int reset_server_features(sd_bus *bus) { +static int reset_server_features(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; int r; r = sd_bus_call_method(bus, @@ -1168,7 +1275,21 @@ static int map_link_domains(sd_bus *bus, const char *member, sd_bus_message *m, return 0; } -static int status_ifindex(sd_bus *bus, int ifindex, const char *name, bool *empty_line) { +static int status_print_strv_ifindex(int ifindex, const char *ifname, char **p) { + char **i; + + printf("%sLink %i (%s)%s:", + ansi_highlight(), ifindex, ifname, ansi_normal()); + + STRV_FOREACH(i, p) + printf(" %s", *i); + + printf("\n"); + + return 0; +} + +static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode mode, bool *empty_line) { struct link_info { uint64_t scopes_mask; @@ -1202,7 +1323,6 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, bool *empt assert(bus); assert(ifindex > 0); - assert(empty_line); if (!name) { if (!if_indextoname(ifindex, ifname)) @@ -1233,7 +1353,49 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, bool *empt (void) pager_open(arg_no_pager, false); - if (*empty_line) + if (mode == STATUS_DNS) { + r = status_print_strv_ifindex(ifindex, name, link_info.dns); + goto finish; + } + + if (mode == STATUS_DOMAIN) { + r = status_print_strv_ifindex(ifindex, name, link_info.domains); + goto finish; + } + + if (mode == STATUS_NTA) { + r = status_print_strv_ifindex(ifindex, name, link_info.ntas); + goto finish; + } + + if (mode == STATUS_LLMNR) { + printf("%sLink %i (%s)%s: %s\n", + ansi_highlight(), ifindex, name, ansi_normal(), + strna(link_info.llmnr)); + + r = 0; + goto finish; + } + + if (mode == STATUS_MDNS) { + printf("%sLink %i (%s)%s: %s\n", + ansi_highlight(), ifindex, name, ansi_normal(), + strna(link_info.mdns)); + + r = 0; + goto finish; + } + + if (mode == STATUS_DNSSEC) { + printf("%sLink %i (%s)%s: %s\n", + ansi_highlight(), ifindex, name, ansi_normal(), + strna(link_info.dnssec)); + + r = 0; + goto finish; + } + + if (empty_line && *empty_line) fputc('\n', stdout); printf("%sLink %i (%s)%s\n", @@ -1276,7 +1438,8 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, bool *empt *i); } - *empty_line = true; + if (empty_line) + *empty_line = true; r = 0; @@ -1399,7 +1562,20 @@ static int map_global_domains(sd_bus *bus, const char *member, sd_bus_message *m return 0; } -static int status_global(sd_bus *bus, bool *empty_line) { +static int status_print_strv_global(char **p) { + char **i; + + printf("%sGlobal%s:", ansi_highlight(), ansi_normal()); + + STRV_FOREACH(i, p) + printf(" %s", *i); + + printf("\n"); + + return 0; +} + +static int status_global(sd_bus *bus, StatusMode mode, bool *empty_line) { struct global_info { char **dns; @@ -1445,6 +1621,45 @@ static int status_global(sd_bus *bus, bool *empty_line) { (void) pager_open(arg_no_pager, false); + if (mode == STATUS_DNS) { + r = status_print_strv_global(global_info.dns); + goto finish; + } + + if (mode == STATUS_DOMAIN) { + r = status_print_strv_global(global_info.domains); + goto finish; + } + + if (mode == STATUS_NTA) { + r = status_print_strv_global(global_info.ntas); + goto finish; + } + + if (mode == STATUS_LLMNR) { + printf("%sGlobal%s: %s\n", ansi_highlight(), ansi_normal(), + strna(global_info.llmnr)); + + r = 0; + goto finish; + } + + if (mode == STATUS_MDNS) { + printf("%sGlobal%s: %s\n", ansi_highlight(), ansi_normal(), + strna(global_info.mdns)); + + r = 0; + goto finish; + } + + if (mode == STATUS_DNSSEC) { + printf("%sGlobal%s: %s\n", ansi_highlight(), ansi_normal(), + strna(global_info.dnssec)); + + r = 0; + goto finish; + } + printf("%sGlobal%s\n", ansi_highlight(), ansi_normal()); printf(" LLMNR setting: %s\n" @@ -1487,7 +1702,7 @@ finish: return r; } -static int status_all(sd_bus *bus) { +static int status_all(sd_bus *bus, StatusMode mode) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; sd_netlink_message *i; @@ -1496,7 +1711,7 @@ static int status_all(sd_bus *bus) { assert(bus); - r = status_global(bus, &empty_line); + r = status_global(bus, mode, &empty_line); if (r < 0) return r; @@ -1540,7 +1755,7 @@ static int status_all(sd_bus *bus) { if (q < 0) return rtnl_log_parse_error(q); - q = status_ifindex(bus, ifindex, name, &empty_line); + q = status_ifindex(bus, ifindex, name, mode, &empty_line); if (q < 0 && r >= 0) r = q; } @@ -1548,226 +1763,418 @@ static int status_all(sd_bus *bus) { return r; } -static int set_link(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r = 0, q; +static int verb_status(int argc, char **argv, void *userdata) { + sd_bus *bus = userdata; + int q, r = 0; - assert(bus); + if (argc > 1) { + char **ifname; + bool empty_line = false; - if (arg_n_set_dns > 0) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; - size_t i; + STRV_FOREACH(ifname, argv + 1) { + int ifindex; - q = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "SetLinkDNS"); - if (q < 0) - return bus_log_create_error(q); + ifindex = parse_ifindex_with_warn(*ifname); + if (ifindex < 0) + continue; - q = sd_bus_message_append(req, "i", arg_ifindex); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_open_container(req, 'a', "(iay)"); - if (q < 0) - return bus_log_create_error(q); - - for (i = 0; i < arg_n_set_dns; i++) { - q = sd_bus_message_open_container(req, 'r', "iay"); + q = status_ifindex(bus, ifindex, NULL, STATUS_ALL, &empty_line); if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_append(req, "i", arg_set_dns[i].family); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_append_array(req, 'y', &arg_set_dns[i].address, FAMILY_ADDRESS_SIZE(arg_set_dns[i].family)); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_close_container(req); - if (q < 0) - return bus_log_create_error(q); - } - - q = sd_bus_message_close_container(req); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_call(bus, req, 0, &error, NULL); - if (q < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) - goto is_managed; - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; - - log_error_errno(q, "Failed to set DNS configuration: %s", bus_error_message(&error, q)); - if (r == 0) r = q; } - } - - if (!strv_isempty(arg_set_domain)) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; - char **p; - - q = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "SetLinkDomains"); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_append(req, "i", arg_ifindex); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_open_container(req, 'a', "(sb)"); - if (q < 0) - return bus_log_create_error(q); - - STRV_FOREACH(p, arg_set_domain) { - const char *n; - - n = **p == '~' ? *p + 1 : *p; - q = sd_bus_message_append(req, "(sb)", n, **p == '~'); - if (q < 0) - return bus_log_create_error(q); - } - - q = sd_bus_message_close_container(req); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_call(bus, req, 0, &error, NULL); - if (q < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) - goto is_managed; - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; - - log_error_errno(q, "Failed to set domain configuration: %s", bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } - - if (arg_set_llmnr) { - q = sd_bus_call_method(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "SetLinkLLMNR", - &error, - NULL, - "is", arg_ifindex, arg_set_llmnr); - if (q < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) - goto is_managed; - - log_error_errno(q, "Failed to set LLMNR configuration: %s", bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } - - if (arg_set_mdns) { - q = sd_bus_call_method(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "SetLinkMulticastDNS", - &error, - NULL, - "is", arg_ifindex, arg_set_mdns); - if (q < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) - goto is_managed; - - log_error_errno(q, "Failed to set MulticastDNS configuration: %s", bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } - - if (arg_set_dnssec) { - q = sd_bus_call_method(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "SetLinkDNSSEC", - &error, - NULL, - "is", arg_ifindex, arg_set_dnssec); - if (q < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) - goto is_managed; - - log_error_errno(q, "Failed to set DNSSEC configuration: %s", bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } - - if (!strv_isempty(arg_set_nta)) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; - - q = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "SetLinkDNSSECNegativeTrustAnchors"); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_append(req, "i", arg_ifindex); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_message_append_strv(req, arg_set_nta); - if (q < 0) - return bus_log_create_error(q); - - q = sd_bus_call(bus, req, 0, &error, NULL); - if (q < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) - goto is_managed; - - log_error_errno(q, "Failed to set DNSSEC NTA configuration: %s", bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } + } else + r = status_all(bus, STATUS_ALL); return r; - -is_managed: - { - char ifname[IFNAMSIZ]; - - return log_error_errno(q, - "The specified interface %s is managed by systemd-networkd. Operation refused.\n" - "Please configure DNS settings for systemd-networkd managed interfaces directly in their .network files.", strna(if_indextoname(arg_ifindex, ifname))); - } } -static int revert_link(sd_bus *bus) { +static int log_interface_is_managed(int r, int ifindex) { + char ifname[IFNAMSIZ]; + + return log_error_errno(r, + "The specified interface %s is managed by systemd-networkd. Operation refused.\n" + "Please configure DNS settings for systemd-networkd managed interfaces directly in their .network files.", + strna(if_indextoname(ifindex, ifname))); +} + +static int verb_dns(int argc, char **argv, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; + sd_bus *bus = userdata; + int ifindex, r; + char **p; assert(bus); + if (argc <= 1) + return status_all(bus, STATUS_DNS); + + ifindex = parse_ifindex_with_warn(argv[1]); + if (ifindex < 0) + return ifindex; + + if (ifindex == LOOPBACK_IFINDEX) { + log_error("Interface can't be the loopback interface (lo). Sorry."); + return -EINVAL; + } + + if (argc == 2) + return status_ifindex(bus, ifindex, NULL, STATUS_DNS, NULL); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "SetLinkDNS"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "i", ifindex); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(req, 'a', "(iay)"); + if (r < 0) + return bus_log_create_error(r); + + STRV_FOREACH(p, argv + 2) { + struct in_addr_data data; + + r = in_addr_from_string_auto(*p, &data.family, &data.address); + if (r < 0) + return log_error_errno(r, "Failed to parse DNS server address: %s", *p); + + r = sd_bus_message_open_container(req, 'r', "iay"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "i", data.family); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_array(req, 'y', &data.address, FAMILY_ADDRESS_SIZE(data.family)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(req); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(req); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, req, 0, &error, NULL); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) + return log_interface_is_managed(r, ifindex); + + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set DNS configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int verb_domain(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; + sd_bus *bus = userdata; + int ifindex, r; + char **p; + + assert(bus); + + if (argc <= 1) + return status_all(bus, STATUS_DOMAIN); + + ifindex = parse_ifindex_with_warn(argv[1]); + if (ifindex < 0) + return ifindex; + + if (ifindex == LOOPBACK_IFINDEX) { + log_error("Interface can't be the loopback interface (lo). Sorry."); + return -EINVAL; + } + + if (argc == 2) + return status_ifindex(bus, ifindex, NULL, STATUS_DOMAIN, NULL); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "SetLinkDomains"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "i", ifindex); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(req, 'a', "(sb)"); + if (r < 0) + return bus_log_create_error(r); + + STRV_FOREACH(p, argv + 2) { + const char *n; + + n = **p == '~' ? *p + 1 : *p; + + r = dns_name_is_valid(n); + if (r < 0) + return log_error_errno(r, "Failed to validate specified domain %s: %m", n); + if (r == 0) { + log_error("Domain not valid: %s", n); + return -EINVAL; + } + + r = sd_bus_message_append(req, "(sb)", n, **p == '~'); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(req); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, req, 0, &error, NULL); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) + return log_interface_is_managed(r, ifindex); + + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set domain configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int verb_llmnr(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int ifindex, r; + + assert(bus); + + if (argc <= 1) + return status_all(bus, STATUS_LLMNR); + + ifindex = parse_ifindex_with_warn(argv[1]); + if (ifindex < 0) + return ifindex; + + if (ifindex == LOOPBACK_IFINDEX) { + log_error("Interface can't be the loopback interface (lo). Sorry."); + return -EINVAL; + } + + if (argc == 2) + return status_ifindex(bus, ifindex, NULL, STATUS_LLMNR, NULL); + + r = sd_bus_call_method(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "SetLinkLLMNR", + &error, + NULL, + "is", ifindex, argv[2]); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) + return log_interface_is_managed(r, ifindex); + + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set LLMNR configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int verb_mdns(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int ifindex, r; + + assert(bus); + + if (argc <= 1) + return status_all(bus, STATUS_MDNS); + + ifindex = parse_ifindex_with_warn(argv[1]); + if (ifindex < 0) + return ifindex; + + if (ifindex == LOOPBACK_IFINDEX) { + log_error("Interface can't be the loopback interface (lo). Sorry."); + return -EINVAL; + } + + if (argc == 2) + return status_ifindex(bus, ifindex, NULL, STATUS_MDNS, NULL); + + r = sd_bus_call_method(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "SetLinkMulticastDNS", + &error, + NULL, + "is", ifindex, argv[2]); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) + return log_interface_is_managed(r, ifindex); + + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set MulticastDNS configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int verb_dnssec(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int ifindex, r; + + assert(bus); + + if (argc <= 1) + return status_all(bus, STATUS_DNSSEC); + + ifindex = parse_ifindex_with_warn(argv[1]); + if (ifindex < 0) + return ifindex; + + if (ifindex == LOOPBACK_IFINDEX) { + log_error("Interface can't be the loopback interface (lo). Sorry."); + return -EINVAL; + } + + if (argc == 2) + return status_ifindex(bus, ifindex, NULL, STATUS_DNSSEC, NULL); + + r = sd_bus_call_method(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "SetLinkDNSSEC", + &error, + NULL, + "is", ifindex, argv[2]); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) + return log_interface_is_managed(r, ifindex); + + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set DNSSEC configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int verb_nta(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; + sd_bus *bus = userdata; + int ifindex, i, r; + + assert(bus); + + if (argc <= 1) + return status_all(bus, STATUS_NTA); + + ifindex = parse_ifindex_with_warn(argv[1]); + if (ifindex < 0) + return ifindex; + + if (ifindex == LOOPBACK_IFINDEX) { + log_error("Interface can't be the loopback interface (lo). Sorry."); + return -EINVAL; + } + + if (argc == 2) + return status_ifindex(bus, ifindex, NULL, STATUS_NTA, NULL); + + for (i = 2; i < argc; i++) { + r = dns_name_is_valid(argv[i]); + if (r < 0) + return log_error_errno(r, "Failed to validate specified domain %s: %m", argv[i]); + if (r == 0) { + log_error("Domain not valid: %s", argv[i]); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "SetLinkDNSSECNegativeTrustAnchors"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "i", ifindex); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(req, argv + 2); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, req, 0, &error, NULL); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) + return log_interface_is_managed(r, ifindex); + + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set DNSSEC NTA configuration: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int verb_revert_link(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int ifindex, r; + + assert(bus); + + ifindex = parse_ifindex_with_warn(argv[1]); + if (ifindex < 0) + return ifindex; + + if (ifindex == LOOPBACK_IFINDEX) { + log_error("Interface can't be the loopback interface (lo). Sorry."); + return -EINVAL; + } + r = sd_bus_call_method(bus, "org.freedesktop.resolve1", "/org/freedesktop/resolve1", @@ -1775,7 +2182,7 @@ static int revert_link(sd_bus *bus) { "RevertLink", &error, NULL, - "i", arg_ifindex); + "i", ifindex); if (r < 0) { if (arg_ifindex_permissive && sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) @@ -1819,7 +2226,7 @@ static void help_dns_classes(void) { } } -static void native_help(void) { +static void compat_help(void) { printf("%1$s [OPTIONS...] HOSTNAME|ADDRESS...\n" "%1$s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n" "%1$s [OPTIONS...] --openpgp EMAIL@DOMAIN...\n" @@ -1862,7 +2269,55 @@ static void native_help(void) { , program_invocation_short_name); } -static int native_parse_argv(int argc, char *argv[]) { +static void native_help(void) { + printf("%1$s [OPTIONS...] {COMMAND} ...\n" + "\n" + "Send control commands to the network name resolution manager, or\n" + "resolve domain names, IPv4 and IPv6 addresses, DNS records, and services.\n" + "\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\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-address=BOOL Resolve address for services (default: yes)\n" + " --service-txt=BOOL Resolve TXT records for services (default: yes)\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" + "\n" + "Commands:\n" + " query HOSTNAME|ADDRESS... Resolve domain names, IPv4 and IPv6 addresses\n" + " service [[NAME] TYPE] DOMAIN Resolve service (SRV)\n" + " openpgp EMAIL@DOMAIN... Query OpenPGP public key\n" + " tlsa DOMAIN[:PORT]... Query TLS public key\n" + " status [LINK...] Show link and server status\n" + " statistics Show resolver statistics\n" + " reset-statistics Reset resolver statistics\n" + " flush-caches Flush all local DNS caches\n" + " reset-server-features Forget learnt DNS server feature levels\n" + " dns [LINK [SERVER...]] Get/set per-interface DNS server address\n" + " domain [LINK [DOMAIN...]] Get/set per-interface search domain\n" + " llmnr [LINK [MODE]] Get/set per-interface LLMNR mode\n" + " mdns [LINK [MODE]] Get/set per-interface MulticastDNS mode\n" + " dnssec [LINK [MODE]] Get/set per-interface DNSSEC mode\n" + " nta [LINK [DOMAIN...]] Get/set per-interface DNSSEC NTA\n" + " revert LINK Revert per-interface configuration\n" + , program_invocation_short_name); +} + +static int verb_help(int argc, char **argv, void *userdata) { + native_help(); + return 0; +} + +static int compat_parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_LEGEND, @@ -1930,7 +2385,7 @@ static int native_parse_argv(int argc, char *argv[]) { switch(c) { case 'h': - native_help(); + compat_help(); return 0; /* done */; case ARG_VERSION: @@ -1945,16 +2400,12 @@ static int native_parse_argv(int argc, char *argv[]) { break; case 'i': - if (parse_ifindex(optarg, &arg_ifindex) < 0) { - int ifi; - - ifi = if_nametoindex(optarg); - if (ifi <= 0) - return log_error_errno(errno, "Unknown interface %s: %m", optarg); - - arg_ifindex = ifi; - } + r = parse_ifindex_with_warn(optarg); + if (r < 0) + return r; + arg_ifname = optarg; + arg_ifindex = r; break; case 't': @@ -2033,11 +2484,11 @@ static int native_parse_argv(int argc, char *argv[]) { case ARG_TLSA: arg_mode = MODE_RESOLVE_TLSA; - arg_service_family = service_family_from_string(optarg); - if (arg_service_family < 0) { + if (service_family_from_string(arg_service_family) < 0) { log_error("Unknown service family \"%s\".", optarg); return -EINVAL; } + arg_service_family = optarg; break; case ARG_RAW: @@ -2110,77 +2561,38 @@ static int native_parse_argv(int argc, char *argv[]) { arg_no_pager = true; break; - case ARG_SET_DNS: { - struct in_addr_data data, *n; - - r = in_addr_from_string_auto(optarg, &data.family, &data.address); + case ARG_SET_DNS: + r = strv_extend(&arg_set_dns, optarg); if (r < 0) - return log_error_errno(r, "Failed to parse DNS server address: %s", optarg); - - n = reallocarray(arg_set_dns, arg_n_set_dns + 1, sizeof(struct in_addr_data)); - if (!n) return log_oom(); - arg_set_dns = n; - arg_set_dns[arg_n_set_dns++] = data; arg_mode = MODE_SET_LINK; break; - } - - case ARG_SET_DOMAIN: { - const char *p; - - p = optarg[0] == '~' ? optarg + 1 : optarg; - - r = dns_name_is_valid(p); - if (r < 0) - return log_error_errno(r, "Failed to validate specified domain %s: %m", p); - if (r == 0) { - log_error("Domain not valid: %s", p); - return -EINVAL; - } + case ARG_SET_DOMAIN: r = strv_extend(&arg_set_domain, optarg); if (r < 0) return log_oom(); arg_mode = MODE_SET_LINK; break; - } case ARG_SET_LLMNR: - r = free_and_strdup(&arg_set_llmnr, optarg); - if (r < 0) - return log_oom(); - + arg_set_llmnr = optarg; arg_mode = MODE_SET_LINK; break; case ARG_SET_MDNS: - r = free_and_strdup(&arg_set_mdns, optarg); - if (r < 0) - return log_oom(); - + arg_set_mdns = optarg; arg_mode = MODE_SET_LINK; break; case ARG_SET_DNSSEC: - r = free_and_strdup(&arg_set_dnssec, optarg); - if (r < 0) - return log_oom(); - + arg_set_dnssec = optarg; arg_mode = MODE_SET_LINK; break; case ARG_SET_NTA: - r = dns_name_is_valid(optarg); - if (r < 0) - return log_error_errno(r, "Failed to validate specified domain %s: %m", optarg); - if (r == 0) { - log_error("Domain not valid: %s", optarg); - return -EINVAL; - } - r = strv_extend(&arg_set_nta, optarg); if (r < 0) return log_oom(); @@ -2231,15 +2643,341 @@ static int native_parse_argv(int argc, char *argv[]) { return 1 /* work to do */; } +static int native_parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_LEGEND, + ARG_CNAME, + ARG_SERVICE_ADDRESS, + ARG_SERVICE_TXT, + ARG_RAW, + ARG_SEARCH, + ARG_NO_PAGER, + }; + + 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-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, + { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, + { "raw", optional_argument, NULL, ARG_RAW }, + { "search", required_argument, NULL, ARG_SEARCH }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + {} + }; + + 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': + native_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': + r = parse_ifindex_with_warn(optarg); + if (r < 0) + return r; + + arg_ifindex = r; + 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); + + 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 if (streq(optarg, "mdns")) + arg_flags |= SD_RESOLVED_MDNS; + else if (streq(optarg, "mdns-ipv4")) + arg_flags |= SD_RESOLVED_MDNS_IPV4; + else if (streq(optarg, "mdns-ipv6")) + arg_flags |= SD_RESOLVED_MDNS_IPV6; + else { + log_error("Unknown protocol specifier: %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_NO_PAGER: + arg_no_pager = true; + 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_class == 0) + arg_class = DNS_CLASS_IN; + + if (arg_class != 0 && arg_type == 0) + arg_type = DNS_TYPE_A; + + return 1 /* work to do */; +} + +static int native_main(int argc, char *argv[], sd_bus *bus) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + { "status", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_status }, + { "query", 2, VERB_ANY, 0, verb_query }, + { "service", 2, 4, 0, verb_service }, + { "openpgp", 2, VERB_ANY, 0, verb_openpgp }, + { "tlsa", 2, VERB_ANY, 0, verb_tlsa }, + { "statistics", VERB_ANY, 1, 0, show_statistics }, + { "reset-statistics", VERB_ANY, 1, 0, reset_statistics }, + { "flush-caches", VERB_ANY, 1, 0, flush_caches }, + { "reset-server-features", VERB_ANY, 1, 0, reset_server_features }, + { "dns", VERB_ANY, VERB_ANY, 0, verb_dns }, + { "domain", VERB_ANY, VERB_ANY, 0, verb_domain }, + { "llmnr", VERB_ANY, 3, 0, verb_llmnr }, + { "mdns", VERB_ANY, 3, 0, verb_mdns }, + { "dnssec", VERB_ANY, 3, 0, verb_dnssec }, + { "nta", VERB_ANY, VERB_ANY, 0, verb_nta }, + { "revert", 2, 2, 0, verb_revert_link }, + {} + }; + + return dispatch_verb(argc, argv, verbs, bus); +} + +static int translate(const char *verb, const char *single_arg, unsigned num_args, char **args, sd_bus *bus) { + char **fake, **p; + unsigned num, i; + + assert(verb); + assert(num_args == 0 || args); + + num = !!single_arg + num_args + 1; + + p = fake = newa0(char *, num + 1); + *p++ = (char *) verb; + if (single_arg) + *p++ = (char *) single_arg; + for (i = 0; i < num_args; i++) + *p++ = args[i]; + + optind = 0; + return native_main(num, fake, bus); +} + +static int compat_main(int argc, char *argv[], sd_bus *bus) { + int r = 0; + + switch (arg_mode) { + case MODE_RESOLVE_HOST: + case MODE_RESOLVE_RECORD: + return translate("query", NULL, argc - optind, argv + optind, bus); + + case MODE_RESOLVE_SERVICE: + return translate("service", NULL, argc - optind, argv + optind, bus); + + case MODE_RESOLVE_OPENPGP: + return translate("openpgp", NULL, argc - optind, argv + optind, bus); + + case MODE_RESOLVE_TLSA: + return translate("tlsa", arg_service_family, argc - optind, argv + optind, bus); + + case MODE_STATISTICS: + return translate("statistics", NULL, 0, NULL, bus); + + case MODE_RESET_STATISTICS: + return translate("reset-statistics", NULL, 0, NULL, bus); + + case MODE_FLUSH_CACHES: + return translate("flush-caches", NULL, 0, NULL, bus); + + case MODE_RESET_SERVER_FEATURES: + return translate("reset-server-features", NULL, 0, NULL, bus); + + case MODE_STATUS: + return translate("status", NULL, argc - optind, argv + optind, bus); + + case MODE_SET_LINK: + if (arg_set_dns) { + r = translate("dns", arg_ifname, strv_length(arg_set_dns), arg_set_dns, bus); + if (r < 0) + return r; + } + + if (arg_set_domain) { + r = translate("domain", arg_ifname, strv_length(arg_set_domain), arg_set_domain, bus); + if (r < 0) + return r; + } + + if (arg_set_nta) { + r = translate("nta", arg_ifname, strv_length(arg_set_nta), arg_set_nta, bus); + if (r < 0) + return r; + } + + if (arg_set_llmnr) { + r = translate("llmnr", arg_ifname, 1, (char **) &arg_set_llmnr, bus); + if (r < 0) + return r; + } + + if (arg_set_mdns) { + r = translate("mdns", arg_ifname, 1, (char **) &arg_set_mdns, bus); + if (r < 0) + return r; + } + + if (arg_set_dnssec) { + r = translate("dnssec", arg_ifname, 1, (char **) &arg_set_dnssec, bus); + if (r < 0) + return r; + } + + return r; + + case MODE_REVERT_LINK: + return translate("revert", arg_ifname, 0, NULL, bus); + + case _MODE_INVALID: + assert_not_reached("invalid mode"); + } + + return 0; +} + int main(int argc, char **argv) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + sd_bus *bus = NULL; int r; + setlocale(LC_ALL, ""); log_parse_environment(); log_open(); if (streq(program_invocation_short_name, "resolvconf")) r = resolvconf_parse_argv(argc, argv); + else if (streq(program_invocation_short_name, "systemd-resolve")) + r = compat_parse_argv(argc, argv); else r = native_parse_argv(argc, argv); if (r <= 0) @@ -2251,211 +2989,20 @@ int main(int argc, char **argv) { 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 = in_addr_ifindex_from_string_auto(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, true); - 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; - - case MODE_FLUSH_CACHES: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = flush_caches(bus); - break; - - case MODE_RESET_SERVER_FEATURES: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = reset_server_features(bus); - break; - - case MODE_STATUS: - - if (argc > optind) { - char **ifname; - bool empty_line = false; - - r = 0; - STRV_FOREACH(ifname, argv + optind) { - int ifindex, q; - - q = parse_ifindex(*ifname, &ifindex); - if (q < 0) { - ifindex = if_nametoindex(*ifname); - if (ifindex <= 0) { - log_error_errno(errno, "Failed to resolve interface name '%s': %m", *ifname); - continue; - } - } - - q = status_ifindex(bus, ifindex, NULL, &empty_line); - if (q < 0 && r >= 0) - r = q; - } - } else - r = status_all(bus); - - break; - - case MODE_SET_LINK: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = set_link(bus); - break; - - case MODE_REVERT_LINK: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = revert_link(bus); - break; - - case _MODE_INVALID: - assert_not_reached("invalid mode"); - } + if (streq(program_invocation_short_name, "systemd-resolve")) + r = compat_main(argc, argv, bus); + else + r = native_main(argc, argv, bus); finish: + /* make sure we terminate the bus connection first, and then close the + * pager, see issue #3543 for the details. */ + sd_bus_flush_close_unref(bus); pager_close(); - free(arg_set_dns); + strv_free(arg_set_dns); strv_free(arg_set_domain); - free(arg_set_llmnr); - free(arg_set_mdns); - free(arg_set_dnssec); strv_free(arg_set_nta); - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/resolve/resolve-tool.h b/src/resolve/resolvectl.h similarity index 90% rename from src/resolve/resolve-tool.h rename to src/resolve/resolvectl.h index 1189fa3ee0..6ecaa4ff31 100644 --- a/src/resolve/resolve-tool.h +++ b/src/resolve/resolvectl.h @@ -5,6 +5,7 @@ #include extern int arg_ifindex; +extern const char *arg_ifname; extern bool arg_ifindex_permissive; typedef enum ExecutionMode { @@ -25,6 +26,5 @@ typedef enum ExecutionMode { extern ExecutionMode arg_mode; -extern struct in_addr_data *arg_set_dns; -extern size_t arg_n_set_dns; +extern char **arg_set_dns; extern char **arg_set_domain;