diff options
Diffstat (limited to 'src/monitor')
-rw-r--r-- | src/monitor/monitor.c | 2631 | ||||
-rw-r--r-- | src/monitor/monitor.h | 36 | ||||
-rw-r--r-- | src/monitor/monitor_interfaces.h | 55 | ||||
-rw-r--r-- | src/monitor/monitor_sbus.c | 195 |
4 files changed, 2917 insertions, 0 deletions
diff --git a/src/monitor/monitor.c b/src/monitor/monitor.c new file mode 100644 index 00000000..ddbfb6a7 --- /dev/null +++ b/src/monitor/monitor.c @@ -0,0 +1,2631 @@ +/* + SSSD + + Service monitor + + Copyright (C) Simo Sorce 2008 + + 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/>. +*/ + +#define _GNU_SOURCE +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/param.h> +#include <time.h> +#include <string.h> +#include "config.h" +#ifdef HAVE_SYS_INOTIFY_H +#include <sys/inotify.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> + +/* Needed for res_init() */ +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <resolv.h> + +#include "util/util.h" +#include "popt.h" +#include "tevent.h" +#include "confdb/confdb.h" +#include "confdb/confdb_setup.h" +#include "collection.h" +#include "ini_config.h" +#include "db/sysdb.h" +#include "monitor/monitor.h" +#include "dbus/dbus.h" +#include "sbus/sssd_dbus.h" +#include "monitor/monitor_interfaces.h" + +/* ping time cannot be less then once every few seconds or the + * monitor will get crazy hammering children with messages */ +#define MONITOR_DEF_PING_TIME 10 + +struct svc_spy; + +struct mt_svc { + struct mt_svc *prev; + struct mt_svc *next; + struct sbus_connection *conn; + struct svc_spy *conn_spy; + + struct mt_ctx *mt_ctx; + + char *provider; + char *command; + char *name; + char *identity; + pid_t pid; + + int ping_time; + + bool svc_started; + + int restarts; + time_t last_restart; + time_t last_ping; + int failed_pongs; + + int debug_level; + + struct tevent_timer *ping_ev; +}; + +struct config_file_callback { + int wd; + int retries; + monitor_reconf_fn fn; + char *filename; + time_t modified; + struct config_file_callback *next; + struct config_file_callback *prev; +}; + +struct config_file_ctx { + TALLOC_CTX *parent_ctx; + struct tevent_timer *timer; + bool needs_update; + struct mt_ctx *mt_ctx; + struct config_file_callback *callbacks; +}; + +struct mt_ctx { + struct tevent_context *ev; + struct confdb_ctx *cdb; + TALLOC_CTX *domain_ctx; /* Memory context for domain list */ + struct sss_domain_info *domains; + TALLOC_CTX *service_ctx; /* Memory context for services */ + char **services; + struct mt_svc *svc_list; + struct sbus_connection *sbus_srv; + struct config_file_ctx *file_ctx; + int inotify_fd; + int service_id_timeout; + bool check_children; + bool services_started; +}; + +static int start_service(struct mt_svc *mt_svc); + +static int monitor_service_init(struct sbus_connection *conn, void *data); + +static int service_send_ping(struct mt_svc *svc); +static void ping_check(DBusPendingCall *pending, void *data); + +static int service_check_alive(struct mt_svc *svc); + +static void set_tasks_checker(struct mt_svc *srv); +static void set_global_checker(struct mt_ctx *ctx); +static int monitor_kill_service (struct mt_svc *svc); + +static int get_service_config(struct mt_ctx *ctx, const char *name, + struct mt_svc **svc_cfg); +static int get_provider_config(struct mt_ctx *ctx, const char *name, + struct mt_svc **svc_cfg); +static int add_new_service(struct mt_ctx *ctx, const char *name); +static int add_new_provider(struct mt_ctx *ctx, const char *name); + +static int mark_service_as_started(struct mt_svc *svc); + +#if 0 +static int monitor_signal_reconf(struct config_file_ctx *file_ctx, + const char *filename); +#endif + +static int update_monitor_config(struct mt_ctx *ctx); +static int monitor_cleanup(void); + +/* dbus_get_monitor_version + * Return the monitor version over D-BUS */ +static int get_monitor_version(DBusMessage *message, + struct sbus_connection *conn) +{ + dbus_uint16_t version = MONITOR_VERSION; + DBusMessage *reply; + dbus_bool_t ret; + + reply = dbus_message_new_method_return(message); + if (!reply) return ENOMEM; + ret = dbus_message_append_args(reply, + DBUS_TYPE_UINT16, &version, + DBUS_TYPE_INVALID); + if (!ret) { + dbus_message_unref(reply); + return EIO; + } + + /* send reply back */ + sbus_conn_send_reply(conn, reply); + dbus_message_unref(reply); + + return EOK; +} + +struct mon_init_conn { + struct mt_ctx *ctx; + struct sbus_connection *conn; + struct tevent_timer *timeout; +}; + +static int add_svc_conn_spy(struct mt_svc *svc); + +/* registers a new client. + * if operation is successful also sends back the Monitor version */ +static int client_registration(DBusMessage *message, + struct sbus_connection *conn) +{ + dbus_uint16_t version = MONITOR_VERSION; + struct mon_init_conn *mini; + struct mt_svc *svc; + void *data; + DBusMessage *reply; + DBusError dbus_error; + dbus_uint16_t svc_ver; + char *svc_name; + dbus_bool_t dbret; + int ret; + + data = sbus_conn_get_private_data(conn); + mini = talloc_get_type(data, struct mon_init_conn); + if (!mini) { + DEBUG(0, ("Connection holds no valid init data\n")); + return EINVAL; + } + + /* First thing, cancel the timeout */ + talloc_zfree(mini->timeout); + + dbus_error_init(&dbus_error); + + dbret = dbus_message_get_args(message, &dbus_error, + DBUS_TYPE_STRING, &svc_name, + DBUS_TYPE_UINT16, &svc_ver, + DBUS_TYPE_INVALID); + if (!dbret) { + DEBUG(1, ("Failed to parse message, killing connection\n")); + if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error); + sbus_disconnect(conn); + /* FIXME: should we just talloc_zfree(conn) ? */ + goto done; + } + + DEBUG(4, ("Received ID registration: (%s,%d)\n", svc_name, svc_ver)); + + /* search this service in the list */ + svc = mini->ctx->svc_list; + while (svc) { + ret = strcasecmp(svc->identity, svc_name); + if (ret == 0) { + break; + } + svc = svc->next; + } + if (!svc) { + DEBUG(0, ("Unable to find peer [%s] in list of services," + " killing connection!\n", svc_name)); + sbus_disconnect(conn); + /* FIXME: should we just talloc_zfree(conn) ? */ + goto done; + } + + /* Fill in svc structure with connection data */ + svc->conn = mini->conn; + + ret = mark_service_as_started(svc); + if (ret) { + DEBUG(1, ("Failed to mark service [%s]!\n", svc_name)); + goto done; + } + + /* reply that all is ok */ + reply = dbus_message_new_method_return(message); + if (!reply) return ENOMEM; + + dbret = dbus_message_append_args(reply, + DBUS_TYPE_UINT16, &version, + DBUS_TYPE_INVALID); + if (!dbret) { + dbus_message_unref(reply); + return EIO; + } + + /* send reply back */ + sbus_conn_send_reply(conn, reply); + dbus_message_unref(reply); + +done: + /* init complete, get rid of temp init context */ + talloc_zfree(mini); + + return EOK; +} + +struct svc_spy { + struct mt_svc *svc; +}; + +static int svc_destructor(void *mem) +{ + struct mt_svc *svc = talloc_get_type(mem, struct mt_svc); + if (!svc) { + /* ?!?!? */ + return 0; + } + + /* always try to delist service */ + DLIST_REMOVE(svc->mt_ctx->svc_list, svc); + + /* svc is beeing freed, neutralize the spy */ + if (svc->conn_spy) { + talloc_set_destructor((TALLOC_CTX *)svc->conn_spy, NULL); + talloc_zfree(svc->conn_spy); + } + return 0; +} + +static int svc_spy_destructor(void *mem) +{ + struct svc_spy *spy = talloc_get_type(mem, struct svc_spy); + if (!spy) { + /* ?!?!? */ + return 0; + } + + /* svc->conn has been freed, NULL the pointer in svc */ + spy->svc->conn = NULL; + return 0; +} + +static int add_svc_conn_spy(struct mt_svc *svc) +{ + struct svc_spy *spy; + + spy = talloc(svc->conn, struct svc_spy); + if (!spy) return ENOMEM; + + spy->svc = svc; + talloc_set_destructor((TALLOC_CTX *)spy, svc_spy_destructor); + svc->conn_spy = spy; + + return EOK; +} + +static int mark_service_as_started(struct mt_svc *svc) +{ + struct mt_ctx *ctx = svc->mt_ctx; + struct mt_svc *iter; + int ret; + int i; + + DEBUG(5, ("Marking %s as started.\n", svc->name)); + svc->svc_started = true; + + /* we need to attach a spy to the connection structure so that if some code + * frees it we can zero it out in the service structure. Otherwise we may + * try to access or even free, freed memory. */ + ret = add_svc_conn_spy(svc); + if (ret) { + DEBUG(0, ("Failed to attch spy\n")); + goto done; + } + + if (!ctx->services_started) { + + /* check if all providers are up */ + for (iter = ctx->svc_list; iter; iter = iter->next) { + if (iter->provider && !iter->svc_started) { + DEBUG(5, ("Still waiting on %s provider.", iter->name)); + break; + } + } + + if (iter) { + /* there are still unstarted providers */ + goto done; + } + + ctx->services_started = true; + + DEBUG(4, ("Now starting services!\n")); + /* then start all services */ + for (i = 0; ctx->services[i]; i++) { + add_new_service(ctx, ctx->services[i]); + } + } + +done: + return ret; +} + +static void services_startup_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct mt_ctx *ctx = talloc_get_type(ptr, struct mt_ctx); + int i; + + DEBUG(6, ("Handling timeout\n")); + + if (!ctx->services_started) { + + DEBUG(1, ("Providers did not start in time, " + "forcing services startup!\n")); + + ctx->services_started = true; + + DEBUG(4, ("Now starting services!\n")); + /* then start all services */ + for (i = 0; ctx->services[i]; i++) { + add_new_service(ctx, ctx->services[i]); + } + } +} + +static int add_services_startup_timeout(struct mt_ctx *ctx) +{ + struct tevent_timer *to; + struct timeval tv; + + /* 5 seconds should be plenty */ + tv = tevent_timeval_current_ofs(5, 0); + to = tevent_add_timer(ctx->ev, ctx, tv, services_startup_timeout, ctx); + if (!to) { + DEBUG(0,("Out of memory?!\n")); + return ENOMEM; + } + + return EOK; +} + +struct sbus_method monitor_methods[] = { + { MON_SRV_METHOD_VERSION, get_monitor_version }, + { MON_SRV_METHOD_REGISTER, client_registration }, + { NULL, NULL } +}; + +struct sbus_interface monitor_server_interface = { + MON_SRV_INTERFACE, + MON_SRV_PATH, + SBUS_DEFAULT_VTABLE, + monitor_methods, + NULL +}; + +/* monitor_dbus_init + * Set up the monitor service as a D-BUS Server */ +static int monitor_dbus_init(struct mt_ctx *ctx) +{ + char *monitor_address; + int ret; + + ret = monitor_get_sbus_address(ctx, &monitor_address); + if (ret != EOK) { + return ret; + } + + ret = sbus_new_server(ctx, ctx->ev, + monitor_address, &monitor_server_interface, + &ctx->sbus_srv, monitor_service_init, ctx); + + talloc_free(monitor_address); + + return ret; +} + +static void svc_try_restart(struct mt_svc *svc, time_t now) +{ + int ret; + + DLIST_REMOVE(svc->mt_ctx->svc_list, svc); + if (svc->last_restart != 0) { + if ((now - svc->last_restart) > 30) { /* TODO: get val from config */ + /* it was long ago reset restart threshold */ + svc->restarts = 0; + } + } + + /* restart the process */ + if (svc->restarts > 3) { /* TODO: get val from config */ + DEBUG(0, ("Process [%s], definitely stopped!\n", svc->name)); + talloc_free(svc); + return; + } + + /* Shut down the current ping timer so it will restart + * cleanly in start_service() + */ + talloc_free(svc->ping_ev); + + ret = start_service(svc); + if (ret != EOK) { + DEBUG(0,("Failed to restart service '%s'\n", svc->name)); + talloc_free(svc); + return; + } + + svc->restarts++; + svc->last_restart = now; + return; +} + +static void tasks_check_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct mt_svc *svc = talloc_get_type(ptr, struct mt_svc); + time_t now = time(NULL); + bool process_alive = true; + int ret; + + ret = service_check_alive(svc); + switch (ret) { + case EOK: + /* all fine */ + break; + + case ECHILD: + DEBUG(1,("Process (%s) is stopped!\n", svc->name)); + process_alive = false; + break; + + default: + /* TODO: should we tear down it ? */ + DEBUG(1,("Checking for service %s(%d) failed!!\n", + svc->name, svc->pid)); + break; + } + + if (process_alive) { + ret = service_send_ping(svc); + switch (ret) { + case EOK: + /* all fine */ + break; + + case ENXIO: + DEBUG(1,("Child (%s) not responding! (yet)\n", svc->name)); + break; + + default: + /* TODO: should we tear it down ? */ + DEBUG(1,("Sending a message to service (%s) failed!!\n", svc->name)); + break; + } + + if (svc->last_ping != 0) { + if ((now - svc->last_ping) > (svc->ping_time)) { + svc->failed_pongs++; + } else { + svc->failed_pongs = 0; + } + if (svc->failed_pongs > 3) { + /* too long since we last heard of this process */ + DEBUG(1, ("Killing service [%s], not responding to pings!\n", + svc->name)); + monitor_kill_service(svc); + process_alive = false; + } + } + + svc->last_ping = now; + } + + if (!process_alive) { + svc_try_restart(svc, now); + return; + } + + /* all fine, set up the task checker again */ + set_tasks_checker(svc); +} + +static void set_tasks_checker(struct mt_svc *svc) +{ + struct tevent_timer *te = NULL; + struct timeval tv; + + gettimeofday(&tv, NULL); + tv.tv_sec += svc->ping_time; + tv.tv_usec = 0; + te = tevent_add_timer(svc->mt_ctx->ev, svc, tv, tasks_check_handler, svc); + if (te == NULL) { + DEBUG(0, ("failed to add event, monitor offline for [%s]!\n", + svc->name)); + /* FIXME: shutdown ? */ + } + svc->ping_ev = te; +} + +static void global_checks_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct mt_ctx *ctx = talloc_get_type(ptr, struct mt_ctx); + struct mt_svc *svc; + int status; + pid_t pid; + + if (!ctx->check_children) { + goto done; + } + + errno = 0; + pid = waitpid(0, &status, WNOHANG); + if (pid == 0) { + goto done; + } + + if (pid == -1) { + DEBUG(0, ("waitpid returned -1 (errno:%d[%s])\n", + errno, strerror(errno))); + goto done; + } + + /* let's see if it is a known service, and try to restart it */ + for (svc = ctx->svc_list; svc; svc = svc->next) { + if (svc->pid == pid) { + time_t now = time(NULL); + DEBUG(1, ("Service [%s] did exit\n", svc->name)); + svc_try_restart(svc, now); + goto done; + } + } + if (svc == NULL) { + DEBUG(0, ("Unknown child (%d) did exit\n", pid)); + } + +done: + set_global_checker(ctx); +} + +static void set_global_checker(struct mt_ctx *ctx) +{ + struct tevent_timer *te = NULL; + struct timeval tv; + + gettimeofday(&tv, NULL); + tv.tv_sec += 1; /* once a second */ + tv.tv_usec = 0; + te = tevent_add_timer(ctx->ev, ctx, tv, global_checks_handler, ctx); + if (te == NULL) { + DEBUG(0, ("failed to add global checker event! PANIC TIME!\n")); + /* FIXME: is this right ? shoulkd we try to clean up first ?*/ + exit(-1); + } +} + +static int monitor_kill_service (struct mt_svc *svc) +{ + int ret; + ret = kill(svc->pid, SIGTERM); + if (ret != EOK) { + DEBUG(0,("Sending signal to child (%s:%d) failed! " + "Ignore and pretend child is dead.\n", + svc->name, svc->pid)); + } + + return ret; +} + +static void shutdown_reply(DBusPendingCall *pending, void *data) +{ + DBusMessage *reply; + int type; + struct mt_svc *svc = talloc_get_type(data, struct mt_svc); + + reply = dbus_pending_call_steal_reply(pending); + if (!reply) { + /* reply should never be null. This function shouldn't be called + * until reply is valid or timeout has occurred. If reply is NULL + * here, something is seriously wrong and we should bail out. + */ + DEBUG(0, ("A reply callback was called but no reply was received" + " and no timeout occurred\n")); + + /* Destroy this connection */ + monitor_kill_service(svc); + goto done; + } + + type = dbus_message_get_type(reply); + switch(type) { + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + /* Ok, we received a confirmation of shutdown */ + break; + + default: + /* Something went wrong on the client side + * Time to forcibly kill the service + */ + DEBUG(0, ("Received an error shutting down service.\n")); + monitor_kill_service(svc); + } + + /* No matter what happened here, we need to free the service */ + talloc_free(svc); + +done: + dbus_pending_call_unref(pending); + dbus_message_unref(reply); +} + +/* monitor_shutdown_service + * Orders a monitored service to shut down cleanly + * This function will free the memory for svc once it + * completes. + */ +static int monitor_shutdown_service(struct mt_svc *svc) +{ + DBusConnection *dbus_conn; + DBusMessage *msg; + DBusPendingCall *pending_reply; + dbus_bool_t dbret; + + /* Stop the service checker */ + + dbus_conn = sbus_get_connection(svc->conn); + + /* Construct a shutdown message */ + msg = dbus_message_new_method_call(NULL, + MONITOR_PATH, + MONITOR_INTERFACE, + MON_CLI_METHOD_SHUTDOWN); + if (!msg) { + DEBUG(0,("Out of memory?!\n")); + monitor_kill_service(svc); + talloc_free(svc); + return ENOMEM; + } + + dbret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply, + svc->mt_ctx->service_id_timeout); + if (!dbret || pending_reply == NULL) { + DEBUG(0, ("D-BUS send failed.\n")); + dbus_message_unref(msg); + monitor_kill_service(svc); + talloc_free(svc); + return EIO; + } + + /* Set up the reply handler */ + dbus_pending_call_set_notify(pending_reply, shutdown_reply, svc, NULL); + dbus_message_unref(msg); + + return EOK; +} + +static void reload_reply(DBusPendingCall *pending, void *data) +{ + DBusMessage *reply; + struct mt_svc *svc = talloc_get_type(data, struct mt_svc); + + reply = dbus_pending_call_steal_reply(pending); + if (!reply) { + /* reply should never be null. This function shouldn't be called + * until reply is valid or timeout has occurred. If reply is NULL + * here, something is seriously wrong and we should bail out. + */ + DEBUG(0, ("A reply callback was called but no reply was received" + " and no timeout occurred\n")); + + /* Destroy this connection */ + sbus_disconnect(svc->conn); + goto done; + } + + /* TODO: Handle cases where the call has timed out or returned + * with an error. + */ +done: + dbus_pending_call_unref(pending); + dbus_message_unref(reply); +} + +#if 0 +This function should be re-enabled once live configuration updates +are working properly. + +static int monitor_signal_reconf(struct config_file_ctx *file_ctx, + const char *filename) +{ + int ret; + DEBUG(1, ("Configuration has changed. Reloading.\n")); + + /* Update the confdb configuration */ + ret = confdb_init_db(filename, file_ctx->mt_ctx->cdb); + if (ret != EOK) { + DEBUG(0, ("Could not reload configuration!")); + kill(getpid(), SIGTERM); + return ret; + } + + /* Update the monitor's configuration and signal children */ + return update_monitor_config(file_ctx->mt_ctx); +} +#endif + +static int service_signal_dns_reload(struct mt_svc *svc); +static int monitor_update_resolv(struct config_file_ctx *file_ctx, + const char *filename) +{ + int ret; + struct mt_svc *cur_svc; + DEBUG(2, ("Resolv.conf has been updated. Reloading.\n")); + + ret = res_init(); + if(ret != 0) { + return EIO; + } + + /* Signal all services to reload their DNS configuration */ + for(cur_svc = file_ctx->mt_ctx->svc_list; cur_svc; cur_svc = cur_svc->next) { + service_signal_dns_reload(cur_svc); + } + return EOK; +} + +static int service_signal(struct mt_svc *svc, const char *svc_signal) +{ + DBusMessage *msg; + dbus_bool_t dbret; + DBusConnection *dbus_conn; + DBusPendingCall *pending_reply; + + if (svc->provider && strcasecmp(svc->provider, "local") == 0) { + /* The local provider requires no signaling */ + return EOK; + } + + if (!svc->conn) { + /* Avoid a race condition where we are trying to + * order a service to reload that hasn't started + * yet. + */ + DEBUG(1,("Could not signal service [%s].\n", svc->name)); + return EIO; + } + + dbus_conn = sbus_get_connection(svc->conn); + msg = dbus_message_new_method_call(NULL, + MONITOR_PATH, + MONITOR_INTERFACE, + svc_signal); + if (!msg) { + DEBUG(0,("Out of memory?!\n")); + monitor_kill_service(svc); + talloc_free(svc); + return ENOMEM; + } + + dbret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply, + svc->mt_ctx->service_id_timeout); + if (!dbret || pending_reply == NULL) { + DEBUG(0, ("D-BUS send failed.\n")); + dbus_message_unref(msg); + monitor_kill_service(svc); + talloc_free(svc); + return EIO; + } + + /* Set up the reply handler */ + dbus_pending_call_set_notify(pending_reply, reload_reply, svc, NULL); + dbus_message_unref(msg); + + return EOK; +} + +static int service_signal_reload(struct mt_svc *svc) +{ + return service_signal(svc, MON_CLI_METHOD_RELOAD); +} +static int service_signal_dns_reload(struct mt_svc *svc) +{ + return service_signal(svc, MON_CLI_METHOD_RES_INIT); +} +static int service_signal_offline(struct mt_svc *svc) +{ + return service_signal(svc, MON_CLI_METHOD_OFFLINE); +} + +static int check_domain_ranges(struct sss_domain_info *domains) +{ + struct sss_domain_info *dom = domains, *other = NULL; + uint32_t id_min, id_max; + + while (dom) { + other = dom->next; + if (dom->id_max && dom->id_min > dom->id_max) { + DEBUG(1, ("Domain '%s' does not have a valid ID range\n", + dom->name)); + return EINVAL; + } + + while (other) { + id_min = MAX(dom->id_min, other->id_min); + id_max = MIN((dom->id_max ? dom->id_max : UINT32_MAX), + (other->id_max ? other->id_max : UINT32_MAX)); + if (id_min <= id_max) { + DEBUG(1, ("Domains '%s' and '%s' overlap in range %u - %u\n", + dom->name, other->name, id_min, id_max)); + } + other = other->next; + } + dom = dom->next; + } + + return EOK; +} + +static int check_local_domain_unique(struct sss_domain_info *domains) +{ + uint8_t count = 0; + + struct sss_domain_info *dom = domains; + + while (dom) { + if (strcasecmp(dom->provider, "local") == 0) { + count++; + } + + if (count > 1) { + break; + } + + dom = dom->next; + } + + if (count > 1) { + return EINVAL; + } + + return EOK; +} + +static char *check_services(char **services) +{ + const char *known_services[] = { "nss", "pam", NULL }; + int i; + int ii; + + /* Check if services we are about to start are in the list if known */ + for (i = 0; services[i]; i++) { + for (ii=0; known_services[ii]; ii++) { + if (strcasecmp(services[i], known_services[ii]) == 0) { + break; + } + } + + if (known_services[ii] == NULL) { + return services[i]; + } + } + + return NULL; +} + +int get_monitor_config(struct mt_ctx *ctx) +{ + int ret; + int timeout_seconds; + char *badsrv = NULL; + + ret = confdb_get_int(ctx->cdb, ctx, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_SBUS_TIMEOUT, + 10, &timeout_seconds); + if (ret != EOK) { + return ret; + } + + ctx->service_id_timeout = timeout_seconds * 1000; /* service_id_timeout is in ms */ + + ctx->service_ctx = talloc_new(ctx); + if(!ctx->service_ctx) { + return ENOMEM; + } + ret = confdb_get_string_as_list(ctx->cdb, ctx->service_ctx, + CONFDB_MONITOR_CONF_ENTRY, + CONFDB_MONITOR_ACTIVE_SERVICES, + &ctx->services); + if (ret != EOK) { + DEBUG(0, ("No services configured!\n")); + return EINVAL; + } + + badsrv = check_services(ctx->services); + if (badsrv != NULL) { + DEBUG(0, ("Invalid service %s\n", badsrv)); + return EINVAL; + } + + ctx->domain_ctx = talloc_new(ctx); + if(!ctx->domain_ctx) { + return ENOMEM; + } + ret = confdb_get_domains(ctx->cdb, &ctx->domains); + if (ret != EOK) { + DEBUG(0, ("No domains configured.\n")); + return ret; + } + + ret = check_local_domain_unique(ctx->domains); + if (ret != EOK) { + DEBUG(0, ("More than one local domain configured.\n")); + return ret; + } + + /* Check UID/GID overlaps */ + ret = check_domain_ranges(ctx->domains); + if (ret != EOK) { + return ret; + } + + return EOK; +} + +static int get_service_config(struct mt_ctx *ctx, const char *name, + struct mt_svc **svc_cfg) +{ + int ret; + char *path; + struct mt_svc *svc; + + *svc_cfg = NULL; + + svc = talloc_zero(ctx, struct mt_svc); + if (!svc) { + return ENOMEM; + } + svc->mt_ctx = ctx; + + svc->name = talloc_strdup(svc, name); + if (!svc->name) { + talloc_free(svc); + return ENOMEM; + } + + svc->identity = talloc_strdup(svc, name); + if (!svc->identity) { + talloc_free(svc); + return ENOMEM; + } + + path = talloc_asprintf(svc, CONFDB_SERVICE_PATH_TMPL, svc->name); + if (!path) { + talloc_free(svc); + return ENOMEM; + } + + ret = confdb_get_string(ctx->cdb, svc, path, + CONFDB_SERVICE_COMMAND, + NULL, &svc->command); + if (ret != EOK) { + DEBUG(0,("Failed to start service '%s'\n", svc->name)); + talloc_free(svc); + return ret; + } + + if (!svc->command) { + svc->command = talloc_asprintf(svc, "%s/sssd_%s -d %d%s%s", + SSSD_LIBEXEC_PATH, + svc->name, debug_level, + (debug_timestamps? + "": " --debug-timestamps=0"), + (debug_to_file ? + " --debug-to-files":"")); + if (!svc->command) { + talloc_free(svc); + return ENOMEM; + } + } + + ret = confdb_get_int(ctx->cdb, svc, path, + CONFDB_SERVICE_TIMEOUT, + MONITOR_DEF_PING_TIME, &svc->ping_time); + if (ret != EOK) { + DEBUG(0,("Failed to start service '%s'\n", svc->name)); + talloc_free(svc); + return ret; + } + + /* 'timeout = 0' should be translated to the default */ + if (svc->ping_time == 0) { + svc->ping_time = MONITOR_DEF_PING_TIME; + } + + *svc_cfg = svc; + talloc_free(path); + + return EOK; +} + +static int add_new_service(struct mt_ctx *ctx, const char *name) +{ + int ret; + struct mt_svc *svc; + + ret = get_service_config(ctx, name, &svc); + + ret = start_service(svc); + if (ret != EOK) { + DEBUG(0,("Failed to start service '%s'\n", svc->name)); + talloc_free(svc); + } + + return ret; +} + +static int get_provider_config(struct mt_ctx *ctx, const char *name, + struct mt_svc **svc_cfg) +{ + int ret; + char *path; + struct mt_svc *svc; + + *svc_cfg = NULL; + + svc = talloc_zero(ctx, struct mt_svc); + if (!svc) { + return ENOMEM; + } + svc->mt_ctx = ctx; + + talloc_set_destructor((TALLOC_CTX *)svc, svc_destructor); + + svc->name = talloc_strdup(svc, name); + if (!svc->name) { + talloc_free(svc); + return ENOMEM; + } + + svc->identity = talloc_asprintf(svc, "%%BE_%s", svc->name); + if (!svc->identity) { + talloc_free(svc); + return ENOMEM; + } + + path = talloc_asprintf(svc, CONFDB_DOMAIN_PATH_TMPL, name); + if (!path) { + talloc_free(svc); + return ENOMEM; + } + + ret = confdb_get_string(ctx->cdb, svc, path, + CONFDB_DOMAIN_ID_PROVIDER, + NULL, &svc->provider); + if (ret != EOK) { + DEBUG(0, ("Failed to find ID provider from [%s] configuration\n", name)); + talloc_free(svc); + return ret; + } + + ret = confdb_get_string(ctx->cdb, svc, path, + CONFDB_DOMAIN_COMMAND, + NULL, &svc->command); + if (ret != EOK) { + DEBUG(0, ("Failed to find command from [%s] configuration\n", name)); + talloc_free(svc); + return ret; + } + + ret = confdb_get_int(ctx->cdb, svc, path, + CONFDB_DOMAIN_TIMEOUT, + MONITOR_DEF_PING_TIME, &svc->ping_time); + if (ret != EOK) { + DEBUG(0,("Failed to start service '%s'\n", svc->name)); + talloc_free(svc); + return ret; + } + + /* 'timeout = 0' should be translated to the default */ + if (svc->ping_time == 0) { + svc->ping_time = MONITOR_DEF_PING_TIME; + } + + talloc_free(path); + + /* if no provider is present do not run the domain */ + if (!svc->provider) { + talloc_free(svc); + return EIO; + } + + /* if there are no custom commands, build a default one */ + if (!svc->command) { + svc->command = talloc_asprintf(svc, + "%s/sssd_be -d %d%s%s --domain %s", + SSSD_LIBEXEC_PATH, debug_level, + (debug_timestamps?"": " --debug-timestamps=0"), + (debug_to_file?" --debug-to-files":""), + svc->name); + if (!svc->command) { + talloc_free(svc); + return ENOMEM; + } + } + + *svc_cfg = svc; + return EOK; +} + +static int add_new_provider(struct mt_ctx *ctx, const char *name) +{ + int ret; + struct mt_svc *svc; + + ret = get_provider_config(ctx, name, &svc); + if (ret != EOK) { + DEBUG(0, ("Could not get provider configuration for [%s]\n", + name)); + return ret; + } + + if (strcasecmp(svc->provider, "local") == 0) { + /* The LOCAL provider requires no back-end currently + * We'll add it to the service list, but we don't need + * to poll it. + */ + svc->svc_started = true; + DLIST_ADD(ctx->svc_list, svc); + return ENOENT; + } + + ret = start_service(svc); + if (ret != EOK) { + DEBUG(0,("Failed to start service '%s'\n", svc->name)); + talloc_free(svc); + } + + return ret; +} + +static void remove_service(struct mt_ctx *ctx, const char *name) +{ + int ret; + struct mt_svc *cur_svc; + + /* Locate the service object in the list */ + cur_svc = ctx->svc_list; + while (cur_svc != NULL) { + if (strcasecmp(name, cur_svc->name) == 0) + break; + cur_svc = cur_svc->next; + } + if (cur_svc != NULL) { + /* Remove the service from the list */ + DLIST_REMOVE(ctx->svc_list, cur_svc); + + /* Shut it down */ + ret = monitor_shutdown_service(cur_svc); + if (ret != EOK) { + DEBUG(0, ("Unable to shut down service [%s]!", + name)); + /* TODO: Handle this better */ + } + } +} + +static int update_monitor_config(struct mt_ctx *ctx) +{ + int ret, i, j; + struct mt_svc *cur_svc; + struct mt_svc *new_svc; + struct sss_domain_info *dom, *new_dom; + struct mt_ctx *new_config; + + new_config = talloc_zero(NULL, struct mt_ctx); + if(!new_config) { + return ENOMEM; + } + + new_config->ev = ctx->ev; + new_config->cdb = ctx->cdb; + ret = get_monitor_config(new_config); + + ctx->service_id_timeout = new_config->service_id_timeout; + + /* Compare the old and new active services */ + /* Have any services been shut down? */ + for (i = 0; ctx->services[i]; i++) { + /* Search for this service in the new config */ + for (j = 0; new_config->services[j]; j++) { + if (strcasecmp(ctx->services[i], new_config->services[j]) == 0) + break; + } + if (new_config->services[j] == NULL) { + /* This service is no longer configured. + * Shut it down. + */ + remove_service(ctx, ctx->services[i]); + } + } + + /* Have any services been added or changed? */ + for (i = 0; new_config->services[i]; i++) { + /* Search for this service in the old config */ + for (j = 0; ctx->services[j]; j++) { + if (strcasecmp(new_config->services[i], ctx->services[j]) == 0) + break; + } + + if (ctx->services[j] == NULL) { + /* New service added */ + add_new_service(ctx, new_config->services[i]); + } + else { + /* Service already enabled, check for changes */ + /* Locate the service object in the list */ + cur_svc = ctx->svc_list; + for (cur_svc = ctx->svc_list; cur_svc; cur_svc = cur_svc->next) { + if (strcasecmp(ctx->services[i], cur_svc->name) == 0) + break; + } + if (cur_svc == NULL) { + DEBUG(0, ("Service entry missing data\n")); + /* This shouldn't be possible, but if it happens + * we'll throw an error + */ + talloc_free(new_config); + return EIO; + } + + /* Read in the new configuration and compare it with the + * old one. + */ + ret = get_service_config(ctx, new_config->services[i], &new_svc); + if (ret != EOK) { + DEBUG(0, ("Unable to determine if service has changed.\n")); + DEBUG(0, ("Disabling service [%s].\n", + new_config->services[i])); + /* Not much we can do here, no way to know whether the + * current configuration is safe, and restarting the + * service won't work because the new startup requires + * this function to work. The only safe thing to do + * is stop the service. + */ + remove_service(ctx, new_config->services[i]); + continue; + } + + if (strcmp(cur_svc->command, new_svc->command) != 0) { + /* The executable path has changed. We need to + * restart the binary completely. If we send a + * shutdown command, the monitor will automatically + * reload the process with the new command. + */ + talloc_free(cur_svc->command); + talloc_steal(cur_svc, new_svc->command); + cur_svc->command = new_svc->command; + + /* TODO: be more graceful about this */ + monitor_kill_service(cur_svc); + } + + cur_svc->ping_time = new_svc->ping_time; + + talloc_free(new_svc); + } + } + + /* Replace the old service list with the new one */ + talloc_free(ctx->service_ctx); + ctx->service_ctx = talloc_steal(ctx, new_config->service_ctx); + ctx->services = new_config->services; + + /* Compare data providers */ + /* Have any providers been disabled? */ + for (dom = ctx->domains; dom; dom = dom->next) { + for (new_dom = new_config->domains; new_dom; new_dom = new_dom->next) { + if (strcasecmp(dom->name, new_dom->name) == 0) break; + } + if (new_dom == NULL) { + /* This provider is no longer configured + * Shut it down + */ + remove_service(ctx, dom->name); + } + } + + /* Have we added or changed any providers? */ + for (new_dom = new_config->domains; new_dom; new_dom = new_dom->next) { + /* Search for this service in the old config */ + for (dom = ctx->domains; dom; dom = dom->next) { + if (strcasecmp(dom->name, new_dom->name) == 0) break; + } + + if (dom == NULL) { + /* New provider added */ + add_new_provider(ctx, new_dom->name); + } + else { + /* Provider is already in the list. + * Check for changes. + */ + /* Locate the service object in the list */ + cur_svc = ctx->svc_list; + while (cur_svc != NULL) { + if (strcasecmp(new_dom->name, cur_svc->name) == 0) + break; + cur_svc = cur_svc->next; + } + if (cur_svc == NULL) { + DEBUG(0, ("Service entry missing data for [%s]\n", new_dom->name)); + /* This shouldn't be possible + */ + talloc_free(new_config); + return EIO; + } + + /* Read in the new configuration and compare it with + * the old one. + */ + ret = get_provider_config(ctx, new_dom->name, &new_svc); + if (ret != EOK) { + DEBUG(0, ("Unable to determine if service has changed.\n")); + DEBUG(0, ("Disabling service [%s].\n", + new_config->services[i])); + /* Not much we can do here, no way to know whether the + * current configuration is safe, and restarting the + * service won't work because the new startup requires + * this function to work. The only safe thing to do + * is stop the service. + */ + remove_service(ctx, dom->name); + continue; + } + + if ((strcmp(cur_svc->command, new_svc->command) != 0) || + (strcmp(cur_svc->provider, new_svc->provider) != 0)) { + /* The executable path or the provider has changed. + * We need to restart the binary completely. If we + * send a shutdown command, the monitor will + * automatically reload the process with the new + * command. + */ + talloc_free(cur_svc->command); + talloc_steal(cur_svc, new_svc->command); + cur_svc->command = new_svc->command; + + /* TODO: be more graceful about this */ + monitor_kill_service(cur_svc); + } + + cur_svc->ping_time = new_svc->ping_time; + } + + } + + /* Replace the old domain list with the new one */ + talloc_free(ctx->domain_ctx); + ctx->domain_ctx = talloc_steal(ctx, new_config->domain_ctx); + ctx->domains = new_config->domains; + + /* Signal all services to reload their configuration */ + for(cur_svc = ctx->svc_list; cur_svc; cur_svc = cur_svc->next) { + service_signal_reload(cur_svc); + } + + talloc_free(new_config); + return EOK; +} + +static void monitor_hup(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + struct mt_ctx *ctx = talloc_get_type(private_data, struct mt_ctx); + + DEBUG(1, ("Received SIGHUP. Rereading configuration.\n")); + update_monitor_config(ctx); +} + +static int monitor_cleanup(void) +{ + char *file; + int ret; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return ENOMEM; + + file = talloc_asprintf(tmp_ctx, "%s/%s.pid", PID_PATH, "sssd"); + if (file == NULL) { + return ENOMEM; + } + + errno = 0; + ret = unlink(file); + if (ret == -1) { + ret = errno; + DEBUG(0, ("Error removing pidfile! (%d [%s])\n", + ret, strerror(ret))); + talloc_free(file); + return errno; + } + + talloc_free(file); + return EOK; +} + +static void monitor_quit(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + DEBUG(8, ("Received shutdown command\n")); + monitor_cleanup(); + +#if HAVE_GETPGRP + if (getpgrp() == getpid()) { + DEBUG(0,("%s: killing children\n", strsignal(signum))); + kill(-getpgrp(), SIGTERM); + } +#endif + + exit(0); +} + +static void signal_offline(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + struct mt_ctx *monitor; + struct mt_svc *cur_svc; + + monitor = talloc_get_type(private_data, struct mt_ctx); + + DEBUG(8, ("Signaling providers to go offline immediately.\n")); + + /* Signal all providers to immediately go offline */ + for(cur_svc = monitor->svc_list; cur_svc; cur_svc = cur_svc->next) { + /* Don't signal services, only providers */ + if (cur_svc->provider) { + service_signal_offline(cur_svc); + } + } +} + +int read_config_file(const char *config_file) +{ + int ret; + struct collection_item *sssd_config = NULL; + struct collection_item *error_list = NULL; + + /* Read the configuration into a collection */ + ret = config_from_file("sssd", config_file, &sssd_config, + INI_STOP_ON_ANY, &error_list); + if (ret != EOK) { + DEBUG(0, ("Parse error reading configuration file [%s]\n", + config_file)); + print_file_parsing_errors(stderr, error_list); + } + + free_ini_config_errors(error_list); + free_ini_config(sssd_config); + return ret; +} + +static errno_t load_configuration(TALLOC_CTX *mem_ctx, + const char *config_file, + struct mt_ctx **monitor) +{ + errno_t ret; + struct mt_ctx *ctx; + char *cdb_file = NULL; + + ctx = talloc_zero(mem_ctx, struct mt_ctx); + if(!ctx) { + return ENOMEM; + } + + cdb_file = talloc_asprintf(ctx, "%s/%s", DB_PATH, CONFDB_FILE); + if (cdb_file == NULL) { + DEBUG(0,("Out of memory, aborting!\n")); + ret = ENOMEM; + goto done; + } + + ret = confdb_init(ctx, &ctx->cdb, cdb_file); + if (ret != EOK) { + DEBUG(0,("The confdb initialization failed\n")); + goto done; + } + + /* Initialize the CDB from the configuration file */ + ret = confdb_test(ctx->cdb); + if (ret == ENOENT) { + /* First-time setup */ + + /* Purge any existing confdb in case an old + * misconfiguration gets in the way + */ + talloc_zfree(ctx->cdb); + unlink(cdb_file); + + ret = confdb_init(ctx, &ctx->cdb, cdb_file); + if (ret != EOK) { + DEBUG(0,("The confdb initialization failed\n")); + goto done; + } + + /* Load special entries */ + ret = confdb_create_base(ctx->cdb); + if (ret != EOK) { + DEBUG(0, ("Unable to load special entries into confdb\n")); + goto done; + } + } else if (ret != EOK) { + DEBUG(0, ("Fatal error initializing confdb\n")); + goto done; + } + talloc_zfree(cdb_file); + + ret = confdb_init_db(config_file, ctx->cdb); + if (ret != EOK) { + DEBUG(0, ("ConfDB initialization has failed [%s]\n", + strerror(ret))); + goto done; + } + + /* Validate the configuration in the database */ + /* Read in the monitor's configuration */ + ret = get_monitor_config(ctx); + if (ret != EOK) { + goto done; + } + + *monitor = ctx; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(ctx); + } + return ret; +} + +#ifdef HAVE_SYS_INOTIFY_H +static void process_config_file(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr); + +static void config_file_changed(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, void *data) +{ + struct tevent_timer *te = NULL; + struct timeval tv; + struct config_file_ctx *file_ctx; + + file_ctx = talloc_get_type(data, struct config_file_ctx); + if (file_ctx->needs_update) { + /* Skip updating. It's already queued for update. + */ + return; + } + + /* We will queue the file for update in one second. + * This way, if there is a script writing to the file + * repeatedly, we won't be attempting to update multiple + * times. + */ + gettimeofday(&tv, NULL); + tv.tv_sec += 1; + + te = tevent_add_timer(ev, ev, tv, process_config_file, file_ctx); + if (!te) { + DEBUG(0, ("Unable to queue config file update! Exiting.")); + kill(getpid(), SIGTERM); + return; + } + file_ctx->needs_update = 1; +} + +struct rewatch_ctx { + struct config_file_callback *cb; + struct config_file_ctx *file_ctx; +}; +static void rewatch_config_file(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr); +static void process_config_file(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + TALLOC_CTX *tmp_ctx; + struct inotify_event *in_event; + char *buf; + char *name; + ssize_t len, total_len; + ssize_t event_size; + struct config_file_ctx *file_ctx; + struct config_file_callback *cb; + struct rewatch_ctx *rw_ctx; + + event_size = sizeof(struct inotify_event); + file_ctx = talloc_get_type(ptr, struct config_file_ctx); + + DEBUG(1, ("Processing config file changes\n")); + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) return; + + buf = talloc_size(tmp_ctx, event_size); + if (!buf) { + talloc_free(tmp_ctx); + return; + } + + total_len = 0; + while (total_len < event_size) { + len = read(file_ctx->mt_ctx->inotify_fd, buf+total_len, + event_size-total_len); + if (len == -1 && errno != EINTR) { + DEBUG(0, ("Critical error reading inotify file descriptor.\n")); + talloc_free(tmp_ctx); + return; + } + total_len += len; + } + + in_event = (struct inotify_event *)buf; + + if (in_event->len > 0) { + /* Read in the name, even though we don't use it, + * so that read ptr is in the right place + */ + name = talloc_size(tmp_ctx, len); + if (!name) { + talloc_free(tmp_ctx); + return; + } + total_len = 0; + while (total_len < in_event->len) { + len = read(file_ctx->mt_ctx->inotify_fd, &name, in_event->len); + if (len == -1 && errno != EINTR) { + DEBUG(0, ("Critical error reading inotify file descriptor.\n")); + talloc_free(tmp_ctx); + return; + } + total_len += len; + } + } + + talloc_free(tmp_ctx); + + for (cb = file_ctx->callbacks; cb; cb = cb->next) { + if (cb->wd == in_event->wd) { + break; + } + } + if (!cb) { + DEBUG(0, ("Unknown watch descriptor\n")); + return; + } + + if (in_event->mask & IN_IGNORED) { + /* Some text editors will move a new file on top of the + * existing one instead of modifying it. In this case, + * the kernel will send us an IN_IGNORE signal. + * We will try to open a new watch descriptor on the + * new file. + */ + struct timeval tv; + struct tevent_timer *tev; + tv.tv_sec = t.tv_sec+5; + tv.tv_usec = t.tv_usec; + + cb->retries = 0; + rw_ctx = talloc(file_ctx, struct rewatch_ctx); + if(!rw_ctx) { + DEBUG(0, ("Could not restore inotify watch. Quitting!\n")); + close(file_ctx->mt_ctx->inotify_fd); + kill(getpid(), SIGTERM); + return; + } + rw_ctx->cb = cb; + rw_ctx->file_ctx = file_ctx; + + tev = tevent_add_timer(ev, rw_ctx, tv, rewatch_config_file, rw_ctx); + if (te == NULL) { + DEBUG(0, ("Could not restore inotify watch. Quitting!\n")); + close(file_ctx->mt_ctx->inotify_fd); + kill(getpid(), SIGTERM); + } + return; + } + + /* Tell the monitor to signal the children */ + cb->fn(file_ctx, cb->filename); + file_ctx->needs_update = 0; +} + +static void rewatch_config_file(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + int err; + struct tevent_timer *tev = NULL; + struct timeval tv; + struct config_file_callback *cb; + + struct rewatch_ctx *rw_ctx; + struct config_file_ctx *file_ctx; + + rw_ctx = talloc_get_type(ptr, struct rewatch_ctx); + + cb = rw_ctx->cb; + file_ctx = rw_ctx->file_ctx; + + /* Retry six times at five-second intervals before giving up */ + cb->retries++; + if (cb->retries > 6) { + DEBUG(0, ("Could not restore inotify watch. Quitting!\n")); + close(file_ctx->mt_ctx->inotify_fd); + kill(getpid(), SIGTERM); + } + + cb->wd = inotify_add_watch(file_ctx->mt_ctx->inotify_fd, + cb->filename, IN_MODIFY); + if (cb->wd < 0) { + err = errno; + + tv.tv_sec = t.tv_sec+5; + tv.tv_usec = t.tv_usec; + + DEBUG(1, ("Could not add inotify watch for file [%s]. Error [%d:%s]\n", + cb->filename, err, strerror(err))); + + tev = tevent_add_timer(ev, ev, tv, rewatch_config_file, rw_ctx); + if (te == NULL) { + DEBUG(0, ("Could not restore inotify watch. Quitting!\n")); + close(file_ctx->mt_ctx->inotify_fd); + kill(getpid(), SIGTERM); + } + + return; + } + cb->retries = 0; + + /* Tell the monitor to signal the children */ + cb->fn(file_ctx, cb->filename); + + talloc_free(rw_ctx); + file_ctx->needs_update = 0; +} +#endif + +static void poll_config_file(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + int ret, err; + struct stat file_stat; + struct timeval tv; + struct config_file_ctx *file_ctx; + struct config_file_callback *cb; + + file_ctx = talloc_get_type(ptr,struct config_file_ctx); + + for (cb = file_ctx->callbacks; cb; cb = cb->next) { + ret = stat(cb->filename, &file_stat); + if (ret < 0) { + err = errno; + DEBUG(0, ("Could not stat file [%s]. Error [%d:%s]\n", + cb->filename, err, strerror(err))); + /* TODO: If the config file is missing, should we shut down? */ + return; + } + + if (file_stat.st_mtime != cb->modified) { + /* Parse the configuration file and signal the children */ + /* Note: this will fire if the modification time changes into the past + * as well as the future. + */ + DEBUG(1, ("Config file changed\n")); + cb->modified = file_stat.st_mtime; + + /* Tell the monitor to signal the children */ + cb->fn(file_ctx, cb->filename); + } + } + + gettimeofday(&tv, NULL); + tv.tv_sec += CONFIG_FILE_POLL_INTERVAL; + tv.tv_usec = 0; + file_ctx->timer = tevent_add_timer(ev, file_ctx->parent_ctx, tv, + poll_config_file, file_ctx); + if (!file_ctx->timer) { + DEBUG(0, ("Error: Config file no longer monitored for changes!")); + } +} + +static int try_inotify(struct config_file_ctx *file_ctx, const char *filename, + monitor_reconf_fn fn) +{ +#ifdef HAVE_SYS_INOTIFY_H + int err, fd_args, ret; + struct tevent_fd *tfd; + struct config_file_callback *cb; + + /* Monitoring the file descriptor should be global */ + if (!file_ctx->mt_ctx->inotify_fd) { + /* Set up inotify to monitor the config file for changes */ + file_ctx->mt_ctx->inotify_fd = inotify_init(); + if (file_ctx->mt_ctx->inotify_fd < 0) { + err = errno; + DEBUG(0, ("Could not initialize inotify, error [%d:%s]\n", + err, strerror(err))); + return err; + } + + fd_args = fcntl(file_ctx->mt_ctx->inotify_fd, F_GETFL, NULL); + if (fd_args < 0) { + /* Could not set nonblocking */ + close(file_ctx->mt_ctx->inotify_fd); + return EINVAL; + } + + fd_args |= O_NONBLOCK; + ret = fcntl(file_ctx->mt_ctx->inotify_fd, F_SETFL, fd_args); + if (ret < 0) { + /* Could not set nonblocking */ + close(file_ctx->mt_ctx->inotify_fd); + return EINVAL; + } + + /* Add the inotify file descriptor to the TEvent context */ + tfd = tevent_add_fd(file_ctx->mt_ctx->ev, file_ctx, + file_ctx->mt_ctx->inotify_fd, + TEVENT_FD_READ, config_file_changed, + file_ctx); + if (!tfd) { + close(file_ctx->mt_ctx->inotify_fd); + return EIO; + } + } + + cb = talloc_zero(file_ctx, struct config_file_callback); + if(!cb) { + close(file_ctx->mt_ctx->inotify_fd); + return EIO; + } + + cb->filename = talloc_strdup(cb, filename); + if (!cb->filename) { + close(file_ctx->mt_ctx->inotify_fd); + return ENOMEM; + } + cb->wd = inotify_add_watch(file_ctx->mt_ctx->inotify_fd, + cb->filename, IN_MODIFY); + if (cb->wd < 0) { + err = errno; + DEBUG(0, ("Could not add inotify watch for file [%s]. Error [%d:%s]\n", + cb->filename, err, strerror(err))); + close(file_ctx->mt_ctx->inotify_fd); + return err; + } + cb->fn = fn; + + DLIST_ADD(file_ctx->callbacks, cb); + + return EOK; +#else + return EINVAL; +#endif +} + +static int monitor_config_file(TALLOC_CTX *mem_ctx, + struct mt_ctx *ctx, + const char *file, + monitor_reconf_fn fn) +{ + int ret, err; + struct timeval tv; + struct stat file_stat; + struct config_file_callback *cb = NULL; + + ret = stat(file, &file_stat); + if (ret < 0) { + err = errno; + DEBUG(0, ("Could not stat file [%s]. Error [%d:%s]\n", + file, err, strerror(err))); + return err; + } + if (!ctx->file_ctx) { + ctx->file_ctx = talloc_zero(mem_ctx, struct config_file_ctx); + if (!ctx->file_ctx) return ENOMEM; + + ctx->file_ctx->parent_ctx = mem_ctx; + ctx->file_ctx->mt_ctx = ctx; + } + ret = try_inotify(ctx->file_ctx, file, fn); + if (ret != EOK) { + /* Could not monitor file with inotify, fall back to polling */ + cb = talloc_zero(ctx->file_ctx, struct config_file_callback); + if (!cb) { + talloc_free(ctx->file_ctx); + return ENOMEM; + } + cb->filename = talloc_strdup(cb, file); + if (!cb->filename) { + talloc_free(ctx->file_ctx); + return ENOMEM; + } + cb->fn = fn; + cb->modified = file_stat.st_mtime; + + DLIST_ADD(ctx->file_ctx->callbacks, cb); + + if(!ctx->file_ctx->timer) { + gettimeofday(&tv, NULL); + tv.tv_sec += CONFIG_FILE_POLL_INTERVAL; + tv.tv_usec = 0; + ctx->file_ctx->timer = tevent_add_timer(ctx->ev, mem_ctx, tv, + poll_config_file, ctx->file_ctx); + if (!ctx->file_ctx->timer) { + talloc_free(ctx->file_ctx); + return EIO; + } + } + } + + return EOK; +} + +int monitor_process_init(struct mt_ctx *ctx, + const char *config_file) +{ + TALLOC_CTX *tmp_ctx; + struct sysdb_ctx_list *db_list; + struct tevent_signal *tes; + struct sss_domain_info *dom; + int num_providers; + int ret; + +#if 0 + This feature is incomplete and can leave the SSSD in a bad state if the + config file is changed while the SSSD is running. + + Uncomment this once the backends are honoring reloadConfig() + + /* Watch for changes to the confdb config file */ + ret = monitor_config_file(ctx, ctx, config_file, monitor_signal_reconf); + if (ret != EOK) { + return ret; + } +#endif + /* Watch for changes to the DNS resolv.conf */ + ret = monitor_config_file(ctx, ctx, RESOLV_CONF_PATH, + monitor_update_resolv); + if (ret != EOK) { + return ret; + } + + /* Avoid a startup race condition between process. + * We need to handle DB upgrades or DB creation only + * in one process before all other start. + */ + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + ret = sysdb_init(tmp_ctx, ctx->ev, ctx->cdb, NULL, true, &db_list); + if (ret != EOK) { + return ret; + } + talloc_zfree(tmp_ctx); + + /* Initialize D-BUS Server + * The monitor will act as a D-BUS server for all + * SSSD processes */ + ret = monitor_dbus_init(ctx); + if (ret != EOK) { + return ret; + } + + /* start providers */ + num_providers = 0; + for (dom = ctx->domains; dom; dom = dom->next) { + ret = add_new_provider(ctx, dom->name); + if (ret != EOK && ret != ENOENT) { + return ret; + } + if (ret != ENOENT) { + num_providers++; + } + } + + if (num_providers > 0) { + /* now set the services stratup timeout * + * (responders will be started automatically when all + * providers are up and running or when the tomeout + * expires) */ + ret = add_services_startup_timeout(ctx); + if (ret != EOK) { + return ret; + } + } else { + int i; + + ctx->services_started = true; + + /* No providers start services immediately + * Normally this means only LOCAL is configured */ + for (i = 0; ctx->services[i]; i++) { + add_new_service(ctx, ctx->services[i]); + } + } + + /* now start checking for global events */ + set_global_checker(ctx); + + /* Set up an event handler for a SIGHUP */ + tes = tevent_add_signal(ctx->ev, ctx, SIGHUP, 0, + monitor_hup, ctx); + if (tes == NULL) { + return EIO; + } + + /* Set up an event handler for a SIGINT */ + tes = tevent_add_signal(ctx->ev, ctx, SIGINT, 0, + monitor_quit, ctx); + if (tes == NULL) { + return EIO; + } + + /* Set up an event handler for a SIGTERM */ + tes = tevent_add_signal(ctx->ev, ctx, SIGTERM, 0, + monitor_quit, ctx); + if (tes == NULL) { + return EIO; + } + + /* Handle SIGUSR1 (tell all providers to go offline) */ + BlockSignals(false, SIGUSR1); + tes = tevent_add_signal(ctx->ev, ctx, SIGUSR1, 0, + signal_offline, ctx); + if (tes == NULL) { + return EIO; + } + + return EOK; +} + +static void init_timeout(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct mon_init_conn *mini; + + DEBUG(2, ("Client timed out before Identification!\n")); + + mini = talloc_get_type(ptr, struct mon_init_conn); + + sbus_disconnect(mini->conn); + talloc_zfree(mini); +} + +/* + * monitor_service_init + * Set up a timeout function and temporary connection structure. + * If the client does not identify before the timeout kicks in, + * the client is forcibly disconnected. + */ +static int monitor_service_init(struct sbus_connection *conn, void *data) +{ + struct mt_ctx *ctx; + struct mon_init_conn *mini; + struct timeval tv; + + DEBUG(3, ("Initializing D-BUS Service\n")); + + ctx = talloc_get_type(data, struct mt_ctx); + + mini = talloc(conn, struct mon_init_conn); + if (!mini) { + DEBUG(0,("Out of memory?!\n")); + talloc_zfree(conn); + return ENOMEM; + } + mini->ctx = ctx; + mini->conn = conn; + + /* 5 seconds should be plenty */ + tv = tevent_timeval_current_ofs(10, 0); + + mini->timeout = tevent_add_timer(ctx->ev, mini, tv, init_timeout, mini); + if (!mini->timeout) { + DEBUG(0,("Out of memory?!\n")); + talloc_zfree(conn); + return ENOMEM; + } + + sbus_conn_set_private_data(conn, mini); + + return EOK; +} + +/* service_send_ping + * this function send a dbus ping to a service. + * It returns EOK if all is fine or ENXIO if the connection is + * not available (either not yet set up or teared down). + * Returns e generic error in other cases. + */ +static int service_send_ping(struct mt_svc *svc) +{ + DBusMessage *msg; + DBusPendingCall *pending_reply; + DBusConnection *dbus_conn; + dbus_bool_t dbret; + + if (!svc->conn) { + DEBUG(8, ("Service not yet initialized\n")); + return ENXIO; + } + + DEBUG(4,("Pinging %s\n", svc->name)); + + dbus_conn = sbus_get_connection(svc->conn); + + /* + * Set up identity request + * This should be a well-known path and method + * for all services + */ + msg = dbus_message_new_method_call(NULL, + MONITOR_PATH, + MONITOR_INTERFACE, + MON_CLI_METHOD_PING); + if (!msg) { + DEBUG(0,("Out of memory?!\n")); + talloc_zfree(svc->conn); + return ENOMEM; + } + + dbret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply, + svc->mt_ctx->service_id_timeout); + if (!dbret || pending_reply == NULL) { + /* + * Critical Failure + * We can't communicate on this connection + * We'll drop it using the default destructor. + */ + DEBUG(0, ("D-BUS send failed.\n")); + talloc_zfree(svc->conn); + return EIO; + } + + /* Set up the reply handler */ + dbus_pending_call_set_notify(pending_reply, ping_check, svc, NULL); + dbus_message_unref(msg); + + return EOK; +} + +static void ping_check(DBusPendingCall *pending, void *data) +{ + struct mt_svc *svc; + DBusMessage *reply; + const char *dbus_error_name; + int type; + + svc = talloc_get_type(data, struct mt_svc); + + reply = dbus_pending_call_steal_reply(pending); + if (!reply) { + /* reply should never be null. This function shouldn't be called + * until reply is valid or timeout has occurred. If reply is NULL + * here, something is seriously wrong and we should bail out. + */ + DEBUG(0, ("A reply callback was called but no reply was received" + " and no timeout occurred\n")); + + /* Destroy this connection */ + sbus_disconnect(svc->conn); + goto done; + } + + type = dbus_message_get_type(reply); + switch (type) { + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + /* ok peer replied, + * make sure we reset the failure counter in the service structure */ + + DEBUG(4,("Service %s replied to ping\n", svc->name)); + + svc->failed_pongs = 0; + break; + + case DBUS_MESSAGE_TYPE_ERROR: + + dbus_error_name = dbus_message_get_error_name(reply); + + /* timeouts are handled in the main service check function */ + if (strcmp(dbus_error_name, DBUS_ERROR_TIMEOUT) == 0) + break; + + DEBUG(0,("A service PING returned an error [%s], closing connection.\n", + dbus_error_name)); + /* Falling through to default intentionally*/ + default: + /* + * Timeout or other error occurred or something + * unexpected happened. + * It doesn't matter which, because either way we + * know that this connection isn't trustworthy. + * We'll destroy it now. + */ + sbus_disconnect(svc->conn); + } + +done: + dbus_pending_call_unref(pending); + dbus_message_unref(reply); +} + + + +/* service_check_alive + * This function checks if the service child is still alive + */ +static int service_check_alive(struct mt_svc *svc) +{ + int status; + pid_t pid; + + DEBUG(4,("Checking service %s(%d) is still alive\n", svc->name, svc->pid)); + + pid = waitpid(svc->pid, &status, WNOHANG); + if (pid == 0) { + return EOK; + } + + if (pid != svc->pid) { + DEBUG(1, ("bad return (%d) from waitpid() waiting for %d\n", + pid, svc->pid)); + /* TODO: what do we do now ? */ + return EINVAL; + } + + if (WIFEXITED(status)) { /* children exited on it's own */ + /* TODO: check configuration to see if it was removed + * from the list of process to run */ + DEBUG(0,("Process [%s] exited\n", svc->name)); + } + + return ECHILD; +} + +static void free_args(char **args) +{ + int i; + + if (args) { + for (i = 0; args[i]; i++) free(args[i]); + free(args); + } +} + + +/* parse a string into arguments. + * arguments are separated by a space + * '\' is an escape character and can be used only to escape + * itself or the white space. + */ +static char **parse_args(const char *str) +{ + const char *p; + char **ret, **r; + char *tmp; + int num; + int i, e; + + tmp = malloc(strlen(str) + 1); + if (!tmp) return NULL; + + ret = NULL; + num = 0; + e = 0; + i = 0; + p = str; + while (*p) { + switch (*p) { + case '\\': + if (e) { + tmp[i] = '\\'; + i++; + e = 0; + } else { + e = 1; + } + break; + case ' ': + if (e) { + tmp[i] = ' '; + i++; + e = 0; + } else { + tmp[i] = '\0'; + i++; + } + break; + default: + if (e) { + tmp[i] = '\\'; + i++; + e = 0; + } + tmp[i] = *p; + i++; + break; + } + + p++; + + /* check if this was the last char */ + if (*p == '\0') { + if (e) { + tmp[i] = '\\'; + i++; + e = 0; + } + tmp[i] = '\0'; + i++; + } + if (tmp[i-1] != '\0' || strlen(tmp) == 0) { + /* check next char and skip multiple spaces */ + continue; + } + + r = realloc(ret, (num + 2) * sizeof(char *)); + if (!r) goto fail; + ret = r; + ret[num+1] = NULL; + ret[num] = strdup(tmp); + if (!ret[num]) goto fail; + num++; + i = 0; + } + + free(tmp); + return ret; + +fail: + free(tmp); + free_args(ret); + return NULL; +} + +static void service_startup_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr); + +static int start_service(struct mt_svc *svc) +{ + struct tevent_timer *te; + struct timeval tv; + + DEBUG(4,("Queueing service %s for startup\n", svc->name)); + + /* at startup we need to start the data providers before the responders + * to avoid races where a service starts before sbus pipes are ready + * to accept connections. So if startup is true delay by 2 seconds any + * process that is not a data provider */ + + tv = tevent_timeval_current(); + + /* Add a timed event to start up the service. + * We have to do this in order to avoid a race + * condition where the service being started forks + * and attempts to connect to the SBUS before + * the monitor is serving it. + */ + te = tevent_add_timer(svc->mt_ctx->ev, svc, tv, + service_startup_handler, svc); + if (te == NULL) { + DEBUG(0, ("Unable to queue service %s for startup\n", svc->name)); + return ENOMEM; + } + return EOK; +} + +static void service_startup_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, void *ptr) +{ + struct mt_svc *mt_svc; + char **args; + + mt_svc = talloc_get_type(ptr, struct mt_svc); + if (mt_svc == NULL) { + return; + } + + mt_svc->pid = fork(); + if (mt_svc->pid != 0) { + if (mt_svc->pid == -1) { + DEBUG(0, ("Could not fork child to start service [%s]. Continuing.\n", mt_svc->name)) + return; + } + + /* Parent */ + mt_svc->mt_ctx->check_children = true; + mt_svc->failed_pongs = 0; + DLIST_ADD(mt_svc->mt_ctx->svc_list, mt_svc); + set_tasks_checker(mt_svc); + + return; + } + + /* child */ + + args = parse_args(mt_svc->command); + execvp(args[0], args); + + /* If we are here, exec() has failed + * Print errno and abort quickly */ + DEBUG(0,("Could not exec %s, reason: %s\n", mt_svc->command, strerror(errno))); + + /* We have to call _exit() instead of exit() here + * because a bug in D-BUS will cause the server to + * close its socket at exit() */ + _exit(1); +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + int opt_daemon = 0; + int opt_interactive = 0; + char *opt_config_file = NULL; + char *config_file = NULL; + int flags = 0; + struct main_context *main_ctx; + TALLOC_CTX *tmp_ctx; + struct mt_ctx *monitor; + int ret; + uid_t uid; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_MAIN_OPTS + {"daemon", 'D', POPT_ARG_NONE, &opt_daemon, 0, \ + _("Become a daemon (default)"), NULL }, \ + {"interactive", 'i', POPT_ARG_NONE, &opt_interactive, 0, \ + _("Run interactive (not a daemon)"), NULL}, \ + {"config", 'c', POPT_ARG_STRING, &opt_config_file, 0, \ + _("Specify a non-default config file"), NULL}, \ + { NULL } + }; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + + if (opt_daemon && opt_interactive) { + fprintf(stderr, "Option -i|--interactive is not allowed together with -D|--daemon\n"); + poptPrintUsage(pc, stderr, 0); + return 1; + } + + poptFreeContext(pc); + + uid = getuid(); + if (uid != 0) { + DEBUG(1, ("Running under %d, must be root\n", uid)); + ERROR("sssd must be run as root\n"); + return 8; + } + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return 7; + } + + if (opt_daemon) flags |= FLAGS_DAEMON; + if (opt_interactive) flags |= FLAGS_INTERACTIVE; + + if (opt_config_file) + config_file = talloc_strdup(tmp_ctx, opt_config_file); + else + config_file = talloc_strdup(tmp_ctx, CONFDB_DEFAULT_CONFIG_FILE); + if(!config_file) + return 6; + + /* we want a pid file check */ + flags |= FLAGS_PID_FILE; + + /* Open before server_setup() does to have logging + * during configuration checking */ + if (debug_to_file) { + ret = open_debug_file(); + if (ret) { + return 7; + } + } + + /* Parse config file, fail if cannot be done */ + ret = load_configuration(tmp_ctx, config_file, &monitor); + if (ret != EOK) { + if (ret == EIO) { + DEBUG(1, ("Cannot read configuration file %s\n", config_file)); + ERROR("Cannot read config file %s, please check if permissions " + "are 0600 and the file is owned by root.root\n", config_file); + } + return 4; + } + + /* set up things like debug , signals, daemonization, etc... */ + ret = server_setup("sssd", flags, CONFDB_MONITOR_CONF_ENTRY, &main_ctx); + if (ret != EOK) return 2; + + monitor->ev = main_ctx->event_ctx; + talloc_steal(main_ctx, monitor); + + ret = monitor_process_init(monitor, + config_file); + if (ret != EOK) return 3; + talloc_free(tmp_ctx); + + /* loop on main */ + server_loop(main_ctx); + + ret = monitor_cleanup(); + if (ret != EOK) return 5; + + return 0; +} diff --git a/src/monitor/monitor.h b/src/monitor/monitor.h new file mode 100644 index 00000000..78e10ef1 --- /dev/null +++ b/src/monitor/monitor.h @@ -0,0 +1,36 @@ +/* + SSSD + + Service monitor + + Copyright (C) Simo Sorce 2008 + + 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/>. +*/ + +#ifndef _MONITOR_H_ +#define _MONITOR_H_ + +#define RESOLV_CONF_PATH "/etc/resolv.conf" +#define CONFIG_FILE_POLL_INTERVAL 5 /* seconds */ + +typedef int (*monitor_reconf_fn) (struct config_file_ctx *file_ctx, + const char *filename); + +struct mt_ctx; + +int monitor_process_init(struct mt_ctx *ctx, + const char *config_file); + +#endif /* _MONITOR_H */ diff --git a/src/monitor/monitor_interfaces.h b/src/monitor/monitor_interfaces.h new file mode 100644 index 00000000..c6361fa2 --- /dev/null +++ b/src/monitor/monitor_interfaces.h @@ -0,0 +1,55 @@ +/* + SSSD + + Sbus Interfaces + + Copyright (C) Simo Sorce 2008 + + 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/>. +*/ + +/*** Monitor ***/ + +#define MONITOR_VERSION 0x0001 + +/*** Monitor SRV Interface ***/ +#define MON_SRV_PATH "/org/freedesktop/sssd/monitor" +#define MON_SRV_INTERFACE "org.freedesktop.sssd.monitor" + +/* Monitor SRV Methods */ +#define MON_SRV_METHOD_VERSION "getVersion" +#define MON_SRV_METHOD_REGISTER "RegisterService" + +/*** Monitor CLI Interface ***/ +#define MONITOR_PATH "/org/freedesktop/sssd/service" +#define MONITOR_INTERFACE "org.freedesktop.sssd.service" + +/* Monitor CLI Methods */ +#define MON_CLI_METHOD_IDENTITY "getIdentity" +#define MON_CLI_METHOD_PING "ping" +#define MON_CLI_METHOD_RELOAD "reloadConfig" +#define MON_CLI_METHOD_SHUTDOWN "shutDown" +#define MON_CLI_METHOD_RES_INIT "resInit" +#define MON_CLI_METHOD_OFFLINE "goOffline" /* Applicable only to providers */ + +#define SSSD_SERVICE_PIPE "private/sbus-monitor" + +int monitor_get_sbus_address(TALLOC_CTX *mem_ctx, char **address); +int monitor_common_send_id(struct sbus_connection *conn, + const char *name, uint16_t version); +int monitor_common_pong(DBusMessage *message, + struct sbus_connection *conn); +int monitor_common_res_init(DBusMessage *message, + struct sbus_connection *conn); + diff --git a/src/monitor/monitor_sbus.c b/src/monitor/monitor_sbus.c new file mode 100644 index 00000000..3f73e84f --- /dev/null +++ b/src/monitor/monitor_sbus.c @@ -0,0 +1,195 @@ +/* + SSSD + + Data Provider Helpers + + Copyright (C) Stephen Gallagher <sgallagh@redhat.com> 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 <http://www.gnu.org/licenses/>. +*/ + +/* Needed for res_init() */ +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <resolv.h> + +#include "util/util.h" +#include "confdb/confdb.h" +#include "sbus/sssd_dbus.h" +#include "monitor/monitor_interfaces.h" + +int monitor_get_sbus_address(TALLOC_CTX *mem_ctx, char **address) +{ + char *default_address; + + *address = NULL; + default_address = talloc_asprintf(mem_ctx, "unix:path=%s/%s", + PIPE_PATH, SSSD_SERVICE_PIPE); + if (default_address == NULL) { + return ENOMEM; + } + + *address = default_address; + return EOK; +} + +static void id_callback(DBusPendingCall *pending, void *ptr) +{ + DBusMessage *reply; + DBusError dbus_error; + dbus_bool_t ret; + dbus_uint16_t mon_ver; + int type; + + dbus_error_init(&dbus_error); + + reply = dbus_pending_call_steal_reply(pending); + if (!reply) { + /* reply should never be null. This function shouldn't be called + * until reply is valid or timeout has occurred. If reply is NULL + * here, something is seriously wrong and we should bail out. + */ + DEBUG(0, ("Severe error. A reply callback was called but no" + " reply was received and no timeout occurred\n")); + + /* FIXME: Destroy this connection ? */ + goto done; + } + + type = dbus_message_get_type(reply); + switch (type) { + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + ret = dbus_message_get_args(reply, &dbus_error, + DBUS_TYPE_UINT16, &mon_ver, + DBUS_TYPE_INVALID); + if (!ret) { + DEBUG(1, ("Failed to parse message\n")); + if (dbus_error_is_set(&dbus_error)) dbus_error_free(&dbus_error); + /* FIXME: Destroy this connection ? */ + goto done; + } + + DEBUG(4, ("Got id ack and version (%d) from Monitor\n", mon_ver)); + + break; + + case DBUS_MESSAGE_TYPE_ERROR: + DEBUG(0,("The Monitor returned an error [%s]\n", + dbus_message_get_error_name(reply))); + /* Falling through to default intentionally*/ + default: + /* + * Timeout or other error occurred or something + * unexpected happened. + * It doesn't matter which, because either way we + * know that this connection isn't trustworthy. + * We'll destroy it now. + */ + + /* FIXME: Destroy this connection ? */ + break; + } + +done: + dbus_pending_call_unref(pending); + dbus_message_unref(reply); +} + +int monitor_common_send_id(struct sbus_connection *conn, + const char *name, uint16_t version) +{ + DBusPendingCall *pending_reply; + DBusConnection *dbus_conn; + DBusMessage *msg; + dbus_bool_t ret; + + dbus_conn = sbus_get_connection(conn); + + /* create the message */ + msg = dbus_message_new_method_call(NULL, + MON_SRV_PATH, + MON_SRV_INTERFACE, + MON_SRV_METHOD_REGISTER); + if (msg == NULL) { + DEBUG(0, ("Out of memory?!\n")); + return ENOMEM; + } + + DEBUG(4, ("Sending ID: (%s,%d)\n", name, version)); + + ret = dbus_message_append_args(msg, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_UINT16, &version, + DBUS_TYPE_INVALID); + if (!ret) { + DEBUG(1, ("Failed to build message\n")); + return EIO; + } + + ret = dbus_connection_send_with_reply(dbus_conn, msg, &pending_reply, + 30000 /* TODO: set timeout */); + if (!ret || !pending_reply) { + /* + * Critical Failure + * We can't communicate on this connection + * We'll drop it using the default destructor. + */ + DEBUG(0, ("D-BUS send failed.\n")); + dbus_message_unref(msg); + return EIO; + } + + /* Set up the reply handler */ + dbus_pending_call_set_notify(pending_reply, id_callback, NULL, NULL); + dbus_message_unref(msg); + + return EOK; +} + +int monitor_common_pong(DBusMessage *message, + struct sbus_connection *conn) +{ + DBusMessage *reply; + dbus_bool_t ret; + + reply = dbus_message_new_method_return(message); + if (!reply) return ENOMEM; + + ret = dbus_message_append_args(reply, DBUS_TYPE_INVALID); + if (!ret) { + dbus_message_unref(reply); + return EIO; + } + + /* send reply back */ + sbus_conn_send_reply(conn, reply); + dbus_message_unref(reply); + + return EOK; +} + +int monitor_common_res_init(DBusMessage *message, + struct sbus_connection *conn) +{ + int ret; + + ret = res_init(); + if(ret != 0) { + return EIO; + } + + /* Send an empty reply to acknowledge receipt */ + return monitor_common_pong(message, conn); +} + |