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