diff --git a/src/acl.c b/src/acl.c index 79e3f6ad..42c4dc48 100644 --- a/src/acl.c +++ b/src/acl.c @@ -31,7 +31,7 @@ struct acl_tag { /* admin/ interface */ struct { - int command; + admin_command_id_t command; acl_policy_t policy; } admin_commands[MAX_ADMIN_COMMANDS]; size_t admin_commands_len; @@ -253,7 +253,7 @@ int acl_set_admin_str__callbck(acl_t *acl, const char *str) { size_t read_i, write_i; - int command = admin_get_command(str); + admin_command_id_t command = admin_get_command(str); if (command == ADMIN_COMMAND_ERROR) return -1; @@ -279,7 +279,7 @@ int acl_set_admin_str__callbck(acl_t *acl, return 0; } -acl_policy_t acl_test_admin(acl_t *acl, int command) +acl_policy_t acl_test_admin(acl_t *acl, admin_command_id_t command) { size_t i; diff --git a/src/acl.h b/src/acl.h index 80222c8c..03101a68 100644 --- a/src/acl.h +++ b/src/acl.h @@ -18,6 +18,11 @@ #include #include "common/httpp/httpp.h" +struct acl_tag; +typedef struct acl_tag acl_t; + +#include "admin.h" + typedef enum acl_policy_tag { /* Error on function call */ ACL_POLICY_ERROR = -1, @@ -27,8 +32,6 @@ typedef enum acl_policy_tag { ACL_POLICY_DENY = 1 } acl_policy_t; -struct acl_tag; -typedef struct acl_tag acl_t; /* basic functions to work with ACLs */ acl_t * acl_new(void); @@ -48,7 +51,7 @@ acl_policy_t acl_test_method(acl_t * acl, httpp_request_type_e method); /* admin/ interface specific functions */ int acl_set_admin_str__callbck(acl_t * acl, acl_policy_t policy, const char * str); #define acl_set_admin_str(acl,policy,str) acl_set_ANY_str((acl), (policy), (str), acl_set_admin_str__callbck) -acl_policy_t acl_test_admin(acl_t * acl, int command); +acl_policy_t acl_test_admin(acl_t * acl, admin_command_id_t command); /* web/ interface specific functions */ int acl_set_web_policy(acl_t * acl, acl_policy_t policy); diff --git a/src/admin.c b/src/admin.c index d1a51e29..e1823306 100644 --- a/src/admin.c +++ b/src/admin.c @@ -46,6 +46,8 @@ #define CATMODULE "admin" +#define ADMIN_MAX_COMMAND_TABLES 8 + /* Helper macros */ #define COMMAND_REQUIRE(client,name,var) \ do { \ @@ -94,14 +96,11 @@ #define DEFAULT_TRANSFORMED_REQUEST "" #define BUILDM3U_RAW_REQUEST "buildm3u" -typedef void (*request_function_ptr)(client_t *, source_t *, admin_format_t); - -typedef struct admin_command_handler { - const char *route; - const int type; - const int format; - const request_function_ptr function; -} admin_command_handler_t; +typedef struct { + const char *prefix; + size_t length; + const admin_command_handler_t *handlers; +} admin_command_table_t; static void command_fallback (client_t *client, source_t *source, admin_format_t response); static void command_metadata (client_t *client, source_t *source, admin_format_t response); @@ -151,33 +150,128 @@ static const admin_command_handler_t handlers[] = { { DEFAULT_RAW_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_TRANSFORMED, command_stats } }; -#define HANDLERS_COUNT (sizeof(handlers)/sizeof(*handlers)) +static admin_command_table_t command_tables[ADMIN_MAX_COMMAND_TABLES] = { + {.prefix = NULL, .length = (sizeof(handlers)/sizeof(*handlers)), .handlers = handlers}, +}; -int admin_get_command(const char *command) +static inline int __is_command_table_valid(const admin_command_table_t * table) +{ + if (table == NULL) + return 0; + + if (table->length == 0 || table->handlers == NULL) + return 0; + + return 1; +} + +static inline const admin_command_table_t * admin_get_table(admin_command_id_t command) +{ + size_t t = (command & 0x00FF0000) >> 16; + + if (t >= (sizeof(command_tables)/sizeof(*command_tables))) + return NULL; + + if (!__is_command_table_valid(&(command_tables[t]))) + return NULL; + + return &(command_tables[t]); +} + +static inline const admin_command_table_t * admin_get_table_by_prefix(const char *command) +{ + const char *end; + size_t i; + size_t len; + + end = strchr(command, '/'); + + if (end == NULL) { + for (i = 0; i < (sizeof(command_tables)/sizeof(*command_tables)); i++) + if (command_tables[i].prefix == NULL && __is_command_table_valid(&(command_tables[i]))) + return &(command_tables[i]); + + return NULL; + } + + len = end - command; + + for (i = 0; i < (sizeof(command_tables)/sizeof(*command_tables)); i++) { + if (!__is_command_table_valid(&(command_tables[i]))) + continue; + + if (command_tables[i].prefix != NULL && strlen(command_tables[i].prefix) == len && strncmp(command_tables[i].prefix, command, len) == 0) { + return &(command_tables[i]); + } + } + + return NULL; +} + +static inline admin_command_id_t admin_get_command_by_table_and_index(const admin_command_table_t *table, size_t index) +{ + size_t t = table - command_tables; + + if (t >= (sizeof(command_tables)/sizeof(*command_tables))) + return ADMIN_COMMAND_ERROR; + + if (index > 0x0FFFF) + return ADMIN_COMMAND_ERROR; + + if (!__is_command_table_valid(table)) + return ADMIN_COMMAND_ERROR; + + return (t << 16) | index; +} + +static inline size_t admin_get_index_by_command(admin_command_id_t command) +{ + return command & 0x0FFFF; +} + +admin_command_id_t admin_get_command(const char *command) { size_t i; + const admin_command_table_t *table = admin_get_table_by_prefix(command); + const char *suffix; - for (i = 0; i < HANDLERS_COUNT; i++) - if (strcmp(handlers[i].route, command) == 0) - return i; + if (table == NULL) + return COMMAND_ERROR; + + suffix = strchr(command, '/'); + if (suffix != NULL) { + suffix++; + } else { + suffix = command; + } + + for (i = 0; i < table->length; i++) + if (strcmp(table->handlers[i].route, suffix) == 0) + return admin_get_command_by_table_and_index(table, i); return COMMAND_ERROR; } /* Get the command handler for command or NULL */ -const admin_command_handler_t* admin_get_handler(int command) +const admin_command_handler_t* admin_get_handler(admin_command_id_t command) { - if (command > 0 && command < HANDLERS_COUNT) - return &handlers[command]; + const admin_command_table_t *table = admin_get_table(command); + size_t index = admin_get_index_by_command(command); - return NULL; + if (table == NULL) + return NULL; + + if (index >= table->length) + return NULL; + + return &(table->handlers[index]); } /* Get the command type for command * If the command is invalid, ADMINTYPE_ERROR is returned. */ -int admin_get_command_type(int command) +int admin_get_command_type(admin_command_id_t command) { const admin_command_handler_t* handler = admin_get_handler(command); @@ -187,6 +281,41 @@ int admin_get_command_type(int command) return ADMINTYPE_ERROR; } +int admin_command_table_register(const char *prefix, size_t handlers_length, const admin_command_handler_t *handlers) +{ + size_t i; + + if (prefix == NULL || handlers_length == 0 || handlers == NULL) + return -1; + + for (i = 0; i < (sizeof(command_tables)/sizeof(*command_tables)); i++) { + if (__is_command_table_valid(&(command_tables[i]))) + continue; + + command_tables[i].prefix = prefix; + command_tables[i].length = handlers_length; + command_tables[i].handlers = handlers; + + return 0; + } + + return -1; +} + +int admin_command_table_unregister(const char *prefix) +{ + size_t i; + + for (i = 0; i < (sizeof(command_tables)/sizeof(*command_tables)); i++) { + if (command_tables[i].prefix != NULL && strcmp(command_tables[i].prefix, prefix) == 0) { + memset(&(command_tables[i]), 0, sizeof(command_tables[i])); + return 0; + } + } + + return -1; +} + /* build an XML doc containing information about currently running sources. * If a mountpoint is passed then that source will not be added to the XML * doc even if the source is running */ @@ -350,7 +479,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) { + if (handler == NULL || handler->function == NULL) { ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %H", uri); client_send_error_by_id(client, ICECAST_ERROR_ADMIN_UNRECOGNISED_COMMAND); diff --git a/src/admin.h b/src/admin.h index 494c5341..d0770414 100644 --- a/src/admin.h +++ b/src/admin.h @@ -16,6 +16,10 @@ #include #include +#include + +/* Command IDs */ +typedef int32_t admin_command_id_t; /* formats */ typedef enum { @@ -25,6 +29,7 @@ typedef enum { ADMIN_FORMAT_PLAINTEXT } admin_format_t; +#include "compat.h" #include "refbuf.h" #include "client.h" #include "source.h" @@ -37,8 +42,17 @@ typedef enum { #define ADMINTYPE_HYBRID (ADMINTYPE_GENERAL|ADMINTYPE_MOUNT) /* special commands */ -#define ADMIN_COMMAND_ERROR (-1) -#define ADMIN_COMMAND_ANY 0 /* for ACL framework */ +#define ADMIN_COMMAND_ERROR ((admin_command_id_t)(-1)) +#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 struct admin_command_handler { + const char *route; + const int type; + const int format; + const admin_request_function_ptr function; +} admin_command_handler_t; void admin_handle_request(client_t *client, const char *uri); @@ -53,7 +67,13 @@ void admin_add_listeners_to_mount(source_t *source, xmlNodePtr admin_add_role_to_authentication(auth_t *auth, xmlNodePtr parent); -int admin_get_command(const char *command); -int admin_get_command_type(int command); +admin_command_id_t admin_get_command(const char *command); +int admin_get_command_type(admin_command_id_t command); + +/* Register and unregister admin commands below /admin/$prefix/. + * All parameters must be kept in memory as long as the registration is valid as there will be no copy made. + */ +int admin_command_table_register(const char *prefix, size_t handlers_length, const admin_command_handler_t *handlers); +int admin_command_table_unregister(const char *prefix); #endif /* __ADMIN_H__ */ diff --git a/src/client.h b/src/client.h index 7ab8e3f6..f619e391 100644 --- a/src/client.h +++ b/src/client.h @@ -46,8 +46,7 @@ typedef enum _reuse_tag { ICECAST_REUSE_UPGRADETLS } reuse_t; -struct _client_tag -{ +struct _client_tag { /* mode of operation for this client */ operation_mode mode; @@ -70,7 +69,7 @@ struct _client_tag int respcode; /* admin command if any. ADMIN_COMMAND_ERROR if not an admin command. */ - int admin_command; + admin_command_id_t admin_command; /* authentication instances we still need to go thru */ struct auth_stack_tag *authstack; @@ -110,7 +109,6 @@ struct _client_tag /* function to check if refbuf needs updating */ int (*check_buffer)(struct source_tag *source, struct _client_tag *client); - }; int client_create (client_t **c_ptr, connection_t *con, http_parser_t *parser); diff --git a/src/event.h b/src/event.h index dc0dedec..aafc9fc8 100644 --- a/src/event.h +++ b/src/event.h @@ -61,7 +61,7 @@ struct event_tag { 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 */ + admin_command_id_t client_admin_command; /* from client->admin_command */ }; struct event_registration_tag { diff --git a/src/source.h b/src/source.h index 8a5e1f15..c34d99a3 100644 --- a/src/source.h +++ b/src/source.h @@ -25,8 +25,7 @@ typedef struct source_tag source_t; #include -struct source_tag -{ +struct source_tag { mutex_t lock; client_t *client; connection_t *con; @@ -83,7 +82,6 @@ struct source_tag refbuf_t *stream_data_tail; playlist_t *history; - }; source_t *source_reserve (const char *mount);