mirror of
https://gitlab.xiph.org/xiph/icecast-server.git
synced 2024-11-03 04:17:17 -05:00
499 lines
16 KiB
C
499 lines
16 KiB
C
/* 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).
|
|
* Copyright 2011-2014, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
|
|
*/
|
|
|
|
/*
|
|
* 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 <config.h>
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#ifndef _WIN32
|
|
# include <sys/wait.h>
|
|
# include <strings.h>
|
|
#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;
|
|
}
|