diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:42 +0000 |
commit | 78e9bb837c258ac0ec7712b3d612cc2f407e731e (patch) | |
tree | f515d16b6efd858a9aeb5b0ef5d6f90bf288283d /src/resolve/resolved-varlink.c | |
parent | Adding debian version 255.5-1. (diff) | |
download | systemd-78e9bb837c258ac0ec7712b3d612cc2f407e731e.tar.xz systemd-78e9bb837c258ac0ec7712b3d612cc2f407e731e.zip |
Merging upstream version 256.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/resolve/resolved-varlink.c')
-rw-r--r-- | src/resolve/resolved-varlink.c | 786 |
1 files changed, 743 insertions, 43 deletions
diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index 3e178a6..25f85d8 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -15,8 +15,19 @@ typedef struct LookupParameters { union in_addr_union address; size_t address_size; char *name; + uint16_t class; + uint16_t type; } LookupParameters; +typedef struct LookupParametersResolveService { + const char *name; + const char *type; + const char *domain; + int family; + int ifindex; + uint64_t flags; +} LookupParametersResolveService; + static void lookup_parameters_destroy(LookupParameters *p) { assert(p); free(p->name); @@ -49,7 +60,11 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_DNSSEC_FAILED: return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSSECValidationFailed", - JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))))); + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))), + JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0, + "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)), + JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg), + "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg)))); case DNS_TRANSACTION_NO_TRUST_ANCHOR: return varlink_error(q->varlink_request, "io.systemd.Resolve.NoTrustAnchor", NULL); @@ -74,7 +89,11 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_RCODE_FAILURE: return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSError", - JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode)))); + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode)), + JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0, + "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)), + JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg), + "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg)))); case DNS_TRANSACTION_NULL: case DNS_TRANSACTION_PENDING: @@ -145,6 +164,7 @@ static bool validate_and_mangle_flags( SD_RESOLVED_NO_TRUST_ANCHOR| SD_RESOLVED_NO_NETWORK| SD_RESOLVED_NO_STALE| + SD_RESOLVED_RELAX_SINGLE_LABEL| ok)) return false; @@ -160,45 +180,23 @@ static bool validate_and_mangle_flags( return true; } -static void vl_method_resolve_hostname_complete(DnsQuery *query) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; - _cleanup_(dns_query_freep) DnsQuery *q = query; - _cleanup_free_ char *normalized = NULL; +static int find_addr_records( + JsonVariant **array, + DnsQuestion *question, + DnsQuery *q, + DnsResourceRecord **canonical, + const char *search_domain) { DnsResourceRecord *rr; - DnsQuestion *question; int ifindex, r; - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname_many(q); - if (r == -ELOOP) { - r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_CNAME) { - /* This was a cname, and the query was restarted. */ - TAKE_PTR(q); - return; - } - - question = dns_query_question_for_protocol(q, q->answer_protocol); - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL; int family; const void *p; - r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + r = dns_question_matches_rr(question, rr, search_domain); if (r < 0) - goto finish; + return r; if (r == 0) continue; @@ -209,8 +207,7 @@ static void vl_method_resolve_hostname_complete(DnsQuery *query) { family = AF_INET6; p = &rr->aaaa.in6_addr; } else { - r = -EAFNOSUPPORT; - goto finish; + return -EAFNOSUPPORT; } r = json_build(&entry, @@ -219,16 +216,53 @@ static void vl_method_resolve_hostname_complete(DnsQuery *query) { JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(family)), JSON_BUILD_PAIR("address", JSON_BUILD_BYTE_ARRAY(p, FAMILY_ADDRESS_SIZE(family))))); if (r < 0) - goto finish; - - if (!canonical) - canonical = dns_resource_record_ref(rr); + return r; - r = json_variant_append_array(&array, entry); + r = json_variant_append_array(array, entry); if (r < 0) - goto finish; + return r; + + if (canonical && !*canonical) + *canonical = dns_resource_record_ref(rr); } + return 0; +} + +static void vl_method_resolve_hostname_complete(DnsQuery *query) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + _cleanup_(dns_query_freep) DnsQuery *q = query; + _cleanup_free_ char *normalized = NULL; + DnsQuestion *question; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname_many(q); + if (r == -ELOOP) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_CNAME) { + /* This was a cname, and the query was restarted. */ + TAKE_PTR(q); + return; + } + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + r = find_addr_records(&array, question, q, &canonical, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + goto finish; + if (json_variant_is_blank_object(array)) { r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); goto finish; @@ -530,6 +564,670 @@ static int vl_method_resolve_address(Varlink *link, JsonVariant *parameters, Var return 1; } +static int append_txt(JsonVariant **txt, DnsResourceRecord *rr) { + int r; + + assert(txt); + assert(rr); + assert(rr->key); + + if (rr->key->type != DNS_TYPE_TXT) + return 0; + + LIST_FOREACH(items, i, rr->txt.items) { + _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL; + + if (i->length <= 0) + continue; + + r = json_variant_new_octescape(&entry, i->data, i->length); + if (r < 0) + return r; + + r = json_variant_append_array(txt, entry); + if (r < 0) + return r; + } + + return 1; +} + +static int append_srv( + DnsQuery *q, + DnsResourceRecord *rr, + JsonVariant **array) { + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ char *normalized = NULL; + int r; + + assert(q); + assert(rr); + assert(rr->key); + assert(array); + + if (rr->key->type != DNS_TYPE_SRV) + return 0; + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + /* First, let's see if we could find an appropriate A or AAAA + * record for the SRV record */ + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + DnsResourceRecord *zz; + DnsQuestion *question; + + if (aux->state != DNS_TRANSACTION_SUCCESS) + continue; + if (aux->auxiliary_result != 0) + continue; + + question = dns_query_question_for_protocol(aux, aux->answer_protocol); + + r = dns_name_equal(dns_question_first_name(question), rr->srv.name); + if (r < 0) + return r; + if (r == 0) + continue; + + DNS_ANSWER_FOREACH(zz, aux->answer) { + r = dns_question_matches_rr(question, zz, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + canonical = dns_resource_record_ref(zz); + break; + } + + if (canonical) + break; + } + + /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */ + if (!canonical) + return 0; + } + + r = dns_name_normalize(rr->srv.name, 0, &normalized); + if (r < 0) + return r; + + r = json_build(&v, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("priority", JSON_BUILD_UNSIGNED(rr->srv.priority)), + JSON_BUILD_PAIR("weight", JSON_BUILD_UNSIGNED(rr->srv.weight)), + JSON_BUILD_PAIR("port", JSON_BUILD_UNSIGNED(rr->srv.port)), + JSON_BUILD_PAIR("hostname", JSON_BUILD_STRING(normalized)))); + if (r < 0) + return r; + + if (canonical) { + normalized = mfree(normalized); + + r = dns_name_normalize(dns_resource_key_name(canonical->key), 0, &normalized); + if (r < 0) + return r; + + r = json_variant_set_field_string(&v, "canonicalName", normalized); + if (r < 0) + return r; + } + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + _cleanup_(json_variant_unrefp) JsonVariant *addresses = NULL; + + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + DnsQuestion *question; + + if (aux->state != DNS_TRANSACTION_SUCCESS) + continue; + if (aux->auxiliary_result != 0) + continue; + + question = dns_query_question_for_protocol(aux, aux->answer_protocol); + + r = dns_name_equal(dns_question_first_name(question), rr->srv.name); + if (r < 0) + return r; + if (r == 0) + continue; + + r = find_addr_records(&addresses, question, aux, NULL, NULL); + if (r < 0) + return r; + } + + r = json_variant_set_field(&v, "addresses", addresses); + if (r < 0) + return r; + } + + r = json_variant_append_array(array, v); + if (r < 0) + return r; + + return 1; /* added */ +} + +static Varlink *get_vl_link_aux_query(DnsQuery *aux) { + assert(aux); + + /* Find the main query */ + while (aux->auxiliary_for) + aux = aux->auxiliary_for; + + return aux->varlink_request; +} + +static void resolve_service_all_complete(DnsQuery *query) { + _cleanup_(dns_query_freep) DnsQuery *q = query; + _cleanup_(json_variant_unrefp) JsonVariant *srv = NULL, *txt = NULL; + _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + DnsQuestion *question; + DnsResourceRecord *rr; + int r; + + assert(q); + + if (q->block_all_complete > 0) { + TAKE_PTR(q); + return; + } + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + DnsQuery *bad = NULL; + bool have_success = false; + + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + switch (aux->state) { + + case DNS_TRANSACTION_PENDING: + /* If an auxiliary query is still pending, let's wait */ + TAKE_PTR(q); + return; + + case DNS_TRANSACTION_SUCCESS: + if (aux->auxiliary_result == 0) + have_success = true; + else + bad = aux; + break; + + default: + bad = aux; + break; + } + } + if (!have_success) { + /* We can only return one error, hence pick the last error we encountered */ + + assert(bad); + if (bad->state == DNS_TRANSACTION_SUCCESS) { + assert(bad->auxiliary_result != 0); + + if (bad->auxiliary_result == -ELOOP) { + r = varlink_error(query->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); + goto finish; + } + + assert(bad->auxiliary_result < 0); + r = bad->auxiliary_result; + goto finish; + } + + bad->varlink_request = get_vl_link_aux_query(bad); + r = reply_query_state(bad); + bad->varlink_request = NULL; + goto finish; + } + } + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_srv(q, rr, &srv); + if (r < 0) + goto finish; + if (r == 0) /* not an SRV record */ + continue; + + if (!canonical) + canonical = dns_resource_record_ref(rr); + } + + if (json_variant_is_blank_object(srv)) { + r = varlink_error(query->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); + goto finish; + } + + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + if (rr->key->type != DNS_TYPE_TXT) + continue; + + r = append_txt(&txt, rr); + if (r < 0) + goto finish; + } + + assert(canonical); + r = dns_service_split(dns_resource_key_name(canonical->key), &name, &type, &domain); + if (r < 0) + goto finish; + + r = varlink_replyb(query->varlink_request, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("services", JSON_BUILD_VARIANT(srv)), + JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_object(txt), "txt", JSON_BUILD_VARIANT(txt)), + JSON_BUILD_PAIR("canonical", JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)), + JSON_BUILD_PAIR("type", JSON_BUILD_STRING(type)), + JSON_BUILD_PAIR("domain", JSON_BUILD_STRING(domain)))), + JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(dns_query_reply_flags_make(query))))); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to resolve service: %m"); + r = varlink_error_errno(q->varlink_request, r); + } +} + +static void resolve_service_hostname_complete(DnsQuery *q) { + int r; + + assert(q); + assert(q->auxiliary_for); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + resolve_service_all_complete(q->auxiliary_for); + return; + } + + r = dns_query_process_cname_many(q); + if (r == DNS_QUERY_CNAME) /* This was a cname, and the query was restarted. */ + return; + + /* This auxiliary lookup is finished or failed, let's see if all are finished now. */ + q->auxiliary_result = r < 0 ? r : 0; + resolve_service_all_complete(q->auxiliary_for); +} + +static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *aux = NULL; + int r; + + assert(q); + assert(rr); + assert(rr->key); + assert(rr->key->type == DNS_TYPE_SRV); + + /* OK, we found an SRV record for the service. Let's resolve + * the hostname included in it */ + + r = dns_question_new_address(&question, q->request_family, rr->srv.name, false); + if (r < 0) + return r; + + r = dns_query_new(q->manager, &aux, question, question, NULL, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + aux->request_family = q->request_family; + aux->complete = resolve_service_hostname_complete; + + r = dns_query_make_auxiliary(aux, q); + if (r == -EAGAIN) + /* Too many auxiliary lookups? If so, don't complain, + * let's just not add this one, we already have more + * than enough */ + return 0; + if (r < 0) + return r; + + /* Note that auxiliary queries do not track the original + * client, only the primary request does that. */ + + r = dns_query_go(aux); + if (r < 0) + return r; + + TAKE_PTR(aux); + return 1; +} + +static void vl_method_resolve_service_complete(DnsQuery *query) { + _cleanup_(dns_query_freep) DnsQuery *q = query; + bool has_root_domain = false; + DnsResourceRecord *rr; + DnsQuestion *question; + unsigned found = 0; + int ifindex, r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname_many(q); + if (r == -ELOOP) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_CNAME) { + /* This was a cname, and the query was restarted. */ + TAKE_PTR(q); + return; + } + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + if (rr->key->type != DNS_TYPE_SRV) + continue; + + if (dns_name_is_root(rr->srv.name)) { + has_root_domain = true; + continue; + } + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + q->block_all_complete++; + r = resolve_service_hostname(q, rr, ifindex); + q->block_all_complete--; + + if (r < 0) + goto finish; + } + + found++; + } + + if (has_root_domain && found <= 0) { + /* If there's exactly one SRV RR and it uses the root domain as hostname, then the service is + * explicitly not offered on the domain. Report this as a recognizable error. See RFC 2782, + * Section "Usage Rules". */ + r = varlink_error(q->varlink_request, "io.systemd.Resolve.ServiceNotProvided", NULL); + goto finish; + } + + if (found <= 0) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); + goto finish; + } + + /* Maybe we are already finished? check now... */ + resolve_service_all_complete(TAKE_PTR(q)); + return; + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send address reply: %m"); + r = varlink_error_errno(q->varlink_request, r); + } +} + +static int vl_method_resolve_service(Varlink* link, JsonVariant* parameters, VarlinkMethodFlags flags, void* userdata) { + static const JsonDispatch dispatch_table[] = { + { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParametersResolveService, name), 0 }, + { "type", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParametersResolveService, type), 0 }, + { "domain", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParametersResolveService, domain), JSON_MANDATORY }, + { "ifindex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParametersResolveService, ifindex), 0 }, + { "family", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParametersResolveService, family), 0 }, + { "flags", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(LookupParametersResolveService, flags), 0 }, + {} + }; + + _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; + LookupParametersResolveService p = { + .family = AF_UNSPEC, + }; + + _cleanup_(dns_query_freep) DnsQuery *q = NULL; + Manager *m; + int r; + + assert(link); + + m = varlink_server_get_userdata(varlink_get_server(link)); + assert(m); + + if (FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) + return -EINVAL; + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.ifindex < 0) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex")); + + if (!IN_SET(p.family, AF_INET, AF_INET6, AF_UNSPEC)) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); + + if (isempty(p.name)) + p.name = NULL; + else if (!dns_service_name_is_valid(p.name)) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name")); + + if (isempty(p.type)) + p.type = NULL; + else if (!dns_srv_type_is_valid(p.type)) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("type")); + + r = dns_name_is_valid(p.domain); + if (r < 0) + return r; + if (r == 0) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("domain")); + + if (p.name && !p.type) /* Service name cannot be specified without service type. */ + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("type")); + + if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS)) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); + + r = dns_question_new_service(&question_utf8, p.name, p.type, p.domain, !(p.flags & SD_RESOLVED_NO_TXT), false); + if (r < 0) + return r; + + r = dns_question_new_service(&question_idna, p.name, p.type, p.domain, !(p.flags & SD_RESOLVED_NO_TXT), true); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question_utf8, question_idna, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + q->varlink_request = varlink_ref(link); + q->request_family = p.family; + q->complete = vl_method_resolve_service_complete; + + varlink_set_userdata(link, q); + + r = dns_query_go(q); + if (r < 0) + return r; + + TAKE_PTR(q); + return 1; +} + +static void vl_method_resolve_record_complete(DnsQuery *query) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + _cleanup_(dns_query_freep) DnsQuery *q = query; + DnsQuestion *question; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname_many(q); + if (r == -ELOOP) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_CNAME) { + /* This was a cname, and the query was restarted. */ + TAKE_PTR(q); + return; + } + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + unsigned added = 0; + int ifindex; + DnsResourceRecord *rr; + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = dns_resource_record_to_json(rr, &v); + if (r < 0) + goto finish; + + r = dns_resource_record_to_wire_format(rr, /* canonical= */ false); /* don't use DNSSEC canonical format, since it removes casing, but we want that for DNS_SD compat */ + if (r < 0) + goto finish; + + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), + JSON_BUILD_PAIR_CONDITION(v, "rr", JSON_BUILD_VARIANT(v)), + JSON_BUILD_PAIR("raw", JSON_BUILD_BASE64(rr->wire_format, rr->wire_format_size)))); + if (r < 0) + goto finish; + + added++; + } + + if (added <= 0) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); + goto finish; + } + + r = varlink_replyb(q->varlink_request, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("rrs", JSON_BUILD_VARIANT(array)), + JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(dns_query_reply_flags_make(q))))); +finish: + if (r < 0) { + log_full_errno(ERRNO_IS_DISCONNECT(r) ? LOG_DEBUG : LOG_ERR, r, "Failed to send record reply: %m"); + varlink_error_errno(q->varlink_request, r); + } +} + +static int vl_method_resolve_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "ifindex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParameters, ifindex), 0 }, + { "name", JSON_VARIANT_STRING, json_dispatch_string, offsetof(LookupParameters, name), JSON_MANDATORY }, + { "class", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(LookupParameters, class), 0 }, + { "type", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(LookupParameters, type), JSON_MANDATORY }, + { "flags", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, + {} + }; + + _cleanup_(lookup_parameters_destroy) LookupParameters p = { + .class = DNS_CLASS_IN, + .type = _DNS_TYPE_INVALID, + }; + _cleanup_(dns_query_freep) DnsQuery *q = NULL; + Manager *m; + int r; + + assert(link); + + m = ASSERT_PTR(varlink_server_get_userdata(varlink_get_server(link))); + + if (FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) + return -EINVAL; + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.ifindex < 0) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex")); + + r = dns_name_is_valid(p.name); + if (r < 0) + return r; + if (r == 0) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name")); + + if (!dns_type_is_valid_query(p.type)) + return varlink_error(link, "io.systemd.Resolve.ResourceRecordTypeInvalidForQuery", NULL); + if (dns_type_is_zone_transfer(p.type)) + return varlink_error(link, "io.systemd.Resolve.ZoneTransfersNotPermitted", NULL); + if (dns_type_is_obsolete(p.type)) + return varlink_error(link, "io.systemd.Resolve.ResourceRecordTypeObsolete", NULL); + + if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_SEARCH)) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); + + _cleanup_(dns_question_unrefp) DnsQuestion *question = dns_question_new(1); + if (!question) + return -ENOMEM; + + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + key = dns_resource_key_new(p.class, p.type, p.name); + if (!key) + return -ENOMEM; + + r = dns_question_add(question, key, /* flags= */ 0); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_CLAMP_TTL); + if (r < 0) + return r; + + q->varlink_request = varlink_ref(link); + varlink_set_userdata(link, q); + q->complete = vl_method_resolve_record_complete; + + r = dns_query_go(q); + if (r < 0) + return r; + + TAKE_PTR(q); + return 1; +} + static int vl_method_subscribe_query_results(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { Manager *m; int r; @@ -540,7 +1238,7 @@ static int vl_method_subscribe_query_results(Varlink *link, JsonVariant *paramet /* if the client didn't set the more flag, it is using us incorrectly */ if (!FLAGS_SET(flags, VARLINK_METHOD_MORE)) - return varlink_error_invalid_parameter(link, NULL); + return varlink_error(link, VARLINK_ERROR_EXPECTED_MORE, NULL); if (json_variant_elements(parameters) > 0) return varlink_error_invalid_parameter(link, parameters); @@ -753,8 +1451,10 @@ static int varlink_main_server_init(Manager *m) { r = varlink_server_bind_method_many( s, - "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname, - "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address); + "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname, + "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address, + "io.systemd.Resolve.ResolveService", vl_method_resolve_service, + "io.systemd.Resolve.ResolveRecord", vl_method_resolve_record); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); |