Commit 293b7db0 by Marvin Scholz

Merge branch 'CORS' of https://github.com/jucrouzet/Icecast-Server into jucrouzet-CORS

parents c1b4e755 a0bb2556
......@@ -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 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
......
......@@ -148,6 +148,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)
......@@ -219,7 +230,7 @@ static inline void __read_unsigned_int(xmlDocPtr doc, xmlNodePtr node, unsigned
}
if (str)
xmlFree(str);
}
}
static inline int __parse_public(const char *str)
{
......@@ -657,6 +668,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));
}
......@@ -1028,6 +1040,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));
......@@ -2220,6 +2234,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 "global.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 */
......@@ -175,6 +175,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;
......@@ -220,6 +235,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;
......
......@@ -44,6 +44,8 @@
/* for ADMIN_COMMAND_ERROR */
#include "admin.h"
#include "cors.h"
#ifdef _WIN32
#define snprintf _snprintf
#endif
......@@ -292,6 +294,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;
......
......@@ -111,6 +111,7 @@ int client_create (client_t **c_ptr, connection_t *con, http_parser_t *parser);
void client_destroy(client_t *client);
void client_send_error(client_t *client, int status, int plain, const char *message);
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);
int client_send_bytes (client_t *client, const void *buf, unsigned len);
int client_read_bytes (client_t *client, void *buf, unsigned len);
......
......@@ -134,7 +134,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);
......@@ -1130,6 +1130,7 @@ static int _handle_aliases(client_t *client, char **uri)
*/
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;
......@@ -1139,7 +1140,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(client, 401, 1, "You need to authenticate\r\n");
free(uri);
......@@ -1157,6 +1164,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(client, 400, 0, "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__ */
......@@ -49,6 +49,7 @@
#include "util.h"
#include "source.h"
#include "admin.h"
#include "cors.h"
#define CATMODULE "util"