async_resolv.c revision 79cafdcc25948300e2b0c85955b67b0d0c4c73c7
/*
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