diff --git a/src/admin.c b/src/admin.c index 0b8dd023..fcf40bb6 100644 --- a/src/admin.c +++ b/src/admin.c @@ -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); } } diff --git a/src/auth.c b/src/auth.c index f063b701..0002e596 100644 --- a/src/auth.c +++ b/src/auth.c @@ -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 */ diff --git a/src/auth.h b/src/auth.h index 1b877b53..a5bd1c1e 100644 --- a/src/auth.h +++ b/src/auth.h @@ -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 */ diff --git a/src/auth_static.c b/src/auth_static.c index c5ca6880..b5df1020 100644 --- a/src/auth_static.c +++ b/src/auth_static.c @@ -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); } diff --git a/src/auth_url.c b/src/auth_url.c index 7ce87c24..02c21e38 100644 --- a/src/auth_url.c +++ b/src/auth_url.c @@ -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) { - 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 (!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."); } - 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'; + } else { + ICECAST_LOG_ERROR("Can not allocate authbackend_userdata. BAD."); + } + + ICECAST_LOG_DEBUG("Got header: %* #H", (int)(size * nmemb + 2), ptr); + + 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."); } } - return (int)bytes; + 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 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); free(userpwd); + auth_user_url_clear(auth_user); if (res) { ICECAST_LOG_WARN("auth to server %s failed with %s", @@ -427,43 +600,46 @@ int auth_get_url_auth(auth_t *authenticator, config_options_t *options) url_info = calloc(1, sizeof(auth_url)); authenticator->state = url_info; - /* default headers */ - url_info->auth_header = strdup("icecast-auth-user: 1\r\n"); - url_info->timelimit_header = strdup("icecast-auth-timelimit:"); - /* force auth thread to call function. this makes sure the auth_t is attached to client */ authenticator->authenticate_client = url_add_client; while(options) { if(strcmp(options->name, "username") == 0) { - free(url_info->username); - url_info->username = strdup(options->value); + replace_string(&(url_info->username), options->value); } else if(strcmp(options->name, "password") == 0) { - free(url_info->password); - url_info->password = strdup(options->value); + replace_string(&(url_info->password), options->value); } else if(strcmp(options->name, "headers") == 0) { - free(url_info->pass_headers); - url_info->pass_headers = strdup(options->value); + replace_string(&(url_info->pass_headers), options->value); } else if(strcmp(options->name, "header_prefix") == 0) { - free(url_info->prefix_headers); - url_info->prefix_headers = strdup(options->value); + replace_string(&(url_info->prefix_headers), options->value); } else if(strcmp(options->name, "client_add") == 0) { - free(url_info->addurl); - url_info->addurl = strdup(options->value); + replace_string(&(url_info->addurl), options->value); } else if(strcmp(options->name, "client_remove") == 0) { authenticator->release_client = url_remove_client; - free(url_info->removeurl); - url_info->removeurl = strdup(options->value); + replace_string(&(url_info->removeurl), options->value); } else if(strcmp(options->name, "action_add") == 0) { addaction = options->value; } else if(strcmp(options->name, "action_remove") == 0) { removeaction = options->value; } else if(strcmp(options->name, "auth_header") == 0) { - free(url_info->auth_header); - url_info->auth_header = strdup(options->value); + replace_string(&(url_info->auth_header), options->value); } else if (strcmp(options->name, "timelimit_header") == 0) { - free(url_info->timelimit_header); - url_info->timelimit_header = strdup(options->value); + replace_string(&(url_info->timelimit_header), options->value); + } else if (strcmp(options->name, "header_auth") == 0) { + replace_string(&(url_info->header_auth), options->value); + util_strtolower(url_info->header_message); + } else if (strcmp(options->name, "header_timelimit") == 0) { + replace_string(&(url_info->header_timelimit), options->value); + util_strtolower(url_info->header_message); + } else if (strcmp(options->name, "header_message") == 0) { + replace_string(&(url_info->header_message), options->value); + util_strtolower(url_info->header_message); + } else if (strcmp(options->name, "header_alter_action") == 0) { + replace_string(&(url_info->header_alter_action), options->value); + util_strtolower(url_info->header_alter_action); + } else if (strcmp(options->name, "header_alter_argument") == 0) { + replace_string(&(url_info->header_alter_argument), options->value); + util_strtolower(url_info->header_alter_argument); } else { ICECAST_LOG_ERROR("Unknown option: %s", options->name); } @@ -479,6 +655,22 @@ int auth_get_url_auth(auth_t *authenticator, config_options_t *options) return -1; } + /* default headers */ + if (url_info->auth_header) { + ICECAST_LOG_WARN("You use old style auth option \"auth_header\". Please switch to new style option \"header_auth\"."); + } else if (!url_info->header_auth && !url_info->auth_header) { + ICECAST_LOG_WARN("You do not have enabled old or new style auth option for auth status header. I will enable both. Please set \"header_auth\"."); + url_info->auth_header = strdup(DEFAULT_HEADER_OLD_RESULT); + url_info->header_auth = strdup(DEFAULT_HEADER_NEW_RESULT); + } + if (url_info->timelimit_header) { + ICECAST_LOG_WARN("You use old style auth option \"timelimit_header\". Please switch to new style option \"header_timelimit\"."); + } else if (!url_info->header_timelimit && !url_info->timelimit_header) { + ICECAST_LOG_WARN("You do not have enabled old or new style auth option for auth timelimit header. I will enable both. Please set \"header_timelimit\"."); + url_info->timelimit_header = strdup(DEFAULT_HEADER_OLD_TIMELIMIT); + url_info->timelimit_header = strdup(DEFAULT_HEADER_NEW_TIMELIMIT); + } + if (url_info->auth_header) url_info->auth_header_len = strlen (url_info->auth_header); if (url_info->timelimit_header) diff --git a/src/client.c b/src/client.c index 5338568d..3a0ad6f2 100644 --- a/src/client.c +++ b/src/client.c @@ -302,7 +302,7 @@ int client_read_bytes(client_t *client, void *buf, unsigned len) return bytes; } -static inline void _client_send_error(client_t *client, const icecast_error_t *error) +static inline void _client_send_report(client_t *client, const char *uuid, const char *message, int http_status, const char *location) { reportxml_t *report; admin_format_t admin_format; @@ -325,17 +325,15 @@ static inline void _client_send_error(client_t *client, const icecast_error_t *e break; } + report = client_get_reportxml(uuid, NULL, message); - report = client_get_reportxml(error->uuid, NULL, error->message); - - client_send_reportxml(client, report, DOCUMENT_DOMAIN_ADMIN, xslt, admin_format, error->http_status); + client_send_reportxml(client, report, DOCUMENT_DOMAIN_ADMIN, xslt, admin_format, http_status, location); refobject_unref(report); } -void client_send_error_by_id(client_t *client, icecast_error_id_t id) +void client_send_error_by_error(client_t *client, const icecast_error_t *error) { - const icecast_error_t *error = error_get_by_id(id); if (!error) { client_send_500(client, "Unknown error ID"); @@ -347,7 +345,15 @@ void client_send_error_by_id(client_t *client, icecast_error_id_t id) return; } - _client_send_error(client, error); + _client_send_report(client, error->uuid, error->message, error->http_status, NULL); +} +void client_send_error_by_uuid(client_t *client, const char *uuid) +{ + client_send_error_by_error(client, error_get_by_uuid(uuid)); +} +void client_send_error_by_id(client_t *client, icecast_error_id_t id) +{ + client_send_error_by_error(client, error_get_by_id(id)); } void client_send_101(client_t *client, reuse_t reuse) @@ -458,8 +464,13 @@ static inline void client_send_500(client_t *client, const char *message) client_destroy(client); } +void client_send_redirect(client_t *client, const char *uuid, int status, const char *location) +{ + _client_send_report(client, uuid, "Redirecting", status, location); +} + /* this function sends a reportxml file to the client in the prefered format. */ -void client_send_reportxml(client_t *client, reportxml_t *report, document_domain_t domain, const char *xsl, admin_format_t admin_format_hint, int status) +void client_send_reportxml(client_t *client, reportxml_t *report, document_domain_t domain, const char *xsl, admin_format_t admin_format_hint, int status, const char *location) { admin_format_t admin_format; xmlDocPtr doc; @@ -514,13 +525,18 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai if (admin_format == ADMIN_FORMAT_RAW) { xmlChar *buff = NULL; + size_t location_length = 0; int len = 0; size_t buf_len; ssize_t ret; xmlDocDumpMemory(doc, &buff, &len); - buf_len = len + 1024; + if (location) { + location_length = strlen(location); + } + + buf_len = len + location_length + 1024; if (buf_len < 4096) buf_len = 4096; @@ -536,9 +552,9 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai client_send_error_by_id(client, ICECAST_ERROR_GEN_HEADER_GEN_FAILED); xmlFree(buff); return; - } else if (buf_len < (size_t)(len + ret + 64)) { + } else if (buf_len < (size_t)(len + location_length + ret + 128)) { void *new_data; - buf_len = ret + len + 64; + buf_len = ret + len + 128; new_data = realloc(client->refbuf->data, buf_len); if (new_data) { ICECAST_LOG_DEBUG("Client buffer reallocation succeeded."); @@ -563,7 +579,10 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai } /* FIXME: in this section we hope no function will ever return -1 */ - ret += snprintf (client->refbuf->data + ret, buf_len - ret, "Content-Length: %d\r\n\r\n%s", xmlStrlen(buff), buff); + if (location) { + ret += snprintf(client->refbuf->data + ret, buf_len - ret, "Location: %s\r\n", location); + } + ret += snprintf(client->refbuf->data + ret, buf_len - ret, "Content-Length: %d\r\n\r\n%s", xmlStrlen(buff), buff); client->refbuf->len = ret; xmlFree(buff); @@ -598,7 +617,7 @@ void client_send_reportxml(client_t *client, reportxml_t *report, document_domai ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template); fastevent_emit(FASTEVENT_TYPE_CLIENT_SEND_RESPONSE, FASTEVENT_FLAG_MODIFICATION_ALLOWED, FASTEVENT_DATATYPE_CLIENT, client); - xslt_transform(doc, fullpath_xslt_template, client, status); + xslt_transform(doc, fullpath_xslt_template, client, status, location); free(fullpath_xslt_template); } diff --git a/src/client.h b/src/client.h index 9a15e567..89996809 100644 --- a/src/client.h +++ b/src/client.h @@ -143,10 +143,12 @@ int client_create (client_t **c_ptr, connection_t *con, http_parser_t *parser); void client_complete(client_t *client); void client_destroy(client_t *client); void client_send_error_by_id(client_t *client, icecast_error_id_t id); +void client_send_error_by_uuid(client_t *client, const char *uuid); void client_send_101(client_t *client, reuse_t reuse); void client_send_204(client_t *client); void client_send_426(client_t *client, reuse_t reuse); -void client_send_reportxml(client_t *client, reportxml_t *report, document_domain_t domain, const char *xsl, admin_format_t admin_format_hint, int status); +void client_send_redirect(client_t *client, const char *uuid, int status, const char *location); +void client_send_reportxml(client_t *client, reportxml_t *report, document_domain_t domain, const char *xsl, admin_format_t admin_format_hint, int status, const char *location); reportxml_t *client_get_reportxml(const char *state_definition, const char *state_akindof, const char *state_text); admin_format_t client_get_admin_format_by_content_negotiation(client_t *client); int client_send_bytes (client_t *client, const void *buf, unsigned len); diff --git a/src/connection.c b/src/connection.c index 63feffe6..b96bb651 100644 --- a/src/connection.c +++ b/src/connection.c @@ -105,6 +105,7 @@ static matchfile_t *banned_ip, *allowed_ip; rwlock_t _source_shutdown_rwlock; +static int _update_admin_command(client_t *client); static void _handle_connection(void); static void get_tls_certificate(ice_config_t *config); @@ -1277,6 +1278,10 @@ static void _handle_authed_client(client_t *client, void *userdata, auth_result auth_stack_release(client->authstack); client->authstack = NULL; + /* Update admin parameters just in case auth changed our URI */ + if (_update_admin_command(client) == -1) + return; + fastevent_emit(FASTEVENT_TYPE_CLIENT_AUTHED, FASTEVENT_FLAG_MODIFICATION_ALLOWED, FASTEVENT_DATATYPE_CLIENT, client); if (result != AUTH_OK) { diff --git a/src/errors.c b/src/errors.c index d553a523..899ed7ce 100644 --- a/src/errors.c +++ b/src/errors.c @@ -10,6 +10,8 @@ #include #endif +#include + #include "errors.h" #include "logging.h" #define CATMODULE "errors" @@ -150,3 +152,15 @@ const icecast_error_t * error_get_by_id(icecast_error_id_t id) { return NULL; } +const icecast_error_t * error_get_by_uuid(const char *uuid) +{ + size_t i; + + for (i = 0; i < (sizeof(__errors)/sizeof(*__errors)); i++) { + if (strcasecmp(__errors[i].uuid, uuid) == 0) { + return &(__errors[i]); + } + } + + return NULL; +} diff --git a/src/errors.h b/src/errors.h index babfed91..1a6828c5 100644 --- a/src/errors.h +++ b/src/errors.h @@ -62,5 +62,6 @@ struct icecast_error_tag { typedef struct icecast_error_tag icecast_error_t; const icecast_error_t * error_get_by_id(icecast_error_id_t id); +const icecast_error_t * error_get_by_uuid(const char *uuid); #endif /* __ERRORS_H__ */ diff --git a/src/stats.c b/src/stats.c index 61871caf..715f22c3 100644 --- a/src/stats.c +++ b/src/stats.c @@ -1030,7 +1030,7 @@ void stats_transform_xslt(client_t *client) doc = stats_get_xml(0, mount, client); - xslt_transform(doc, xslpath, client, 200); + xslt_transform(doc, xslpath, client, 200, NULL); xmlFreeDoc(doc); free(xslpath); diff --git a/src/util.c b/src/util.c index a0e11b69..cd510fb0 100644 --- a/src/util.c +++ b/src/util.c @@ -17,6 +17,7 @@ #endif #include +#include #include #include #include @@ -1429,3 +1430,35 @@ int get_line(FILE *file, char *buf, size_t siz) } return 0; } + +int replace_string(char **dst, const char *src) +{ + char *n; + + if (!dst) + return -1; + + if (src) { + n = strdup(src); + if (!n) + return -1; + } else { + n = NULL; + } + + free(*dst); + *dst = n; + + return 0; +} + +int util_strtolower(char *str) +{ + if (!str) + return -1; + + for (; *str; str++) + *str = tolower(*str); + + return 0; +} diff --git a/src/util.h b/src/util.h index dc0c461e..973c27ca 100644 --- a/src/util.h +++ b/src/util.h @@ -128,4 +128,7 @@ struct tm *localtime_r(const time_t *timep, struct tm *result); char *util_conv_string (const char *string, const char *in_charset, const char *out_charset); int get_line(FILE *file, char *buf, size_t siz); + +int replace_string(char **dst, const char *src); +int util_strtolower(char *str); #endif /* __UTIL_H__ */ diff --git a/src/xslt.c b/src/xslt.c index b1c4508a..aa4e1ec5 100644 --- a/src/xslt.c +++ b/src/xslt.c @@ -320,7 +320,7 @@ static inline void _send_error(client_t *client, icecast_error_id_t id, int old_ client_send_error_by_id(client, id); } -void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client, int status) +void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client, int status, const char *location) { xmlDocPtr res; xsltStylesheetPtr cur; @@ -374,7 +374,14 @@ void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client, in ssize_t ret; int failed = 0; refbuf_t *refbuf; + size_t location_length = 0; ssize_t full_len = strlen(mediatype) + (ssize_t)len + (ssize_t)1024; + + if (location) { + location_length = strlen(location); + full_len += location_length; + } + if (full_len < 4096) full_len = 4096; refbuf = refbuf_new (full_len); @@ -386,9 +393,9 @@ void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client, in ICECAST_LOG_ERROR("Dropping client as we can not build response headers."); _send_error(client, ICECAST_ERROR_GEN_HEADER_GEN_FAILED, status); } else { - if ( full_len < (ret + (ssize_t)len + (ssize_t)64) ) { + if ( full_len < (ret + (ssize_t)len + (ssize_t)128) ) { void *new_data; - full_len = ret + (ssize_t)len + (ssize_t)64; + full_len = ret + (ssize_t)len + (ssize_t)128; new_data = realloc(refbuf->data, full_len); if (new_data) { ICECAST_LOG_DEBUG("Client buffer reallocation succeeded."); @@ -408,7 +415,11 @@ void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client, in } if (!failed) { - snprintf(refbuf->data + ret, full_len - ret, "Content-Length: %d\r\n\r\n%s", len, string); + /* FIXME: in this section we hope no function will ever return -1 */ + if (location) { + ret += snprintf(refbuf->data + ret, full_len - ret, "Location: %s\r\n", location); + } + ret += snprintf(refbuf->data + ret, full_len - ret, "Content-Length: %d\r\n\r\n%s", len, string); client->respcode = status; client_set_queue (client, NULL); diff --git a/src/xslt.h b/src/xslt.h index c5d68e10..2fe5fd53 100644 --- a/src/xslt.h +++ b/src/xslt.h @@ -16,7 +16,7 @@ #include "icecasttypes.h" -void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client, int status); +void xslt_transform(xmlDocPtr doc, const char *xslfilename, client_t *client, int status, const char *location); void xslt_initialize(void); void xslt_shutdown(void); void xslt_clear_cache(void);