/* SSSD Data Provider Helpers Copyright (C) Simo Sorce 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 "providers/dp_backend.h" #include "resolv/async_resolv.h" struct be_svc_callback { struct be_svc_callback *prev; struct be_svc_callback *next; struct be_svc_data *svc; be_svc_callback_fn_t *fn; void *private_data; }; struct be_svc_data { struct be_svc_data *prev; struct be_svc_data *next; const char *name; struct fo_service *fo_service; struct fo_server *last_good_srv; time_t last_status_change; bool run_callbacks; struct be_svc_callback *callbacks; struct fo_server *first_resolved; }; struct be_failover_ctx { struct fo_ctx *fo_ctx; struct be_resolv_ctx *be_res; struct be_svc_data *svcs; struct tevent_timer *primary_server_handler; }; static const char *proto_table[] = { FO_PROTO_TCP, FO_PROTO_UDP, NULL }; int be_fo_is_srv_identifier(const char *server) { return server && strcasecmp(server, BE_SRV_IDENTIFIER) == 0; } static int be_fo_get_options(struct be_ctx *ctx, struct fo_options *opts) { opts->service_resolv_timeout = dp_opt_get_int(ctx->be_res->opts, DP_RES_OPT_RESOLVER_TIMEOUT); opts->retry_timeout = 30; opts->srv_retry_timeout = 14400; opts->family_order = ctx->be_res->family_order; return EOK; } int be_init_failover(struct be_ctx *ctx) { int ret; struct fo_options fopts; if (ctx->be_fo != NULL) { return EOK; } ctx->be_fo = talloc_zero(ctx, struct be_failover_ctx); if (!ctx->be_fo) { return ENOMEM; } ret = be_res_init(ctx); if (ret != EOK) { DEBUG(SSSDBG_FATAL_FAILURE, ("fatal error initializing resolver context\n")); talloc_zfree(ctx->be_fo); return ret; } ctx->be_fo->be_res = ctx->be_res; ret = be_fo_get_options(ctx, &fopts); if (ret != EOK) { talloc_zfree(ctx->be_fo); return ret; } ctx->be_fo->fo_ctx = fo_context_init(ctx->be_fo, &fopts); if (!ctx->be_fo->fo_ctx) { talloc_zfree(ctx->be_fo); return ENOMEM; } return EOK; } static int be_svc_data_destroy(void *memptr) { struct be_svc_data *svc; svc = talloc_get_type(memptr, struct be_svc_data); while (svc->callbacks) { /* callbacks removes themselves from the list, * so this while will freem them all and then terminate */ talloc_free(svc->callbacks); } return 0; } /* * Find registered be_svc_data by service name. */ static struct be_svc_data *be_fo_find_svc_data(struct be_ctx *ctx, const char *service_name) { struct be_svc_data *svc; if (!ctx || !ctx->be_fo) { return 0; } DLIST_FOR_EACH(svc, ctx->be_fo->svcs) { if (strcmp(svc->name, service_name) == 0) { return svc; } } return 0; } int be_fo_add_service(struct be_ctx *ctx, const char *service_name, datacmp_fn user_data_cmp) { struct fo_service *service; struct be_svc_data *svc; int ret; svc = be_fo_find_svc_data(ctx, service_name); if (svc) { DEBUG(6, ("Failover service already initialized!\n")); /* we already have a service up and configured, * can happen when using both id and auth provider */ return EOK; } /* if not in the be service list, try to create new one */ ret = fo_new_service(ctx->be_fo->fo_ctx, service_name, user_data_cmp, &service); if (ret != EOK && ret != EEXIST) { DEBUG(1, ("Failed to create failover service!\n")); return ret; } svc = talloc_zero(ctx->be_fo, struct be_svc_data); if (!svc) { return ENOMEM; } talloc_set_destructor((TALLOC_CTX *)svc, be_svc_data_destroy); svc->name = talloc_strdup(svc, service_name); if (!svc->name) { talloc_zfree(svc); return ENOMEM; } svc->fo_service = service; DLIST_ADD(ctx->be_fo->svcs, svc); return EOK; } static int be_svc_callback_destroy(void *memptr) { struct be_svc_callback *callback; callback = talloc_get_type(memptr, struct be_svc_callback); if (callback->svc) { DLIST_REMOVE(callback->svc->callbacks, callback); } return 0; } int be_fo_service_add_callback(TALLOC_CTX *memctx, struct be_ctx *ctx, const char *service_name, be_svc_callback_fn_t *fn, void *private_data) { struct be_svc_callback *callback; struct be_svc_data *svc; svc = be_fo_find_svc_data(ctx, service_name); if (NULL == svc) { return ENOENT; } callback = talloc_zero(memctx, struct be_svc_callback); if (!callback) { return ENOMEM; } talloc_set_destructor((TALLOC_CTX *)callback, be_svc_callback_destroy); callback->svc = svc; callback->fn = fn; callback->private_data = private_data; DLIST_ADD(svc->callbacks, callback); return EOK; } void be_fo_set_srv_lookup_plugin(struct be_ctx *ctx, fo_srv_lookup_plugin_send_t send_fn, fo_srv_lookup_plugin_recv_t recv_fn, void *pvt, const char *plugin_name) { bool bret; DEBUG(SSSDBG_TRACE_FUNC, ("Trying to set SRV lookup plugin to %s\n", plugin_name)); bret = fo_set_srv_lookup_plugin(ctx->be_fo->fo_ctx, send_fn, recv_fn, pvt); if (bret) { DEBUG(SSSDBG_TRACE_FUNC, ("SRV lookup plugin is now %s\n", plugin_name)); } else { DEBUG(SSSDBG_MINOR_FAILURE, ("Unable to set SRV lookup plugin, " "another plugin may be already in place\n")); } } errno_t be_fo_set_dns_srv_lookup_plugin(struct be_ctx *be_ctx, const char *hostname) { struct fo_resolve_srv_dns_ctx *srv_ctx = NULL; char resolved_hostname[HOST_NAME_MAX]; errno_t ret; if (hostname == NULL) { ret = gethostname(resolved_hostname, HOST_NAME_MAX); if (ret != EOK) { ret = errno; DEBUG(SSSDBG_CRIT_FAILURE, ("gethostname() failed: [%d]: %s\n", ret, strerror(ret))); return ret; } resolved_hostname[HOST_NAME_MAX-1] = '\0'; hostname = resolved_hostname; } srv_ctx = fo_resolve_srv_dns_ctx_init(be_ctx, be_ctx->be_res->resolv, be_ctx->be_res->family_order, default_host_dbs, hostname, be_ctx->domain->name); if (srv_ctx == NULL) { DEBUG(SSSDBG_FATAL_FAILURE, ("Out of memory?\n")); return ENOMEM; } be_fo_set_srv_lookup_plugin(be_ctx, fo_resolve_srv_dns_send, fo_resolve_srv_dns_recv, srv_ctx, "DNS"); return EOK; } int be_fo_add_srv_server(struct be_ctx *ctx, const char *service_name, const char *query_service, const char *default_discovery_domain, enum be_fo_protocol proto, bool proto_fallback, void *user_data) { struct be_svc_data *svc; const char *domain; int ret; int i; svc = be_fo_find_svc_data(ctx, service_name); if (NULL == svc) { return ENOENT; } domain = dp_opt_get_string(ctx->be_res->opts, DP_RES_OPT_DNS_DOMAIN); if (!domain) { domain = default_discovery_domain; } /* Add the first protocol as the primary lookup */ ret = fo_add_srv_server(svc->fo_service, query_service, domain, ctx->domain->name, proto_table[proto], user_data); if (ret && ret != EEXIST) { DEBUG(1, ("Failed to add SRV lookup reference to failover service\n")); return ret; } if (proto_fallback) { i = (proto + 1) % BE_FO_PROTO_SENTINEL; /* All the rest as fallback */ while (i != proto) { ret = fo_add_srv_server(svc->fo_service, query_service, domain, ctx->domain->name, proto_table[i], user_data); if (ret && ret != EEXIST) { DEBUG(1, ("Failed to add SRV lookup reference to failover service\n")); return ret; } i = (i + 1) % BE_FO_PROTO_SENTINEL; } } return EOK; } int be_fo_get_server_count(struct be_ctx *ctx, const char *service_name) { struct be_svc_data *svc_data; svc_data = be_fo_find_svc_data(ctx, service_name); if (!svc_data) { return 0; } return fo_get_server_count(svc_data->fo_service); } int be_fo_add_server(struct be_ctx *ctx, const char *service_name, const char *server, int port, void *user_data, bool primary) { struct be_svc_data *svc; int ret; svc = be_fo_find_svc_data(ctx, service_name); if (NULL == svc) { return ENOENT; } ret = fo_add_server(svc->fo_service, server, port, user_data, primary); if (ret && ret != EEXIST) { DEBUG(1, ("Failed to add server to failover service\n")); return ret; } return EOK; } struct be_resolve_server_state { struct tevent_context *ev; struct be_ctx *ctx; struct be_svc_data *svc; int attempts; struct fo_server *srv; bool first_try; }; struct be_primary_server_ctx { struct be_ctx *bctx; struct tevent_context *ev; struct be_svc_data *svc; unsigned long timeout; int attempts; }; errno_t be_resolve_server_process(struct tevent_req *subreq, struct be_resolve_server_state *state, struct tevent_req **new_subreq); static void be_primary_server_done(struct tevent_req *subreq); static errno_t be_primary_server_timeout_activate(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct be_ctx *bctx, struct be_svc_data *svc, const unsigned long timeout_seconds); static void be_primary_server_timeout(struct tevent_context *ev, struct tevent_timer *te, struct timeval tv, void *pvt) { struct be_primary_server_ctx *ctx = talloc_get_type(pvt, struct be_primary_server_ctx); struct tevent_req *subreq; ctx->bctx->be_fo->primary_server_handler = NULL; DEBUG(SSSDBG_TRACE_FUNC, ("Looking for primary server!\n")); subreq = fo_resolve_service_send(ctx->bctx, ctx->ev, ctx->bctx->be_fo->be_res->resolv, ctx->bctx->be_fo->fo_ctx, ctx->svc->fo_service); if (subreq == NULL) { return; } tevent_req_set_callback(subreq, be_primary_server_done, ctx); } static void be_primary_server_done(struct tevent_req *subreq) { errno_t ret; struct be_primary_server_ctx *ctx; struct be_resolve_server_state *resolve_state; struct tevent_req *new_subreq; ctx = tevent_req_callback_data(subreq, struct be_primary_server_ctx); resolve_state = talloc_zero(ctx->bctx, struct be_resolve_server_state); if (resolve_state == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("talloc_zero() failed\n")); return; } resolve_state->attempts = ctx->attempts; resolve_state->ctx = ctx->bctx; resolve_state->ev = ctx->ev; resolve_state->first_try = true; resolve_state->srv = NULL; resolve_state->svc = ctx->svc; ret = be_resolve_server_process(subreq, resolve_state, &new_subreq); talloc_free(subreq); if (ret == EAGAIN) { ctx->attempts++; tevent_req_set_callback(new_subreq, be_primary_server_done, ctx); return; } else if (ret == EIO || (ret == EOK && !fo_is_server_primary(resolve_state->srv))) { /* Schedule another lookup * (either no server could be found or it was not primary) */ ret = be_primary_server_timeout_activate(ctx->bctx, ctx->ev, ctx->bctx, ctx->svc, ctx->timeout); if (ret != EOK) { DEBUG(SSSDBG_MINOR_FAILURE, ("Could not schedule primary server lookup\n")); } } else if (ret == EOK) { be_run_reconnect_cb(ctx->bctx); } talloc_zfree(ctx); /* If an error occurred just end the routine */ } static errno_t be_primary_server_timeout_activate(TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct be_ctx *bctx, struct be_svc_data *svc, const unsigned long timeout_seconds) { struct timeval tv; struct be_primary_server_ctx *ctx; struct be_failover_ctx *fo_ctx = bctx->be_fo; if (fo_ctx->primary_server_handler != NULL) { DEBUG(SSSDBG_TRACE_FUNC, ("The primary server reconnection " "is already scheduled\n")); return EOK; } ctx = talloc_zero(mem_ctx, struct be_primary_server_ctx); if (ctx == NULL) { return ENOMEM; } ctx->bctx = bctx; ctx->ev = ev; ctx->svc = svc; ctx->timeout = timeout_seconds; tv = tevent_timeval_current(); tv = tevent_timeval_add(&tv, timeout_seconds, 0); fo_ctx->primary_server_handler = tevent_add_timer(ev, bctx, tv, be_primary_server_timeout, ctx); if (fo_ctx->primary_server_handler == NULL) { DEBUG(SSSDBG_CRIT_FAILURE, ("tevent_add_timer failed.\n")); talloc_free(ctx); return ENOMEM; } DEBUG(SSSDBG_TRACE_INTERNAL, ("Primary server reactivation timeout set " "to %lu seconds\n", timeout_seconds)); return EOK; } static void be_resolve_server_done(struct tevent_req *subreq); struct tevent_req *be_resolve_server_send(TALLOC_CTX *memctx, struct tevent_context *ev, struct be_ctx *ctx, const char *service_name, bool first_try) { struct tevent_req *req, *subreq; struct be_resolve_server_state *state; struct be_svc_data *svc; req = tevent_req_create(memctx, &state, struct be_resolve_server_state); if (!req) return NULL; state->ev = ev; state->ctx = ctx; svc = be_fo_find_svc_data(ctx, service_name); if (NULL == svc) { tevent_req_error(req, EINVAL); tevent_req_post(req, ev); return req; } state->svc = svc; state->attempts = 0; state->first_try = first_try; subreq = fo_resolve_service_send(state, ev, ctx->be_fo->be_res->resolv, ctx->be_fo->fo_ctx, svc->fo_service); if (!subreq) { talloc_zfree(req); return NULL; } tevent_req_set_callback(subreq, be_resolve_server_done, req); return req; } static void be_resolve_server_done(struct tevent_req *subreq) { struct tevent_req *new_subreq; struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); struct be_resolve_server_state *state = tevent_req_data(req, struct be_resolve_server_state); time_t timeout = fo_get_service_retry_timeout(state->svc->fo_service) + 1; int ret; ret = be_resolve_server_process(subreq, state, &new_subreq); talloc_zfree(subreq); if (ret == EAGAIN) { tevent_req_set_callback(new_subreq, be_resolve_server_done, req); return; } else if (ret != EOK) { goto fail; } if (!fo_is_server_primary(state->srv)) { /* FIXME: make the timeout configurable */ ret = be_primary_server_timeout_activate(state->ctx, state->ev, state->ctx, state->svc, timeout); if (ret != EOK) { goto fail; } } tevent_req_done(req); return; fail: DEBUG(SSSDBG_TRACE_LIBS, ("Server resolution failed: %d\n", ret)); state->svc->first_resolved = NULL; tevent_req_error(req, ret); } errno_t be_resolve_server_process(struct tevent_req *subreq, struct be_resolve_server_state *state, struct tevent_req **new_subreq) { errno_t ret; time_t srv_status_change; struct be_svc_callback *callback; ret = fo_resolve_service_recv(subreq, &state->srv); switch (ret) { case EOK: if (!state->srv) { return EFAULT; } break; case ENOENT: /* all servers have been tried and none * was found good, go offline */ return EIO; default: /* mark server as bad and retry */ if (!state->srv) { return EFAULT; } DEBUG(SSSDBG_MINOR_FAILURE, ("Couldn't resolve server (%s), resolver returned (%d)\n", fo_get_server_str_name(state->srv), ret)); state->attempts++; if (state->attempts >= 10) { DEBUG(SSSDBG_OP_FAILURE, ("Failed to find a server after 10 attempts\n")); return EIO; } /* now try next one */ DEBUG(SSSDBG_TRACE_LIBS, ("Trying with the next one!\n")); subreq = fo_resolve_service_send(state, state->ev, state->ctx->be_fo->be_res->resolv, state->ctx->be_fo->fo_ctx, state->svc->fo_service); if (!subreq) { return ENOMEM; } if (new_subreq) { *new_subreq = subreq; } return EAGAIN; } /* all fine we got the server */ if (state->svc->first_resolved == NULL || state->first_try == true) { DEBUG(SSSDBG_TRACE_LIBS, ("Saving the first resolved server\n")); state->svc->first_resolved = state->srv; } else if (state->svc->first_resolved == state->srv) { DEBUG(SSSDBG_OP_FAILURE, ("The fail over cycled through all available servers\n")); return ENOENT; } if (DEBUG_IS_SET(SSSDBG_FUNC_DATA) && fo_get_server_name(state->srv)) { struct resolv_hostent *srvaddr; char ipaddr[128]; srvaddr = fo_get_server_hostent(state->srv); if (!srvaddr) { DEBUG(SSSDBG_CRIT_FAILURE, ("FATAL: No hostent available for server (%s)\n", fo_get_server_str_name(state->srv))); return EFAULT; } inet_ntop(srvaddr->family, srvaddr->addr_list[0]->ipaddr, ipaddr, 128); DEBUG(SSSDBG_FUNC_DATA, ("Found address for server %s: [%s] TTL %d\n", fo_get_server_str_name(state->srv), ipaddr, srvaddr->addr_list[0]->ttl)); } srv_status_change = fo_get_server_hostname_last_change(state->srv); /* now call all svc callbacks if server changed or if it is explicitly * requested or if the server is the same but changed status since last time*/ if (state->srv != state->svc->last_good_srv || state->svc->run_callbacks || srv_status_change > state->svc->last_status_change) { state->svc->last_good_srv = state->srv; state->svc->last_status_change = srv_status_change; state->svc->run_callbacks = false; DLIST_FOR_EACH(callback, state->svc->callbacks) { callback->fn(callback->private_data, state->srv); } } return EOK; } int be_resolve_server_recv(struct tevent_req *req, struct fo_server **srv) { struct be_resolve_server_state *state = tevent_req_data(req, struct be_resolve_server_state); TEVENT_REQ_RETURN_ON_ERROR(req); if (srv) { *srv = state->srv; } return EOK; } void be_fo_try_next_server(struct be_ctx *ctx, const char *service_name) { struct be_svc_data *svc; svc = be_fo_find_svc_data(ctx, service_name); if (svc) { fo_try_next_server(svc->fo_service); } } int be_fo_run_callbacks_at_next_request(struct be_ctx *ctx, const char *service_name) { struct be_svc_data *svc; svc = be_fo_find_svc_data(ctx, service_name); if (NULL == svc) { return ENOENT; } svc->run_callbacks = true; return EOK; } void reset_fo(struct be_ctx *be_ctx) { fo_reset_services(be_ctx->be_fo->fo_ctx); } void be_fo_set_port_status(struct be_ctx *ctx, const char *service_name, struct fo_server *server, enum port_status status) { struct be_svc_data *be_svc; be_svc = be_fo_find_svc_data(ctx, service_name); if (be_svc == NULL) { DEBUG(SSSDBG_OP_FAILURE, ("No service associated with name %s\n", service_name)); return; } if (!fo_svc_has_server(be_svc->fo_service, server)) { DEBUG(SSSDBG_OP_FAILURE, ("The server %p is not valid anymore, cannot set its status\n", server)); return; } /* Now we know that the server is valid */ fo_set_port_status(server, status); if (status == PORT_WORKING) { /* We were successful in connecting to the server. Cycle through all * available servers next time */ be_svc->first_resolved = NULL; } } /* Resolver back end interface */ static struct dp_option dp_res_default_opts[] = { { "lookup_family_order", DP_OPT_STRING, { "ipv4_first" }, NULL_STRING }, { "dns_resolver_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, { "dns_resolver_op_timeout", DP_OPT_NUMBER, { .number = 6 }, NULL_NUMBER }, { "dns_discovery_domain", DP_OPT_STRING, NULL_STRING, NULL_STRING }, DP_OPTION_TERMINATOR }; static errno_t be_res_get_opts(struct be_resolv_ctx *res_ctx, struct confdb_ctx *cdb, const char *conf_path) { errno_t ret; const char *str_family; ret = dp_get_options(res_ctx, cdb, conf_path, dp_res_default_opts, DP_RES_OPTS, &res_ctx->opts); if (ret != EOK) { return ret; } str_family = dp_opt_get_string(res_ctx->opts, DP_RES_OPT_FAMILY_ORDER); DEBUG(SSSDBG_CONF_SETTINGS, ("Lookup order: %s\n", str_family)); if (strcasecmp(str_family, "ipv4_first") == 0) { res_ctx->family_order = IPV4_FIRST; } else if (strcasecmp(str_family, "ipv4_only") == 0) { res_ctx->family_order = IPV4_ONLY; } else if (strcasecmp(str_family, "ipv6_first") == 0) { res_ctx->family_order = IPV6_FIRST; } else if (strcasecmp(str_family, "ipv6_only") == 0) { res_ctx->family_order = IPV6_ONLY; } else { DEBUG(SSSDBG_OP_FAILURE, ("Unknown value for option %s: %s\n", dp_res_default_opts[DP_RES_OPT_FAMILY_ORDER].opt_name, str_family)); return EINVAL; } return EOK; } errno_t be_res_init(struct be_ctx *ctx) { errno_t ret; if (ctx->be_res != NULL) { return EOK; } ctx->be_res = talloc_zero(ctx, struct be_resolv_ctx); if (!ctx->be_res) { return ENOMEM; } ret = be_res_get_opts(ctx->be_res, ctx->cdb, ctx->conf_path); if (ret != EOK) { talloc_zfree(ctx->be_res); return ret; } ret = resolv_init(ctx, ctx->ev, dp_opt_get_int(ctx->be_res->opts, DP_RES_OPT_RESOLVER_OP_TIMEOUT), &ctx->be_res->resolv); if (ret != EOK) { talloc_zfree(ctx->be_res); return ret; } return EOK; }