diff --git a/conf/icecast.xml.in b/conf/icecast.xml.in index e451c453..67828d18 100644 --- a/conf/icecast.xml.in +++ b/conf/icecast.xml.in @@ -93,7 +93,7 @@ If you don't want this, comment out the following line or read up on CORS. --> -
+
@@ -202,7 +202,7 @@ -
+
diff --git a/conf/icecast_minimal.xml.in b/conf/icecast_minimal.xml.in index ab229e9f..8926e95a 100644 --- a/conf/icecast_minimal.xml.in +++ b/conf/icecast_minimal.xml.in @@ -34,6 +34,6 @@ information -
+
diff --git a/conf/icecast_shoutcast_compat.xml.in b/conf/icecast_shoutcast_compat.xml.in index dcd7535b..8244b590 100644 --- a/conf/icecast_shoutcast_compat.xml.in +++ b/conf/icecast_shoutcast_compat.xml.in @@ -47,6 +47,6 @@ false -
+
diff --git a/src/acl.c b/src/acl.c index 80494572..0496402b 100644 --- a/src/acl.c +++ b/src/acl.c @@ -44,6 +44,9 @@ struct acl_tag { /* mount specific functons */ time_t max_connection_duration; size_t max_connections_per_user; + + /* HTTP headers to send to clients using this role */ + ice_config_http_header_t *http_headers; }; /* some string util functions */ @@ -195,6 +198,20 @@ acl_t *acl_new_from_xml_node(xmlNodePtr node) prop = prop->next; } + /* if we're new style configured try to read child nodes */ + if (xmlStrcmp(node->name, XMLSTR("acl")) == 0) { + xmlNodePtr child = node->xmlChildrenNode; + do { + if (child == NULL) + break; + if (xmlIsBlankNode(child)) + continue; + if (xmlStrcmp(child->name, XMLSTR("http-headers")) == 0) { + config_parse_http_headers(child->xmlChildrenNode, &(ret->http_headers)); + } + } while ((child = child->next)); + } + return ret; } @@ -215,6 +232,8 @@ void acl_release(acl_t * acl) if (acl->refcount) return; + config_clear_http_header(acl->http_headers); + free(acl); } @@ -349,3 +368,11 @@ ssize_t acl_get_max_connections_per_user(acl_t *acl) return acl->max_connections_per_user; } + +const ice_config_http_header_t *acl_get_http_headers(acl_t * acl) +{ + if (!acl) + return NULL; + + return acl->http_headers; +} diff --git a/src/acl.h b/src/acl.h index aec52c82..7be9d20a 100644 --- a/src/acl.h +++ b/src/acl.h @@ -19,6 +19,7 @@ #include "common/httpp/httpp.h" #include "icecasttypes.h" +#include "cfgfile.h" typedef enum acl_policy_tag { /* Error on function call */ @@ -60,4 +61,7 @@ time_t acl_get_max_connection_duration(acl_t * acl); int acl_set_max_connections_per_user(acl_t * acl, size_t limit); ssize_t acl_get_max_connections_per_user(acl_t * acl); +/* HTTP specific functions */ +const ice_config_http_header_t *acl_get_http_headers(acl_t * acl); + #endif diff --git a/src/auth.c b/src/auth.c index 049e1d65..eda1a34f 100644 --- a/src/auth.c +++ b/src/auth.c @@ -182,6 +182,8 @@ static void queue_auth_client (auth_client *auth_user) * refcounted and only actual freed after the last use */ void auth_release (auth_t *authenticator) { + size_t i; + if (authenticator == NULL) return; @@ -215,6 +217,13 @@ void auth_release (auth_t *authenticator) { if (authenticator->mount) free(authenticator->mount); acl_release(authenticator->acl); + config_clear_http_header(authenticator->http_headers); + + for (i = 0; i < authenticator->filter_origin_len; i++) { + free(authenticator->filter_origin[i].origin); + } + free(authenticator->filter_origin); + free(authenticator); } @@ -435,6 +444,8 @@ static void *auth_run_thread (void *arg) static void auth_add_client(auth_t *auth, client_t *client, void (*on_no_match)(client_t *client, void (*on_result)(client_t *client, void *userdata, auth_result result), void *userdata), void (*on_result)(client_t *client, void *userdata, auth_result result), void *userdata) { auth_client *auth_user; auth_matchtype_t matchtype; + const char *origin; + size_t i; ICECAST_LOG_DEBUG("Trying to add client %p to auth %p's (role %s) queue.", client, auth, auth->role); @@ -454,6 +465,31 @@ static void auth_add_client(auth_t *auth, client_t *client, void (*on_no_match)( return; } + /* Filter for CORS "Origin". + * + * CORS actually knows about non-CORS requests and assigned the "null" + * location to them. + */ + origin = httpp_getvar(client->parser, "origin"); + if (!origin) + origin = "null"; + + matchtype = auth->filter_origin_policy; + for (i = 0; i < auth->filter_origin_len; i++) { + if (strcmp(auth->filter_origin[i].origin, origin) == 0) { + matchtype = auth->filter_origin[i].type; + break; + } + } + if (matchtype == AUTH_MATCHTYPE_NOMATCH) { + if (on_no_match) { + on_no_match(client, on_result, userdata); + } else if (on_result) { + on_result(client, userdata, AUTH_NOMATCH); + } + return; + } + if (client->admin_command == ADMIN_COMMAND_ERROR) { /* this is a web/ client */ matchtype = auth->filter_web_policy; @@ -611,6 +647,49 @@ static inline void auth_get_authenticator__filter_admin(auth_t *auth, xmlNodePtr } } +static inline void auth_get_authenticator__filter_origin(auth_t *auth, xmlNodePtr node, const char *name, auth_matchtype_t matchtype) +{ + char * tmp = (char*)xmlGetProp(node, XMLSTR(name)); + + if (tmp) { + char *cur = tmp; + char *next; + + while (cur) { + next = strstr(cur, ","); + if (next) { + *next = 0; + next++; + for (; *next == ' '; next++); + } + + if (strcmp(cur, "*") == 0) { + auth->filter_origin_policy = matchtype; + } else { + void *n = realloc(auth->filter_origin, (auth->filter_origin_len + 1)*sizeof(*auth->filter_origin)); + if (!n) { + ICECAST_LOG_ERROR("Can not allocate memory. BAD."); + break; + } + + auth->filter_origin = n; + auth->filter_origin[auth->filter_origin_len].type = matchtype; + auth->filter_origin[auth->filter_origin_len].origin = strdup(cur); + if (auth->filter_origin[auth->filter_origin_len].origin) { + auth->filter_origin_len++; + } else { + ICECAST_LOG_ERROR("Can not allocate memory. BAD."); + break; + } + } + + cur = next; + } + + free(tmp); + } +} + static inline void auth_get_authenticator__init_method(auth_t *auth, int *method_inited, auth_matchtype_t init_with) { size_t i; @@ -716,7 +795,7 @@ auth_t *auth_get_authenticator(xmlNodePtr node) { auth_t *auth = calloc(1, sizeof(auth_t)); config_options_t *options = NULL, **next_option = &options; - xmlNodePtr option; + xmlNodePtr child; char *method; char *tmp; size_t i; @@ -814,39 +893,54 @@ auth_t *auth_get_authenticator(xmlNodePtr node) auth_get_authenticator__filter_admin(auth, node, &filter_admin_index, "match-admin", AUTH_MATCHTYPE_MATCH); auth_get_authenticator__filter_admin(auth, node, &filter_admin_index, "nomatch-admin", AUTH_MATCHTYPE_NOMATCH); + auth->filter_origin_policy = AUTH_MATCHTYPE_MATCH; + auth_get_authenticator__filter_origin(auth, node, "match-origin", AUTH_MATCHTYPE_MATCH); + auth_get_authenticator__filter_origin(auth, node, "nomatch-origin", AUTH_MATCHTYPE_NOMATCH); + auth_get_authenticator__permission_alter(auth, node, "may-alter", AUTH_MATCHTYPE_MATCH); auth_get_authenticator__permission_alter(auth, node, "may-not-alter", AUTH_MATCHTYPE_NOMATCH); - /* BEFORE RELEASE 2.5.0 TODO: Migrate this to config_parse_options(). */ - option = node->xmlChildrenNode; - while (option) - { - xmlNodePtr current = option; - option = option->next; - if (xmlStrcmp (current->name, XMLSTR("option")) == 0) - { + /* sub node parsing */ + child = node->xmlChildrenNode; + do { + if (child == NULL) + break; + + if (xmlIsBlankNode(child)) + continue; + + if (xmlStrcmp (child->name, XMLSTR("option")) == 0) { config_options_t *opt = calloc(1, sizeof (config_options_t)); - opt->name = (char *)xmlGetProp(current, XMLSTR("name")); - if (opt->name == NULL) - { + opt->name = (char *)xmlGetProp(child, XMLSTR("name")); + if (opt->name == NULL) { free(opt); continue; } - opt->value = (char *)xmlGetProp(current, XMLSTR("value")); - if (opt->value == NULL) - { + opt->value = (char *)xmlGetProp(child, XMLSTR("value")); + if (opt->value == NULL) { xmlFree(opt->name); free(opt); continue; } *next_option = opt; next_option = &opt->next; + } else if (xmlStrcmp (child->name, XMLSTR("http-headers")) == 0) { + config_parse_http_headers(child->xmlChildrenNode, &(auth->http_headers)); + } else if (xmlStrcmp (child->name, XMLSTR("acl")) == 0) { + if (!auth->acl) { + auth->acl = acl_new_from_xml_node(child); + } else { + ICECAST_LOG_ERROR("More than one ACL defined in role! Not supported (yet)."); + } + } else { + ICECAST_LOG_WARN("unknown auth setting (%H)", child->name); } - else - if (xmlStrcmp (current->name, XMLSTR("text")) != 0) - ICECAST_LOG_WARN("unknown auth setting (%s)", current->name); + } while ((child = child->next)); + + if (!auth->acl) { + /* If we did not get a try ACL as part of (old style). */ + auth->acl = acl_new_from_xml_node(node); } - auth->acl = acl_new_from_xml_node(node); if (!auth->acl) { auth_release(auth); auth = NULL; diff --git a/src/auth.h b/src/auth.h index a5bd1c1e..91c4715c 100644 --- a/src/auth.h +++ b/src/auth.h @@ -26,6 +26,7 @@ #include "common/httpp/httpp.h" #include "icecasttypes.h" +#include "cfgfile.h" /* implemented */ #define AUTH_TYPE_ANONYMOUS "anonymous" @@ -115,6 +116,13 @@ struct auth_tag admin_command_id_t command; } filter_admin[MAX_ADMIN_COMMANDS]; + struct { + auth_matchtype_t type; + char *origin; + } *filter_origin; + size_t filter_origin_len; + auth_matchtype_t filter_origin_policy; + /* permissions */ auth_matchtype_t permission_alter[AUTH_ALTER_SEND_ERROR+1]; @@ -152,6 +160,9 @@ struct auth_tag acl_t *acl; /* role name for later matching, may be NULL if no role name was given in config */ char *role; + + /* HTTP headers to send to clients using this role */ + ice_config_http_header_t *http_headers; }; /* prototypes for auths that do not need own header file */ diff --git a/src/cfgfile.c b/src/cfgfile.c index 8f306e1c..55534d79 100644 --- a/src/cfgfile.c +++ b/src/cfgfile.c @@ -141,10 +141,6 @@ static void _parse_authentication(xmlDocPtr doc, ice_config_t *c, char **source_password); -static void _parse_http_headers(xmlDocPtr doc, - xmlNodePtr node, - ice_config_http_header_t **http_headers); - static void _parse_relay(xmlDocPtr doc, xmlNodePtr node, ice_config_t *c, const char *mount); static void _parse_mount(xmlDocPtr doc, xmlNodePtr parentnode, ice_config_t *c); @@ -512,13 +508,14 @@ static void __append_old_style_url_event(event_registration_t **list, xmlFreeNode(exec); } -static void config_clear_http_header(ice_config_http_header_t *header) +void config_clear_http_header(ice_config_http_header_t *header) { ice_config_http_header_t *old; while (header) { xmlFree(header->name); - xmlFree(header->value); + if (header->value) + xmlFree(header->value); old = header; header = header->next; free(old); @@ -1061,7 +1058,7 @@ static void _parse_root(xmlDocPtr doc, } else if (xmlStrcmp(node->name, XMLSTR("limits")) == 0) { _parse_limits(doc, node->xmlChildrenNode, configuration); } else if (xmlStrcmp(node->name, XMLSTR("http-headers")) == 0) { - _parse_http_headers(doc, node->xmlChildrenNode, &(configuration->http_headers)); + config_parse_http_headers(node->xmlChildrenNode, &(configuration->http_headers)); } else if (xmlStrcmp(node->name, XMLSTR("relay")) == 0) { _parse_relay(doc, node->xmlChildrenNode, configuration, NULL); } else if (xmlStrcmp(node->name, XMLSTR("mount")) == 0) { @@ -1555,7 +1552,7 @@ static void _parse_mount(xmlDocPtr doc, mount->subtype = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); } else if (xmlStrcmp(node->name, XMLSTR("http-headers")) == 0) { - _parse_http_headers(doc, node->xmlChildrenNode, + config_parse_http_headers(node->xmlChildrenNode, &(mount->http_headers)); } else if (xmlStrcmp(node->name, XMLSTR("event-bindings")) == 0 || xmlStrcmp(node->name, XMLSTR("kartoffelsalat")) == 0) { @@ -1643,9 +1640,8 @@ static void _parse_mount(xmlDocPtr doc, } } -static void _parse_http_headers(xmlDocPtr doc, - xmlNodePtr node, - ice_config_http_header_t **http_headers) +void config_parse_http_headers(xmlNodePtr node, + ice_config_http_header_t **http_headers) { ice_config_http_header_t *header; ice_config_http_header_t *next; @@ -1664,13 +1660,15 @@ static void _parse_http_headers(xmlDocPtr doc, continue; if (!(name = (char *)xmlGetProp(node, XMLSTR("name")))) break; - if (!(value = (char *)xmlGetProp(node, XMLSTR("value")))) - break; + + value = (char *)xmlGetProp(node, XMLSTR("value")); type = HTTP_HEADER_TYPE_STATIC; /* default */ if ((tmp = (char *)xmlGetProp(node, XMLSTR("type")))) { if (strcmp(tmp, "static") == 0) { type = HTTP_HEADER_TYPE_STATIC; + } else if (strcmp(tmp, "cors") == 0 || strcmp(tmp, "corpse") == 0) { + type = HTTP_HEADER_TYPE_CORS; } else { ICECAST_LOG_WARN("Unknown type %s for " "HTTP Header %s", tmp, name); diff --git a/src/cfgfile.h b/src/cfgfile.h index af7f0e6b..f2bcb104 100644 --- a/src/cfgfile.h +++ b/src/cfgfile.h @@ -32,7 +32,9 @@ typedef enum _http_header_type { /* static: headers are passed as is to the client. */ - HTTP_HEADER_TYPE_STATIC + HTTP_HEADER_TYPE_STATIC, + /* CORS: headers are only sent to the client if it's a CORS request. */ + HTTP_HEADER_TYPE_CORS } http_header_type; typedef struct ice_config_http_header_tag { @@ -299,6 +301,10 @@ listener_t *config_copy_listener_one(const listener_t *listener); config_options_t *config_parse_options(xmlNodePtr node); void config_clear_options(config_options_t *options); +void config_parse_http_headers(xmlNodePtr node, + ice_config_http_header_t **http_headers); +void config_clear_http_header(ice_config_http_header_t *header); + int config_rehash(void); ice_config_locks *config_locks(void); diff --git a/src/util.c b/src/util.c index 891717ef..fa50dd52 100644 --- a/src/util.c +++ b/src/util.c @@ -50,6 +50,8 @@ #include "client.h" #include "source.h" #include "admin.h" +#include "auth.h" +#include "acl.h" #define CATMODULE "util" @@ -598,7 +600,7 @@ unsigned int util_str_to_unsigned_int(const char *str, const unsigned int defaul } /* TODO, FIXME: handle memory allocation errors better. */ -static inline void _build_headers_loop(char **ret, size_t *len, ice_config_http_header_t *header, int status) { +static inline void _build_headers_loop(char **ret, size_t *len, const ice_config_http_header_t *header, int status, const char *allow, client_t *client) { size_t headerlen; const char *name; const char *value; @@ -620,7 +622,39 @@ static inline void _build_headers_loop(char **ret, size_t *len, ice_config_htt switch (header->type) { case HTTP_HEADER_TYPE_STATIC: value = header->value; - break; + break; + case HTTP_HEADER_TYPE_CORS: + if (name && client && client->parser) { + const char *origin = httpp_getvar(client->parser, "origin"); + if (origin) { + value = header->value; + if (!value) { + if (strcasecmp(name, "Access-Control-Allow-Origin") == 0) { + if (status >= 200 && status <= 299) { + value = origin; + } else if (status >= 400 && status <= 599) { + value = "null"; + } else { + /* do not set as we do not have a default for that. */ + } + } else if (strcasecmp(name, "Access-Control-Allow-Methods") == 0) { + if (status >= 200 && status <= 299) { + /* only use the default if we are posive reply. */ + value = allow; + } + } else if (strcasecmp(name, "Access-Control-Expose-Headers") == 0) { + value = "icy-br, icy-description, icy-genre, icy-name, icy-pub, icy-url"; + } else if (strcasecmp(name, "Access-Control-Max-Age") == 0) { + value = "300"; /* 300s = 5 minutes */ + /* No default (yet) + * } else if (strcasecmp(name, "Access-Control-Allow-Credentials") == 0) { + * } else if (strcasecmp(name, "Access-Control-Allow-Headers") == 0) { + */ + } + } + } + } + break; } /* check data */ @@ -644,7 +678,8 @@ 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, const char *allow, ice_config_t *config, source_t *source, client_t *client) { + const ice_config_http_header_t *header; mount_proxy *mountproxy = NULL; char *ret = NULL; size_t len = 1; @@ -655,9 +690,13 @@ static inline char * _build_headers(int status, ice_config_t *config, source_t * ret = calloc(1, 1); *ret = 0; - _build_headers_loop(&ret, &len, config->http_headers, status); + _build_headers_loop(&ret, &len, config->http_headers, status, allow, client); if (mountproxy && mountproxy->http_headers) - _build_headers_loop(&ret, &len, mountproxy->http_headers, status); + _build_headers_loop(&ret, &len, mountproxy->http_headers, status, allow, client); + if (client && client->auth && (header = client->auth->http_headers)) + _build_headers_loop(&ret, &len, header, status, allow, client); + if (client && client->acl && (header = acl_get_http_headers(client->acl))) + _build_headers_loop(&ret, &len, header, status, allow, client); return ret; } @@ -785,7 +824,7 @@ ssize_t util_http_build_header(char * out, size_t len, ssize_t offset, } config = config_get_config(); - extra_headers = _build_headers(status, config, source); + extra_headers = _build_headers(status, allow_header, 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,