diff options
Diffstat (limited to 'lib/addns/dnsquery_srv.c')
-rw-r--r-- | lib/addns/dnsquery_srv.c | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/lib/addns/dnsquery_srv.c b/lib/addns/dnsquery_srv.c new file mode 100644 index 0000000..6cba22f --- /dev/null +++ b/lib/addns/dnsquery_srv.c @@ -0,0 +1,560 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "replace.h" +#include "dnsquery.h" +#include "dnsquery_srv.h" +#include "lib/util/debug.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/talloc_stack.h" +#include "lib/util/samba_util.h" +#include "librpc/gen_ndr/dns.h" +#include "librpc/ndr/libndr.h" + +/* + * For an array of dns_rr_srv records, issue A/AAAA queries for those + * records where the initial reply did not return IP addresses. + */ + +struct dns_rr_srv_fill_state { + struct dns_rr_srv *srvs; + size_t num_srvs; + + struct tevent_req **subreqs; + size_t num_outstanding; +}; + +static void dns_rr_srv_fill_done_a(struct tevent_req *subreq); +#if defined(HAVE_IPV6) +static void dns_rr_srv_fill_done_aaaa(struct tevent_req *subreq); +#endif +static void dns_rr_srv_fill_timedout(struct tevent_req *subreq); + +static struct tevent_req *dns_rr_srv_fill_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct dns_rr_srv *srvs, + size_t num_srvs, + uint32_t timeout) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct dns_rr_srv_fill_state *state = NULL; + size_t i, num_subreqs; + + req = tevent_req_create(mem_ctx, &state, struct dns_rr_srv_fill_state); + if (req == NULL) { + return NULL; + } + state->srvs = srvs; + state->num_srvs = num_srvs; + + /* + * Without IPv6 we only use half of this, but who does not + * have IPv6 these days? + */ + num_subreqs = num_srvs * 2; + + state->subreqs = talloc_zero_array( + state, struct tevent_req *, num_subreqs); + if (tevent_req_nomem(state->subreqs, req)) { + return tevent_req_post(req, ev); + } + + for (i=0; i<num_srvs; i++) { + + if (srvs[i].hostname == NULL) { + continue; + } + if (srvs[i].ss_s != NULL) { + /* IP address returned in SRV record. */ + continue; + } + + subreq = ads_dns_lookup_a_send( + state->subreqs, ev, srvs[i].hostname); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(state->subreqs); + return tevent_req_post(req, ev); + } + tevent_req_set_callback( + subreq, dns_rr_srv_fill_done_a, req); + + state->subreqs[i*2] = subreq; + state->num_outstanding += 1; + +#if defined(HAVE_IPV6) + subreq = ads_dns_lookup_aaaa_send( + state->subreqs, ev, srvs[i].hostname); + if (tevent_req_nomem(subreq, req)) { + TALLOC_FREE(state->subreqs); + return tevent_req_post(req, ev); + } + tevent_req_set_callback( + subreq, dns_rr_srv_fill_done_aaaa, req); + + state->subreqs[i*2+1] = subreq; + state->num_outstanding += 1; +#endif + } + + if (state->num_outstanding == 0) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + subreq = tevent_wakeup_send( + state->subreqs, + ev, + tevent_timeval_current_ofs(timeout, 0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, dns_rr_srv_fill_timedout, req); + + return req; +} + +static void dns_rr_srv_fill_done( + struct tevent_req *subreq, + NTSTATUS (*recv_fn)( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t *rcode_out, + size_t *num_names_out, + char ***hostnames_out, + struct samba_sockaddr **addrs_out)) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct dns_rr_srv_fill_state *state = tevent_req_data( + req, struct dns_rr_srv_fill_state); + size_t num_subreqs = talloc_array_length(state->subreqs); + struct dns_rr_srv *srv = NULL; + size_t num_ips; + struct sockaddr_storage *tmp = NULL; + uint8_t rcode = 0; + char **hostnames_out = NULL; + struct samba_sockaddr *addrs = NULL; + size_t num_addrs = 0; + NTSTATUS status; + size_t i; + const char *ip_dbg_str = (recv_fn == ads_dns_lookup_a_recv) ? + "A" : "AAAA"; + + /* + * This loop walks all potential subreqs. Typical setups won't + * have more than a few DCs. If you have really many DCs + * (hundreds) and a DNS that doesn't return the DC IPs in the + * SRV reply, you have bigger problems than this loop linearly + * walking a pointer array. This is theoretically O(n^2), but + * probably the DNS roundtrip time outweights this by a + * lot. And we have a global timeout on this whole + * dns_rr_srv_fill routine. + */ + for (i=0; i<num_subreqs; i++) { + if (state->subreqs[i] == subreq) { + state->subreqs[i] = NULL; + break; + } + } + if (i == num_subreqs) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return; + } + + srv = &state->srvs[i/2]; /* 2 subreq per srv */ + + status = recv_fn( + subreq, + state, + &rcode, + &num_addrs, + &hostnames_out, + &addrs); + TALLOC_FREE(subreq); + + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("async DNS %s lookup for %s returned %s\n", + ip_dbg_str, + srv->hostname, + nt_errstr(status)); + num_addrs = 0; + goto done; + } + + if (rcode != DNS_RCODE_OK) { + DBG_INFO("async DNS %s lookup for %s returned DNS code " + "%"PRIu8"\n", + ip_dbg_str, + srv->hostname, + rcode); + num_addrs = 0; + goto done; + } + + if (num_addrs == 0) { + DBG_INFO("async DNS %s lookup for %s returned 0 addresses.\n", + ip_dbg_str, + srv->hostname); + goto done; + } + + num_ips = talloc_array_length(srv->ss_s); + + if (num_ips + num_addrs < num_addrs) { + /* overflow */ + goto done; + } + + tmp = talloc_realloc( + state->srvs, + srv->ss_s, + struct sockaddr_storage, + num_ips + num_addrs); + if (tmp == NULL) { + goto done; + } + + for (i=0; i<num_addrs; i++) { + char addr[INET6_ADDRSTRLEN]; + DBG_INFO("async DNS %s lookup for %s [%zu] got %s -> %s\n", + ip_dbg_str, + srv->hostname, + i, + hostnames_out[i], + print_sockaddr(addr, sizeof(addr), &addrs[i].u.ss)); + tmp[num_ips + i] = addrs[i].u.ss; + } + srv->ss_s = tmp; + srv->num_ips = num_ips + num_addrs; + +done: + state->num_outstanding -= 1; + if (state->num_outstanding == 0) { + tevent_req_done(req); + } +} + +static void dns_rr_srv_fill_done_a(struct tevent_req *subreq) +{ + dns_rr_srv_fill_done(subreq, ads_dns_lookup_a_recv); +} + +#if defined(HAVE_IPV6) +static void dns_rr_srv_fill_done_aaaa(struct tevent_req *subreq) +{ + dns_rr_srv_fill_done(subreq, ads_dns_lookup_aaaa_recv); +} +#endif + +static void dns_rr_srv_fill_timedout(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct dns_rr_srv_fill_state *state = tevent_req_data( + req, struct dns_rr_srv_fill_state); + bool ok; + + if (DEBUGLEVEL >= DBGLVL_INFO) { + size_t i, num_addrs = 0; + + for (i=0; i<state->num_srvs; i++) { + /* + * Count for the debug. Code that fills this + * in ensures no wrap. + */ + num_addrs += state->srvs[i].num_ips; + } + + DBG_INFO("async DNS lookup timed out after %zu addresses " + "returned (not an error)\n", + num_addrs); + } + + ok = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + TALLOC_FREE(state->subreqs); + if (!ok) { + tevent_req_oom(subreq); + return; + } + + tevent_req_done(req); +} + +static NTSTATUS dns_rr_srv_fill_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_ntstatus(req); +} + +/* + * Request a SRV record and fill in the A/AAAA records if the SRV + * record did not carry them. + */ + +struct ads_dns_query_srv_state { + struct tevent_context *ev; + uint32_t async_dns_timeout; + const char *query; + + struct tevent_req *fill_req; + struct tevent_req *timeout_req; + struct dns_rr_srv *srvs; + size_t num_srvs; +}; + +static void ads_dns_query_srv_site_aware_done(struct tevent_req *subreq); +static void ads_dns_query_srv_done(struct tevent_req *subreq); +static void ads_dns_query_srv_filled(struct tevent_req *subreq); + +struct tevent_req *ads_dns_query_srv_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + uint32_t async_dns_timeout, + const char *sitename, + const char *query) +{ + struct tevent_req *req = NULL, *subreq = NULL; + struct ads_dns_query_srv_state *state = NULL; + + req = tevent_req_create( + mem_ctx, &state, struct ads_dns_query_srv_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->async_dns_timeout = async_dns_timeout; + state->query = query; + + if ((sitename != NULL) && (sitename[0] != '\0')) { + char *after_tcp = NULL; + char *site_aware = NULL; + + /* + * ".<SITENAME>._sites" comes after "._tcp." + */ + after_tcp = strstr(state->query, "._tcp."); + if (after_tcp == NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + after_tcp += 6; /* strlen("._tcp.") */ + + site_aware = talloc_asprintf( + state, + "%.*s%s._sites.%s", + (int)(after_tcp - state->query), + state->query, + sitename, + after_tcp); + if (tevent_req_nomem(site_aware, req)) { + return tevent_req_post(req, ev); + } + + subreq = ads_dns_lookup_srv_send(state, ev, site_aware); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback( + subreq, ads_dns_query_srv_site_aware_done, req); + return req; + } + + subreq = ads_dns_lookup_srv_send(state, state->ev, state->query); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, ads_dns_query_srv_done, req); + return req; +} + +static void ads_dns_query_srv_site_aware_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct ads_dns_query_srv_state *state = tevent_req_data( + req, struct ads_dns_query_srv_state); + NTSTATUS status; + + status = ads_dns_lookup_srv_recv( + subreq, state, &state->srvs, &state->num_srvs); + TALLOC_FREE(subreq); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) || + NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_REFUSED)) { + tevent_req_nterror(req, status); + return; + } + + if (NT_STATUS_IS_OK(status) && (state->num_srvs != 0)) { + if (state->async_dns_timeout == 0) { + tevent_req_done(req); + return; + } + + subreq = dns_rr_srv_fill_send( + state, + state->ev, + state->srvs, + state->num_srvs, + state->async_dns_timeout); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback( + subreq, ads_dns_query_srv_filled, req); + return; + } + + subreq = ads_dns_lookup_srv_send(state, state->ev, state->query); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, ads_dns_query_srv_done, req); +} + +static void ads_dns_query_srv_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct ads_dns_query_srv_state *state = tevent_req_data( + req, struct ads_dns_query_srv_state); + NTSTATUS status; + + status = ads_dns_lookup_srv_recv( + subreq, state, &state->srvs, &state->num_srvs); + if (tevent_req_nterror(req, status)) { + return; + } + + if ((state->num_srvs == 0) || (state->async_dns_timeout == 0)) { + tevent_req_done(req); + return; + } + + subreq = dns_rr_srv_fill_send( + state, + state->ev, + state->srvs, + state->num_srvs, + state->async_dns_timeout); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, ads_dns_query_srv_filled, req); +} + +static void ads_dns_query_srv_filled(struct tevent_req *subreq) +{ + NTSTATUS status = dns_rr_srv_fill_recv(subreq); + return tevent_req_simple_finish_ntstatus(subreq, status); +} + +NTSTATUS ads_dns_query_srv_recv( + struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct dns_rr_srv **srvs, + size_t *num_srvs) +{ + struct ads_dns_query_srv_state *state = tevent_req_data( + req, struct ads_dns_query_srv_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + if (srvs != NULL) { + *srvs = talloc_move(mem_ctx, &state->srvs); + } + if (num_srvs != NULL) { + *num_srvs = state->num_srvs; + } + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS ads_dns_query_srv( + TALLOC_CTX *mem_ctx, + uint32_t async_dns_timeout, + const char *sitename, + const char *query, + struct dns_rr_srv **srvs, + size_t *num_srvs) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev = NULL; + struct tevent_req *req = NULL; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = ads_dns_query_srv_send( + frame, ev, async_dns_timeout, sitename, query); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = ads_dns_query_srv_recv(req, mem_ctx, srvs, num_srvs); +fail: + TALLOC_FREE(frame); + return status; +} + +char *ads_dns_query_string_dcs(TALLOC_CTX *mem_ctx, const char *realm) +{ + char *ret = talloc_asprintf(mem_ctx, "_ldap._tcp.dc._msdcs.%s", realm); + return ret; +} + +char *ads_dns_query_string_gcs(TALLOC_CTX *mem_ctx, const char *realm) +{ + char *ret = talloc_asprintf(mem_ctx, "_ldap._tcp.gc._msdcs.%s", realm); + return ret; +} + +char *ads_dns_query_string_kdcs(TALLOC_CTX *mem_ctx, const char *realm) +{ + char *ret = talloc_asprintf( + mem_ctx, "_kerberos._tcp.dc._msdcs.%s", realm); + return ret; +} + +char *ads_dns_query_string_pdc(TALLOC_CTX *mem_ctx, const char *realm) +{ + char *ret = talloc_asprintf( + mem_ctx, "_ldap._tcp.pdc._msdcs.%s", realm); + return ret; +} + +char *ads_dns_query_string_dcs_guid( + TALLOC_CTX *mem_ctx, + const struct GUID *domain_guid, + const char *realm) +{ + struct GUID_txt_buf buf; + char *ret = NULL; + + talloc_asprintf( + mem_ctx, + "_ldap._tcp.%s.domains._msdcs.%s", + GUID_buf_string(domain_guid, &buf), + realm); + return ret; +} |