Commit bdcf008b authored by Philipp Schafft's avatar Philipp Schafft 🦁
Browse files

Added <event>: Unified handling of events.

<event> has been added and can be used within <kartoffelsalat>
both in <icecast> and <mount>.
<event> takes backend depending <option> child tags.
Currently supported backends:
 - log: send message to error log.
 - exec: executes a program or script.
 - url: delivers the event via HTTP.

within <mount> <on-connect> and <on-disconnect> has been replaced by
<event>. Config parser can on-the-fly convert old tags.
Also <authentication type="url"> within <mount> has been fixed
for those cases with <option name="mount_add" .../> and
<option name="mount_remove" .../> which are now on-the-fly converted
by the parser to corresponding <event> tags.

Please also see TAGs added as per #2098. Some include hints for
documentation updates needed after this change. Those updates
should take place before 2.4.2.
parent 8f706a4c
......@@ -111,7 +111,7 @@ XIPH_PATH_CURL([
AC_CHECK_DECL([CURLOPT_NOSIGNAL],
[ AC_DEFINE([HAVE_AUTH_URL], 1, [Define to compile in auth URL support code])
AC_CHECK_FUNCS([curl_global_init])
ICECAST_OPTIONAL="$ICECAST_OPTIONAL auth_url.o"
ICECAST_OPTIONAL="$ICECAST_OPTIONAL auth_url.o event_url.o"
enable_curl="yes"
XIPH_VAR_APPEND([XIPH_CPPFLAGS],[$CURL_CFLAGS])
XIPH_VAR_PREPEND([XIPH_LIBS],[$CURL_LIBS])
......
......@@ -11,6 +11,7 @@ INCLUDES = -I./common/
noinst_HEADERS = admin.h cfgfile.h logging.h sighandler.h connection.h \
global.h util.h slave.h source.h stats.h refbuf.h client.h \
compat.h fserve.h xslt.h yp.h md5.h \
event.h event_log.h event_exec.h event_url.h \
acl.h auth.h auth_htpasswd.h auth_url.h auth_anonymous.h auth_static.h \
format.h format_ogg.h format_mp3.h format_ebml.h \
format_vorbis.h format_theora.h format_flac.h format_speex.h format_midi.h \
......@@ -19,9 +20,11 @@ icecast_SOURCES = cfgfile.c main.c logging.c sighandler.c connection.c global.c
util.c slave.c source.c stats.c refbuf.c client.c \
xslt.c fserve.c admin.c md5.c \
format.c format_ogg.c format_mp3.c format_midi.c format_flac.c format_ebml.c \
acl.c auth.c auth_htpasswd.c auth_anonymous.c auth_static.c format_kate.c format_skeleton.c format_opus.c
format_kate.c format_skeleton.c format_opus.c \
event.c event_log.c event_exec.c \
acl.c auth.c auth_htpasswd.c auth_anonymous.c auth_static.c
EXTRA_icecast_SOURCES = yp.c \
auth_url.c \
auth_url.c event_url.c \
format_vorbis.c format_theora.c format_speex.c
icecast_DEPENDENCIES = @ICECAST_OPTIONAL@ common/net/libicenet.la common/thread/libicethread.la \
......
......@@ -470,6 +470,7 @@ auth_t *auth_get_authenticator(xmlNodePtr node)
auth->method[i] = 1;
}
/* BEFORE RELEASE 2.4.2 TODO: Migrate this to config_parse_options(). */
option = node->xmlChildrenNode;
while (option)
{
......
......@@ -33,6 +33,7 @@
#include "logging.h"
#include "util.h"
#include "auth.h"
#include "event.h"
/* for config_reread_config() */
#include "yp.h"
......@@ -58,7 +59,7 @@
#define CONFIG_DEFAULT_PLAYLIST_LOG NULL
#define CONFIG_DEFAULT_ACCESS_LOG "access.log"
#define CONFIG_DEFAULT_ERROR_LOG "error.log"
#define CONFIG_DEFAULT_LOG_LEVEL 3
#define CONFIG_DEFAULT_LOG_LEVEL ICECAST_LOGLEVEL_INFO
#define CONFIG_DEFAULT_CHROOT 0
#define CONFIG_DEFAULT_CHUID 0
#define CONFIG_DEFAULT_USER NULL
......@@ -102,6 +103,7 @@ static void _parse_mount(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c);
static void _parse_listen_socket(xmlDocPtr doc, xmlNodePtr node,
ice_config_t *c);
static void _add_server(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c);
static void _parse_events(event_registration_t **events, xmlNodePtr node);
static void merge_mounts(mount_proxy * dst, mount_proxy * src);
static inline void _merge_mounts_all(ice_config_t *c);
......@@ -146,20 +148,6 @@ static inline int __parse_public(const char *str) {
return util_str_to_bool(str);
}
static inline int __parse_loglevel(const char *str) {
if (strcasecmp(str, "debug") == 0 || strcasecmp(str, "DBUG") == 0)
return 4;
if (strcasecmp(str, "information") == 0 || strcasecmp(str, "INFO") == 0)
return 3;
if (strcasecmp(str, "warning") == 0 || strcasecmp(str, "WARN") == 0)
return 2;
if (strcasecmp(str, "error") == 0 || strcasecmp(str, "EROR") == 0)
return 1;
/* gussing it is old-style numerical setting */
return atoi(str);
}
static void __append_old_style_auth(auth_stack_t **stack, const char *name, const char *type, const char *username, const char *password, const char *method, const char *allow_method, int allow_web, const char *allow_admin) {
xmlNodePtr role, user, pass;
auth_t *auth;
......@@ -266,6 +254,45 @@ static void __append_old_style_urlauth(auth_stack_t **stack, const char *client_
xmlFreeNode(role);
}
static void __append_old_style_exec_event(event_registration_t **list, const char *trigger, const char *executable) {
xmlNodePtr exec;
event_registration_t *er;
exec = xmlNewNode(NULL, XMLSTR("event"));
xmlSetProp(exec, XMLSTR("type"), XMLSTR("exec"));
xmlSetProp(exec, XMLSTR("trigger"), XMLSTR(trigger));
__append_option_tag(exec, "executable", executable);
er = event_new_from_xml_node(exec);
event_registration_push(list, er);
event_registration_release(er);
xmlFreeNode(exec);
}
static void __append_old_style_url_event(event_registration_t **list, const char *trigger, const char *url, const char *action, const char *username, const char *password) {
xmlNodePtr exec;
event_registration_t *er;
exec = xmlNewNode(NULL, XMLSTR("event"));
xmlSetProp(exec, XMLSTR("type"), XMLSTR("url"));
xmlSetProp(exec, XMLSTR("trigger"), XMLSTR(trigger));
__append_option_tag(exec, "url", url);
__append_option_tag(exec, "action", action);
__append_option_tag(exec, "username", username);
__append_option_tag(exec, "password", password);
er = event_new_from_xml_node(exec);
event_registration_push(list, er);
event_registration_release(er);
xmlFreeNode(exec);
}
static void config_clear_http_header(ice_config_http_header_t *header) {
ice_config_http_header_t *old;
......@@ -323,8 +350,6 @@ static void config_clear_mount(mount_proxy *mount)
if (mount->mountname) xmlFree (mount->mountname);
if (mount->dumpfile) xmlFree (mount->dumpfile);
if (mount->intro_filename) xmlFree (mount->intro_filename);
if (mount->on_connect) xmlFree (mount->on_connect);
if (mount->on_disconnect) xmlFree (mount->on_disconnect);
if (mount->fallback_mount) xmlFree (mount->fallback_mount);
if (mount->stream_name) xmlFree (mount->stream_name);
if (mount->stream_description) xmlFree (mount->stream_description);
......@@ -336,6 +361,8 @@ static void config_clear_mount(mount_proxy *mount)
if (mount->cluster_password) xmlFree (mount->cluster_password);
if (mount->authstack) auth_stack_release(mount->authstack);
event_registration_release(mount->event);
config_clear_http_header(mount->http_headers);
free (mount);
}
......@@ -391,6 +418,8 @@ void config_clear(ice_config_t *c)
if (c->group) xmlFree(c->group);
if (c->mimetypes_fn) xmlFree (c->mimetypes_fn);
event_registration_release(c->event);
while ((c->listen_sock = config_clear_listener (c->listen_sock)))
;
......@@ -744,6 +773,9 @@ static void _parse_root(xmlDocPtr doc, xmlNodePtr node,
_parse_logging(doc, node->xmlChildrenNode, configuration);
} else if (xmlStrcmp (node->name, XMLSTR("security")) == 0) {
_parse_security(doc, node->xmlChildrenNode, configuration);
} else if (xmlStrcmp (node->name, XMLSTR("kartoffelsalat")) == 0) {
/* BEFORE RELEASE NEXT REVIEW: Should this tag really be <kartoffelsalat>? */
_parse_events(&configuration->event, node->xmlChildrenNode);
}
} while ((node = node->next));
......@@ -962,7 +994,11 @@ static void _parse_mount_oldstyle_authentication(mount_proxy *mount, xmlNodePtr
child = child->next;
}
/* TODO: FIXME: XXX: implement support for mount_add and mount_remove (using only username and password) here. */
if (mount_add)
__append_old_style_url_event(&mount->event, "source-connect", mount_add, "mount_add", username, password);
if (mount_remove)
__append_old_style_url_event(&mount->event, "source-disconnect", mount_add, "mount_remove", username, password);
__append_old_style_urlauth(authstack, listener_add, listener_remove, "listener_add", "listener_remove", username, password, 0, auth_header, timelimit_header, headers, header_prefix);
__append_old_style_urlauth(authstack, stream_auth, NULL, "stream_auth", NULL, username, password, 1, auth_header, timelimit_header, headers, header_prefix);
if (listener_add)
......@@ -1123,12 +1159,18 @@ static void _parse_mount(xmlDocPtr doc, xmlNodePtr node,
}
}
else if (xmlStrcmp (node->name, XMLSTR("on-connect")) == 0) {
mount->on_connect = (char *)xmlNodeListGetString(
doc, node->xmlChildrenNode, 1);
tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
if (tmp) {
__append_old_style_exec_event(&mount->event, "source-connect", tmp);
xmlFree(tmp);
}
}
else if (xmlStrcmp (node->name, XMLSTR("on-disconnect")) == 0) {
mount->on_disconnect = (char *)xmlNodeListGetString(
doc, node->xmlChildrenNode, 1);
tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
if (tmp) {
__append_old_style_exec_event(&mount->event, "source-disconnect", tmp);
xmlFree(tmp);
}
}
else if (xmlStrcmp (node->name, XMLSTR("max-listener-duration")) == 0) {
tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
......@@ -1181,6 +1223,9 @@ static void _parse_mount(xmlDocPtr doc, xmlNodePtr node,
doc, node->xmlChildrenNode, 1);
} else if (xmlStrcmp (node->name, XMLSTR("http-headers")) == 0) {
_parse_http_headers(doc, node->xmlChildrenNode, &(mount->http_headers));
} else if (xmlStrcmp (node->name, XMLSTR("kartoffelsalat")) == 0) {
/* BEFORE RELEASE NEXT REVIEW: Should this tag really be <kartoffelsalat>? */
_parse_events(&mount->event, node->xmlChildrenNode);
}
} while ((node = node->next));
......@@ -1689,7 +1734,7 @@ static void _parse_logging(xmlDocPtr doc, xmlNodePtr node,
if (tmp) xmlFree(tmp);
} else if (xmlStrcmp (node->name, XMLSTR("loglevel")) == 0) {
char *tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
configuration->loglevel = __parse_loglevel(tmp);
configuration->loglevel = util_str_to_loglevel(tmp);
if (tmp) xmlFree(tmp);
} else if (xmlStrcmp (node->name, XMLSTR("logarchive")) == 0) {
char *tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
......@@ -1779,6 +1824,64 @@ static void _add_server(xmlDocPtr doc, xmlNodePtr node,
}
}
static void _parse_events(event_registration_t **events, xmlNodePtr node) {
while (node) {
if (xmlStrcmp(node->name, XMLSTR("event")) == 0) {
event_registration_t *reg = event_new_from_xml_node(node);
event_registration_push(events, reg);
event_registration_release(reg);
}
node = node->next;
}
}
config_options_t *config_parse_options(xmlNodePtr node) {
config_options_t *ret = NULL;
config_options_t *cur = NULL;
if (!node)
return NULL;
node = node->xmlChildrenNode;
if (!node)
return NULL;
do {
if (xmlStrcmp(node->name, XMLSTR("option")) != 0)
continue;
if (cur) {
cur->next = calloc(1, sizeof(config_options_t));
cur = cur->next;
} else {
cur = ret = calloc(1, sizeof(config_options_t));
}
cur->type = (char *)xmlGetProp(node, XMLSTR("type"));
cur->name = (char *)xmlGetProp(node, XMLSTR("name"));
cur->value = (char *)xmlGetProp(node, XMLSTR("value"));
/* map type="default" to NULL. This is to avoid many extra xmlCharStrdup()s. */
if (cur->type && strcmp(cur->type, "default") == 0) {
xmlFree(cur->type);
cur->type = NULL;
}
} while ((node = node->next));
return ret;
}
void config_clear_options(config_options_t *options) {
while (options) {
config_options_t *opt = options;
options = opt->next;
if (opt->type) xmlFree(opt->type);
if (opt->name) xmlFree(opt->name);
if (opt->value) xmlFree(opt->value);
free(opt);
}
}
static void merge_mounts(mount_proxy * dst, mount_proxy * src) {
ice_config_http_header_t *http_header_next;
ice_config_http_header_t **http_header_tail;
......@@ -1814,10 +1917,6 @@ static void merge_mounts(mount_proxy * dst, mount_proxy * src) {
dst->mp3_meta_interval = src->mp3_meta_interval;
if (!dst->cluster_password)
dst->cluster_password = (char*)xmlStrdup((xmlChar*)src->cluster_password);
if (!dst->on_connect)
dst->on_connect = (char*)xmlStrdup((xmlChar*)src->on_connect);
if (!dst->on_disconnect)
dst->on_disconnect = (char*)xmlStrdup((xmlChar*)src->on_disconnect);
if (!dst->max_listener_duration)
dst->max_listener_duration = src->max_listener_duration;
if (!dst->stream_name)
......
......@@ -24,6 +24,7 @@
struct _mount_proxy;
#include <libxml/tree.h>
#include "common/thread/thread.h"
#include "common/avl/avl.h"
#include "global.h"
......@@ -58,6 +59,7 @@ typedef struct ice_config_dir_tag {
} ice_config_dir_t;
typedef struct _config_options {
char *type;
char *name;
char *value;
struct _config_options *next;
......@@ -99,10 +101,10 @@ typedef struct _mount_proxy {
ice_config_http_header_t *http_headers; /* additional HTTP headers */
struct event_registration_tag *event;
char *cluster_password;
struct auth_stack_tag *authstack;
char *on_connect;
char *on_disconnect;
unsigned int max_listener_duration;
char *stream_name;
......@@ -155,6 +157,8 @@ typedef struct ice_config_tag {
char *shoutcast_mount;
struct auth_stack_tag *authstack;
struct event_registration_tag *event;
int touch_interval;
ice_config_dir_t *dir_list;
......@@ -226,6 +230,9 @@ void config_clear(ice_config_t *config);
mount_proxy *config_find_mount (ice_config_t *config, const char *mount, mount_type type);
listener_t *config_get_listen_sock (ice_config_t *config, connection_t *con);
config_options_t *config_parse_options(xmlNodePtr node);
void config_clear_options(config_options_t *options);
int config_rehash(void);
ice_config_locks *config_locks(void);
......
/* Icecast
*
* This program is distributed under the GNU General Public License, version 2.
* A copy of this license is included with this source.
*
* Copyright 2014, Philipp Schafft <lion@lion.leolix.org>
*/
/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdlib.h>
#include "event.h"
#include "event_log.h"
#include "event_exec.h"
#include "event_url.h"
#include "logging.h"
#include "admin.h"
#define CATMODULE "event"
static mutex_t event_lock;
static event_t *event_queue = NULL;
static int event_running = 0;
static thread_type *event_thread = NULL;
/* work with event_t* */
static void event_addref(event_t *event) {
if (!event)
return;
event->refcount++;
}
static void event_release(event_t *event) {
if (!event)
return;
event->refcount--;
if (event->refcount)
return;
free(event->trigger);
free(event->uri);
free(event->connection_ip);
free(event->client_role);
free(event->client_username);
free(event->client_useragent);
event_release(event->next);
free(event);
}
static void event_push(event_t **event, event_t *next) {
size_t i = 0;
if (!event || !next)
return;
while (*event && i < 128) {
event = &(*event)->next;
i++;
}
if (i == 128) {
ICECAST_LOG_ERROR("Can not push event %p into queue. Queue is full.", event);
return;
}
event_addref(*event = next);
}
static void event_push_reglist(event_t *event, event_registration_t *reglist) {
size_t i;
if (!event || !reglist)
return;
for (i = 0; i < (sizeof(event->reglist)/sizeof(*event->reglist)); i++) {
if (!event->reglist[i]) {
event_registration_addref(event->reglist[i] = reglist);
return;
}
}
ICECAST_LOG_ERROR("Can not push reglist %p into event %p. No space left on event.", reglist, event);
}
static event_t *event_new(const char *trigger) {
event_t *ret;
if (!trigger)
return NULL;
ret = calloc(1, sizeof(event_t));
if (!ret)
return NULL;
ret->refcount = 1;
ret->trigger = strdup(trigger);
ret->client_admin_command = ADMIN_COMMAND_ERROR;
if (!ret->trigger) {
event_release(ret);
return NULL;
}
return ret;
}
/* subsystem functions */
static inline void _try_event(event_registration_t *er, event_t *event) {
/* er is already locked */
if (strcmp(er->trigger, event->trigger) != 0)
return;
if (er->emit)
er->emit(er->state, event);
}
static inline void _try_registrations(event_registration_t *er, event_t *event) {
if (!er)
return;
thread_mutex_lock(&er->lock);
while (1) {
/* try registration */
_try_event(er, event);
/* go to next registration */
if (er->next) {
event_registration_t *next = er->next;
thread_mutex_lock(&next->lock);
thread_mutex_unlock(&er->lock);
er = next;
} else {
break;
}
}
thread_mutex_unlock(&er->lock);
}
static void *event_run_thread (void *arg) {
int running = 0;
do {
event_t *event;
size_t i;
thread_mutex_lock(&event_lock);
running = event_running;
if (event_queue) {
event = event_queue;
event_queue = event_queue->next;
} else {
event = NULL;
}
thread_mutex_unlock(&event_lock);
/* sleep if nothing todo and then try again */
if (!event) {
thread_sleep(150000);
continue;
}
for (i = 0; i < (sizeof(event->reglist)/sizeof(*event->reglist)); i++)
_try_registrations(event->reglist[i], event);
event_release(event);
} while (running);
return NULL;
}
void event_initialise(void) {
/* create mutex */
thread_mutex_create(&event_lock);
/* initialise everything */
thread_mutex_lock(&event_lock);
event_running = 1;
thread_mutex_unlock(&event_lock);
/* start thread */
event_thread = thread_create("events thread", event_run_thread, NULL, THREAD_ATTACHED);
}
void event_shutdown(void) {
/* stop thread */
if (!event_running)
return;
thread_mutex_lock(&event_lock);
event_running = 0;
thread_mutex_unlock(&event_lock);
/* join thread as soon as it stopped */
thread_join(event_thread);
/* shutdown everything */
thread_mutex_lock(&event_lock);
event_thread = NULL;
event_release(event_queue);
event_queue = NULL;
thread_mutex_unlock(&event_lock);
/* destry mutex */
thread_mutex_destroy(&event_lock);
}
/* basic functions to work with event registrations */
event_registration_t * event_new_from_xml_node(xmlNodePtr node) {
event_registration_t *ret = calloc(1, sizeof(event_registration_t));
config_options_t *options;
int rv;
if(!ret)
return NULL;
ret->refcount = 1;
/* BEFORE RELEASE 2.4.2 DOCUMENT: Document <event type="..." trigger="..."> */
ret->type = (char*)xmlGetProp(node, XMLSTR("type"));
ret->trigger = (char*)xmlGetProp(node, XMLSTR("trigger"));
if (!ret->type || !ret->trigger) {
ICECAST_LOG_ERROR("Event node isn't complet. Type or Trigger missing.");
event_registration_release(ret);
return NULL;
}
options = config_parse_options(node);
if (strcmp(ret->type, EVENT_TYPE_LOG) == 0) {
rv = event_get_log(ret, options);
} else if (strcmp(ret->type, EVENT_TYPE_EXEC) == 0) {
rv = event_get_exec(ret, options);
#ifdef HAVE_AUTH_URL
} else if (strcmp(ret->type, EVENT_TYPE_URL) == 0) {
rv = event_get_url(ret, options);
#endif
} else {
ICECAST_LOG_ERROR("Event backend %s is unknown.", ret->type);
rv = -1;
}
config_clear_options(options);
if (rv != 0) {
ICECAST_LOG_ERROR("Can not set up event backend %s for trigger %s", ret->type, ret->trigger);
event_registration_release(ret);
return NULL;
}
return ret;
}
void event_registration_addref(event_registration_t * er) {
if(!er)
return;
thread_mutex_lock(&er->lock);
er->refcount++;
thread_mutex_unlock(&er->lock);
}
void event_registration_release(event_registration_t *er) {
if(!er)
return;
thread_mutex_lock(&er->lock);
er->refcount--;
if (er->refcount) {
thread_mutex_unlock(&er->lock);
return;
}