diff --git a/src/Makefile.am b/src/Makefile.am index 61e94508..6281f7d1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,6 +7,7 @@ bin_PROGRAMS = icecast noinst_HEADERS = \ icecasttypes.h \ admin.h \ + resourcematch.h \ main.h \ cfgfile.h \ logging.h \ @@ -69,6 +70,7 @@ icecast_SOURCES = \ xslt.c \ fserve.c \ admin.c \ + resourcematch.c \ md5.c \ matchfile.c \ tls.c \ diff --git a/src/admin.c b/src/admin.c index a924c3e2..382bf1ba 100644 --- a/src/admin.c +++ b/src/admin.c @@ -118,37 +118,37 @@ static void command_updatemetadata (client_t *client, source_t *source, adm static void command_buildm3u (client_t *client, source_t *source, admin_format_t response); static const admin_command_handler_t handlers[] = { - { "*", ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, NULL }, /* for ACL framework */ - { FALLBACK_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_fallback }, - { FALLBACK_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_fallback }, - { METADATA_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_metadata }, - { METADATA_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_metadata }, - { SHOUTCAST_METADATA_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_shoutcast_metadata }, - { LISTCLIENTS_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_show_listeners }, - { LISTCLIENTS_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_show_listeners }, - { STATS_RAW_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_RAW, command_stats }, - { STATS_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_stats }, - { "stats.xml", ADMINTYPE_HYBRID, ADMIN_FORMAT_RAW, command_stats }, - { QUEUE_RELOAD_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_queue_reload }, - { QUEUE_RELOAD_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_queue_reload }, - { LISTMOUNTS_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_list_mounts }, - { LISTMOUNTS_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_list_mounts }, - { STREAMLIST_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_list_mounts }, - { STREAMLIST_PLAINTEXT_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_PLAINTEXT, command_list_mounts }, - { STREAMLIST_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_list_mounts }, - { MOVECLIENTS_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_move_clients }, - { MOVECLIENTS_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_move_clients }, - { KILLCLIENT_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_kill_client }, - { KILLCLIENT_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_kill_client }, - { KILLSOURCE_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_kill_source }, - { KILLSOURCE_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_kill_source }, - { MANAGEAUTH_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_manageauth }, - { MANAGEAUTH_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_manageauth }, - { UPDATEMETADATA_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_updatemetadata }, - { UPDATEMETADATA_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_updatemetadata }, - { BUILDM3U_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_buildm3u }, - { DEFAULT_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_stats }, - { DEFAULT_RAW_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_stats } + { "*", ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, NULL, NULL}, /* for ACL framework */ + { FALLBACK_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_fallback, NULL}, + { FALLBACK_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_fallback, NULL}, + { METADATA_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_metadata, NULL}, + { METADATA_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_metadata, NULL}, + { SHOUTCAST_METADATA_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_shoutcast_metadata, NULL}, + { LISTCLIENTS_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_show_listeners, NULL}, + { LISTCLIENTS_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_show_listeners, NULL}, + { STATS_RAW_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_RAW, command_stats, NULL}, + { STATS_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_stats, NULL}, + { "stats.xml", ADMINTYPE_HYBRID, ADMIN_FORMAT_RAW, command_stats, NULL}, + { QUEUE_RELOAD_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_queue_reload, NULL}, + { QUEUE_RELOAD_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_queue_reload, NULL}, + { LISTMOUNTS_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_list_mounts, NULL}, + { LISTMOUNTS_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_list_mounts, NULL}, + { STREAMLIST_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_list_mounts, NULL}, + { STREAMLIST_PLAINTEXT_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_PLAINTEXT, command_list_mounts, NULL}, + { STREAMLIST_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_list_mounts, NULL}, + { MOVECLIENTS_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_move_clients, NULL}, + { MOVECLIENTS_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_move_clients, NULL}, + { KILLCLIENT_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_kill_client, NULL}, + { KILLCLIENT_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_kill_client, NULL}, + { KILLSOURCE_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_kill_source, NULL}, + { KILLSOURCE_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_kill_source, NULL}, + { MANAGEAUTH_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_manageauth, NULL}, + { MANAGEAUTH_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_manageauth, NULL}, + { UPDATEMETADATA_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_updatemetadata, NULL}, + { UPDATEMETADATA_HTML_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_HTML, command_updatemetadata, NULL}, + { BUILDM3U_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, command_buildm3u, NULL}, + { DEFAULT_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_stats, NULL}, + { DEFAULT_RAW_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_stats, NULL} }; static admin_command_table_t command_tables[ADMIN_MAX_COMMAND_TABLES] = { @@ -247,7 +247,7 @@ admin_command_id_t admin_get_command(const char *command) } for (i = 0; i < table->length; i++) - if (strcmp(table->handlers[i].route, suffix) == 0) + if (resourcematch_match(table->handlers[i].route, suffix, NULL) == RESOURCEMATCH_MATCH) return admin_get_command_by_table_and_index(table, i); return COMMAND_ERROR; @@ -480,7 +480,7 @@ void admin_handle_request(client_t *client, const char *uri) handler = admin_get_handler(client->admin_command); /* Check if admin command is valid */ - if (handler == NULL || handler->function == NULL) { + if (handler == NULL || (handler->function == NULL && handler->function_with_parameters == NULL)) { ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %H", uri); client_send_error_by_id(client, ICECAST_ERROR_ADMIN_UNRECOGNISED_COMMAND); @@ -545,7 +545,25 @@ void admin_handle_request(client_t *client, const char *uri) switch (client->parser->req_type) { case httpp_req_get: case httpp_req_post: - handler->function(client, source, format); + if (handler->function) { + handler->function(client, source, format); + } else { + resourcematch_extract_t *extract = NULL; + const char *suffix = strchr(uri, '/'); + + if (!suffix) { + client_send_error_by_id(client, ICECAST_ERROR_ADMIN_UNRECOGNISED_COMMAND); + } else { + suffix++; + + if (resourcematch_match(handler->route, suffix, &extract) == RESOURCEMATCH_MATCH) { + handler->function_with_parameters(client, source, format, extract); + resourcematch_extract_free(extract); + } else { + client_send_error_by_id(client, ICECAST_ERROR_ADMIN_UNRECOGNISED_COMMAND); + } + } + } break; case httpp_req_options: client_send_204(client); diff --git a/src/admin.h b/src/admin.h index 06945d2d..6d4b03c6 100644 --- a/src/admin.h +++ b/src/admin.h @@ -19,6 +19,7 @@ #include "icecasttypes.h" #include "compat.h" +#include "resourcematch.h" /* types */ #define ADMINTYPE_ERROR (-1) @@ -31,12 +32,14 @@ #define ADMIN_COMMAND_ANY ((admin_command_id_t)0) /* for ACL framework */ typedef void (*admin_request_function_ptr)(client_t * client, source_t * source, admin_format_t format); +typedef void (*admin_request_function_with_parameters_ptr)(client_t * client, source_t * source, admin_format_t format, resourcematch_extract_t *parameters); typedef struct admin_command_handler { const char *route; const int type; const int format; const admin_request_function_ptr function; + const admin_request_function_with_parameters_ptr function_with_parameters; } admin_command_handler_t; void admin_handle_request(client_t *client, const char *uri); diff --git a/src/resourcematch.c b/src/resourcematch.c new file mode 100644 index 00000000..77ae63b6 --- /dev/null +++ b/src/resourcematch.c @@ -0,0 +1,191 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2018, Philipp "ph3-der-loewe" Schafft , + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "resourcematch.h" + +static size_t count_groups(const char *pattern) +{ + size_t ret = 0; + + while (*pattern) { + for (; *pattern && *pattern != '%'; pattern++); + + if (!*pattern) { + return ret; + } + + pattern++; + + if (!*pattern) + return ret; + + if (*pattern != '%') + ret++; + + pattern++; + } + + return ret; +} + +static resourcematch_extract_t * allocate_extract(const char *pattern) +{ + size_t groups = count_groups(pattern); + resourcematch_extract_t *ret; + + ret = calloc(1, sizeof(*ret)); + if (!ret) + return NULL; + + ret->groups = groups; + ret->group = calloc(groups, sizeof(*ret->group)); + + return ret; +} + +static void strip_common_prefix(const char **pattern, const char **string) +{ + const char *p = *pattern; + const char *s = *string; + + for (; *p && *p != '%' && *p == *s; p++, s++); + + *pattern = p; + *string = s; +} + +static inline void setup_group(resourcematch_extract_t *extract, size_t idx, char type) +{ + if (!extract) + return; + + extract->group[idx].type = type; + extract->group[idx].raw = NULL; +} + +static inline resourcematch_result_t match_lli(const char **string, resourcematch_extract_t *extract, size_t idx, int base) +{ + long long int ret; + char *endptr; + + errno = 0; + ret = strtoll(*string, &endptr, base); + if (errno != 0) + return RESOURCEMATCH_ERROR; + + if (extract) { + extract->group[idx].result.lli = ret; + } + + *string = endptr; + + return RESOURCEMATCH_MATCH; +} + +resourcematch_result_t resourcematch_match(const char *pattern, const char *string, resourcematch_extract_t **extract) +{ + resourcematch_result_t ret; + resourcematch_extract_t *matches = NULL; + size_t idx = 0; + + if (!pattern || !string) + return RESOURCEMATCH_ERROR; + + if (extract) { + matches = allocate_extract(pattern); + if (!matches) + return RESOURCEMATCH_NOMATCH; + } + + while (1) { + strip_common_prefix(&pattern, &string); + + if (!*pattern && !*string) { + if (extract) + *extract = matches; + + return RESOURCEMATCH_MATCH; + } else if (!*pattern || !*string) { + if (extract) + resourcematch_extract_free(matches); + + return RESOURCEMATCH_NOMATCH; + } + + if (*pattern != '%') { + if (extract) + resourcematch_extract_free(matches); + + return RESOURCEMATCH_NOMATCH; + } + + pattern++; + + switch (*pattern) { + case '%': + if (*string == '%') { + string++; + } else { + if (extract) + resourcematch_extract_free(matches); + + return RESOURCEMATCH_NOMATCH; + } + break; +#define _test_int(type,base) \ + case (type): \ + setup_group(matches, idx, *pattern); \ +\ + ret = match_lli(&string, matches, idx, (base)); \ + if (ret != RESOURCEMATCH_MATCH) { \ + if (extract) \ + resourcematch_extract_free(matches); \ +\ + return ret; \ + } \ + idx++; \ + break; + + _test_int('i', 0); + _test_int('d', 10); + _test_int('x', 16); + _test_int('o', 8); + + default: + if (extract) + resourcematch_extract_free(matches); + + return RESOURCEMATCH_ERROR; + break; + } + + pattern++; + } +} + +void resourcematch_extract_free(resourcematch_extract_t *extract) +{ + size_t i; + + if (!extract) + return; + + for (i = 0; i < extract->groups; i++) { + free(extract->group[i].raw); + } + + free(extract->group); + free(extract); +} diff --git a/src/resourcematch.h b/src/resourcematch.h new file mode 100644 index 00000000..534b6874 --- /dev/null +++ b/src/resourcematch.h @@ -0,0 +1,37 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2018, Philipp "ph3-der-loewe" Schafft , + */ + +#ifndef __RESOURCEMATCH_H__ +#define __RESOURCEMATCH_H__ + +#include + +typedef enum { + RESOURCEMATCH_ERROR, + RESOURCEMATCH_MATCH, + RESOURCEMATCH_NOMATCH +} resourcematch_result_t; + +typedef struct { + char type; + char *raw; + union { + const char *string; + long long int lli; + } result; +} resourcematch_group_t; + +typedef struct { + size_t groups; + resourcematch_group_t *group; +} resourcematch_extract_t; + +resourcematch_result_t resourcematch_match(const char *pattern, const char *string, resourcematch_extract_t **extract); +void resourcematch_extract_free(resourcematch_extract_t *extract); + +#endif