diff --git a/src/Makefile.am b/src/Makefile.am index aa2d5d9ea459d32835c05296e18b51bb07b282f9..61bf2c9d8dc821dce4264b787f28d7dd95653e59 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,7 +11,7 @@ noinst_HEADERS = config.h os.h logging.h sighandler.h connection.h global.h\ compat.h format_mp3.h fserve.h xslt.h geturl.h yp.h event.h icecast_SOURCES = config.c main.c logging.c sighandler.c connection.c global.c\ util.c slave.c source.c stats.c refbuf.c client.c format.c format_vorbis.c\ - format_mp3.c xslt.c fserve.c geturl.c yp.c event.c + format_mp3.c xslt.c fserve.c geturl.c yp.c event.c admin.c icecast_LDADD = net/libicenet.la thread/libicethread.la httpp/libicehttpp.la\ log/libicelog.la avl/libiceavl.la timing/libicetiming.la diff --git a/src/admin.c b/src/admin.c new file mode 100644 index 0000000000000000000000000000000000000000..026d2b734338bcc74838a366d8e54237708f43f8 --- /dev/null +++ b/src/admin.c @@ -0,0 +1,223 @@ +#include +#include + +#include "config.h" +#include "connection.h" +#include "refbuf.h" +#include "client.h" +#include "source.h" +#include "global.h" +#include "event.h" +#include "stats.h" + +#include "format.h" +#include "format_mp3.h" + +#include "logging.h" + +#define CATMODULE "admin" + +#define COMMAND_ERROR (-1) +#define COMMAND_FALLBACK 1 +#define COMMAND_RAW_STATS 2 +#define COMMAND_METADATA_UPDATE 3 + +int admin_get_command(char *command) +{ + if(!strcmp(command, "fallbacks")) + return COMMAND_FALLBACK; + else if(!strcmp(command, "rawstats")) + return COMMAND_RAW_STATS; + else if(!strcmp(command, "stats.xml")) /* The old way */ + return COMMAND_RAW_STATS; + else if(!strcmp(command, "metadata")) + return COMMAND_METADATA_UPDATE; + else + return COMMAND_ERROR; +} + +static void command_fallback(client_t *client, source_t *source); +static void command_metadata(client_t *client, source_t *source); + +static void command_raw_stats(client_t *client); + +static void admin_handle_mount_request(client_t *client, source_t *source, + int command); +static void admin_handle_general_request(client_t *client, int command); + +void admin_handle_request(client_t *client, char *uri) +{ + char *mount, *command_string; + int command; + + if(strncmp("/admin/", uri, 7)) { + ERROR0("Internal error: admin request isn't"); + client_send_401(client); + return; + } + + command_string = uri + 7; + + command = admin_get_command(command_string); + + if(command < 0) { + ERROR1("Error parsing command string or unrecognised command: %s", + command_string); + client_send_400(client, "Unrecognised command"); + return; + } + + mount = httpp_get_query_param(client->parser, "mount"); + + if(mount != NULL) { + source_t *source; + + /* This is a mount request, handle it as such */ + if(!connection_check_source_pass(client->parser, mount)) { + INFO1("Bad or missing password on mount modification admin " + "request (command: %s)", command_string); + client_send_401(client); + return; + } + + avl_tree_rlock(global.source_tree); + source = source_find_mount(mount); + avl_tree_unlock(global.source_tree); + + if(source == NULL) { + WARN2("Admin command %s on non-existent source %s", + command_string, mount); + client_send_400(client, "Source does not exist"); + return; + } + + INFO2("Received admin command %s on mount \"%s\"", + command_string, mount); + + admin_handle_mount_request(client, source, command); + } + else { + + if(!connection_check_admin_pass(client->parser)) { + INFO1("Bad or missing password on admin command " + "request (command: %s)", command_string); + client_send_401(client); + return; + } + + admin_handle_general_request(client, command); + } +} + +static void admin_handle_general_request(client_t *client, int command) +{ + switch(command) { + case COMMAND_RAW_STATS: + command_raw_stats(client); + break; + default: + WARN0("General admin request not recognised"); + client_send_400(client, "Unknown admin request"); + return; + } +} + +static void admin_handle_mount_request(client_t *client, source_t *source, + int command) +{ + switch(command) { + case COMMAND_FALLBACK: + command_fallback(client, source); + break; + case COMMAND_METADATA_UPDATE: + command_metadata(client, source); + break; + default: + WARN0("Mount request not recognised"); + client_send_400(client, "Mount request unknown"); + return; + } +} + +#define COMMAND_REQUIRE(client,name,var) \ + do { \ + (var) = httpp_get_query_param((client)->parser, (name)); \ + if((var) == NULL) { \ + client_send_400((client), "Missing parameter"); \ + return; \ + } \ + } while(0); + +static void command_success(client_t *client, char *message) +{ + int bytes; + + client->respcode = 200; + bytes = sock_write(client->con->sock, + "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n" + "Admin request successful" + "

%s

", message); + if(bytes > 0) client->con->sent_bytes = bytes; + client_destroy(client); +} + +static void command_fallback(client_t *client, source_t *source) +{ + char *fallback; + char *old; + + DEBUG0("Got fallback request"); + + COMMAND_REQUIRE(client, "fallback", fallback); + + old = source->fallback_mount; + source->fallback_mount = strdup(fallback); + free(old); + + command_success(client, "Fallback configured"); +} + +static void command_metadata(client_t *client, source_t *source) +{ + char *action; + char *value; + mp3_state *state; + + DEBUG0("Got metadata update request"); + + COMMAND_REQUIRE(client, "mode", action); + COMMAND_REQUIRE(client, "song", value); + + if(source->format->type != FORMAT_TYPE_MP3) { + client_send_400(client, "Not mp3, cannot update metadata"); + return; + } + + if(strcmp(action, "updinfo") != 0) { + client_send_400(client, "No such action"); + return; + } + + state = source->format->_state; + + thread_mutex_lock(&(state->lock)); + free(state->metadata); + state->metadata = strdup(value); + state->metadata_age++; + state->metadata_raw = 0; + thread_mutex_unlock(&(state->lock)); + + DEBUG2("Metadata on mountpoint %s changed to \"%s\"", source->mount, value); + stats_event(source->mount, "title", value); + + command_success(client, "Metadata update successful"); +} + +static void command_raw_stats(client_t *client) { + DEBUG0("Stats request, sending xml stats"); + + stats_sendxml(client); + client_destroy(client); + return; +} + diff --git a/src/admin.h b/src/admin.h new file mode 100644 index 0000000000000000000000000000000000000000..072451a0df99262b9e1b98f5d1d14d569f7debfb --- /dev/null +++ b/src/admin.h @@ -0,0 +1,9 @@ +#ifndef __ADMIN_H__ +#define __ADMIN_H__ + +#include "refbuf.h" +#include "client.h" + +void admin_handle_request(client_t *client, char *uri); + +#endif /* __ADMIN_H__ */ diff --git a/src/connection.c b/src/connection.c index dcc8dd9ed666c734bc68a7b000fdbc2e93bc149a..a68598bb86a4a1a204d19d61ced6b738c2a19aeb 100644 --- a/src/connection.c +++ b/src/connection.c @@ -41,6 +41,7 @@ #include "format.h" #include "format_mp3.h" #include "event.h" +#include "admin.h" #define CATMODULE "connection" @@ -437,7 +438,7 @@ static int _check_pass_ice(http_parser_t *parser, char *correctpass) return 1; } -static int _check_relay_pass(http_parser_t *parser) +int connection_check_relay_pass(http_parser_t *parser) { ice_config_t *config = config_get_config(); char *pass = config->relay_password; @@ -448,7 +449,7 @@ static int _check_relay_pass(http_parser_t *parser) return _check_pass_http(parser, "relay", pass); } -static int _check_admin_pass(http_parser_t *parser) +int connection_check_admin_pass(http_parser_t *parser) { ice_config_t *config = config_get_config(); char *pass = config->admin_password; @@ -461,7 +462,7 @@ static int _check_admin_pass(http_parser_t *parser) return _check_pass_http(parser, "admin", pass); } -static int _check_source_pass(http_parser_t *parser, char *mount) +int connection_check_source_pass(http_parser_t *parser, char *mount) { ice_config_t *config = config_get_config(); char *pass = config->source_password; @@ -502,108 +503,6 @@ static int _check_source_pass(http_parser_t *parser, char *mount) return ret; } -static void handle_fallback_request(client_t *client) -{ - source_t *source; - char *mount, *value, *old; - int bytes; - - mount = httpp_get_query_param(client->parser, "mount"); - value = httpp_get_query_param(client->parser, "fallback"); - - if(!_check_source_pass(client->parser, mount)) { - INFO0("Bad or missing password on fallback configuration request"); - client_send_401(client); - return; - } - - if(value == NULL || mount == NULL) { - client_send_400(client, "Missing parameter"); - return; - } - - avl_tree_rlock(global.source_tree); - source = source_find_mount(mount); - avl_tree_unlock(global.source_tree); - - if(source == NULL) { - client_send_400(client, "Current source not found"); - return; - } - - old = source->fallback_mount; - source->fallback_mount = strdup(value); - free(old); - - client->respcode = 200; - bytes = sock_write(client->con->sock, - "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n" - "Fallback configured"); - if(bytes > 0) client->con->sent_bytes = bytes; - client_destroy(client); -} - -static void handle_metadata_request(client_t *client) -{ - source_t *source; - char *action; - char *mount; - char *value; - mp3_state *state; - int bytes; - - action = httpp_get_query_param(client->parser, "mode"); - mount = httpp_get_query_param(client->parser, "mount"); - value = httpp_get_query_param(client->parser, "song"); - - if(!_check_source_pass(client->parser, mount)) { - INFO0("Metadata request with wrong or missing password"); - client_send_401(client); - return; - } - - if(value == NULL || action == NULL || mount == NULL) { - client_send_400(client, "Missing parameter"); - return; - } - - avl_tree_rlock(global.source_tree); - source = source_find_mount(mount); - avl_tree_unlock(global.source_tree); - - if(source == NULL) { - client_send_400(client, "No such mountpoint"); - return; - } - - if(source->format->type != FORMAT_TYPE_MP3) { - client_send_400(client, "Not mp3, cannot update metadata"); - return; - } - - if(strcmp(action, "updinfo") != 0) { - client_send_400(client, "No such action"); - return; - } - - state = source->format->_state; - thread_mutex_lock(&(state->lock)); - free(state->metadata); - state->metadata = strdup(value); - state->metadata_age++; - state->metadata_raw = 0; - thread_mutex_unlock(&(state->lock)); - - DEBUG2("Metadata on mountpoint %s changed to \"%s\"", mount, value); - stats_event(mount, "title", value); - client->respcode = 200; - bytes = sock_write(client->con->sock, - "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n" - "Update successful"); - if(bytes > 0) client->con->sent_bytes = bytes; - client_destroy(client); -} - static void _handle_source_request(connection_t *con, http_parser_t *parser, char *uri) { @@ -614,7 +513,7 @@ static void _handle_source_request(connection_t *con, INFO1("Source logging in at mountpoint \"%s\"", uri); stats_event_inc(NULL, "source_connections"); - if (!_check_source_pass(parser, uri)) { + if (!connection_check_source_pass(parser, uri)) { INFO1("Source (%s) attempted to login with invalid or missing password", uri); client_send_401(client); return; @@ -645,7 +544,7 @@ static void _handle_stats_request(connection_t *con, stats_event_inc(NULL, "stats_connections"); - if (!_check_admin_pass(parser)) { + if (!connection_check_admin_pass(parser)) { ERROR0("Bad password for stats connection"); connection_close(con); httpp_destroy(parser); @@ -699,27 +598,10 @@ static void _handle_get_request(connection_t *con, ** aren't subject to the limits. */ /* TODO: add GUID-xxxxxx */ - if (strcmp(uri, "/admin/stats.xml") == 0) { - if (!_check_admin_pass(parser)) { - INFO0("Request for /admin/stats.xml with incorrect or no password"); - client_send_401(client); - return; - } - DEBUG0("Stats request, sending xml stats"); - stats_sendxml(client); - client_destroy(client); - return; - } - - if(strcmp(uri, "/admin/metadata") == 0) { - DEBUG0("Got metadata update request"); - handle_metadata_request(client); - return; - } - if(strcmp(uri, "/admin/fallbacks") == 0) { - DEBUG0("Got fallback request"); - handle_fallback_request(client); + /* Dispatch all admin requests */ + if (strncmp(uri, "/admin/", 7) == 0) { + admin_handle_request(client, uri); return; } @@ -801,7 +683,7 @@ static void _handle_get_request(connection_t *con, } if (strcmp(uri, "/admin/streamlist") == 0) { - if (!_check_relay_pass(parser)) { + if (!connection_check_relay_pass(parser)) { INFO0("Client attempted to fetch /admin/streamlist with bad password"); client_send_401(client); } else { diff --git a/src/connection.h b/src/connection.h index c7272112f3c38ec80c0dfd9b80bac014c8fb2a37..9fc670e88eaee4a2456b0d077eac9c60f800a0fa 100644 --- a/src/connection.h +++ b/src/connection.h @@ -37,6 +37,10 @@ int connection_create_source(struct _client_tag *client, connection_t *con, void connection_inject_event(int eventnum, void *event_data); +int connection_check_source_pass(http_parser_t *parser, char *mount); +int connection_check_relay_pass(http_parser_t *parser); +int connection_check_admin_pass(http_parser_t *parser); + extern rwlock_t _source_shutdown_rwlock; #endif /* __CONNECTION_H__ */ diff --git a/src/source.c b/src/source.c index ea39227583ef12e854325c36e47d34f4969737d2..bb76812c7c1dc5372a73123ac8a2cf6038484e61 100644 --- a/src/source.c +++ b/src/source.c @@ -674,13 +674,10 @@ static int _parse_audio_info(source_t *source, char *s) pvar = strchr(token, '='); if (pvar) { variable = (char *)malloc(pvar-token+1); - memset(variable, '\000', pvar-token+1); strncpy(variable, token, pvar-token); pvar++; if (strlen(pvar)) { - value = (char *)malloc(strlen(pvar)+1); - memset(value, '\000', strlen(pvar)+1); - strncpy(value, pvar, strlen(pvar)); + value = util_url_unescape(pvar); util_dict_set(source->audio_info, variable, value); stats_event(source->mount, variable, value); if (value) { @@ -688,7 +685,7 @@ static int _parse_audio_info(source_t *source, char *s) } } if (variable) { - free(variable); + free(variable); } } s = NULL; diff --git a/src/util.c b/src/util.c index 279d1bbc895ae33c71c7fdb6a3d0ee0ff9926047..4779d2036197dbf26457c3e1eaff089058a28bfe 100644 --- a/src/util.c +++ b/src/util.c @@ -216,7 +216,54 @@ char *util_get_path_from_normalised_uri(char *uri) { return fullpath; } +static char hexchars[16] = { + '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' +}; + +static char safechars[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + char *util_url_escape(char *src) +{ + int len = strlen(src); + /* Efficiency not a big concern here, keep the code simple/conservative */ + char *dst = calloc(1, len*3 + 1); + unsigned char *source = src; + int i,j=0; + + for(i=0; i < len; i++) { + if(safechars[source[i]]) { + dst[j++] = source[i]; + } + else { + dst[j] = '%'; + dst[j+1] = hexchars[ source[i] & 0x15 ]; + dst[j+2] = hexchars[ (source[i] >> 4) & 0x15 ]; + j+= 3; + } + } + + dst[j] = 0; + return dst; +} + +char *util_url_unescape(char *src) { int len = strlen(src); unsigned char *decoded; @@ -247,7 +294,7 @@ char *util_url_escape(char *src) done = 1; break; case 0: - ERROR0("Fatal internal logic error in util_url_escape()"); + ERROR0("Fatal internal logic error in util_url_unescape()"); free(decoded); return NULL; break; @@ -275,7 +322,7 @@ char *util_normalise_uri(char *uri) { if(uri[0] != '/') return NULL; - path = util_url_escape(uri); + path = util_url_unescape(uri); if(path == NULL) { WARN1("Error decoding URI: %s\n", uri); diff --git a/src/util.h b/src/util.h index e8fc7e6236518cc69c777936c2ba4734f01f0ba6..c3eec5f4813cd135f188cd279236c8ce79b85a6c 100644 --- a/src/util.h +++ b/src/util.h @@ -14,6 +14,7 @@ char *util_normalise_uri(char *uri); char *util_base64_encode(char *data); char *util_base64_decode(unsigned char *input); +char *util_url_unescape(char *src); char *util_url_escape(char *src); /* String dictionary type, without support for NULL keys, or multiple