From b919feeb115751ddda02a326e1d2636f1c83e32f Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Wed, 15 Jul 2009 18:21:33 +0200 Subject: Async DNS integration Integrates the c-ares asynchronous resolved library into SSSD. --- server/Makefile.am | 6 +- server/configure.ac | 1 + server/external/libcares.m4 | 9 + server/resolv/async_resolv.c | 642 +++++++++++++++++++++++++++++++++++++++++++ server/resolv/async_resolv.h | 82 ++++++ sssd.spec.in | 1 + 6 files changed, 740 insertions(+), 1 deletion(-) create mode 100644 server/external/libcares.m4 create mode 100644 server/resolv/async_resolv.c create mode 100644 server/resolv/async_resolv.h 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 + Jakub Hrozek + + 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 . +*/ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 + Jakub Hrozek + + 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 . +*/ + +#ifndef __ASYNC_RESOLV_H__ +#define __ASYNC_RESOLV_H__ + +#include +#include + +/* + * 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__ */ diff --git a/sssd.spec.in b/sssd.spec.in index fcdf0d0dc..aa284ff66 100644 --- a/sssd.spec.in +++ b/sssd.spec.in @@ -43,6 +43,7 @@ BuildRequires: libxslt BuildRequires: libxml2 BuildRequires: docbook-style-xsl BuildRequires: krb5-devel +BuildRequires: c-ares-devel %description Provides a set of daemons to manage access to remote directories and -- cgit