...
 
......@@ -13,14 +13,14 @@ noinst_HEADERS = admin.h cfgfile.h logging.h sighandler.h connection.h \
acl.h auth.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 \
format_kate.h format_skeleton.h format_opus.h
format_kate.h format_skeleton.h format_opus.h cors.h
icecast_SOURCES = cfgfile.c main.c logging.c sighandler.c connection.c global.c \
util.c errors.c slave.c source.c stats.c refbuf.c client.c playlist.c \
xslt.c fserve.c admin.c md5.c matchfile.c tls.c \
format.c format_ogg.c format_mp3.c format_midi.c format_flac.c format_ebml.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
acl.c auth.c auth_htpasswd.c auth_anonymous.c auth_static.c cors.c
EXTRA_icecast_SOURCES = curl.c yp.c \
auth_url.c event_url.c \
format_vorbis.c format_theora.c format_speex.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;
......
......@@ -18,6 +18,11 @@
#include <libxml/tree.h>
#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);
......
......@@ -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},
};
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;
int admin_get_command(const char *command)
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]);
for (i = 0; i < HANDLERS_COUNT; i++)
if (strcmp(handlers[i].route, command) == 0)
return 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;
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);
......
......@@ -16,6 +16,10 @@
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <stdint.h>
/* 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__ */
......@@ -150,6 +150,17 @@ 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);
static void _parse_cors(xmlDocPtr doc,
xmlNodePtr node,
ice_config_cors_path_t **cors_paths);
static int _parse_cors_path(xmlDocPtr doc,
xmlNodePtr node,
ice_config_cors_path_t *cors_path);
static void config_clear_cors(ice_config_cors_path_t *cors_paths);
operation_mode config_str_to_omode(const char *str)
{
if (!str || !*str)
......@@ -221,7 +232,7 @@ static inline void __read_unsigned_int(xmlDocPtr doc, xmlNodePtr node, unsigned
}
if (str)
xmlFree(str);
}
}
static inline int __parse_public(const char *str)
{
......@@ -664,6 +675,7 @@ void config_clear(ice_config_t *c)
#endif
config_clear_http_header(c->http_headers);
config_clear_cors(c->cors_paths);
memset(c, 0, sizeof(ice_config_t));
}
......@@ -1037,6 +1049,8 @@ static void _parse_root(xmlDocPtr doc,
} else if (xmlStrcmp(node->name, XMLSTR("event-bindings")) == 0 ||
xmlStrcmp(node->name, XMLSTR("kartoffelsalat")) == 0) {
_parse_events(&configuration->event, node->xmlChildrenNode);
} else if (xmlStrcmp(node->name, XMLSTR("cors")) == 0) {
_parse_cors(doc, node->xmlChildrenNode, &(configuration->cors_paths));
}
} while ((node = node->next));
......@@ -2259,6 +2273,254 @@ static void _parse_events(event_registration_t **events, xmlNodePtr node)
}
}
static ice_config_cors_path_t* _cors_sort_paths_by_base_length_desc(ice_config_cors_path_t *cors_paths)
{
ice_config_cors_path_t *curr = cors_paths;
ice_config_cors_path_t *prev = cors_paths;
ice_config_cors_path_t *largest = cors_paths;
ice_config_cors_path_t *largestPrev = cors_paths;
ice_config_cors_path_t *tmp;
// End of sorting or only one path or no path
if (!cors_paths || !cors_paths->next) {
return cors_paths;
}
// Find the largest base and set it first.
while(curr != NULL) {
if(strlen(curr->base) > strlen(largest->base)) {
largestPrev = prev;
largest = curr;
}
prev = curr;
curr = curr->next;
}
if(largest != cors_paths) {
largestPrev->next = cors_paths;
tmp = cors_paths->next;
cors_paths->next = largest->next;
largest->next = tmp;
}
// Recurse to the rest of the list
largest->next = _cors_sort_paths_by_base_length_desc(largest->next);
return largest;
}
static void _parse_cors(xmlDocPtr doc,
xmlNodePtr node,
ice_config_cors_path_t **cors_paths)
{
ice_config_cors_path_t *path = NULL;
ice_config_cors_path_t *next = NULL;
char *base = NULL;
do {
if (node == NULL)
break;
if (xmlIsBlankNode(node))
continue;
if (!node->name)
continue;
if (xmlStrcmp(node->name, XMLSTR("path")) != 0) {
continue;
}
if (
!(base = (char *)xmlGetProp(node, XMLSTR("base"))) ||
!strlen(base)
) {
ICECAST_LOG_WARN("Ignoring <cors><path> tag without base attribute or empty");
xmlFree(xmlGetProp(node, XMLSTR("base")));
continue;
}
path = calloc(1, sizeof(ice_config_cors_path_t));
if (!path) {
ICECAST_LOG_ERROR("Out of memory while parsing config file");
break;
}
path->base = base;
if (!_parse_cors_path(doc, node, path)) {
base = NULL;
if (!*cors_paths) {
*cors_paths = path;
continue;
}
next = *cors_paths;
while (next->next) {
next = next->next;
}
next->next = path;
} else {
free(path);
}
} while ((node = node->next));
/* in case we used break we may need to clean those up */
if (base)
xmlFree(base);
*cors_paths = _cors_sort_paths_by_base_length_desc(*cors_paths);
}
static void _cors_sort_origins_by_length_desc(char **origins)
{
uint length;
char *temp;
// If there are no origins or only one, no sort.
if (!origins || !origins[1]) {
return;
}
// Count origins.
for (length = 0; origins[length]; length++);
// Sort origin by length, descending.
for (int step = 0; step < length; step++)
{
for (int i = 0; i < length - step - 1; i++)
{
if (strlen(origins[i]) < strlen(origins[i+1]))
{
temp = origins[i];
origins[i] = origins[i + 1];
origins[i + 1] = temp;
}
}
}
}
static int _parse_cors_path(xmlDocPtr doc,
xmlNodePtr node,
ice_config_cors_path_t *cors_path)
{
int allowed_count = 0;
int forbidden_count = 0;
int exposed_headers_count = 0;
xmlNodePtr tmpNode = node->xmlChildrenNode;
while ((tmpNode = tmpNode->next)) {
if (tmpNode == NULL)
break;
if (!tmpNode->name)
continue;
if (xmlStrcmp(tmpNode->name, XMLSTR("no-cors")) == 0) {
cors_path->no_cors = 1;
return 0;
}
if (xmlIsBlankNode(tmpNode))
continue;
if (xmlStrcmp(tmpNode->name, XMLSTR("allowed-origin")) == 0) {
allowed_count++;
continue;
}
if (xmlStrcmp(tmpNode->name, XMLSTR("forbidden-origin")) == 0) {
forbidden_count++;
continue;
}
if (xmlStrcmp(tmpNode->name, XMLSTR("exposed-header")) == 0) {
exposed_headers_count++;
continue;
}
}
if (!allowed_count && !forbidden_count && !exposed_headers_count) {
return 1;
}
if (allowed_count) {
cors_path->allowed = calloc(allowed_count + 1, sizeof(char *));
if (!cors_path->allowed) {
ICECAST_LOG_ERROR("Out of memory while parsing config file");
return 1;
}
cors_path->allowed[allowed_count] = NULL;
}
if (forbidden_count) {
cors_path->forbidden = calloc(forbidden_count + 1, sizeof(char *));
if (!cors_path->forbidden) {
ICECAST_LOG_ERROR("Out of memory while parsing config file");
if (cors_path->allowed)
free(cors_path->allowed);
return 1;
}
cors_path->forbidden[forbidden_count] = NULL;
}
tmpNode = node->xmlChildrenNode;
allowed_count = forbidden_count = exposed_headers_count = 0;
while ((tmpNode = tmpNode->next)) {
if (tmpNode == NULL)
break;
if (!tmpNode->name)
continue;
if (xmlIsBlankNode(tmpNode))
continue;
if (xmlStrcmp(tmpNode->name, XMLSTR("allowed-origin")) == 0) {
cors_path->allowed[allowed_count++] = (char *)xmlNodeListGetString(doc, tmpNode->xmlChildrenNode, 1);
continue;
}
if (xmlStrcmp(tmpNode->name, XMLSTR("forbidden-origin")) == 0) {
cors_path->forbidden[forbidden_count++] = (char *)xmlNodeListGetString(doc, tmpNode->xmlChildrenNode, 1);
continue;
}
if (xmlStrcmp(tmpNode->name, XMLSTR("exposed-header")) == 0) {
char *orig_value = (char *)xmlNodeListGetString(doc, tmpNode->xmlChildrenNode, 1);
int first_value = 1;
if (!cors_path->exposed_headers) {
cors_path->exposed_headers = calloc(strlen(orig_value) + 1, sizeof(char));
} else {
cors_path->exposed_headers = realloc(
cors_path->exposed_headers,
(strlen(cors_path->exposed_headers) + strlen(orig_value) + 3)
);
first_value = 0;
}
if (!cors_path->exposed_headers) {
ICECAST_LOG_ERROR("Out of memory while parsing config file");
if (cors_path->allowed)
free(cors_path->allowed);
if (cors_path->forbidden)
free(cors_path->forbidden);
return 1;
}
if (!first_value)
cors_path->exposed_headers = strcat(cors_path->exposed_headers, ", ");
cors_path->exposed_headers = strcat(cors_path->exposed_headers, orig_value);
xmlFree(orig_value);
continue;
}
}
_cors_sort_origins_by_length_desc(cors_path->allowed);
_cors_sort_origins_by_length_desc(cors_path->forbidden);
return 0;
}
void config_clear_cors(ice_config_cors_path_t *cors_paths)
{
while (cors_paths) {
ice_config_cors_path_t *path = cors_paths;
cors_paths = path->next;
if (path->allowed) {
for (int i = 0; path->allowed[i]; i++) {
xmlFree(path->allowed[i]);
}
free(path->allowed);
}
if (path->forbidden) {
for (int i = 0; path->forbidden[i]; i++) {
xmlFree(path->forbidden[i]);
}
free(path->forbidden);
}
if (path->exposed_headers) {
free(path->exposed_headers);
}
xmlFree(path->base);
free(path);
}
}
config_options_t *config_parse_options(xmlNodePtr node)
{
config_options_t *ret = NULL;
......
......@@ -3,7 +3,7 @@
* This program is distributed under the GNU General Public License, version 2.
* A copy of this license is included with this source.
*
* Copyright 2000-2004, Jack Moffitt <jack@xiph.org>,
* Copyright 2000-2004, Jack Moffitt <jack@xiph.org>,
* Michael Smith <msmith@xiph.org>,
* oddsock <oddsock@xiph.org>,
* Karl Heyes <karl@xiph.org>
......@@ -30,7 +30,7 @@ struct _mount_proxy;
#include "slave.h"
#include "connection.h"
#define XMLSTR(str) ((xmlChar *)(str))
#define XMLSTR(str) ((xmlChar *)(str))
typedef enum _operation_mode {
/* Default operation mode. may depend on context */
......@@ -178,6 +178,21 @@ typedef struct _listener_t {
tlsmode_t tls;
} listener_t;
typedef struct ice_config_cors_path {
/* base path */
char *base;
/* no-cors path */
int no_cors;
/* allowed origins */
char **allowed;
/* forbidden origins */
char **forbidden;
/* exposed headers */
char *exposed_headers;
/* link to the next list element */
struct ice_config_cors_path *next;
} ice_config_cors_path_t;
typedef struct _config_tls_context {
char *cert_file;
char *key_file;
......@@ -224,6 +239,7 @@ typedef struct ice_config_tag {
char *master_password;
ice_config_http_header_t *http_headers;
ice_config_cors_path_t *cors_paths;
/* is TLS supported by the server? */
int tls_ok;
......
......@@ -46,6 +46,8 @@
/* for ADMIN_COMMAND_ERROR */
#include "admin.h"
#include "cors.h"
#ifdef _WIN32
#define snprintf _snprintf
#endif
......@@ -319,6 +321,42 @@ void client_send_101(client_t *client, reuse_t reuse)
fserve_add_client(client, NULL);
}
/* Sends an empty 204 response (for OPTIONS) */
void client_send_204(client_t *client)
{
ssize_t ret;
char *message;
message = calloc(PER_CLIENT_REFBUF_SIZE, sizeof(char));
if (!message) {
client_send_500(client, "Unable to allocate memory for response");
return;
}
ret = util_http_build_header(message, // Response buffer
PER_CLIENT_REFBUF_SIZE, // Buffer size
0, // Offset
0, // Prevent cache
204, // Status code
"No Content", // Status message
NULL, // Content-Type
NULL, // Charset
NULL, // Data
NULL, // Source
client);
if (ret == -1 || ret >= PER_CLIENT_REFBUF_SIZE) {
free(message);
ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
client_send_500(client, "Header generation failed.");
return;
}
client_send_bytes(client, message, strlen(message));
client_destroy(client);
free(message);
}
void client_send_426(client_t *client, reuse_t reuse)
{
ssize_t ret;
......
......@@ -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,13 +109,13 @@ 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);
void client_destroy(client_t *client);
void client_send_error_by_id(client_t *client, icecast_error_id_t id);
void client_send_101(client_t *client, reuse_t reuse);
void client_send_204(client_t *client);
void client_send_426(client_t *client, reuse_t reuse);
admin_format_t client_get_admin_format_by_content_negotiation(client_t *client);
int client_send_bytes (client_t *client, const void *buf, unsigned len);
......
......@@ -135,7 +135,7 @@ void connection_shutdown(void)
tls_ctx_unref(tls_ctx);
matchfile_release(banned_ip);
matchfile_release(allowed_ip);
thread_cond_destroy(&global.shutdown_cond);
thread_rwlock_destroy(&_source_shutdown_rwlock);
thread_spin_destroy (&_connection_lock);
......@@ -1172,6 +1172,7 @@ static void _handle_admin_request(client_t *client, char *adminuri)
*/
static void _handle_authed_client(client_t *client, void *uri, auth_result result)
{
httpp_request_type_e req_type;
auth_stack_release(client->authstack);
client->authstack = NULL;
......@@ -1181,7 +1182,13 @@ static void _handle_authed_client(client_t *client, void *uri, auth_result resul
return;
}
if (acl_test_method(client->acl, client->parser->req_type) != ACL_POLICY_ALLOW) {
// If path is not /admin/ OPTIONS should respect the same acl as GET
// for preflighted request
req_type = client->parser->req_type;
if (strstr(client->parser->uri, "/admin/") != client->parser->uri) {
req_type = httpp_req_get;
}
if (acl_test_method(client->acl, req_type) != ACL_POLICY_ALLOW) {
ICECAST_LOG_ERROR("Client (role=%s, username=%s) not allowed to use this request method on %H", client->role, client->username, uri);
client_send_error_by_id(client, ICECAST_ERROR_GEN_CLIENT_NEEDS_TO_AUTHENTICATE);
free(uri);
......@@ -1211,6 +1218,9 @@ static void _handle_authed_client(client_t *client, void *uri, auth_result resul
case httpp_req_get:
_handle_get_request(client, uri);
break;
case httpp_req_options:
client_send_204(client);
break;
default:
ICECAST_LOG_ERROR("Wrong request type from client");
client_send_error_by_id(client, ICECAST_ERROR_CON_UNKNOWN_REQUEST);
......
/* Icecast
*
* This program is distributed under the GNU General Public License, version 2.
* A copy of this license is included with this source.
*
* Copyright 2017, Julien CROUZET <contact@juliencrouzet.fr>
*/
/**
* Cors handling functions
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <ctype.h>
#include "cfgfile.h"
#include "client.h"
#include "logging.h"
#define CATMODULE "CORS"
static const char* cors_header_names[7] = {
"access-control-allow-origin",
"access-control-expose-headers",
"access-control-max-age",
"access-control-allow-credentials",
"access-control-allow-methods",
"access-control-allow-headers",
NULL
};
static const char *icy_headers = "icy-br, icy-caps, icy-description, icy-genre, icy-metaint, icy-metadata-interval, icy-name, icy-pub, icy-public, icy-url";
static ice_config_cors_path_t* _find_matching_path(ice_config_cors_path_t *paths, char *path)
{
ice_config_cors_path_t *matching_path = paths;
while(matching_path) {
if (strncmp(matching_path->base, path, strlen(matching_path->base)) == 0) {
return matching_path;
}
matching_path = matching_path->next;
}
return NULL;
}
static int _cors_valid_origin(ice_config_cors_path_t *path, const char *origin) {
if (path->forbidden) {
for (int i = 0; path->forbidden[i]; i++) {
if (strstr(origin, path->forbidden[i]) == origin) {
ICECAST_LOG_DEBUG(
"Declared origin \"%s\" matches forbidden origin \"%s\", not sending CORS",
origin,
path->forbidden[i]
);
return 0;
}
}
}
if (path->allowed) {
for (int i = 0; path->allowed[i]; i++) {
if ((strlen(path->allowed[i]) == 1) && path->allowed[i][0] == '*') {
ICECAST_LOG_DEBUG(
"All (\"*\") allowed origin for \"%s\", sending CORS",
origin
);
return 1;
}
if (strstr(origin, path->allowed[i]) == origin) {
ICECAST_LOG_DEBUG(
"Declared origin \"%s\" matches allowed origin \"%s\", sending CORS",
origin,
path->allowed[i]
);
return 1;
}
}
}
ICECAST_LOG_DEBUG(
"Declared origin \"%s\" does not matches any declared origin, not sending CORS",
origin
);
return 0;
}
static void _add_header(char **out,
size_t *len,
const char *header_name,
const char *header_value)
{
int new_length;
char *new_out;
if (!header_name || !header_value || !strlen(header_name)) {
return;
}
new_length = strlen(header_name) + strlen(header_value) + 4;
new_out = calloc(*len + new_length, sizeof(char));
if (!new_out) {
ICECAST_LOG_ERROR("Out of memory while setting CORS header.");
return;
}
snprintf(new_out,
*len + new_length,
"%s%s: %s\r\n",
*out,
header_name,
header_value);
free(*out);
*len += new_length;
*out = new_out;
}
/**
* Removes an header by its name in current headers list.
* Header removal is needed to remove any manually added headers
* added while a forbidden rule is active.
*/
static void _remove_header(char **out,
size_t *len,
const char *header_name)
{
int header_start[100];
int header_end[100];
int current_position = 0;
int found_count = 0;
char *new_out;
if (!*len)
return;
while((current_position < (*len -1)) && found_count < 100) {
char *substr = strcasestr((*out + current_position), header_name);
char *substr_end;
if (!substr) {
break;
}
substr_end = strstr(substr, "\r\n");
if (!substr_end) {
return;
}
header_start[found_count] = substr - *out;
header_end[found_count] = substr_end - *out + 2;
current_position = header_end[found_count];
found_count++;
}
if (!found_count) {
return;
}
current_position = 0;
new_out = calloc(*len + 1, sizeof(char));
if (!new_out) {
return;
}
free(*out);
for (int i = 0; i < found_count; i++) {
while (current_position < header_start[i]) {
new_out[current_position] = *out[current_position];
}
current_position = header_end[i];
}
while (current_position < *len) {
new_out[current_position] = 0;
current_position++;
}
*out = new_out;
for (int i = 0; i < found_count; i++) {
*len -= header_end[i] - header_start[i];
}
return;
}
static void _add_cors(char **out,
size_t *len,
ice_config_cors_path_t *path,
char *origin)
{
_add_header(out, len, "Access-Control-Allow-Origin", origin);
if (path->exposed_headers) {
_add_header(out, len, "Access-Control-Expose-Headers", path->exposed_headers);
} else {
_add_header(out, len, "Access-Control-Expose-Headers", icy_headers);
}
_add_header(out, len, "Access-Control-Max-Age", "3600");
_add_header(out, len, "Access-Control-Allow-Credentials", "true");
_add_header(out, len, "Access-Control-Allow-Methods", "GET");
_add_header(out, len, "Access-Control-Allow-Headers", "icy-metadata");
return;
}
static void _remove_cors(char **out, size_t *len) {
for(int i = 0; cors_header_names[i]; i++) {
_remove_header(out, len, cors_header_names[i]);
}
return;
}
void cors_set_headers(char **out,
size_t *len,
ice_config_cors_path_t *paths,
struct _client_tag *client)
{
char *origin = NULL;
char *path = (char *)client->parser->uri;
ice_config_cors_path_t *matching_path;
if (!paths)
return;
if (!(origin = (char *)httpp_getvar(client->parser, "origin")))
return;
if (!path)
return;
matching_path = _find_matching_path(paths, path);
if (!matching_path) {
ICECAST_LOG_DEBUG(
"Requested path \"%s\" does not matches any declared CORS configured path",
path
);
return;
}
ICECAST_LOG_DEBUG(
"Requested path \"%s\" matches the \"%s\" declared CORS path",
path,
matching_path->base
);
_remove_cors(out, len);
if (
!matching_path->no_cors &&
_cors_valid_origin(matching_path, origin)
) {
_add_cors(out, len, matching_path, origin);
}
return;
}
/* Icecast
*
* This program is distributed under the GNU General Public License, version 2.
* A copy of this license is included with this source.
*
* Copyright 2017, Julien CROUZET <contact@juliencrouzet.fr>
*/
#ifndef __CORS_H__
#define __CORS_H__
#include <sys/types.h>
#include "cfgfile.h"
#include "client.h"
void cors_set_headers(char **out,
size_t *len,
ice_config_cors_path_t *options,
struct _client_tag *client);
#endif /* __CORS_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 {
......
......@@ -25,8 +25,7 @@ typedef struct source_tag source_t;
#include <stdio.h>
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);
......
......@@ -49,6 +49,7 @@
#include "util.h"
#include "source.h"
#include "admin.h"
#include "cors.h"
#define CATMODULE "util"
......@@ -641,7 +642,11 @@ static inline void _build_headers_loop(char **ret, size_t *len, ice_config_htt
} while ((header = header->next));
*ret = r;
}
static inline char * _build_headers(int status, ice_config_t *config, source_t *source) {
static inline char * _build_headers(int status,
ice_config_t *config,
source_t *source,
struct _client_tag *client)
{
mount_proxy *mountproxy = NULL;
char *ret = NULL;
size_t len = 1;
......@@ -656,6 +661,8 @@ static inline char * _build_headers(int status, ice_config_t *config, source_t *
if (mountproxy && mountproxy->http_headers)
_build_headers_loop(&ret, &len, mountproxy->http_headers, status);
cors_set_headers(&ret, &len, config->cors_paths, client);
return ret;
}
......@@ -760,13 +767,13 @@ ssize_t util_http_build_header(char * out, size_t len, ssize_t offset,
currenttime_buffer[0] = '\0';
config = config_get_config();
extra_headers = _build_headers(status, config, source);
extra_headers = _build_headers(status, config, source, client);
ret = snprintf (out, len, "%sServer: %s\r\nConnection: %s\r\nAccept-Encoding: identity\r\nAllow: %s\r\n%s%s%s%s%s%s%s%s",
status_buffer,
config->server_id,
connection_header,
(client && client->admin_command == ADMIN_COMMAND_ERROR ?
"GET, SOURCE" : "GET"),
"GET, OPTIONS, SOURCE" : "GET, OPTIONS"),
upgrade_header,
currenttime_buffer,
contenttype_buffer,
......