/* * 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 . */ #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; isubreqs, 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 outweighs this by a * lot. And we have a global timeout on this whole * dns_rr_srv_fill routine. */ for (i=0; isubreqs[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 %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; inum_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; /* * ".._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; }