mirror of
https://gitlab.xiph.org/xiph/icecast-server.git
synced 2024-12-04 14:46:30 -05:00
1213 lines
35 KiB
C
1213 lines
35 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 2000-2004, Jack Moffitt <jack@xiph.org,
|
|
* Michael Smith <msmith@xiph.org>,
|
|
* oddsock <oddsock@xiph.org>,
|
|
* Karl Heyes <karl@xiph.org>
|
|
* and others (see AUTHORS for details).
|
|
* Copyright 2013-2018, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
|
|
*/
|
|
|
|
/**
|
|
* Client authentication functions
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
|
|
#include <igloo/httpp.h>
|
|
|
|
#include "auth.h"
|
|
#include "source.h"
|
|
#include "client.h"
|
|
#include "errors.h"
|
|
#include "cfgfile.h"
|
|
#include "stats.h"
|
|
#include "fserve.h"
|
|
#include "admin.h"
|
|
#include "acl.h"
|
|
|
|
#include "logging.h"
|
|
#define CATMODULE "auth"
|
|
|
|
/* data structures */
|
|
struct auth_stack_tag {
|
|
size_t refcount;
|
|
auth_t *auth;
|
|
igloo_mutex_t lock;
|
|
auth_stack_t *next;
|
|
};
|
|
|
|
/* code */
|
|
static void __handle_auth_client(auth_t *auth, auth_client *auth_user);
|
|
|
|
static igloo_mutex_t _auth_lock; /* protects _current_id */
|
|
static volatile unsigned long _current_id = 0;
|
|
|
|
static unsigned long _next_auth_id(void) {
|
|
unsigned long id;
|
|
|
|
igloo_thread_mutex_lock(&_auth_lock);
|
|
id = _current_id++;
|
|
igloo_thread_mutex_unlock(&_auth_lock);
|
|
|
|
return id;
|
|
}
|
|
|
|
static const struct {
|
|
auth_result result;
|
|
const char *string;
|
|
} __auth_results[] = {
|
|
{.result = AUTH_UNDEFINED, .string = "undefined"},
|
|
{.result = AUTH_OK, .string = "ok"},
|
|
{.result = AUTH_FAILED, .string = "failed"},
|
|
{.result = AUTH_RELEASED, .string = "released"},
|
|
{.result = AUTH_FORBIDDEN, .string = "forbidden"},
|
|
{.result = AUTH_NOMATCH, .string = "no match"},
|
|
{.result = AUTH_USERADDED, .string = "user added"},
|
|
{.result = AUTH_USEREXISTS, .string = "user exists"},
|
|
{.result = AUTH_USERDELETED, .string = "user deleted"}
|
|
};
|
|
|
|
static const char *auth_result2str(auth_result res)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < (sizeof(__auth_results)/sizeof(*__auth_results)); i++) {
|
|
if (__auth_results[i].result == res)
|
|
return __auth_results[i].string;
|
|
}
|
|
|
|
return "(unknown)";
|
|
}
|
|
|
|
auth_result auth_str2result(const char *str)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < (sizeof(__auth_results)/sizeof(*__auth_results)); i++) {
|
|
if (strcasecmp(__auth_results[i].string, str) == 0)
|
|
return __auth_results[i].result;
|
|
}
|
|
|
|
return AUTH_FAILED;
|
|
}
|
|
|
|
static auth_client *auth_client_setup (client_t *client)
|
|
{
|
|
/* This will look something like "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" */
|
|
auth_client *auth_user;
|
|
|
|
do {
|
|
const char *header;
|
|
char *userpass, *tmp;
|
|
char *username, *password;
|
|
|
|
/* check if we already have auth infos */
|
|
if (client->username || client->password)
|
|
break;
|
|
|
|
header = igloo_httpp_getvar(client->parser, "authorization");
|
|
|
|
if (header == NULL)
|
|
break;
|
|
|
|
if (strncmp(header, "Basic ", 6) == 0) {
|
|
userpass = util_base64_decode (header+6);
|
|
if (userpass == NULL) {
|
|
ICECAST_LOG_WARN("Base64 decode of Authorization header \"%s\" failed",
|
|
header+6);
|
|
break;
|
|
}
|
|
|
|
tmp = strchr(userpass, ':');
|
|
if (tmp == NULL) {
|
|
free(userpass);
|
|
break;
|
|
}
|
|
|
|
*tmp = 0;
|
|
username = userpass;
|
|
password = tmp+1;
|
|
client->username = strdup(username);
|
|
client->password = strdup(password);
|
|
free (userpass);
|
|
break;
|
|
}
|
|
ICECAST_LOG_INFO("unhandled authorization header: %s", header);
|
|
} while (0);
|
|
|
|
auth_user = calloc(1, sizeof(auth_client));
|
|
auth_user->client = client;
|
|
return auth_user;
|
|
}
|
|
|
|
|
|
static void queue_auth_client (auth_client *auth_user)
|
|
{
|
|
auth_t *auth;
|
|
|
|
if (auth_user == NULL)
|
|
return;
|
|
auth_user->next = NULL;
|
|
if (auth_user->client == NULL || auth_user->client->auth == NULL)
|
|
{
|
|
ICECAST_LOG_WARN("internal state is incorrect for %p", auth_user->client);
|
|
return;
|
|
}
|
|
auth = auth_user->client->auth;
|
|
ICECAST_LOG_DEBUG("...refcount on auth_t %s is now %d", auth->mount, (int)auth->refcount);
|
|
if (auth->immediate) {
|
|
__handle_auth_client(auth, auth_user);
|
|
} else {
|
|
igloo_thread_mutex_lock (&auth->lock);
|
|
*auth->tailp = auth_user;
|
|
auth->tailp = &auth_user->next;
|
|
auth->pending_count++;
|
|
ICECAST_LOG_INFO("auth on %s has %d pending", auth->mount, auth->pending_count);
|
|
igloo_thread_mutex_unlock (&auth->lock);
|
|
}
|
|
}
|
|
|
|
|
|
/* release the auth. It is referred to by multiple structures so this is
|
|
* refcounted and only actual freed after the last use
|
|
*/
|
|
void auth_release (auth_t *authenticator) {
|
|
size_t i;
|
|
|
|
if (authenticator == NULL)
|
|
return;
|
|
|
|
igloo_thread_mutex_lock(&authenticator->lock);
|
|
authenticator->refcount--;
|
|
ICECAST_LOG_DEBUG("...refcount on auth_t %s is now %d", authenticator->mount, (int)authenticator->refcount);
|
|
if (authenticator->refcount)
|
|
{
|
|
igloo_thread_mutex_unlock(&authenticator->lock);
|
|
return;
|
|
}
|
|
|
|
/* cleanup auth thread attached to this auth */
|
|
if (authenticator->running) {
|
|
authenticator->running = 0;
|
|
igloo_thread_mutex_unlock(&authenticator->lock);
|
|
igloo_thread_join(authenticator->thread);
|
|
igloo_thread_mutex_lock(&authenticator->lock);
|
|
}
|
|
|
|
if (authenticator->free)
|
|
authenticator->free(authenticator);
|
|
if (authenticator->type)
|
|
xmlFree (authenticator->type);
|
|
if (authenticator->role)
|
|
xmlFree (authenticator->role);
|
|
if (authenticator->management_url)
|
|
xmlFree (authenticator->management_url);
|
|
igloo_thread_mutex_unlock(&authenticator->lock);
|
|
igloo_thread_mutex_destroy(&authenticator->lock);
|
|
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);
|
|
}
|
|
|
|
/* increment refcount on the auth.
|
|
*/
|
|
void auth_addref (auth_t *authenticator) {
|
|
if (authenticator == NULL)
|
|
return;
|
|
|
|
igloo_thread_mutex_lock (&authenticator->lock);
|
|
authenticator->refcount++;
|
|
ICECAST_LOG_DEBUG("...refcount on auth_t %s is now %d", authenticator->mount, (int)authenticator->refcount);
|
|
igloo_thread_mutex_unlock (&authenticator->lock);
|
|
}
|
|
|
|
static void auth_client_free (auth_client *auth_user)
|
|
{
|
|
if (!auth_user)
|
|
return;
|
|
|
|
free(auth_user->alter_client_arg);
|
|
free(auth_user);
|
|
}
|
|
|
|
|
|
/* verify that the client is still connected. */
|
|
static int is_client_connected (client_t *client) {
|
|
/* As long as igloo_sock_active() is broken we need to disable this:
|
|
|
|
int ret = 1;
|
|
if (client)
|
|
if (igloo_sock_active(client->con->sock) == 0)
|
|
ret = 0;
|
|
return ret;
|
|
*/
|
|
return 1;
|
|
}
|
|
|
|
static auth_result auth_new_client (auth_t *auth, auth_client *auth_user) {
|
|
client_t *client = auth_user->client;
|
|
auth_result ret = AUTH_FAILED;
|
|
|
|
/* make sure there is still a client at this point, a slow backend request
|
|
* can be avoided if client has disconnected */
|
|
if (is_client_connected(client) == 0) {
|
|
ICECAST_LOG_DEBUG("client is no longer connected");
|
|
client->respcode = 400;
|
|
auth_release (client->auth);
|
|
client->auth = NULL;
|
|
return AUTH_FAILED;
|
|
}
|
|
|
|
if (auth->authenticate_client) {
|
|
ret = auth->authenticate_client(auth_user);
|
|
if (ret != AUTH_OK)
|
|
{
|
|
auth_release (client->auth);
|
|
client->auth = NULL;
|
|
return ret;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* wrapper function for auth thread to drop client connections
|
|
*/
|
|
static auth_result auth_remove_client(auth_t *auth, auth_client *auth_user)
|
|
{
|
|
client_t *client = auth_user->client;
|
|
auth_result ret = AUTH_RELEASED;
|
|
|
|
(void)auth;
|
|
|
|
if (client->auth->release_client)
|
|
ret = client->auth->release_client(auth_user);
|
|
|
|
auth_release(client->auth);
|
|
client->auth = NULL;
|
|
|
|
/* client is going, so auth is not an issue at this point */
|
|
acl_release(client->acl);
|
|
client->acl = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline int __handle_auth_client_alter(auth_t *auth, auth_client *auth_user)
|
|
{
|
|
client_t *client = auth_user->client;
|
|
const char *uuid = NULL;
|
|
const char *location = NULL;
|
|
int http_status = 0;
|
|
|
|
void client_send_redirect(client_t *client, const char *uuid, int status, const char *location);
|
|
|
|
switch (auth_user->alter_client_action) {
|
|
case AUTH_ALTER_NOOP:
|
|
return 0;
|
|
break;
|
|
case AUTH_ALTER_REWRITE:
|
|
free(client->uri);
|
|
client->uri = auth_user->alter_client_arg;
|
|
auth_user->alter_client_arg = NULL;
|
|
return 0;
|
|
break;
|
|
case AUTH_ALTER_REDIRECT:
|
|
/* fall through */
|
|
case AUTH_ALTER_REDIRECT_SEE_OTHER:
|
|
uuid = "be7fac90-54fb-4673-9e0d-d15d6a4963a2";
|
|
http_status = 303;
|
|
location = auth_user->alter_client_arg;
|
|
break;
|
|
case AUTH_ALTER_REDIRECT_TEMPORARY:
|
|
uuid = "4b08a03a-ecce-4981-badf-26b0bb6c9d9c";
|
|
http_status = 307;
|
|
location = auth_user->alter_client_arg;
|
|
break;
|
|
case AUTH_ALTER_REDIRECT_PERMANENT:
|
|
uuid = "36bf6815-95cb-4cc8-a7b0-6b4b0c82ac5d";
|
|
http_status = 308;
|
|
location = auth_user->alter_client_arg;
|
|
break;
|
|
case AUTH_ALTER_SEND_ERROR:
|
|
client_send_error_by_uuid(client, auth_user->alter_client_arg);
|
|
return 1;
|
|
break;
|
|
}
|
|
|
|
if (uuid && location && http_status) {
|
|
client_send_redirect(client, uuid, http_status, location);
|
|
return 1;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
static void __handle_auth_client (auth_t *auth, auth_client *auth_user) {
|
|
auth_result result;
|
|
|
|
if (auth_user->process) {
|
|
result = auth_user->process(auth, auth_user);
|
|
} else {
|
|
ICECAST_LOG_ERROR("client auth process not set");
|
|
result = AUTH_FAILED;
|
|
}
|
|
|
|
ICECAST_LOG_DEBUG("client %p on auth %p role %s processed: %s", auth_user->client, auth, auth->role, auth_result2str(result));
|
|
|
|
if (result == AUTH_OK) {
|
|
if (auth_user->client->acl)
|
|
acl_release(auth_user->client->acl);
|
|
acl_addref(auth_user->client->acl = auth->acl);
|
|
if (auth->role && !auth_user->client->role) /* TODO: Handle errors here */
|
|
auth_user->client->role = strdup(auth->role);
|
|
}
|
|
|
|
if (result != AUTH_NOMATCH) {
|
|
if (__handle_auth_client_alter(auth, auth_user) == 1)
|
|
return;
|
|
}
|
|
|
|
if (result == AUTH_NOMATCH && auth_user->on_no_match) {
|
|
auth_user->on_no_match(auth_user->client, auth_user->on_result, auth_user->userdata);
|
|
} else if (auth_user->on_result) {
|
|
auth_user->on_result(auth_user->client, auth_user->userdata, result);
|
|
}
|
|
|
|
auth_client_free (auth_user);
|
|
}
|
|
|
|
/* The auth thread main loop. */
|
|
static void *auth_run_thread (void *arg)
|
|
{
|
|
auth_t *auth = arg;
|
|
|
|
ICECAST_LOG_INFO("Authentication thread started");
|
|
while (1) {
|
|
igloo_thread_mutex_lock(&auth->lock);
|
|
|
|
if (!auth->running) {
|
|
igloo_thread_mutex_unlock(&auth->lock);
|
|
break;
|
|
}
|
|
|
|
if (auth->head) {
|
|
auth_client *auth_user;
|
|
|
|
/* may become NULL before lock taken */
|
|
auth_user = (auth_client*)auth->head;
|
|
if (auth_user == NULL)
|
|
{
|
|
igloo_thread_mutex_unlock (&auth->lock);
|
|
continue;
|
|
}
|
|
ICECAST_LOG_DEBUG("%d client(s) pending on %s (role %s)", auth->pending_count, auth->mount, auth->role);
|
|
auth->head = auth_user->next;
|
|
if (auth->head == NULL)
|
|
auth->tailp = &auth->head;
|
|
auth->pending_count--;
|
|
igloo_thread_mutex_unlock(&auth->lock);
|
|
auth_user->next = NULL;
|
|
|
|
__handle_auth_client(auth, auth_user);
|
|
|
|
continue;
|
|
} else {
|
|
igloo_thread_mutex_unlock(&auth->lock);
|
|
}
|
|
igloo_thread_sleep (150000);
|
|
}
|
|
ICECAST_LOG_INFO("Authentication thread shutting down");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Add a client.
|
|
*/
|
|
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);
|
|
|
|
/* TODO: replace that magic number */
|
|
if (auth->pending_count > 100) {
|
|
ICECAST_LOG_WARN("too many clients awaiting authentication on auth %p", auth);
|
|
client_send_error_by_id(client, ICECAST_ERROR_AUTH_BUSY);
|
|
return;
|
|
}
|
|
|
|
if (auth->filter_method[client->parser->req_type] == 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;
|
|
}
|
|
|
|
/* 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;
|
|
} else {
|
|
/* this is a admin/ client */
|
|
size_t i;
|
|
|
|
matchtype = AUTH_MATCHTYPE_UNUSED;
|
|
|
|
for (i = 0; i < (sizeof(auth->filter_admin)/sizeof(*(auth->filter_admin))); i++) {
|
|
if (auth->filter_admin[i].type != AUTH_MATCHTYPE_UNUSED && auth->filter_admin[i].command == client->admin_command) {
|
|
matchtype = auth->filter_admin[i].type;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (matchtype == AUTH_MATCHTYPE_UNUSED)
|
|
matchtype = auth->filter_admin_policy;
|
|
}
|
|
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;
|
|
}
|
|
|
|
auth_release(client->auth);
|
|
auth_addref(client->auth = auth);
|
|
auth_user = auth_client_setup(client);
|
|
auth_user->process = auth_new_client;
|
|
auth_user->on_no_match = on_no_match;
|
|
auth_user->on_result = on_result;
|
|
auth_user->userdata = userdata;
|
|
ICECAST_LOG_DEBUG("adding client %p for authentication on %p", client, auth);
|
|
queue_auth_client(auth_user);
|
|
}
|
|
|
|
static void __auth_on_result_destroy_client(client_t *client, void *userdata, auth_result result)
|
|
{
|
|
(void)userdata, (void)result;
|
|
|
|
client_destroy(client);
|
|
}
|
|
|
|
/* determine whether we need to process this client further. This
|
|
* involves any auth exit, typically for external auth servers.
|
|
*/
|
|
int auth_release_client (client_t *client) {
|
|
if (!client->acl)
|
|
return 0;
|
|
|
|
if (client->auth && client->auth->release_client) {
|
|
auth_client *auth_user = auth_client_setup(client);
|
|
auth_user->process = auth_remove_client;
|
|
auth_user->on_result = __auth_on_result_destroy_client;
|
|
queue_auth_client(auth_user);
|
|
return 1;
|
|
} else if (client->auth) {
|
|
auth_release(client->auth);
|
|
client->auth = NULL;
|
|
}
|
|
|
|
acl_release(client->acl);
|
|
client->acl = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int get_authenticator (auth_t *auth, config_options_t *options)
|
|
{
|
|
if (auth->type == NULL)
|
|
{
|
|
ICECAST_LOG_WARN("no authentication type defined");
|
|
return -1;
|
|
}
|
|
do
|
|
{
|
|
ICECAST_LOG_DEBUG("type is %s", auth->type);
|
|
|
|
if (strcmp(auth->type, AUTH_TYPE_URL) == 0) {
|
|
#ifdef HAVE_CURL
|
|
if (auth_get_url_auth(auth, options) < 0)
|
|
return -1;
|
|
break;
|
|
#else
|
|
ICECAST_LOG_ERROR("Auth URL disabled");
|
|
return -1;
|
|
#endif
|
|
} else if (strcmp(auth->type, AUTH_TYPE_HTPASSWD) == 0) {
|
|
if (auth_get_htpasswd_auth(auth, options) < 0)
|
|
return -1;
|
|
break;
|
|
} else if (strcmp(auth->type, AUTH_TYPE_ANONYMOUS) == 0) {
|
|
if (auth_get_anonymous_auth(auth, options) < 0)
|
|
return -1;
|
|
break;
|
|
} else if (strcmp(auth->type, AUTH_TYPE_STATIC) == 0) {
|
|
if (auth_get_static_auth(auth, options) < 0)
|
|
return -1;
|
|
break;
|
|
} else if (strcmp(auth->type, AUTH_TYPE_LEGACY_PASSWORD) == 0) {
|
|
if (auth_get_static_auth(auth, options) < 0)
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
ICECAST_LOG_ERROR("Unrecognised authenticator type: \"%s\"", auth->type);
|
|
return -1;
|
|
} while (0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static inline void auth_get_authenticator__filter_admin(auth_t *auth, xmlNodePtr node, size_t *filter_admin_index, 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 (*filter_admin_index < (sizeof(auth->filter_admin)/sizeof(*(auth->filter_admin)))) {
|
|
auth->filter_admin[*filter_admin_index].command = admin_get_command(cur);
|
|
switch (auth->filter_admin[*filter_admin_index].command) {
|
|
case ADMIN_COMMAND_ERROR:
|
|
ICECAST_LOG_ERROR("Can not add unknown %s command to role.", name);
|
|
break;
|
|
case ADMIN_COMMAND_ANY:
|
|
auth->filter_admin_policy = matchtype;
|
|
break;
|
|
default:
|
|
auth->filter_admin[*filter_admin_index].type = matchtype;
|
|
(*filter_admin_index)++;
|
|
break;
|
|
}
|
|
} else {
|
|
ICECAST_LOG_ERROR("Can not add more %s commands to role.", name);
|
|
}
|
|
|
|
cur = next;
|
|
}
|
|
|
|
free(tmp);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (*method_inited)
|
|
return;
|
|
|
|
for (i = 0; i < (sizeof(auth->filter_method)/sizeof(*auth->filter_method)); i++)
|
|
auth->filter_method[i] = init_with;
|
|
|
|
*method_inited = 1;
|
|
}
|
|
|
|
static inline int auth_get_authenticator__filter_method(auth_t *auth, xmlNodePtr node, const char *name, auth_matchtype_t matchtype, int *method_inited, auth_matchtype_t init_with)
|
|
{
|
|
char * tmp = (char*)xmlGetProp(node, XMLSTR(name));
|
|
|
|
if (tmp) {
|
|
char *cur = tmp;
|
|
|
|
auth_get_authenticator__init_method(auth, method_inited, init_with);
|
|
|
|
while (cur) {
|
|
char *next = strstr(cur, ",");
|
|
igloo_httpp_request_type_e idx;
|
|
|
|
if (next) {
|
|
*next = 0;
|
|
next++;
|
|
for (; *next == ' '; next++);
|
|
}
|
|
|
|
if (strcmp(cur, "*") == 0) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < (sizeof(auth->filter_method)/sizeof(*(auth->filter_method))); i++)
|
|
auth->filter_method[i] = matchtype;
|
|
break;
|
|
}
|
|
|
|
idx = igloo_httpp_str_to_method(cur);
|
|
if (idx == igloo_httpp_req_unknown) {
|
|
ICECAST_LOG_ERROR("Can not add known method \"%H\" to role's %s", cur, name);
|
|
return -1;
|
|
}
|
|
auth->filter_method[idx] = matchtype;
|
|
cur = next;
|
|
}
|
|
|
|
free(tmp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int auth_get_authenticator__permission_alter(auth_t *auth, xmlNodePtr node, const char *name, auth_matchtype_t matchtype)
|
|
{
|
|
char * tmp = (char*)xmlGetProp(node, XMLSTR(name));
|
|
|
|
if (tmp) {
|
|
char *cur = tmp;
|
|
|
|
while (cur) {
|
|
char *next = strstr(cur, ",");
|
|
auth_alter_t idx;
|
|
|
|
if (next) {
|
|
*next = 0;
|
|
next++;
|
|
for (; *next == ' '; next++);
|
|
}
|
|
|
|
if (strcmp(cur, "*") == 0) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < (sizeof(auth->permission_alter)/sizeof(*(auth->permission_alter))); i++)
|
|
auth->permission_alter[i] = matchtype;
|
|
break;
|
|
}
|
|
|
|
idx = auth_str2alter(cur);
|
|
if (idx == AUTH_ALTER_NOOP) {
|
|
ICECAST_LOG_ERROR("Can not add unknown alter action \"%H\" to role's %s", cur, name);
|
|
return -1;
|
|
} else if (idx == AUTH_ALTER_REDIRECT) {
|
|
auth->permission_alter[AUTH_ALTER_REDIRECT] = matchtype;
|
|
auth->permission_alter[AUTH_ALTER_REDIRECT_SEE_OTHER] = matchtype;
|
|
auth->permission_alter[AUTH_ALTER_REDIRECT_TEMPORARY] = matchtype;
|
|
auth->permission_alter[AUTH_ALTER_REDIRECT_PERMANENT] = matchtype;
|
|
} else {
|
|
auth->permission_alter[idx] = matchtype;
|
|
}
|
|
|
|
cur = next;
|
|
}
|
|
|
|
free(tmp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
auth_t *auth_get_authenticator(xmlNodePtr node)
|
|
{
|
|
auth_t *auth = calloc(1, sizeof(auth_t));
|
|
config_options_t *options = NULL, **next_option = &options;
|
|
xmlNodePtr child;
|
|
char *method;
|
|
char *tmp;
|
|
size_t i;
|
|
size_t filter_admin_index = 0;
|
|
int method_inited = 0;
|
|
|
|
if (auth == NULL)
|
|
return NULL;
|
|
|
|
igloo_thread_mutex_create(&auth->lock);
|
|
auth->refcount = 1;
|
|
auth->id = _next_auth_id();
|
|
auth->type = (char*)xmlGetProp(node, XMLSTR("type"));
|
|
auth->role = (char*)xmlGetProp(node, XMLSTR("name"));
|
|
auth->management_url = (char*)xmlGetProp(node, XMLSTR("management-url"));
|
|
auth->filter_web_policy = AUTH_MATCHTYPE_MATCH;
|
|
auth->filter_admin_policy = AUTH_MATCHTYPE_MATCH;
|
|
|
|
for (i = 0; i < (sizeof(auth->filter_admin)/sizeof(*(auth->filter_admin))); i++) {
|
|
auth->filter_admin[i].type = AUTH_MATCHTYPE_UNUSED;
|
|
auth->filter_admin[i].command = ADMIN_COMMAND_ERROR;
|
|
}
|
|
|
|
for (i = 0; i < (sizeof(auth->permission_alter)/sizeof(*(auth->permission_alter))); i++)
|
|
auth->permission_alter[i] = AUTH_MATCHTYPE_NOMATCH;
|
|
|
|
if (!auth->type) {
|
|
auth_release(auth);
|
|
return NULL;
|
|
}
|
|
|
|
method = (char*)xmlGetProp(node, XMLSTR("method"));
|
|
if (method) {
|
|
char *cur = method;
|
|
char *next;
|
|
|
|
for (i = 0; i < (sizeof(auth->filter_method)/sizeof(*auth->filter_method)); i++)
|
|
auth->filter_method[i] = AUTH_MATCHTYPE_NOMATCH;
|
|
method_inited = 1;
|
|
|
|
while (cur) {
|
|
igloo_httpp_request_type_e idx;
|
|
|
|
next = strstr(cur, ",");
|
|
if (next) {
|
|
*next = 0;
|
|
next++;
|
|
for (; *next == ' '; next++);
|
|
}
|
|
|
|
if (strcmp(cur, "*") == 0) {
|
|
for (i = 0; i < (sizeof(auth->filter_method)/sizeof(*auth->filter_method)); i++)
|
|
auth->filter_method[i] = AUTH_MATCHTYPE_MATCH;
|
|
break;
|
|
}
|
|
|
|
idx = igloo_httpp_str_to_method(cur);
|
|
if (idx == igloo_httpp_req_unknown) {
|
|
auth_release(auth);
|
|
return NULL;
|
|
}
|
|
auth->filter_method[idx] = AUTH_MATCHTYPE_MATCH;
|
|
|
|
cur = next;
|
|
}
|
|
|
|
xmlFree(method);
|
|
}
|
|
|
|
auth_get_authenticator__filter_method(auth, node, "match-method", AUTH_MATCHTYPE_MATCH, &method_inited, AUTH_MATCHTYPE_NOMATCH);
|
|
auth_get_authenticator__filter_method(auth, node, "nomatch-method", AUTH_MATCHTYPE_NOMATCH, &method_inited, AUTH_MATCHTYPE_MATCH);
|
|
|
|
auth_get_authenticator__init_method(auth, &method_inited, AUTH_MATCHTYPE_MATCH);
|
|
|
|
tmp = (char*)xmlGetProp(node, XMLSTR("match-web"));
|
|
if (tmp) {
|
|
if (strcmp(tmp, "*") == 0) {
|
|
auth->filter_web_policy = AUTH_MATCHTYPE_MATCH;
|
|
} else {
|
|
auth->filter_web_policy = AUTH_MATCHTYPE_NOMATCH;
|
|
}
|
|
free(tmp);
|
|
}
|
|
|
|
tmp = (char*)xmlGetProp(node, XMLSTR("nomatch-web"));
|
|
if (tmp) {
|
|
if (strcmp(tmp, "*") == 0) {
|
|
auth->filter_web_policy = AUTH_MATCHTYPE_NOMATCH;
|
|
} else {
|
|
auth->filter_web_policy = AUTH_MATCHTYPE_MATCH;
|
|
}
|
|
free(tmp);
|
|
}
|
|
|
|
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);
|
|
|
|
/* 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(child, XMLSTR("name"));
|
|
if (opt->name == NULL) {
|
|
free(opt);
|
|
continue;
|
|
}
|
|
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);
|
|
}
|
|
} while ((child = child->next));
|
|
|
|
if (!auth->acl) {
|
|
/* If we did not get a <acl> try ACL as part of <role> (old style). */
|
|
auth->acl = acl_new_from_xml_node(node);
|
|
}
|
|
if (!auth->acl) {
|
|
auth_release(auth);
|
|
auth = NULL;
|
|
} else {
|
|
if (get_authenticator (auth, options) < 0) {
|
|
auth_release(auth);
|
|
auth = NULL;
|
|
} else {
|
|
auth->tailp = &auth->head;
|
|
if (!auth->immediate) {
|
|
auth->running = 1;
|
|
auth->thread = igloo_thread_create("auth thread", auth_run_thread, auth, igloo_THREAD_ATTACHED);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (options) {
|
|
config_options_t *opt = options;
|
|
options = opt->next;
|
|
xmlFree(opt->name);
|
|
xmlFree(opt->value);
|
|
free (opt);
|
|
}
|
|
|
|
if (auth && !auth->management_url && (auth->adduser || auth->deleteuser || auth->listuser)) {
|
|
char url[128];
|
|
snprintf(url, sizeof(url), "/admin/manageauth.xsl?id=%lu", auth->id);
|
|
auth->management_url = (char*)xmlCharStrdup(url);
|
|
}
|
|
|
|
return auth;
|
|
}
|
|
|
|
int auth_alter_client(auth_t *auth, auth_client *auth_user, auth_alter_t action, const char *arg)
|
|
{
|
|
if (!auth || !auth_user || !arg)
|
|
return -1;
|
|
|
|
if (action < 0 || action >= (sizeof(auth->permission_alter)/sizeof(*(auth->permission_alter))))
|
|
return -1;
|
|
|
|
if (auth->permission_alter[action] != AUTH_MATCHTYPE_MATCH)
|
|
return -1;
|
|
|
|
if (util_replace_string(&(auth_user->alter_client_arg), arg) != 0)
|
|
return -1;
|
|
|
|
auth_user->alter_client_action = action;
|
|
|
|
return 0;
|
|
}
|
|
|
|
auth_alter_t auth_str2alter(const char *str)
|
|
{
|
|
if (!str)
|
|
return AUTH_ALTER_NOOP;
|
|
|
|
if (strcasecmp(str, "noop") == 0) {
|
|
return AUTH_ALTER_NOOP;
|
|
} else if (strcasecmp(str, "rewrite") == 0) {
|
|
return AUTH_ALTER_REWRITE;
|
|
} else if (strcasecmp(str, "redirect") == 0) {
|
|
return AUTH_ALTER_REDIRECT;
|
|
} else if (strcasecmp(str, "redirect_see_other") == 0) {
|
|
return AUTH_ALTER_REDIRECT_SEE_OTHER;
|
|
} else if (strcasecmp(str, "redirect_temporary") == 0) {
|
|
return AUTH_ALTER_REDIRECT_TEMPORARY;
|
|
} else if (strcasecmp(str, "redirect_permanent") == 0) {
|
|
return AUTH_ALTER_REDIRECT_PERMANENT;
|
|
} else if (strcasecmp(str, "send_error") == 0) {
|
|
return AUTH_ALTER_SEND_ERROR;
|
|
} else {
|
|
return AUTH_ALTER_NOOP;
|
|
}
|
|
}
|
|
|
|
/* these are called at server start and termination */
|
|
|
|
void auth_initialise (void)
|
|
{
|
|
igloo_thread_mutex_create(&_auth_lock);
|
|
}
|
|
|
|
void auth_shutdown (void)
|
|
{
|
|
ICECAST_LOG_INFO("Auth shutdown");
|
|
igloo_thread_mutex_destroy(&_auth_lock);
|
|
}
|
|
|
|
/* authstack functions */
|
|
|
|
static void __move_client_forward_in_auth_stack(client_t *client, void (*on_result)(client_t *client, void *userdata, auth_result result), void *userdata) {
|
|
auth_stack_next(&client->authstack);
|
|
if (client->authstack) {
|
|
auth_stack_add_client(client->authstack, client, on_result, userdata);
|
|
} else {
|
|
if (on_result)
|
|
on_result(client, userdata, AUTH_NOMATCH);
|
|
}
|
|
}
|
|
|
|
void auth_stack_add_client(auth_stack_t *stack, client_t *client, void (*on_result)(client_t *client, void *userdata, auth_result result), void *userdata) {
|
|
auth_t *auth;
|
|
|
|
if (!stack || !client || (client->authstack && client->authstack != stack))
|
|
return;
|
|
|
|
if (!client->authstack)
|
|
auth_stack_addref(stack);
|
|
client->authstack = stack;
|
|
auth = auth_stack_get(stack);
|
|
auth_add_client(auth, client, __move_client_forward_in_auth_stack, on_result, userdata);
|
|
auth_release(auth);
|
|
}
|
|
|
|
void auth_stack_release(auth_stack_t *stack) {
|
|
if (!stack)
|
|
return;
|
|
|
|
igloo_thread_mutex_lock(&stack->lock);
|
|
stack->refcount--;
|
|
igloo_thread_mutex_unlock(&stack->lock);
|
|
|
|
if (stack->refcount)
|
|
return;
|
|
|
|
auth_release(stack->auth);
|
|
auth_stack_release(stack->next);
|
|
igloo_thread_mutex_destroy(&stack->lock);
|
|
free(stack);
|
|
}
|
|
|
|
void auth_stack_addref(auth_stack_t *stack) {
|
|
if (!stack)
|
|
return;
|
|
igloo_thread_mutex_lock(&stack->lock);
|
|
stack->refcount++;
|
|
igloo_thread_mutex_unlock(&stack->lock);
|
|
}
|
|
|
|
int auth_stack_next(auth_stack_t **stack) {
|
|
auth_stack_t *next;
|
|
if (!stack || !*stack)
|
|
return -1;
|
|
igloo_thread_mutex_lock(&(*stack)->lock);
|
|
next = (*stack)->next;
|
|
auth_stack_addref(next);
|
|
igloo_thread_mutex_unlock(&(*stack)->lock);
|
|
auth_stack_release(*stack);
|
|
*stack = next;
|
|
if (!next)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int auth_stack_push(auth_stack_t **stack, auth_t *auth) {
|
|
auth_stack_t *next;
|
|
|
|
if (!stack || !auth)
|
|
return -1;
|
|
|
|
next = calloc(1, sizeof(*next));
|
|
if (!next) {
|
|
return -1;
|
|
}
|
|
igloo_thread_mutex_create(&next->lock);
|
|
next->refcount = 1;
|
|
next->auth = auth;
|
|
auth_addref(auth);
|
|
|
|
if (*stack) {
|
|
auth_stack_append(*stack, next);
|
|
auth_stack_release(next);
|
|
return 0;
|
|
} else {
|
|
*stack = next;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int auth_stack_append(auth_stack_t *stack, auth_stack_t *tail) {
|
|
auth_stack_t *next, *cur;
|
|
|
|
if (!stack)
|
|
return -1;
|
|
|
|
auth_stack_addref(cur = stack);
|
|
igloo_thread_mutex_lock(&cur->lock);
|
|
while (1) {
|
|
next = cur->next;
|
|
if (!cur->next)
|
|
break;
|
|
|
|
auth_stack_addref(next);
|
|
igloo_thread_mutex_unlock(&cur->lock);
|
|
auth_stack_release(cur);
|
|
cur = next;
|
|
igloo_thread_mutex_lock(&cur->lock);
|
|
}
|
|
|
|
auth_stack_addref(cur->next = tail);
|
|
igloo_thread_mutex_unlock(&cur->lock);
|
|
auth_stack_release(cur);
|
|
|
|
return 0;
|
|
}
|
|
|
|
auth_t *auth_stack_get(auth_stack_t *stack) {
|
|
auth_t *auth;
|
|
|
|
if (!stack)
|
|
return NULL;
|
|
|
|
igloo_thread_mutex_lock(&stack->lock);
|
|
auth_addref(auth = stack->auth);
|
|
igloo_thread_mutex_unlock(&stack->lock);
|
|
return auth;
|
|
}
|
|
|
|
auth_t *auth_stack_getbyid(auth_stack_t *stack, unsigned long id) {
|
|
auth_t *ret = NULL;
|
|
|
|
if (!stack)
|
|
return NULL;
|
|
|
|
auth_stack_addref(stack);
|
|
|
|
while (!ret && stack) {
|
|
auth_t *auth = auth_stack_get(stack);
|
|
if (auth->id == id) {
|
|
ret = auth;
|
|
break;
|
|
}
|
|
auth_release(auth);
|
|
auth_stack_next(&stack);
|
|
}
|
|
|
|
if (stack)
|
|
auth_stack_release(stack);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
acl_t *auth_stack_get_anonymous_acl(auth_stack_t *stack, igloo_httpp_request_type_e method) {
|
|
acl_t *ret = NULL;
|
|
|
|
if (!stack || method < 0 || method > igloo_httpp_req_unknown)
|
|
return NULL;
|
|
|
|
auth_stack_addref(stack);
|
|
|
|
while (!ret && stack) {
|
|
auth_t *auth = auth_stack_get(stack);
|
|
if (auth->filter_method[method] != AUTH_MATCHTYPE_NOMATCH && strcmp(auth->type, AUTH_TYPE_ANONYMOUS) == 0) {
|
|
acl_addref(ret = auth->acl);
|
|
}
|
|
auth_release(auth);
|
|
|
|
if (!ret)
|
|
auth_stack_next(&stack);
|
|
}
|
|
|
|
if (stack)
|
|
auth_stack_release(stack);
|
|
|
|
return ret;
|
|
}
|