diff options
Diffstat (limited to 'src/resolv/async_resolv.c')
-rw-r--r-- | src/resolv/async_resolv.c | 1062 |
1 files changed, 1062 insertions, 0 deletions
diff --git a/src/resolv/async_resolv.c b/src/resolv/async_resolv.c new file mode 100644 index 000000000..70d8d11e4 --- /dev/null +++ b/src/resolv/async_resolv.c @@ -0,0 +1,1062 @@ +/* + 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 "config.h" +#include "resolv/async_resolv.h" +#include "util/dlinklist.h" +#include "util/util.h" + +#ifndef HAVE_ARES_DATA +#define ares_parse_srv_reply(abuf, alen, srv_out) \ + _ares_parse_srv_reply(abuf, alen, srv_out) +#define ares_parse_txt_reply(abuf, alen, txt_out) \ + _ares_parse_txt_reply(abuf, alen, txt_out) +#define ares_free_data(dataptr) \ + _ares_free_data(dataptr) +#define ares_malloc_data(data) \ + _ares_malloc_data(data) +#endif /* HAVE_ARES_DATA */ + +struct fd_watch { + struct fd_watch *prev; + struct fd_watch *next; + + int fd; + struct resolv_ctx *ctx; + struct tevent_fd *fde; +}; + +struct resolv_ctx { + /* Contexts are linked so we can keep track of them and re-create + * the ares channels in all of them at once if we need to. */ + struct resolv_ctx *prev; + struct resolv_ctx *next; + + 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; + + /* 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 resolv_ctx *context_list; + +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(1, ("Invalid ares channel - this is likely a bug\n")); + return; + } + + if (flags & TEVENT_FD_READ) { + ares_process_fd(watch->ctx->channel, watch->fd, ARES_SOCKET_BAD); + } + if (flags & TEVENT_FD_WRITE) { + ares_process_fd(watch->ctx->channel, ARES_SOCKET_BAD, watch->fd); + } +} + +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 }; + struct timeval *tvp; + + 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(1, ("Out of memory\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(9, ("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 +schedule_timeout_watcher(struct tevent_context *ev, struct resolv_ctx *ctx) +{ + ctx->pending_requests++; + if (ctx->timeout_watcher) { + return; + } + + DEBUG(9, ("Scheduling DNS timeout watcher\n")); + add_timeout_timer(ev, ctx); +} + +static void +unschedule_timeout_watcher(struct resolv_ctx *ctx) +{ + if (ctx->pending_requests <= 0) { + DEBUG(1, ("Pending DNS requests mismatch\n")); + return; + } + + ctx->pending_requests--; + if (ctx->pending_requests == 0) { + DEBUG(9, ("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(1, ("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(1, ("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; + + DLIST_REMOVE(context_list, ctx); + + if (ctx->channel == NULL) { + DEBUG(1, ("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(4, ("Initializing new c-ares channel\n")); + /* FIXME: the options would contain + * the nameservers to contact, the domains + * to search, timeout... => get from confdb + */ + options.sock_state_cb = fd_event; + options.sock_state_cb_data = ctx; + options.timeout = ctx->timeout * 1000; + options.tries = 1; + ret = ares_init_options(&new_channel, &options, + ARES_OPT_SOCK_STATE_CB | + ARES_OPT_TIMEOUTMS | + ARES_OPT_TRIES); + if (ret != ARES_SUCCESS) { + DEBUG(1, ("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(4, ("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, struct resolv_ctx **ctxp) +{ + int ret; + struct resolv_ctx *ctx; + + ctx = talloc_zero(mem_ctx, struct resolv_ctx); + if (ctx == NULL) + return ENOMEM; + + ctx->ev_ctx = ev_ctx; + ctx->timeout = timeout; + + ret = recreate_ares_channel(ctx); + if (ret != EOK) { + goto done; + } + + DLIST_ADD(context_list, ctx); + talloc_set_destructor(ctx, resolv_ctx_destructor); + + *ctxp = ctx; + return EOK; + +done: + talloc_free(ctx); + return ret; +} + +void +resolv_reread_configuration(void) +{ + struct resolv_ctx *ctx; + + DEBUG(4, ("Recreating all c-ares channels\n")); + DLIST_FOR_EACH(ctx, context_list) { + recreate_ares_channel(ctx); + } +} + +struct hostent * +resolv_copy_hostent(TALLOC_CTX *mem_ctx, struct hostent *src) +{ + struct hostent *ret; + int len; + int i; + + ret = talloc_zero(mem_ctx, struct hostent); + if (ret == NULL) { + return NULL; + } + + if (src->h_name != NULL) { + ret->h_name = talloc_strdup(ret, src->h_name); + if (ret->h_name == NULL) { + goto fail; + } + } + if (src->h_aliases != NULL) { + for (len = 0; src->h_aliases[len] != NULL; len++); + ret->h_aliases = talloc_size(ret, sizeof(char *) * (len + 1)); + if (ret->h_aliases == NULL) { + goto fail; + } + for (i = 0; i < len; i++) { + ret->h_aliases[i] = talloc_strdup(ret->h_aliases, src->h_aliases[i]); + if (ret->h_aliases[i] == NULL) { + goto fail; + } + } + ret->h_aliases[len] = NULL; + } + ret->h_addrtype = src->h_addrtype; + ret->h_length = src->h_length; + if (src->h_addr_list != NULL) { + for (len = 0; src->h_addr_list[len] != NULL; len++); + ret->h_addr_list = talloc_size(ret, sizeof(char *) * (len + 1)); + if (ret->h_addr_list == NULL) { + goto fail; + } + for (i = 0; i < len; i++) { + ret->h_addr_list[i] = talloc_memdup(ret->h_addr_list, + src->h_addr_list[i], + ret->h_length); + if (ret->h_addr_list[i] == NULL) { + goto fail; + } + } + ret->h_addr_list[len] = NULL; + } + + return ret; + +fail: + talloc_free(ret); + return NULL; +} + +/******************************************************************* + * Get host by name. * + *******************************************************************/ + +struct gethostbyname_state { + struct resolv_ctx *resolv_ctx; + /* Part of the query. */ + const char *name; + int family; + /* These are returned by ares. The hostent struct will be freed + * when the user callback returns. */ + struct hostent *hostent; + int status; + int timeouts; + int retrying; +}; + +static void +ares_gethostbyname_wakeup(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) +{ + struct tevent_req *req, *subreq; + struct gethostbyname_state *state; + struct timeval tv = { 0, 0 }; + + DEBUG(4, ("Trying to resolve A record of '%s'\n", name)); + + if (ctx->channel == NULL) { + DEBUG(1, ("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->name = name; + state->family = AF_INET; + state->hostent = NULL; + state->status = 0; + state->timeouts = 0; + state->retrying = 0; + + /* We need to have a wrapper around ares_gethostbyname(), because + * ares_gethostbyname() 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(1, ("Failed to add critical timer to run next operation!\n")); + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, ares_gethostbyname_wakeup, req); + schedule_timeout_watcher(ev, ctx); + + return req; +} + +static void +resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent *hostent); + +static void +resolv_gethostbyname_done(void *arg, int status, int timeouts, struct hostent *hostent) +{ + struct tevent_req *req = talloc_get_type(arg, struct tevent_req); + struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state); + + if (state->retrying == 0 && status == ARES_EDESTRUCTION + && state->resolv_ctx->channel != NULL) { + state->retrying = 1; + ares_gethostbyname(state->resolv_ctx->channel, state->name, + state->family, resolv_gethostbyname_done, req); + return; + } + + unschedule_timeout_watcher(state->resolv_ctx); + + if (hostent != NULL) { + state->hostent = resolv_copy_hostent(req, hostent); + if (state->hostent == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + } else { + state->hostent = NULL; + } + state->status = status; + state->timeouts = timeouts; + + if (status != ARES_SUCCESS) { + if (status == ARES_ENOTFOUND || status == ARES_ENODATA) { + /* IPv4 failure. Try IPv6 */ + state->family = AF_INET6; + state->retrying = 0; + state->timeouts = 0; + DEBUG(4, ("Trying to resolve AAAA record of '%s'\n", + state->name)); + ares_gethostbyname(state->resolv_ctx->channel, state->name, + state->family, resolv_gethostbyname6_done, + req); + return; + } + + /* Any other error indicates a server error, + * so don't bother trying again + */ + tevent_req_error(req, return_code(status)); + } + else { + tevent_req_done(req); + } +} + +static void +resolv_gethostbyname6_done(void *arg, int status, int timeouts, struct hostent *hostent) +{ + struct tevent_req *req = talloc_get_type(arg, struct tevent_req); + struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state); + + if (state->retrying == 0 && status == ARES_EDESTRUCTION) { + state->retrying = 1; + ares_gethostbyname(state->resolv_ctx->channel, state->name, + state->family, resolv_gethostbyname6_done, req); + return; + } + + if (hostent != NULL) { + state->hostent = resolv_copy_hostent(req, hostent); + if (state->hostent == NULL) { + tevent_req_error(req, ENOMEM); + return; + } + } else { + state->hostent = NULL; + } + state->status = status; + state->timeouts = timeouts; + + if (status != ARES_SUCCESS) { + tevent_req_error(req, return_code(status)); + } + else { + tevent_req_done(req); + } +} + +int +resolv_gethostbyname_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, + int *status, int *timeouts, + struct hostent **hostent) +{ + 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 (hostent) { + *hostent = talloc_steal(mem_ctx, state->hostent); + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + return EOK; +} + +static void +ares_gethostbyname_wakeup(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); + + if (!tevent_wakeup_recv(subreq)) { + return; + } + talloc_zfree(subreq); + + if (state->resolv_ctx->channel == NULL) { + DEBUG(1, ("Invalid ares channel - this is likely a bug\n")); + tevent_req_error(req, EIO); + return; + } + + ares_gethostbyname(state->resolv_ctx->channel, state->name, + state->family, resolv_gethostbyname_done, req); +} + +/* SRV and TXT parsing is not used anywhere in the code yet, so we disable it + * for now + */ +#ifdef BUILD_TXT_SRV + +/* + * 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 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; + int status; + int timeouts; + int retrying; +}; + +static void +ares_getsrv_wakeup(struct tevent_req *subreq); + +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(4, ("Trying to resolve SRV record of '%s'\n", query)); + + if (ctx->channel == NULL) { + DEBUG(1, ("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->status = 0; + state->timeouts = 0; + state->retrying = 0; + + subreq = tevent_wakeup_send(req, ev, tv); + if (subreq == NULL) { + DEBUG(1, ("Failed to add critical timer to run next operation!\n")); + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, ares_getsrv_wakeup, req); + schedule_timeout_watcher(ev, ctx); + + return req; +} + +static void +resolv_getsrv_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen) +{ + struct tevent_req *req = talloc_get_type(arg, struct tevent_req); + struct getsrv_state *state = tevent_req_data(req, struct getsrv_state); + int ret; + struct ares_srv_reply *reply_list; + + 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_srv, resolv_getsrv_done, req); + return; + } + + unschedule_timeout_watcher(state->resolv_ctx); + + state->status = status; + state->timeouts = timeouts; + + if (status != ARES_SUCCESS) { + tevent_req_error(req, return_code(status)); + ret = return_code(status); + goto fail; + } + + ret = ares_parse_srv_reply(abuf, alen, &reply_list); + if (status != ARES_SUCCESS) { + DEBUG(2, ("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; + + 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) +{ + 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); + + 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(1, ("Invalid ares channel - this is likely a bug\n")); + tevent_req_error(req, EIO); + return; + } + + ares_query(state->resolv_ctx->channel, state->query, + ns_c_in, ns_t_srv, resolv_getsrv_done, req); +} + +/* + * 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 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); + +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(4, ("Trying to resolve TXT record of '%s'\n", query)); + + if (ctx->channel == NULL) { + DEBUG(1, ("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; + + subreq = tevent_wakeup_send(req, ev, tv); + if (subreq == NULL) { + DEBUG(1, ("Failed to add critical timer to run next operation!\n")); + talloc_zfree(req); + return NULL; + } + tevent_req_set_callback(subreq, ares_gettxt_wakeup, req); + schedule_timeout_watcher(ev, ctx); + + return req; +} + +static void +resolv_gettxt_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen) +{ + struct tevent_req *req = talloc_get_type(arg, struct tevent_req); + struct gettxt_state *state = tevent_req_data(req, struct gettxt_state); + int ret; + struct ares_txt_reply *reply_list; + + 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; + } + + unschedule_timeout_watcher(state->resolv_ctx); + + 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(2, ("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(1, ("Invalid ares channel - this is likely a bug\n")); + tevent_req_error(req, EIO); + return; + } + + ares_query(state->resolv_ctx->channel, state->query, + ns_c_in, ns_t_txt, resolv_gettxt_done, req); +} + +#endif |