/*
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/>.
*/
#include "util/util.h"
#include "util/child_common.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/param.h>
#include <time.h>
#include <string.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 "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"
#include "responder/common/responder_sbus.h"
#ifdef USE_KEYRING
#include <keyutils.h>
#endif
/* 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
/* terminate the child after this interval by default if it
* doesn't shutdown on receiving SIGTERM */
#define MONITOR_DEF_FORCE_TIME 60
/* TODO: get the restart related values from config */
#define MONITOR_RESTART_CNT_INTERVAL_RESET 30
/* maximum allowed number of service restarts if the restarts
* were less than MONITOR_RESTART_CNT_INTERVAL_RESET apart, which would
* indicate a crash after startup or after every request */
#define MONITOR_MAX_SVC_RESTARTS 2
/* The services are restarted with a delay in case the restart was
* hitting a race condition where the DP is not ready yet either.
* The MONITOR_MAX_RESTART_DELAY defines the maximum delay between
* restarts.
*/
#define MONITOR_MAX_RESTART_DELAY 4
/* name of the monitor server instance */
#define MONITOR_NAME "sssd"
#define SSSD_PIDFILE_PATH PID_PATH"/"MONITOR_NAME".pid"
/* Special value to leave the Kerberos Replay Cache set to use
* the libkrb5 defaults
*/
#define KRB5_RCACHE_DIR_DISABLE "__LIBKRB5_DEFAULTS__"
int cmdline_debug_level;
int cmdline_debug_timestamps;
int cmdline_debug_microseconds;
struct svc_spy;
enum mt_svc_type {
MT_SVC_SERVICE,
MT_SVC_PROVIDER
};
struct mt_svc {
struct mt_svc *prev;
struct mt_svc *next;
enum mt_svc_type type;
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;
int kill_time;
bool svc_started;
int restarts;
time_t last_restart;
int failed_pongs;
DBusPendingCall *pending;
int debug_level;
struct tevent_timer *ping_ev;
struct sss_child_ctx *child_ctx;
struct tevent_timer *sigkill_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;
int num_services;
int started_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;
struct netlink_ctx *nlctx;
const char *conf_path;
struct sss_sigchild_ctx *sigchld_ctx;
bool is_daemon;
pid_t parent_pid;
};
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 int service_signal_reset_offline(struct mt_svc *svc);
static void ping_check(DBusPendingCall *pending, void *data);
static void set_tasks_checker(struct mt_svc *srv);
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,
int restarts);
static int add_new_provider(struct mt_ctx *ctx,
const char *name,
int restarts);
static int mark_service_as_started(struct mt_svc *svc);
static int monitor_cleanup(void);
static void network_status_change_cb(void *cb_data)
{
struct mt_svc *iter;
struct mt_ctx *ctx = (struct mt_ctx *) cb_data;
DEBUG(SSSDBG_TRACE_INTERNAL, ("A networking status change detected "
"signaling providers to reset offline status\n"));
for (iter = ctx->svc_list; iter; iter = iter->next) {
/* Don't signal services, only providers */
if (iter->provider) {
service_signal_reset_offline(iter);
}
}
}
/* 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;
}
/* try to delist service */
if (svc->mt_ctx) {
DLIST_REMOVE(svc->mt_ctx->svc_list, svc);
}
/* Cancel any pending pings */
if (svc->pending) {
dbus_pending_call_cancel(svc->pending);
}
/* 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);
}
if (svc->type == MT_SVC_SERVICE && svc->svc_started
&& svc->mt_ctx != NULL && svc->mt_ctx->started_services > 0) {
svc->mt_ctx->started_services--;
}
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_spy = NULL;
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.\n", 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], 0);
}
}
if (svc->type == MT_SVC_SERVICE) {
ctx->started_services++;
}
if (ctx->started_services == ctx->num_services) {
/* Initialization is complete, terminate parent process if in daemon
* mode. Make sure we send the signal to the right process */
if (ctx->is_daemon) {
if (ctx->parent_pid <= 1 || ctx->parent_pid != getppid()) {
/* the parent process was already terminated */
DEBUG(SSSDBG_MINOR_FAILURE, ("Invalid parent pid: %d\n",
ctx->parent_pid));
goto done;
}
DEBUG(SSSDBG_TRACE_FUNC, ("SSSD is initialized, "
"terminating parent process\n"));
errno = 0;
ret = kill(ctx->parent_pid, SIGTERM);
if (ret != 0) {
ret = errno;
DEBUG(SSSDBG_FATAL_FAILURE, ("Unable to terminate parent "
"process [%d]: %s\n", ret, strerror(ret)));
}
}
}
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], 0);
}
}
}
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,
false, &ctx->sbus_srv, monitor_service_init, ctx);
talloc_free(monitor_address);
return ret;
}
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);
int ret;
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->failed_pongs >= 3) {
/* too long since we last heard of this process */
DEBUG(SSSDBG_CRIT_FAILURE,
("Killing service [%s], not responding to pings!\n",
svc->name));
/* Kill the service. The SIGCHLD handler will restart it */
monitor_kill_service(svc);
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 ? */
}
|