diff options
Diffstat (limited to 'src/resolve/resolved-bus.c')
-rw-r--r-- | src/resolve/resolved-bus.c | 840 |
1 files changed, 652 insertions, 188 deletions
diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index f0a3b607d4..1908cae2b7 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -31,15 +31,14 @@ static int reply_query_state(DnsQuery *q) { const char *name; int r; - if (q->request_hostname) - name = q->request_hostname; - else { + if (q->request_address_valid) { r = in_addr_to_string(q->request_family, &q->request_address, &ip); if (r < 0) return r; name = ip; - } + } else + name = dns_question_name(q->question); switch (q->state) { @@ -132,10 +131,9 @@ static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifin } static void bus_method_resolve_hostname_complete(DnsQuery *q) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL, *canonical = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - unsigned added = 0, i; + unsigned added = 0; int r; assert(q); @@ -145,6 +143,16 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { goto finish; } + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(q->question)); + goto finish; + } + if (r < 0) + goto finish; + if (r > 0) /* This was a cname, and the query was restarted. */ + return; + r = sd_bus_message_new_method_return(q->request, &reply); if (r < 0) goto finish; @@ -154,92 +162,42 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { goto finish; if (q->answer) { - answer = dns_answer_ref(q->answer); + DnsResourceRecord *rr; + int ifindex; - for (i = 0; i < answer->n_rrs; i++) { - r = dns_question_matches_rr(q->question, answer->items[i].rr); + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(q->question, rr); if (r < 0) goto finish; - if (r == 0) { - /* Hmm, if this is not an address record, - maybe it's a cname? If so, remember this */ - r = dns_question_matches_cname(q->question, answer->items[i].rr); - if (r < 0) - goto finish; - if (r > 0) - cname = dns_resource_record_ref(answer->items[i].rr); - + if (r == 0) continue; - } - r = append_address(reply, answer->items[i].rr, answer->items[i].ifindex); + r = append_address(reply, rr, ifindex); if (r < 0) goto finish; if (!canonical) - canonical = dns_resource_record_ref(answer->items[i].rr); + canonical = dns_resource_record_ref(rr); added ++; } } - if (added == 0) { - if (!cname) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of requested type", q->request_hostname); - goto finish; - } - - /* This has a cname? Then update the query with the - * new cname. */ - r = dns_query_cname_redirect(q, cname); - if (r < 0) { - if (r == -ELOOP) - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop on '%s'", q->request_hostname); - else - r = sd_bus_reply_method_errno(q->request, -r, NULL); - - goto finish; - } - - /* Before we restart the query, let's see if any of - * the RRs we already got already answers our query */ - for (i = 0; i < answer->n_rrs; i++) { - r = dns_question_matches_rr(q->question, answer->items[i].rr); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = append_address(reply, answer->items[i].rr, answer->items[i].ifindex); - if (r < 0) - goto finish; - - if (!canonical) - canonical = dns_resource_record_ref(answer->items[i].rr); - - added++; - } - - /* If we didn't find anything, then let's restart the - * query, this time with the cname */ - if (added <= 0) { - r = dns_query_go(q); - if (r < 0) { - r = sd_bus_reply_method_errno(q->request, -r, NULL); - goto finish; - } - - return; - } + if (added <= 0) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_name(q->question)); + goto finish; } r = sd_bus_message_close_container(reply); if (r < 0) goto finish; - /* Return the precise spelling and uppercasing reported by the server */ + /* Return the precise spelling and uppercasing and CNAME target reported by the server */ assert(canonical); - r = sd_bus_message_append(reply, "st", DNS_RESOURCE_KEY_NAME(canonical->key), SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family)); + r = sd_bus_message_append( + reply, "st", + DNS_RESOURCE_KEY_NAME(canonical->key), + SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family)); if (r < 0) goto finish; @@ -248,23 +206,23 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { finish: if (r < 0) { log_error_errno(r, "Failed to send hostname reply: %m"); - sd_bus_reply_method_errno(q->request, -r, NULL); + sd_bus_reply_method_errno(q->request, r, NULL); } dns_query_free(q); } -static int check_ifindex_flags(int ifindex, uint64_t *flags, sd_bus_error *error) { +static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus_error *error) { assert(flags); if (ifindex < 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); - if (*flags & ~SD_RESOLVED_FLAGS_ALL) + if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); - if (*flags == 0) - *flags = SD_RESOLVED_FLAGS_DEFAULT; + if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ + *flags |= SD_RESOLVED_PROTOCOLS_ALL; return 0; } @@ -281,6 +239,8 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, assert(message); assert(m); + assert_cc(sizeof(int) == sizeof(int32_t)); + r = sd_bus_message_read(message, "isit", &ifindex, &hostname, &family, &flags); if (r < 0) return r; @@ -288,41 +248,19 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - r = dns_name_normalize(hostname, NULL); + r = dns_name_is_valid(hostname); if (r < 0) + return r; + if (r == 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname); - r = check_ifindex_flags(ifindex, &flags, error); + r = check_ifindex_flags(ifindex, &flags, 0, error); if (r < 0) return r; - question = dns_question_new(family == AF_UNSPEC ? 2 : 1); - if (!question) - return -ENOMEM; - - if (family != AF_INET6) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, hostname); - if (!key) - return -ENOMEM; - - r = dns_question_add(question, key); - if (r < 0) - return r; - } - - if (family != AF_INET) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, hostname); - if (!key) - return -ENOMEM; - - r = dns_question_add(question, key); - if (r < 0) - return r; - } + r = dns_question_new_address(&question, family, hostname); + if (r < 0) + return r; r = dns_query_new(m, &q, question, ifindex, flags); if (r < 0) @@ -330,27 +268,28 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, q->request = sd_bus_message_ref(message); q->request_family = family; - q->request_hostname = hostname; q->complete = bus_method_resolve_hostname_complete; r = dns_query_bus_track(q, message); if (r < 0) - return r; + goto fail; r = dns_query_go(q); - if (r < 0) { - dns_query_free(q); - return r; - } + if (r < 0) + goto fail; return 1; + +fail: + dns_query_free(q); + return r; } static void bus_method_resolve_address_complete(DnsQuery *q) { _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - unsigned added = 0, i; - int r; + DnsResourceRecord *rr; + unsigned added = 0; + int ifindex, r; assert(q); @@ -359,6 +298,8 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { goto finish; } + /* We don't process CNAME for PTR lookups. */ + r = sd_bus_message_new_method_return(q->request, &reply); if (r < 0) goto finish; @@ -368,16 +309,14 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { goto finish; if (q->answer) { - answer = dns_answer_ref(q->answer); - - for (i = 0; i < answer->n_rrs; i++) { - r = dns_question_matches_rr(q->question, answer->items[i].rr); + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(q->question, rr); if (r < 0) goto finish; if (r == 0) continue; - r = sd_bus_message_append(reply, "(is)", answer->items[i].ifindex, answer->items[i].rr->ptr.name); + r = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name); if (r < 0) goto finish; @@ -385,12 +324,11 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { } } - if (added == 0) { + if (added <= 0) { _cleanup_free_ char *ip = NULL; in_addr_to_string(q->request_family, &q->request_address, &ip); - - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Address '%s' does not have any RR of requested type", ip); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Address '%s' does not have any RR of requested type", strna(ip)); goto finish; } @@ -407,16 +345,14 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { finish: if (r < 0) { log_error_errno(r, "Failed to send address reply: %m"); - sd_bus_reply_method_errno(q->request, -r, NULL); + sd_bus_reply_method_errno(q->request, r, NULL); } dns_query_free(q); } static int bus_method_resolve_address(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - _cleanup_free_ char *reverse = NULL; Manager *m = userdata; int family, ifindex; uint64_t flags; @@ -428,6 +364,8 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s assert(message); assert(m); + assert_cc(sizeof(int) == sizeof(int32_t)); + r = sd_bus_message_read(message, "ii", &ifindex, &family); if (r < 0) return r; @@ -446,25 +384,11 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s if (r < 0) return r; - r = check_ifindex_flags(ifindex, &flags, error); - if (r < 0) - return r; - - r = dns_name_reverse(family, d, &reverse); + r = check_ifindex_flags(ifindex, &flags, 0, error); if (r < 0) return r; - question = dns_question_new(1); - if (!question) - return -ENOMEM; - - key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse); - if (!key) - return -ENOMEM; - - reverse = NULL; - - r = dns_question_add(question, key); + r = dns_question_new_reverse(&question, family, d); if (r < 0) return r; @@ -479,21 +403,58 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s r = dns_query_bus_track(q, message); if (r < 0) - return r; + goto fail; r = dns_query_go(q); - if (r < 0) { - dns_query_free(q); - return r; - } + if (r < 0) + goto fail; return 1; + +fail: + dns_query_free(q); + return r; +} + +static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + size_t start; + int r; + + assert(m); + assert(rr); + + r = sd_bus_message_open_container(m, 'r', "iqqay"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "iqq", + ifindex, + rr->key->class, + rr->key->type); + if (r < 0) + return r; + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); + if (r < 0) + return r; + + p->refuse_compression = true; + + r = dns_packet_append_rr(p, rr, &start); + if (r < 0) + return r; + + r = sd_bus_message_append_array(m, 'y', DNS_PACKET_DATA(p) + start, p->size - start); + if (r < 0) + return r; + + return sd_bus_message_close_container(m); } static void bus_method_resolve_record_complete(DnsQuery *q) { _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - unsigned added = 0, i; + unsigned added = 0; int r; assert(q); @@ -503,6 +464,16 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { goto finish; } + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(q->question)); + goto finish; + } + if (r < 0) + goto finish; + if (r > 0) /* Following a CNAME */ + return; + r = sd_bus_message_new_method_return(q->request, &reply); if (r < 0) goto finish; @@ -512,44 +483,17 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { goto finish; if (q->answer) { - answer = dns_answer_ref(q->answer); - - for (i = 0; i < answer->n_rrs; i++) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - size_t start; + DnsResourceRecord *rr; + int ifindex; - r = dns_question_matches_rr(q->question, answer->items[i].rr); + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(q->question, rr); if (r < 0) goto finish; if (r == 0) continue; - r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); - if (r < 0) - goto finish; - - p->refuse_compression = true; - - r = dns_packet_append_rr(p, answer->items[i].rr, &start); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'r', "iqqay"); - if (r < 0) - goto finish; - - r = sd_bus_message_append(reply, "iqq", - answer->items[i].ifindex, - answer->items[i].rr->key->class, - answer->items[i].rr->key->type); - if (r < 0) - goto finish; - - r = sd_bus_message_append_array(reply, 'y', DNS_PACKET_DATA(p) + start, p->size - start); - if (r < 0) - goto finish; - - r = sd_bus_message_close_container(reply); + r = bus_message_append_rr(reply, rr, ifindex); if (r < 0) goto finish; @@ -558,7 +502,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { } if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", q->request_hostname); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_question_name(q->question)); goto finish; } @@ -575,7 +519,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { finish: if (r < 0) { log_error_errno(r, "Failed to send record reply: %m"); - sd_bus_reply_method_errno(q->request, -r, NULL); + sd_bus_reply_method_errno(q->request, r, NULL); } dns_query_free(q); @@ -594,15 +538,19 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd assert(message); assert(m); + assert_cc(sizeof(int) == sizeof(int32_t)); + r = sd_bus_message_read(message, "isqqt", &ifindex, &name, &class, &type, &flags); if (r < 0) return r; - r = dns_name_normalize(name, NULL); + r = dns_name_is_valid(name); if (r < 0) + return r; + if (r == 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name); - r = check_ifindex_flags(ifindex, &flags, error); + r = check_ifindex_flags(ifindex, &flags, 0, error); if (r < 0) return r; @@ -623,20 +571,535 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd return r; q->request = sd_bus_message_ref(message); - q->request_hostname = name; q->complete = bus_method_resolve_record_complete; r = dns_query_bus_track(q, message); if (r < 0) - return r; + goto fail; r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + DnsQuery *aux; + int r; + + assert(q); + assert(reply); + assert(rr); + assert(rr->key); + + 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; + + if (aux->state != DNS_TRANSACTION_SUCCESS) + continue; + if (aux->auxiliary_result != 0) + continue; + + r = dns_name_equal(dns_question_name(aux->question), rr->srv.name); + if (r < 0) + return r; + if (r == 0) + continue; + + DNS_ANSWER_FOREACH(zz, aux->answer) { + + r = dns_question_matches_rr(aux->question, zz); + 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 = sd_bus_message_open_container(reply, 'r', "qqqsa(iiay)s"); + if (r < 0) + return r; + + r = sd_bus_message_append( + reply, + "qqqs", + rr->srv.priority, rr->srv.weight, rr->srv.port, rr->srv.name); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + return r; + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + DnsResourceRecord *zz; + int ifindex; + + if (aux->state != DNS_TRANSACTION_SUCCESS) + continue; + if (aux->auxiliary_result != 0) + continue; + + r = dns_name_equal(dns_question_name(aux->question), rr->srv.name); + if (r < 0) + return r; + if (r == 0) + continue; + + DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) { + + r = dns_question_matches_rr(aux->question, zz); + if (r < 0) + return r; + if (r == 0) + continue; + + r = append_address(reply, zz, ifindex); + if (r < 0) + return r; + } + } + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + /* Note that above we appended the hostname as encoded in the + * SRV, and here the canonical hostname this maps to. */ + r = sd_bus_message_append(reply, "s", canonical ? DNS_RESOURCE_KEY_NAME(canonical->key) : rr->srv.name); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return 1; +} + +static int append_txt(sd_bus_message *reply, DnsResourceRecord *rr) { + DnsTxtItem *i; + int r; + + assert(reply); + assert(rr); + assert(rr->key); + + if (rr->key->type != DNS_TYPE_TXT) + return 0; + + LIST_FOREACH(items, i, rr->txt.items) { + + if (i->length <= 0) + continue; + + r = sd_bus_message_append_array(reply, 'y', i->data, i->length); + if (r < 0) + return r; + } + + return 1; +} + +static void resolve_service_all_complete(DnsQuery *q) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; + DnsQuery *aux; + unsigned added = false; + int r; + + assert(q); + + if (q->block_all_complete > 0) + 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 */ + 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 = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(bad->question)); + goto finish; + } + + r = bad->auxiliary_result; + goto finish; + } + + r = reply_query_state(bad); + goto finish; + } + } + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(qqqsa(iiay)s)"); + if (r < 0) + goto finish; + + if (q->answer) { + DnsResourceRecord *rr; + + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(q->question, rr); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_srv(q, reply, rr); + if (r < 0) + goto finish; + if (r == 0) /* not an SRV record */ + continue; + + if (!canonical) + canonical = dns_resource_record_ref(rr); + + added++; + } + } + + if (added <= 0) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_name(q->question)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "ay"); + if (r < 0) + goto finish; + + if (q->answer) { + DnsResourceRecord *rr; + + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(q->question, rr); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_txt(reply, rr); + if (r < 0) + goto finish; + } + } + + r = sd_bus_message_close_container(reply); + 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 = sd_bus_message_append( + reply, + "ssst", + name, type, domain, + SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send service reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +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(q); + if (r > 0) /* 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; + 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; + DnsQuery *aux; + 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); + if (r < 0) + return r; + + r = dns_query_new(q->manager, &aux, question, ifindex, q->flags); + 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 */ + + dns_query_free(aux); + return 0; + } + if (r < 0) + goto fail; + + /* Note that auxiliary queries do not track the original bus + * client, only the primary request does that. */ + + r = dns_query_go(aux); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(aux); + return r; +} + +static void bus_method_resolve_service_complete(DnsQuery *q) { + unsigned found = 0; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(q->question)); + goto finish; + } + if (r < 0) + goto finish; + if (r > 0) /* This was a cname, and the query was restarted. */ + return; + + if (q->answer) { + DnsResourceRecord *rr; + int ifindex; + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(q->question, rr); + if (r < 0) + goto finish; + if (r == 0) + continue; + + if (rr->key->type != DNS_TYPE_SRV) + 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 (found <= 0) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_name(q->question)); + goto finish; + } + + /* Maybe we are already finished? check now... */ + resolve_service_all_complete(q); + return; + +finish: if (r < 0) { - dns_query_free(q); + log_error_errno(r, "Failed to send service reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + const char *name, *type, *domain, *joined; + _cleanup_free_ char *n = NULL; + Manager *m = userdata; + int family, ifindex; + uint64_t flags; + DnsQuery *q; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "isssit", &ifindex, &name, &type, &domain, &family, &flags); + if (r < 0) return r; + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + if (isempty(name)) + name = NULL; + else { + if (!dns_service_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name); } + if (isempty(type)) + type = NULL; + else { + r = dns_srv_type_verify(type); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type); + } + + r = dns_name_is_valid(domain); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid domain '%s'", domain); + + if (name && !type) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type."); + + r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error); + if (r < 0) + return r; + + if (type) { + /* If the type is specified, we generate the full domain name to look up ourselves */ + r = dns_service_join(name, type, domain, &n); + if (r < 0) + return r; + + joined = n; + } else + /* If no type is specified, we assume the domain + * contains the full domain name to lookup already */ + joined = domain; + + r = dns_question_new_service(&question, joined, !(flags & SD_RESOLVED_NO_TXT)); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question, ifindex, flags); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + q->complete = bus_method_resolve_service_complete; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + return 1; + +fail: + dns_query_free(q); + return r; } static const sd_bus_vtable resolve_vtable[] = { @@ -644,6 +1107,7 @@ static const sd_bus_vtable resolve_vtable[] = { SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; |