summaryrefslogtreecommitdiffstats
path: root/src/providers/fail_over.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/providers/fail_over.c')
-rw-r--r--src/providers/fail_over.c1861
1 files changed, 1861 insertions, 0 deletions
diff --git a/src/providers/fail_over.c b/src/providers/fail_over.c
new file mode 100644
index 0000000..7cb6424
--- /dev/null
+++ b/src/providers/fail_over.c
@@ -0,0 +1,1861 @@
+/*
+ SSSD
+
+ Fail over helper functions.
+
+ Authors:
+ Martin Nagy <mnagy@redhat.com>
+ Jakub Hrozek <jhrozek@redhat.com>
+
+ Copyright (C) Red Hat, Inc 2010
+
+ 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/time.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <strings.h>
+#include <talloc.h>
+#include <netdb.h>
+
+#include "util/dlinklist.h"
+#include "util/refcount.h"
+#include "util/util.h"
+#include "providers/fail_over.h"
+#include "resolv/async_resolv.h"
+
+#define STATUS_DIFF(p, now) ((now).tv_sec - (p)->last_status_change.tv_sec)
+#define SERVER_NAME(s) ((s)->common ? (s)->common->name : "(no name)")
+
+#define DEFAULT_PORT_STATUS PORT_NEUTRAL
+#define DEFAULT_SERVER_STATUS SERVER_NAME_NOT_RESOLVED
+#define DEFAULT_SRV_STATUS SRV_NEUTRAL
+
+enum srv_lookup_status {
+ SRV_NEUTRAL, /* We didn't try this SRV lookup yet */
+ SRV_RESOLVED, /* This SRV lookup is resolved */
+ SRV_RESOLVE_ERROR, /* Could not resolve this SRV lookup */
+ SRV_EXPIRED /* Need to refresh the SRV query */
+};
+
+struct fo_ctx {
+ struct fo_service *service_list;
+ struct server_common *server_common_list;
+
+ struct fo_options *opts;
+
+ fo_srv_lookup_plugin_send_t srv_send_fn;
+ fo_srv_lookup_plugin_recv_t srv_recv_fn;
+ void *srv_pvt;
+};
+
+struct fo_service {
+ struct fo_service *prev;
+ struct fo_service *next;
+
+ struct fo_ctx *ctx;
+ char *name;
+ struct fo_server *active_server;
+ struct fo_server *last_tried_server;
+ struct fo_server *server_list;
+
+ /* Function pointed by user_data_cmp returns 0 if user_data is equal
+ * or nonzero value if not. Set to NULL if no user data comparison
+ * is needed in fail over duplicate servers detection.
+ */
+ datacmp_fn user_data_cmp;
+};
+
+struct fo_server {
+ REFCOUNT_COMMON;
+
+ struct fo_server *prev;
+ struct fo_server *next;
+
+ bool primary;
+ void *user_data;
+ int port;
+ enum port_status port_status;
+ struct srv_data *srv_data;
+ struct fo_service *service;
+ struct timeval last_status_change;
+ struct server_common *common;
+
+ TALLOC_CTX *fo_internal_owner;
+};
+
+struct server_common {
+ REFCOUNT_COMMON;
+
+ struct fo_ctx *ctx;
+
+ struct server_common *prev;
+ struct server_common *next;
+
+ char *name;
+ struct resolv_hostent *rhostent;
+ struct resolve_service_request *request_list;
+ enum server_status server_status;
+ struct timeval last_status_change;
+};
+
+struct srv_data {
+ char *dns_domain;
+ char *discovery_domain;
+ char *sssd_domain;
+ char *proto;
+ char *srv;
+
+ struct fo_server *meta;
+
+ int srv_lookup_status;
+ int ttl;
+ struct timeval last_status_change;
+};
+
+struct resolve_service_request {
+ struct resolve_service_request *prev;
+ struct resolve_service_request *next;
+
+ struct server_common *server_common;
+ struct tevent_req *req;
+ struct tevent_context *ev;
+};
+
+struct status {
+ int value;
+ struct timeval last_change;
+};
+
+struct fo_ctx *
+fo_context_init(TALLOC_CTX *mem_ctx, struct fo_options *opts)
+{
+ struct fo_ctx *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct fo_ctx);
+ if (ctx == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "No memory\n");
+ return NULL;
+ }
+ ctx->opts = talloc_zero(ctx, struct fo_options);
+ if (ctx->opts == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "No memory\n");
+ return NULL;
+ }
+
+ ctx->opts->srv_retry_neg_timeout = opts->srv_retry_neg_timeout;
+ ctx->opts->retry_timeout = opts->retry_timeout;
+ ctx->opts->family_order = opts->family_order;
+ ctx->opts->service_resolv_timeout = opts->service_resolv_timeout;
+ ctx->opts->use_search_list = opts->use_search_list;
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Created new fail over context, retry timeout is %"SPRItime"\n",
+ ctx->opts->retry_timeout);
+ return ctx;
+}
+
+static const char *
+str_port_status(enum port_status status)
+{
+ switch (status) {
+ case PORT_NEUTRAL:
+ return "neutral";
+ case PORT_WORKING:
+ return "working";
+ case PORT_NOT_WORKING:
+ return "not working";
+ }
+
+ return "unknown port status";
+}
+
+static const char *
+str_srv_data_status(enum srv_lookup_status status)
+{
+ switch (status) {
+ case SRV_NEUTRAL:
+ return "neutral";
+ case SRV_RESOLVED:
+ return "resolved";
+ case SRV_RESOLVE_ERROR:
+ return "not resolved";
+ case SRV_EXPIRED:
+ return "expired";
+ }
+
+ return "unknown SRV lookup status";
+}
+
+static void dump_srv_data(const struct srv_data *srv_data)
+{
+ if (srv_data == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "srv_data is NULL\n");
+ return;
+ }
+
+ DEBUG(SSSDBG_OP_FAILURE, "srv_data: dns_domain [%s] discovery_domain [%s] "
+ "sssd_domain [%s] proto [%s] srv [%s] "
+ "srv_lookup_status [%s] ttl [%d] "
+ "last_status_change [%"SPRItime"]\n",
+ srv_data->dns_domain == NULL ? "dns_domain is NULL"
+ : srv_data->dns_domain,
+ srv_data->discovery_domain == NULL ? "discovery_domain is NULL"
+ : srv_data->discovery_domain,
+ srv_data->sssd_domain == NULL ? "sssd_domain is NULL"
+ : srv_data->sssd_domain,
+ srv_data->proto == NULL ? "proto is NULL"
+ : srv_data->proto,
+ srv_data->srv == NULL ? "srv is NULL"
+ : srv_data->srv,
+ str_srv_data_status(srv_data->srv_lookup_status),
+ srv_data->ttl, srv_data->last_status_change.tv_sec);
+}
+
+void dump_fo_server(const struct fo_server *srv)
+{
+ DEBUG(SSSDBG_OP_FAILURE, "fo_server: primary [%s] port [%d] "
+ "port_status [%s] common->name [%s].\n",
+ srv->primary ? "true" : "false", srv->port,
+ str_port_status(srv->port_status),
+ srv->common == NULL ? "common is NULL"
+ : (srv->common->name == NULL
+ ? "common->name is NULL"
+ : srv->common->name));
+ dump_srv_data(srv->srv_data);
+}
+
+void dump_fo_server_list(const struct fo_server *srv)
+{
+ const struct fo_server *s;
+
+ s = srv;
+ while (s->prev != NULL) {
+ s = s->prev;
+ }
+
+ while (s != NULL) {
+ dump_fo_server(s);
+ s = s->next;
+ }
+}
+
+static const char *
+str_server_status(enum server_status status)
+{
+ switch (status) {
+ case SERVER_NAME_NOT_RESOLVED:
+ return "name not resolved";
+ case SERVER_RESOLVING_NAME:
+ return "resolving name";
+ case SERVER_NAME_RESOLVED:
+ return "name resolved";
+ case SERVER_WORKING:
+ return "working";
+ case SERVER_NOT_WORKING:
+ return "not working";
+ }
+
+ return "unknown server status";
+}
+
+int fo_is_srv_lookup(struct fo_server *s)
+{
+ return s && s->srv_data;
+}
+
+static void fo_server_free(struct fo_server *server)
+{
+ if (server == NULL) {
+ return;
+ }
+
+ talloc_free(server->fo_internal_owner);
+}
+
+static struct fo_server *
+collapse_srv_lookup(struct fo_server **_server)
+{
+ struct fo_server *tmp, *meta, *server;
+
+ server = *_server;
+ meta = server->srv_data->meta;
+ DEBUG(SSSDBG_CONF_SETTINGS, "Need to refresh SRV lookup for domain %s\n",
+ meta->srv_data->dns_domain);
+
+ if (server != meta) {
+ while (server->prev && server->prev->srv_data == meta->srv_data) {
+ tmp = server->prev;
+ DLIST_REMOVE(server->service->server_list, tmp);
+ fo_server_free(tmp);
+ }
+ while (server->next && server->next->srv_data == meta->srv_data) {
+ tmp = server->next;
+ DLIST_REMOVE(server->service->server_list, tmp);
+ fo_server_free(tmp);
+ }
+
+ if (server == server->service->active_server) {
+ server->service->active_server = NULL;
+ }
+ if (server == server->service->last_tried_server) {
+ server->service->last_tried_server = meta;
+ }
+
+ /* add back the meta server to denote SRV lookup */
+ DLIST_ADD_AFTER(server->service->server_list, meta, server);
+ DLIST_REMOVE(server->service->server_list, server);
+ fo_server_free(server);
+ }
+
+ meta->srv_data->srv_lookup_status = SRV_NEUTRAL;
+ meta->srv_data->last_status_change.tv_sec = 0;
+
+ *_server = NULL;
+
+ return meta;
+}
+
+static enum srv_lookup_status
+get_srv_data_status(struct srv_data *data)
+{
+ struct timeval tv;
+ time_t timeout;
+
+ gettimeofday(&tv, NULL);
+
+ /* Determine timeout value based on state of previous lookup. */
+ if (data->srv_lookup_status == SRV_RESOLVE_ERROR) {
+ timeout = data->meta->service->ctx->opts->srv_retry_neg_timeout;
+ } else {
+ timeout = data->ttl;
+ }
+
+ if (STATUS_DIFF(data, tv) > timeout) {
+ switch(data->srv_lookup_status) {
+ case SRV_EXPIRED:
+ case SRV_NEUTRAL:
+ break;
+ case SRV_RESOLVED:
+ data->srv_lookup_status = SRV_EXPIRED;
+ data->last_status_change.tv_sec = 0;
+ break;
+ case SRV_RESOLVE_ERROR:
+ data->srv_lookup_status = SRV_NEUTRAL;
+ data->last_status_change.tv_sec = 0;
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Changing state of SRV lookup from 'SRV_RESOLVE_ERROR' to "
+ "'SRV_NEUTRAL'.\n");
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unknown state for SRV server!\n");
+ }
+ }
+
+ return data->srv_lookup_status;
+}
+
+static void
+set_srv_data_status(struct srv_data *data, enum srv_lookup_status status)
+{
+ DEBUG(SSSDBG_CONF_SETTINGS, "Marking SRV lookup of service '%s' as '%s'\n",
+ data->meta->service->name, str_srv_data_status(status));
+
+ gettimeofday(&data->last_status_change, NULL);
+ data->srv_lookup_status = status;
+}
+
+/*
+ * This function will return the status of the server. If the status was
+ * last updated a long time ago, we will first reset the status.
+ */
+static enum server_status
+get_server_status(struct fo_server *server)
+{
+ struct timeval tv;
+ time_t timeout;
+
+ if (server->common == NULL)
+ return SERVER_NAME_RESOLVED;
+
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Status of server '%s' is '%s'\n", SERVER_NAME(server),
+ str_server_status(server->common->server_status));
+
+ timeout = server->service->ctx->opts->retry_timeout;
+ gettimeofday(&tv, NULL);
+ if (timeout != 0 && server->common->server_status == SERVER_NOT_WORKING) {
+ if (STATUS_DIFF(server->common, tv) > timeout) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Resetting the server status of '%s'\n",
+ SERVER_NAME(server));
+ server->common->server_status = SERVER_NAME_NOT_RESOLVED;
+ server->common->last_status_change.tv_sec = tv.tv_sec;
+ }
+ }
+
+ if (server->common->rhostent && server->common->rhostent->addr_list[0] &&
+ STATUS_DIFF(server->common, tv) >
+ server->common->rhostent->addr_list[0]->ttl) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Hostname resolution expired, resetting the server "
+ "status of '%s'\n", SERVER_NAME(server));
+ fo_set_server_status(server, SERVER_NAME_NOT_RESOLVED);
+ }
+
+ return server->common->server_status;
+}
+
+/*
+ * This function will return the status of the service. If the status was
+ * last updated a long time ago, we will first reset the status.
+ */
+static enum port_status
+get_port_status(struct fo_server *server)
+{
+ struct timeval tv;
+ time_t timeout;
+
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Port status of port %d for server '%s' is '%s'\n", server->port,
+ SERVER_NAME(server), str_port_status(server->port_status));
+
+ if (server->port_status == PORT_NOT_WORKING) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "SSSD is unable to complete the full "
+ "connection request, this internal status does not necessarily "
+ "indicate network port issues.\n");
+ }
+
+ timeout = server->service->ctx->opts->retry_timeout;
+ if (timeout != 0 && server->port_status == PORT_NOT_WORKING) {
+ gettimeofday(&tv, NULL);
+ if (STATUS_DIFF(server, tv) > timeout) {
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Resetting the status of port %d for server '%s'\n",
+ server->port, SERVER_NAME(server));
+ server->port_status = PORT_NEUTRAL;
+ server->last_status_change.tv_sec = tv.tv_sec;
+ }
+ }
+
+ return server->port_status;
+}
+
+static int
+server_works(struct fo_server *server)
+{
+ if (get_server_status(server) == SERVER_NOT_WORKING)
+ return 0;
+
+ return 1;
+}
+
+static int
+service_works(struct fo_server *server)
+{
+ if (!server_works(server))
+ return 0;
+ if (get_port_status(server) == PORT_NOT_WORKING)
+ return 0;
+
+ return 1;
+}
+
+static int
+service_destructor(struct fo_service *service)
+{
+ DLIST_REMOVE(service->ctx->service_list, service);
+ return 0;
+}
+
+int
+fo_new_service(struct fo_ctx *ctx, const char *name,
+ datacmp_fn user_data_cmp,
+ struct fo_service **_service)
+{
+ struct fo_service *service;
+ int ret;
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Creating new service '%s'\n", name);
+ ret = fo_get_service(ctx, name, &service);
+ if (ret == EOK) {
+ DEBUG(SSSDBG_FUNC_DATA, "Service '%s' already exists\n", name);
+ if (_service) {
+ *_service = service;
+ }
+ return EEXIST;
+ } else if (ret != ENOENT) {
+ return ret;
+ }
+
+ service = talloc_zero(ctx, struct fo_service);
+ if (service == NULL)
+ return ENOMEM;
+
+ service->name = talloc_strdup(service, name);
+ if (service->name == NULL) {
+ talloc_free(service);
+ return ENOMEM;
+ }
+
+ service->user_data_cmp = user_data_cmp;
+
+ service->ctx = ctx;
+ DLIST_ADD(ctx->service_list, service);
+
+ talloc_set_destructor(service, service_destructor);
+ if (_service) {
+ *_service = service;
+ }
+
+ return EOK;
+}
+
+int
+fo_get_service(struct fo_ctx *ctx, const char *name,
+ struct fo_service **_service)
+{
+ struct fo_service *service;
+
+ DLIST_FOR_EACH(service, ctx->service_list) {
+ if (!strcmp(name, service->name)) {
+ *_service = service;
+ return EOK;
+ }
+ }
+
+ return ENOENT;
+}
+
+static int
+get_server_common(TALLOC_CTX *mem_ctx, struct fo_ctx *ctx, const char *name,
+ struct server_common **_common)
+{
+ struct server_common *common;
+
+ DLIST_FOR_EACH(common, ctx->server_common_list) {
+ if (!strcasecmp(name, common->name)) {
+ *_common = rc_reference(mem_ctx, struct server_common, common);
+ if (*_common == NULL)
+ return ENOMEM;
+ return EOK;
+ }
+ }
+
+ return ENOENT;
+}
+
+static int server_common_destructor(void *memptr)
+{
+ struct server_common *common;
+
+ common = talloc_get_type(memptr, struct server_common);
+ if (common->request_list) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "BUG: pending requests still associated with this server\n");
+ return -1;
+ }
+ DLIST_REMOVE(common->ctx->server_common_list, common);
+
+ return 0;
+}
+
+static struct server_common *
+create_server_common(TALLOC_CTX *mem_ctx, struct fo_ctx *ctx, const char *name)
+{
+ struct server_common *common;
+
+ common = rc_alloc(mem_ctx, struct server_common);
+ if (common == NULL) {
+ return NULL;
+ }
+
+ common->name = talloc_strdup(common, name);
+ if (common->name == NULL) {
+ return NULL;
+ }
+
+ common->ctx = ctx;
+ common->prev = NULL;
+ common->next = NULL;
+ common->rhostent = NULL;
+ common->request_list = NULL;
+ common->server_status = DEFAULT_SERVER_STATUS;
+ common->last_status_change.tv_sec = 0;
+ common->last_status_change.tv_usec = 0;
+
+ talloc_set_destructor((TALLOC_CTX *) common, server_common_destructor);
+ DLIST_ADD_END(ctx->server_common_list, common, struct server_common *);
+ return common;
+}
+
+static struct fo_server *
+fo_server_alloc(struct fo_service *service, int port,
+ void *user_data, bool primary)
+{
+ static struct fo_server *server;
+ TALLOC_CTX *server_owner;
+
+ server_owner = talloc_new(service);
+ if (server_owner == NULL) {
+ return NULL;
+ }
+
+ server = rc_alloc(server_owner, struct fo_server);
+ if (server == NULL) {
+ return NULL;
+ }
+
+ server->fo_internal_owner = server_owner;
+
+ server->common = NULL;
+ server->next = NULL;
+ server->prev = NULL;
+ server->srv_data = NULL;
+ server->last_status_change.tv_sec = 0;
+ server->last_status_change.tv_usec = 0;
+
+ server->port = port;
+ server->user_data = user_data;
+ server->service = service;
+ server->port_status = DEFAULT_PORT_STATUS;
+ server->primary = primary;
+
+ return server;
+}
+
+int
+fo_add_srv_server(struct fo_service *service, const char *srv,
+ const char *discovery_domain, const char *sssd_domain,
+ const char *proto, void *user_data)
+{
+ struct fo_server *server;
+
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Adding new SRV server to service '%s' using '%s'.\n",
+ service->name, proto);
+
+ DLIST_FOR_EACH(server, service->server_list) {
+ /* Compare user data only if user_data_cmp and both arguments
+ * are not NULL.
+ */
+ if (server->service->user_data_cmp && user_data && server->user_data) {
+ if (server->service->user_data_cmp(server->user_data, user_data)) {
+ continue;
+ }
+ }
+
+ if (fo_is_srv_lookup(server)) {
+ if (((discovery_domain == NULL &&
+ server->srv_data->dns_domain == NULL) ||
+ (discovery_domain != NULL &&
+ server->srv_data->dns_domain != NULL &&
+ strcasecmp(server->srv_data->dns_domain, discovery_domain) == 0)) &&
+ strcasecmp(server->srv_data->proto, proto) == 0) {
+ return EEXIST;
+ }
+ }
+ }
+
+ /* SRV servers are always primary */
+ server = fo_server_alloc(service, 0, user_data, true);
+ if (server == NULL) {
+ return ENOMEM;
+ }
+
+ /* add the SRV-specific data */
+ server->srv_data = talloc_zero(service, struct srv_data);
+ if (server->srv_data == NULL)
+ return ENOMEM;
+
+ server->srv_data->proto = talloc_strdup(server->srv_data, proto);
+ server->srv_data->srv = talloc_strdup(server->srv_data, srv);
+ if (server->srv_data->proto == NULL ||
+ server->srv_data->srv == NULL)
+ return ENOMEM;
+
+ if (discovery_domain) {
+ server->srv_data->discovery_domain = talloc_strdup(server->srv_data,
+ discovery_domain);
+ if (server->srv_data->discovery_domain == NULL)
+ return ENOMEM;
+ server->srv_data->dns_domain = talloc_strdup(server->srv_data,
+ discovery_domain);
+ if (server->srv_data->dns_domain == NULL)
+ return ENOMEM;
+ }
+
+ server->srv_data->sssd_domain =
+ talloc_strdup(server->srv_data, sssd_domain);
+ if (server->srv_data->sssd_domain == NULL)
+ return ENOMEM;
+
+ server->srv_data->meta = server;
+ server->srv_data->srv_lookup_status = DEFAULT_SRV_STATUS;
+ server->srv_data->last_status_change.tv_sec = 0;
+
+ DLIST_ADD_END(service->server_list, server, struct fo_server *);
+ return EOK;
+}
+
+static struct fo_server *
+create_fo_server(struct fo_service *service, const char *name,
+ int port, void *user_data, bool primary)
+{
+ struct fo_server *server;
+ int ret;
+
+ server = fo_server_alloc(service, port, user_data, primary);
+ if (server == NULL)
+ return NULL;
+
+ server->port = port;
+ server->user_data = user_data;
+ server->service = service;
+ server->port_status = DEFAULT_PORT_STATUS;
+ server->primary = primary;
+
+ if (name != NULL) {
+ ret = get_server_common(server, service->ctx, name, &server->common);
+ if (ret == ENOENT) {
+ server->common = create_server_common(server, service->ctx, name);
+ if (server->common == NULL) {
+ fo_server_free(server);
+ return NULL;
+ }
+ } else if (ret != EOK) {
+ fo_server_free(server);
+ return NULL;
+ }
+ }
+
+ return server;
+}
+
+int
+fo_get_server_count(struct fo_service *service)
+{
+ struct fo_server *server;
+ int count = 0;
+
+ DLIST_FOR_EACH(server, service->server_list) {
+ count++;
+ }
+
+ return count;
+}
+
+static bool fo_server_match(struct fo_server *server,
+ const char *name,
+ int port,
+ void *user_data)
+{
+ if (server->port != port) {
+ return false;
+ }
+
+ /* Compare user data only if user_data_cmp and both arguments
+ * are not NULL.
+ */
+ if (server->service->user_data_cmp && server->user_data && user_data) {
+ if (server->service->user_data_cmp(server->user_data, user_data)) {
+ return false;
+ }
+ }
+
+ if (name == NULL && server->common == NULL) {
+ return true;
+ }
+
+ if (name != NULL &&
+ server->common != NULL && server->common->name != NULL) {
+ if (!strcasecmp(name, server->common->name))
+ return true;
+ }
+
+ return false;
+}
+
+static bool fo_server_cmp(struct fo_server *s1, struct fo_server *s2)
+{
+ char *name = NULL;
+
+ if (s2->common != NULL) {
+ name = s2->common->name;
+ }
+
+ return fo_server_match(s1, name, s2->port, s2->user_data);
+}
+
+static bool fo_server_exists(struct fo_server *list,
+ const char *name,
+ int port,
+ void *user_data)
+{
+ struct fo_server *server = NULL;
+
+ DLIST_FOR_EACH(server, list) {
+ if (fo_server_match(server, name, port, user_data)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static errno_t fo_add_server_to_list(struct fo_server **to_list,
+ struct fo_server *check_list,
+ struct fo_server *server,
+ const char *service_name)
+{
+ const char *debug_name = NULL;
+ const char *name = NULL;
+ bool exists;
+
+ if (server->common == NULL || server->common->name == NULL) {
+ debug_name = "(no name)";
+ name = NULL;
+ } else {
+ debug_name = server->common->name;
+ name = server->common->name;
+ }
+
+ exists = fo_server_exists(check_list, name, server->port,
+ server->user_data);
+
+ if (exists) {
+ DEBUG(SSSDBG_TRACE_FUNC, "Server '%s:%d' for service '%s' "
+ "is already present\n", debug_name, server->port, service_name);
+ return EEXIST;
+ }
+
+ DLIST_ADD_END(*to_list, server, struct fo_server *);
+
+ DEBUG(SSSDBG_TRACE_FUNC, "Inserted %s server '%s:%d' to service "
+ "'%s'\n", (server->primary ? "primary" : "backup"),
+ debug_name, server->port, service_name);
+
+ return EOK;
+}
+
+static errno_t fo_add_server_list(struct fo_service *service,
+ struct fo_server *after_server,
+ struct fo_server_info *servers,
+ size_t num_servers,
+ struct srv_data *srv_data,
+ void *user_data,
+ bool primary,
+ struct fo_server **_last_server)
+{
+ struct fo_server *server = NULL;
+ struct fo_server *last_server = NULL;
+ struct fo_server *srv_list = NULL;
+ size_t i;
+ errno_t ret;
+
+ for (i = 0; i < num_servers; i++) {
+ server = create_fo_server(service, servers[i].host, servers[i].port,
+ user_data, primary);
+ if (server == NULL) {
+ return ENOMEM;
+ }
+
+ server->srv_data = srv_data;
+
+ ret = fo_add_server_to_list(&srv_list, service->server_list,
+ server, service->name);
+ if (ret != EOK) {
+ fo_server_free(server);
+ continue;
+ }
+
+ last_server = server;
+ }
+
+ if (srv_list != NULL) {
+ DLIST_ADD_LIST_AFTER(service->server_list, after_server,
+ srv_list, struct fo_server *);
+ }
+
+ if (_last_server != NULL) {
+ *_last_server = last_server == NULL ? after_server : last_server;
+ }
+
+ return EOK;
+}
+
+int
+fo_add_server(struct fo_service *service, const char *name, int port,
+ void *user_data, bool primary)
+{
+ struct fo_server *server;
+ errno_t ret;
+
+ server = create_fo_server(service, name, port, user_data, primary);
+ if (!server) {
+ return ENOMEM;
+ }
+
+ ret = fo_add_server_to_list(&service->server_list, service->server_list,
+ server, service->name);
+ if (ret != EOK) {
+ fo_server_free(server);
+ }
+
+ return ret;
+}
+
+void fo_ref_server(TALLOC_CTX *ref_ctx,
+ struct fo_server *server)
+{
+ if (server) {
+ server = rc_reference(ref_ctx, struct fo_server, server);
+ }
+}
+
+static int
+get_first_server_entity(struct fo_service *service, struct fo_server **_server)
+{
+ struct fo_server *server;
+
+ /* If we already have a working server, use that one. */
+ server = service->active_server;
+ if (server != NULL) {
+ if (service_works(server) && fo_is_server_primary(server)) {
+ goto done;
+ }
+ service->active_server = NULL;
+ }
+
+ /*
+ * Otherwise iterate through the server list.
+ */
+
+ /* First, try primary servers after the last one we tried.
+ * (only if the last one was primary as well)
+ */
+ if (service->last_tried_server != NULL &&
+ service->last_tried_server->primary) {
+ if (service->last_tried_server->port_status == PORT_NEUTRAL &&
+ server_works(service->last_tried_server)) {
+ server = service->last_tried_server;
+ goto done;
+ }
+
+ DLIST_FOR_EACH(server, service->last_tried_server->next) {
+ /* Go only through primary servers */
+ if (!server->primary) continue;
+
+ if (service_works(server)) {
+ goto done;
+ }
+ }
+ }
+
+ /* If none were found, try at the start, primary first */
+ DLIST_FOR_EACH(server, service->server_list) {
+ /* First iterate only over primary servers */
+ if (!server->primary) continue;
+
+ if (service_works(server)) {
+ goto done;
+ }
+ if (server == service->last_tried_server) {
+ break;
+ }
+ }
+
+ DLIST_FOR_EACH(server, service->server_list) {
+ /* Now iterate only over backup servers */
+ if (server->primary) continue;
+
+ if (service_works(server)) {
+ goto done;
+ }
+ }
+
+ service->last_tried_server = NULL;
+ return ENOENT;
+
+done:
+ service->last_tried_server = server;
+ *_server = server;
+ return EOK;
+}
+
+static int
+resolve_service_request_destructor(struct resolve_service_request *request)
+{
+ DLIST_REMOVE(request->server_common->request_list, request);
+ return 0;
+}
+
+static int
+set_lookup_hook(struct tevent_context *ev,
+ struct fo_server *server,
+ struct tevent_req *req)
+{
+ struct resolve_service_request *request;
+
+ request = talloc(req, struct resolve_service_request);
+ if (request == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "No memory\n");
+ talloc_free(request);
+ return ENOMEM;
+ }
+ request->server_common = rc_reference(request, struct server_common,
+ server->common);
+ if (request->server_common == NULL) {
+ talloc_free(request);
+ return ENOMEM;
+ }
+ request->ev = ev;
+ request->req = req;
+ DLIST_ADD(server->common->request_list, request);
+ talloc_set_destructor(request, resolve_service_request_destructor);
+
+ return EOK;
+}
+
+
+
+/*******************************************************************
+ * Get server to connect to. *
+ *******************************************************************/
+
+struct resolve_service_state {
+ struct fo_server *server;
+
+ struct resolv_ctx *resolv;
+ struct tevent_context *ev;
+ struct tevent_timer *timeout_handler;
+ struct fo_ctx *fo_ctx;
+};
+
+static errno_t fo_resolve_service_activate_timeout(struct tevent_req *req,
+ struct tevent_context *ev, const unsigned long timeout_seconds);
+static void fo_resolve_service_cont(struct tevent_req *subreq);
+static void fo_resolve_service_done(struct tevent_req *subreq);
+static bool fo_resolve_service_server(struct tevent_req *req);
+
+/* Forward declarations for SRV resolving */
+static struct tevent_req *
+resolve_srv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *resolv, struct fo_ctx *ctx,
+ struct fo_server *server);
+static int
+resolve_srv_recv(struct tevent_req *req, struct fo_server **server);
+
+struct tevent_req *
+fo_resolve_service_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *resolv, struct fo_ctx *ctx,
+ struct fo_service *service)
+{
+ int ret;
+ struct fo_server *server;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct resolve_service_state *state;
+
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Trying to resolve service '%s'\n", service->name);
+ req = tevent_req_create(mem_ctx, &state, struct resolve_service_state);
+ if (req == NULL)
+ return NULL;
+
+ state->resolv = resolv;
+ state->ev = ev;
+ state->fo_ctx = ctx;
+
+ ret = get_first_server_entity(service, &server);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "No available servers for service '%s'\n", service->name);
+ goto done;
+ }
+
+ /* Activate per-service timeout handler */
+ ret = fo_resolve_service_activate_timeout(req, ev,
+ ctx->opts->service_resolv_timeout);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Could not set service timeout [dns_resolver_timeout]\n");
+ goto done;
+ }
+
+ if (fo_is_srv_lookup(server)) {
+ /* Don't know the server yet, must do a SRV lookup */
+ subreq = resolve_srv_send(state, ev, resolv,
+ ctx, server);
+ if (subreq == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq,
+ fo_resolve_service_cont,
+ req);
+ return req;
+ }
+
+ /* This is a regular server, just do hostname lookup */
+ state->server = server;
+ if (fo_resolve_service_server(req)) {
+ tevent_req_post(req, ev);
+ }
+
+ ret = EOK;
+done:
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void set_server_common_status(struct server_common *common,
+ enum server_status status);
+
+static void
+fo_resolve_service_timeout(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv, void *pvt)
+{
+ struct tevent_req *req = talloc_get_type(pvt, struct tevent_req);
+
+ DEBUG(SSSDBG_MINOR_FAILURE, "Service resolving timeout reached\n");
+ tevent_req_error(req, ETIMEDOUT);
+}
+
+static errno_t
+fo_resolve_service_activate_timeout(struct tevent_req *req,
+ struct tevent_context *ev,
+ const unsigned long timeout_seconds)
+{
+ struct timeval tv;
+ struct resolve_service_state *state = tevent_req_data(req,
+ struct resolve_service_state);
+
+ tv = tevent_timeval_current();
+ tv = tevent_timeval_add(&tv, timeout_seconds, 0);
+ state->timeout_handler = tevent_add_timer(ev, state, tv,
+ fo_resolve_service_timeout, req);
+ if (state->timeout_handler == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n");
+ return ENOMEM;
+ }
+
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Resolve timeout [dns_resolver_timeout] set to %lu seconds\n",
+ timeout_seconds);
+ return EOK;
+}
+
+/* SRV resolving finished, see if we got server to work with */
+static void
+fo_resolve_service_cont(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct resolve_service_state *state = tevent_req_data(req,
+ struct resolve_service_state);
+ int ret;
+
+ ret = resolve_srv_recv(subreq, &state->server);
+ talloc_zfree(subreq);
+
+ /* We will proceed normally on ERR_SRV_DUPLICATES and if the server
+ * is already being resolved, we hook to that request. */
+ if (ret != EOK && ret != ERR_SRV_DUPLICATES) {
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ fo_resolve_service_server(req);
+}
+
+static bool
+fo_resolve_service_server(struct tevent_req *req)
+{
+ struct resolve_service_state *state = tevent_req_data(req,
+ struct resolve_service_state);
+ struct tevent_req *subreq;
+ int ret;
+
+ switch (get_server_status(state->server)) {
+ case SERVER_NAME_NOT_RESOLVED: /* Request name resolution. */
+ subreq = resolv_gethostbyname_send(state->server->common,
+ state->ev, state->resolv,
+ state->server->common->name,
+ state->fo_ctx->opts->family_order,
+ default_host_dbs);
+ if (subreq == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return true;
+ }
+ tevent_req_set_callback(subreq, fo_resolve_service_done,
+ state->server->common);
+ fo_set_server_status(state->server, SERVER_RESOLVING_NAME);
+ /* FALLTHROUGH */
+ SSS_ATTRIBUTE_FALLTHROUGH;
+ case SERVER_RESOLVING_NAME:
+ /* Name resolution is already under way. Just add ourselves into the
+ * waiting queue so we get notified after the operation is finished. */
+ ret = set_lookup_hook(state->ev, state->server, req);
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ return true;
+ }
+ break;
+ default: /* The name is already resolved. Return immediately. */
+ tevent_req_done(req);
+ return true;
+ }
+
+ return false;
+}
+
+static void
+fo_resolve_service_done(struct tevent_req *subreq)
+{
+ struct server_common *common = tevent_req_callback_data(subreq,
+ struct server_common);
+ int resolv_status;
+ struct resolve_service_request *request;
+ int ret;
+
+ if (common->rhostent != NULL) {
+ talloc_zfree(common->rhostent);
+ }
+
+ ret = resolv_gethostbyname_recv(subreq, common,
+ &resolv_status, NULL,
+ &common->rhostent);
+ talloc_zfree(subreq);
+ if (ret != EOK) {
+ if (resolv_status == ARES_EFILE) {
+ /* resolv_strerror(resolv_status) provided msg from c-ares lib.
+ * c-ares lib in most distros will default to /etc/hosts for
+ * file based host resolving */
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to resolve server '%s': %s [%s]\n",
+ common->name,
+ resolv_strerror(resolv_status),
+ _PATH_HOSTS);
+ } else {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Failed to resolve server '%s': %s\n",
+ common->name,
+ resolv_strerror(resolv_status));
+ }
+ /* If the resolver failed to resolve a hostname but did not
+ * encounter an error, tell the caller to retry another server.
+ *
+ * If there are no more servers to try, the next request would
+ * just shortcut with ENOENT.
+ */
+ if (ret == ENOENT) {
+ ret = EAGAIN;
+ }
+ set_server_common_status(common, SERVER_NOT_WORKING);
+ } else {
+ set_server_common_status(common, SERVER_NAME_RESOLVED);
+ }
+
+ /* Take care of all requests for this server. */
+ while ((request = common->request_list) != NULL) {
+ DLIST_REMOVE(common->request_list, request);
+
+ /* If the request callback decresed refcount on the returned
+ * server, we would have crashed as common would not be valid
+ * anymore. Rather schedule the notify for next tev iteration
+ */
+ tevent_req_defer_callback(request->req, request->ev);
+
+ if (ret) {
+ tevent_req_error(request->req, ret);
+ } else {
+ tevent_req_done(request->req);
+ }
+ }
+}
+
+int
+fo_resolve_service_recv(struct tevent_req *req,
+ TALLOC_CTX *ref_ctx,
+ struct fo_server **server)
+{
+ struct resolve_service_state *state;
+
+ state = tevent_req_data(req, struct resolve_service_state);
+
+ /* always return the server if asked for, otherwise the caller
+ * cannot mark it as faulty in case we return an error */
+ if (server != NULL) {
+ fo_ref_server(ref_ctx, state->server);
+ *server = state->server;
+ }
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/*******************************************************************
+ * Resolve the server to connect to using a SRV query. *
+ *******************************************************************/
+
+static void resolve_srv_done(struct tevent_req *subreq);
+
+struct resolve_srv_state {
+ struct fo_server *meta;
+ struct fo_service *service;
+
+ struct fo_server *out;
+
+ struct resolv_ctx *resolv;
+ struct tevent_context *ev;
+ struct fo_ctx *fo_ctx;
+};
+
+static struct tevent_req *
+resolve_srv_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct resolv_ctx *resolv, struct fo_ctx *ctx,
+ struct fo_server *server)
+{
+ int ret;
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct resolve_srv_state *state;
+ int status;
+
+ req = tevent_req_create(mem_ctx, &state, struct resolve_srv_state);
+ if (req == NULL)
+ return NULL;
+
+ state->service = server->service;
+ state->ev = ev;
+ state->resolv = resolv;
+ state->fo_ctx = ctx;
+ state->meta = server->srv_data->meta;
+
+ status = get_srv_data_status(server->srv_data);
+ DEBUG(SSSDBG_FUNC_DATA, "The status of SRV lookup is %s\n",
+ str_srv_data_status(status));
+ switch(status) {
+ case SRV_EXPIRED: /* Need a refresh */
+ state->meta = collapse_srv_lookup(&server);
+ /* FALLTHROUGH.
+ * "server" might be invalid now if the SRV
+ * query collapsed
+ * */
+ SSS_ATTRIBUTE_FALLTHROUGH;
+ case SRV_NEUTRAL: /* Request SRV lookup */
+ if (server != NULL && server != state->meta) {
+ /* A server created by expansion of meta server was marked as
+ * neutral. We have to collapse the servers and issue new
+ * SRV resolution. */
+ state->meta = collapse_srv_lookup(&server);
+ }
+
+ if (ctx->srv_send_fn == NULL || ctx->srv_recv_fn == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "No SRV lookup plugin is set\n");
+ ret = ENOTSUP;
+ goto done;
+ }
+
+ subreq = ctx->srv_send_fn(state, ev,
+ state->meta->srv_data->srv,
+ state->meta->srv_data->proto,
+ state->meta->srv_data->discovery_domain,
+ ctx->srv_pvt);
+ if (subreq == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ tevent_req_set_callback(subreq, resolve_srv_done, req);
+ break;
+ case SRV_RESOLVE_ERROR: /* query could not be resolved but don't retry yet */
+ ret = EIO;
+ state->out = server;
+
+ /* The port status was reseted to neutral but we still haven't reached
+ * timeout to try to resolve SRV record again. We will set the port
+ * status back to not working. */
+ fo_set_port_status(state->meta, PORT_NOT_WORKING);
+ goto done;
+ case SRV_RESOLVED: /* The query is resolved and valid. Return. */
+ state->out = server;
+ tevent_req_done(req);
+ tevent_req_post(req, state->ev);
+ return req;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Unexpected status %d for a SRV server\n", status);
+ ret = EIO;
+ goto done;
+ }
+
+ ret = EOK;
+done:
+ if (ret != EOK) {
+ tevent_req_error(req, ret);
+ tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void
+resolve_srv_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct resolve_srv_state *state = tevent_req_data(req,
+ struct resolve_srv_state);
+ struct fo_server *last_server = NULL;
+ struct fo_server_info *primary_servers = NULL;
+ struct fo_server_info *backup_servers = NULL;
+ size_t num_primary_servers = 0;
+ size_t num_backup_servers = 0;
+ char *dns_domain = NULL;
+ int ret;
+ uint32_t ttl;
+
+ ret = state->fo_ctx->srv_recv_fn(state, subreq, &dns_domain, &ttl,
+ &primary_servers, &num_primary_servers,
+ &backup_servers, &num_backup_servers);
+ talloc_free(subreq);
+ switch (ret) {
+ case EOK:
+ if ((num_primary_servers == 0 || primary_servers == NULL)
+ && (num_backup_servers == 0 || backup_servers == NULL)) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "SRV lookup plugin returned EOK but "
+ "no servers\n");
+ ret = EFAULT;
+ goto done;
+ }
+
+ state->meta->srv_data->ttl = ttl;
+ talloc_zfree(state->meta->srv_data->dns_domain);
+ state->meta->srv_data->dns_domain = talloc_steal(state->meta->srv_data,
+ dns_domain);
+
+ last_server = state->meta;
+
+ if (primary_servers != NULL) {
+ ret = fo_add_server_list(state->service, last_server,
+ primary_servers, num_primary_servers,
+ state->meta->srv_data,
+ state->meta->user_data,
+ true, &last_server);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+ if (backup_servers != NULL) {
+ ret = fo_add_server_list(state->service, last_server,
+ backup_servers, num_backup_servers,
+ state->meta->srv_data,
+ state->meta->user_data,
+ false, &last_server);
+ if (ret != EOK) {
+ goto done;
+ }
+ }
+
+ if (last_server == state->meta) {
+ /* SRV lookup returned only those servers that are already present.
+ * This may happen only when an ongoing SRV resolution already
+ * exist. We will return server, but won't set any state. */
+ DEBUG(SSSDBG_TRACE_FUNC, "SRV lookup did not return "
+ "any new server.\n");
+ ret = ERR_SRV_DUPLICATES;
+
+ /* Since no new server is returned, state->meta->next is NULL.
+ * We return last tried server if possible which is server
+ * from previous resolution of SRV record, and first server
+ * otherwise. */
+ if (state->service->last_tried_server != NULL) {
+ state->out = state->service->last_tried_server;
+ goto done;
+ }
+
+ state->out = state->service->server_list;
+ goto done;
+ }
+
+ /* At least one new server was inserted.
+ * We will return the first new server. */
+ if (state->meta->next == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "BUG: state->meta->next is NULL\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ state->out = state->meta->next;
+
+ /* And remove meta server from the server list. It will be
+ * inserted again during srv collapse. */
+ DLIST_REMOVE(state->service->server_list, state->meta);
+ if (state->service->last_tried_server == state->meta) {
+ state->service->last_tried_server = state->out;
+ }
+
+ set_srv_data_status(state->meta->srv_data, SRV_RESOLVED);
+ ret = EOK;
+ break;
+ case ERR_SRV_NOT_FOUND:
+ /* fall through */
+ SSS_ATTRIBUTE_FALLTHROUGH;
+ case ERR_SRV_LOOKUP_ERROR:
+ fo_set_port_status(state->meta, PORT_NOT_WORKING);
+ /* fall through */
+ SSS_ATTRIBUTE_FALLTHROUGH;
+ default:
+ DEBUG(SSSDBG_OP_FAILURE, "Unable to resolve SRV [%d]: %s\n",
+ ret, sss_strerror(ret));
+ }
+
+done:
+ if (ret == ERR_SRV_DUPLICATES) {
+ tevent_req_error(req, ret);
+ return;
+ } else if (ret != EOK) {
+ state->out = state->meta;
+ set_srv_data_status(state->meta->srv_data, SRV_RESOLVE_ERROR);
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int
+resolve_srv_recv(struct tevent_req *req, struct fo_server **server)
+{
+ struct resolve_srv_state *state = tevent_req_data(req,
+ struct resolve_srv_state);
+
+ /* always return the server if asked for, otherwise the caller
+ * cannot mark it as faulty in case we return an error */
+ if (server) {
+ *server = state->out;
+ }
+
+ TEVENT_REQ_RETURN_ON_ERROR(req);
+
+ return EOK;
+}
+
+/*******************************************************************
+ * Get Fully Qualified Domain Name of the host machine *
+ *******************************************************************/
+static void
+set_server_common_status(struct server_common *common,
+ enum server_status status)
+{
+ DEBUG(SSSDBG_CONF_SETTINGS, "Marking server '%s' as '%s'\n", common->name,
+ str_server_status(status));
+
+ common->server_status = status;
+ gettimeofday(&common->last_status_change, NULL);
+}
+
+void
+fo_set_server_status(struct fo_server *server, enum server_status status)
+{
+ if (server->common == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Bug: Trying to set server status of a name-less server\n");
+ return;
+ }
+
+ set_server_common_status(server->common, status);
+}
+
+void
+fo_set_port_status(struct fo_server *server, enum port_status status)
+{
+ struct fo_server *siter;
+
+ DEBUG(SSSDBG_CONF_SETTINGS,
+ "Marking port %d of server '%s' as '%s'\n", server->port,
+ SERVER_NAME(server), str_port_status(status));
+
+ server->port_status = status;
+ gettimeofday(&server->last_status_change, NULL);
+ if (status == PORT_WORKING) {
+ fo_set_server_status(server, SERVER_WORKING);
+ server->service->active_server = server;
+ }
+
+ if (!server->common || !server->common->name) return;
+
+ /* It is possible to introduce duplicates when expanding SRV results
+ * into fo_server structures. Find the duplicates and set the same
+ * status */
+ DLIST_FOR_EACH(siter, server->service->server_list) {
+ if (fo_server_cmp(siter, server)) {
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "Marking port %d of duplicate server '%s' as '%s'\n",
+ siter->port, SERVER_NAME(siter),
+ str_port_status(status));
+ siter->port_status = status;
+ gettimeofday(&siter->last_status_change, NULL);
+ }
+ }
+}
+
+struct fo_server *fo_get_active_server(struct fo_service *service)
+{
+ return service->active_server;
+}
+
+void fo_try_next_server(struct fo_service *service)
+{
+ struct fo_server *server;
+
+ if (!service) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Bug: No service supplied\n");
+ return;
+ }
+
+ server = service->active_server;
+ if (!server) {
+ return;
+ }
+
+ service->active_server = 0;
+
+ if (server->port_status == PORT_WORKING) {
+ server->port_status = PORT_NOT_WORKING;
+ }
+}
+
+void *
+fo_get_server_user_data(struct fo_server *server)
+{
+ return server->user_data;
+}
+
+int
+fo_get_server_port(struct fo_server *server)
+{
+ return server->port;
+}
+
+const char *
+fo_get_server_name(struct fo_server *server)
+{
+ if (!server->common) {
+ return NULL;
+ }
+ return server->common->name;
+}
+
+const char *fo_get_server_str_name(struct fo_server *server)
+{
+ if (!server->common) {
+ if (fo_is_srv_lookup(server)) {
+ return "SRV lookup meta-server";
+ }
+ return "unknown name";
+ }
+
+ return server->common->name;
+}
+
+struct resolv_hostent *
+fo_get_server_hostent(struct fo_server *server)
+{
+ if (server->common == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Bug: Trying to get hostent from a name-less server\n");
+ return NULL;
+ }
+
+ return server->common->rhostent;
+}
+
+bool
+fo_is_server_primary(struct fo_server *server)
+{
+ return server->primary;
+}
+
+time_t
+fo_get_server_hostname_last_change(struct fo_server *server)
+{
+ if (server->common == NULL) {
+ return 0;
+ }
+ return server->common->last_status_change.tv_sec;
+}
+
+struct fo_server *fo_server_first(struct fo_server *server)
+{
+ if (!server) return NULL;
+
+ while (server->prev) { server = server->prev; }
+ return server;
+}
+
+struct fo_server *fo_server_next(struct fo_server *server)
+{
+ if (!server) return NULL;
+
+ return server->next;
+}
+
+size_t fo_server_count(struct fo_server *server)
+{
+ struct fo_server *item = fo_server_first(server);
+ size_t size = 0;
+
+ while (item) {
+ ++size;
+ item = item->next;
+ }
+ return size;
+}
+
+time_t fo_get_service_retry_timeout(struct fo_service *svc)
+{
+ if (svc == NULL || svc->ctx == NULL || svc->ctx->opts == NULL) {
+ return 0;
+ }
+
+ return svc->ctx->opts->retry_timeout;
+}
+
+bool fo_get_use_search_list(struct fo_server *server)
+{
+ if (
+ server == NULL ||
+ server->service == NULL ||
+ server->service->ctx == NULL ||
+ server->service->ctx->opts == NULL
+ ) {
+ return true;
+ }
+
+ return server->service->ctx->opts->use_search_list;
+}
+
+
+void fo_reset_servers(struct fo_service *service)
+{
+ struct fo_server *server;
+
+ DLIST_FOR_EACH(server, service->server_list) {
+ if (server->srv_data != NULL) {
+ set_srv_data_status(server->srv_data, SRV_NEUTRAL);
+ }
+
+ if (server->common) {
+ fo_set_server_status(server, SERVER_NAME_NOT_RESOLVED);
+ }
+
+ fo_set_port_status(server, PORT_NEUTRAL);
+ }
+}
+
+
+void fo_reset_services(struct fo_ctx *fo_ctx)
+{
+ struct fo_service *service;
+
+ DEBUG(SSSDBG_TRACE_LIBS,
+ "Resetting all servers in all services\n");
+
+ DLIST_FOR_EACH(service, fo_ctx->service_list) {
+ fo_reset_servers(service);
+ }
+}
+
+bool fo_svc_has_server(struct fo_service *service, struct fo_server *server)
+{
+ struct fo_server *srv;
+
+ DLIST_FOR_EACH(srv, service->server_list) {
+ if (srv == server) return true;
+ }
+
+ return false;
+}
+
+const char **fo_svc_server_list(TALLOC_CTX *mem_ctx,
+ struct fo_service *service,
+ size_t *_count)
+{
+ const char **list;
+ const char *server;
+ struct fo_server *srv;
+ size_t count;
+
+ count = 0;
+ DLIST_FOR_EACH(srv, service->server_list) {
+ count++;
+ }
+
+ list = talloc_zero_array(mem_ctx, const char *, count + 1);
+ if (list == NULL) {
+ return NULL;
+ }
+
+ count = 0;
+ DLIST_FOR_EACH(srv, service->server_list) {
+ server = fo_get_server_name(srv);
+ if (server == NULL) {
+ /* _srv_ */
+ continue;
+ }
+
+ list[count] = talloc_strdup(list, server);
+ if (list[count] == NULL) {
+ talloc_free(list);
+ return NULL;
+ }
+ count++;
+ }
+
+ if (_count != NULL) {
+ *_count = count;
+ }
+
+ return list;
+}
+
+bool fo_set_srv_lookup_plugin(struct fo_ctx *ctx,
+ fo_srv_lookup_plugin_send_t send_fn,
+ fo_srv_lookup_plugin_recv_t recv_fn,
+ void *pvt)
+{
+ if (ctx == NULL || send_fn == NULL || recv_fn == NULL) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Invalid parameters\n");
+ return false;
+ }
+
+ if (ctx->srv_send_fn != NULL || ctx->srv_recv_fn != NULL) {
+ DEBUG(SSSDBG_MINOR_FAILURE, "SRV lookup plugin is already set\n");
+ return false;
+ }
+
+ ctx->srv_send_fn = send_fn;
+ ctx->srv_recv_fn = recv_fn;
+ ctx->srv_pvt = talloc_steal(ctx, pvt);
+
+ return true;
+}