1
0
mirror of https://gitlab.xiph.org/xiph/icecast-server.git synced 2024-06-23 06:25:24 +00:00
icecast-server/src/acl.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(ice_config_t *configuration, 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), configuration);
}
} 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;
}