summaryrefslogtreecommitdiff
path: root/tacplus-daemon/tacplus_srv_conn.c
diff options
context:
space:
mode:
Diffstat (limited to 'tacplus-daemon/tacplus_srv_conn.c')
-rw-r--r--tacplus-daemon/tacplus_srv_conn.c595
1 files changed, 595 insertions, 0 deletions
diff --git a/tacplus-daemon/tacplus_srv_conn.c b/tacplus-daemon/tacplus_srv_conn.c
new file mode 100644
index 0000000..6da9f48
--- /dev/null
+++ b/tacplus-daemon/tacplus_srv_conn.c
@@ -0,0 +1,595 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "global.h"
+#include "tacplus_srv_conn.h"
+#include "dbus_service.h"
+#include "statistics.h"
+#include "parser.h"
+
+
+
+static bool
+tacplus_server_remaining_hold_down_at(const struct tacplus_options_server *server,
+ const struct timespec *cur_time,
+ struct timespec *remaining)
+{
+ struct timespec expires;
+
+ if (remaining)
+ SET_TIMESPEC_VALS(*remaining, 0, 0);
+
+ /* Hold down disabled */
+ if (server->hold_down == 0)
+ return false;
+
+ /* No trouble seen */
+ if (TIMESPEC_VALS_EQ(server->state.lastTrouble, -1, -1))
+ return false;
+
+ /* Clock shift - not much to do other than expire the timer */
+ if (timespec_cmp(cur_time, &server->state.lastTrouble) < 0)
+ return false;
+
+ expires = server->state.lastTrouble;
+ expires.tv_sec += server->hold_down;
+
+ if (timespec_cmp(cur_time, &expires) >= 0)
+ return false;
+
+ if (remaining)
+ timespec_sub(&expires, cur_time, remaining);
+
+ return true;
+}
+
+bool
+tacplus_server_remaining_hold_down(const struct tacplus_options_server *server,
+ struct timespec *remaining)
+{
+ struct timespec cur_time;
+
+ cur_mono_time(&cur_time);
+ return tacplus_server_remaining_hold_down_at(server, &cur_time, remaining);
+}
+
+time_t
+tacplus_server_remaining_hold_down_secs(const struct tacplus_options_server *server)
+{
+ struct timespec remaining;
+
+ tacplus_server_remaining_hold_down(server, &remaining);
+
+ /* Round to nearest second */
+ if (remaining.tv_nsec >= SEC_TO_NSECS/2)
+ remaining.tv_sec++;
+
+ return remaining.tv_sec;
+}
+
+bool
+tacplus_server_is_held_down(const struct tacplus_options_server *server)
+{
+ return tacplus_server_remaining_hold_down(server, NULL);
+}
+
+void
+tacplus_server_activate_hold_down(struct tacplus_options_server *server)
+{
+ if (server->hold_down) {
+ char *addr_str = addrinfo_to_string(server->addrs);
+
+ syslog(LOG_DEBUG, "Hold down timer started on %s for %us",
+ strOrNil(addr_str), server->hold_down);
+ free(addr_str);
+ }
+
+ cur_mono_time(&server->state.lastTrouble);
+}
+
+void
+tacplus_server_reset_hold_down(struct tacplus_options_server *server)
+{
+ SET_TIMESPEC_VALS(server->state.lastTrouble, -1, -1);
+}
+
+static
+bool tacplus_connect_ith(
+#ifdef HAVE_LIBTAC_EVENT
+ struct tac_session *sess,
+#endif
+ unsigned i)
+{
+ struct tacplus_options *opts = connControl->opts;
+ struct tacplus_options_server *server = tacplus_server(opts, i);
+ int timeout;
+ char *dest_addr_str = NULL;
+ char *src_addr_str = NULL;
+ struct addrinfo *addr, *src_addr = NULL;
+ bool success = false;
+
+ addr = server->addrs;
+
+ dest_addr_str = addrinfo_to_string(addr);
+
+ if (server->src_addrs)
+ src_addr = server->src_addrs;
+ else if (server->src_intf) {
+ int if_up = is_interface_up(server->src_intf);
+ switch (if_up) {
+ case 1:
+ break;
+ case 0:
+ syslog(LOG_DEBUG, "Source interface %s is not up",
+ server->src_intf);
+ /* fall through */
+ default:
+ goto fail;
+ }
+
+ src_addr = get_interface_addrinfo(server->src_intf, addr->ai_family);
+ if (! src_addr)
+ goto fail;
+ }
+
+#ifdef HAVE_LIBTAC_EVENT
+ timeout = opts->setupTimeout;
+#else
+ timeout = server->timeout;
+#endif
+
+ syslog(LOG_DEBUG, "Opening TCP connection to [%u] %s:%d "
+ "using timeout of %d second(s)",
+ i, strOrNil(dest_addr_str), get_addrinfo_port(addr),
+ timeout);
+
+ if (src_addr) {
+ src_addr_str = addrinfo_to_string(src_addr);
+ syslog(LOG_DEBUG, "Using source address: %s", strOrNil(src_addr_str));
+ free(src_addr_str);
+ src_addr_str = NULL;
+ }
+
+#ifdef HAVE_LIBTAC_EVENT
+ success = tac_connect_single_ev(sess, connControl->tac_event,
+ server->addrs, src_addr, timeout);
+#else
+ int fd = tac_connect_single(addr, server->secret, src_addr, timeout);
+ success = fd > 0 ? true : false;
+
+ server->fd = success ? fd : -1;
+#endif
+
+ if (server->src_intf)
+ free_interface_addrinfo(&src_addr);
+
+ if (!success) {
+fail:
+ syslog(LOG_DEBUG, "Failed to connect to %s",
+ strOrNil(dest_addr_str));
+ inc_failed_connects(i);
+
+ tacplus_server_activate_hold_down(tacplus_server(opts, i));
+
+ free(dest_addr_str);
+ dest_addr_str = NULL;
+ return false;
+ }
+
+#ifdef HAVE_LIBTAC_EVENT
+ tac_session_set_oob(sess, oob_callback);
+ tac_session_set_response(sess, response_callback);
+
+ /* In all releases so far we used "login' as authentication type. */
+ /* TODO: consider to make this configurable in future */
+ tac_session_set_authen_type(sess, TAC_PLUS_AUTHEN_TYPE_ASCII);
+
+ tac_session_set_secret(sess, server->secret);
+ tac_session_set_timeout(sess, server->timeout);
+
+ /* need to store index of opts->server[] into tac_session */
+ struct tac_session_extra *extra = tac_session_get_user_data(sess);
+ extra->server_id = i;
+ extra->server = server;
+ extra->state = UNINITIALIZED;
+
+ server->session = sess;
+#endif
+
+ syslog(LOG_DEBUG, "TCP connection to %s successfully opened",
+ strOrNil(dest_addr_str));
+ free(dest_addr_str);
+
+ return true;
+}
+
+static inline void
+tacplus_set_active_server(struct tacplus_options *opts,
+ const struct tacplus_options_server *server)
+{
+ opts->curr_server = server->id;
+}
+
+static inline void
+tacplus_clear_active_server(struct tacplus_options *opts)
+{
+ opts->curr_server = INVALID_SERVER_ID;
+}
+
+static inline bool
+tacplus_have_active_server(const struct tacplus_options *opts)
+{
+ return opts->curr_server < opts->n_servers ? true : false;
+}
+
+static inline void
+tacplus_set_next_server(struct tacplus_options *opts,
+ const struct tacplus_options_server *server)
+{
+ opts->next_server = server->id;
+}
+
+static inline void
+tacplus_clear_next_server(struct tacplus_options *opts)
+{
+ opts->next_server = INVALID_SERVER_ID;
+}
+
+static inline bool
+tacplus_have_next_server(const struct tacplus_options *opts)
+{
+ return opts->next_server < opts->n_servers ? true : false;
+}
+
+/*
+ * This is a somewhat limited use function, designed for initialising an
+ * iteration counter used for looping over the server list - see tacplus_connect()
+ */
+static unsigned
+tacplus_get_server_list_pos(struct tacplus_options *opts)
+{
+ /*
+ * If the next expiring, and higher priority, server's hold down timer has
+ * expired then start from the beginning of the server list. This allows us
+ * to choose the highest priority server which is available and updates
+ * next_server if necessary (during the connection loop routine).
+ */
+ if (tacplus_have_next_server(opts) &&
+ (! tacplus_server_is_held_down(tacplus_server(opts, opts->next_server)))) {
+ tacplus_clear_next_server(opts);
+ return HIGHEST_PRIO_SERVER_ID;
+ }
+ /*
+ * Otherwise if there is no active server then all servers are held down
+ * and there is no need to iterate the server list.
+ *
+ * In this condition there *should* always be a next_server set, if there
+ * isn't then we simply iterate from the highest priority server.
+ */
+ else if (! tacplus_have_active_server(opts)) {
+ if (! tacplus_have_next_server(opts)) {
+ syslog(LOG_DEBUG, "No active server and no next server");
+ return HIGHEST_PRIO_SERVER_ID;
+ }
+
+ return INVALID_SERVER_ID;
+ }
+
+ /*
+ * Otherwise if there isn't a higher priority server whose hold down timer
+ * has expired then start iterating the server list from the last used
+ * (active) server.
+ */
+ return opts->curr_server;
+}
+
+static void
+tacplus_update_next_server(struct tacplus_options *opts,
+ const struct tacplus_options_server *server)
+{
+ struct timespec cur_time, remaining, next_remaining;
+
+ if (! tacplus_have_next_server(opts)) {
+ tacplus_set_next_server(opts, server);
+ return;
+ }
+
+ cur_mono_time(&cur_time);
+ tacplus_server_remaining_hold_down_at(server, &cur_time, &remaining);
+ tacplus_server_remaining_hold_down_at(tacplus_server(opts, opts->next_server),
+ &cur_time, &next_remaining);
+
+ if (timespec_cmp(&remaining, &next_remaining) < 0)
+ tacplus_set_next_server(opts, server);
+}
+
+#ifdef HAVE_LIBTAC_EVENT
+unsigned tacplus_connect_all(void)
+{
+ struct tacplus_options *opts = connControl->opts;
+ unsigned tries, i, successes;
+ struct tac_session *sess;
+
+ syslog(LOG_DEBUG, "Number of servers in config: %d", opts->n_servers);
+
+ sess = tac_session_alloc_extra(sizeof(struct tac_session_extra));
+
+ for (successes = tries = 0, i = opts->curr_server; tries < opts->n_servers; tries++) {
+ i = (i + 1) % opts->n_servers;
+
+ if (opts->server[i].session)
+ continue;
+
+ if (! tacplus_server_is_held_down(tacplus_server(opts, i)) &&
+ tacplus_connect_ith(sess, i)) {
+ successes++;
+ sess = tac_session_alloc_extra(sizeof(struct tac_session_extra));
+ }
+ }
+
+ /* the last one will always be unused */
+ tac_session_free(sess);
+
+ return successes;
+}
+#endif
+
+static bool
+go_offline_until_next_hold_down_expiry(struct tacplus_options *opts)
+{
+ struct itimerspec it = {};
+ struct timespec *ts = &it.it_value;
+
+ if (! tacplus_have_next_server(opts)) {
+ syslog(LOG_DEBUG, "Cannot go offline (no next server)");
+ return false;
+ }
+
+ tacplus_server_remaining_hold_down(tacplus_server(opts, opts->next_server), ts);
+ syslog(LOG_DEBUG, "Setting offline timer for %lis %lins", ts->tv_sec, ts->tv_nsec);
+
+ return tacplusd_go_offline(ts);
+}
+
+bool tacplus_connect(void)
+{
+ static bool last_connect_failed;
+ struct tacplus_options *opts = connControl->opts;
+ bool all_servers_held_down = true;
+ unsigned i;
+
+ syslog(LOG_DEBUG, "Number of servers in config: %d", opts->n_servers);
+
+ /* If we are offline there is no point continuing */
+ if (! tacplusd_online()) {
+ syslog(LOG_INFO, "TACACS+ component offline");
+ goto fail;
+ }
+
+#ifdef HAVE_LIBTAC_EVENT
+ struct tac_session *sess;
+
+ /* if the connection is already up and multiplexed, re-use it */
+ i = opts->curr_server;
+ if (opts->server[i].session) {
+ sess = opts->server[i].session;
+ if (sess->tac_multiplex)
+ return true;
+ }
+
+ sess = tac_session_alloc_extra(sizeof(struct tac_session_extra));
+#endif
+
+ for (i = tacplus_get_server_list_pos(opts); i < opts->n_servers; i++) {
+ if (! tacplus_server_is_held_down(tacplus_server(opts, i)) &&
+#ifdef HAVE_LIBTAC_EVENT
+ tacplus_connect_ith(sess, i)
+#else
+ tacplus_connect_ith(i)
+#endif
+ )
+ {
+ if (last_connect_failed) {
+ char *serv_addr = addrinfo_to_string(tacplus_server(opts, i)->addrs);
+ syslog(LOG_NOTICE, "Successfully connected to TACACS+ server at "
+ "%s following failure(s)", strOrNil(serv_addr));
+ free(serv_addr);
+ }
+
+ tacplus_set_active_server(opts, tacplus_server(opts, i));
+ last_connect_failed = false;
+ return true;
+ }
+
+ if (! tacplus_server_is_held_down(tacplus_server(opts, i))) {
+ if (all_servers_held_down)
+ tacplus_set_active_server(opts, tacplus_server(opts, i));
+
+ all_servers_held_down = false;
+ }
+
+ tacplus_update_next_server(opts, tacplus_server(opts, i));
+ }
+
+ if (all_servers_held_down) {
+ go_offline_until_next_hold_down_expiry(opts);
+ tacplus_clear_active_server(opts);
+ }
+
+#ifdef HAVE_LIBTAC_EVENT
+ tac_session_free(sess);
+#endif
+
+fail:
+ if (all_servers_held_down)
+ syslog(LOG_WARNING, "All servers have active hold down timers");
+
+ last_connect_failed = true;
+ return false;
+}
+
+#ifdef HAVE_LIBTAC_EVENT
+void tacplus_session_close(struct tac_session *sess)
+{
+ struct tac_session_extra *extra = (struct tac_session_extra *)tac_session_get_user_data(sess);
+
+ connControl->opts->server[extra->server_id].session = NULL;
+
+ /* not strictly necessary but useful for debugging... */
+ extra->server = NULL;
+
+ tac_session_free(sess);
+}
+#else
+void tacplus_close()
+{
+ struct tac_session_extra extra = {};
+
+ tacplus_current_session_extra (connControl->opts, &extra);
+ if (extra.server && extra.server->fd >= 0) {
+ close(extra.server->fd);
+ extra.server->fd = -1;
+ }
+}
+#endif
+
+static struct tacplus_options_server *
+tacplus_lookup_server_by_addr(struct tacplus_options *opts,
+ const struct sockaddr *saddr)
+{
+ struct tacplus_options_server *server;
+ unsigned i;
+
+ for (i = 0; i < opts->n_servers; i++) {
+ server = tacplus_server(opts, i);
+
+ if (sockaddr_addr_equal(server->addrs->ai_addr, saddr))
+ return server;
+ }
+
+ return NULL;
+}
+
+void
+tacplus_copy_server_state(struct tacplus_options *from_opts,
+ struct tacplus_options *to_opts)
+{
+ struct tacplus_options_server *new_server, *existing_server;
+ unsigned i;
+
+ assert(from_opts != to_opts);
+
+ for (i = 0; i < to_opts->n_servers; i++) {
+ new_server = tacplus_server(to_opts, i);
+ existing_server = tacplus_lookup_server_by_addr(
+ from_opts, new_server->addrs->ai_addr);
+ if (! existing_server)
+ continue;
+
+ new_server->state = existing_server->state;
+
+ /*
+ * If the existing server did not have a hold down timer configured
+ * then ensure all hold down state is cleared on the new server
+ */
+ if (existing_server->hold_down == 0)
+ tacplus_server_reset_hold_down(new_server);
+ }
+}
+
+struct tacplus_options *tacplus_options_alloc(unsigned n)
+{
+ struct tacplus_options *ret = NULL;
+
+ ret = calloc(1, sizeof(*ret) + (n * sizeof(ret->server[0])));
+ ret->n_servers = n;
+
+ for (unsigned i = 0; i < n; i++) {
+#ifndef HAVE_LIBTAC_EVENT
+ ret->server[i].fd = -1;
+#endif
+ tacplus_server_reset_hold_down(&ret->server[i]);
+ }
+
+ return ret;
+}
+
+struct tacplus_options *tacplus_parse_options(const char *file)
+{
+ struct tacplus_options *opts = NULL;
+
+ read_config(file, &opts);
+ return opts;
+}
+
+struct tacplus_options *tacplus_parse_reload_options(const char *file,
+ struct tacplus_options **cur_opts)
+{
+ struct tacplus_options *opts = tacplus_parse_options(file);
+
+ if (cur_opts && *cur_opts)
+ tacplus_reload_options(cur_opts, opts);
+
+ return opts;
+}
+
+struct tacplus_options *tacplus_reload_options(struct tacplus_options **cur_opts,
+ struct tacplus_options *new_opts)
+{
+ if (new_opts) {
+ tacplus_copy_server_state(*cur_opts, new_opts);
+
+ /* Now we have the server state populate the active and next server IDs */
+ tacplus_clear_active_server(new_opts);
+ TACPLUS_SERVER_LOOP(new_opts, serv) {
+ if (tacplus_server_is_held_down(serv))
+ tacplus_update_next_server(new_opts, serv);
+ else if (! tacplus_have_active_server(new_opts))
+ tacplus_set_active_server(new_opts, serv);
+ }
+
+ /*
+ * If we are currently offline then check to see if we can come back
+ * online. This is indicated by the presence of an "active" server.
+ * Otherwise we go (remain) offline based upon our current state
+ * and the new options.
+ */
+ if (! tacplusd_online()) {
+ if (tacplus_have_active_server(new_opts))
+ tacplusd_go_online();
+ else
+ go_offline_until_next_hold_down_expiry(new_opts);
+ }
+ }
+
+ cleanup_tacplus_options(cur_opts);
+ return new_opts;
+}
+
+void cleanup_tacplus_options(struct tacplus_options **opts)
+{
+ if (! opts || ! *opts)
+ return;
+
+ TACPLUS_SERVER_LOOP(*opts, serv) {
+ freeaddrinfo(serv->addrs);
+ freeaddrinfo(serv->src_addrs);
+ free((char *)serv->src_intf);
+ free((char *)serv->secret);
+ }
+
+ free(*opts);
+ *opts = NULL;
+}