/* 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 . */ #include #include #include #include #include "../events/events.h" #include "util/util.h" #include "service.h" #include "confdb/confdb.h" #include "monitor.h" #include "dbus/dbus.h" #include "dbus/sssd_dbus.h" /* TODO: Get these values from LDB */ #define SERVICE_PATH "/org/freeipa/sssd/service" #define SERVICE_INTERFACE "org.freeipa.sssd.service" #define SERVICE_METHOD_IDENTITY "getIdentity" /* TODO: get this value from LDB */ #define DBUS_ADDRESS "unix:path=/var/lib/sss/pipes/private/dbus" struct mt_ctx { struct event_context *ev; struct confdb_ctx *cdb; char **services; }; struct mt_srv { const char *name; struct mt_ctx *mt_ctx; pid_t pid; time_t last_restart; int restarts; }; static int dbus_service_init(struct dbus_connection_toplevel_context *dct_ctx); static void identity_check(DBusPendingCall *pending, void *data); /* dbus_get_monitor_version * Return the monitor version over D-BUS */ static int dbus_get_monitor_version(DBusMessage *message, void *data, DBusMessage **r) { const char *version = MONITOR_VERSION; DBusMessage *reply; dbus_bool_t ret; reply = dbus_message_new_method_return(message); ret = dbus_message_append_args(reply, DBUS_TYPE_STRING, &version, DBUS_TYPE_INVALID); if (!ret) { return EIO; } *r = reply; return EOK; } struct sssd_dbus_method monitor_methods[] = { { MONITOR_METHOD_VERSION, dbus_get_monitor_version}, {NULL, NULL} }; /* monitor_dbus_init * Set up the monitor service as a D-BUS Server */ static int monitor_dbus_init(struct mt_ctx *ctx) { struct sssd_dbus_method_ctx *sd_ctx; int ret; sd_ctx = talloc_zero(ctx, struct sssd_dbus_method_ctx); if (!sd_ctx) { return ENOMEM; } /* Set up globally-available D-BUS methods */ sd_ctx->interface = talloc_strdup(sd_ctx, MONITOR_DBUS_INTERFACE); if (!sd_ctx->interface) { talloc_free(sd_ctx); return ENOMEM; } sd_ctx->path = talloc_strdup(sd_ctx, MONITOR_DBUS_PATH); if (!sd_ctx->path) { talloc_free(sd_ctx); return ENOMEM; } sd_ctx->methods = monitor_methods; sd_ctx->message_handler = NULL; /* Use the default message_handler */ ret = sssd_new_dbus_server(ctx->ev, sd_ctx, DBUS_ADDRESS, dbus_service_init); return ret; } static void set_tasks_checker(struct mt_srv *srv); static void tasks_check_handler(struct event_context *ev, struct timed_event *te, struct timeval t, void *ptr) { struct mt_srv *srv = talloc_get_type(ptr, struct mt_srv); time_t now = time(NULL); int status; pid_t pid; int ret; pid = waitpid(srv->pid, &status, WNOHANG); if (pid == 0) { set_tasks_checker(srv); return; } if (pid != srv->pid) { DEBUG(1, ("bad return (%d) from waitpid() waiting for %d\n", pid, srv->pid)); /* TODO: what do we do now ? */ } 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 on it's own ?!\n", srv->name)); } if (srv->last_restart != 0) { if ((now - srv->last_restart) > 30) { /* TODO: get val from config */ /* it was long ago reset restart threshold */ srv->restarts = 0; } } /* restart the process */ if (srv->restarts < 3) { /* TODO: get val from config */ ret = server_service_init(srv->name, srv->mt_ctx->ev, &srv->pid); if (ret != EOK) { DEBUG(0,("Failed to restart service '%s'\n", srv->name)); talloc_free(srv); return; } srv->restarts++; srv->last_restart = now; set_tasks_checker(srv); return; } DEBUG(0, ("Process [%s], definitely stopped!\n", srv->name)); talloc_free(srv); } static void set_tasks_checker(struct mt_srv *srv) { struct timed_event *te = NULL; struct timeval tv; gettimeofday(&tv, NULL); tv.tv_sec += 2; tv.tv_usec = 0; te = event_add_timed(srv->mt_ctx->ev, srv, tv, tasks_check_handler, srv); if (te == NULL) { DEBUG(0, ("failed to add event, monitor offline for [%s]!\n", srv->name)); /* FIXME: shutdown ? */ } } int start_monitor(TALLOC_CTX *mem_ctx, struct event_context *event_ctx, struct confdb_ctx *cdb) { struct mt_ctx *ctx; struct mt_srv *srv; int ret, i; ctx = talloc_zero(mem_ctx, struct mt_ctx); if (!ctx) { DEBUG(0, ("fatal error initializing monitor!\n")); return ENOMEM; } ctx->ev = event_ctx; ret = confdb_get_param(cdb, mem_ctx, "config.services", "activeServices", &ctx->services); if (ctx->services[0] == NULL) { DEBUG(0, ("No services configured!\n")); return EINVAL; } for (i = 0; ctx->services[i]; i++) { srv = talloc_zero(ctx, struct mt_srv); if (!srv) { talloc_free(ctx); return ENOMEM; } srv->name = ctx->services[i]; srv->mt_ctx = ctx; ret = server_service_init(srv->name, event_ctx, &srv->pid); if (ret != EOK) { DEBUG(0,("Failed to restart service '%s'\n", srv->name)); talloc_free(srv); } set_tasks_checker(srv); } /* 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; } return EOK; } /* * dbus_service_init * This function should initiate a query to the newly connected * service to discover the service's identity (invoke the getIdentity * method on the new client). The reply callback for this request * should set the connection destructor appropriately. */ static int dbus_service_init(struct dbus_connection_toplevel_context *dct_ctx) { DBusMessage *msg; DBusPendingCall *pending_reply; DBusConnection *conn; DBusError dbus_error; dbus_bool_t dbret; DEBUG(0,("Initializing D-BUS Service")); conn = sssd_get_dbus_connection(dct_ctx); dbus_error_init(&dbus_error); /* * Set up identity request * This should be a well-known path and method * for all services */ msg = dbus_message_new_method_call(NULL, SERVICE_PATH, SERVICE_INTERFACE, SERVICE_METHOD_IDENTITY); dbret = dbus_connection_send_with_reply(conn, msg, &pending_reply, -1); if (!dbret) { /* * 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_free(dct_ctx); } /* Set up the reply handler */ dbus_pending_call_set_notify(pending_reply, identity_check, dct_ctx, NULL); dbus_message_unref(msg); return EOK; } static void identity_check(DBusPendingCall *pending, void *data) { struct dbus_connection_toplevel_context *dct_ctx; DBusMessage *reply; DBusError dbus_error; int type; dct_ctx = talloc_get_type(data, struct dbus_connection_toplevel_context); 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, ("Serious error. A reply callback was called but no reply was received and no timeout occurred\n")); /* Destroy this connection */ sssd_dbus_disconnect(dct_ctx); return; } type = dbus_message_get_type(reply); switch (type) { case DBUS_MESSAGE_TYPE_METHOD_RETURN: /* Got the service name and version */ /* Extract the name and version from the message */ /* Set up the destructor for this service */ break; case DBUS_MESSAGE_TYPE_ERROR: DEBUG(0,("getIdentity returned an error %s, closing connection.\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. */ sssd_dbus_disconnect(dct_ctx); break; } }