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);
......
This diff is collapsed.
......@@ -17,14 +17,19 @@
#include <config.h>
#endif
struct source_tag;
struct auth_tag;
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include "source.h"
#include "cfgfile.h"
#include "client.h"
#include "thread/thread.h"
typedef enum
{
AUTH_UNDEFINED,
AUTH_OK,
AUTH_FAILED,
AUTH_FORBIDDEN,
......@@ -33,23 +38,57 @@ typedef enum
AUTH_USERDELETED,
} auth_result;
typedef struct auth_client_tag
{
char *mount;
client_t *client;
void (*process)(struct auth_client_tag *auth_user);
struct auth_client_tag *next;
} auth_client;
typedef struct auth_tag
{
char *mount;
/* Authenticate using the given username and password */
auth_result (*authenticate)(struct auth_tag *self,
source_t *source, char *username, char *password);
auth_result (*authenticate)(auth_client *aclient);
auth_result (*release_client)(auth_client *auth_user);
/* callbacks to specific auth for notifying auth server on source
* startup or shutdown
*/
void (*stream_start)(auth_client *auth_user);
void (*stream_end)(auth_client *auth_user);
void (*free)(struct auth_tag *self);
auth_result (*adduser)(struct auth_tag *auth, const char *username, const char *password);
auth_result (*deleteuser)(struct auth_tag *auth, const char *username);
auth_result (*listuser)(struct auth_tag *auth, xmlNodePtr srcnode);
int refcount;
int allow_duplicate_users;
void *state;
char *type;
} auth_t;
auth_result auth_check_client(source_t *source, client_t *client);
void add_client (const char *mount, client_t *client);
int release_client (client_t *client);
void auth_initialise ();
auth_t *auth_get_authenticator (xmlNodePtr node);
void auth_release (auth_t *authenticator);
/* call to send a url request when source starts */
void auth_stream_start (struct _mount_proxy *mountinfo, const char *mount);
/* call to send a url request when source ends */
void auth_stream_end (struct _mount_proxy *mountinfo, const char *mount);
auth_t *auth_get_authenticator(char *type, config_options_t *options);
void *auth_clear(auth_t *authenticator);
int auth_get_userlist(source_t *source, xmlNodePtr srcnode);
int auth_adduser(source_t *source, char *username, char *password);
int auth_deleteuser(source_t *source, char *username);
/* called from auth thread, after the client has successfully authenticated
* and requires adding to source or fserve. */
int auth_postprocess_client (auth_client *auth_user);
#endif
......
/* 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).
*/
/**
* Client authentication functions
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "auth.h"
#include "source.h"
#include "client.h"
#include "cfgfile.h"
#include "httpp/httpp.h"
#include "md5.h"
#include "logging.h"
#define CATMODULE "auth_htpasswd"
#ifdef WIN32
#define snprintf _snprintf
#endif
static auth_result htpasswd_adduser (auth_t *auth, const char *username, const char *password);
static auth_result htpasswd_deleteuser(auth_t *auth, const char *username);
static auth_result htpasswd_userlist(auth_t *auth, xmlNodePtr srcnode);
static int _free_user (void *key);
typedef struct
{
char *name;
char *pass;
} htpasswd_user;
typedef struct {
char *filename;
rwlock_t file_rwlock;
avl_tree *users;
time_t mtime;
} htpasswd_auth_state;
static void htpasswd_clear(auth_t *self) {
htpasswd_auth_state *state = self->state;
free(state->filename);
if (state->users)
avl_tree_free (state->users, _free_user);
thread_rwlock_destroy(&state->file_rwlock);
free(state);
}
static int get_line(FILE *file, char *buf, int len)
{
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;
}
return 0;
}
/* md5 hash */
static char *get_hash(const char *data, int len)
{
struct MD5Context context;
unsigned char digest[16];
MD5Init(&context);
MD5Update(&context, data, len);
MD5Final(digest, &context);
return util_bin_to_hex(digest, 16);
}
#define MAX_LINE_LEN 512
static int compare_users (void *arg, void *a, void *b)
{
htpasswd_user *user1 = (htpasswd_user *)a;
htpasswd_user *user2 = (htpasswd_user *)b;
return strcmp (user1->name, user2->name);
}
static int _free_user (void *key)
{
htpasswd_user *user = (htpasswd_user *)key;
free (user->name); /* ->pass is part of same buffer */
free (user);
return 1;
}
static void htpasswd_recheckfile (htpasswd_auth_state *htpasswd)
{
FILE *passwdfile;
avl_tree *new_users;
int num = 0;
struct stat file_stat;
char *sep;
char line [MAX_LINE_LEN];
if (stat (htpasswd->filename, &file_stat) < 0)
{
WARN1 ("failed to check status of %s", htpasswd->filename);
return;
}
if (file_stat.st_mtime == htpasswd->mtime)
{
/* common case, no update to file */
return;
}
INFO1 ("re-reading htpasswd file \"%s\"", htpasswd->filename);
passwdfile = fopen (htpasswd->filename, "rb");
if (passwdfile == NULL)
{
WARN2("Failed to open authentication database \"%s\": %s",
htpasswd->filename, strerror(errno));
return;
}
htpasswd->mtime = file_stat.st_mtime;
new_users = avl_tree_new (compare_users, NULL);
while (get_line(passwdfile, line, MAX_LINE_LEN))
{
int len;
htpasswd_user *entry;
num++;
if(!line[0] || line[0] == '#')
continue;
sep = strrchr (line, ':');
if (sep == NULL)
{
WARN2("No separator on line %d (%s)", num, htpasswd->filename);
continue;
}
entry = calloc (1, sizeof (htpasswd_user));
len = strlen (line) + 1;
entry->name = malloc (len);
*sep = 0;
memcpy (entry->name, line, len);
entry->pass = entry->name + (sep-line) + 1;
avl_insert (new_users, entry);
}
fclose (passwdfile);
thread_rwlock_wlock (&htpasswd->file_rwlock);
if (htpasswd->users)
avl_tree_free (htpasswd->users, _free_user);
htpasswd->users = new_users;
thread_rwlock_unlock (&htpasswd->file_rwlock);
}
static auth_result htpasswd_auth (auth_client *auth_user)
{
auth_t *auth = auth_user->client->auth;
htpasswd_auth_state *htpasswd = auth->state;
client_t *client = auth_user->client;
htpasswd_user entry;
void *result;
if (client->username == NULL || client->password == NULL)
return AUTH_FAILED;
htpasswd_recheckfile (htpasswd);
thread_rwlock_rlock (&htpasswd->file_rwlock);
entry.name = client->username;
if (avl_get_by_key (htpasswd->users, &entry, &result) == 0)
{
htpasswd_user *found = result;
char *hashed_pw;
thread_rwlock_unlock (&htpasswd->file_rwlock);
hashed_pw = get_hash (client->password, strlen (client->password));
if (strcmp (found->pass, hashed_pw) == 0)
{
free (hashed_pw);
return AUTH_OK;
}
free (hashed_pw);
DEBUG0 ("incorrect password for client");
return AUTH_FAILED;
}
DEBUG1 ("no such username: %s", client->username);
thread_rwlock_unlock (&htpasswd->file_rwlock);
return AUTH_FAILED;
}
int auth_get_htpasswd_auth (auth_t *authenticator, config_options_t *options)
{
htpasswd_auth_state *state;
authenticator->authenticate = htpasswd_auth;
authenticator->free = htpasswd_clear;
authenticator->adduser = htpasswd_adduser;
authenticator->deleteuser = htpasswd_deleteuser;
authenticator->listuser = htpasswd_userlist;
state = calloc(1, sizeof(htpasswd_auth_state));
while(options) {
if(!strcmp(options->name, "filename"))
state->filename = strdup(options->value);
options = options->next;
}
if(!state->filename) {
free(state);
ERROR0("No filename given in options for authenticator.");
return -1;
}
authenticator->state = state;
DEBUG1("Configured htpasswd authentication using password file %s",
state->filename);
thread_rwlock_create(&state->file_rwlock);
htpasswd_recheckfile (state);
return 0;
}
static auth_result htpasswd_adduser (auth_t *auth, const char *username, const char *password)
{
FILE *passwdfile;
char *hashed_password = NULL;
htpasswd_auth_state *state = auth->state;
htpasswd_user entry;
void *result;
htpasswd_recheckfile (state);
thread_rwlock_wlock (&state->file_rwlock);
entry.name = (char*)username;
if (avl_get_by_key (state->users, &entry, &result) == 0)
{
thread_rwlock_unlock (&state->file_rwlock);
return AUTH_USEREXISTS;
}
passwdfile = fopen(state->filename, "ab");
if (passwdfile == NULL)
{
thread_rwlock_unlock (&state->file_rwlock);
WARN2("Failed to open authentication database \"%s\": %s",
state->filename, strerror(errno));
return AUTH_FAILED;
}
hashed_password = get_hash(password, strlen(password));
if (hashed_password) {
fprintf(passwdfile, "%s:%s\n", username, hashed_password);
free(hashed_password);
}
fclose(passwdfile);
thread_rwlock_unlock (&state->file_rwlock);
return AUTH_USERADDED;
}
static auth_result htpasswd_deleteuser(auth_t *auth, const char *username)
{
FILE *passwdfile;
FILE *tmp_passwdfile;
htpasswd_auth_state *state;
char line[MAX_LINE_LEN];
char *sep;
char *tmpfile = NULL;
int tmpfile_len = 0;
struct stat file_info;
state = auth->state;
thread_rwlock_wlock (&state->file_rwlock);
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;
}
tmpfile_len = strlen(state->filename) + 6;
tmpfile = calloc(1, tmpfile_len);
snprintf (tmpfile, tmpfile_len, "%s.tmp", state->filename);
if (stat (tmpfile, &file_info) == 0)
{
WARN1 ("temp file \"%s\" exists, rejecting operation", tmpfile);
free (tmpfile);
fclose (passwdfile);
thread_rwlock_unlock (&state->file_rwlock);
return AUTH_FAILED;
}
tmp_passwdfile = fopen(tmpfile, "wb");
if(tmp_passwdfile == NULL) {
WARN2("Failed to open temporary authentication database \"%s\": %s",
tmpfile, strerror(errno));
fclose(passwdfile);
free(tmpfile);
thread_rwlock_unlock (&state->file_rwlock);
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 did not match on the user, so copy it to the temp file */
/* and put the : back in */
*sep = ':';
fprintf(tmp_passwdfile, "%s\n", line);
}
}
fclose(tmp_passwdfile);
fclose(passwdfile);
/* Now move the contents of the tmp file to the original */
/* Windows won't let us rename a file if the destination file
exists...so, lets remove the original first */
if (remove(state->filename) != 0) {
ERROR3("Problem moving temp authentication file to original \"%s\" - \"%s\": %s",
tmpfile, state->filename, strerror(errno));
}
else {
if (rename(tmpfile, state->filename) != 0) {
ERROR3("Problem moving temp authentication file to original \"%s\" - \"%s\": %s",
tmpfile, state->filename, strerror(errno));
}
}
free(tmpfile);
thread_rwlock_unlock (&state->file_rwlock);
htpasswd_recheckfile (state);
return AUTH_USERDELETED;
}
static auth_result htpasswd_userlist(auth_t *auth, xmlNodePtr srcnode)
{
htpasswd_auth_state *state;
xmlNodePtr newnode;
avl_node *node;
state = auth->state;
htpasswd_recheckfile (state);
thread_rwlock_rlock (&state->file_rwlock);
node = avl_get_first (state->users);
while (node)
{
htpasswd_user *user = (htpasswd_user *)node->key;
newnode = xmlNewChild (srcnode, NULL, "User", NULL);
xmlNewChild(newnode, NULL, "username", user->name);
xmlNewChild(newnode, NULL, "password", user->pass);
node = avl_get_next (node);
}
thread_rwlock_unlock (&state->file_rwlock);
return AUTH_OK;
}
/* 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).
*/
#ifndef __AUTH_HTPASSWD_H__
#define __AUTH_HTPASSWD_H__
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
void auth_get_htpasswd_auth (auth_t *auth, config_options_t *options);
#endif
......@@ -522,8 +522,6 @@ static void _parse_mount(xmlDocPtr doc, xmlNodePtr node,
mount_proxy *mount = calloc(1, sizeof(mount_proxy));
mount_proxy *current = configuration->mounts;
mount_proxy *last=NULL;
xmlNodePtr option;
config_options_t *last_option;
while(current) {
last = current;
......@@ -601,35 +599,7 @@ static void _parse_mount(xmlDocPtr doc, xmlNodePtr node,
if(tmp) xmlFree(tmp);
}
else if (strcmp(node->name, "authentication") == 0) {
mount->auth_type = xmlGetProp(node, "type");
option = node->xmlChildrenNode;
last_option = NULL;
while(option != NULL) {
if(strcmp(option->name, "option") == 0) {
config_options_t *opt = malloc(sizeof(config_options_t));
opt->name = xmlGetProp(option, "name");
if(!opt->name) {