summaryrefslogtreecommitdiffstats
path: root/src/resolv
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
commit74aa0bc6779af38018a03fd2cf4419fe85917904 (patch)
tree9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/resolv
parentInitial commit. (diff)
downloadsssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz
sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/resolv')
-rw-r--r--src/resolv/async_resolv.c2626
-rw-r--r--src/resolv/async_resolv.h230
-rw-r--r--src/resolv/async_resolv_utils.c343
3 files changed, 3199 insertions, 0 deletions
diff --git a/src/resolv/async_resolv.c b/src/resolv/async_resolv.c
new file mode 100644
index 0000000..6b3a8ce
--- /dev/null
+++ b/src/resolv/async_resolv.c
@@ -0,0 +1,2626 @@
+/*
+ SSSD
+
+ Async resolver
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ 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 <sys/select.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+
+#include <ares.h>
+#include <talloc.h>
+#include <tevent.h>
+
+#include <errno.h>
+#include <netdb.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "config.h"
+#include "resolv/async_resolv.h"
+#include "util/dlinklist.h"
+#include "util/util.h"
+
+#define DNS__16BIT(p) (((p)[0] << 8) | (p)[1])
+
+/*
+ * Macro DNS__32BIT reads a network long (32 bit) given in network
+ * byte order, and returns its value as an unsigned int. Copied
+ * from c-ares source code.
+ */
+#define DNS__32BIT(p) ((unsigned int) \
+ (((unsigned int)((unsigned char)(p)[0]) << 24U) | \
+ ((unsigned int)((unsigned char)(p)[1]) << 16U) | \
+ ((unsigned int)((unsigned char)(p)[2]) << 8U) | \
+ ((unsigned int)((unsigned char)(p)[3]))))
+
+#define DNS_HEADER_ANCOUNT(h) DNS__16BIT((h) + 6)
+#define DNS_RR_LEN(r) DNS__16BIT((r) + 8)
+#define DNS_RR_TTL(r) DNS__32BIT((r) + 4)
+
+enum host_database default_host_dbs[] = { DB_FILES, DB_DNS, DB_SENTINEL };
+
+struct fd_watch {
+ struct fd_watch *prev;
+ struct fd_watch *next;
+
+ int fd;
+ struct resolv_ctx *ctx;
+ struct tevent_fd *fde;
+};
+
+struct resolv_ctx {
+ struct tevent_context *ev_ctx;
+ ares_channel channel;
+
+ /* List of file descriptors that are watched by tevent. */
+ struct fd_watch *fds;
+
+ /* Time in milliseconds before canceling a DNS request */
+ int timeout;
+
+ /* Time in milliseconds for communication with single DNS server. */
+ int ares_timeout;
+
+ /* Use search list from resolv.conf and perform DNS search if needed. */
+ bool use_search_list;
+
+ /* The timeout watcher periodically calls ares_process_fd() to check
+ * if our pending requests didn't timeout. */
+ int pending_requests;
+ struct tevent_timer *timeout_watcher;
+};
+
+struct request_watch {
+ struct tevent_req *req;
+ struct resolv_request *rr;
+};
+
+struct resolv_request {
+ struct resolv_ctx *ctx;
+ struct request_watch *rwatch;
+ struct tevent_timer *request_timeout;
+};
+
+static int
+return_code(int ares_code)
+{
+ switch (ares_code) {
+ case ARES_SUCCESS:
+ return EOK;
+ case ARES_ENOMEM:
+ return ENOMEM;
+ case ARES_EFILE:
+ default:
+ return EIO;
+ }
+}
+
+const char *
+resolv_strerror(int ares_code)
+{
+ return ares_strerror(ares_code);
+}
+
+static int
+fd_watch_destructor(struct fd_watch *f)
+{
+ DLIST_REMOVE(f->ctx->fds, f);
+ f->fd = -1;
+
+ return 0;
+}
+
+static void
+fd_input_available(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *data)
+{
+ struct fd_watch *watch = talloc_get_type(data, struct fd_watch);
+
+ if (watch->ctx->channel == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid ares channel - this is likely a bug\n");
+ return;
+ }
+
+ ares_process_fd(watch->ctx->channel,
+ flags & TEVENT_FD_READ ? watch->fd : ARES_SOCKET_BAD,
+ flags & TEVENT_FD_WRITE ? watch->fd : ARES_SOCKET_BAD);
+}
+
+static void
+check_fd_timeouts(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval current_time, void *private_data);
+
+static void
+add_timeout_timer(struct tevent_context *ev, struct resolv_ctx *ctx)
+{
+ struct timeval tv = { 0, 0 };
+ struct timeval *tvp;
+
+ if (ctx->timeout_watcher) {
+ return;
+ }
+
+ tvp = ares_timeout(ctx->channel, NULL, &tv);
+
+ if (tvp == NULL) {
+ tvp = &tv;
+ }
+
+ /* Enforce a minimum of 1 second. */
+ if (tvp->tv_sec < 1) {
+ tv = tevent_timeval_current_ofs(1, 0);
+ } else {
+ tv = tevent_timeval_current_ofs(tvp->tv_sec, tvp->tv_usec);
+ }
+
+ ctx->timeout_watcher = tevent_add_timer(ev, ctx, tv, check_fd_timeouts,
+ ctx);
+ if (ctx->timeout_watcher == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer() failed\n");
+ }
+}
+
+static void
+check_fd_timeouts(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval current_time, void *private_data)
+{
+ struct resolv_ctx *ctx = talloc_get_type(private_data, struct resolv_ctx);
+
+ DEBUG(SSSDBG_TRACE_ALL, "Checking for DNS timeouts\n");
+
+ /* NULLify the timeout_watcher so we don't
+ * free it in the _done() function if it
+ * gets called. Now that we're already in
+ * the handler, tevent will take care of
+ * freeing it when it returns.
+ */
+ ctx->timeout_watcher = NULL;
+
+ ares_process_fd(ctx->channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
+
+ if (ctx->pending_requests > 0) {
+ add_timeout_timer(ev, ctx);
+ }
+}
+
+static void
+resolv_request_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct resolv_request *rreq;
+
+ DEBUG(SSSDBG_MINOR_FAILURE, "The resolve request timed out\n");
+
+ rreq = talloc_get_type(pvt, struct resolv_request);
+ if (rreq->rwatch == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "The request already completed\n");
+ return;
+ }
+
+ tevent_req_error(rreq->rwatch->req, ETIMEDOUT);
+ rreq->rwatch = NULL;
+}
+
+static int
+request_watch_destructor(struct request_watch *rwatch)
+{
+ DEBUG(SSSDBG_TRACE_FUNC, "Deleting request watch\n");
+ if (rwatch->rr) rwatch->rr->rwatch = NULL;
+ return 0;
+}
+
+static struct resolv_request *
+schedule_request_timeout(struct tevent_context *ev, struct resolv_ctx *ctx,
+ struct tevent_req *req)
+{
+ struct resolv_request *rreq;
+ struct timeval tv;
+
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Scheduling a timeout of %d seconds\n",
+ ctx->timeout);
+ tv = tevent_timeval_current_ofs(ctx->timeout, 0);
+
+ /* Intentionally allocating on ctx, because the request might go away
+ * before c-ares returns */
+ rreq = talloc(ctx, struct resolv_request);
+ if (!rreq) {
+ talloc_zfree(req);
+ return NULL;
+ }
+ rreq->ctx = ctx;
+ rreq->request_timeout = tevent_add_timer(ev, rreq, tv,
+ resolv_request_timeout,
+ rreq);
+ if (rreq->request_timeout == NULL) {
+ talloc_free(rreq);
+ return NULL;
+ }
+
+ /* The watch will go away when the request finishes */
+ rreq->rwatch = talloc(req, struct request_watch);
+ if (!rreq->rwatch) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ rreq->rwatch->req = req;
+ rreq->rwatch->rr = rreq;
+ talloc_set_destructor(rreq->rwatch, request_watch_destructor);
+
+ return rreq;
+}
+
+static struct resolv_request *
+schedule_timeout_watcher(struct tevent_context *ev, struct resolv_ctx *ctx,
+ struct tevent_req *req)
+{
+ struct resolv_request *rreq;
+
+ rreq = schedule_request_timeout(ev, ctx, req);
+ if (!rreq) return NULL;
+
+ ctx->pending_requests++;
+
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Scheduling DNS timeout watcher\n");
+ add_timeout_timer(ev, ctx);
+ return rreq;
+}
+
+static void
+unschedule_timeout_watcher(struct resolv_ctx *ctx, struct resolv_request *rreq)
+{
+ /* Unlink the watch if the request is still active */
+ if (rreq->rwatch) {
+ rreq->rwatch->rr = NULL;
+ }
+ talloc_free(rreq); /* Cancels the tevent timeout as well */
+
+ if (ctx->pending_requests <= 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Pending DNS requests mismatch\n");
+ return;
+ }
+
+ ctx->pending_requests--;
+ if (ctx->pending_requests == 0) {
+ DEBUG(SSSDBG_TRACE_ALL, "Unscheduling DNS timeout watcher\n");
+ talloc_zfree(ctx->timeout_watcher);
+ }
+}
+
+static void fd_event_add(struct resolv_ctx *ctx, int s, int flags);
+static void fd_event_close(struct resolv_ctx *ctx, int s);
+
+/*
+ * When ares is ready to read or write to a file descriptor, it will
+ * call this callback. If both read and write are 0, it means that ares
+ * will soon close the socket. We are mainly using this function to register
+ * new file descriptors with tevent.
+ */
+static void
+fd_event(void *data, int s, int fd_read, int fd_write)
+{
+ struct resolv_ctx *ctx = talloc_get_type(data, struct resolv_ctx);
+ struct fd_watch *watch;
+ int flags;
+
+ /* The socket is about to get closed. */
+ if (fd_read == 0 && fd_write == 0) {
+ fd_event_close(ctx, s);
+ return;
+ }
+
+ flags = fd_read ? TEVENT_FD_READ : 0;
+ flags |= fd_write ? TEVENT_FD_WRITE : 0;
+
+ /* Are we already watching this file descriptor? */
+ watch = ctx->fds;
+ while (watch) {
+ if (watch->fd == s) {
+ tevent_fd_set_flags(watch->fde, flags);
+ return;
+ }
+ watch = watch->next;
+ }
+
+ fd_event_add(ctx, s, flags);
+}
+
+static void
+fd_event_add(struct resolv_ctx *ctx, int s, int flags)
+{
+ struct fd_watch *watch;
+
+ /* The file descriptor is new, register it with tevent. */
+ watch = talloc(ctx, struct fd_watch);
+ if (watch == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Out of memory allocating fd_watch structure\n");
+ return;
+ }
+ talloc_set_destructor(watch, fd_watch_destructor);
+
+ watch->fd = s;
+ watch->ctx = ctx;
+
+ watch->fde = tevent_add_fd(ctx->ev_ctx, watch, s, flags,
+ fd_input_available, watch);
+ if (watch->fde == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_fd() failed\n");
+ talloc_free(watch);
+ return;
+ }
+ DLIST_ADD(ctx->fds, watch);
+}
+
+static void
+fd_event_close(struct resolv_ctx *ctx, int s)
+{
+ struct fd_watch *watch;
+
+ /* Remove the socket from list */
+ watch = ctx->fds;
+ while (watch) {
+ if (watch->fd == s) {
+ talloc_free(watch);
+ return;
+ }
+ watch = watch->next;
+ }
+}
+
+static int
+resolv_ctx_destructor(struct resolv_ctx *ctx)
+{
+ ares_channel channel;
+
+ if (ctx->channel == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Ares channel already destroyed?\n");
+ return -1;
+ }
+
+ /* Set ctx->channel to NULL first, so that callbacks that get
+ * ARES_EDESTRUCTION won't retry. */
+ channel = ctx->channel;
+ ctx->channel = NULL;
+ ares_destroy(channel);
+
+ return 0;
+}
+
+static int
+recreate_ares_channel(struct resolv_ctx *ctx)
+{
+ int ret;
+ ares_channel new_channel;
+ ares_channel old_channel;
+ struct ares_options options;
+
+ DEBUG(SSSDBG_CONF_SETTINGS, "Initializing new c-ares channel\n");
+ /* FIXME: the options would contain
+ * the nameservers to contact, the domains
+ * to search... => get from confdb
+ */
+ options.sock_state_cb = fd_event;
+ options.sock_state_cb_data = ctx;
+ options.timeout = ctx->ares_timeout;
+ /* Only affects ares_gethostbyname */
+ options.lookups = discard_const("f");
+ options.tries = 1;
+ options.flags = 0;
+ if (ctx->use_search_list == false) {
+ options.flags |= ARES_FLAG_NOSEARCH;
+ }
+
+ ret = ares_init_options(&new_channel, &options,
+ ARES_OPT_SOCK_STATE_CB |
+ ARES_OPT_TIMEOUTMS |
+ ARES_OPT_LOOKUPS |
+ ARES_OPT_TRIES |
+ ARES_OPT_FLAGS);
+ if (ret != ARES_SUCCESS) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to initialize ares channel: %s\n",
+ resolv_strerror(ret));
+ return return_code(ret);
+ }
+
+ old_channel = ctx->channel;
+ ctx->channel = new_channel;
+ if (old_channel != NULL) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Destroying the old c-ares channel\n");
+ ares_destroy(old_channel);
+ }
+
+ return EOK;
+}
+
+int
+resolv_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev_ctx,
+ int timeout, int ares_timeout, bool use_search_list,
+ struct resolv_ctx **ctxp)
+{
+ int ret;
+ struct resolv_ctx *ctx;
+
+ if (timeout < 1) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "The timeout is too short, DNS operations are going to fail. "
+ "This is a bug outside unit tests\n");
+ }
+
+ ctx = talloc_zero(mem_ctx, struct resolv_ctx);
+ if (ctx == NULL)
+ return ENOMEM;
+
+ ctx->ev_ctx = ev_ctx;
+ ctx->timeout = timeout;
+ ctx->ares_timeout = ares_timeout;
+ ctx->use_search_list = use_search_list;
+
+ ret = recreate_ares_channel(ctx);
+ if (ret != EOK) {
+ goto done;
+ }
+
+ talloc_set_destructor(ctx, resolv_ctx_destructor);
+
+ *ctxp = ctx;
+ return EOK;
+
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+void
+resolv_reread_configuration(struct resolv_ctx *ctx)
+{
+ recreate_ares_channel(ctx);
+}
+
+static errno_t
+resolv_copy_in_addr(TALLOC_CTX *mem_ctx, struct resolv_addr *ret,
+ struct ares_addrttl *attl)
+{
+ ret->ipaddr = talloc_array(mem_ctx, uint8_t, sizeof(struct in_addr));
+ if (!ret->ipaddr) return ENOMEM;
+
+ memcpy(ret->ipaddr, &attl->ipaddr, sizeof(struct in_addr));
+ ret->ttl = attl->ttl;
+
+ return EOK;
+}
+
+static errno_t
+resolv_copy_in6_addr(TALLOC_CTX *mem_ctx, struct resolv_addr *ret,
+ struct ares_addr6ttl *a6ttl)
+{
+ ret->ipaddr = talloc_array(mem_ctx, uint8_t, sizeof(struct in6_addr));
+ if (!ret->ipaddr) return ENOMEM;
+
+ memcpy(ret->ipaddr, &a6ttl->ip6addr, sizeof(struct in6_addr));
+ ret->ttl = a6ttl->ttl;
+
+ return EOK;
+}
+
+static struct resolv_hostent *
+resolv_copy_hostent_common(TALLOC_CTX *mem_ctx, struct hostent *src)
+{
+ struct resolv_hostent *ret;
+ int len;
+ int i;
+
+ ret = talloc_zero(mem_ctx, struct resolv_hostent);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ if (src->h_name != NULL) {
+ ret->name = talloc_strdup(ret, src->h_name);
+ if (ret->name == NULL) {
+ goto fail;
+ }
+ }
+ if (src->h_aliases != NULL) {
+ for (len = 0; src->h_aliases[len] != NULL; len++);
+
+ ret->aliases = talloc_array(ret, char *, len + 1);
+ if (ret->aliases == NULL) {
+ goto fail;
+ }
+
+ for (i = 0; i < len; i++) {
+ ret->aliases[i] = talloc_strdup(ret->aliases, src->h_aliases[i]);
+ if (ret->aliases[i] == NULL) {
+ goto fail;
+ }
+ }
+ ret->aliases[len] = NULL;
+ }
+
+ ret->family = src->h_addrtype;
+ return ret;
+
+fail:
+ talloc_free(ret);
+ return NULL;
+}
+
+struct resolv_hostent *
+resolv_copy_hostent(TALLOC_CTX *mem_ctx, struct hostent *src)
+{
+ struct resolv_hostent *ret;
+ int len;
+ int i;
+
+ ret = resolv_copy_hostent_common(mem_ctx, src);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ if (src->h_addr_list != NULL) {
+ for (len = 0; src->h_addr_list[len] != NULL; len++);
+
+ ret->addr_list = talloc_array(ret, struct resolv_addr *, len + 1);
+ if (ret->addr_list == NULL) {
+ goto fail;
+ }
+
+ for (i = 0; i < len; i++) {
+ ret->addr_list[i] = talloc_zero(ret->addr_list,
+ struct resolv_addr);
+ if (ret->addr_list[i] == NULL) {
+ goto fail;
+ }
+
+ ret->addr_list[i]->ipaddr = talloc_memdup(ret->addr_list[i],
+ src->h_addr_list[i],
+ src->h_length);
+ if (ret->addr_list[i]->ipaddr == NULL) {
+ goto fail;
+ }
+ ret->addr_list[i]->ttl = RESOLV_DEFAULT_TTL;
+ }
+ ret->addr_list[len] = NULL;
+ }
+ return ret;
+
+fail:
+ talloc_free(ret);
+ return NULL;
+}
+
+struct resolv_hostent *
+resolv_copy_hostent_ares(TALLOC_CTX *mem_ctx, struct hostent *src,
+ int family, void *ares_ttl_data,
+ int num_ares_ttl_data)
+{
+ struct resolv_hostent *ret;
+ errno_t cret;
+ int i;
+
+ ret = resolv_copy_hostent_common(mem_ctx, src);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ if (num_ares_ttl_data > 0) {
+ ret->addr_list = talloc_array(ret, struct resolv_addr *,
+ num_ares_ttl_data + 1);
+ if (ret->addr_list == NULL) {
+ goto fail;
+ }
+
+ for (i = 0; i < num_ares_ttl_data; i++) {
+ ret->addr_list[i] = talloc_zero(ret->addr_list,
+ struct resolv_addr);
+ if (ret->addr_list[i] == NULL) {
+ goto fail;
+ }
+
+ switch (family) {
+ case AF_INET:
+ cret = resolv_copy_in_addr(ret->addr_list, ret->addr_list[i],
+ &((struct ares_addrttl *) ares_ttl_data)[i]);
+ break;
+ case AF_INET6:
+ cret = resolv_copy_in6_addr(ret->addr_list, ret->addr_list[i],
+ &((struct ares_addr6ttl *) ares_ttl_data)[i]);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unknown address family %d\n", family);
+ goto fail;
+ }
+
+ if (cret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Could not copy address\n");
+ goto fail;
+ }
+ }
+ ret->addr_list[num_ares_ttl_data] = NULL;
+ }
+
+ ret->family = family;
+ return ret;
+
+fail:
+ talloc_free(ret);
+ return NULL;
+}
+
+/* =================== Resolve host name in files =========================*/
+struct gethostbyname_files_state {
+ struct resolv_ctx *resolv_ctx;
+
+ /* Part of the query. */
+ const char *name;
+ int family;
+
+ /* query result */
+ struct resolv_hostent *rhostent;
+
+ /* returned by ares. */
+ int status;
+};
+
+/* Fake up an async interface even though files would
+ * always be blocking */
+static struct tevent_req *
+resolv_gethostbyname_files_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *ctx,
+ const char *name,
+ int family)
+{
+ struct tevent_req *req;
+ struct gethostbyname_files_state *state;
+ struct hostent *hostent = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct gethostbyname_files_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->resolv_ctx = ctx;
+ state->name = name;
+ state->rhostent = NULL;
+ state->family = family;
+
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Trying to resolve %s record of '%s' in files\n",
+ state->family == AF_INET ? "A" : "AAAA", state->name);
+
+ state->status = ares_gethostbyname_file(state->resolv_ctx->channel,
+ state->name, state->family,
+ &hostent);
+
+ if (state->status == ARES_SUCCESS) {
+ state->rhostent = resolv_copy_hostent(state, hostent);
+ if (state->rhostent == NULL) {
+ tevent_req_error(req, ENOMEM);
+ goto done;
+ }
+ } else if (state->status == ARES_ENOTFOUND ||
+ state->status == ARES_ENODATA) {
+ /* Just say we didn't find anything and let the caller decide
+ * about retrying */
+ tevent_req_error(req, ENOENT);
+ goto done;
+ } else {
+ tevent_req_error(req, return_code(state->status));
+ goto done;
+ }
+
+ tevent_req_done(req);
+done:
+ if (hostent) ares_free_hostent(hostent);
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static errno_t
+resolv_gethostbyname_files_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ int *status, struct resolv_hostent **rhostent)
+{
+ struct gethostbyname_files_state *state = tevent_req_data(req,
+ struct gethostbyname_files_state);
+
+ /* Fill in even in case of error as status contains the
+ * c-ares return code */
+ if (status) {
+ *status = state->status;
+ }
+ if (rhostent) {
+ *rhostent = talloc_steal(mem_ctx, state->rhostent);
+ }
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/* ==================== Resolve host name in DNS =========================*/
+struct gethostbyname_dns_state {
+ struct resolv_ctx *resolv_ctx;
+ struct tevent_context *ev;
+
+ /* Part of the query. */
+ const char *name;
+ int family;
+
+ /* query result */
+ struct resolv_hostent *rhostent;
+
+ /* These are returned by ares. */
+ int status;
+ int timeouts;
+ int retrying;
+};
+
+static void
+resolv_gethostbyname_dns_wakeup(struct tevent_req *subreq);
+static void
+resolv_gethostbyname_dns_query(struct tevent_req *req,
+ struct gethostbyname_dns_state *state);
+static void
+resolv_gethostbyname_dns_query_done(void *arg, int status, int timeouts,
+ unsigned char *abuf, int alen);
+static int
+resolv_gethostbyname_dns_parse(struct gethostbyname_dns_state *state,
+ int status, unsigned char *abuf, int alen);
+
+static struct tevent_req *
+resolv_gethostbyname_dns_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *ctx, const char *name,
+ int family)
+{
+ struct tevent_req *req, *subreq;
+ struct gethostbyname_dns_state *state;
+ struct timeval tv = { 0, 0 };
+
+ if (ctx->channel == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid ares channel - this is likely a bug\n");
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct gethostbyname_dns_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->resolv_ctx = ctx;
+ state->ev = ev;
+ state->name = name;
+ state->rhostent = NULL;
+ state->status = 0;
+ state->timeouts = 0;
+ state->retrying = 0;
+ state->family = family;
+
+ /* We need to have a wrapper around ares async calls, because
+ * they can in some cases call it's callback immediately.
+ * This would not let our caller to set a callback for req. */
+ subreq = tevent_wakeup_send(req, ev, tv);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to add critical timer to run next operation!\n");
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, resolv_gethostbyname_dns_wakeup, req);
+
+ return req;
+}
+
+static void
+resolv_gethostbyname_dns_wakeup(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct gethostbyname_dns_state *state = tevent_req_data(req,
+ struct gethostbyname_dns_state);
+
+ if (!tevent_wakeup_recv(subreq)) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+ talloc_zfree(subreq);
+
+ if (state->resolv_ctx->channel == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid ares channel - this is likely a bug\n");
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ resolv_gethostbyname_dns_query(req, state);
+}
+
+static void
+resolv_gethostbyname_dns_query(struct tevent_req *req,
+ struct gethostbyname_dns_state *state)
+{
+ struct resolv_request *rreq;
+
+ DEBUG(SSSDBG_CONF_SETTINGS, "Trying to resolve %s record of '%s' in DNS\n",
+ state->family == AF_INET ? "A" : "AAAA", state->name);
+
+ rreq = schedule_timeout_watcher(state->ev, state->resolv_ctx, req);
+ if (!rreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ ares_search(state->resolv_ctx->channel,
+ state->name, ns_c_in,
+ (state->family == AF_INET) ? ns_t_a : ns_t_aaaa,
+ resolv_gethostbyname_dns_query_done, rreq);
+}
+
+static void
+resolv_gethostbyname_dns_query_done(void *arg, int status, int timeouts,
+ unsigned char *abuf, int alen)
+{
+ errno_t ret;
+ struct gethostbyname_dns_state *state;
+ struct resolv_request *rreq = talloc_get_type(arg, struct resolv_request);
+ struct tevent_req *req;
+
+
+ if (rreq->rwatch == NULL) {
+ /* The tevent request was cancelled while the ares call was still in
+ * progress so nobody cares about the result now. Quit. */
+ unschedule_timeout_watcher(rreq->ctx, rreq);
+ return;
+ }
+
+ req = rreq->rwatch->req;
+ unschedule_timeout_watcher(rreq->ctx, rreq);
+
+ state = tevent_req_data(req, struct gethostbyname_dns_state);
+
+ state->status = status;
+ state->timeouts = timeouts;
+
+ /* If resolv.conf changed during processing of a request we might
+ * destroy the old channel before the request has a chance to finish.
+ * We must resend the request in this case */
+ if (state->retrying == 0 && status == ARES_EDESTRUCTION
+ && state->resolv_ctx->channel != NULL) {
+ state->retrying = 1;
+ resolv_gethostbyname_dns_query(req, state);
+ return;
+ }
+
+ if (status == ARES_ENOTFOUND || status == ARES_ENODATA) {
+ /* Just say we didn't find anything and let the caller decide
+ * about retrying */
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ if (status != ARES_SUCCESS) {
+ /* Any other error indicates a server error,
+ * so don't bother trying again
+ */
+ tevent_req_error(req, return_code(status));
+ return;
+ }
+
+ ret = resolv_gethostbyname_dns_parse(state, status, abuf, alen);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int
+resolv_gethostbyname_dns_parse(struct gethostbyname_dns_state *state,
+ int status, unsigned char *abuf, int alen)
+{
+ struct hostent *hostent = NULL;
+ int naddrttls;
+ errno_t ret;
+ void *addr = NULL;
+
+ naddrttls = DNS_HEADER_ANCOUNT(abuf);
+
+ switch (state->family) {
+ case AF_INET:
+ DEBUG(SSSDBG_TRACE_LIBS, "Parsing an A reply\n");
+
+ addr = talloc_array(state, struct ares_addrttl, naddrttls);
+ if (!addr) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ status = ares_parse_a_reply(abuf, alen, &hostent,
+ (struct ares_addrttl *) addr,
+ &naddrttls);
+ break;
+ case AF_INET6:
+ DEBUG(SSSDBG_TRACE_LIBS, "Parsing an AAAA reply\n");
+
+ addr = talloc_array(state, struct ares_addr6ttl, naddrttls);
+ if (!addr) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ status = ares_parse_aaaa_reply(abuf, alen, &hostent,
+ (struct ares_addr6ttl *) addr,
+ &naddrttls);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unknown family %d\n", state->family);
+ ret = EAFNOSUPPORT;
+ goto fail;
+ }
+
+ if ((hostent != NULL) && (status == ARES_SUCCESS)) {
+ state->rhostent = resolv_copy_hostent_ares(state, hostent,
+ state->family,
+ addr, naddrttls);
+ ares_free_hostent(hostent);
+ if (state->rhostent == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* The address list is NULL. This is probably a bug in
+ * c-ares, but we need to handle it gracefully.
+ */
+ if (state->rhostent->addr_list == NULL) {
+ talloc_zfree(state->rhostent);
+ return ENOENT;
+ }
+ } else if (status != ARES_SUCCESS) {
+ DEBUG(SSSDBG_OP_FAILURE, "Failed to parse reply: %d\n", status);
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE, "NULL parse result!\n");
+ }
+
+ talloc_free(addr);
+ return return_code(status);
+
+fail:
+ talloc_free(addr);
+ return ret;
+}
+
+static int
+resolv_gethostbyname_dns_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ int *status, int *timeouts,
+ struct resolv_hostent **rhostent)
+{
+ struct gethostbyname_dns_state *state = tevent_req_data(req,
+ struct gethostbyname_dns_state);
+
+ /* Fill in even in case of error as status contains the
+ * c-ares return code */
+ if (status) {
+ *status = state->status;
+ }
+ if (timeouts) {
+ *timeouts = state->timeouts;
+ }
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (rhostent) {
+ *rhostent = talloc_steal(mem_ctx, state->rhostent);
+ }
+
+ return EOK;
+}
+
+/*******************************************************************
+ * Get host by name. *
+ *******************************************************************/
+
+struct gethostbyname_state {
+ struct resolv_ctx *resolv_ctx;
+ struct tevent_context *ev;
+
+ /* Part of the query. */
+ const char *name;
+ int family;
+
+ /* In which order to use IPv4, or v6 */
+ enum restrict_family family_order;
+
+ /* Known hosts databases and index to the current one */
+ enum host_database *db;
+ int dbi;
+
+ /* These are returned by ares. The hostent struct will be freed
+ * when the user callback returns. */
+ struct resolv_hostent *rhostent;
+ int status;
+ int timeouts;
+ int retrying;
+};
+
+static errno_t
+resolv_gethostbyname_unix(TALLOC_CTX *mem_ctx, const char *path,
+ struct resolv_hostent **_rhostent);
+static errno_t
+resolv_gethostbyname_address(TALLOC_CTX *mem_ctx, const char *address,
+ struct resolv_hostent **_rhostent);
+static inline int
+resolv_gethostbyname_family_init(enum restrict_family family_order);
+static errno_t
+resolv_gethostbyname_step(struct tevent_req *req);
+
+struct tevent_req *
+resolv_gethostbyname_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *ctx, const char *name,
+ enum restrict_family family_order,
+ enum host_database *db)
+{
+ struct tevent_req *req;
+ struct gethostbyname_state *state;
+ errno_t ret;
+
+ if (ctx->channel == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid ares channel - this is likely a bug\n");
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct gethostbyname_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->resolv_ctx = ctx;
+ state->ev = ev;
+ state->name = talloc_strdup(state, name);
+ if (state->name == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup() failed\n");
+ goto fail;
+ }
+
+ state->rhostent = NULL;
+ state->status = 0;
+ state->timeouts = 0;
+ state->retrying = 0;
+ state->family_order = family_order;
+ state->family = resolv_gethostbyname_family_init(state->family_order);
+ state->db = db;
+ state->dbi = 0;
+
+ /* Do not attempt to resolve unix domain sockets */
+ if (resolv_is_unix(state->name)) {
+ ret = resolv_gethostbyname_unix(state, state->name,
+ &state->rhostent);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Cannot create a fake hostent structure\n");
+ goto fail;
+ }
+
+ tevent_req_done(req);
+ tevent_req_post(req, ev);
+ return req;
+ }
+
+ /* Do not attempt to resolve IP addresses */
+ if (resolv_is_address(state->name)) {
+ ret = resolv_gethostbyname_address(state, state->name,
+ &state->rhostent);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Cannot create a fake hostent structure\n");
+ goto fail;
+ }
+
+ tevent_req_done(req);
+ tevent_req_post(req, ev);
+ return req;
+ }
+
+ ret = resolv_gethostbyname_step(req);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Cannot start the resolving\n");
+ goto fail;
+ }
+
+ return req;
+
+fail:
+ talloc_zfree(req);
+ return NULL;
+}
+
+bool
+resolv_is_unix(const char *name)
+{
+ if (name && name[0] == '/') {
+ return 1;
+ }
+ DEBUG(SSSDBG_TRACE_ALL,
+ "[%s] does not look like a unix domain socket\n", name);
+
+ return 0;
+}
+
+bool
+resolv_is_address(const char *name)
+{
+ struct addrinfo hints;
+ struct addrinfo *res = NULL;
+ int ret;
+
+ memset((void *) &hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_NUMERICHOST; /* No network lookups */
+
+ ret = getaddrinfo(name, NULL, &hints, &res);
+ if (ret != 0) {
+ if (ret == -2) {
+ DEBUG(SSSDBG_TRACE_ALL,
+ "[%s] does not look like an IP address\n", name);
+ } else {
+ DEBUG(SSSDBG_OP_FAILURE, "getaddrinfo failed [%d]: %s\n",
+ ret, gai_strerror(ret));
+ }
+ } else { /* ret == 0 */
+ freeaddrinfo(res);
+ }
+
+ return ret == 0;
+}
+
+static errno_t
+resolv_gethostbyname_unix(TALLOC_CTX *mem_ctx, const char *path,
+ struct resolv_hostent **_rhostent)
+{
+ struct resolv_hostent *rhostent;
+ TALLOC_CTX *tmp_ctx;
+ errno_t ret;
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) return ENOMEM;
+
+ rhostent = talloc_zero(tmp_ctx, struct resolv_hostent);
+ if (!rhostent) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ rhostent->name = talloc_strdup(rhostent, path);
+ rhostent->addr_list = talloc_array(rhostent, struct resolv_addr *, 1);
+
+ if (!rhostent->name ||
+ !rhostent->addr_list) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ rhostent->addr_list[0] = NULL;
+ rhostent->family = AF_UNIX;
+ rhostent->aliases = NULL;
+
+ *_rhostent = talloc_move(mem_ctx, &rhostent);
+ ret = EOK;
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static errno_t
+resolv_gethostbyname_address(TALLOC_CTX *mem_ctx, const char *address,
+ struct resolv_hostent **_rhostent)
+{
+ struct resolv_hostent *rhostent;
+ TALLOC_CTX *tmp_ctx;
+ errno_t ret;
+ int family;
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) return ENOMEM;
+
+ rhostent = talloc_zero(tmp_ctx, struct resolv_hostent);
+ if (!rhostent) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ rhostent->name = talloc_strdup(rhostent, address);
+ rhostent->addr_list = talloc_array(rhostent, struct resolv_addr *, 2);
+
+ if (!rhostent->name ||
+ !rhostent->addr_list) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ rhostent->addr_list[0] = talloc_zero(rhostent->addr_list,
+ struct resolv_addr);
+ if (!rhostent->addr_list[0]) {
+ ret = ENOMEM;
+ goto done;
+ }
+ rhostent->addr_list[0]->ipaddr = talloc_array(rhostent->addr_list[0],
+ uint8_t,
+ sizeof(struct in6_addr));
+ if (!rhostent->addr_list[0]->ipaddr) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ family = AF_INET;
+ ret = inet_pton(family, address,
+ rhostent->addr_list[0]->ipaddr);
+ if (ret != 1) {
+ family = AF_INET6;
+ ret = inet_pton(family, address,
+ rhostent->addr_list[0]->ipaddr);
+ if (ret != 1) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Could not parse address as neither v4 nor v6\n");
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ rhostent->addr_list[0]->ttl = RESOLV_DEFAULT_TTL;
+ rhostent->addr_list[1] = NULL;
+ rhostent->family = family;
+ rhostent->aliases = NULL;
+
+ *_rhostent = talloc_move(mem_ctx, &rhostent);
+ ret = EOK;
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static inline int
+resolv_gethostbyname_family_init(enum restrict_family family_order)
+{
+ switch(family_order) {
+ case IPV4_ONLY:
+ case IPV4_FIRST:
+ return AF_INET;
+ case IPV6_ONLY:
+ case IPV6_FIRST:
+ return AF_INET6;
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unknown address family order %d\n", family_order);
+ return -1;
+}
+
+static int
+resolv_gethostbyname_next(struct gethostbyname_state *state)
+{
+ if (state->family_order == IPV4_FIRST &&
+ state->family == AF_INET) {
+ state->family = AF_INET6;
+ return EOK;
+ } else if (state->family_order == IPV6_FIRST &&
+ state->family == AF_INET6) {
+ state->family = AF_INET;
+ return EOK;
+ } else {
+ /* No more address families for this DB, check if
+ * there is another DB to try */
+ DEBUG(SSSDBG_FUNC_DATA, "No more address families to retry\n");
+ state->dbi++;
+ if (state->db[state->dbi] != DB_SENTINEL) {
+ state->family = resolv_gethostbyname_family_init(
+ state->family_order);
+ return EOK;
+ }
+ }
+
+ DEBUG(SSSDBG_CONF_SETTINGS, "No more hosts databases to retry\n");
+ return ENOENT;
+}
+
+static void
+resolv_gethostbyname_done(struct tevent_req *subreq);
+
+static errno_t
+resolv_gethostbyname_step(struct tevent_req *req)
+{
+ struct gethostbyname_state *state = tevent_req_data(req,
+ struct gethostbyname_state);
+ struct tevent_req *subreq;
+
+ switch(state->db[state->dbi]) {
+ case DB_FILES:
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Querying files\n");
+ subreq = resolv_gethostbyname_files_send(state, state->ev,
+ state->resolv_ctx,
+ state->name,
+ state->family);
+ break;
+ case DB_DNS:
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Querying DNS\n");
+ subreq = resolv_gethostbyname_dns_send(state, state->ev,
+ state->resolv_ctx,
+ state->name,
+ state->family);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid hosts database\n");
+ return EINVAL;
+ }
+
+ if (subreq == NULL) {
+ return ENOMEM;
+ }
+
+ tevent_req_set_callback(subreq, resolv_gethostbyname_done, req);
+ return EOK;
+}
+
+static void
+resolv_gethostbyname_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct gethostbyname_state *state = tevent_req_data(req,
+ struct gethostbyname_state);
+ errno_t ret;
+
+ switch(state->db[state->dbi]) {
+ case DB_FILES:
+ ret = resolv_gethostbyname_files_recv(subreq, state,
+ &state->status,
+ &state->rhostent);
+ /* files is synchronous, there can be no timeouts */
+ state->timeouts = 0;
+ break;
+ case DB_DNS:
+ ret = resolv_gethostbyname_dns_recv(subreq, state,
+ &state->status, &state->timeouts,
+ &state->rhostent);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid hosts database\n");
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ talloc_zfree(subreq);
+
+ if (ret == ENOENT) {
+ ret = resolv_gethostbyname_next(state);
+ if (ret == EOK) {
+ ret = resolv_gethostbyname_step(req);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ }
+ return;
+ }
+
+ /* No more databases and/or address families */
+ tevent_req_error(req, ENOENT);
+ return;
+ } else if (ret == ETIMEDOUT) {
+ /* In case we killed the request before c-ares answered */
+ state->status = ARES_ETIMEOUT;
+ }
+
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "querying hosts database failed [%d]: %s\n",
+ ret, strerror(ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+int
+resolv_gethostbyname_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ int *status, int *timeouts,
+ struct resolv_hostent **rhostent)
+{
+ struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state);
+
+ /* Fill in even in case of error as status contains the
+ * c-ares return code */
+ if (status) {
+ *status = state->status;
+ }
+ if (timeouts) {
+ *timeouts = state->timeouts;
+ }
+ if (rhostent) {
+ *rhostent = talloc_steal(mem_ctx, state->rhostent);
+ }
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+char *
+resolv_get_string_address_index(TALLOC_CTX *mem_ctx,
+ struct resolv_hostent *hostent,
+ unsigned int addrindex)
+{
+ char *address;
+
+ if (!hostent) return NULL;
+
+ address = talloc_zero_size(mem_ctx, 128);
+ if (address == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ return NULL;
+ }
+
+ errno = 0;
+ if (inet_ntop(hostent->family, hostent->addr_list[addrindex]->ipaddr,
+ address, 128) == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "inet_ntop failed [%d][%s].\n", errno, strerror(errno));
+ talloc_free(address);
+ return NULL;
+ }
+
+ return address;
+}
+
+char *
+resolv_get_string_ptr_address(TALLOC_CTX *mem_ctx,
+ int family, uint8_t *address)
+{
+ char *straddr;
+
+ if (family == AF_INET6) {
+ int i;
+ char hexbyte[3];
+
+ straddr = talloc_strdup(mem_ctx, "\0");
+ if (!straddr) {
+ return NULL;
+ }
+
+ for (i = 15; i >= 0; i--) {
+ snprintf(hexbyte, 3, "%02x", address[i]);
+ straddr = talloc_asprintf_append(straddr, "%c.%c.",
+ hexbyte[1], hexbyte[0]);
+ }
+ straddr = talloc_asprintf_append(straddr, "ip6.arpa.");
+ } else if (family == AF_INET) {
+ straddr = talloc_asprintf(mem_ctx,
+ "%u.%u.%u.%u.in-addr.arpa.",
+ (address[3]),
+ (address[2]),
+ (address[1]),
+ (address[0]));
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unknown address family\n");
+ return NULL;
+ }
+
+ return straddr;
+}
+
+struct sockaddr *
+resolv_get_sockaddr_address_index(TALLOC_CTX *mem_ctx,
+ struct resolv_hostent *hostent,
+ int port, int addrindex,
+ socklen_t *sockaddr_len)
+{
+ struct sockaddr *sockaddr;
+ int len;
+
+ if (!hostent) return NULL;
+
+ switch(hostent->family) {
+ case AF_INET:
+ len = sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ len = sizeof(struct sockaddr_in6);
+ break;
+ case AF_UNIX:
+ len = sizeof(struct sockaddr_un);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unknown address family %d\n", hostent->family);
+ return NULL;
+ }
+
+ sockaddr = (struct sockaddr *)talloc_zero(mem_ctx, struct sockaddr_storage);
+ if (sockaddr == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+ return NULL;
+ }
+
+ switch(hostent->family) {
+ case AF_INET:
+ sockaddr->sa_family = AF_INET;
+ memcpy(&((struct sockaddr_in *) sockaddr)->sin_addr,
+ hostent->addr_list[addrindex]->ipaddr,
+ sizeof(struct in_addr));
+ ((struct sockaddr_in *) sockaddr)->sin_port = (in_port_t) htons(port);
+
+ break;
+ case AF_INET6:
+ sockaddr->sa_family = AF_INET6;
+ memcpy(&((struct sockaddr_in6 *) sockaddr)->sin6_addr,
+ hostent->addr_list[addrindex]->ipaddr,
+ sizeof(struct in6_addr));
+ ((struct sockaddr_in6 *) sockaddr)->sin6_port = (in_port_t) htons(port);
+ break;
+ case AF_UNIX:
+ sockaddr->sa_family = AF_UNIX;
+ strncpy(((struct sockaddr_un *) sockaddr)->sun_path, hostent->name,
+ sizeof(((struct sockaddr_un *) sockaddr)->sun_path) - 1);
+ if (strlen(hostent->name) >=
+ sizeof(((struct sockaddr_un *) sockaddr)->sun_path)) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Path '%s' too long\n", hostent->name);
+ return NULL;
+ }
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unknown address family %d\n", hostent->family);
+ return NULL;
+ }
+
+ if (sockaddr_len != NULL) {
+ *sockaddr_len = len;
+ }
+
+ return sockaddr;
+}
+
+/*
+ * A simple helper function that will take an array of struct ares_srv_reply that
+ * was allocated by malloc() in c-ares and copies it using talloc. The old one
+ * is freed and the talloc one is put into 'reply_list' instead.
+ */
+static int
+rewrite_talloc_srv_reply(TALLOC_CTX *mem_ctx, struct ares_srv_reply **reply_list)
+{
+ struct ares_srv_reply *ptr = NULL;
+ struct ares_srv_reply *new_list = NULL;
+ struct ares_srv_reply *old_list = *reply_list;
+
+ /* Nothing to do, but not an error */
+ if (!old_list) {
+ return EOK;
+ }
+
+ /* Copy the linked list */
+ while (old_list) {
+ /* Special case for the first node */
+ if (!new_list) {
+ new_list = talloc_zero(mem_ctx, struct ares_srv_reply);
+ if (new_list == NULL) {
+ ares_free_data(*reply_list);
+ return ENOMEM;
+ }
+ ptr = new_list;
+ } else {
+ ptr->next = talloc_zero(new_list, struct ares_srv_reply);
+ if (ptr->next == NULL) {
+ ares_free_data(*reply_list);
+ talloc_free(new_list);
+ return ENOMEM;
+ }
+ ptr = ptr->next;
+ }
+
+ ptr->weight = old_list->weight;
+ ptr->priority = old_list->priority;
+ ptr->port = old_list->port;
+ ptr->host = talloc_strdup(ptr, old_list->host);
+ if (ptr->host == NULL) {
+ ares_free_data(*reply_list);
+ talloc_free(new_list);
+ return ENOMEM;
+ }
+
+ old_list = old_list->next;
+ }
+
+ /* Free the old one (uses malloc). */
+ ares_free_data(*reply_list);
+
+ /* And now put our own new_list in place. */
+ *reply_list = new_list;
+
+ return EOK;
+}
+
+/*******************************************************************
+ * Get SRV record *
+ *******************************************************************/
+
+struct getsrv_state {
+ struct tevent_context *ev;
+ struct resolv_ctx *resolv_ctx;
+ /* the SRV query - for example _ldap._tcp.example.com */
+ const char *query;
+
+ /* parsed data returned by ares */
+ struct ares_srv_reply *reply_list;
+ uint32_t ttl;
+ int status;
+ int timeouts;
+ int retrying;
+};
+
+static void
+ares_getsrv_wakeup(struct tevent_req *subreq);
+static void
+resolv_getsrv_query(struct tevent_req *req,
+ struct getsrv_state *state);
+
+struct tevent_req *
+resolv_getsrv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *ctx, const char *query)
+{
+ struct tevent_req *req, *subreq;
+ struct getsrv_state *state;
+ struct timeval tv = { 0, 0 };
+
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Trying to resolve SRV record of '%s'\n", query);
+
+ if (ctx->channel == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid ares channel - this is likely a bug\n");
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct getsrv_state);
+ if (req == NULL)
+ return NULL;
+
+ state->resolv_ctx = ctx;
+ state->query = query;
+ state->reply_list = NULL;
+ state->ttl = 0;
+ state->status = 0;
+ state->timeouts = 0;
+ state->retrying = 0;
+ state->ev = ev;
+
+ subreq = tevent_wakeup_send(req, ev, tv);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to add critical timer to run next operation!\n");
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, ares_getsrv_wakeup, req);
+
+ return req;
+}
+
+/*
+ * Implemented based on http://tools.ietf.org/html/rfc2181#section-5
+ *
+ * Especially:
+ * 5.2. TTLs of RRs in an RRSet
+ * Consequently the use of differing TTLs in an RRSet is hereby
+ * deprecated, the TTLs of all RRs in an RRSet must be the same.
+ * ...
+ * Should an authoritative source send such a malformed RRSet, the
+ * client should treat the RRs for all purposes as if all TTLs in the
+ * RRSet had been set to the value of the lowest TTL in the RRSet.
+ *
+ * On success, returns true and sets the TTL in the _ttl parameter. On
+ * failure, returns false and _ttl is undefined.
+ */
+static bool
+resolv_get_ttl(const unsigned char *abuf, const int alen, uint32_t *_ttl)
+{
+ const unsigned char *aptr;
+ int ret;
+ char *name = NULL;
+ long len;
+ uint32_t ttl = 0;
+ uint32_t rr_ttl;
+ unsigned int rr_len;
+ unsigned int ancount;
+ unsigned int i;
+
+ /* Read the number of RRs and then skip past the header */
+ if (alen < NS_HFIXEDSZ) {
+ return false;
+ }
+
+ ancount = DNS_HEADER_ANCOUNT(abuf);
+ if (ancount == 0) {
+ return false;
+ }
+
+ aptr = abuf + NS_HFIXEDSZ;
+
+ /* We only care about len from the question data,
+ * so that we can move past hostname */
+ ret = ares_expand_name(aptr, abuf, alen, &name, &len);
+ ares_free_string(name);
+ if (ret != ARES_SUCCESS) {
+ return false;
+ }
+
+ /* Skip past the question */
+ aptr += len + NS_QFIXEDSZ;
+ if (aptr > abuf + alen) {
+ return false;
+ }
+
+ /* Examine each RR in turn and read the lowest TTL */
+ for (i = 0; i < ancount; i++) {
+ /* Decode the RR up to the data field. */
+ ret = ares_expand_name(aptr, abuf, alen, &name, &len);
+ ares_free_string(name);
+ if (ret != ARES_SUCCESS) {
+ return false;
+ }
+
+ aptr += len;
+ if (aptr + NS_RRFIXEDSZ > abuf + alen) {
+ return false;
+ }
+
+ rr_len = DNS_RR_LEN(aptr);
+ rr_ttl = DNS_RR_TTL(aptr);
+ if (aptr + rr_len > abuf + alen) {
+ return false;
+ }
+ aptr += NS_RRFIXEDSZ + rr_len;
+
+ if (ttl > 0) {
+ ttl = MIN(ttl, rr_ttl);
+ } else {
+ ttl = rr_ttl; /* special-case for first TTL */
+ }
+ }
+
+ *_ttl = ttl;
+ return true;
+}
+
+static void
+resolv_getsrv_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen)
+{
+ struct resolv_request *rreq = talloc_get_type(arg, struct resolv_request);
+ struct tevent_req *req;
+ struct getsrv_state *state;
+ int ret;
+ bool ok;
+ struct ares_srv_reply *reply_list;
+
+ if (rreq->rwatch == NULL) {
+ /* The tevent request was cancelled while the ares call was still in
+ * progress so nobody cares about the result now. Quit. */
+ unschedule_timeout_watcher(rreq->ctx, rreq);
+ return;
+ }
+
+ req = rreq->rwatch->req;
+ unschedule_timeout_watcher(rreq->ctx, rreq);
+ state = tevent_req_data(req, struct getsrv_state);
+
+ if (state->retrying == 0 && status == ARES_EDESTRUCTION
+ && state->resolv_ctx->channel != NULL) {
+ state->retrying = 1;
+ resolv_getsrv_query(req, state);
+ return;
+ }
+
+ state->status = status;
+ state->timeouts = timeouts;
+
+ if (status != ARES_SUCCESS) {
+ ret = return_code(status);
+ goto fail;
+ }
+
+ ret = ares_parse_srv_reply(abuf, alen, &reply_list);
+ if (ret != ARES_SUCCESS) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "SRV record parsing failed: %d: %s\n", ret, ares_strerror(ret));
+ ret = return_code(ret);
+ goto fail;
+ }
+ ret = rewrite_talloc_srv_reply(req, &reply_list);
+ if (ret != EOK) {
+ goto fail;
+ }
+ state->reply_list = reply_list;
+ ok = resolv_get_ttl(abuf, alen, &state->ttl);
+ if (ok == false) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "Could not read TTL, using the default..\n");
+ state->ttl = RESOLV_DEFAULT_SRV_TTL;
+ }
+ DEBUG(SSSDBG_TRACE_LIBS, "Using TTL [%"PRIu32"]\n", state->ttl);
+
+ tevent_req_done(req);
+ return;
+
+fail:
+ state->reply_list = NULL;
+ tevent_req_error(req, ret);
+}
+
+int
+resolv_getsrv_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, int *status,
+ int *timeouts, struct ares_srv_reply **reply_list,
+ uint32_t *ttl)
+{
+ struct getsrv_state *state = tevent_req_data(req, struct getsrv_state);
+
+ if (status)
+ *status = state->status;
+ if (timeouts)
+ *timeouts = state->timeouts;
+ if (reply_list)
+ *reply_list = talloc_steal(mem_ctx, state->reply_list);
+ if (ttl) {
+ *ttl = state->ttl;
+ }
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+static void
+ares_getsrv_wakeup(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct getsrv_state *state = tevent_req_data(req,
+ struct getsrv_state);
+
+ if (!tevent_wakeup_recv(subreq)) {
+ return;
+ }
+ talloc_zfree(subreq);
+
+ if (state->resolv_ctx->channel == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid ares channel - this is likely a bug\n");
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ return resolv_getsrv_query(req, state);
+}
+
+static void
+resolv_getsrv_query(struct tevent_req *req,
+ struct getsrv_state *state)
+{
+ struct resolv_request *rreq;
+
+ rreq = schedule_timeout_watcher(state->ev, state->resolv_ctx, req);
+ if (!rreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ ares_query(state->resolv_ctx->channel, state->query,
+ ns_c_in, ns_t_srv, resolv_getsrv_done, rreq);
+}
+
+/* TXT parsing is not used anywhere in the code yet, so we disable it
+ * for now
+ */
+#ifdef BUILD_TXT
+
+/*
+ * A simple helper function that will take an array of struct txt_reply that
+ * was allocated by malloc() in c-ares and copies it using talloc. The old one
+ * is freed and the talloc one is put into 'reply_list' instead.
+ */
+static int
+rewrite_talloc_txt_reply(TALLOC_CTX *mem_ctx, struct ares_txt_reply **reply_list)
+{
+ struct ares_txt_reply *ptr = NULL;
+ struct ares_txt_reply *new_list = NULL;
+ struct ares_txt_reply *old_list = *reply_list;
+
+ /* Nothing to do, but not an error */
+ if (!old_list) {
+ return EOK;
+ }
+
+ /* Copy the linked list */
+ while (old_list) {
+
+ /* Special case for the first node */
+ if (!new_list) {
+ new_list = talloc_zero(mem_ctx, struct ares_txt_reply);
+ if (new_list == NULL) {
+ ares_free_data(*reply_list);
+ talloc_free(new_list);
+ return ENOMEM;
+ }
+ ptr = new_list;
+ } else {
+ ptr->next = talloc_zero(new_list, struct ares_txt_reply);
+ if (ptr->next == NULL) {
+ ares_free_data(*reply_list);
+ talloc_free(new_list);
+ return ENOMEM;
+ }
+ ptr = ptr->next;
+ }
+
+ ptr->length = old_list->length;
+ ptr->txt = talloc_memdup(ptr, old_list->txt,
+ old_list->length);
+ if (ptr->txt == NULL) {
+ ares_free_data(*reply_list);
+ talloc_free(new_list);
+ return ENOMEM;
+ }
+
+ old_list = old_list->next;
+ }
+
+ ares_free_data(*reply_list);
+
+ /* And now put our own new_list in place. */
+ *reply_list = new_list;
+
+ return EOK;
+}
+
+/*******************************************************************
+ * Get TXT record *
+ *******************************************************************/
+
+struct gettxt_state {
+ struct tevent_context *ev;
+ struct resolv_ctx *resolv_ctx;
+ /* the TXT query */
+ const char *query;
+
+ /* parsed data returned by ares */
+ struct ares_txt_reply *reply_list;
+ int status;
+ int timeouts;
+ int retrying;
+};
+
+static void
+ares_gettxt_wakeup(struct tevent_req *subreq);
+static void
+resolv_gettxt_query(struct tevent_req *req,
+ struct gettxt_state *state);
+
+struct tevent_req *
+resolv_gettxt_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *ctx, const char *query)
+{
+ struct tevent_req *req, *subreq;
+ struct gettxt_state *state;
+ struct timeval tv = { 0, 0 };
+
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Trying to resolve TXT record of '%s'\n", query);
+
+ if (ctx->channel == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid ares channel - this is likely a bug\n");
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct gettxt_state);
+ if (req == NULL)
+ return NULL;
+
+ state->resolv_ctx = ctx;
+ state->query = query;
+ state->reply_list = NULL;
+ state->status = 0;
+ state->timeouts = 0;
+ state->retrying = 0;
+ state->ev = ev;
+
+ subreq = tevent_wakeup_send(req, ev, tv);
+ if (subreq == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to add critical timer to run next operation!\n");
+ talloc_zfree(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, ares_gettxt_wakeup, req);
+
+ return req;
+}
+
+static void
+resolv_gettxt_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen)
+{
+ struct resolv_request *rreq = talloc_get_type(arg, struct resolv_request);
+ struct tevent_req *req;
+ struct gettxt_state *state;
+ int ret;
+ struct ares_txt_reply *reply_list;
+
+ if (rreq->rwatch == NULL) {
+ /* The tevent request was cancelled while the ares call was still in
+ * progress so nobody cares about the result now. Quit. */
+ unschedule_timeout_watcher(rreq->ctx, rreq);
+ return;
+ }
+
+ req = rreq->rwatch->req;
+ unschedule_timeout_watcher(rreq->ctx, rreq);
+ state = tevent_req_data(req, struct gettxt_state);
+
+ if (state->retrying == 0 && status == ARES_EDESTRUCTION
+ && state->resolv_ctx->channel != NULL) {
+ state->retrying = 1;
+ ares_query(state->resolv_ctx->channel, state->query,
+ ns_c_in, ns_t_txt, resolv_gettxt_done, req);
+ return;
+ }
+
+ state->status = status;
+ state->timeouts = timeouts;
+
+ if (status != ARES_SUCCESS) {
+ ret = return_code(status);
+ goto fail;
+ }
+
+ ret = ares_parse_txt_reply(abuf, alen, &reply_list);
+ if (status != ARES_SUCCESS) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "TXT record parsing failed: %d: %s\n", ret, ares_strerror(ret));
+ ret = return_code(ret);
+ goto fail;
+ }
+ ret = rewrite_talloc_txt_reply(req, &reply_list);
+ if (ret != EOK) {
+ goto fail;
+ }
+ state->reply_list = reply_list;
+
+ tevent_req_done(req);
+ return;
+
+fail:
+ state->reply_list = NULL;
+ tevent_req_error(req, ret);
+}
+
+int
+resolv_gettxt_recv(TALLOC_CTX *mem_ctx, struct tevent_req *req, int *status,
+ int *timeouts, struct ares_txt_reply **reply_list)
+{
+ struct gettxt_state *state = tevent_req_data(req, struct gettxt_state);
+
+ if (status)
+ *status = state->status;
+ if (timeouts)
+ *timeouts = state->timeouts;
+ if (reply_list)
+ *reply_list = talloc_steal(mem_ctx, state->reply_list);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+static void
+ares_gettxt_wakeup(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct gettxt_state *state = tevent_req_data(req,
+ struct gettxt_state);
+
+ if (!tevent_wakeup_recv(subreq)) {
+ return;
+ }
+ talloc_zfree(subreq);
+
+ if (state->resolv_ctx->channel == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Invalid ares channel - this is likely a bug\n");
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ return resolv_gettxt_query(req, state);
+}
+
+static void
+resolv_gettxt_query(struct tevent_req *req,
+ struct gettxt_state *state)
+{
+ struct resolv_request *rreq;
+
+ rreq = schedule_timeout_watcher(state->ev, state->resolv_ctx, req);
+ if (!rreq) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ ares_query(state->resolv_ctx->channel, state->query,
+ ns_c_in, ns_t_txt, resolv_gettxt_done, rreq);
+}
+
+#endif
+
+static struct ares_srv_reply *split_reply_list(struct ares_srv_reply *list)
+{
+ struct ares_srv_reply *single_step, *double_step, *prev;
+
+ if (!list) {
+ return NULL;
+ }
+
+ prev = list;
+ single_step = list->next;
+ double_step = single_step->next;
+
+ while (double_step && double_step->next) {
+ prev = single_step;
+ single_step = single_step->next;
+ double_step = double_step->next->next;
+ }
+
+ prev->next = NULL;
+ return single_step;
+}
+
+static struct ares_srv_reply *merge_reply_list(struct ares_srv_reply *left,
+ struct ares_srv_reply *right)
+{
+ struct ares_srv_reply *l, *r;
+ struct ares_srv_reply *res, *res_start;
+
+ if (!left)
+ return right;
+ if (!right)
+ return left;
+
+ if (left->priority < right->priority) {
+ res_start = left;
+ l = left->next;
+ r = right;
+ } else {
+ res_start = right;
+ l = left;
+ r = right->next;
+ }
+
+ res = res_start;
+
+ while(l && r) {
+ if (l->priority < r->priority) {
+ res->next = l;
+ res = l;
+ l = l->next;
+ } else {
+ res->next = r;
+ res = r;
+ r = r->next;
+ }
+ }
+
+ res->next = l ? l : r;
+
+ return res_start;
+}
+
+/**
+ * sort linked list of struct ares_srv_reply by priority using merge sort.
+ *
+ * Merge sort is ideal for sorting linked lists as there is no problem
+ * with absence of random access into the list. The complexity is O(n log n)
+ *
+ * For reference, see Robert Sedgewick's "Algorithms in C", Addison-Wesley,
+ * ISBN 0-201-51425
+ */
+static struct ares_srv_reply *reply_priority_sort(struct ares_srv_reply *list)
+{
+ struct ares_srv_reply *half;
+
+ if (!list || !list->next)
+ return list;
+
+ half = split_reply_list(list);
+ list = merge_reply_list(reply_priority_sort(list),
+ reply_priority_sort(half));
+
+ return list;
+}
+
+static int reply_weight_rearrange(int len,
+ struct ares_srv_reply **start,
+ struct ares_srv_reply **end)
+{
+ int i;
+ int total, selected;
+ int *totals;
+ struct ares_srv_reply *r, *prev, *tmp;
+ struct ares_srv_reply *new_start = NULL;
+ struct ares_srv_reply *new_end = NULL;
+ int ret;
+
+ if (len <= 1) {
+ return EOK;
+ }
+
+ totals = talloc_array(NULL, int, len);
+ if (!totals) {
+ return ENOMEM;
+ }
+
+ /* promote all servers with weight==0 to the top */
+ r = *(start);
+ prev = NULL;
+ while (r != NULL) {
+ if (r->weight == 0 && r != *start) {
+ /* remove from the old list */
+ prev->next = r->next;
+
+ /* add to the head of the new list */
+ tmp = r;
+ r = r->next;
+
+ tmp->next = *start;
+ *start = tmp;
+ } else {
+ prev = r;
+ r = r->next;
+ }
+ }
+ *end = prev ? prev : *start;
+
+ while (*start != NULL) {
+ /* Compute the sum of the weights of those RRs, and with each RR
+ * associate the running sum in the selected order.
+ */
+ total = 0;
+ memset(totals, -1, sizeof(int) * len);
+ for (i = 0, r = *start; r != NULL; r=r->next, ++i) {
+ totals[i] = r->weight + total;
+ total = totals[i];
+ }
+
+ /* choose a uniform random number between 0 and the sum computed
+ * (inclusive), and select the RR whose running sum value is the
+ * first in the selected order which is greater than or equal to
+ * the random number selected.
+ */
+ selected = (int)((total + 1) * (sss_rand()/(RAND_MAX + 1.0)));
+ for (i = 0, r = *start, prev = NULL; r != NULL; r=r->next, ++i) {
+ if (totals[i] >= selected)
+ break;
+
+ prev = r;
+ }
+
+ if (r == NULL || totals[i] == -1) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Bug: did not select any server!\n");
+ ret = EIO;
+ goto done;
+ }
+
+ /* remove r from the old list */
+ if (prev) {
+ prev->next = r->next;
+ } else {
+ *start = r->next;
+ }
+
+ /* add r to the end of the new list */
+ if (!new_start) {
+ new_start = r;
+ new_end = r;
+ } else {
+ new_end->next = r;
+ new_end = r;
+ }
+ }
+
+ if (new_end == NULL) {
+ ret = EINVAL;
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Bug: no new server has been selected!\n");
+ goto done;
+ }
+ new_end->next = NULL;
+
+ /* return the rearranged list */
+ *start = new_start;
+ *end = new_end;
+ ret = EOK;
+
+done:
+ talloc_free(totals);
+ return ret;
+}
+
+int
+resolv_sort_srv_reply(struct ares_srv_reply **reply)
+{
+ int ret;
+ struct ares_srv_reply *pri_start, *pri_end, *next, *prev_end;
+ int len;
+
+ /* RFC 2782 says: If there is precisely one SRV RR, and its Target is "."
+ * (the root domain), abort.
+ */
+ if (*reply && !(*reply)->next && strcmp((*reply)->host, ".") == 0) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "DNS returned only the root domain, aborting\n");
+ return EIO;
+ }
+
+ /* sort the list by priority */
+ *reply = reply_priority_sort(*reply);
+
+ pri_start = *reply;
+ prev_end = NULL;
+
+ while (pri_start) {
+ pri_end = pri_start;
+
+ /* Find nodes with the same priority */
+ len = 1;
+ while (pri_end->next && pri_end->priority == pri_end->next->priority) {
+ pri_end = pri_end->next;
+ len++;
+ }
+
+ /* rearrange each priority level according to the weight field */
+ next = pri_end->next;
+ pri_end->next = NULL;
+ ret = reply_weight_rearrange(len, &pri_start, &pri_end);
+ if (ret) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Error rearranging priority level [%d]: %s\n",
+ ret, strerror(ret));
+ return ret;
+ }
+
+ /* Hook the level back into the list */
+ if (prev_end) {
+ prev_end->next = pri_start;
+ } else {
+ *reply = pri_start;
+ }
+ pri_end->next = next;
+
+ /* Move on to the next level */
+ prev_end = pri_end;
+ pri_start = next;
+ }
+
+ return EOK;
+}
+
+struct resolv_hostport_list_state {
+ struct tevent_context *ev;
+ struct resolv_ctx *ctx;
+ struct resolv_hostport *hostport_list;
+ size_t list_size;
+ size_t limit;
+ enum restrict_family family_order;
+ enum host_database *db;
+
+ size_t hpindex;
+
+ struct resolv_hostport_addr **rhp_addrs;
+ size_t addrindex;
+};
+
+static errno_t resolv_hostport_list_step(struct tevent_req *req);
+static void resolv_hostport_list_resolv_hostname_done(struct tevent_req *subreq);
+
+struct tevent_req *resolv_hostport_list_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *ctx,
+ struct resolv_hostport *hostport_list,
+ size_t list_size,
+ size_t limit,
+ enum restrict_family family_order,
+ enum host_database *db)
+{
+ struct resolv_hostport_list_state *state;
+ struct tevent_req *req;
+ errno_t ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct resolv_hostport_list_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->ctx = ctx;
+ state->hostport_list = hostport_list;
+ state->family_order = family_order;
+ state->db = db;
+ state->list_size = list_size;
+ state->limit = limit;
+
+ state->rhp_addrs = talloc_array(state,
+ struct resolv_hostport_addr *,
+ state->list_size);
+ if (state->rhp_addrs == NULL) {
+ ret = ENOMEM;
+ goto immediately;
+ }
+
+ ret = resolv_hostport_list_step(req);
+ if (ret != EAGAIN) {
+ goto immediately;
+ }
+
+ return req;
+
+immediately:
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ } else {
+ tevent_req_done(req);
+ }
+ tevent_req_post(req, ev);
+ return req;
+}
+
+static errno_t resolv_hostport_list_step(struct tevent_req *req)
+{
+ struct tevent_req *subreq = NULL;
+ struct resolv_hostport_list_state *state = tevent_req_data(req,
+ struct resolv_hostport_list_state);
+
+ if (state->hpindex >= state->list_size) {
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Done\n");
+ return EOK;
+ }
+
+ subreq = resolv_gethostbyname_send(state,
+ state->ev,
+ state->ctx,
+ state->hostport_list[state->hpindex].host,
+ state->family_order,
+ state->db);
+ if (subreq == NULL) {
+ return ENOMEM;
+ }
+ tevent_req_set_callback(subreq,
+ resolv_hostport_list_resolv_hostname_done, req);
+ return EAGAIN;
+}
+
+static struct resolv_hostport_addr*
+resolv_hostport_addr_new(TALLOC_CTX *mem_ctx,
+ const char *host,
+ int port,
+ struct resolv_hostent *reply)
+{
+ struct resolv_hostport_addr *rhp_addr;
+
+ rhp_addr = talloc_zero(mem_ctx, struct resolv_hostport_addr);
+ if (rhp_addr == NULL) {
+ return NULL;
+ }
+
+ rhp_addr->origin.host = talloc_strdup(rhp_addr, host);
+ if (rhp_addr->origin.host == NULL) {
+ return NULL;
+ }
+
+ rhp_addr->origin.port = port;
+ rhp_addr->reply = talloc_steal(rhp_addr, reply);
+ return rhp_addr;
+}
+
+static void resolv_hostport_list_resolv_hostname_done(struct tevent_req *subreq)
+{
+ errno_t ret;
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct resolv_hostport_list_state *state = tevent_req_data(req,
+ struct resolv_hostport_list_state);
+ struct resolv_hostent *rhostent;
+ int resolv_status;
+
+ ret = resolv_gethostbyname_recv(subreq, state, &resolv_status, NULL,
+ &rhostent);
+ talloc_zfree(subreq);
+
+ if (ret != EOK) {
+ /* Don't abort the request, just go to the next one */
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not resolve address for this machine, error [%d]: %s, "
+ "resolver returned: [%d]: %s\n", ret, sss_strerror(ret),
+ resolv_status, resolv_strerror(resolv_status));
+ } else {
+ state->rhp_addrs[state->addrindex] = \
+ resolv_hostport_addr_new(state->rhp_addrs,
+ state->hostport_list[state->hpindex].host,
+ state->hostport_list[state->hpindex].port,
+ rhostent);
+ state->addrindex++;
+
+ if (state->limit > 0 && state->addrindex >= state->limit) {
+ DEBUG(SSSDBG_TRACE_INTERNAL,
+ "Reached the limit or addresses to resolve\n");
+ tevent_req_done(req);
+ return;
+ }
+ }
+
+ state->hpindex++;
+
+ ret = resolv_hostport_list_step(req);
+ if (ret == EOK) {
+ tevent_req_done(req);
+ return;
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ /* Next iteration .. */
+}
+
+int resolv_hostport_list_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *_rhp_len,
+ struct resolv_hostport_addr ***_rhp_addrs)
+{
+ struct resolv_hostport_list_state *state = tevent_req_data(req,
+ struct resolv_hostport_list_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (_rhp_len) {
+ *_rhp_len = state->addrindex;
+ }
+
+ if (_rhp_addrs) {
+ *_rhp_addrs = talloc_steal(mem_ctx, state->rhp_addrs);
+ }
+
+ return EOK;
+}
diff --git a/src/resolv/async_resolv.h b/src/resolv/async_resolv.h
new file mode 100644
index 0000000..a1f9285
--- /dev/null
+++ b/src/resolv/async_resolv.h
@@ -0,0 +1,230 @@
+/*
+ SSSD
+
+ Async resolver header
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2009
+
+ 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/>.
+*/
+
+#ifndef __ASYNC_RESOLV_H__
+#define __ASYNC_RESOLV_H__
+
+#include <netdb.h>
+#include <ares.h>
+
+#include "config.h"
+#include "confdb/confdb.h"
+
+#ifndef RESOLV_DEFAULT_TTL
+#define RESOLV_DEFAULT_TTL 7200
+#endif /* RESOLV_DEFAULT_TTL */
+
+#ifndef RESOLV_DEFAULT_SRV_TTL
+#define RESOLV_DEFAULT_SRV_TTL 14400
+#endif /* RESOLV_DEFAULT_SRV_TTL */
+
+#include "util/util.h"
+
+/*
+ * An opaque structure which holds context for a module using the async
+ * resolver. Is should be used as a "local-global" variable - in sssd,
+ * every backend should have its own.
+
+ * Do NOT free the context until there are any pending resolv_ calls
+ */
+struct resolv_ctx;
+
+int resolv_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev_ctx,
+ int timeout, int ares_timeout, bool use_search_list,
+ struct resolv_ctx **ctxp);
+
+void resolv_reread_configuration(struct resolv_ctx *ctx);
+
+const char *resolv_strerror(int ares_code);
+
+struct resolv_hostent *
+resolv_copy_hostent(TALLOC_CTX *mem_ctx, struct hostent *src);
+
+struct resolv_hostent *
+resolv_copy_hostent_ares(TALLOC_CTX *mem_ctx, struct hostent *src,
+ int family, void *ares_ttl_data,
+ int num_ares_ttl_data);
+
+/** Get host by name **/
+enum host_database {
+ DB_FILES,
+ DB_DNS,
+
+ DB_SENTINEL
+};
+
+enum restrict_family {
+ IPV4_ONLY,
+ IPV4_FIRST,
+ IPV6_ONLY,
+ IPV6_FIRST
+};
+
+/* If resolv_hostent->family is AF_INET, then ipaddr points to
+ * struct in_addr, else if family is AF_INET6, ipaddr points to
+ * struct in6_addr
+ */
+struct resolv_addr {
+ uint8_t *ipaddr;
+ int ttl;
+};
+
+struct resolv_hostent {
+ char *name; /* official name of host */
+ char **aliases; /* alias list */
+ int family; /* host address type */
+
+ struct resolv_addr **addr_list; /* list of addresses */
+};
+
+/* The default database order */
+extern enum host_database default_host_dbs[];
+
+struct tevent_req *resolv_gethostbyname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *ctx,
+ const char *name,
+ enum restrict_family family_order,
+ enum host_database *db);
+
+int resolv_gethostbyname_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ int *status, int *timeouts,
+ struct resolv_hostent **rhostent);
+
+struct resolv_hostport {
+ const char *host;
+ int port;
+};
+
+struct resolv_hostport_addr {
+ struct resolv_hostport origin;
+ struct resolv_hostent *reply;
+};
+
+/* Resolves a list of resolv_hostport tuples into a list of
+ * resolv_hostport_addr. Any unresolvable addresses are skipped.
+ *
+ * Optionally takes a limit argument and stops after the request
+ * had resolved addresses up to the limit.
+ */
+struct tevent_req *resolv_hostport_list_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *ctx,
+ struct resolv_hostport *hostport_list,
+ size_t list_size,
+ size_t limit,
+ enum restrict_family family_order,
+ enum host_database *db);
+
+int resolv_hostport_list_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *_rhp_len,
+ struct resolv_hostport_addr ***_rhp_addrs);
+
+char *
+resolv_get_string_address_index(TALLOC_CTX *mem_ctx,
+ struct resolv_hostent *hostent,
+ unsigned int addrindex);
+
+char *
+resolv_get_string_ptr_address(TALLOC_CTX *mem_ctx,
+ int family, uint8_t *address);
+
+#define resolv_get_string_address(mem_ctx, hostent) \
+ resolv_get_string_address_index(mem_ctx, hostent, 0)
+
+struct sockaddr *
+resolv_get_sockaddr_address_index(TALLOC_CTX *mem_ctx,
+ struct resolv_hostent *hostent,
+ int port, int addrindex,
+ socklen_t *sockaddr_len);
+
+#define resolv_get_sockaddr_address(mem_ctx, rhostent, port, sockaddr_len) \
+ resolv_get_sockaddr_address_index(mem_ctx, rhostent, port, 0, sockaddr_len)
+
+/** Get SRV record **/
+struct tevent_req *resolv_getsrv_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *ctx,
+ const char *query);
+
+int resolv_getsrv_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ int *status,
+ int *timeouts,
+ struct ares_srv_reply **reply_list,
+ uint32_t *ttl);
+
+/* This is an implementation of section "Usage rules" of RFC 2782 */
+int
+resolv_sort_srv_reply(struct ares_srv_reply **reply);
+
+/** Get TXT record **/
+struct tevent_req *resolv_gettxt_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *ctx,
+ const char *query);
+
+int resolv_gettxt_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ int *status,
+ int *timeouts,
+ struct ares_txt_reply **reply_list);
+
+/** Utils **/
+
+struct tevent_req *
+resolv_get_domain_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *resolv_ctx,
+ const char *hostname,
+ enum host_database *host_dbs,
+ enum restrict_family family_order);
+
+errno_t resolv_get_domain_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ char **_dns_domain);
+
+struct tevent_req *
+resolv_discover_srv_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *resolv_ctx,
+ const char *service,
+ const char *protocol,
+ const char **discovery_domains);
+
+errno_t resolv_discover_srv_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ares_srv_reply **_reply_list,
+ uint32_t *_ttl,
+ char **_dns_domain);
+
+bool
+resolv_is_address(const char *name);
+
+bool
+resolv_is_unix(const char *name);
+
+#endif /* __ASYNC_RESOLV_H__ */
diff --git a/src/resolv/async_resolv_utils.c b/src/resolv/async_resolv_utils.c
new file mode 100644
index 0000000..f86181b
--- /dev/null
+++ b/src/resolv/async_resolv_utils.c
@@ -0,0 +1,343 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2013 Red Hat
+
+ 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 <string.h>
+#include <talloc.h>
+#include <tevent.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "util/util.h"
+#include "resolv/async_resolv.h"
+
+struct resolv_get_domain_state {
+ char *fqdn;
+ char *hostname;
+};
+
+static void resolv_get_domain_done(struct tevent_req *subreq);
+
+struct tevent_req *
+resolv_get_domain_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *resolv_ctx,
+ const char *hostname,
+ enum host_database *host_dbs,
+ enum restrict_family family_order)
+{
+ struct resolv_get_domain_state *state = NULL;
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ char system_hostname[HOST_NAME_MAX + 1];
+ errno_t ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct resolv_get_domain_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+ return NULL;
+ }
+
+ if (hostname == NULL) {
+ /* use system hostname */
+ ret = gethostname(system_hostname, sizeof(system_hostname));
+ if (ret) {
+ ret = errno;
+ DEBUG(SSSDBG_CRIT_FAILURE, "gethostname() failed: [%d]: %s\n",
+ ret, strerror(ret));
+ goto immediately;
+ }
+ system_hostname[HOST_NAME_MAX] = '\0';
+ hostname = system_hostname;
+ }
+
+ state->fqdn = NULL;
+ state->hostname = talloc_strdup(state, hostname);
+ if (state->hostname == NULL) {
+ ret = ENOMEM;
+ goto immediately;
+ }
+
+ DEBUG(SSSDBG_TRACE_LIBS, "Host name is: %s\n", state->hostname);
+
+ subreq = resolv_gethostbyname_send(state, ev, resolv_ctx, state->hostname,
+ family_order, host_dbs);
+ if (subreq == NULL) {
+ talloc_zfree(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, resolv_get_domain_done, req);
+
+ return req;
+
+immediately:
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+
+ return req;
+}
+
+static void resolv_get_domain_done(struct tevent_req *subreq)
+{
+ struct resolv_get_domain_state *state = NULL;
+ struct tevent_req *req = NULL;
+ struct resolv_hostent *rhostent;
+ int resolv_status;
+ errno_t ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct resolv_get_domain_state);
+
+ ret = resolv_gethostbyname_recv(subreq, req, &resolv_status,
+ NULL, &rhostent);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not get fully qualified name for host name %s "
+ "error [%d]: %s, resolver returned: [%d]: %s\n",
+ state->hostname, ret, strerror(ret), resolv_status,
+ resolv_strerror(resolv_status));
+ state->fqdn = state->hostname;
+ } else {
+ DEBUG(SSSDBG_TRACE_LIBS, "The FQDN is: %s\n", rhostent->name);
+ state->fqdn = talloc_steal(state, rhostent->name);
+ talloc_zfree(rhostent);
+ }
+
+ tevent_req_done(req);
+}
+
+errno_t resolv_get_domain_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ char **_dns_domain)
+{
+ struct resolv_get_domain_state *state = NULL;
+ char *dns_domain = NULL;
+ char *domptr = NULL;
+
+ state = tevent_req_data(req, struct resolv_get_domain_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ domptr = strchr(state->fqdn, '.');
+ if (domptr == NULL || (*(domptr+1) == '\0')) {
+ /* If the FQDN did not contain a dot or the dot was the last character
+ * (broken DNS server perhaps) */
+ dns_domain = state->fqdn;
+ } else {
+ dns_domain = domptr + 1;
+ }
+
+ *_dns_domain = talloc_strdup(mem_ctx, dns_domain);
+ if (*_dns_domain == NULL) {
+ return ENOMEM;
+ }
+
+ return EOK;
+}
+
+struct resolv_discover_srv_state {
+ struct tevent_context *ev;
+ struct resolv_ctx *resolv_ctx;
+ const char *service;
+ const char *protocol;
+ const char **discovery_domains;
+ int domain_index;
+
+ struct ares_srv_reply *reply_list;
+ uint32_t ttl;
+};
+
+static errno_t resolv_discover_srv_next_domain(struct tevent_req *req);
+static void resolv_discover_srv_done(struct tevent_req *subreq);
+
+struct tevent_req *resolv_discover_srv_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *resolv_ctx,
+ const char *service,
+ const char *protocol,
+ const char **discovery_domains)
+{
+ struct resolv_discover_srv_state *state = NULL;
+ struct tevent_req *req = NULL;
+ errno_t ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct resolv_discover_srv_state);
+ if (req == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
+ return NULL;
+ }
+
+ if (resolv_ctx == NULL || service == NULL || protocol == NULL
+ || discovery_domains == NULL) {
+ ret = EINVAL;
+ goto immediately;
+ }
+
+ state->ev = ev;
+ state->resolv_ctx = resolv_ctx;
+ state->discovery_domains = discovery_domains;
+ state->service = service;
+ state->protocol = protocol;
+ state->domain_index = 0;
+
+ ret = resolv_discover_srv_next_domain(req);
+ if (ret != EAGAIN) {
+ goto immediately;
+ }
+
+ return req;
+
+immediately:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_error(req, ret);
+ }
+ tevent_req_post(req, ev);
+
+ return req;
+}
+
+static errno_t resolv_discover_srv_next_domain(struct tevent_req *req)
+{
+ struct resolv_discover_srv_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ const char *domain = NULL;
+ char *query = NULL;
+ errno_t ret;
+
+ state = tevent_req_data(req, struct resolv_discover_srv_state);
+
+ domain = state->discovery_domains[state->domain_index];
+ if (domain == NULL) {
+ ret = EOK;
+ goto done;
+ }
+
+ query = talloc_asprintf(state, "_%s._%s.%s", state->service,
+ state->protocol, domain);
+ if (query == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_TRACE_FUNC, "SRV resolution of service '%s'. Will use DNS "
+ "discovery domain '%s'\n", state->service, domain);
+
+ subreq = resolv_getsrv_send(state, state->ev,
+ state->resolv_ctx, query);
+ if (subreq == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, resolv_discover_srv_done, req);
+
+ state->domain_index++;
+ ret = EAGAIN;
+
+done:
+ if (ret != EAGAIN) {
+ talloc_free(query);
+ }
+
+ return ret;
+}
+
+static void resolv_discover_srv_done(struct tevent_req *subreq)
+{
+ struct resolv_discover_srv_state *state = NULL;
+ struct tevent_req *req = NULL;
+ int status;
+ errno_t ret;
+
+ req = tevent_req_callback_data(subreq, struct tevent_req);
+ state = tevent_req_data(req, struct resolv_discover_srv_state);
+
+ ret = resolv_getsrv_recv(state, subreq, &status, NULL,
+ &state->reply_list, &state->ttl);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "SRV query failed [%d]: %s\n",
+ status, resolv_strerror(status));
+
+ if (status == ARES_ENOTFOUND) {
+ /* continue with next discovery domain */
+ ret = resolv_discover_srv_next_domain(req);
+ if (ret == EOK) {
+ /* there are no more domains to try */
+ ret = ENOENT;
+ }
+
+ goto done;
+ }
+
+ /* critical error when fetching SRV record */
+ ret = EIO;
+ goto done;
+ }
+
+done:
+ if (ret == EOK) {
+ tevent_req_done(req);
+ } else if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ }
+
+ return;
+}
+
+errno_t resolv_discover_srv_recv(TALLOC_CTX *mem_ctx,
+ struct tevent_req *req,
+ struct ares_srv_reply **_reply_list,
+ uint32_t *_ttl,
+ char **_dns_domain)
+{
+ struct resolv_discover_srv_state *state = NULL;
+ char *domain = NULL;
+
+ state = tevent_req_data(req, struct resolv_discover_srv_state);
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ if (_dns_domain != NULL) {
+ /* domain_index now points to selected domain + 1 */
+ domain = talloc_strdup(mem_ctx,
+ state->discovery_domains[state->domain_index - 1]);
+ if (domain == NULL) {
+ return ENOMEM;
+ }
+
+ *_dns_domain = domain;
+ }
+
+ if (_reply_list != NULL) {
+ *_reply_list = talloc_steal(mem_ctx, state->reply_list);
+ }
+
+ if (_ttl != NULL) {
+ *_ttl = state->ttl;
+ }
+
+ return EOK;
+}