diff --git a/src/event.c b/src/event.c index e7cebb55..a76d70eb 100644 --- a/src/event.c +++ b/src/event.c @@ -12,16 +12,23 @@ #endif #include +#include #include #include +#include +#include + #include "event.h" #include "fastevent.h" #include "logging.h" +#include "string_renderer.h" #include "admin.h" #include "connection.h" #include "client.h" +#include "source.h" #include "cfgfile.h" +#include "global.h" /* for igloo_instance */ #define CATMODULE "event" @@ -31,6 +38,87 @@ static bool event_running = false; static thread_type *event_thread = NULL; static cond_t cond; +/* ignores errors */ +static void extra_add(event_t *event, event_extra_key_t key, const char *value) +{ + if (!value) + return; + + if (event->extra_fill == event->extra_size) { + event_extra_entry_t *n = realloc(event->extra_entries, sizeof(*n)*(event->extra_size + 16)); + if (!n) + return; + memset(&(n[event->extra_size]), 0, sizeof(*n)*16); + event->extra_size += 16; + event->extra_entries = n; + } + + if (igloo_sp_replace(value, &(event->extra_entries[event->extra_fill].value), igloo_instance) == igloo_ERROR_NONE) { + event->extra_entries[event->extra_fill].key = key; + event->extra_fill++; + } +} + +const char * event_extra_get(const event_t *event, const event_extra_key_t key) +{ + size_t i; + + if (!event || !event->extra_entries) + return NULL; + + for (i = 0; i < event->extra_fill; i++) { + if (event->extra_entries[i].key == key) + return event->extra_entries[i].value; + } + + return NULL; +} + +const char * event_extra_key_name(event_extra_key_t key) +{ + switch (key) { + case EVENT_EXTRA_KEY_URI: return "uri"; break; + case EVENT_EXTRA_KEY_CONNECTION_IP: return "connection-ip"; break; + case EVENT_EXTRA_KEY_CLIENT_ROLE: return "client-role"; break; + case EVENT_EXTRA_KEY_CLIENT_USERNAME: return "client-username"; break; + case EVENT_EXTRA_KEY_CLIENT_USERAGENT: return "client-useragent"; break; + case EVENT_EXTRA_KEY_SOURCE_MEDIA_TYPE: return "source-media-type"; break; + case EVENT_EXTRA_KEY_DUMPFILE_FILENAME: return "dumpfile-filename"; break; +#ifndef DEVEL_LOGGING + default: break; +#endif + } + + return NULL; +} + +igloo_error_t event_to_string_renderer(const event_t *event, string_renderer_t *renderer) +{ + static const event_extra_key_t key_list[] = { + EVENT_EXTRA_KEY_URI, + EVENT_EXTRA_KEY_SOURCE_MEDIA_TYPE, + EVENT_EXTRA_KEY_CONNECTION_IP, + EVENT_EXTRA_KEY_CLIENT_ROLE, + EVENT_EXTRA_KEY_CLIENT_USERNAME, + EVENT_EXTRA_KEY_CLIENT_USERAGENT, + EVENT_EXTRA_KEY_DUMPFILE_FILENAME, + EVENT_EXTRA_LIST_END + }; + + string_renderer_add_kv_with_options(renderer, "trigger", event->trigger, STRING_RENDERER_ENCODING_PLAIN, false, false); + for (size_t i = 0; key_list[i] != EVENT_EXTRA_LIST_END; i++) { + string_renderer_add_kv_with_options(renderer, event_extra_key_name(key_list[i]), event_extra_get(event, key_list[i]), STRING_RENDERER_ENCODING_PLAIN, false, false); + } + + if (event->client_data) { + string_renderer_add_ki_with_options(renderer, "connection-id", event->connection_id, STRING_RENDERER_ENCODING_PLAIN, true, false); + string_renderer_add_ki_with_options(renderer, "connection-time", event->connection_time, STRING_RENDERER_ENCODING_PLAIN, true, false); + string_renderer_add_ki_with_options(renderer, "client-admin-command", event->client_admin_command, STRING_RENDERER_ENCODING_PLAIN, true, false); + } + + return igloo_ERROR_NONE; +} + /* work with event_t* */ static void event_addref(event_t *event) { if (!event) @@ -58,11 +146,11 @@ static void event_release(event_t *event) { event_registration_release(event->reglist[i]); free(event->trigger); - free(event->uri); - free(event->connection_ip); - free(event->client_role); - free(event->client_username); - free(event->client_useragent); + + for (i = 0; i < event->extra_fill; i++) + igloo_sp_unref(&(event->extra_entries[i].value), igloo_instance); + free(event->extra_entries); + to_free = event->next; free(event); thread_mutex_unlock(&event_lock); @@ -362,19 +450,39 @@ void event_emit(event_t *event) { thread_mutex_unlock(&event_lock); } -/* this function needs to extract all the info 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) { +void event_emit_va(const char *trigger, ...) { event_t *event = event_new(trigger); + source_t *source = NULL; + client_t *client = NULL; + const char *uri = NULL; ice_config_t *config; - mount_proxy *mount; + const mount_proxy *mount; + va_list ap; if (!event) { ICECAST_LOG_ERROR("Can not create event."); return; } + va_start(ap, trigger); + while (true) { + event_extra_key_t key = va_arg(ap, event_extra_key_t); + + if (key == EVENT_EXTRA_LIST_END) { + break; + } else if (key == EVENT_EXTRA_KEY_URI) { + uri = va_arg(ap, const char *); + } else if (key == EVENT_EXTRA_SOURCE) { + source = va_arg(ap, source_t *); + } else if (key == EVENT_EXTRA_CLIENT) { + client = va_arg(ap, client_t *); + } + } + va_end(ap); + + if (source && !uri) + uri = source->mount; + config = config_get_config(); event_push_reglist(event, config->event); @@ -402,24 +510,43 @@ void event_emit_clientevent(const char *trigger, client_t *client, const char *u } #endif + if (uri) + extra_add(event, EVENT_EXTRA_KEY_URI, uri); + + va_start(ap, trigger); + while (true) { + event_extra_key_t key = va_arg(ap, event_extra_key_t); + + if (key == EVENT_EXTRA_LIST_END) { + break; + } else if (key == EVENT_EXTRA_SOURCE || key == EVENT_EXTRA_CLIENT) { + /* shift one arg off */ + va_arg(ap, const void *); + } else { + const char *value = va_arg(ap, const char *); + + extra_add(event, key, value); + } + } + va_end(ap); + + if (source) { + if (source->format && source->format->contenttype) { + extra_add(event, EVENT_EXTRA_KEY_SOURCE_MEDIA_TYPE, source->format->contenttype); + } + } + if (client) { - const char *tmp; + event->client_data = true; 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); + extra_add(event, EVENT_EXTRA_KEY_CONNECTION_IP, client->con->ip); + extra_add(event, EVENT_EXTRA_KEY_CLIENT_ROLE, client->role); + extra_add(event, EVENT_EXTRA_KEY_CLIENT_USERNAME, client->username); + extra_add(event, EVENT_EXTRA_KEY_CLIENT_USERAGENT, httpp_getvar(client->parser, "user-agent")); } - if (uri) - event->uri = strdup(uri); - event_emit(event); event_release(event); } diff --git a/src/event.h b/src/event.h index a697d886..70863723 100644 --- a/src/event.h +++ b/src/event.h @@ -9,10 +9,14 @@ #ifndef __EVENT_H__ #define __EVENT_H__ +#include + #include #include #include +#include + #include "common/thread/thread.h" #include "icecasttypes.h" @@ -25,6 +29,26 @@ #define MAX_REGLISTS_PER_EVENT 8 +typedef enum { + /* special keys */ + EVENT_EXTRA_LIST_END, + EVENT_EXTRA_CLIENT, + EVENT_EXTRA_SOURCE, + /* real keys */ + EVENT_EXTRA_KEY_URI, + EVENT_EXTRA_KEY_CONNECTION_IP, + EVENT_EXTRA_KEY_CLIENT_ROLE, + EVENT_EXTRA_KEY_CLIENT_USERNAME, + EVENT_EXTRA_KEY_CLIENT_USERAGENT, + EVENT_EXTRA_KEY_SOURCE_MEDIA_TYPE, + EVENT_EXTRA_KEY_DUMPFILE_FILENAME, +} event_extra_key_t; + +typedef struct { + event_extra_key_t key; + const char *value; +} event_extra_entry_t; + struct event_registration_tag; typedef struct event_registration_tag event_registration_t; @@ -49,14 +73,14 @@ struct event_tag { char *trigger; /* from client */ - char *uri; /* from context */ + bool client_data; 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") */ admin_command_id_t client_admin_command; /* from client->admin_command */ + /* extra */ + size_t extra_size; + size_t extra_fill; + event_extra_entry_t *extra_entries; }; struct event_registration_tag { @@ -97,8 +121,13 @@ 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) +void event_emit_va(const char *trigger, ...); +#define event_emit_global(event) event_emit_va((event), EVENT_EXTRA_LIST_END) + +/* reading extra from events */ +const char * event_extra_get(const event_t *event, const event_extra_key_t key); +const char * event_extra_key_name(event_extra_key_t key); +igloo_error_t event_to_string_renderer(const event_t *event, string_renderer_t *renderer); /* expects renderer in list mode */ /* Implementations */ int event_get_exec(event_registration_t *er, config_options_t *options); diff --git a/src/event_exec.c b/src/event_exec.c index 8eeb9b3f..13e9f3ab 100644 --- a/src/event_exec.c +++ b/src/event_exec.c @@ -10,6 +10,7 @@ #include #endif +#include #include #include @@ -46,6 +47,13 @@ typedef struct event_exec { char **argv; } event_exec_t; +static char *_null_aware_strdup(const char *s) +{ + if (!s) + return NULL; + return strdup(s); +} + /* OS independed code: */ static inline size_t __argvtype2offset(event_exec_argvtype_t argvtype) { switch (argvtype) { @@ -79,6 +87,8 @@ static inline event_exec_argvtype_t __str2argvtype(const char *str) { } static inline char **__setup_argv(event_exec_t *self, event_t *event) { + char *uri; + self->argv[0] = self->executable; switch (self->argvtype) { @@ -89,14 +99,16 @@ static inline char **__setup_argv(event_exec_t *self, event_t *event) { self->argv[2] = event->trigger ? event->trigger : ""; /* fall through */ case ARGVTYPE_ONLY_URI: - self->argv[1] = event->uri ? event->uri : ""; + uri = _null_aware_strdup(event_extra_get(event, EVENT_EXTRA_KEY_URI)); + self->argv[1] = uri ? uri : ""; break; case ARGVTYPE_LEGACY: /* This mode is similar to ARGVTYPE_ONLY_URI * but if URI is unknown the parameter is skipped! */ - if (event->uri) { - self->argv[1] = event->uri; + uri = _null_aware_strdup(event_extra_get(event, EVENT_EXTRA_KEY_URI)); + if (uri) { + self->argv[1] = uri; } else { self->argv[1] = self->executable; return &self->argv[1]; @@ -122,6 +134,12 @@ static inline void __update_environ(const char *name, const char *value) { #else #define __update_environ(x,y) #endif + +static inline void __update_environ_with_key(const event_t *event, const char *name, event_extra_key_t key) +{ + __update_environ(name, event_extra_get(event, key)); +} + static inline void __setup_environ(ice_config_t *config, event_exec_t *self, event_t *event) { mount_proxy *mountinfo; source_t *source; @@ -132,13 +150,15 @@ static inline void __setup_environ(ice_config_t *config, event_exec_t *self, eve __update_environ("ICECAST_HOSTNAME", config->hostname); __update_environ("ICECAST_ADMIN", config->admin); __update_environ("ICECAST_LOGDIR", config->log_dir); - __update_environ("EVENT_URI", event->uri); __update_environ("EVENT_TRIGGER", event->trigger); /* new name */ __update_environ("SOURCE_ACTION", event->trigger); /* old name (deprecated) */ - __update_environ("CLIENT_IP", event->connection_ip); - __update_environ("CLIENT_ROLE", event->client_role); - __update_environ("CLIENT_USERNAME", event->client_username); - __update_environ("CLIENT_USERAGENT", event->client_useragent); + __update_environ_with_key(event, "EVENT_URI", EVENT_EXTRA_KEY_URI); + __update_environ_with_key(event, "SOURCE_MEDIA_TYPE", EVENT_EXTRA_KEY_SOURCE_MEDIA_TYPE); + __update_environ_with_key(event, "CLIENT_IP", EVENT_EXTRA_KEY_CONNECTION_IP); + __update_environ_with_key(event, "CLIENT_ROLE", EVENT_EXTRA_KEY_CLIENT_ROLE); + __update_environ_with_key(event, "CLIENT_USERNAME", EVENT_EXTRA_KEY_CLIENT_USERNAME); + __update_environ_with_key(event, "CLIENT_USERAGENT", EVENT_EXTRA_KEY_CLIENT_USERAGENT); + __update_environ_with_key(event, "DUMPFILE_FILENAME", EVENT_EXTRA_KEY_DUMPFILE_FILENAME); snprintf(buf, sizeof(buf), "%lu", event->connection_id); __update_environ("CLIENT_ID", buf); @@ -147,7 +167,7 @@ static inline void __setup_environ(ice_config_t *config, event_exec_t *self, eve snprintf(buf, sizeof(buf), "%i", event->client_admin_command); __update_environ("CLIENT_ADMIN_COMMAND", buf); - mountinfo = config_find_mount(config, event->uri, MOUNT_TYPE_NORMAL); + mountinfo = config_find_mount(config, event_extra_get(event, EVENT_EXTRA_KEY_URI), MOUNT_TYPE_NORMAL); if (mountinfo) { __update_environ("MOUNT_NAME", mountinfo->stream_name); __update_environ("MOUNT_DESCRIPTION", mountinfo->stream_description); @@ -156,7 +176,7 @@ static inline void __setup_environ(ice_config_t *config, event_exec_t *self, eve } avl_tree_rlock(global.source_tree); - source = source_find_mount(event->uri); + source = source_find_mount(event_extra_get(event, EVENT_EXTRA_KEY_URI)); if (source) { __update_environ("SOURCE_MOUNTPOINT", source->mount); __update_environ("SOURCE_PUBLIC", source->yp_public ? "true" : "false"); @@ -223,7 +243,7 @@ static void _run_script (event_exec_t *self, event_t *event) { default: /* parent */ break; } - exit (0); + _exit(0); case -1: ICECAST_LOG_ERROR("Unable to fork %s", strerror (errno)); break; diff --git a/src/event_log.c b/src/event_log.c index 07471506..5b069243 100644 --- a/src/event_log.c +++ b/src/event_log.c @@ -12,7 +12,13 @@ #include +#include "icecasttypes.h" +#include +#include + #include "event.h" +#include "global.h" /* for igloo_instance */ +#include "string_renderer.h" #include "util.h" #include "cfgfile.h" #include "logging.h" @@ -24,18 +30,24 @@ typedef struct event_log { int level; } event_log_t; + static int event_log_emit(void *state, event_t *event) { event_log_t *self = state; + string_renderer_t * renderer; + + if (igloo_ro_new(&renderer, string_renderer_t, igloo_instance) != igloo_ERROR_NONE) + return 0; + + string_renderer_start_list(renderer, " ", "=", false, false, STRING_RENDERER_ENCODING_H_ALT_SPACE); + event_to_string_renderer(event, renderer); + string_renderer_end_list(renderer); ICECAST_LOG(self->level, ICECAST_LOGFLAG_NONE, - "%s%strigger=%# H uri=%#H " - "connection_id=%lu connection_ip=%#H connection_time=%lli " - "client_role=%# H client_username=%#H client_useragent=%# H client_admin_command=%i", + "%s%s%s", self->prefix ? self->prefix : "", self->prefix ? ": " : "", - event->trigger, - event->uri, - event->connection_id, event->connection_ip, (long long int)event->connection_time, - event->client_role, event->client_username, event->client_useragent, event->client_admin_command); + string_renderer_to_string_zero_copy(renderer)); + + igloo_ro_unref(&renderer); return 0; } diff --git a/src/event_url.c b/src/event_url.c index 40ec8e79..9489eb46 100644 --- a/src/event_url.c +++ b/src/event_url.c @@ -11,7 +11,14 @@ #endif #include +#include +#include "icecasttypes.h" +#include +#include + +#include "global.h" /* for igloo_instance */ +#include "string_renderer.h" #include "curl.h" #include "event.h" #include "cfgfile.h" @@ -21,6 +28,7 @@ typedef struct event_url { + bool legacy; char *url; char *action; char *userpwd; @@ -43,39 +51,45 @@ static inline char *__escape(const char *src, const char *default_value) { static int event_url_emit(void *state, event_t *event) { event_url_t *self = state; ice_config_t *config; - char *action, *mount, *server, *role, *username, *ip, *agent; time_t duration; - char post[4096]; + string_renderer_t * renderer; - action = util_url_escape(self->action ? self->action : event->trigger); - mount = __escape(event->uri, ""); - role = __escape(event->client_role, ""); - username = __escape(event->client_username, ""); - ip = __escape(event->connection_ip, ""); - agent = __escape(event->client_useragent, "-"); + if (igloo_ro_new(&renderer, string_renderer_t, igloo_instance) != igloo_ERROR_NONE) + return 0; - if (event->connection_time) { + if (event->client_data) { duration = time(NULL) - event->connection_time; } else { duration = 0; } - config = config_get_config(); - server = __escape(config->hostname, ""); + string_renderer_start_list_formdata(renderer); + if (self->legacy) { + /* Old style */ + string_renderer_add_kv_with_options(renderer, "action", self->action ? self->action : event->trigger, STRING_RENDERER_ENCODING_PLAIN, false, false); + string_renderer_add_kv_with_options(renderer, "mount", event_extra_get(event, EVENT_EXTRA_KEY_URI), STRING_RENDERER_ENCODING_PLAIN, true, true); + string_renderer_add_ki_with_options(renderer, "client", event->connection_id, STRING_RENDERER_ENCODING_PLAIN, true, true); + string_renderer_add_kv_with_options(renderer, "role", event_extra_get(event, EVENT_EXTRA_KEY_CLIENT_ROLE), STRING_RENDERER_ENCODING_PLAIN, true, true); + string_renderer_add_kv_with_options(renderer, "username", event_extra_get(event, EVENT_EXTRA_KEY_CLIENT_USERNAME), STRING_RENDERER_ENCODING_PLAIN, true, true); + string_renderer_add_kv_with_options(renderer, "ip", event_extra_get(event, EVENT_EXTRA_KEY_CONNECTION_IP), STRING_RENDERER_ENCODING_PLAIN, true, true); + string_renderer_add_kv_with_options(renderer, "agent", event_extra_get(event, EVENT_EXTRA_KEY_CLIENT_USERAGENT) ? event_extra_get(event, EVENT_EXTRA_KEY_CLIENT_USERAGENT) : "-", STRING_RENDERER_ENCODING_PLAIN, true, true); + string_renderer_add_ki_with_options(renderer, "duration", duration, STRING_RENDERER_ENCODING_PLAIN, true, true); + string_renderer_add_ki_with_options(renderer, "admin", event->client_admin_command, STRING_RENDERER_ENCODING_PLAIN, true, true); + } else { + /* new style */ + event_to_string_renderer(event, renderer); + } - snprintf (post, sizeof (post), - "action=%s&mount=%s&server=%s&port=%d&client=%lu&role=%s&username=%s&ip=%s&agent=%s&duration=%lli&admin=%i", - action, mount, server, config->port, - event->connection_id, role, username, ip, agent, (long long int)duration, event->client_admin_command); + /* common */ + config = config_get_config(); + string_renderer_add_kv_with_options(renderer, "server", config->hostname, STRING_RENDERER_ENCODING_PLAIN, true, true); + if (self->legacy) { + string_renderer_add_ki_with_options(renderer, "port", config->port, STRING_RENDERER_ENCODING_PLAIN, true, true); + } config_release_config(); - free(action); - free(mount); - free(server); - free(role); - free(username); - free(ip); - free(agent); + string_renderer_end_list(renderer); + if (strchr(self->url, '@') == NULL && self->userpwd) { curl_easy_setopt(self->handle, CURLOPT_USERPWD, self->userpwd); @@ -84,11 +98,13 @@ static int event_url_emit(void *state, event_t *event) { } curl_easy_setopt(self->handle, CURLOPT_URL, self->url); - curl_easy_setopt(self->handle, CURLOPT_POSTFIELDS, post); + curl_easy_setopt(self->handle, CURLOPT_POSTFIELDS, string_renderer_to_string_zero_copy(renderer)); if (curl_easy_perform(self->handle)) ICECAST_LOG_WARN("auth to server %s failed with %s", self->url, self->errormsg); + igloo_ro_unref(&renderer); + return 0; } @@ -109,6 +125,8 @@ int event_get_url(event_registration_t *er, config_options_t *options) { if (!self) return -1; + self->legacy = true; + if (options) { do { if (options->type) @@ -129,6 +147,8 @@ int event_get_url(event_registration_t *er, config_options_t *options) { password = options->value; } else if (strcmp(options->name, "action") == 0) { util_replace_string(&(self->action), options->value); + } else if (strcmp(options->name, "legacy") == 0) { + self->legacy = util_str_to_bool(options->value); } else { ICECAST_LOG_ERROR("Unknown