summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJakub Hrozek <jhrozek@redhat.com>2009-07-15 18:21:33 +0200
committerSimo Sorce <ssorce@redhat.com>2009-07-24 10:21:43 -0400
commitb919feeb115751ddda02a326e1d2636f1c83e32f (patch)
treeea91693dc259406706652314a34c3f7562bec494 /server
parent1394ff5e0131f3c7d7d37a675b43e3fa6cf58bbf (diff)
downloadsssd-b919feeb115751ddda02a326e1d2636f1c83e32f.tar.gz
sssd-b919feeb115751ddda02a326e1d2636f1c83e32f.tar.xz
sssd-b919feeb115751ddda02a326e1d2636f1c83e32f.zip
Async DNS integration
Integrates the c-ares asynchronous resolved library into SSSD.
Diffstat (limited to 'server')
-rw-r--r--server/Makefile.am6
-rw-r--r--server/configure.ac1
-rw-r--r--server/external/libcares.m49
-rw-r--r--server/resolv/async_resolv.c642
-rw-r--r--server/resolv/async_resolv.h82
5 files changed, 739 insertions, 1 deletions
diff --git a/server/Makefile.am b/server/Makefile.am
index 539c90063..575f5212e 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -149,6 +149,9 @@ SSSD_RESPONDER_OBJ = \
SSSD_TOOLS_OBJ = \
tools/tools_util.c
+SSSD_RESOLV_OBJ = \
+ resolv/async_resolv.c
+
SSSD_LIBS = \
$(TALLOC_LIBS) \
$(TDB_LIBS) \
@@ -196,7 +199,8 @@ dist_noinst_HEADERS = \
providers/ldap/sdap.h \
providers/ldap/sdap_async.h \
tools/tools_util.h \
- krb5_plugin/sssd_krb5_locator_plugin.h
+ krb5_plugin/sssd_krb5_locator_plugin.h \
+ resolv/async_resolv.h
####################
# Program Binaries #
diff --git a/server/configure.ac b/server/configure.ac
index 084ef3bce..5a9888ba0 100644
--- a/server/configure.ac
+++ b/server/configure.ac
@@ -59,6 +59,7 @@ m4_include([external/pam.m4])
m4_include([external/ldap.m4])
m4_include([external/libpcre.m4])
m4_include([external/krb5.m4])
+m4_include([external/libcares.m4])
m4_include([util/signal.m4])
PKG_CHECK_MODULES([DBUS],[dbus-1])
diff --git a/server/external/libcares.m4 b/server/external/libcares.m4
new file mode 100644
index 000000000..09451b3f7
--- /dev/null
+++ b/server/external/libcares.m4
@@ -0,0 +1,9 @@
+AC_SUBST(CARES_OBJ)
+AC_SUBST(CARES_LIBS)
+AC_SUBST(CARES_CFLAGS)
+
+AC_CHECK_HEADERS(ares.h,
+ [AC_CHECK_LIB([cares], [ares_init], [ CARES_LIBS="-lcares" ], [AC_MSG_ERROR([No usable c-ares library found])])],
+ [AC_MSG_ERROR([c-ares header files are not installed])]
+)
+
diff --git a/server/resolv/async_resolv.c b/server/resolv/async_resolv.c
new file mode 100644
index 000000000..70bea6c8e
--- /dev/null
+++ b/server/resolv/async_resolv.c
@@ -0,0 +1,642 @@
+/*
+ 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"
+
+/* TODO: remove later
+ * These functions are available in the latest tevent/talloc and are the ones
+ * that should be used as tevent_req is rightfully opaque there */
+#ifndef tevent_req_data
+#define tevent_req_data(req, type) ((type *)req->private_state)
+#endif
+
+#ifndef tevent_req_set_callback
+#define tevent_req_set_callback(req, func, data) \
+ do { req->async.fn = func; req->async.private_data = data; } while(0)
+#endif
+
+#ifndef tevent_req_callback_data
+#define tevent_req_callback_data(req, type) ((type *)req->async.private_data)
+#endif
+
+struct fd_watch {
+ struct fd_watch *prev;
+ struct fd_watch *next;
+
+ int fd;
+ struct resolv_ctx *ctx;
+};
+
+struct resolv_ctx {
+ struct tevent_context *ev_ctx;
+
+ ares_channel channel;
+ /* List of file descriptors that are watched by tevent. */
+ struct fd_watch *fds;
+};
+
+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 -1;
+ }
+}
+
+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 = data;
+
+ if (watch->ctx->channel == NULL) {
+ DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
+ return;
+ }
+ ares_process_fd(watch->ctx->channel, watch->fd, watch->fd);
+}
+
+
+static void fd_event_add(struct resolv_ctx *ctx, int s);
+static void fd_event_write(struct resolv_ctx *ctx, int s);
+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 = data;
+ struct fd_watch *watch;
+
+ /* The socket is about to get closed. */
+ if (fd_read == 0 && fd_write == 0) {
+ fd_event_close(ctx, s);
+ return;
+ }
+
+ /* If ares needs to write to a descriptor */
+ if (fd_write == 1) {
+ fd_event_write(ctx, s);
+ }
+
+ /* Are we already watching this file descriptor? */
+ watch = ctx->fds;
+ while (watch) {
+ if (watch->fd == s) {
+ return;
+ }
+ watch = watch->next;
+ }
+
+ fd_event_add(ctx, s);
+}
+
+static void
+fd_event_add(struct resolv_ctx *ctx, int s)
+{
+ struct fd_watch *watch;
+ struct tevent_fd *fde;
+
+ /* 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"));
+ return;
+ }
+ talloc_set_destructor(watch, fd_watch_destructor);
+
+ watch->fd = s;
+ watch->ctx = ctx;
+
+ fde = tevent_add_fd(ctx->ev_ctx, watch, s, TEVENT_FD_READ, fd_input_available, watch);
+ if (fde == NULL) {
+ DEBUG(1, ("tevent_add_fd() failed"));
+ talloc_free(watch);
+ return;
+ }
+ DLIST_ADD(ctx->fds, watch);
+}
+
+static void
+fd_event_write(struct resolv_ctx *ctx, int s)
+{
+ if (ctx->channel == NULL) {
+ DEBUG(1, ("Invalid ares channel - this is likely a bug\n"));
+ return;
+ }
+ /* do not allow any read. */
+ ares_process_fd(ctx->channel, ARES_SOCKET_BAD, s);
+}
+
+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)
+{
+ if (ctx->channel == NULL) {
+ DEBUG(1, ("Ares channel already destroyed?\n"));
+ return -1;
+ }
+
+ ares_destroy(ctx->channel);
+ ctx->channel = NULL;
+
+ return 0;
+}
+
+int
+resolv_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev_ctx,
+ struct resolv_ctx **ctxp)
+{
+ int ret;
+ struct resolv_ctx *ctx;
+ struct ares_options options;
+
+ ctx = talloc_zero(mem_ctx, struct resolv_ctx);
+ if (ctx == NULL)
+ return ENOMEM;
+
+ ctx->ev_ctx = ev_ctx;
+
+ /* 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;
+ ret = ares_init_options(&ctx->channel, &options, ARES_OPT_SOCK_STATE_CB);
+ if (ret != ARES_SUCCESS) {
+ DEBUG(1, ("Failed to initialize ares channel: %s",
+ resolv_strerror(ret)));
+ ret = return_code(ret);
+ goto done;
+ }
+
+ talloc_set_destructor(ctx, resolv_ctx_destructor);
+
+ *ctxp = ctx;
+ return EOK;
+
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+/*******************************************************************
+ * 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. */
+ const struct hostent *hostent;
+ int status;
+ int timeouts;
+};
+
+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, int family)
+{
+ struct tevent_req *req, *subreq;
+ struct gethostbyname_state *state;
+ struct timeval tv = { 0, 0 };
+
+ 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 = family;
+ state->hostent = NULL;
+ state->status = 0;
+ state->timeouts = 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(mem_ctx, 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);
+
+ return req;
+}
+
+static void
+resolv_gethostbyname_done(void *arg, int status, int timeouts, struct hostent *hostent)
+{
+ struct tevent_req *req = arg;
+ struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state);
+
+ state->hostent = hostent;
+ 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, int *status, int *timeouts,
+ struct hostent const **hostent)
+{
+ struct gethostbyname_state *state = tevent_req_data(req, struct gethostbyname_state);
+ enum tevent_req_state tstate;
+ uint64_t err;
+
+ /* 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 = state->hostent;
+
+ if (tevent_req_is_error(req, &tstate, &err)) {
+ return -1;
+ }
+
+ 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);
+}
+
+/*******************************************************************
+ * 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 srv_reply *reply_list;
+ int num_replies;
+ int status;
+ int timeouts;
+};
+
+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 };
+
+ 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->num_replies = 0;
+ state->status = 0;
+ state->timeouts = 0;
+
+ subreq = tevent_wakeup_send(mem_ctx, 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);
+
+ return req;
+}
+
+static void
+resolv_getsrv_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen)
+{
+ struct tevent_req *req = arg;
+ struct getsrv_state *state = tevent_req_data(req, struct getsrv_state);
+ int ret;
+ int num_replies;
+ struct srv_reply *reply_list;
+
+ state->status = status;
+ state->timeouts = timeouts;
+
+ if (status != ARES_SUCCESS) {
+ tevent_req_error(req, return_code(status));
+ return;
+ }
+
+ ret = ares_parse_srv_reply(abuf, alen, &reply_list, &num_replies);
+ if (status != ARES_SUCCESS) {
+ DEBUG(2, ("SRV record parsing failed: %d: %s\n", ret, ares_strerror(ret)));
+ tevent_req_error(req, return_code(ret));
+ return;
+ }
+ state->reply_list = reply_list;
+ state->num_replies = num_replies;
+
+ tevent_req_done(req);
+}
+
+int
+resolv_getsrv_recv(struct tevent_req *req, int *status, int *timeouts,
+ struct srv_reply const **reply_list, int *num_replies)
+{
+ struct getsrv_state *state = tevent_req_data(req, struct getsrv_state);
+ enum tevent_req_state tstate;
+ uint64_t err;
+
+ if (status)
+ *status = state->status;
+ if (timeouts)
+ *timeouts = state->timeouts;
+ if (reply_list)
+ *reply_list = state->reply_list;
+ if (num_replies)
+ *num_replies = state->num_replies;
+
+ if (tevent_req_is_error(req, &tstate, &err))
+ return -1;
+
+ 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);
+}
+
+/*******************************************************************
+ * Get TXT record *
+ *******************************************************************/
+
+struct gettxt_state {
+ struct resolv_ctx *resolv_ctx;
+ /* the TXT query */
+ const char *query;
+
+ /* parsed data returned by ares */
+ struct txt_reply *reply_list;
+ int num_replies;
+ int status;
+ int timeouts;
+};
+
+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 };
+
+ 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->num_replies = 0;
+ state->status = 0;
+ state->timeouts = 0;
+
+
+ subreq = tevent_wakeup_send(mem_ctx, 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);
+
+ return req;
+}
+
+static void
+resolv_gettxt_done(void *arg, int status, int timeouts, unsigned char *abuf, int alen)
+{
+ struct tevent_req *req = arg;
+ struct gettxt_state *state = tevent_req_data(req, struct gettxt_state);
+ int ret;
+ int num_replies;
+ struct txt_reply *reply_list;
+
+ state->status = status;
+ state->timeouts = timeouts;
+
+ if (status != ARES_SUCCESS) {
+ tevent_req_error(req, return_code(status));
+ return;
+ }
+
+ ret = ares_parse_txt_reply(abuf, alen, &reply_list, &num_replies);
+ if (status != ARES_SUCCESS) {
+ DEBUG(2, ("TXT record parsing failed: %d: %s\n", ret, ares_strerror(ret)));
+ tevent_req_error(req, return_code(ret));
+ return;
+ }
+ state->reply_list = reply_list;
+ state->num_replies = num_replies;
+
+ tevent_req_done(req);
+}
+
+int
+resolv_gettxt_recv(struct tevent_req *req, int *status, int *timeouts,
+ struct txt_reply const **reply_list, int *num_replies)
+{
+ struct gettxt_state *state = tevent_req_data(req, struct gettxt_state);
+ enum tevent_req_state tstate;
+ uint64_t err;
+
+ if (status)
+ *status = state->status;
+ if (timeouts)
+ *timeouts = state->timeouts;
+ if (reply_list)
+ *reply_list = state->reply_list;
+ if (num_replies)
+ *num_replies = state->num_replies;
+
+ if (tevent_req_is_error(req, &tstate, &err))
+ return -1;
+
+ 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);
+}
+
diff --git a/server/resolv/async_resolv.h b/server/resolv/async_resolv.h
new file mode 100644
index 000000000..6acb6b2a3
--- /dev/null
+++ b/server/resolv/async_resolv.h
@@ -0,0 +1,82 @@
+/*
+ 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>
+
+/*
+ * 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,
+ struct resolv_ctx **ctxp);
+
+const char *resolv_strerror(int ares_code);
+
+/** Get host by name **/
+struct tevent_req *resolv_gethostbyname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct resolv_ctx *ctx,
+ const char *name,
+ int family);
+
+int resolv_gethostbyname_recv(struct tevent_req *req,
+ int *status,
+ int *timeouts,
+ struct hostent const **hostent);
+
+/** 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(struct tevent_req *req,
+ int *status,
+ int *timeouts,
+ struct srv_reply const **reply_list,
+ int *num_replies);
+
+/** 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(struct tevent_req *req,
+ int *status,
+ int *timeouts,
+ struct txt_reply const **reply_list,
+ int *num_replies);
+
+#endif /* __ASYNC_RESOLV_H__ */