summaryrefslogtreecommitdiff
path: root/tacplus-daemon/dbus_service.c
diff options
context:
space:
mode:
Diffstat (limited to 'tacplus-daemon/dbus_service.c')
-rw-r--r--tacplus-daemon/dbus_service.c907
1 files changed, 907 insertions, 0 deletions
diff --git a/tacplus-daemon/dbus_service.c b/tacplus-daemon/dbus_service.c
new file mode 100644
index 0000000..b56d26a
--- /dev/null
+++ b/tacplus-daemon/dbus_service.c
@@ -0,0 +1,907 @@
+/*
+ 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 <stdbool.h>
+#include <systemd/sd-bus.h>
+
+#include "dbus_service.h"
+#include "global.h"
+#include "queue.h"
+#include "statistics.h"
+#include "transaction.h"
+
+#define __unused __attribute__((unused))
+
+/* Abstraction of commonly used types */
+#define BUS_TYPE_ARRAY "a" /* SD_BUS_TYPE_ARRAY */
+#define BUS_TYPE_INT32 "i" /* SD_BUS_TYPE_INT32 */
+#define BUS_TYPE_STRING "s" /* SD_BUS_TYPE_STRING */
+
+#define BUS_TYPE_DICT_ELEM(K,V) \
+ "{" /* SD_BUS_TYPE_DICT_ENTRY_BEGIN */ \
+ K V \
+ "}" /* SD_BUS_TYPE_DICT_ENTRY_END */
+
+#define BUS_TYPE_DICT(K,V) BUS_TYPE_ARRAY BUS_TYPE_DICT_ELEM(K,V)
+
+#define BUS_TYPE_STR_STR_DICT_ELEM BUS_TYPE_DICT_ELEM(BUS_TYPE_STRING, BUS_TYPE_STRING)
+#define BUS_TYPE_STR_STR_DICT BUS_TYPE_DICT(BUS_TYPE_STRING, BUS_TYPE_STRING)
+
+/* DBus method signatures */
+#define GET_STATUS_ARGS ""
+#define GET_STATUS_RET BUS_TYPE_ARRAY BUS_TYPE_STRING
+
+#define ACCOUNT_SEND_ARGS BUS_TYPE_INT32 BUS_TYPE_STRING BUS_TYPE_STRING \
+ BUS_TYPE_STRING BUS_TYPE_STR_STR_DICT
+#define ACCOUNT_SEND_RET BUS_TYPE_INT32
+
+#define CMD_ACCOUNT_SEND_ARGS ACCOUNT_SEND_ARGS BUS_TYPE_ARRAY BUS_TYPE_STRING
+#define CMD_ACCOUNT_SEND_RET ACCOUNT_SEND_RET
+
+#define AUTHEN_SEND_ARGS BUS_TYPE_STRING BUS_TYPE_STRING BUS_TYPE_STRING BUS_TYPE_STRING
+#define AUTHEN_SEND_RET BUS_TYPE_INT32
+
+#define AUTHOR_SEND_ARGS BUS_TYPE_STRING BUS_TYPE_STRING BUS_TYPE_STRING BUS_TYPE_STR_STR_DICT
+#define AUTHOR_SEND_RET BUS_TYPE_INT32 BUS_TYPE_STR_STR_DICT
+
+#define CMD_AUTHOR_SEND_ARGS AUTHOR_SEND_ARGS BUS_TYPE_ARRAY BUS_TYPE_STRING
+#define CMD_AUTHOR_SEND_RET AUTHOR_SEND_RET
+
+struct tacplus_dbus_service {
+ sd_bus *bus;
+ bool stop;
+ bool process;
+
+ Queue *tacacs_query_q;
+ Queue *tacacs_response_q;
+
+ pthread_mutex_t bus_lock;
+ pthread_t request_thread, reply_thread, dbus_thread;
+
+ uint64_t acct_task_id;
+};
+
+static struct tacplus_dbus_service _service = { .stop = true, .process = false };
+static tacplus_dbus_service_t service = &_service;
+
+static void transaction_queue_free_element(void *e)
+{
+ struct transaction *t = e;
+
+ /* To cleanly exit, we add bogus entries to our queues that trick
+ * our threads into returning from pthread_cond_wait. These bogus
+ * queue elements are allocated with calloc, hence the check for
+ * NULL.
+ */
+ if (t->user)
+ sd_bus_message_unref(t->user);
+
+ /* Do any per-type cleanup */
+ switch (t->type) {
+ case TRANSACTION_ACCOUNT:
+ free(t->request.account.task_id);
+ free(t->request.account.r_addr);
+ for (char **arg = t->request.account.command; arg && *arg; arg++)
+ free(*arg);
+ free(t->request.account.command);
+ break;
+ case TRANSACTION_AUTHOR:
+ for (char **arg = t->request.author.cmd; arg && *arg; arg++)
+ free(*arg);
+ free(t->request.author.cmd);
+ break;
+ case TRANSACTION_AUTHEN:
+ default:
+ break;
+ }
+
+ transaction_free(&t);
+}
+
+void dbus_service_init()
+{
+ pthread_mutex_init(&service->bus_lock, NULL);
+
+ service->tacacs_query_q = create_queue(transaction_queue_free_element);
+ service->tacacs_response_q = create_queue(transaction_queue_free_element);
+}
+
+void dbus_service_deinit()
+{
+ destroy_queue(&service->tacacs_query_q);
+ destroy_queue(&service->tacacs_response_q);
+
+ pthread_mutex_lock(&service->bus_lock);
+ sd_bus_release_name(service->bus, TACPLUS_DAEMON);
+ sd_bus_close(service->bus);
+ sd_bus_unref(service->bus);
+ service->bus = NULL;
+ pthread_mutex_unlock(&service->bus_lock);
+
+ pthread_mutex_destroy(&service->bus_lock);
+}
+
+static int
+fill_bus_msg_from_account_transaction(const struct account_send_response *r,
+ sd_bus_message *m)
+{
+ return sd_bus_message_append(m, BUS_TYPE_INT32, r->status);
+}
+
+static int
+fill_bus_msg_from_authen_transaction(const struct authen_send_response *r,
+ sd_bus_message *m)
+{
+ return sd_bus_message_append(m, BUS_TYPE_INT32, r->status);
+}
+
+static int
+fill_bus_msg_from_author_transaction(const struct author_send_response *r,
+ sd_bus_message *m)
+{
+ int type, ret;
+
+ switch (r->status) {
+ case TAC_PLUS_AUTHOR_STATUS_PASS_ADD:
+ case TAC_PLUS_AUTHOR_STATUS_PASS_REPL:
+ case TAC_PLUS_AUTHOR_STATUS_FAIL:
+ case TAC_PLUS_AUTHOR_STATUS_ERROR:
+ type = r->status;
+ break;
+ case TAC_PLUS_AUTHOR_STATUS_FOLLOW:
+ default:
+ type = TAC_PLUS_AUTHOR_STATUS_ERROR;
+ break;
+ }
+
+ ret = sd_bus_message_append(m, BUS_TYPE_INT32, type);
+ if (ret < 0)
+ return ret;
+
+ sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, BUS_TYPE_STR_STR_DICT_ELEM);
+ if (ret < 0)
+ return ret;
+
+ for (struct transaction_attrib *attr = r->attrs; attr; attr = attr->next) {
+ ret = sd_bus_message_append(m, BUS_TYPE_STR_STR_DICT_ELEM, attr->name, attr->value);
+ if (ret < 0)
+ return ret;
+ }
+
+ return sd_bus_message_close_container(m);
+}
+
+static sd_bus_message *
+fill_bus_msg_for_transaction(struct transaction *t)
+{
+ sd_bus_message *reply;
+ int ret;
+
+ if (! t->user) {
+ syslog(LOG_ERR, "Cannot generate method response without method call message");
+ return NULL;
+ }
+
+ ret = sd_bus_message_new_method_return(t->user, &reply);
+ if (ret < 0) {
+ syslog(LOG_ERR, "Failed to allocate method response message: %d", ret);
+ return NULL;
+ }
+
+ switch (t->type) {
+ case TRANSACTION_ACCOUNT:
+ ret = fill_bus_msg_from_account_transaction(&t->response.account, reply);
+ break;
+ case TRANSACTION_AUTHEN:
+ ret = fill_bus_msg_from_authen_transaction(&t->response.authen, reply);
+ break;
+ case TRANSACTION_AUTHOR:
+ ret = fill_bus_msg_from_author_transaction(&t->response.author, reply);
+ break;
+ default:
+ ret = -1;
+ break;
+ }
+
+ if (ret < 0) {
+ sd_bus_message_unref(reply);
+ return NULL;
+ }
+
+ return reply;
+}
+
+static void release_transaction_for_bus_message(struct transaction **t)
+{
+ if (t && *t) {
+ transaction_queue_free_element(*t);
+ *t = NULL;
+ }
+}
+
+static void *consume_dbus_reply_thread(void *arg __unused)
+{
+ while (! service->stop) {
+ struct transaction *t;
+
+ pthread_mutex_lock(&(service->tacacs_response_q->lock));
+ while (is_queue_empty(service->tacacs_response_q)) {
+ pthread_cond_wait(&(service->tacacs_response_q->empty),
+ &(service->tacacs_response_q->lock));
+ }
+ pthread_mutex_unlock(&(service->tacacs_response_q->lock));
+
+ /* TODO: what if there's still a valid msg to send? */
+ if (service->stop || !service->process) {
+ syslog(LOG_DEBUG, "TACACS+ reply_thread: stopping");
+ break;
+ }
+
+ t = dequeue(service->tacacs_response_q);
+ if (t->type == TRANSACTION_INVALID) {
+ release_transaction_for_bus_message(&t);
+ continue;
+ }
+
+ syslog(LOG_DEBUG, "Processing %s transaction response",
+ transaction_type_str(t->type));
+
+ pthread_mutex_lock(&service->bus_lock);
+
+ sd_bus_message *reply = fill_bus_msg_for_transaction(t);
+ if (reply) {
+ int ret = sd_bus_send(sd_bus_message_get_bus(reply), reply, NULL);
+
+ if (ret < 0)
+ syslog(LOG_DEBUG, "Failed to send %s transaction response: %d",
+ transaction_type_str(t->type), ret);
+ else
+ syslog(LOG_DEBUG, "Sent %s transaction response",
+ transaction_type_str(t->type));
+
+ sd_bus_message_unref(reply);
+ reply = NULL;
+ }
+ else {
+ syslog(LOG_ERR, "Failed to generate response for transaction");
+ }
+
+ pthread_mutex_unlock(&service->bus_lock);
+
+ release_transaction_for_bus_message(&t);
+ }
+
+ syslog(LOG_DEBUG, "TACACS+ reply_thread: exiting");
+ pthread_exit(NULL);
+}
+
+static void *consume_dbus_req_thread(void *arg __unused)
+{
+ while (! service->stop) {
+ struct transaction *t;
+
+ pthread_mutex_lock(&(service->tacacs_query_q->lock));
+ while (is_queue_empty(service->tacacs_query_q)) {
+ pthread_cond_wait(&(service->tacacs_query_q->empty),
+ &(service->tacacs_query_q->lock));
+ }
+ pthread_mutex_unlock(&(service->tacacs_query_q->lock));
+
+ /* at this point we now have at least one tacacs+ request in our queue */
+
+ if (service->stop || !service->process) {
+ syslog(LOG_DEBUG, "TACACS+ request_thread: stopping");
+ break;
+ }
+
+ t = dequeue(service->tacacs_query_q);
+ if (t->type == TRANSACTION_INVALID) {
+ release_transaction_for_bus_message(&t);
+ continue;
+ }
+
+ syslog(LOG_DEBUG, "Processing %s transaction from queue",
+ transaction_type_str(t->type));
+
+ switch (t->type) {
+ case TRANSACTION_ACCOUNT:
+ tacplus_acct_send(t);
+ break;
+ case TRANSACTION_AUTHEN:
+ tacplus_authen_send(t);
+ break;
+ case TRANSACTION_AUTHOR:
+ tacplus_author_send(t);
+ break;
+ default:
+ syslog(LOG_ERR, "Unknown transaction type %d - ignoring", t->type);
+ release_transaction_for_bus_message(&t);
+ continue;
+ }
+
+ syslog(LOG_DEBUG, "Completed %s transaction, queueing response",
+ transaction_type_str(t->type));
+
+ enqueue(service->tacacs_response_q, t);
+ }
+
+ syslog(LOG_DEBUG, "TACACS+ request_thread: exiting");
+ pthread_exit(NULL);
+}
+
+static int fill_status_reply(struct sd_bus_message *m)
+{
+ int ret;
+
+ ret = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, BUS_TYPE_STRING);
+ if (ret < 0)
+ return ret;
+
+ for (unsigned i = 0 ; i < connControl->opts->n_servers ; i++) {
+ uint16_t port;
+ char *addr;
+ char *src = NULL;
+ char *val;
+ size_t val_size;
+ FILE *val_stream;
+
+ addr = addrinfo_to_string(tacplus_server(connControl->opts, i)->addrs);
+ if (! addr)
+ continue;
+
+ if (connControl->opts->server[i].src_addrs)
+ src = addrinfo_to_string(connControl->opts->server[i].src_addrs);
+ else if (connControl->opts->server[i].src_intf)
+ src = strdup(connControl->opts->server[i].src_intf);
+
+ port = get_addrinfo_port(tacplus_server(connControl->opts, i)->addrs);
+
+ val_stream = open_memstream(&val, &val_size);
+
+ fprintf(val_stream, "%s,%d,%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%ld",
+ addr,
+ port,
+ src ? src : "",
+ get_authen_requests(i),
+ get_authen_replies(i),
+ get_author_requests(i),
+ get_author_replies(i),
+ get_acct_requests(i),
+ get_acct_replies(i),
+ get_unknown_replies(i),
+ get_failed_connects(i),
+ (i == connControl->opts->curr_server) ? true : false,
+ tacplus_server_remaining_hold_down_secs(&connControl->opts->server[i]));
+
+ fclose(val_stream);
+
+ sd_bus_message_append(m, BUS_TYPE_STRING, val);
+
+ free(addr);
+ free(val);
+ free(src);
+ }
+
+ return sd_bus_message_close_container(m);
+}
+
+static int get_status(sd_bus_message *m,
+ __unused void *userdata,
+ __unused sd_bus_error *error)
+{
+ int ret;
+ sd_bus_message *reply;
+
+ syslog(LOG_DEBUG, "get_status() call");
+
+ ret = sd_bus_message_new_method_return(m, &reply);
+ if (ret < 0)
+ return ret;
+
+ ret = fill_status_reply(reply);
+ if (ret < 0) {
+ syslog(LOG_ERR, "Failed to generate status reply: %d", ret);
+ sd_bus_message_unref(reply);
+ return ret;
+ }
+
+ ret = sd_bus_send(sd_bus_message_get_bus(m), reply, NULL);
+ if (ret < 0)
+ syslog(LOG_DEBUG, "Failed to send get_status() response: %d", ret);
+
+ sd_bus_message_unref(reply);
+ return ret;
+}
+
+static int queue_transaction_for_bus_message(struct transaction *t,
+ sd_bus_message *m)
+{
+ /*
+ * Stash message with the transaction so we can send a reply later. We
+ * must add a ref to ensure the sd-bus library doesn't free the message
+ * until we are done.
+ */
+ t->user = m;
+ sd_bus_message_ref(m);
+
+ enqueue(service->tacacs_query_q, t);
+
+ syslog(LOG_DEBUG, "Queued %s transaction request",
+ transaction_type_str(t->type));
+
+ /* Return non-zero to allow us to send an asynchronous reply */
+ return 1;
+}
+
+typedef int (*account_variant_fill_handler)(struct account_send_param *,
+ sd_bus_message *);
+
+static int fill_account_transaction_from_bus_msg(struct account_send_param *p,
+ sd_bus_message *m,
+ account_variant_fill_handler var_fn)
+{
+ int ret;
+ char *key = NULL, *value = NULL;
+
+ ret = sd_bus_message_read(m, BUS_TYPE_INT32 BUS_TYPE_STRING
+ BUS_TYPE_STRING BUS_TYPE_STRING,
+ &p->account_flag, &p->name, &p->tty, &p->r_addr);
+ if (ret < 0)
+ return ret;
+
+ ret = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY,
+ BUS_TYPE_STR_STR_DICT_ELEM);
+ if (ret < 0)
+ return ret;
+
+ while (sd_bus_message_read(m, BUS_TYPE_STR_STR_DICT_ELEM, &key, &value) > 0) {
+ if (strcmp("task_id", key) == 0) {
+ p->task_id = value;
+ }
+ else if (strcmp("start_time", key) == 0) {
+ p->start_time = value;
+ }
+ else if (strcmp("stop_time", key) == 0) {
+ p->stop_time = value;
+ }
+ else if (strcmp("service", key) == 0) {
+ p->service = value;
+ }
+ else if (strcmp("protocol", key) == 0) {
+ p->protocol = value;
+ }
+ else {
+ syslog(LOG_ERR, "Ignoring unsupported attribute-key: %s", key);
+ }
+ }
+
+ ret = sd_bus_message_exit_container(m);
+ if (ret < 0 || !var_fn)
+ return ret;
+
+ return var_fn(p, m);
+}
+
+static void fill_account_transaction_task_id(struct account_send_param *p) {
+ /*
+ * Choose a new task ID if one was not provided
+ *
+ * Otherwise copy the provided ID. This allows common cleanup in
+ * transaction_queue_free_element() since the memory lifecycle of the
+ * data filled into the transaction by fill_account_transaction_from_bus_msg()
+ * is managed by sd-dbus. Hence we also don't free any existing value.
+ */
+ if (!p->task_id) {
+ char buf[TAC_PLUS_ATTRIB_MAX_LEN] = {0};
+ assert(snprintf(buf, sizeof(buf), "%lu", service->acct_task_id++) < (int) sizeof(buf));
+ p->task_id = strdup(buf);
+ } else {
+ p->task_id = strdup(p->task_id);
+ }
+}
+
+static void fill_account_transaction_rem_addr(struct account_send_param *p) {
+ /*
+ * If we don't have a remote login address then attempt to obtain one based
+ * on the TTY (if we were passed one).
+ *
+ * Otherwise copy the provided address. This allows common cleanup in
+ * transaction_queue_free_element() since the memory lifecycle of the
+ * data filled into the transaction by fill_account_transaction_from_bus_msg()
+ * is managed by sd-dbus. Hence we also don't free any existing value.
+ */
+ char *r_addr;
+ if ((!p->r_addr || strlen(p->r_addr) == 0) &&
+ (r_addr = get_tty_login_addr(p->tty))) {
+ p->r_addr = r_addr;
+ } else {
+ p->r_addr = strdup(p->r_addr);
+ }
+}
+
+#define ACCOUNT_SEND_VARIANT(N, V) \
+static int N(sd_bus_message *m, \
+ __unused void *userdata, \
+ __unused sd_bus_error *error) \
+{ \
+ struct transaction *t; \
+ int ret; \
+ \
+ syslog(LOG_DEBUG, #N "() call"); \
+ \
+ t = transaction_new(TRANSACTION_ACCOUNT); \
+ if (!t) \
+ return -ENOMEM; \
+ \
+ ret = fill_account_transaction_from_bus_msg(&t->request.account, m, V); \
+ if (ret < 0) { \
+ syslog(LOG_ERR, "Failed to parse " #N " call: %d", ret); \
+ transaction_free(&t); \
+ return ret; \
+ } \
+ \
+ fill_account_transaction_task_id(&t->request.account); \
+ fill_account_transaction_rem_addr(&t->request.account); \
+ \
+ return queue_transaction_for_bus_message(t, m); \
+}
+
+ACCOUNT_SEND_VARIANT(account_send, NULL);
+
+static int cmd_account_fill_handler(struct account_send_param *p,
+ sd_bus_message *m)
+{
+ return sd_bus_message_read_strv(m, &(p->command));
+}
+
+ACCOUNT_SEND_VARIANT(cmd_account_send, cmd_account_fill_handler);
+
+static int fill_authen_transaction_from_bus_msg(struct authen_send_param *p,
+ sd_bus_message *m)
+{
+ return sd_bus_message_read(m, AUTHEN_SEND_ARGS,
+ &p->user, &p->password,
+ &p->tty, &p->r_addr);
+}
+
+static int authen_send(sd_bus_message *m,
+ __unused void *userdata,
+ __unused sd_bus_error *error)
+{
+ struct transaction *t;
+ int ret;
+
+ syslog(LOG_DEBUG, "authen_send() call");
+
+ t = transaction_new(TRANSACTION_AUTHEN);
+ if (!t)
+ return -ENOMEM;
+
+ ret = fill_authen_transaction_from_bus_msg(&t->request.authen, m);
+ if (ret < 0) {
+ syslog(LOG_ERR, "Failed to parse authen_send() call: %d", ret);
+ transaction_free(&t);
+ return ret;
+ }
+
+ return queue_transaction_for_bus_message(t, m);
+}
+
+typedef int (*author_variant_fill_handler)(struct author_send_param *,
+ sd_bus_message *);
+
+static int fill_author_transaction_from_bus_msg(struct author_send_param *p,
+ sd_bus_message *m,
+ author_variant_fill_handler var_fn)
+{
+ int ret;
+ char *key = NULL, *value = NULL;
+
+ ret = sd_bus_message_read(m, BUS_TYPE_STRING BUS_TYPE_STRING BUS_TYPE_STRING,
+ &p->login, &p->tty, &p->r_addr);
+ if (ret < 0)
+ return ret;
+
+ ret = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY,
+ BUS_TYPE_STR_STR_DICT_ELEM);
+ if (ret < 0)
+ return ret;
+
+ while (sd_bus_message_read(m, BUS_TYPE_STR_STR_DICT_ELEM, &key, &value) > 0) {
+ if (strcmp("protocol", key) == 0) {
+ p->protocol = value;
+ }
+ else if (strcmp("service", key) == 0) {
+ p->service = value;
+ }
+ else if (strcmp("secrets", key) == 0) {
+ p->secrets = value;
+ }
+ else {
+ syslog(LOG_ERR, "Ignoring unsupported attribute-key: %s", key);
+ }
+ }
+
+ ret = sd_bus_message_exit_container(m);
+ if (ret < 0 || !var_fn)
+ return ret;
+
+ return var_fn(p, m);
+}
+
+#define AUTHOR_SEND_VARIANT(N, V) \
+static int N(sd_bus_message *m, \
+ __unused void *userdata, \
+ __unused sd_bus_error *error) \
+{ \
+ struct transaction *t; \
+ int ret; \
+ \
+ syslog(LOG_DEBUG, #N "() call"); \
+ \
+ t = transaction_new(TRANSACTION_AUTHOR); \
+ if (!t) \
+ return -ENOMEM; \
+ \
+ ret = fill_author_transaction_from_bus_msg(&t->request.author, m, V); \
+ if (ret < 0) { \
+ syslog(LOG_ERR, "Failed to parse " #N "() call: %d", ret); \
+ transaction_free(&t); \
+ return ret; \
+ } \
+ \
+ return queue_transaction_for_bus_message(t, m); \
+}
+
+AUTHOR_SEND_VARIANT(author_send, NULL);
+
+static int cmd_author_fill_handler(struct author_send_param *p,
+ sd_bus_message *m)
+{
+ return sd_bus_message_read_strv(m, &(p->cmd));
+}
+
+AUTHOR_SEND_VARIANT(cmd_author_send, cmd_author_fill_handler);
+
+int signal_offline_state_change() {
+ if (service->stop) {
+ syslog(LOG_ERR, "Unable to signal offline state change");
+ return -1;
+ }
+
+ pthread_mutex_lock(&service->bus_lock);
+ int ret = sd_bus_emit_properties_changed(
+ service->bus, TACPLUS_DAEMON_PATH, TACPLUS_DAEMON, "offline", NULL);
+ pthread_mutex_unlock(&service->bus_lock);
+
+ if (ret < 0)
+ syslog(LOG_ERR, "Failed to signal offline state change");
+ return ret;
+}
+
+static int offline_property_get(__unused sd_bus *bus,
+ __unused const char *path,
+ __unused const char *interface,
+ __unused const char *property,
+ sd_bus_message *reply,
+ __unused void *userdata,
+ __unused sd_bus_error *error)
+{
+ return sd_bus_message_append(reply, "b", connControl->state.offline);
+}
+
+static const sd_bus_vtable serv_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD("get_status", GET_STATUS_ARGS, GET_STATUS_RET,
+ get_status, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("account_send", ACCOUNT_SEND_ARGS, ACCOUNT_SEND_RET,
+ account_send, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("cmd_account_send", CMD_ACCOUNT_SEND_ARGS, CMD_ACCOUNT_SEND_RET,
+ cmd_account_send, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("authen_send", AUTHEN_SEND_ARGS, AUTHEN_SEND_RET,
+ authen_send, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("author_send", AUTHOR_SEND_ARGS, AUTHOR_SEND_RET,
+ author_send, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("cmd_author_send", CMD_AUTHOR_SEND_ARGS, CMD_AUTHOR_SEND_RET,
+ cmd_author_send, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_PROPERTY("offline", "b", offline_property_get, 0,
+ SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_VTABLE_END
+};
+
+static void dbus_service_fail(tacplus_dbus_service_t service)
+{
+ service->stop = true;
+ if (kill(getpid(), SIGTERM) != 0)
+ abort();
+}
+
+static void *dbus_service_listen(__unused void *arg)
+{
+ int ret;
+
+ while (! service->stop) {
+ pthread_mutex_lock(&service->bus_lock);
+ ret = sd_bus_process(service->bus, NULL);
+ pthread_mutex_unlock(&service->bus_lock);
+
+ if (ret > 0) /* More queued messages to process */
+ continue;
+ if (ret < 0)
+ goto fail;
+
+ ret = sd_bus_wait(service->bus, 1000000);
+ if (ret < 0)
+ goto fail;
+ }
+
+ syslog(LOG_DEBUG, "Stopping dbus_service_listen thread");
+ return NULL;
+
+fail:
+ syslog(LOG_ERR, "DBus processing error: %s (%d)", strerror(-ret), ret);
+ dbus_service_fail(service);
+ return NULL;
+}
+
+static void force_wake_queue_threads(tacplus_dbus_service_t service)
+{
+ assert(service->stop || !service->process);
+
+ /* Wake up queue threads - transaction type MUST be TRANSACTION_INVALID */
+
+ enqueue(service->tacacs_query_q, transaction_new(TRANSACTION_INVALID));
+ enqueue(service->tacacs_response_q, transaction_new(TRANSACTION_INVALID));
+}
+
+static void start_processing(tacplus_dbus_service_t service)
+{
+ assert(! service->stop);
+
+ if (service->process) {
+ syslog(LOG_DEBUG, "Processing already started");
+ return;
+ }
+
+ /*
+ * The process flag MUST be set before starting the consumer threads
+ * otherwise they may immediately exit
+ */
+ service->process = true;
+
+ if (pthread_create(&service->request_thread, NULL,
+ consume_dbus_req_thread, NULL)) {
+ syslog(LOG_ERR, "Failed to instantiate request_thread");
+ service->process = false;
+ return;
+ }
+
+ if (pthread_create(&service->reply_thread, NULL,
+ consume_dbus_reply_thread, NULL)) {
+ syslog(LOG_ERR, "Failed to instantiate reply_thread");
+ service->process = false;
+ force_wake_queue_threads(service);
+ pthread_join(service->request_thread, NULL);
+ return;
+ }
+}
+
+static void stop_processing(tacplus_dbus_service_t service)
+{
+ /* Check the processing threads have started */
+ if (!service->process)
+ return;
+
+ service->process = false;
+ force_wake_queue_threads(service);
+
+ syslog(LOG_DEBUG, "Waiting on request_thread...");
+ pthread_join(service->request_thread, NULL);
+
+ syslog(LOG_DEBUG, "Waiting on reply_thread...");
+ pthread_join(service->reply_thread, NULL);
+}
+
+void dbus_service_wait(void)
+{
+ syslog(LOG_DEBUG, "Waiting on dbus_thread...");
+ pthread_join(service->dbus_thread, NULL);
+}
+
+// Calling thread must hold service->bus_lock
+static void _dbus_service_start_fail_cleanup()
+{
+ if (service->bus)
+ sd_bus_unref(service->bus);
+ service->bus = NULL;
+ pthread_mutex_unlock(&service->bus_lock);
+}
+
+int dbus_service_start()
+{
+ int ret;
+
+ if (service->bus || !service->stop) {
+ syslog(LOG_DEBUG, "DBus service is already running");
+ return 0;
+ }
+
+ pthread_mutex_lock(&service->bus_lock);
+
+ ret = sd_bus_open_system(&service->bus);
+ if (ret < 0) {
+ _dbus_service_start_fail_cleanup();
+ return ret;
+ }
+
+ ret = sd_bus_request_name(service->bus, TACPLUS_DAEMON, 0);
+ if (ret < 0) {
+ _dbus_service_start_fail_cleanup();
+ return ret;
+ }
+
+ ret = sd_bus_add_object_vtable(service->bus, NULL, TACPLUS_DAEMON_PATH,
+ TACPLUS_DAEMON, serv_vtable, NULL);
+ if (ret < 0) {
+ sd_bus_release_name(service->bus, TACPLUS_DAEMON);
+ _dbus_service_start_fail_cleanup();
+ return ret;
+ }
+
+ service->stop = false;
+
+ ret = pthread_create(&service->dbus_thread, NULL, dbus_service_listen, NULL);
+ if (ret != 0) {
+ syslog(LOG_ERR, "Failed to instantiate dbus_thread: %s", strerror(ret));
+ sd_bus_release_name(service->bus, TACPLUS_DAEMON);
+ _dbus_service_start_fail_cleanup();
+ return ret;
+ }
+
+ start_processing(service);
+
+ /* TODO: add dbus filters? */
+ pthread_mutex_unlock(&service->bus_lock);
+ return service->process ? 0 : -1;
+}
+
+void dbus_service_stop(void)
+{
+ service->stop = true;
+ stop_processing(service);
+}
+
+void dbus_service_pause()
+{
+ /*
+ * Stop request/reply consumer threads and acquire the bus lock.
+ * Acquiring the bus lock is required to prevent the processing of
+ * DBus requests.
+ */
+ stop_processing(service);
+ pthread_mutex_lock(&service->bus_lock);
+}
+
+int dbus_service_resume()
+{
+ /*
+ * Start the request/reply consumer threads and release the bus lock
+ * so we start processing DBus requests once again.
+ */
+ start_processing(service);
+ pthread_mutex_unlock(&service->bus_lock);
+ return service->process ? 0 : -1;
+}
+
+bool dbus_service_failed()
+{
+ return (service->stop && service->process);
+}