Commit 15b3a5f8 authored by Karl Heyes's avatar Karl Heyes

Initial auth merge. Add an auth thread (multiple threads can be done later)

which can be used to handle authentication mechanisms without taking locks
for long periods.  Non-authenticated mountpoints bypass the auth thread.

The lookup/checking of the source_t is done after the authentication succeeds
so the fallback mechanism does not affect which authenticator is used. This
can be extended to allow us to authenticate in webroot as well. XML re-read
changes will take effect immediately for new listeners but existing listeners
will use the original auth_t (refcounted) when they exit.

htpasswd access has been seperated out from auth.c, and implements an AVL
tree for a faster username lookup.  The htpasswd file timestamp is checked
just in case there are changes made externally

svn path=/icecast/trunk/icecast/; revision=9713
parent 33cf86f5
......@@ -7,14 +7,16 @@ SUBDIRS = avl thread httpp net log timing
bin_PROGRAMS = icecast
noinst_HEADERS = admin.h cfgfile.h os.h logging.h sighandler.h connection.h \
global.h util.h slave.h source.h stats.h refbuf.h client.h format.h \
compat.h format_mp3.h fserve.h xslt.h yp.h event.h md5.h \
auth.h format_ogg.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 \
format.h format_ogg.h format_mp3.h \
format_vorbis.h format_theora.h format_flac.h format_speex.h format_midi.h
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 auth.c md5.c \
format.c format_ogg.c format_mp3.c format_midi.c format_flac.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 \
auth.c auth_htpasswd.c
EXTRA_icecast_SOURCES = yp.c \
format_vorbis.c format_theora.c format_speex.c
......
......@@ -229,6 +229,9 @@ xmlDocPtr admin_build_sourcelist (const char *mount)
if (source->running || source->on_demand)
{
ice_config_t *config;
mount_proxy *mountinfo;
srcnode = xmlNewChild(xmlnode, NULL, "source", NULL);
xmlSetProp(srcnode, "mount", source->mount);
......@@ -237,6 +240,16 @@ xmlDocPtr admin_build_sourcelist (const char *mount)
source->fallback_mount:"");
snprintf (buf, sizeof(buf), "%lu", source->listeners);
xmlNewChild(srcnode, NULL, "listeners", buf);
config = config_get_config();
mountinfo = config_find_mount (config, source->mount);
if (mountinfo && mountinfo->auth)
{
xmlNewChild(srcnode, NULL, "authenticator",
mountinfo->auth->type);
}
config_release_config();
if (source->running)
{
snprintf (buf, sizeof(buf), "%lu",
......@@ -245,10 +258,6 @@ xmlDocPtr admin_build_sourcelist (const char *mount)
xmlNewChild (srcnode, NULL, "content-type",
source->format->contenttype);
}
if (source->authenticator) {
xmlNewChild(srcnode, NULL, "authenticator",
source->authenticator->type);
}
}
node = avl_get_next(node);
}
......@@ -707,12 +716,21 @@ static void command_manageauth(client_t *client, source_t *source,
char *password = NULL;
char *message = NULL;
int ret = AUTH_OK;
ice_config_t *config = config_get_config ();
mount_proxy *mountinfo = config_find_mount (config, source->mount);
if((COMMAND_OPTIONAL(client, "action", action))) {
if (mountinfo == NULL || mountinfo->auth == NULL)
{
WARN1 ("manage auth request for %s but no facility available", source->mount);
config_release_config ();
client_send_404 (client, "no such auth facility");
return;
}
if (!strcmp(action, "add")) {
COMMAND_REQUIRE(client, "username", username);
COMMAND_REQUIRE(client, "password", password);
ret = auth_adduser(source, username, password);
ret = mountinfo->auth->adduser(mountinfo->auth, username, password);
if (ret == AUTH_FAILED) {
message = strdup("User add failed - check the icecast error log");
}
......@@ -725,7 +743,7 @@ static void command_manageauth(client_t *client, source_t *source,
}
if (!strcmp(action, "delete")) {
COMMAND_REQUIRE(client, "username", username);
ret = auth_deleteuser(source, username);
ret = mountinfo->auth->deleteuser(mountinfo->auth, username);
if (ret == AUTH_FAILED) {
message = strdup("User delete failed - check the icecast error log");
}
......@@ -747,7 +765,10 @@ static void command_manageauth(client_t *client, source_t *source,
xmlDocSetRootElement(doc, node);
auth_get_userlist(source, srcnode);
if (mountinfo && mountinfo->auth && mountinfo->auth->listuser)
mountinfo->auth->listuser (mountinfo->auth, srcnode);
config_release_config ();
admin_send_response(doc, client, response,
MANAGEAUTH_TRANSFORMED_REQUEST);
......
......@@ -22,487 +22,534 @@
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "auth.h"
#include "auth_htpasswd.h"
#include "source.h"
#include "client.h"
#include "cfgfile.h"
#include "stats.h"
#include "httpp/httpp.h"
#include "md5.h"
#include "logging.h"
#define CATMODULE "auth"
#ifdef _WIN32
#define snprintf _snprintf
#endif
int auth_is_listener_connected(source_t *source, char *username)
static volatile auth_client *clients_to_auth;
static volatile unsigned int auth_pending_count;
static volatile int auth_running;
static mutex_t auth_lock;
static thread_type *auth_thread;
static void auth_client_setup (mount_proxy *mountinfo, client_t *client)
{
client_t *client;
avl_node *client_node;
avl_tree_rlock(source->client_tree);
client_node = avl_get_first(source->client_tree);
while(client_node) {
client = (client_t *)client_node->key;
if (client->username) {
if (!strcmp(client->username, username)) {
avl_tree_unlock(source->client_tree);
return 1;
/* This will look something like "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" */
char *header = httpp_getvar(client->parser, "authorization");
char *userpass, *tmp;
char *username, *password;
do
{
if (header == NULL)
break;
if (strncmp(header, "Basic ", 6) == 0)
{
userpass = util_base64_decode (header+6);
if (userpass == NULL)
{
WARN1("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;
}
client_node = avl_get_next(client_node);
}
INFO1 ("unhandled authorization header: %s", header);
avl_tree_unlock(source->client_tree);
return 0;
} while (0);
client->auth = mountinfo->auth;
client->auth->refcount++;
}
auth_result auth_check_client(source_t *source, client_t *client)
{
auth_t *authenticator = source->authenticator;
auth_result result;
if(authenticator) {
/* This will look something like "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" */
char *header = httpp_getvar(client->parser, "authorization");
char *userpass, *tmp;
char *username, *password;
if(header == NULL)
return AUTH_FAILED;
if(strncmp(header, "Basic ", 6)) {
INFO0("Authorization not using Basic");
return 0;
}
userpass = util_base64_decode(header+6);
if(userpass == NULL) {
WARN1("Base64 decode of Authorization header \"%s\" failed",
header+6);
return AUTH_FAILED;
}
tmp = strchr(userpass, ':');
if(!tmp) {
free(userpass);
return AUTH_FAILED;
}
*tmp = 0;
username = userpass;
password = tmp+1;
static void queue_auth_client (auth_client *auth_user)
{
thread_mutex_lock (&auth_lock);
auth_user->next = (auth_client *)clients_to_auth;
clients_to_auth = auth_user;
auth_pending_count++;
thread_mutex_unlock (&auth_lock);
}
result = authenticator->authenticate(
authenticator, source, username, password);
if(result == AUTH_OK)
client->username = strdup(username);
/* 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)
{
if (authenticator == NULL)
return;
free(userpass);
authenticator->refcount--;
if (authenticator->refcount)
return;
return result;
}
else
return AUTH_FAILED;
if (authenticator->free)
authenticator->free (authenticator);
free (authenticator->type);
free (authenticator);
}
static auth_t *auth_get_htpasswd_auth(config_options_t *options);
auth_t *auth_get_authenticator(char *type, config_options_t *options)
void auth_client_free (auth_client *auth_user)
{
auth_t *auth = NULL;
if(!strcmp(type, "htpasswd")) {
auth = auth_get_htpasswd_auth(options);
auth->type = strdup(type);
}
else {
ERROR1("Unrecognised authenticator type: \"%s\"", type);
return NULL;
}
if (auth_user == NULL)
return;
if (auth_user->client)
{
client_t *client = auth_user->client;
if(!auth)
ERROR1("Couldn't configure authenticator of type \"%s\"", type);
return auth;
if (client->respcode)
client_destroy (client);
else
client_send_401 (client);
auth_user->client = NULL;
}
free (auth_user->mount);
free (auth_user);
}
typedef struct {
char *filename;
int allow_duplicate_users;
rwlock_t file_rwlock;
} htpasswd_auth_state;
static void htpasswd_clear(auth_t *self) {
htpasswd_auth_state *state = self->state;
free(state->filename);
thread_rwlock_destroy(&state->file_rwlock);
free(state);
free(self->type);
free(self);
}
static int get_line(FILE *file, char *buf, int len)
/* wrapper function for auth thread to authenticate new listener
* connection details
*/
static void auth_new_listener (auth_client *auth_user)
{
if(fgets(buf, len, file)) {
int len = strlen(buf);
if(len > 0 && buf[len-1] == '\n') {
buf[--len] = 0;
if(len > 0 && buf[len-1] == '\r')
buf[--len] = 0;
}
return 1;
client_t *client = auth_user->client;
if (client->auth->authenticate)
{
if (client->auth->authenticate (auth_user) != AUTH_OK)
return;
}
return 0;
if (auth_postprocess_client (auth_user) < 0)
INFO1 ("client %lu failed", client->con->id);
}
/* md5 hash */
static char *get_hash(char *data, int len)
/* wrapper function are auth thread to authenticate new listener
* connections
*/
static void auth_remove_listener (auth_client *auth_user)
{
struct MD5Context context;
unsigned char digest[16];
client_t *client = auth_user->client;
if (client->auth->release_client)
client->auth->release_client (auth_user);
auth_release (client->auth);
client->auth = NULL;
return;
}
MD5Init(&context);
MD5Update(&context, data, len);
/* The auth thread main loop. */
static void *auth_run_thread (void *arg)
{
INFO0 ("Authentication thread started");
while (1)
{
if (clients_to_auth)
{
auth_client *auth_user;
MD5Final(digest, &context);
thread_mutex_lock (&auth_lock);
auth_user = (auth_client*)clients_to_auth;
clients_to_auth = auth_user->next;
auth_pending_count--;
thread_mutex_unlock (&auth_lock);
auth_user->next = NULL;
return util_bin_to_hex(digest, 16);
}
if (auth_user->process)
auth_user->process (auth_user);
else
ERROR0 ("client auth process not set");
#define MAX_LINE_LEN 512
auth_client_free (auth_user);
/* Not efficient; opens and scans the entire file for every request */
static auth_result htpasswd_auth(auth_t *auth, source_t *source, char *username, char *password)
{
htpasswd_auth_state *state = auth->state;
FILE *passwdfile = NULL;
char line[MAX_LINE_LEN];
char *sep;
thread_rwlock_rlock(&state->file_rwlock);
if (!state->allow_duplicate_users) {
if (auth_is_listener_connected(source, username)) {
thread_rwlock_unlock(&state->file_rwlock);
return AUTH_FORBIDDEN;
continue;
}
/* is there a request to shutdown */
if (auth_running == 0)
break;
thread_sleep (150000);
}
passwdfile = fopen(state->filename, "rb");
if(passwdfile == NULL) {
WARN2("Failed to open authentication database \"%s\": %s",
state->filename, strerror(errno));
thread_rwlock_unlock(&state->file_rwlock);
return AUTH_FAILED;
}
INFO0 ("Authenication thread shutting down");
return NULL;
}
while(get_line(passwdfile, line, MAX_LINE_LEN)) {
if(!line[0] || line[0] == '#')
continue;
sep = strchr(line, ':');
if(sep == NULL) {
DEBUG0("No separator in line");
continue;
}
/* Check whether this client is currently on this mount, the client may be
* on either the active or pending lists.
* return 1 if ok to add or 0 to prevent
*/
static int check_duplicate_logins (source_t *source, client_t *client)
{
auth_t *auth = client->auth;
/* allow multiple authenticated relays */
if (client->username == NULL)
return 1;
*sep = 0;
if(!strcmp(username, line)) {
/* Found our user, now: does the hash of password match hash? */
char *hash = sep+1;
char *hashed_password = get_hash(password, strlen(password));
if(!strcmp(hash, hashed_password)) {
fclose(passwdfile);
free(hashed_password);
thread_rwlock_unlock(&state->file_rwlock);
return AUTH_OK;
if (auth && auth->allow_duplicate_users == 0)
{
avl_node *node;
avl_tree_rlock (source->client_tree);
node = avl_get_first (source->client_tree);
while (node)
{
client_t *client = (client_t *)node->key;
if (client->username && strcmp (client->username, client->username) == 0)
{
avl_tree_unlock (source->client_tree);
return 0;
}
node = avl_get_next (node);
}
avl_tree_unlock (source->client_tree);
avl_tree_rlock (source->pending_tree);
node = avl_get_first (source->pending_tree);
while (node)
{
client_t *client = (client_t *)node->key;
if (client->username && strcmp (client->username, client->username) == 0)
{
avl_tree_unlock (source->pending_tree);
return 0;
}
free(hashed_password);
/* We don't keep searching through the file */
break;
node = avl_get_next (node);
}
avl_tree_unlock (source->pending_tree);
}
fclose(passwdfile);
thread_rwlock_unlock(&state->file_rwlock);
return AUTH_FAILED;
return 1;
}
static auth_t *auth_get_htpasswd_auth(config_options_t *options)
/* if 0 is returned then the client should not be touched, however if -1
* is returned then the caller is responsible for handling the client
*/
static int add_client_to_source (source_t *source, client_t *client)
{
auth_t *authenticator = calloc(1, sizeof(auth_t));
htpasswd_auth_state *state;
do
{
DEBUG3 ("max on %s is %ld (cur %lu)", source->mount,
source->max_listeners, source->listeners);
if (source->max_listeners == -1)
break;
if (source->listeners < (unsigned long)source->max_listeners)
break;
authenticator->authenticate = htpasswd_auth;
authenticator->free = htpasswd_clear;
/* now we fail the client */
return -1;
state = calloc(1, sizeof(htpasswd_auth_state));
} while (1);
/* lets add the client to the active list */
avl_tree_wlock (source->pending_tree);
avl_insert (source->pending_tree, client);
avl_tree_unlock (source->pending_tree);
stats_event_inc (NULL, "listener_connections");
state->allow_duplicate_users = 1;
while(options) {
if(!strcmp(options->name, "filename"))
state->filename = strdup(options->value);
if(!strcmp(options->name, "allow_duplicate_users"))
state->allow_duplicate_users = atoi(options->value);
options = options->next;
}
client->write_to_client = format_generic_write_to_client;
client->check_buffer = format_check_http_buffer;
client->refbuf->len = PER_CLIENT_REFBUF_SIZE;
memset (client->refbuf->data, 0, PER_CLIENT_REFBUF_SIZE);
if(!state->filename) {
free(state);
free(authenticator);
ERROR0("No filename given in options for authenticator.");
return NULL;
if (source->running == 0 && source->on_demand)
{
/* enable on-demand relay to start, wake up the slave thread */
DEBUG0("kicking off on-demand relay");
source->on_demand_req = 1;
slave_rescan ();
}
DEBUG1 ("Added client to %s", source->mount);
return 0;
}
authenticator->state = state;
DEBUG1("Configured htpasswd authentication using password file %s",
state->filename);
thread_rwlock_create(&state->file_rwlock);
/* Add listener to the pending lists of either the source or fserve thread.
* This can be run from the connection or auth thread context
*/
static int add_authenticated_client (const char *mount, mount_proxy *mountinfo, client_t *client)
{
int ret = 0;
source_t *source = NULL;
return authenticator;
}
avl_tree_rlock (global.source_tree);
source = source_find_mount (mount);
int auth_htpasswd_existing_user(auth_t *auth, char *username)
{
FILE *passwdfile;
htpasswd_auth_state *state;
int ret = AUTH_OK;
char line[MAX_LINE_LEN];
char *sep;
state = auth->state;
passwdfile = fopen(state->filename, "rb");
if(passwdfile == NULL) {
WARN2("Failed to open authentication database \"%s\": %s",
state->filename, strerror(errno));
return AUTH_FAILED;
}
while(get_line(passwdfile, line, MAX_LINE_LEN)) {
if(!line[0] || line[0] == '#')
continue;
sep = strchr(line, ':');
if(sep == NULL) {
DEBUG0("No separator in line");
continue;
}
*sep = 0;
if (!strcmp(username, line)) {
/* We found the user, break out of the loop */
ret = AUTH_USEREXISTS;
break;
if (source)
{
if (client->auth && check_duplicate_logins (source, client) == 0)
{
avl_tree_unlock (global.source_tree);
return -1;
}
ret = add_client_to_source (source, client);
avl_tree_unlock (global.source_tree);
if (ret == 0)
DEBUG0 ("client authenticated, passed to source");
}
fclose(passwdfile);
return ret;
}
int auth_htpasswd_adduser(auth_t *auth, char *username, char *password)
int auth_postprocess_client (auth_client *auth_user)
{
FILE *passwdfile;
char *hashed_password = NULL;
htpasswd_auth_state *state;
int ret;
ice_config_t *config = config_get_config();
if (auth_htpasswd_existing_user(auth, username) == AUTH_USEREXISTS) {
return AUTH_USEREXISTS;
}
state = auth->state;
passwdfile = fopen(state->filename, "ab");
mount_proxy *mountinfo = config_find_mount (config, auth_user->mount);
auth_user->client->authenticated = 1;
if(passwdfile == NULL) {
WARN2("Failed to open authentication database \"%s\": %s",
state->filename, strerror(errno));
return AUTH_FAILED;
}
ret = add_authenticated_client (auth_user->mount, mountinfo, auth_user->client);