Commit a642cac5 authored by Philipp Schafft's avatar Philipp Schafft 🦁

Wow. Mega patch!

This patch *replaces* the authentication system completly.

What is new:
 - <authentication> in mount section is now a container object.
 - <authentication> in root and mount section may hold any number of <role>-Tags.
 - <role> tags:
   Those tags define a 'role' and it's ACL rules.
   A role is a instance of an authentication module (see below).
   <role> takes the following options. All but type are optional.
   - authentication related:
     - type: Type of the authentication module (values: anonymous, static, legacy-password, url or htpasswd;
             symbolic constants in auth.h)
     - name: Name for the role. For later matching. (values: any string; default: (none))
     - method: This rule is only active on the given list of HTTP methods.
               (list of enum values: methods as recognized by httpp/ (e.g: get,post); default: *)
   - ACL related:
     - allow-method: Allowed HTTP methods.
       (list of enum values: methods as recognized by httpp/ (e.g: get,post); default: get)
     - deny-method: Rejected HTTP methods.
       (list of enum values: methods as recognized by httpp/ (e.g: get,post); default: *)
     - allow-admin: Allowed admin commands. (list of enum values: admin command; default: buildm3u)
     - deny-admin: Rejected admin commands. (list of enum values: admin command; default: *)
     - allow-web: Allowed web pages. (values: empty or *; default: *)
     - deny-web: Rejected web pages. (values: empty or *; default: (empty))
     - connections-per-user: maximum number of simultaneous connections per role and username.
       This is only active on active sources.  (values: unlimited or number of connections; default: unlimited)
     - connection-duration: maximum time of a connection. This is only active on active sources.
       (values: unlimited or number of secounds; default: unlimited)
   <role> takes <option> child tags. <option> tags contain a name and a value option.
   Meaning of <option> tags is up to the authentication module.
 - <role>s are considered to build a stack. If a role returns with AUTH_NOMATCH the next one will be tried.
 - <role>s are tested in this order: mount specific, default mount specific, global, internal fallback.
   Internal fallback is set to allow web/ access via GET, POST and HEAD (only GET supported by this time)
   and rejects all other requests.
 - New authentication module: anonymous
   This module matches all requests. No options taken.
 - New authentication module: static
   This module matches with a static username and password.
   It takes two <option>s. One with name="username" and one with name="password" to set username and password.
   This replaces old style <*-username> and <*-password> tags.
 - New authentication module: legacy-password
   This module matches with a statich password.
   It takes one <option> with name="password" to set password.
   This replaces old ICE and ICY (shoutcast compat mode) authentication.
 - Parsing <authentication> in <mount> with a type set in a special way to allow 100% backward compatibility.
 - Parsing of <source-password>, <admin-password>, <admin-user>, <relay-password> and <relay-user> in global
   <authentication> for 100% backward compatibility.
 - <alias> is now proccessed very early. This enables them to be used for all kinds of requests.

To Do List & What does not yet work:
 - type="url" auth: mount_add and mount_remove.
   This should be replaced by an unique feature I would call '<event>'.
 - Admin commands manageauth and manageauth.xsl are disabled as they need more review:
   This code needs to be ported to support multiple <role>s per <mount>.
 - url authentication module can not yet return AUTH_NOMATCH.
   This needs to be reviewed and discussed on how to handle this case best way.
 - Default config files needs to be updated to reflect the changes.
   As this is quite some political act it should be done in dicussion with the whole team
   and permission of the release manager.
 - Docs need to be updated to reflect the changes.

How does it work:
 Code has been changed so that authentification is done early for all clients.
 This allows accessing the ACL data (client->acl) from nearly everywhere in the code.

 After accept() and initial client setup the request is parsed. In the next step
 all <alias>es are resolved. After this the client is passed for authentication.
 After authentication it is passed to the corresponding subsystem depending on kind of request.

 All authentication instances have a thread running for doing the authentication.
 This thread works on a queue of clients.

Hints for testers:
 - Test with default config.
 - Test with diffrent authentication modules in <mount>.
 - Test shoutcast compatibility mode.
 - Test with new style <authentication> and any amount of <role> (zero to quite some).
 - Test <alias> lookup on all kinds of objects.
 - Test source level credential login into the admin interface.
 - Test shoucast style meta data updates.
 - Test playlist generation.

Thank you for reading this long commit message. Have fun reading the full patch!

svn path=/icecast/trunk/icecast/; revision=19358
parent 637af17f
......@@ -9,7 +9,7 @@ bin_PROGRAMS = icecast
noinst_HEADERS = admin.h cfgfile.h logging.h sighandler.h connection.h \
global.h util.h slave.h source.h stats.h refbuf.h client.h \
compat.h fserve.h xslt.h yp.h event.h md5.h \
auth.h auth_htpasswd.h auth_url.h \
acl.h auth.h auth_htpasswd.h auth_url.h auth_anonymous.h auth_static.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
......@@ -17,7 +17,7 @@ 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 \
xslt.c fserve.c event.c admin.c md5.c \
format.c format_ogg.c format_mp3.c format_midi.c format_flac.c format_ebml.c \
auth.c auth_htpasswd.c format_kate.c format_skeleton.c format_opus.c
acl.c auth.c auth_htpasswd.c auth_anonymous.c auth_static.c format_kate.c format_skeleton.c format_opus.c
EXTRA_icecast_SOURCES = yp.c \
auth_url.c \
format_vorbis.c format_theora.c format_speex.c
......@@ -36,4 +36,3 @@ debug:
profile:
$(MAKE) all CFLAGS="@PROFILE@"
/* 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, Philipp 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 <stdio.h>
#define MAX_ADMIN_COMMANDS 32
/* define internal structure */
struct acl_tag {
/* reference counter */
size_t refcount;
/* allowed methods */
acl_policy_t method[httpp_req_unknown+1];
/* admin/ interface */
struct {
int 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;
};
/* 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");
acl_set_admin_str(ret, ACL_POLICY_DENY, "*");
acl_set_admin_str(ret, ACL_POLICY_ALLOW, "buildm3u");
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;
prop = node->properties;
while (prop) {
tmp = (char*)xmlGetProp(node, prop->name);
if (tmp) {
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, "*"))
acl_set_web_policy(ret, ACL_POLICY_ALLOW);
} else if (strcmp((const char*)prop->name, "deny-web") == 0) {
if (strstr(tmp, "*"))
acl_set_web_policy(ret, ACL_POLICY_DENY);
} 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;
}
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;
free(acl);
}
/* 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;
int 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, int 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;
}
/* 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, Philipp Schafft <lion@lion.leolix.org>
*/
#ifndef __ACL_H__
#define __ACL_H__
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include "httpp/httpp.h"
typedef enum acl_policy_tag {
/* Error on function call */
ACL_POLICY_ERROR = -1,
/* Client is allowed to do operation, go ahead! */
ACL_POLICY_ALLOW = 0,
/* Client is not allowed to do so, send error! */
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);
acl_t * acl_new_from_xml_node(xmlNodePtr node);
void acl_addref(acl_t * acl);
void acl_release(acl_t * acl);
/* special functions */
int acl_set_ANY_str(acl_t * acl, acl_policy_t policy, const char * str, int (*callback)(acl_t *, acl_policy_t, const char *));
/* HTTP Method specific functions */
int acl_set_method_str__callback(acl_t * acl, acl_policy_t policy, const char * str);
#define acl_set_method_str(acl,policy,str) acl_set_ANY_str((acl), (policy), (str), acl_set_method_str__callback)
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);
/* web/ interface specific functions */
int acl_set_web_policy(acl_t * acl, acl_policy_t policy);
acl_policy_t acl_test_web(acl_t * acl);
/* mount specific functons */
int acl_set_max_connection_duration(acl_t * acl, time_t duration);
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);
#endif
......@@ -8,6 +8,7 @@
* oddsock <oddsock@xiph.org>,
* Karl Heyes <karl@xiph.org>
* and others (see AUTHORS for details).
* Copyright 2012-2014, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
*/
#ifdef HAVE_CONFIG_H
......@@ -116,70 +117,73 @@
#define DEFAULT_TRANSFORMED_REQUEST ""
#define BUILDM3U_RAW_REQUEST "buildm3u"
int admin_get_command(const char *command)
{
if(!strcmp(command, FALLBACK_RAW_REQUEST))
return COMMAND_RAW_FALLBACK;
else if(!strcmp(command, FALLBACK_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_FALLBACK;
else if(!strcmp(command, METADATA_RAW_REQUEST))
return COMMAND_RAW_METADATA_UPDATE;
else if(!strcmp(command, METADATA_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_METADATA_UPDATE;
else if(!strcmp(command, SHOUTCAST_METADATA_REQUEST))
return COMMAND_SHOUTCAST_METADATA_UPDATE;
else if(!strcmp(command, LISTCLIENTS_RAW_REQUEST))
return COMMAND_RAW_SHOW_LISTENERS;
else if(!strcmp(command, LISTCLIENTS_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_SHOW_LISTENERS;
else if(!strcmp(command, STATS_RAW_REQUEST))
return COMMAND_RAW_STATS;
else if(!strcmp(command, STATS_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_STATS;
else if(!strcmp(command, "stats.xml")) /* The old way */
return COMMAND_RAW_STATS;
else if(!strcmp(command, QUEUE_RELOAD_RAW_REQUEST))
return COMMAND_RAW_QUEUE_RELOAD;
else if(!strcmp(command, QUEUE_RELOAD_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_QUEUE_RELOAD;
else if(!strcmp(command, LISTMOUNTS_RAW_REQUEST))
return COMMAND_RAW_LIST_MOUNTS;
else if(!strcmp(command, LISTMOUNTS_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_LIST_MOUNTS;
else if(!strcmp(command, STREAMLIST_RAW_REQUEST))
return COMMAND_RAW_LISTSTREAM;
else if(!strcmp(command, STREAMLIST_PLAINTEXT_REQUEST))
return COMMAND_PLAINTEXT_LISTSTREAM;
else if(!strcmp(command, MOVECLIENTS_RAW_REQUEST))
return COMMAND_RAW_MOVE_CLIENTS;
else if(!strcmp(command, MOVECLIENTS_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_MOVE_CLIENTS;
else if(!strcmp(command, KILLCLIENT_RAW_REQUEST))
return COMMAND_RAW_KILL_CLIENT;
else if(!strcmp(command, KILLCLIENT_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_KILL_CLIENT;
else if(!strcmp(command, KILLSOURCE_RAW_REQUEST))
return COMMAND_RAW_KILL_SOURCE;
else if(!strcmp(command, KILLSOURCE_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_KILL_SOURCE;
else if(!strcmp(command, MANAGEAUTH_RAW_REQUEST))
return COMMAND_RAW_MANAGEAUTH;
else if(!strcmp(command, MANAGEAUTH_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_MANAGEAUTH;
else if(!strcmp(command, UPDATEMETADATA_RAW_REQUEST))
return COMMAND_RAW_UPDATEMETADATA;
else if(!strcmp(command, UPDATEMETADATA_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_UPDATEMETADATA;
else if(!strcmp(command, BUILDM3U_RAW_REQUEST))
return COMMAND_BUILDM3U;
else if(!strcmp(command, DEFAULT_TRANSFORMED_REQUEST))
return COMMAND_TRANSFORMED_STATS;
else if(!strcmp(command, DEFAULT_RAW_REQUEST))
return COMMAND_TRANSFORMED_STATS;
else if(!strcmp(command, "*")) /* for ACL framework */
return COMMAND_ANY;
else
return COMMAND_ERROR;
typedef struct admin_command_tag {
const int id;
const char *name;
const int type;
const int format;
} admin_command_t;
/*
COMMAND_TRANSFORMED_METADATA_UPDATE -> METADATA_TRANSFORMED_REQUEST
COMMAND_TRANSFORMED_UPDATEMETADATA -> UPDATEMETADATA_TRANSFORMED_REQUEST
*/
static const admin_command_t commands[] = {
{COMMAND_RAW_FALLBACK, FALLBACK_RAW_REQUEST, ADMINTYPE_MOUNT, RAW},
{COMMAND_TRANSFORMED_FALLBACK, FALLBACK_TRANSFORMED_REQUEST, ADMINTYPE_MOUNT, TRANSFORMED},
{COMMAND_RAW_METADATA_UPDATE, METADATA_RAW_REQUEST, ADMINTYPE_MOUNT, RAW},
{COMMAND_SHOUTCAST_METADATA_UPDATE, SHOUTCAST_METADATA_REQUEST, ADMINTYPE_MOUNT, TRANSFORMED},
{COMMAND_TRANSFORMED_METADATA_UPDATE, METADATA_TRANSFORMED_REQUEST, ADMINTYPE_MOUNT, TRANSFORMED},
{COMMAND_RAW_SHOW_LISTENERS, LISTCLIENTS_RAW_REQUEST, ADMINTYPE_MOUNT, RAW},
{COMMAND_TRANSFORMED_SHOW_LISTENERS, LISTCLIENTS_TRANSFORMED_REQUEST, ADMINTYPE_MOUNT, TRANSFORMED},
{COMMAND_RAW_STATS, STATS_RAW_REQUEST, ADMINTYPE_HYBRID, RAW},
{COMMAND_TRANSFORMED_STATS, STATS_TRANSFORMED_REQUEST, ADMINTYPE_HYBRID, TRANSFORMED},
{COMMAND_RAW_STATS, "stats.xml", ADMINTYPE_HYBRID, RAW}, /* The old way */
{COMMAND_RAW_QUEUE_RELOAD, QUEUE_RELOAD_RAW_REQUEST, ADMINTYPE_GENERAL, RAW},
{COMMAND_TRANSFORMED_QUEUE_RELOAD, QUEUE_RELOAD_TRANSFORMED_REQUEST, ADMINTYPE_GENERAL, TRANSFORMED},
{COMMAND_RAW_LIST_MOUNTS, LISTMOUNTS_RAW_REQUEST, ADMINTYPE_GENERAL, RAW},
{COMMAND_TRANSFORMED_LIST_MOUNTS, LISTMOUNTS_TRANSFORMED_REQUEST, ADMINTYPE_GENERAL, TRANSFORMED},
{COMMAND_RAW_LISTSTREAM, STREAMLIST_RAW_REQUEST, ADMINTYPE_GENERAL, RAW},
{COMMAND_PLAINTEXT_LISTSTREAM, STREAMLIST_PLAINTEXT_REQUEST, ADMINTYPE_GENERAL, PLAINTEXT},
{COMMAND_TRANSFORMED_LISTSTREAM, STREAMLIST_TRANSFORMED_REQUEST, ADMINTYPE_GENERAL, TRANSFORMED},
{COMMAND_RAW_MOVE_CLIENTS, MOVECLIENTS_RAW_REQUEST, ADMINTYPE_MOUNT, RAW},
{COMMAND_TRANSFORMED_MOVE_CLIENTS, MOVECLIENTS_TRANSFORMED_REQUEST, ADMINTYPE_HYBRID, TRANSFORMED},
{COMMAND_RAW_KILL_CLIENT, KILLCLIENT_RAW_REQUEST, ADMINTYPE_MOUNT, RAW},
{COMMAND_TRANSFORMED_KILL_CLIENT, KILLCLIENT_TRANSFORMED_REQUEST, ADMINTYPE_MOUNT, TRANSFORMED},
{COMMAND_RAW_KILL_SOURCE, KILLSOURCE_RAW_REQUEST, ADMINTYPE_MOUNT, RAW},
{COMMAND_TRANSFORMED_KILL_SOURCE, KILLSOURCE_TRANSFORMED_REQUEST, ADMINTYPE_MOUNT, TRANSFORMED},
{COMMAND_RAW_MANAGEAUTH, MANAGEAUTH_RAW_REQUEST, ADMINTYPE_MOUNT, RAW},
{COMMAND_TRANSFORMED_MANAGEAUTH, MANAGEAUTH_TRANSFORMED_REQUEST, ADMINTYPE_MOUNT, TRANSFORMED},
{COMMAND_RAW_UPDATEMETADATA, UPDATEMETADATA_RAW_REQUEST, ADMINTYPE_MOUNT, RAW},
{COMMAND_TRANSFORMED_UPDATEMETADATA, UPDATEMETADATA_TRANSFORMED_REQUEST, ADMINTYPE_MOUNT, TRANSFORMED},
{COMMAND_BUILDM3U, BUILDM3U_RAW_REQUEST, ADMINTYPE_MOUNT, RAW},
{COMMAND_TRANSFORMED_STATS, DEFAULT_TRANSFORMED_REQUEST, ADMINTYPE_HYBRID, TRANSFORMED},
{COMMAND_TRANSFORMED_STATS, DEFAULT_RAW_REQUEST, ADMINTYPE_HYBRID, TRANSFORMED},
{COMMAND_ANY, "*", ADMINTYPE_GENERAL, TRANSFORMED} /* for ACL framework */
};
int admin_get_command(const char *command) {
size_t i;
for (i = 0; i < (sizeof(commands)/sizeof(*commands)); i++)
if (strcmp(commands[i].name, command) == 0)
return commands[i].id;
return COMMAND_ERROR;
}
int admin_get_command_type(int command) {
size_t i;
if (command == ADMIN_COMMAND_ERROR || command == COMMAND_ANY)
return ADMINTYPE_ERROR;
for (i = 0; i < (sizeof(commands)/sizeof(*commands)); i++)
if (commands[i].id == command)
return commands[i].type;
return ADMINTYPE_ERROR;
}
static void command_fallback(client_t *client, source_t *source, int response);
......@@ -201,9 +205,8 @@ static void command_kill_source(client_t *client, source_t *source,
int response);
static void command_updatemetadata(client_t *client, source_t *source,
int response);
static void admin_handle_mount_request(client_t *client, source_t *source,
int command);
static void admin_handle_general_request(client_t *client, int command);
static void admin_handle_mount_request(client_t *client, source_t *source);
static void admin_handle_general_request(client_t *client);
/* 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
......@@ -238,6 +241,7 @@ xmlDocPtr admin_build_sourcelist (const char *mount)
{
ice_config_t *config;
mount_proxy *mountinfo;
acl_t *acl = NULL;
srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
......@@ -250,11 +254,14 @@ xmlDocPtr admin_build_sourcelist (const char *mount)
config = config_get_config();
mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
if (mountinfo && mountinfo->auth)
{
xmlNewChild(srcnode, NULL, XMLSTR("authenticator"),
XMLSTR(mountinfo->auth->type));
if (mountinfo)
acl = auth_stack_get_anonymous_acl(mountinfo->authstack);
if (!acl)
auth_stack_get_anonymous_acl(config->authstack);
if (acl && acl_test_web(acl) == ACL_POLICY_DENY) {
xmlNewChild(srcnode, NULL, XMLSTR("authenticator"), XMLSTR("(dummy)"));
}
acl_release(acl);
config_release_config();
if (source->running)
......@@ -359,7 +366,6 @@ void admin_send_response (xmlDocPtr doc, client_t *client,
void admin_handle_request(client_t *client, const char *uri)
{
const char *mount, *command_string;
int command;
ICECAST_LOG_DEBUG("Admin request (%s)", uri);
if (!((strcmp(uri, "/admin.cgi") == 0) ||
......@@ -377,40 +383,23 @@ void admin_handle_request(client_t *client, const char *uri)
}
ICECAST_LOG_DEBUG("Got command (%s)", command_string);
command = admin_get_command(command_string);
if(command <= 0) {
if (client->admin_command <= 0) {
ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %s",
command_string);
client_send_error(client, 400, 0, "Unrecognised command");
return;
}
if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {
ice_config_t *config;
const char *sc_mount;
const char *pass = httpp_get_query_param (client->parser, "pass");
listener_t *listener;
if (pass == NULL)
{
client_send_error(client, 400, 0, "missing pass parameter");
if (acl_test_admin(client->acl, client->admin_command) != ACL_POLICY_ALLOW) {
if (client->admin_command == COMMAND_RAW_METADATA_UPDATE &&
(acl_test_method(client->acl, httpp_req_source) == ACL_POLICY_ALLOW ||
acl_test_method(client->acl, httpp_req_put) == ACL_POLICY_ALLOW)) {
ICECAST_LOG_DEBUG("Granted right to call COMMAND_RAW_METADATA_UPDATE to client because it is allowed to do SOURCE or PUT.");
} else {
client_send_error(client, 401, 1, "You need to authenticate\r\n");
return;
}
global_lock();
config = config_get_config ();
sc_mount = config->shoutcast_mount;
listener = config_get_listen_sock (config, client->con);
if (listener && listener->shoutcast_mount)
sc_mount = listener->shoutcast_mount;
httpp_set_query_param (client->parser, "mount", sc_mount);
httpp_setvar (client->parser, HTTPP_VAR_PROTOCOL, "ICY");
httpp_setvar (client->parser, HTTPP_VAR_ICYPASSWORD, pass);
config_release_config ();
global_unlock();
}
mount = httpp_get_query_param(client->parser, "mount");
......@@ -419,28 +408,12 @@ void admin_handle_request(client_t *client, const char *uri)
source_t *source;
/* this request does not require auth but can apply to files on webroot */
if (command == COMMAND_BUILDM3U)
{
command_buildm3u (client, mount);
if (client->admin_command == COMMAND_BUILDM3U) {
command_buildm3u(client, mount);
return;
}
/* This is a mount request, handle it as such */
if (client->authenticated == 0 && !connection_check_admin_pass(client->parser))
{
switch (client_check_source_auth (client, mount))
{
case 0:
break;
default:
ICECAST_LOG_INFO("Bad or missing password on mount modification admin "
"request (command: %s)", command_string);
client_send_error(client, 401, 1, "You need to authenticate\r\n");
/* fall through */
case 1:
return;
}
}
/* This is a mount request, handle it as such */
avl_tree_rlock(global.source_tree);
source = source_find_mount_raw(mount);
......@@ -461,7 +434,7 @@ void admin_handle_request(client_t *client, const char *uri)
client_send_error(client, 400, 0, "Source is not available");
return;
}
if (command == COMMAND_SHOUTCAST_METADATA_UPDATE &&
if (client->admin_command == COMMAND_SHOUTCAST_METADATA_UPDATE &&
source->shoutcast_compat == 0)
{
avl_tree_unlock (global.source_tree);
......@@ -472,39 +445,18 @@ void admin_handle_request(client_t *client, const char *uri)
}
ICECAST_LOG_INFO("Received admin command %s on mount \"%s\"",
command_string, mount);
admin_handle_mount_request(client, source, command);
admin_handle_mount_request(client, source);
avl_tree_unlock(global.source_tree);
}
}
else {
if (command == COMMAND_PLAINTEXT_LISTSTREAM) {
/* this request is used by a slave relay to retrieve
mounts from the master, so handle this request
validating against the relay password */
if(!connection_check_relay_pass(client->parser)) {
ICECAST_LOG_INFO("Bad or missing password on admin command "
"request (command: %s)", command_string);
client_send_error(client, 401, 1, "You need to authenticate\r\n");
return;
}
}
else {
if(!connection_check_admin_pass(client->parser)) {
ICECAST_LOG_INFO("Bad or missing password on admin command "
"request (command: %s)", command_string);
client_send_error(client, 401, 1, "You need to authenticate\r\n");
return;
}
}
admin_handle_general_request(client, command);
admin_handle_general_request(client);
}
}
static void admin_handle_general_request(client_t *client, int command)
static void admin_handle_general_request(client_t *client)
{
switch(command) {
switch(client->admin_command) {
case COMMAND_RAW_STATS:
command_stats(client, NULL, RAW);
break;
......@@ -542,10 +494,8 @@ static void admin_handle_general_request(client_t *client, int command)
}
}
static void admin_handle_mount_request(client_t *client, source_t *source,
int command)
{
switch(command) {
static void admin_handle_mount_request(client_t *client, source_t *source) {
switch(client->admin_command) {
case COMMAND_RAW_STATS:
command_stats(client, source->mount, RAW);
break;
......@@ -836,14 +786,22 @@ static void command_manageauth(client_t *client, source_t *source,
int ret = AUTH_OK;
ice_config_t *config = config_get_config ();
mount_proxy *mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
auth_t *auth;
do
{
#if 0
if (mountinfo == NULL || mountinfo->auth == NULL)
{
ICECAST_LOG_WARN("manage auth request for %s but no facility available", source->mount);
break;
}