nagios4/base/nerd.c
2019-04-18 17:09:18 +02:00

502 lines
12 KiB
C

#ifdef ENABLE_NERD
/*
* Nagios Event Radio Dispatcher
*
* This is a subscriber service which initiates a unix domain socket,
* listens to it and lets other programs connect to it and subscribe
* to various channels using a simple text-based syntax.
*
* This code uses the eventbroker api to get its data, which means
* we're finally eating our own dogfood in that respect.
*
* Note: Disabled by default as of 4.4.0
* ./configure --enable-nerd
* to enable!
*/
#define _GNU_SOURCE 1
#include <stdio.h>
#include "include/config.h"
#include <sys/types.h>
#include <sys/socket.h>
#include "lib/libnagios.h"
#include "include/common.h"
#include "include/objects.h"
#include "include/broker.h"
#include "include/nebmods.h"
#include "include/nebmodules.h"
#include "include/nebstructs.h"
struct nerd_channel {
const char *name; /* name of this channel */
const char *description; /* user-presentable string to document the purpouse of this channel */
unsigned int id; /* channel id (might vary between invocations) */
unsigned int required_options; /* event_broker_options required for this channel */
unsigned int num_callbacks;
unsigned int callbacks[NEBCALLBACK_NUMITEMS];
int (*handler)(int , void *); /* callback handler for this channel */
objectlist *subscriptions; /* subscriber list */
};
static nebmodule nerd_mod; /* fake module to get our callbacks accepted */
static struct nerd_channel **channels;
static unsigned int num_channels, alloc_channels;
static unsigned int chan_host_checks_id, chan_service_checks_id;
static unsigned int chan_opath_checks_id;
static struct nerd_channel *find_channel(const char *name)
{
unsigned int i;
for(i = 0; i < num_channels; i++) {
struct nerd_channel *chan = channels[i];
if(!strcmp(name, chan->name)) {
return chan;
}
}
return NULL;
}
int nerd_get_channel_id(const char *name)
{
struct nerd_channel *chan;
chan = find_channel(name);
if (!chan)
return -1;
return chan->id;
}
static struct nerd_channel *nerd_get_channel(unsigned int chan_id)
{
return chan_id >= num_channels ? NULL : channels[chan_id];
}
objectlist *nerd_get_subscriptions(int chan_id)
{
struct nerd_channel *chan = nerd_get_channel(chan_id);
return chan ? chan->subscriptions : NULL;
}
static int nerd_register_channel_callbacks(struct nerd_channel *chan)
{
unsigned int i;
for(i = 0; i < chan->num_callbacks; i++) {
int result = neb_register_callback(chan->callbacks[i], &nerd_mod, 0, chan->handler);
if(result != 0) {
logit(NSLOG_RUNTIME_ERROR, TRUE, "nerd: Failed to register callback %d for channel '%s': %d\n",
chan->callbacks[i], chan->name, result);
return -1;
}
}
return 0;
}
static int nerd_deregister_channel_callbacks(struct nerd_channel *chan)
{
unsigned int i;
for(i = 0; i < chan->num_callbacks; i++) {
neb_deregister_callback(chan->callbacks[i], chan->handler);
}
return 0;
}
static int subscribe(int sd, struct nerd_channel *chan, char *fmt)
{
struct nerd_subscription *subscr;
if(!(subscr = calloc(1, sizeof(*subscr))))
return -1;
subscr->sd = sd;
subscr->chan = chan;
subscr->format = fmt ? strdup(fmt) : NULL;
if(!chan->subscriptions) {
nerd_register_channel_callbacks(chan);
}
prepend_object_to_objectlist(&chan->subscriptions, subscr);
return 0;
}
static int cancel_channel_subscription(struct nerd_channel *chan, int sd)
{
objectlist *list, *next, *prev = NULL;
int cancelled = 0;
if(!chan)
return -1;
for(list = chan->subscriptions; list; list = next) {
struct nerd_subscription *subscr = (struct nerd_subscription *)list->object_ptr;
next = list->next;
if(subscr->sd == sd) {
cancelled++;
free(list);
free(subscr);
if(prev) {
prev->next = next;
} else {
chan->subscriptions = next;
}
continue;
}
prev = list;
}
if(cancelled) {
logit(NSLOG_INFO_MESSAGE, TRUE, "nerd: Cancelled %d subscription%s to channel '%s' for %d\n",
cancelled, cancelled == 1 ? "" : "s", chan->name, sd);
}
if(chan->subscriptions == NULL)
nerd_deregister_channel_callbacks(chan);
return 0;
}
static int unsubscribe(int sd, struct nerd_channel *chan)
{
objectlist *list, *next, *prev = NULL;
for(list = chan->subscriptions; list; list = next) {
struct nerd_subscription *subscr = (struct nerd_subscription *)list->object_ptr;
next = list->next;
if(subscr->sd == sd) {
/* found it, so remove it */
free(subscr);
free(list);
if(!prev) {
chan->subscriptions = next;
continue;
} else {
prev->next = next;
}
continue;
}
prev = list;
}
if(chan->subscriptions == NULL) {
nerd_deregister_channel_callbacks(chan);
}
return 0;
}
/* removes a subscriber entirely and closes its socket */
int nerd_cancel_subscriber(int sd)
{
unsigned int i;
for(i = 0; i < num_channels; i++) {
cancel_channel_subscription(channels[i], sd);
}
iobroker_close(nagios_iobs, sd);
return 0;
}
int nerd_broadcast(unsigned int chan_id, void *buf, unsigned int len)
{
struct nerd_channel *chan;
objectlist *list, *next;
if (!(chan = nerd_get_channel(chan_id)))
return -1;
for(list = chan->subscriptions; list; list = next) {
struct nerd_subscription *subscr = (struct nerd_subscription *)list->object_ptr;
int result;
next = list->next;
result = send(subscr->sd, buf, len, 0);
if(result < 0) {
if (errno == EAGAIN)
return 0;
nerd_cancel_subscriber(subscr->sd);
return 500;
}
}
return 0;
}
static int chan_host_checks(int cb, void *data)
{
nebstruct_host_check_data *ds = (nebstruct_host_check_data *)data;
check_result *cr = (check_result *)ds->check_result_ptr;
host *h;
char *buf;
int len;
if(ds->type != NEBTYPE_HOSTCHECK_PROCESSED)
return 0;
if(channels[chan_host_checks_id]->subscriptions == NULL)
return 0;
h = (host *)ds->object_ptr;
len = asprintf(&buf, "%s from %d -> %d: %s\n", h->name, h->last_state, h->current_state, cr->output);
nerd_broadcast(chan_host_checks_id, buf, len);
free(buf);
return 0;
}
static int chan_service_checks(int cb, void *data)
{
nebstruct_service_check_data *ds = (nebstruct_service_check_data *)data;
check_result *cr = (check_result *)ds->check_result_ptr;
service *s;
char *buf;
int len;
if(ds->type != NEBTYPE_SERVICECHECK_PROCESSED)
return 0;
s = (service *)ds->object_ptr;
len = asprintf(&buf, "%s;%s from %d -> %d: %s\n", s->host_name, s->description, s->last_state, s->current_state, cr->output);
nerd_broadcast(chan_service_checks_id, buf, len);
free(buf);
return 0;
}
static char **host_parent_path_cache;
static const char *host_parent_path(host *leaf, char sep)
{
char *ret;
unsigned int len = 0, pos = 0;
objectlist *stack = NULL, *list, *next;
host *h = leaf;
if(!h->parent_hosts)
return h->name;
if(!host_parent_path_cache) {
host_parent_path_cache = calloc(num_objects.hosts, sizeof(char *));
}
if(host_parent_path_cache[h->id]) {
return host_parent_path_cache[h->id];
}
while(h) {
len += strlen(h->name) + 2; /* room for separator */
prepend_object_to_objectlist(&stack, h->name);
if(h->parent_hosts && h->parent_hosts->host_ptr)
h = h->parent_hosts->host_ptr;
else
break;
}
ret = malloc(len + 1);
for(list = stack; list; list = next) {
char *ppart = (char *)list->object_ptr;
next = list->next;
free(list);
ret[pos++] = sep;
memcpy(ret + pos, ppart, strlen(ppart));
pos += strlen(ppart);
}
ret[pos++] = 0;
host_parent_path_cache[leaf->id] = ret;
return ret;
}
static int chan_opath_checks(int cb, void *data)
{
const int red = 0xff0000, green = 0xff00, blue = 0xff, pale = 0x555555;
unsigned int color;
check_result *cr;
host *h;
const char *name = "_HOST_";
char *buf = NULL;
if(cb == NEBCALLBACK_HOST_CHECK_DATA) {
nebstruct_host_check_data *ds = (nebstruct_host_check_data *)data;
if(ds->type != NEBTYPE_HOSTCHECK_PROCESSED)
return 0;
cr = ds->check_result_ptr;
h = ds->object_ptr;
color = red | green;
} else if(cb == NEBCALLBACK_SERVICE_CHECK_DATA) {
nebstruct_service_check_data *ds = (nebstruct_service_check_data *)data;
service *s;
if(ds->type != NEBTYPE_SERVICECHECK_PROCESSED)
return 0;
s = (service *)ds->object_ptr;
h = s->host_ptr;
cr = ds->check_result_ptr;
color = (red | green | blue) ^ pale;
name = s->description;
} else
return 0;
asprintf(&buf, "%lu|%s|M|%s/%s|%06X\n", cr->finish_time.tv_sec,
check_result_source(cr), host_parent_path(h, '/'), name, color);
nerd_broadcast(chan_opath_checks_id, buf, strlen(buf));
free(buf);
return 0;
}
static int nerd_deinit(void)
{
unsigned int i;
if(host_parent_path_cache) {
for(i = 0; i < num_objects.hosts; i++) {
my_free(host_parent_path_cache[i]);
}
my_free(host_parent_path_cache);
}
for(i = 0; i < num_channels; i++) {
struct nerd_channel *chan = channels[i];
objectlist *list, *next;
for(list = chan->subscriptions; list; list = next) {
struct nerd_subscription *subscr = (struct nerd_subscription *)list->object_ptr;
iobroker_close(nagios_iobs, subscr->sd);
next = list->next;
free(list);
free(subscr);
}
chan->subscriptions = NULL;
my_free(chan);
}
my_free(channels);
num_channels = 0;
alloc_channels = 0;
return 0;
}
int nerd_mkchan(const char *name, const char *description, int (*handler)(int, void *), unsigned int callbacks)
{
struct nerd_channel *chan, **ptr;
int i;
if(num_channels + 1 >= alloc_channels) {
alloc_channels = alloc_nr(alloc_channels);
ptr = realloc(channels, alloc_channels * sizeof(struct nerd_channel *));
if(!ptr)
return -1;
channels = ptr;
}
if(!(chan = calloc(1, sizeof(*chan))))
return -1;
chan->name = name;
chan->description = description;
chan->handler = handler;
for(i = 0; callbacks && i < NEBCALLBACK_NUMITEMS; i++) {
if(!(callbacks & (1 << i)))
continue;
chan->callbacks[chan->num_callbacks++] = i;
}
channels[num_channels++] = chan;
logit(NSLOG_INFO_MESSAGE, TRUE, "nerd: Channel %s registered successfully\n", chan->name);
return num_channels - 1;
}
#define NERD_SUBSCRIBE 0
#define NERD_UNSUBSCRIBE 1
static int nerd_qh_handler(int sd, char *request, unsigned int len)
{
char *chan_name, *fmt;
struct nerd_channel *chan;
int action;
if (!*request || !strcmp(request, "help")) {
nsock_printf_nul(sd, "Manage subscriptions to NERD channels.\n"
"Valid commands:\n"
" list list available channels\n"
" subscribe <channel> subscribe to a channel\n"
" unsubscribe <channel> unsubscribe to a channel\n");
return 0;
}
if (!strcmp(request, "list")) {
unsigned int i;
for (i = 0; i < num_channels; i++) {
chan = channels[i];
nsock_printf(sd, "%-15s %s\n", chan->name, chan->description);
}
nsock_printf(sd, "%c", 0);
return 0;
}
chan_name = strchr(request, ' ');
if(!chan_name)
return 400;
*chan_name = 0;
chan_name++;
if(!strcmp(request, "subscribe"))
action = NERD_SUBSCRIBE;
else if(!strcmp(request, "unsubscribe"))
action = NERD_UNSUBSCRIBE;
else {
return 400;
}
/* might have a format-string */
if((fmt = strchr(chan_name, ':')))
*(fmt++) = 0;
chan = find_channel(chan_name);
if(!chan) {
return 400;
}
if(action == NERD_SUBSCRIBE)
subscribe(sd, chan, fmt);
else
unsubscribe(sd, chan);
return 0;
}
/* nebmod_init(), but loaded even if no modules are */
int nerd_init(void)
{
nerd_mod.deinit_func = nerd_deinit;
nerd_mod.filename = (char *)"NERD"; /* something to log */
if(qh_register_handler("nerd", "Nagios Event Radio Dispatcher - Subscriber Service", 0, nerd_qh_handler) < 0) {
logit(NSLOG_RUNTIME_ERROR, TRUE, "nerd: Failed to register with query handler\n");
return ERROR;
}
neb_add_core_module(&nerd_mod);
chan_host_checks_id = nerd_mkchan("hostchecks",
"Host check results",
chan_host_checks, nebcallback_flag(NEBCALLBACK_HOST_CHECK_DATA));
chan_service_checks_id = nerd_mkchan("servicechecks",
"Service check results",
chan_service_checks, nebcallback_flag(NEBCALLBACK_SERVICE_CHECK_DATA));
chan_opath_checks_id = nerd_mkchan("opathchecks",
"Host and service checks in gource's log format",
chan_opath_checks, nebcallback_flag(NEBCALLBACK_HOST_CHECK_DATA) | nebcallback_flag(NEBCALLBACK_SERVICE_CHECK_DATA));
logit(NSLOG_INFO_MESSAGE, TRUE, "nerd: Fully initialized and ready to rock!\n");
return 0;
}
#endif