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

Merge branch 'feature-auth-redirect'

parents 6c491b38 4cb4a9b5
Pipeline #316 failed with stage
in 12 seconds
......@@ -470,7 +470,7 @@ void admin_send_response(xmlDocPtr doc,
config_release_config();
ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template);
xslt_transform(doc, fullpath_xslt_template, client, 200);
xslt_transform(doc, fullpath_xslt_template, client, 200, NULL);
free(fullpath_xslt_template);
}
}
......
......@@ -62,40 +62,43 @@ static unsigned long _next_auth_id(void) {
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)
{
switch (res) {
case AUTH_UNDEFINED:
return "undefined";
break;
case AUTH_OK:
return "ok";
break;
case AUTH_FAILED:
return "failed";
break;
case AUTH_RELEASED:
return "released";
break;
case AUTH_FORBIDDEN:
return "forbidden";
break;
case AUTH_NOMATCH:
return "no match";
break;
case AUTH_USERADDED:
return "user added";
break;
case AUTH_USEREXISTS:
return "user exists";
break;
case AUTH_USERDELETED:
return "user deleted";
break;
default:
return "(unknown)";
break;
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)
......@@ -227,9 +230,11 @@ void auth_addref (auth_t *authenticator) {
static void auth_client_free (auth_client *auth_user)
{
if (auth_user == NULL)
if (!auth_user)
return;
free (auth_user);
free(auth_user->alter_client_arg);
free(auth_user);
}
......@@ -295,6 +300,55 @@ static auth_result auth_remove_client(auth_t *auth, auth_client *auth_user)
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;
......@@ -315,6 +369,11 @@ static void __handle_auth_client (auth_t *auth, auth_client *auth_user) {
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) {
......@@ -582,6 +641,52 @@ static inline int auth_get_authenticator__filter_method(auth_t *auth, xmlNodePtr
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));
......@@ -609,6 +714,9 @@ auth_t *auth_get_authenticator(xmlNodePtr node)
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;
......@@ -680,6 +788,9 @@ auth_t *auth_get_authenticator(xmlNodePtr node)
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_get_authenticator__permission_alter(auth, node, "may-alter", AUTH_MATCHTYPE_MATCH);
auth_get_authenticator__permission_alter(auth, node, "may-not-alter", AUTH_MATCHTYPE_NOMATCH);
/* BEFORE RELEASE 2.5.0 TODO: Migrate this to config_parse_options(). */
option = node->xmlChildrenNode;
while (option)
......@@ -743,6 +854,48 @@ auth_t *auth_get_authenticator(xmlNodePtr node)
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 (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 */
......
......@@ -65,6 +65,23 @@ typedef enum {
AUTH_MATCHTYPE_NOMATCH
} auth_matchtype_t;
typedef enum {
/* Used internally by auth system. */
AUTH_ALTER_NOOP = 0,
/* Internal rewrite of URI */
AUTH_ALTER_REWRITE,
/* Redirect to another location. */
AUTH_ALTER_REDIRECT,
/* See some other resource */
AUTH_ALTER_REDIRECT_SEE_OTHER,
/* This resource is currently located elsewhere */
AUTH_ALTER_REDIRECT_TEMPORARY,
/* This resource is now located at new location */
AUTH_ALTER_REDIRECT_PERMANENT,
/* Send an error report to the client */
AUTH_ALTER_SEND_ERROR
} auth_alter_t;
typedef struct auth_client_tag auth_client;
struct auth_client_tag {
client_t *client;
......@@ -72,6 +89,9 @@ struct auth_client_tag {
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;
void *authbackend_userdata;
auth_alter_t alter_client_action;
char *alter_client_arg;
auth_client *next;
};
......@@ -95,6 +115,9 @@ struct auth_tag
admin_command_id_t command;
} filter_admin[MAX_ADMIN_COMMANDS];
/* permissions */
auth_matchtype_t permission_alter[AUTH_ALTER_SEND_ERROR+1];
/* whether authenticate_client() and release_client() will return immediate.
* Setting this will result in no thread being started for this.
*/
......@@ -141,6 +164,8 @@ int auth_get_htpasswd_auth(auth_t *auth, config_options_t *options);
void auth_initialise(void);
void auth_shutdown(void);
auth_result auth_str2result(const char *str);
auth_t *auth_get_authenticator(xmlNodePtr node);
void auth_release(auth_t *authenticator);
void auth_addref(auth_t *authenticator);
......@@ -154,6 +179,9 @@ void auth_stack_add_client(auth_stack_t *stack,
auth_result result),
void *userdata);
int auth_alter_client(auth_t *auth, auth_client *auth_user, auth_alter_t action, const char *arg);
auth_alter_t auth_str2alter(const char *str);
void auth_stack_release(auth_stack_t *stack);
void auth_stack_addref(auth_stack_t *stack);
int auth_stack_next(auth_stack_t **stack); /* returns -1 on error, 0 on success, +1 if no next element is present */
......
......@@ -20,6 +20,7 @@
#include "auth.h"
#include "cfgfile.h"
#include "client.h"
#include "util.h"
#include "logging.h"
#define CATMODULE "auth_static"
......@@ -27,6 +28,8 @@
typedef struct auth_static {
char *username;
char *password;
auth_alter_t action;
char *arg;
} auth_static_t;
static auth_result static_auth(auth_client *auth_user)
......@@ -45,19 +48,28 @@ static auth_result static_auth(auth_client *auth_user)
if (!client->password)
return AUTH_NOMATCH;
if (strcmp(auth_info->password, client->password) == 0)
return AUTH_OK;
if (strcmp(auth_info->password, client->password) != 0)
return AUTH_FAILED;
return AUTH_FAILED;
if (auth_info->action != AUTH_ALTER_NOOP) {
if (auth_alter_client(auth, auth_user, auth_info->action, auth_info->arg) != 0) {
ICECAST_LOG_ERROR("Can not alter client.");
}
}
return AUTH_OK;
}
static void clear_auth (auth_t *auth)
{
auth_static_t *auth_info = auth->state;
if (auth_info->username)
free(auth_info->username);
if (auth_info->password)
free(auth_info->password);
if (!auth_info)
return;
free(auth_info->username);
free(auth_info->password);
free(auth_info->arg);
free(auth_info);
auth->state = NULL;
}
......@@ -106,6 +118,15 @@ int auth_get_static_auth (auth_t *authenticator, config_options_t *options)
if (auth_info->password)
free(auth_info->password);
auth_info->password = strdup(options->value);
} else if (strcmp(options->name, "action") == 0) {
auth_info->action = auth_str2alter(options->value);
if (auth_info->action == AUTH_ALTER_NOOP) {
ICECAST_LOG_ERROR("Invalid action given.");
clear_auth(authenticator);
return -1;
}
} else if (strcmp(options->name, "argument") == 0) {
replace_string(&(auth_info->arg), options->value);
} else {
ICECAST_LOG_ERROR("Unknown option: %s", options->name);
}
......
......@@ -67,6 +67,7 @@
# define strncasecmp strnicmp
#endif
#include "util.h"
#include "curl.h"
#include "auth.h"
#include "source.h"
......@@ -78,6 +79,16 @@
#include "logging.h"
#define CATMODULE "auth_url"
/* Default headers */
#define DEFAULT_HEADER_OLD_RESULT "icecast-auth-user: 1\r\n"
#define DEFAULT_HEADER_OLD_TIMELIMIT "icecast-auth-timelimit:"
#define DEFAULT_HEADER_OLD_MESSAGE "icecast-auth-message"
#define DEFAULT_HEADER_NEW_RESULT "x-icecast-auth-result"
#define DEFAULT_HEADER_NEW_TIMELIMIT "x-icecast-auth-timelimit"
#define DEFAULT_HEADER_NEW_MESSAGE "x-icecast-auth-message"
#define DEFAULT_HEADER_NEW_ALTER_ACTION "x-icecast-auth-alter-action"
#define DEFAULT_HEADER_NEW_ALTER_ARGUMENT "x-icecast-auth-alter-argument"
typedef struct {
char *pass_headers; // headers passed from client to addurl.
char *prefix_headers; // prefix for passed headers.
......@@ -87,16 +98,37 @@ typedef struct {
char *removeaction;
char *username;
char *password;
/* old style */
char *auth_header;
int auth_header_len;
size_t auth_header_len;
char *timelimit_header;
int timelimit_header_len;
size_t timelimit_header_len;
/* new style */
char *header_auth;
char *header_timelimit;
char *header_message;
char *header_alter_action;
char *header_alter_argument;
char *userpwd;
CURL *handle;
char errormsg[CURL_ERROR_SIZE];
auth_result result;
} auth_url;
typedef struct {
char *all_headers;
size_t all_headers_len;
http_parser_t *parser;
} auth_user_url_t;
static inline const char * __str_or_default(const char *str, const char *def)
{
if (str)
return str;
return def;
}
static void auth_url_clear(auth_t *self)
{
......@@ -116,42 +148,181 @@ static void auth_url_clear(auth_t *self)
free(url->removeaction);
free(url->auth_header);
free(url->timelimit_header);
free(url->header_auth);
free(url->header_timelimit);
free(url->header_message);
free(url->header_alter_action);
free(url->header_alter_argument);
free(url->userpwd);
free(url);
}
static void auth_user_url_clear(auth_client *auth_user)
{
auth_user_url_t *au_url = auth_user->authbackend_userdata;
if (!au_url)
return;
free(au_url->all_headers);
if (au_url->parser)
httpp_destroy(au_url->parser);
free(au_url);
auth_user->authbackend_userdata = NULL;
}
static void handle_returned_header__complete(auth_client *auth_user)
{
auth_user_url_t *au_url = auth_user->authbackend_userdata;
const char *tmp;
const char *action;
const char *argument;
auth_url *url = auth_user->client->auth->state;
if (!au_url)
return;
if (au_url->parser)
return;
au_url->parser = httpp_create_parser();
httpp_initialize(au_url->parser, NULL);
if (!httpp_parse_response(au_url->parser, au_url->all_headers, au_url->all_headers_len, NULL)) {
ICECAST_LOG_ERROR("Can not parse auth backend reply.");
return;
}
tmp = httpp_getvar(au_url->parser, HTTPP_VAR_ERROR_CODE);
if (tmp[0] == '2') {
ICECAST_LOG_DEBUG("Got final status: %#H", tmp);
} else {
ICECAST_LOG_DEBUG("Got non-final status: %#H", tmp);
httpp_destroy(au_url->parser);
au_url->parser = NULL;
au_url->all_headers_len = 0;
return;
}
if (url->header_auth) {
tmp = httpp_getvar(au_url->parser, url->header_auth);
if (tmp) {
url->result = auth_str2result(tmp);
}
}
if (url->header_timelimit) {
tmp = httpp_getvar(au_url->parser, url->header_timelimit);
if (tmp) {
long long int ret;
char *endptr;
errno = 0;
ret = strtoll(tmp, &endptr, 0);
if (endptr != tmp && errno == 0) {
auth_user->client->con->discon_time = time(NULL) + (time_t)ret;
} else {
ICECAST_LOG_ERROR("Auth backend returned invalid new style timelimit header: % #H", tmp);
}
}
}
action = httpp_getvar(au_url->parser, __str_or_default(url->header_alter_action, DEFAULT_HEADER_NEW_ALTER_ACTION));
argument = httpp_getvar(au_url->parser, __str_or_default(url->header_alter_argument, DEFAULT_HEADER_NEW_ALTER_ARGUMENT));
if (action && argument) {
if (auth_alter_client(auth_user->client->auth, auth_user, auth_str2alter(action), argument) != 0) {
ICECAST_LOG_ERROR("Auth backend returned invalid alter action/argument.");
}
} else if (action || argument) {
ICECAST_LOG_ERROR("Auth backend returned incomplete alter action/argument.");
}
if (url->header_message) {
tmp = httpp_getvar(au_url->parser, url->header_message);
} else {
tmp = httpp_getvar(au_url->parser, DEFAULT_HEADER_NEW_MESSAGE);
if (!tmp)
tmp = httpp_getvar(au_url->parser, DEFAULT_HEADER_OLD_MESSAGE);
}
if (tmp) {
snprintf(url->errormsg, sizeof(url->errormsg), "%s", tmp);
}
}
static size_t handle_returned_header(void *ptr,
size_t size,
size_t nmemb,
void *stream)
{
size_t len = size * nmemb;
auth_client *auth_user = stream;
unsigned bytes = size * nmemb;
client_t *client = auth_user->client;
auth_t *auth;
auth_url *url;
if (!client)
return len;
auth = client->auth;
url = auth->state;
if (!auth_user->authbackend_userdata) {
auth_user->authbackend_userdata = calloc(1, sizeof(auth_user_url_t));
}
if (auth_user->authbackend_userdata) {
auth_user_url_t *au_url = auth_user->authbackend_userdata;
char *n = realloc(au_url->all_headers, au_url->all_headers_len + len);
if (n) {
au_url->all_headers = n;
memcpy(n + au_url->all_headers_len, ptr, len);
au_url->all_headers_len += len;
} else {
ICECAST_LOG_ERROR("Can not allocate buffer for auth backend reply headers. BAD.");
}
} else {
ICECAST_LOG_ERROR("Can not allocate authbackend_userdata. BAD.");
}
ICECAST_LOG_DEBUG("Got header: %* #H", (int)(size * nmemb + 2), ptr);
if (client) {
auth_t *auth = client->auth;
auth_url *url = auth->state;
if (strncasecmp(ptr, url->auth_header, url->auth_header_len) == 0)
url->result = AUTH_OK;
if (strncasecmp(ptr, url->timelimit_header,
url->timelimit_header_len) == 0) {
unsigned int limit = 0;
sscanf ((char *)ptr+url->timelimit_header_len, "%u\r\n", &limit);
client->con->discon_time = time(NULL) + limit;
if (url->auth_header && len >= url->auth_header_len && strncasecmp(ptr, url->auth_header, url->auth_header_len) == 0) {
url->result = AUTH_OK;
}
if (url->timelimit_header && len > url->timelimit_header_len && strncasecmp(ptr, url->timelimit_header, url->timelimit_header_len) == 0) {
const char *input = ptr;
unsigned int limit = 0;
if (len >= 2 && input[len - 2] == '\r' && input[len - 1] == '\n') {
input += url->timelimit_header_len;
if (sscanf(input, "%u\r\n", &limit) == 1) {
client->con->discon_time = time(NULL) + limit;
} else {
ICECAST_LOG_ERROR("Auth backend returned invalid timeline header: Can not parse limit");
}
} else {
ICECAST_LOG_ERROR("Auth backend returned invalid timelimit header.");
}
if (strncasecmp (ptr, "icecast-auth-message: ", 22) == 0) {
char *eol;
snprintf(url->errormsg, sizeof(url->errormsg), "%s", (char*)ptr+22);
eol = strchr(url->errormsg, '\r');
if (eol == NULL)
eol = strchr(url->errormsg, '\n');
if (eol)
*eol = '\0';
}
if (len == 1) {
const char *c = ptr;
if (c[0] == '\r' || c[0] == '\n') {
handle_returned_header__complete(auth_user);
}
} else if (len == 2) {
const char *c = ptr;
if ((c[0] == '\r' || c[0] == '\n') && (c[1] == '\r' || c[1] == '\n')) {
handle_returned_header__complete(auth_user);
}
}
return (int)bytes;
return len;
}
static auth_result url_remove_client(auth_client *auth_user)
......@@ -250,6 +421,7 @@ static auth_result url_remove_client(auth_client *auth_user)
url->removeurl, url->errormsg);
free(userpwd);
auth_user_url_clear(auth_user);
return AUTH_OK;
}
......@@ -382,6 +554,7 @@ static auth_result url_add_client(auth_client *auth_user)
res = curl_easy_perform(url->handle);