mirror of
https://gitlab.xiph.org/xiph/icecast-server.git
synced 2024-12-04 14:46:30 -05:00
e43fd645c1
Closes: #2347
394 lines
10 KiB
C
394 lines
10 KiB
C
/* Icecast
|
|
*
|
|
* This program is distributed under the GNU General Public License, version 2.
|
|
* A copy of this license is included with this source.
|
|
*
|
|
* Copyright 2014-2018, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
|
|
*/
|
|
|
|
/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "acl.h"
|
|
#include "admin.h"
|
|
#include "util.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#define MAX_ADMIN_COMMANDS 32
|
|
|
|
/* define internal structure */
|
|
struct acl_tag {
|
|
/* reference counter */
|
|
size_t refcount;
|
|
|
|
/* name, may be NULL if name was given in config */
|
|
char *name;
|
|
|
|
/* allowed methods */
|
|
acl_policy_t method[httpp_req_unknown+1];
|
|
|
|
/* admin/ interface */
|
|
struct {
|
|
admin_command_id_t command;
|
|
acl_policy_t policy;
|
|
} admin_commands[MAX_ADMIN_COMMANDS];
|
|
size_t admin_commands_len;
|
|
acl_policy_t admin_command_policy;
|
|
|
|
/* web/ interface */
|
|
acl_policy_t web_policy;
|
|
|
|
/* 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 */
|
|
static inline void __skip_spaces(const char **str)
|
|
{
|
|
register const char * p;
|
|
|
|
for (p = *str; *p == ' '; p++);
|
|
*str = p;
|
|
}
|
|
|
|
int acl_set_ANY_str(acl_t *acl,
|
|
acl_policy_t policy,
|
|
const char *str,
|
|
int (*callback)(acl_t *, acl_policy_t, const char *))
|
|
{
|
|
const char *end;
|
|
size_t len;
|
|
char buf[64];
|
|
int ret;
|
|
|
|
if ( !acl || !str || !callback || (policy != ACL_POLICY_ALLOW && policy != ACL_POLICY_DENY) )
|
|
return -1;
|
|
|
|
do {
|
|
__skip_spaces(&str);
|
|
end = strstr(str, ",");
|
|
if (end) {
|
|
len = end - str;
|
|
} else {
|
|
len = strlen(str);
|
|
}
|
|
|
|
if (len > (sizeof(buf) - 1))
|
|
return -1;
|
|
memcpy(buf, str, len);
|
|
buf[len] = 0;
|
|
|
|
ret = callback(acl, policy, buf);
|
|
if (ret)
|
|
return ret;
|
|
|
|
str += len + 1;
|
|
} while (end);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* basic functions to work with ACLs */
|
|
acl_t *acl_new(void)
|
|
{
|
|
acl_t * ret = calloc(1, sizeof(*ret));
|
|
if (!ret)
|
|
return NULL;
|
|
|
|
ret->refcount = 1;
|
|
|
|
acl_set_method_str(ret, ACL_POLICY_DENY, "*");
|
|
acl_set_method_str(ret, ACL_POLICY_ALLOW, "get,options");
|
|
|
|
acl_set_admin_str(ret, ACL_POLICY_DENY, "*");
|
|
acl_set_admin_str(ret, ACL_POLICY_ALLOW, "buildm3u,publicstats,publicstats.json");
|
|
|
|
acl_set_web_policy(ret, ACL_POLICY_ALLOW);
|
|
|
|
acl_set_max_connection_duration(ret, -1);
|
|
acl_set_max_connections_per_user(ret, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
acl_t *acl_new_from_xml_node(xmlNodePtr node)
|
|
{
|
|
acl_t * ret;
|
|
char * tmp;
|
|
xmlAttrPtr prop;
|
|
|
|
if (!node)
|
|
return NULL;
|
|
|
|
ret = acl_new();
|
|
if (!ret)
|
|
return NULL;
|
|
|
|
ret->name = (char*)xmlGetProp(node, XMLSTR("name"));
|
|
|
|
prop = node->properties;
|
|
while (prop) {
|
|
tmp = (char*)xmlGetProp(node, prop->name);
|
|
if (tmp) {
|
|
/* basic {allow|deny}-* options */
|
|
if (strcmp((const char*)prop->name, "allow-method") == 0) {
|
|
acl_set_method_str(ret, ACL_POLICY_ALLOW, tmp);
|
|
} else if (strcmp((const char*)prop->name, "deny-method") == 0) {
|
|
acl_set_method_str(ret, ACL_POLICY_DENY, tmp);
|
|
} else if (strcmp((const char*)prop->name, "allow-admin") == 0) {
|
|
acl_set_admin_str(ret, ACL_POLICY_ALLOW, tmp);
|
|
} else if (strcmp((const char*)prop->name, "deny-admin") == 0) {
|
|
acl_set_admin_str(ret, ACL_POLICY_DENY, tmp);
|
|
} else if (strcmp((const char*)prop->name, "allow-web") == 0) {
|
|
if (strstr(tmp, "*") || util_str_to_bool(tmp)) {
|
|
acl_set_web_policy(ret, ACL_POLICY_ALLOW);
|
|
} else {
|
|
acl_set_web_policy(ret, ACL_POLICY_DENY);
|
|
}
|
|
} else if (strcmp((const char*)prop->name, "deny-web") == 0) {
|
|
if (strstr(tmp, "*") || util_str_to_bool(tmp)) {
|
|
acl_set_web_policy(ret, ACL_POLICY_DENY);
|
|
} else {
|
|
acl_set_web_policy(ret, ACL_POLICY_ALLOW);
|
|
}
|
|
|
|
/* wildcard {allow,deny} option */
|
|
} else if (strcmp((const char*)prop->name, "allow-all") == 0) {
|
|
if (strstr(tmp, "*") || util_str_to_bool(tmp)) {
|
|
acl_set_method_str(ret, ACL_POLICY_ALLOW, "*");
|
|
acl_set_admin_str(ret, ACL_POLICY_ALLOW, "*");
|
|
acl_set_web_policy(ret, ACL_POLICY_ALLOW);
|
|
} else {
|
|
acl_set_method_str(ret, ACL_POLICY_DENY, "*");
|
|
acl_set_admin_str(ret, ACL_POLICY_DENY, "*");
|
|
acl_set_web_policy(ret, ACL_POLICY_DENY);
|
|
}
|
|
} else if (strcmp((const char*)prop->name, "deny-all") == 0) {
|
|
if (strstr(tmp, "*") || util_str_to_bool(tmp)) {
|
|
acl_set_method_str(ret, ACL_POLICY_DENY, "*");
|
|
acl_set_admin_str(ret, ACL_POLICY_DENY, "*");
|
|
acl_set_web_policy(ret, ACL_POLICY_DENY);
|
|
} else {
|
|
acl_set_method_str(ret, ACL_POLICY_ALLOW, "*");
|
|
acl_set_admin_str(ret, ACL_POLICY_ALLOW, "*");
|
|
acl_set_web_policy(ret, ACL_POLICY_ALLOW);
|
|
}
|
|
|
|
/* other options */
|
|
} else if (strcmp((const char*)prop->name, "connections-per-user") == 0) {
|
|
if (strcmp(tmp, "*") == 0 || strcmp(tmp, "unlimited") == 0) {
|
|
acl_set_max_connections_per_user(ret, 0);
|
|
} else {
|
|
acl_set_max_connections_per_user(ret, atoi(tmp));
|
|
}
|
|
} else if (strcmp((const char*)prop->name, "connection-duration") == 0) {
|
|
if (strcmp(tmp, "*") == 0 || strcmp(tmp, "unlimited") == 0) {
|
|
acl_set_max_connection_duration(ret, 0);
|
|
} else {
|
|
acl_set_max_connection_duration(ret, atoi(tmp));
|
|
}
|
|
}
|
|
xmlFree(tmp);
|
|
}
|
|
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;
|
|
}
|
|
|
|
void acl_addref(acl_t * acl)
|
|
{
|
|
if (!acl)
|
|
return;
|
|
|
|
acl->refcount++;
|
|
}
|
|
|
|
void acl_release(acl_t * acl)
|
|
{
|
|
if (!acl)
|
|
return;
|
|
|
|
acl->refcount--;
|
|
if (acl->refcount)
|
|
return;
|
|
|
|
config_clear_http_header(acl->http_headers);
|
|
|
|
if (acl->name)
|
|
xmlFree(acl->name);
|
|
|
|
free(acl);
|
|
}
|
|
|
|
const char *acl_get_name(acl_t * acl)
|
|
{
|
|
if (!acl)
|
|
return NULL;
|
|
return acl->name;
|
|
}
|
|
|
|
/* HTTP Method specific functions */
|
|
int acl_set_method_str__callback(acl_t *acl,
|
|
acl_policy_t policy,
|
|
const char *str)
|
|
{
|
|
httpp_request_type_e method;
|
|
size_t i;
|
|
|
|
if (strcmp(str, "*") == 0) {
|
|
for (i = 0; i < (sizeof(acl->method)/sizeof(*acl->method)); i++)
|
|
acl->method[i] = policy;
|
|
} else {
|
|
method = httpp_str_to_method(str);
|
|
if (method == httpp_req_unknown)
|
|
return -1;
|
|
|
|
acl->method[method] = policy;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
acl_policy_t acl_test_method(acl_t * acl, httpp_request_type_e method)
|
|
{
|
|
if (!acl || method < httpp_req_none || method > httpp_req_unknown)
|
|
return ACL_POLICY_ERROR;
|
|
|
|
return acl->method[method];
|
|
}
|
|
|
|
/* admin/ interface specific functions */
|
|
int acl_set_admin_str__callbck(acl_t *acl,
|
|
acl_policy_t policy,
|
|
const char *str)
|
|
{
|
|
size_t read_i, write_i;
|
|
admin_command_id_t command = admin_get_command(str);
|
|
|
|
if (command == ADMIN_COMMAND_ERROR)
|
|
return -1;
|
|
|
|
if (command == ADMIN_COMMAND_ANY) {
|
|
acl->admin_command_policy = policy;
|
|
for (read_i = write_i = 0; read_i < acl->admin_commands_len; read_i++) {
|
|
if (acl->admin_commands[read_i].policy == policy)
|
|
continue;
|
|
acl->admin_commands[write_i] = acl->admin_commands[read_i];
|
|
write_i++; /* no need to check bounds here as this loop can only compress the array */
|
|
}
|
|
acl->admin_commands_len = write_i;
|
|
return 0;
|
|
}
|
|
|
|
if (acl->admin_commands_len == MAX_ADMIN_COMMANDS)
|
|
return -1;
|
|
|
|
acl->admin_commands[acl->admin_commands_len].command = command;
|
|
acl->admin_commands[acl->admin_commands_len].policy = policy;
|
|
acl->admin_commands_len++;
|
|
return 0;
|
|
}
|
|
|
|
acl_policy_t acl_test_admin(acl_t *acl, admin_command_id_t command)
|
|
{
|
|
size_t i;
|
|
|
|
if (!acl)
|
|
return ACL_POLICY_ERROR;
|
|
|
|
for (i = 0; i < acl->admin_commands_len; i++)
|
|
if (acl->admin_commands[i].command == command)
|
|
return acl->admin_commands[i].policy;
|
|
|
|
return acl->admin_command_policy;
|
|
}
|
|
|
|
/* web/ interface specific functions */
|
|
int acl_set_web_policy(acl_t *acl, acl_policy_t policy)
|
|
{
|
|
if (!acl || (policy != ACL_POLICY_ALLOW && policy != ACL_POLICY_DENY))
|
|
return -1;
|
|
|
|
acl->web_policy = policy;
|
|
|
|
return 0;
|
|
}
|
|
|
|
acl_policy_t acl_test_web(acl_t *acl)
|
|
{
|
|
if (!acl)
|
|
return ACL_POLICY_ERROR;
|
|
|
|
return acl->web_policy;
|
|
}
|
|
|
|
/* mount specific functons */
|
|
int acl_set_max_connection_duration(acl_t *acl, time_t duration)
|
|
{
|
|
if (!acl)
|
|
return -1;
|
|
|
|
acl->max_connection_duration = duration;
|
|
|
|
return 0;
|
|
}
|
|
|
|
time_t acl_get_max_connection_duration(acl_t *acl)
|
|
{
|
|
if (!acl)
|
|
return -1;
|
|
|
|
return acl->max_connection_duration;
|
|
}
|
|
|
|
int acl_set_max_connections_per_user(acl_t *acl, size_t limit)
|
|
{
|
|
if (!acl)
|
|
return -1;
|
|
|
|
acl->max_connections_per_user = limit;
|
|
|
|
return 0;
|
|
}
|
|
|
|
ssize_t acl_get_max_connections_per_user(acl_t *acl)
|
|
{
|
|
if (!acl)
|
|
return -1;
|
|
|
|
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;
|
|
}
|