/* 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 , * Michael Smith , * oddsock , * Karl Heyes * and others (see AUTHORS for details). * Copyright 2011-2014, Philipp "ph3-der-loewe" Schafft , */ /* * Client authentication via URL functions * * authenticate user via a URL, this is done via libcurl so https can also * be handled. The request will have POST information about the request in * the form of * * action=listener_add&client=1&server=host&port=8000&mount=/live&user=fred&pass=mypass&ip=127.0.0.1&agent="" * * For a user to be accecpted the following HTTP header needs * to be returned (the actual string can be specified in the xml file) * * icecast-auth-user: 1 * * A listening client may also be configured as only to stay connected for a * certain length of time. eg The auth server may only allow a 15 minute * playback by sending back. * * icecast-auth-timelimit: 900 * * On client disconnection another request can be sent to a URL with the POST * information of * * action=listener_remove&server=host&port=8000&client=1&mount=/live&user=fred&pass=mypass&duration=3600 * * client refers to the icecast client identification number. mount refers * to the mountpoint (beginning with / and may contain query parameters eg ?& * encoded) and duration is the amount of time in seconds. user and pass * setting can be blank * * On source client connection, a request can be made to trigger a URL request * to verify the details externally. Post info is * * action=stream_auth&mount=/stream&ip=IP&server=SERVER&port=8000&user=fred&pass=pass * * As admin requests can come in for a stream (eg metadata update) these requests * can be issued while stream is active. For these &admin=1 is added to the POST * details. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #ifndef _WIN32 # include # include #else # define snprintf _snprintf # define strncasecmp strnicmp #endif #include "curl.h" #include "auth.h" #include "source.h" #include "client.h" #include "cfgfile.h" #include "connection.h" #include "common/httpp/httpp.h" #include "logging.h" #define CATMODULE "auth_url" typedef struct { char *pass_headers; // headers passed from client to addurl. char *prefix_headers; // prefix for passed headers. char *addurl; char *removeurl; char *addaction; char *removeaction; char *username; char *password; char *auth_header; int auth_header_len; char *timelimit_header; int timelimit_header_len; char *userpwd; CURL *handle; char errormsg[CURL_ERROR_SIZE]; auth_result result; } auth_url; static void auth_url_clear(auth_t *self) { auth_url *url; ICECAST_LOG_INFO("Doing auth URL cleanup"); url = self->state; self->state = NULL; icecast_curl_free(url->handle); free(url->username); free(url->password); free(url->pass_headers); free(url->prefix_headers); free(url->removeurl); free(url->addurl); free(url->addaction); free(url->removeaction); free(url->auth_header); free(url->timelimit_header); free(url->userpwd); free(url); } static size_t handle_returned_header(void *ptr, size_t size, size_t nmemb, void *stream) { auth_client *auth_user = stream; unsigned bytes = size * nmemb; client_t *client = auth_user->client; 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 (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'; } } return (int)bytes; } static auth_result url_remove_client(auth_client *auth_user) { client_t *client = auth_user->client; auth_t *auth = client->auth; auth_url *url = auth->state; time_t duration = time(NULL) - client->con->con_time; char *username, *password, *mount, *server; const char *mountreq; ice_config_t *config; int port; char *userpwd = NULL, post[4096]; const char *agent; char *user_agent, *ipaddr; if (url->removeurl == NULL) return AUTH_OK; config = config_get_config(); server = util_url_escape(config->hostname); port = config->port; config_release_config(); agent = httpp_getvar(client->parser, "user-agent"); if (agent) { user_agent = util_url_escape(agent); } else { user_agent = strdup("-"); } if (client->username) { username = util_url_escape(client->username); } else { username = strdup(""); } if (client->password) { password = util_url_escape(client->password); } else { password = strdup(""); } /* get the full uri (with query params if available) */ mountreq = httpp_getvar(client->parser, HTTPP_VAR_RAWURI); if (mountreq == NULL) mountreq = httpp_getvar(client->parser, HTTPP_VAR_URI); mount = util_url_escape(mountreq); ipaddr = util_url_escape(client->con->ip); snprintf(post, sizeof (post), "action=%s&server=%s&port=%d&client=%lu&mount=%s" "&user=%s&pass=%s&duration=%lu&ip=%s&agent=%s", url->removeaction, /* already escaped */ server, port, client->con->id, mount, username, password, (long unsigned)duration, ipaddr, user_agent); free(server); free(mount); free(username); free(password); free(ipaddr); free(user_agent); if (strchr (url->removeurl, '@') == NULL) { if (url->userpwd) { curl_easy_setopt(url->handle, CURLOPT_USERPWD, url->userpwd); } else { /* auth'd requests may not have a user/pass, but may use query args */ if (client->username && client->password) { size_t len = strlen(client->username) + strlen(client->password) + 2; userpwd = malloc(len); snprintf(userpwd, len, "%s:%s", client->username, client->password); curl_easy_setopt(url->handle, CURLOPT_USERPWD, userpwd); } else { curl_easy_setopt(url->handle, CURLOPT_USERPWD, ""); } } } else { /* url has user/pass but libcurl may need to clear any existing settings */ curl_easy_setopt(url->handle, CURLOPT_USERPWD, ""); } curl_easy_setopt(url->handle, CURLOPT_URL, url->removeurl); curl_easy_setopt(url->handle, CURLOPT_POSTFIELDS, post); curl_easy_setopt(url->handle, CURLOPT_WRITEHEADER, auth_user); if (curl_easy_perform (url->handle)) ICECAST_LOG_WARN("auth to server %s failed with %s", url->removeurl, url->errormsg); free(userpwd); return AUTH_OK; } static auth_result url_add_client(auth_client *auth_user) { client_t *client = auth_user->client; auth_t *auth = client->auth; auth_url *url = auth->state; int res = 0, port; const char *agent; char *user_agent, *username, *password; const char *mountreq; char *mount, *ipaddr, *server; ice_config_t *config; char *userpwd = NULL, post [4096]; ssize_t post_offset; char *pass_headers, *cur_header, *next_header; const char *header_val; char *header_valesc; if (url->addurl == NULL) return AUTH_OK; config = config_get_config(); server = util_url_escape(config->hostname); port = config->port; config_release_config(); agent = httpp_getvar(client->parser, "user-agent"); if (agent) { user_agent = util_url_escape(agent); } else { user_agent = strdup("-"); } if (client->username) { username = util_url_escape(client->username); } else { username = strdup(""); } if (client->password) { password = util_url_escape(client->password); } else { password = strdup(""); } /* get the full uri (with query params if available) */ mountreq = httpp_getvar(client->parser, HTTPP_VAR_RAWURI); if (mountreq == NULL) mountreq = httpp_getvar(client->parser, HTTPP_VAR_URI); mount = util_url_escape(mountreq); ipaddr = util_url_escape(client->con->ip); post_offset = snprintf(post, sizeof (post), "action=%s&server=%s&port=%d&client=%lu&mount=%s" "&user=%s&pass=%s&ip=%s&agent=%s", url->addaction, /* already escaped */ server, port, client->con->id, mount, username, password, ipaddr, user_agent); free(server); free(mount); free(user_agent); free(username); free(password); free(ipaddr); pass_headers = NULL; if (url->pass_headers) pass_headers = strdup(url->pass_headers); if (pass_headers) { cur_header = pass_headers; while (cur_header) { next_header = strstr(cur_header, ","); if (next_header) { *next_header=0; next_header++; } header_val = httpp_getvar (client->parser, cur_header); if (header_val) { header_valesc = util_url_escape (header_val); post_offset += snprintf(post + post_offset, sizeof(post) - post_offset, "&%s%s=%s", url->prefix_headers ? url->prefix_headers : "", cur_header, header_valesc); free(header_valesc); } cur_header = next_header; } } if (strchr(url->addurl, '@') == NULL) { if (url->userpwd) { curl_easy_setopt(url->handle, CURLOPT_USERPWD, url->userpwd); } else { /* auth'd requests may not have a user/pass, but may use query args */ if (client->username && client->password) { size_t len = strlen(client->username) + strlen(client->password) + 2; userpwd = malloc (len); snprintf(userpwd, len, "%s:%s", client->username, client->password); curl_easy_setopt(url->handle, CURLOPT_USERPWD, userpwd); } else { curl_easy_setopt (url->handle, CURLOPT_USERPWD, ""); } } } else { /* url has user/pass but libcurl may need to clear any existing settings */ curl_easy_setopt(url->handle, CURLOPT_USERPWD, ""); } curl_easy_setopt(url->handle, CURLOPT_URL, url->addurl); curl_easy_setopt(url->handle, CURLOPT_POSTFIELDS, post); curl_easy_setopt(url->handle, CURLOPT_WRITEHEADER, auth_user); url->errormsg[0] = '\0'; url->result = AUTH_FAILED; res = curl_easy_perform(url->handle); free(userpwd); if (res) { ICECAST_LOG_WARN("auth to server %s failed with %s", url->addurl, url->errormsg); return AUTH_FAILED; } /* we received a response, lets see what it is */ if (url->result == AUTH_FAILED) { ICECAST_LOG_INFO("client auth (%s) failed with \"%s\"", url->addurl, url->errormsg); } return url->result; } static auth_result auth_url_adduser(auth_t *auth, const char *username, const char *password) { return AUTH_FAILED; } static auth_result auth_url_deleteuser(auth_t *auth, const char *username) { return AUTH_FAILED; } static auth_result auth_url_listuser(auth_t *auth, xmlNodePtr srcnode) { return AUTH_FAILED; } int auth_get_url_auth(auth_t *authenticator, config_options_t *options) { auth_url *url_info; const char *addaction = "listener_add"; const char *removeaction = "listener_remove"; authenticator->free = auth_url_clear; authenticator->adduser = auth_url_adduser; authenticator->deleteuser = auth_url_deleteuser; authenticator->listuser = auth_url_listuser; 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); } else if(strcmp(options->name, "password") == 0) { free(url_info->password); url_info->password = strdup(options->value); } else if(strcmp(options->name, "headers") == 0) { free(url_info->pass_headers); url_info->pass_headers = strdup(options->value); } else if(strcmp(options->name, "header_prefix") == 0) { free(url_info->prefix_headers); url_info->prefix_headers = strdup(options->value); } else if(strcmp(options->name, "client_add") == 0) { free(url_info->addurl); url_info->addurl = strdup(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); } 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); } else if (strcmp(options->name, "timelimit_header") == 0) { free(url_info->timelimit_header); url_info->timelimit_header = strdup(options->value); } else { ICECAST_LOG_ERROR("Unknown option: %s", options->name); } options = options->next; } url_info->addaction = util_url_escape(addaction); url_info->removeaction = util_url_escape(removeaction); url_info->handle = icecast_curl_new(NULL, &url_info->errormsg[0]); if (url_info->handle == NULL) { auth_url_clear(authenticator); return -1; } if (url_info->auth_header) url_info->auth_header_len = strlen (url_info->auth_header); if (url_info->timelimit_header) url_info->timelimit_header_len = strlen (url_info->timelimit_header); curl_easy_setopt(url_info->handle, CURLOPT_HEADERFUNCTION, handle_returned_header); if (url_info->username && url_info->password) { int len = strlen(url_info->username) + strlen(url_info->password) + 2; url_info->userpwd = malloc(len); snprintf(url_info->userpwd, len, "%s:%s", url_info->username, url_info->password); } ICECAST_LOG_INFO("URL based authentication setup"); return 0; }