diff --git a/configure.in b/configure.in index 3c2aef65..159001ee 100644 --- a/configure.in +++ b/configure.in @@ -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]) diff --git a/src/Makefile.am b/src/Makefile.am index 400237ff..6ebabe2c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/auth.c b/src/auth.c index 4b3792b3..70b9aea6 100644 --- a/src/auth.c +++ b/src/auth.c @@ -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) { diff --git a/src/cfgfile.c b/src/cfgfile.c index 88581965..716af00e 100644 --- a/src/cfgfile.c +++ b/src/cfgfile.c @@ -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 ? */ + _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 ? */ + _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) diff --git a/src/cfgfile.h b/src/cfgfile.h index 134bd100..98c6c186 100644 --- a/src/cfgfile.h +++ b/src/cfgfile.h @@ -24,6 +24,7 @@ struct _mount_proxy; +#include #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); diff --git a/src/event.c b/src/event.c new file mode 100644 index 00000000..1c9bf3e9 --- /dev/null +++ b/src/event.c @@ -0,0 +1,376 @@ +/* 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 + */ + +/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#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 */ + 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; + } + + if (er->next) + event_registration_release(er->next); + + xmlFree(er->type); + xmlFree(er->trigger); + + if (er->free) + er->free(er->state); + + thread_mutex_unlock(&er->lock); + thread_mutex_destroy(&er->lock); + free(er); +} + +void event_registration_push(event_registration_t **er, event_registration_t *tail) { + event_registration_t *next, *cur; + + if (!er || !tail) + return; + + if (!*er) { + event_registration_addref(*er = tail); + return; + } + + event_registration_addref(cur = *er); + thread_mutex_lock(&cur->lock); + while (1) { + next = cur->next; + if (!cur->next) + break; + + event_registration_addref(next); + thread_mutex_unlock(&cur->lock); + event_registration_release(cur); + cur = next; + thread_mutex_lock(&cur->lock); + } + + event_registration_addref(cur->next = tail); + thread_mutex_unlock(&cur->lock); + event_registration_release(cur); +} + +/* event signaling */ +void event_emit(event_t *event) { + thread_mutex_lock(&event_lock); + event_push(&event_queue, event); + thread_mutex_unlock(&event_lock); +} + +/* this function needs to extract all the infos from the client, source and mount object + * as after return the pointers become invalid. + */ +void event_emit_clientevent(const char *trigger, client_t *client, const char *uri) { + event_t *event = event_new(trigger); + ice_config_t *config; + mount_proxy *mount; + + if (!event) { + ICECAST_LOG_ERROR("Can not create event."); + return; + } + + if (client) { + const char *tmp; + event->connection_id = client->con->id; + event->connection_time = client->con->con_time; + event->client_admin_command = client->admin_command; + event->connection_ip = strdup(client->con->ip); + if (client->role) + event->client_role = strdup(client->role); + if (client->username) + event->client_username = strdup(client->username); + tmp = httpp_getvar(client->parser, "user-agent"); + if (tmp) + event->client_useragent = strdup(tmp); + } + + if (uri) + event->uri = strdup(uri); + + config = config_get_config(); + event_push_reglist(event, config->event); + + mount = config_find_mount(config, uri, MOUNT_TYPE_NORMAL); + if (mount && mount->mounttype == MOUNT_TYPE_NORMAL) + event_push_reglist(event, mount->event); + + mount = config_find_mount(config, uri, MOUNT_TYPE_DEFAULT); + if (mount && mount->mounttype == MOUNT_TYPE_DEFAULT) + event_push_reglist(event, mount->event); + config_release_config(); + + event_emit(event); + event_release(event); +} diff --git a/src/event.h b/src/event.h new file mode 100644 index 00000000..a65f128a --- /dev/null +++ b/src/event.h @@ -0,0 +1,108 @@ +/* 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 + */ + +#ifndef __EVENT_H__ +#define __EVENT_H__ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include "common/httpp/httpp.h" + +#include "cfgfile.h" +#include "client.h" +#include "source.h" + +/* implemented */ +#define EVENT_TYPE_LOG "log" +#define EVENT_TYPE_EXEC "exec" +/* not implemented */ +#define EVENT_TYPE_URL "url" + +#define MAX_REGLISTS_PER_EVENT 8 + +struct event_registration_tag; +typedef struct event_registration_tag event_registration_t; + +struct event_tag; +typedef struct event_tag event_t; +/* this has no lock member to protect multiple accesses as every non-readonly access is within event.c + * and is protected by global lock or on the same thread anyway. + */ +struct event_tag { + /* refernece counter */ + size_t refcount; + /* reference to next element in chain */ + event_t *next; + + /* event_registration lists. + * They are referenced here to avoid the need to access + * config and global client list each time an event is emitted. + */ + event_registration_t *reglist[MAX_REGLISTS_PER_EVENT]; + + /* trigger name */ + char *trigger; + + /* from client */ + char *uri; /* from context */ + unsigned long connection_id; /* from client->con->id */ + char *connection_ip; /* from client->con->ip */ + time_t connection_time; /* from client->con->con_time */ + char *client_role; /* from client->role */ + char *client_username; /* from client->username */ + char *client_useragent; /* from httpp_getvar(client->parser, "user-agent") */ + int client_admin_command; /* from client->admin_command */ +}; + +struct event_registration_tag { + /* refernece counter */ + size_t refcount; + /* reference to next element in chain */ + event_registration_t *next; + + /* protection */ + mutex_t lock; + + /* type of event */ + char *type; + + /* trigger name */ + char *trigger; + + /* backend state */ + void *state; + + /* emit events */ + int (*emit)(void *state, event_t *event); + + /* free backend state */ + void (*free)(void *state); +}; + +/* subsystem functions */ +void event_initialise(void); +void event_shutdown(void); + + +/* basic functions to work with event registrations */ +event_registration_t * event_new_from_xml_node(xmlNodePtr node); + +void event_registration_addref(event_registration_t *er); +void event_registration_release(event_registration_t *er); +void event_registration_push(event_registration_t **er, event_registration_t *tail); + +/* event signaling */ +void event_emit_clientevent(const char *trigger, client_t *client, const char *uri); +#define event_emit_global(x) event_emit_clientevent((x), NULL, NULL) + +#endif diff --git a/src/event_exec.c b/src/event_exec.c new file mode 100644 index 00000000..4fb02b0a --- /dev/null +++ b/src/event_exec.c @@ -0,0 +1,209 @@ +/* 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 "ph3-der-loewe" Schafft , + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + + +#ifndef _WIN32 +#include +/* for __setup_empty_script_environment() */ +#include +#include +#endif + +#include "event.h" +#include "logging.h" +#define CATMODULE "event_exec" + + +typedef struct event_exec { +/* REVIEW: Some ideas for future work: +