diff --git a/man/resolvectl.xml b/man/resolvectl.xml index 70400d0bca3..7d6d4e3fbea 100644 --- a/man/resolvectl.xml +++ b/man/resolvectl.xml @@ -103,6 +103,12 @@ are converted to the corresponding DNS domain name, and any OPENPGPKEY keys are printed. + The resource record type is implied by this command. If is specified, + it must be OPENPGPKEY. + + If combined with , outputs the resource record data in JSON format. + Unauthenticated key data is refused in JSON format. + @@ -119,6 +125,12 @@ 443 will be used by default. The family may be specified as the first argument, otherwise tcp will be used. + The resource record type is implied by this command. If is specified, + it must be TLSA. + + If combined with , outputs the resource record data in JSON format. + Unauthenticated key data is refused in JSON format. + diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 853df2a9879..cbe4899162d 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -567,6 +567,8 @@ static int output_rr_packet(DnsResourceRecord *rr, int ifindex) { return 0; } +static DEFINE_POINTER_ARRAY_FREE_FUNC(DnsResourceRecord*, dns_resource_record_unref); + static int idna_candidate(const char *name, char **ret) { _cleanup_free_ char *idnafied = NULL; int r; @@ -668,32 +670,50 @@ static int resolve_record(const char *name, uint16_t class, uint16_t type, bool if (r < 0) return r; + if (reply.n_records == 0) { + if (warn_missing) + log_error("%s: no records found", name); + return -ESRCH; + } + + DnsResourceRecord **rrs = new0(DnsResourceRecord*, reply.n_records); + size_t n_rrs = reply.n_records; + if (!rrs) + return log_oom(); + CLEANUP_ARRAY(rrs, n_rrs, dns_resource_record_unref_array); + + bool json = sd_json_format_enabled(arg_json_format_flags); bool needs_authentication = false; FOREACH_ARRAY(record, reply.records, reply.n_records) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - r = dns_resource_record_new_from_raw(&rr, record->raw.iov_base, record->raw.iov_len); + size_t i = record - reply.records; + + r = dns_resource_record_new_from_raw(&rrs[i], record->raw.iov_base, record->raw.iov_len); if (r < 0) return log_error_errno(r, "Failed to parse RR: %m"); + if (dns_type_needs_authentication(rrs[i]->key->type)) { + needs_authentication = true; + + if (json && !FLAGS_SET(reply.flags, SD_RESOLVED_AUTHENTICATED)) + return log_error_errno(SYNTHETIC_ERRNO(EKEYREJECTED), + "Refusing to output unauthenticated DNS records that require " + "authentication in JSON format."); + } + } + + FOREACH_ARRAY(record, reply.records, reply.n_records) { + size_t i = record - reply.records; + if (arg_raw == RAW_PACKET) { uint64_t u64 = htole64(record->raw.iov_len); fwrite(&u64, sizeof(u64), 1, stdout); fwrite(record->raw.iov_base, 1, record->raw.iov_len, stdout); } else { - r = output_rr_packet(rr, record->ifindex); + r = output_rr_packet(rrs[i], record->ifindex); if (r < 0) return r; } - - if (dns_type_needs_authentication(rr->key->type)) - needs_authentication = true; - } - - if (reply.n_records == 0) { - if (warn_missing) - log_error("%s: no records found", name); - return -ESRCH; } print_source(reply.flags, ts); @@ -1042,8 +1062,8 @@ static int verb_openpgp(int argc, char *argv[], uintptr_t _data, void *userdata) #if HAVE_OPENSSL int ret = 0; - if (sd_json_format_enabled(arg_json_format_flags)) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); + if (!IN_SET(arg_type, 0, DNS_TYPE_OPENPGPKEY)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The openpgp command may only be combined with --type=OPENPGPKEY."); STRV_FOREACH(p, strv_skip(argv, 1)) RET_GATHER(ret, resolve_openpgp(*p)); @@ -1098,8 +1118,8 @@ static int verb_tlsa(int argc, char *argv[], uintptr_t _data, void *userdata) { assert(argc >= 2); - if (sd_json_format_enabled(arg_json_format_flags)) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); + if (!IN_SET(arg_type, 0, DNS_TYPE_TLSA)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The tlsa command may only be combined with --type=TLSA."); if (service_family_is_valid(argv[1])) { family = argv[1]; @@ -3659,6 +3679,9 @@ static int native_parse_argv(int argc, char *argv[], char ***remaining_args) { break; } + if (arg_raw != RAW_NONE && sd_json_format_enabled(arg_json_format_flags)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--raw and --json= may not be combined."); + if (arg_type == 0 && arg_class != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--class= may only be used in conjunction with --type=."); diff --git a/src/resolve/test-dns-rr.c b/src/resolve/test-dns-rr.c index 834c100b076..18361be85b6 100644 --- a/src/resolve/test-dns-rr.c +++ b/src/resolve/test-dns-rr.c @@ -2264,6 +2264,22 @@ TEST(dns_resource_record_to_json_soa) { ASSERT_EQ(sd_json_variant_unsigned(sd_json_variant_by_key(j, "minimum")), UINT64_C(3600)); } +TEST(dns_resource_record_to_json_openpgpkey) { + static const uint8_t data[] = { 0, 1, 2, 3 }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = + ASSERT_NOT_NULL(dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_OPENPGPKEY, "www.example.com")); + ASSERT_NOT_NULL(rr->generic.data = memdup(data, sizeof(data))); + rr->generic.data_size = sizeof(data); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + ASSERT_OK(dns_resource_record_to_json(rr, &j)); + + sd_json_variant *key = ASSERT_NOT_NULL(sd_json_variant_by_key(j, "key")); + ASSERT_EQ(sd_json_variant_unsigned(sd_json_variant_by_key(key, "type")), (uint64_t) DNS_TYPE_OPENPGPKEY); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(j, "data")), "AAECAw=="); +} + TEST(dns_resource_record_to_string_ptr) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; const char *str; diff --git a/src/shared/dns-rr.c b/src/shared/dns-rr.c index 74be11cded5..26d2e3934a0 100644 --- a/src/shared/dns-rr.c +++ b/src/shared/dns-rr.c @@ -2487,6 +2487,12 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, sd_json_variant **ret) { SD_JSON_BUILD_PAIR_UNSIGNED("matchingType", rr->tlsa.matching_type), SD_JSON_BUILD_PAIR_HEX("data", rr->tlsa.data, rr->tlsa.data_size)); + case DNS_TYPE_OPENPGPKEY: + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_VARIANT("key", k), + SD_JSON_BUILD_PAIR_BASE64("data", rr->generic.data, rr->generic.data_size)); + case DNS_TYPE_SVCB: case DNS_TYPE_HTTPS: { _cleanup_(sd_json_variant_unrefp) sd_json_variant *p = NULL; diff --git a/test/knot-data/zones/untrusted.test.zone b/test/knot-data/zones/untrusted.test.zone index a0dca62ca87..158761c14bd 100644 --- a/test/knot-data/zones/untrusted.test.zone +++ b/test/knot-data/zones/untrusted.test.zone @@ -24,3 +24,5 @@ mail A 10.0.0.122 myservice A 10.0.0.123 AAAA fd00:dead:beef:cafe::123 _mysvc._tcp SRV 10 5 1234 myservice + +5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey OPENPGPKEY AAECAw== diff --git a/test/units/TEST-75-RESOLVED.sh b/test/units/TEST-75-RESOLVED.sh index 770d9769604..41436aac38b 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -607,6 +607,20 @@ testcase_08_resolved() { run resolvectl openpgp mr.smith@signed.test grep -qF "5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test" "$RUN_OUT" grep -qF "authenticated: yes" "$RUN_OUT" + run resolvectl openpgp mr.smith@signed.test --json=short + grep -qF '"key":{"class":1,"type":61,"name":"5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test"}' "$RUN_OUT" + grep -qF '"data":"' "$RUN_OUT" + (! run resolvectl openpgp mr.smith@untrusted.test --json=short) + grep -qF "Refusing to output unauthenticated DNS records that require authentication in JSON format." "$RUN_OUT" + run resolvectl openpgp mr.smith@signed.test --json=short --type=OPENPGPKEY + grep -qF '"key":{"class":1,"type":61,"name":"5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test"}' "$RUN_OUT" + grep -qF '"data":"' "$RUN_OUT" + (! run resolvectl openpgp mr.smith@signed.test --json=short --type=A) + grep -qF -- "The openpgp command may only be combined with --type=OPENPGPKEY." "$RUN_OUT" + (! run resolvectl tlsa signed.test:invalid --json=short) + grep -qF 'Invalid port "invalid".' "$RUN_OUT" + (! run resolvectl tlsa signed.test --json=short --type=A) + grep -qF -- "The tlsa command may only be combined with --type=TLSA." "$RUN_OUT" # Check zone transfers (AXFR/IXFR) # Note: since resolved doesn't support zone transfers, let's just make sure it # simply refuses such requests without choking on them