auth_url.c 16.3 KB
Newer Older
Karl Heyes's avatar
Karl Heyes committed
1 2 3 4 5
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
6
 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org>,
Karl Heyes's avatar
Karl Heyes committed
7 8 9 10
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
 *                      Karl Heyes <karl@xiph.org>
 *                      and others (see AUTHORS for details).
Philipp Schafft's avatar
Philipp Schafft committed
11
 * Copyright 2011-2014, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
Karl Heyes's avatar
Karl Heyes committed
12 13
 */

14
/*
Karl Heyes's avatar
Karl Heyes committed
15 16 17 18 19 20
 * 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
 *
21
 * action=listener_add&client=1&server=host&port=8000&mount=/live&user=fred&pass=mypass&ip=127.0.0.1&agent=""
Karl Heyes's avatar
Karl Heyes committed
22 23 24 25 26 27
 *
 * 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
 *
28 29 30 31 32 33
 * 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
 *
Karl Heyes's avatar
Karl Heyes committed
34 35 36
 * On client disconnection another request can be sent to a URL with the POST
 * information of
 *
37
 * action=listener_remove&server=host&port=8000&client=1&mount=/live&user=fred&pass=mypass&duration=3600
Karl Heyes's avatar
Karl Heyes committed
38 39 40 41 42 43
 *
 * 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
 *
44 45 46 47 48 49 50 51
 * 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.
Karl Heyes's avatar
Karl Heyes committed
52 53 54 55 56 57 58 59 60 61 62
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#ifndef _WIN32
63 64
#   include <sys/wait.h>
#   include <strings.h>
Karl Heyes's avatar
Karl Heyes committed
65
#else
66 67
#   define snprintf _snprintf
#   define strncasecmp strnicmp
Karl Heyes's avatar
Karl Heyes committed
68 69 70 71 72 73 74 75
#endif

#include <curl/curl.h>

#include "auth.h"
#include "source.h"
#include "client.h"
#include "cfgfile.h"
Marvin Scholz's avatar
Marvin Scholz committed
76
#include "common/httpp/httpp.h"
Karl Heyes's avatar
Karl Heyes committed
77 78 79 80 81

#include "logging.h"
#define CATMODULE "auth_url"

typedef struct {
82 83
    char *pass_headers; // headers passed from client to addurl.
    char *prefix_headers; // prefix for passed headers.
Karl Heyes's avatar
Karl Heyes committed
84 85
    char *addurl;
    char *removeurl;
Philipp Schafft's avatar
Philipp Schafft committed
86 87
    char *addaction;
    char *removeaction;
Karl Heyes's avatar
Karl Heyes committed
88 89 90 91
    char *username;
    char *password;
    char *auth_header;
    int  auth_header_len;
92 93
    char *timelimit_header;
    int  timelimit_header_len;
94
    char *userpwd;
Karl Heyes's avatar
Karl Heyes committed
95 96
    CURL *handle;
    char errormsg [CURL_ERROR_SIZE];
Philipp Schafft's avatar
Philipp Schafft committed
97
    auth_result result;
Karl Heyes's avatar
Karl Heyes committed
98 99 100 101
} auth_url;


static void auth_url_clear(auth_t *self)
102 103
{
    auth_url *url;
104

105
    ICECAST_LOG_INFO("Doing auth URL cleanup");
106
    url = self->state;
107
    self->state = NULL;
Philipp Schafft's avatar
Philipp Schafft committed
108 109 110 111 112 113 114 115 116 117 118 119 120
    curl_easy_cleanup(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);
Karl Heyes's avatar
Karl Heyes committed
121 122 123
}


124
#ifdef CURLOPT_PASSWDFUNCTION
125 126 127 128 129 130
/* make sure that prompting at the console does not occur */
static int my_getpass(void *client, char *prompt, char *buffer, int buflen)
{
    buffer[0] = '\0';
    return 0;
}
131
#endif
132 133


134
static size_t handle_returned_header (void *ptr, size_t size, size_t nmemb, void *stream)
Karl Heyes's avatar
Karl Heyes committed
135 136 137 138 139 140 141 142 143 144
{
    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)
Philipp Schafft's avatar
Philipp Schafft committed
145
            url->result = AUTH_OK;
146 147 148 149 150 151
        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;
        }
Karl Heyes's avatar
Karl Heyes committed
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
        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;
}

/* capture returned data, but don't do anything with it */
168
static size_t handle_returned_data (void *ptr, size_t size, size_t nmemb, void *stream)
Karl Heyes's avatar
Karl Heyes committed
169 170 171 172 173
{
    return (int)(size*nmemb);
}


Philipp Schafft's avatar
Philipp Schafft committed
174
static auth_result url_remove_client (auth_client *auth_user)
Karl Heyes's avatar
Karl Heyes committed
175 176 177 178 179 180
{
    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;
181
    const char *mountreq;
Karl Heyes's avatar
Karl Heyes committed
182 183
    ice_config_t *config;
    int port;
184
    char *userpwd = NULL, post [4096];
185 186
    const char *agent;
    char *user_agent, *ipaddr;
Karl Heyes's avatar
Karl Heyes committed
187

188 189
    if (url->removeurl == NULL)
        return AUTH_OK;
190

Karl Heyes's avatar
Karl Heyes committed
191 192 193 194 195
    config = config_get_config ();
    server = util_url_escape (config->hostname);
    port = config->port;
    config_release_config ();

196 197 198 199 200 201
    agent = httpp_getvar (client->parser, "user-agent");
    if (agent)
        user_agent = util_url_escape (agent);
    else
        user_agent = strdup ("-");

Karl Heyes's avatar
Karl Heyes committed
202 203 204 205 206 207 208 209 210 211 212
    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) */
213 214 215 216
    mountreq = httpp_getvar (client->parser, HTTPP_VAR_RAWURI);
    if (mountreq == NULL)
        mountreq = httpp_getvar (client->parser, HTTPP_VAR_URI);
    mount = util_url_escape (mountreq);
217
    ipaddr = util_url_escape (client->con->ip);
Karl Heyes's avatar
Karl Heyes committed
218 219

    snprintf (post, sizeof (post),
Philipp Schafft's avatar
Philipp Schafft committed
220
            "action=%s&server=%s&port=%d&client=%lu&mount=%s"
221
            "&user=%s&pass=%s&duration=%lu&ip=%s&agent=%s",
Philipp Schafft's avatar
Philipp Schafft committed
222
            url->removeaction, /* already escaped */
Karl Heyes's avatar
Karl Heyes committed
223
            server, port, client->con->id, mount, username,
224
            password, (long unsigned)duration, ipaddr, user_agent);
Karl Heyes's avatar
Karl Heyes committed
225 226 227 228
    free (server);
    free (mount);
    free (username);
    free (password);
229 230
    free (ipaddr);
    free (user_agent);
Karl Heyes's avatar
Karl Heyes committed
231

232 233 234 235 236 237 238 239 240
    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)
            {
241
                size_t len = strlen (client->username) + strlen (client->password) + 2;
242 243 244 245 246 247 248 249 250 251 252 253 254
                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, "");
    }
Karl Heyes's avatar
Karl Heyes committed
255 256 257 258 259
    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))
260
        ICECAST_LOG_WARN("auth to server %s failed with %s", url->removeurl, url->errormsg);
Karl Heyes's avatar
Karl Heyes committed
261

262 263
    free (userpwd);

Karl Heyes's avatar
Karl Heyes committed
264 265 266 267
    return AUTH_OK;
}


Philipp Schafft's avatar
Philipp Schafft committed
268
static auth_result url_add_client (auth_client *auth_user)
Karl Heyes's avatar
Karl Heyes committed
269 270 271 272 273
{
    client_t *client = auth_user->client;
    auth_t *auth = client->auth;
    auth_url *url = auth->state;
    int res = 0, port;
274 275 276
    const char *agent;
    char *user_agent, *username, *password;
    const char *mountreq;
Karl Heyes's avatar
Karl Heyes committed
277 278
    char *mount, *ipaddr, *server;
    ice_config_t *config;
279
    char *userpwd = NULL, post [4096];
280 281 282 283
    ssize_t post_offset;
    char *pass_headers, *cur_header, *next_header;
    const char *header_val;
    char *header_valesc;
Karl Heyes's avatar
Karl Heyes committed
284 285 286 287 288 289 290 291

    if (url->addurl == NULL)
        return AUTH_OK;

    config = config_get_config ();
    server = util_url_escape (config->hostname);
    port = config->port;
    config_release_config ();
292

Karl Heyes's avatar
Karl Heyes committed
293
    agent = httpp_getvar (client->parser, "user-agent");
294 295 296 297 298
    if (agent)
        user_agent = util_url_escape (agent);
    else
        user_agent = strdup ("-");

Karl Heyes's avatar
Karl Heyes committed
299
    if (client->username)
300
        username = util_url_escape (client->username);
Karl Heyes's avatar
Karl Heyes committed
301 302
    else
        username = strdup ("");
303

Karl Heyes's avatar
Karl Heyes committed
304
    if (client->password)
305
        password = util_url_escape (client->password);
Karl Heyes's avatar
Karl Heyes committed
306 307 308 309
    else
        password = strdup ("");

    /* get the full uri (with query params if available) */
310 311 312 313
    mountreq = httpp_getvar (client->parser, HTTPP_VAR_RAWURI);
    if (mountreq == NULL)
        mountreq = httpp_getvar (client->parser, HTTPP_VAR_URI);
    mount = util_url_escape (mountreq);
Karl Heyes's avatar
Karl Heyes committed
314 315
    ipaddr = util_url_escape (client->con->ip);

316
    post_offset = snprintf (post, sizeof (post),
Philipp Schafft's avatar
Philipp Schafft committed
317
            "action=%s&server=%s&port=%d&client=%lu&mount=%s"
Karl Heyes's avatar
Karl Heyes committed
318
            "&user=%s&pass=%s&ip=%s&agent=%s",
Philipp Schafft's avatar
Philipp Schafft committed
319
            url->addaction, /* already escaped */
Karl Heyes's avatar
Karl Heyes committed
320 321 322 323 324 325 326 327 328
            server, port, client->con->id, mount, username,
            password, ipaddr, user_agent);
    free (server);
    free (mount);
    free (user_agent);
    free (username);
    free (password);
    free (ipaddr);

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
    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;
        }
    }

358 359 360 361 362 363 364 365 366
    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)
            {
367
                size_t len = strlen (client->username) + strlen (client->password) + 2;
368 369 370 371 372 373 374 375 376 377 378 379 380
                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, "");
    }
Karl Heyes's avatar
Karl Heyes committed
381 382 383 384 385
    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';

Philipp Schafft's avatar
Philipp Schafft committed
386
    url->result = AUTH_FAILED;
Karl Heyes's avatar
Karl Heyes committed
387 388
    res = curl_easy_perform (url->handle);

389 390
    free (userpwd);

Karl Heyes's avatar
Karl Heyes committed
391 392
    if (res)
    {
393
        ICECAST_LOG_WARN("auth to server %s failed with %s", url->addurl, url->errormsg);
Karl Heyes's avatar
Karl Heyes committed
394 395 396
        return AUTH_FAILED;
    }
    /* we received a response, lets see what it is */
Philipp Schafft's avatar
Philipp Schafft committed
397 398
    if (url->result == AUTH_FAILED) {
        ICECAST_LOG_INFO("client auth (%s) failed with \"%s\"", url->addurl, url->errormsg);
399
    }
Philipp Schafft's avatar
Philipp Schafft committed
400
    return url->result;
401 402
}

Karl Heyes's avatar
Karl Heyes committed
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
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;
}

418
int auth_get_url_auth(auth_t *authenticator, config_options_t *options)
Karl Heyes's avatar
Karl Heyes committed
419 420
{
    auth_url *url_info;
Philipp Schafft's avatar
Philipp Schafft committed
421 422
    const char * addaction = "listener_add";
    const char * removeaction = "listener_remove";
Karl Heyes's avatar
Karl Heyes committed
423 424 425 426 427 428 429

    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));
430
    authenticator->state = url_info;
431 432

    /* default headers */
433 434
    url_info->auth_header = strdup("icecast-auth-user: 1\r\n");
    url_info->timelimit_header = strdup("icecast-auth-timelimit:");
Karl Heyes's avatar
Karl Heyes committed
435

436
    /* force auth thread to call function. this makes sure the auth_t is attached to client */
Philipp Schafft's avatar
Philipp Schafft committed
437
    authenticator->authenticate_client = url_add_client;
438

Karl Heyes's avatar
Karl Heyes committed
439
    while(options) {
Philipp Schafft's avatar
Philipp Schafft committed
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
        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);
471
        }
Karl Heyes's avatar
Karl Heyes committed
472 473
        options = options->next;
    }
Philipp Schafft's avatar
Philipp Schafft committed
474 475 476 477

    url_info->addaction = util_url_escape(addaction);
    url_info->removeaction = util_url_escape(removeaction);

Karl Heyes's avatar
Karl Heyes committed
478 479 480
    url_info->handle = curl_easy_init ();
    if (url_info->handle == NULL)
    {
481
        auth_url_clear (authenticator);
Karl Heyes's avatar
Karl Heyes committed
482 483
        return -1;
    }
Philipp Schafft's avatar
Philipp Schafft committed
484

Karl Heyes's avatar
Karl Heyes committed
485 486
    if (url_info->auth_header)
        url_info->auth_header_len = strlen (url_info->auth_header);
487 488
    if (url_info->timelimit_header)
        url_info->timelimit_header_len = strlen (url_info->timelimit_header);
Karl Heyes's avatar
Karl Heyes committed
489 490 491 492 493 494 495

    curl_easy_setopt (url_info->handle, CURLOPT_USERAGENT, ICECAST_VERSION_STRING);
    curl_easy_setopt (url_info->handle, CURLOPT_HEADERFUNCTION, handle_returned_header);
    curl_easy_setopt (url_info->handle, CURLOPT_WRITEFUNCTION, handle_returned_data);
    curl_easy_setopt (url_info->handle, CURLOPT_WRITEDATA, url_info->handle);
    curl_easy_setopt (url_info->handle, CURLOPT_NOSIGNAL, 1L);
    curl_easy_setopt (url_info->handle, CURLOPT_TIMEOUT, 15L);
496
#ifdef CURLOPT_PASSWDFUNCTION
497
    curl_easy_setopt (url_info->handle, CURLOPT_PASSWDFUNCTION, my_getpass);
498
#endif
Karl Heyes's avatar
Karl Heyes committed
499 500
    curl_easy_setopt (url_info->handle, CURLOPT_ERRORBUFFER, &url_info->errormsg[0]);

501 502 503 504 505 506 507
    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);
    }

508
    ICECAST_LOG_INFO("URL based authentication setup");
Karl Heyes's avatar
Karl Heyes committed
509 510
    return 0;
}