diff options
-rw-r--r-- | Makefile.am | 6 | ||||
-rw-r--r-- | man/systemd-resolve.xml | 40 | ||||
-rw-r--r-- | shell-completion/bash/systemd-nspawn | 5 | ||||
-rw-r--r-- | shell-completion/bash/systemd-resolve | 64 | ||||
-rw-r--r-- | shell-completion/zsh/_systemd-resolve | 64 | ||||
-rw-r--r-- | src/activate/activate.c | 2 | ||||
-rw-r--r-- | src/resolve/resolve-tool.c | 97 | ||||
-rw-r--r-- | src/resolve/resolved-dns-rr.c | 33 |
8 files changed, 286 insertions, 25 deletions
diff --git a/Makefile.am b/Makefile.am index 5e3c12c47f..03341fcd28 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5205,6 +5205,12 @@ systemd_resolve_LDADD = \ 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 \ diff --git a/man/systemd-resolve.xml b/man/systemd-resolve.xml index c288fd974e..de3bbce6dd 100644 --- a/man/systemd-resolve.xml +++ b/man/systemd-resolve.xml @@ -86,6 +86,13 @@ <cmdsynopsis> <command>systemd-resolve</command> <arg choice="opt" rep="repeat">OPTIONS</arg> + <command> --tlsa</command> + <arg choice="plain"><replaceable>DOMAIN<optional>:PORT</optional></replaceable></arg> + </cmdsynopsis> + + <cmdsynopsis> + <command>systemd-resolve</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> <command> --statistics</command> </cmdsynopsis> @@ -121,10 +128,15 @@ is assumed to be a domain name, that is already prefixed with an SRV type, and an SRV lookup is done (no TXT).</para> - <para>The <option>--openpgp</option> switch may be use to query PGP keys stored as the + <para>The <option>--openpgp</option> switch may be used to query PGP keys stored as <ulink url="https://tools.ietf.org/html/draft-wouters-dane-openpgp-02">OPENPGPKEY</ulink> resource records. When this option is specified one or more e-mail address must be specified.</para> + <para>The <option>--tlsa</option> switch maybe be used to query TLS public + keys stored as + <ulink url="https://tools.ietf.org/html/rfc6698">TLSA</ulink> resource records. + When this option is specified one or more domain names must be specified.</para> + <para>The <option>--statistics</option> switch may be used to show resolver statistics, including information about the number of successful and failed DNSSEC validations.</para> @@ -217,6 +229,20 @@ </varlistentry> <varlistentry> + <term><option>--tlsa</option></term> + + <listitem><para>Enables TLSA resource record resolution (see above). + A query will be performed for each of the specified names prefixed with + the port and family + (<literal>_<replaceable>port</replaceable>._<replaceable>family</replaceable>.<replaceable>domain</replaceable></literal>). + The port number may be specified after a colon + (<literal>:</literal>), otherwise <constant>443</constant> will be used + by default. The family may be specified as an argument after + <option>--tlsa</option>, otherwise <constant>tcp</constant> will be + used.</para></listitem> + </varlistentry> + + <varlistentry> <term><option>--cname=</option><replaceable>BOOL</replaceable></term> <listitem><para>Takes a boolean parameter. If true (the default), DNS CNAME or DNAME redirections are @@ -325,6 +351,18 @@ d08ee310438ca124a6149ea5cc21b6313b390dce485576eff96f8722._openpgpkey.fedoraproje ... </programlisting> </example> + + <example> + <title>Retrieve a TLS key (<literal>=tcp</literal> and + <literal>:443</literal> could be skipped)</title> + + <programlisting>$ systemd-resolve --tlsa=tcp fedoraproject.org:443 +_443._tcp.fedoraproject.org IN TLSA 0 0 1 19400be5b7a31fb733917700789d2f0a2471c0c9d506c0e504c06c16d7cb17c0 + -- Cert. usage: CA constraint + -- Selector: Full Certificate + -- Matching type: SHA-256 +</programlisting> + </example> </refsect1> <refsect1> diff --git a/shell-completion/bash/systemd-nspawn b/shell-completion/bash/systemd-nspawn index 429e712eb3..8318f6e590 100644 --- a/shell-completion/bash/systemd-nspawn +++ b/shell-completion/bash/systemd-nspawn @@ -45,7 +45,10 @@ __get_env() { } __get_interfaces(){ - cut -f 1 -d ' ' /proc/net/dev | tail -n +3 | tr -s '\n' | tr -d ':' | xargs + { cd /sys/class/net && echo *; } | \ + while read -d' ' -r name; do + [[ "$name" != "lo" ]] && echo "$name" + done } _systemd_nspawn() { diff --git a/shell-completion/bash/systemd-resolve b/shell-completion/bash/systemd-resolve new file mode 100644 index 0000000000..0c501c9405 --- /dev/null +++ b/shell-completion/bash/systemd-resolve @@ -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 <http://www.gnu.org/licenses/>. + +__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/shell-completion/zsh/_systemd-resolve b/shell-completion/zsh/_systemd-resolve new file mode 100644 index 0000000000..c318ab50f1 --- /dev/null +++ b/shell-completion/zsh/_systemd-resolve @@ -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 <http://www.gnu.org/licenses/>. + +_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/activate/activate.c b/src/activate/activate.c index 23244fdc62..d6e2d07ff2 100644 --- a/src/activate/activate.c +++ b/src/activate/activate.c @@ -385,7 +385,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "+hl:aEd", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "+hl:aE:d", options, NULL)) >= 0) switch(c) { case 'h': help(); diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c index a519074278..484fbb4d92 100644 --- a/src/resolve/resolve-tool.c +++ b/src/resolve/resolve-tool.c @@ -44,12 +44,19 @@ 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 { @@ -57,10 +64,34 @@ static enum { 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]; @@ -844,6 +875,38 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { 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; @@ -1031,6 +1094,7 @@ static void help(void) { " --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" @@ -1050,6 +1114,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_SERVICE_ADDRESS, ARG_SERVICE_TXT, ARG_OPENPGP, + ARG_TLSA, ARG_RAW, ARG_SEARCH, ARG_STATISTICS, @@ -1069,6 +1134,7 @@ static int parse_argv(int argc, char *argv[]) { { "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, }, @@ -1183,6 +1249,15 @@ static int parse_argv(int argc, char *argv[]) { 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."); @@ -1261,7 +1336,7 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } - if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) { + if (arg_type != 0 && arg_mode == MODE_RESOLVE_SERVICE) { log_error("--service and --type= may not be combined."); return -EINVAL; } @@ -1378,6 +1453,24 @@ int main(int argc, char **argv) { } 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."); diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index d0a86ef206..6a29a93a26 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -1116,40 +1116,30 @@ const char *dns_resource_record_to_string(DnsResourceRecord *rr) { case DNS_TYPE_TLSA: { const char *cert_usage, *selector, *matching_type; - char *ss; - int n; cert_usage = tlsa_cert_usage_to_string(rr->tlsa.cert_usage); selector = tlsa_selector_to_string(rr->tlsa.selector); matching_type = tlsa_matching_type_to_string(rr->tlsa.matching_type); - r = asprintf(&s, "%s %u %u %u %n", - k, - rr->tlsa.cert_usage, - rr->tlsa.selector, - rr->tlsa.matching_type, - &n); - if (r < 0) - return NULL; - - r = base64_append(&s, n, - rr->tlsa.data, rr->tlsa.data_size, - 8, columns()); - if (r < 0) + t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); + if (!t) return NULL; - r = asprintf(&ss, "%s\n" + r = asprintf(&s, + "%s %u %u %u %s\n" " -- Cert. usage: %s\n" " -- Selector: %s\n" " -- Matching type: %s", - s, + k, + rr->tlsa.cert_usage, + rr->tlsa.selector, + rr->tlsa.matching_type, + t, cert_usage, selector, matching_type); if (r < 0) return NULL; - free(s); - s = ss; break; } @@ -1228,13 +1218,16 @@ ssize_t dns_resource_record_payload(DnsResourceRecord *rr, void **out) { case DNS_TYPE_MX: case DNS_TYPE_LOC: case DNS_TYPE_DS: - case DNS_TYPE_SSHFP: 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; |