auth_url.c 24.6 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).
11
 * Copyright 2011-2018, 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
#endif

70
#include "util.h"
71
#include "curl.h"
Karl Heyes's avatar
Karl Heyes committed
72 73 74 75
#include "auth.h"
#include "source.h"
#include "client.h"
#include "cfgfile.h"
76
#include "connection.h"
Marvin Scholz's avatar
Marvin Scholz committed
77

78
#include <igloo/httpp.h>
Karl Heyes's avatar
Karl Heyes committed
79 80 81 82

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

83
/* Default headers */
84 85 86 87 88 89 90 91
#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"
92

Karl Heyes's avatar
Karl Heyes committed
93
typedef struct {
Marvin Scholz's avatar
Marvin Scholz committed
94 95 96 97 98 99 100 101
    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;
102 103

    /* old style */
Marvin Scholz's avatar
Marvin Scholz committed
104
    char       *auth_header;
105
    size_t      auth_header_len;
Marvin Scholz's avatar
Marvin Scholz committed
106
    char       *timelimit_header;
107 108 109 110
    size_t      timelimit_header_len;
    /* new style */
    char       *header_auth;
    char       *header_timelimit;
111
    char       *header_message;
112 113
    char       *header_alter_action;
    char       *header_alter_argument;
114

Marvin Scholz's avatar
Marvin Scholz committed
115 116 117
    char       *userpwd;
    CURL       *handle;
    char        errormsg[CURL_ERROR_SIZE];
Philipp Schafft's avatar
Philipp Schafft committed
118
    auth_result result;
Karl Heyes's avatar
Karl Heyes committed
119 120
} auth_url;

121 122 123
typedef struct {
    char *all_headers;
    size_t all_headers_len;
124
    igloo_http_parser_t *parser;
125
} auth_user_url_t;
Karl Heyes's avatar
Karl Heyes committed
126

127 128 129 130 131 132 133
static inline const char * __str_or_default(const char *str, const char *def)
{
    if (str)
        return str;
    return def;
}

Karl Heyes's avatar
Karl Heyes committed
134
static void auth_url_clear(auth_t *self)
135 136
{
    auth_url *url;
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
137

138
    ICECAST_LOG_INFO("Doing auth URL cleanup");
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
139
    url = self->state;
140
    self->state = NULL;
141
    icecast_curl_free(url->handle);
Philipp Schafft's avatar
Philipp Schafft committed
142 143 144 145 146 147 148 149 150 151
    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);
152 153
    free(url->header_auth);
    free(url->header_timelimit);
154
    free(url->header_message);
155 156
    free(url->header_alter_action);
    free(url->header_alter_argument);
Philipp Schafft's avatar
Philipp Schafft committed
157 158
    free(url->userpwd);
    free(url);
Karl Heyes's avatar
Karl Heyes committed
159 160
}

161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
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;
180 181
    const char *action;
    const char *argument;
182 183 184 185 186 187 188 189
    auth_url *url = auth_user->client->auth->state;

    if (!au_url)
        return;

    if (au_url->parser)
        return;

190 191
    au_url->parser = igloo_httpp_create_parser();
    igloo_httpp_initialize(au_url->parser, NULL);
192

193
    if (!igloo_httpp_parse_response(au_url->parser, au_url->all_headers, au_url->all_headers_len, NULL)) {
194 195 196 197
        ICECAST_LOG_ERROR("Can not parse auth backend reply.");
        return;
    }

198
    tmp = igloo_httpp_getvar(au_url->parser, igloo_HTTPP_VAR_ERROR_CODE);
199 200 201 202 203 204 205 206 207 208 209
    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) {
210
        tmp = igloo_httpp_getvar(au_url->parser, url->header_auth);
211 212 213 214 215 216
        if (tmp) {
            url->result = auth_str2result(tmp);
        }
    }

    if (url->header_timelimit) {
217
        tmp = igloo_httpp_getvar(au_url->parser, url->header_timelimit);
218 219 220 221 222 223 224 225 226 227 228 229 230 231
        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);
            }
        }
    }

232 233
    action   = igloo_httpp_getvar(au_url->parser, __str_or_default(url->header_alter_action, DEFAULT_HEADER_NEW_ALTER_ACTION));
    argument = igloo_httpp_getvar(au_url->parser, __str_or_default(url->header_alter_argument, DEFAULT_HEADER_NEW_ALTER_ARGUMENT));
234 235 236 237 238 239 240 241 242

    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.");
    }

243
    if (url->header_message) {
244
        tmp = igloo_httpp_getvar(au_url->parser, url->header_message);
245
    } else {
246
        tmp = igloo_httpp_getvar(au_url->parser, DEFAULT_HEADER_NEW_MESSAGE);
247
        if (!tmp)
248
            tmp = igloo_httpp_getvar(au_url->parser, DEFAULT_HEADER_OLD_MESSAGE);
249
    }
250 251 252 253 254
    if (tmp) {
        snprintf(url->errormsg, sizeof(url->errormsg), "%s", tmp);
    }
}

Marvin Scholz's avatar
Marvin Scholz committed
255 256 257 258
static size_t handle_returned_header(void      *ptr,
                                     size_t    size,
                                     size_t    nmemb,
                                     void      *stream)
Karl Heyes's avatar
Karl Heyes committed
259
{
260
    size_t len = size * nmemb;
Karl Heyes's avatar
Karl Heyes committed
261 262
    auth_client *auth_user = stream;
    client_t *client = auth_user->client;
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    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.");
    }
Karl Heyes's avatar
Karl Heyes committed
289

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
    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.");
        }
    }

    if (len == 1) {
        const char *c = ptr;
        if (c[0] == '\r' || c[0] == '\n') {
            handle_returned_header__complete(auth_user);
317
        }
318 319 320 321
    } 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);
Karl Heyes's avatar
Karl Heyes committed
322 323 324
        }
    }

325 326

    return len;
Karl Heyes's avatar
Karl Heyes committed
327 328
}

Marvin Scholz's avatar
Marvin Scholz committed
329
static auth_result url_remove_client(auth_client *auth_user)
Karl Heyes's avatar
Karl Heyes committed
330
{
Marvin Scholz's avatar
Marvin Scholz committed
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
    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;
347
    int             ret;
Karl Heyes's avatar
Karl Heyes committed
348

349 350
    if (url->removeurl == NULL)
        return AUTH_OK;
351

Marvin Scholz's avatar
Marvin Scholz committed
352 353
    config = config_get_config();
    server = util_url_escape(config->hostname);
Karl Heyes's avatar
Karl Heyes committed
354
    port = config->port;
Marvin Scholz's avatar
Marvin Scholz committed
355
    config_release_config();
Karl Heyes's avatar
Karl Heyes committed
356

357
    agent = igloo_httpp_getvar(client->parser, "user-agent");
Marvin Scholz's avatar
Marvin Scholz committed
358 359 360 361 362
    if (agent) {
        user_agent = util_url_escape(agent);
    } else {
        user_agent = strdup("-");
    }
363

Marvin Scholz's avatar
Marvin Scholz committed
364 365 366 367 368
    if (client->username) {
        username = util_url_escape(client->username);
    } else {
        username = strdup("");
    }
Karl Heyes's avatar
Karl Heyes committed
369

Marvin Scholz's avatar
Marvin Scholz committed
370 371 372 373 374
    if (client->password) {
        password = util_url_escape(client->password);
    } else {
        password = strdup("");
    }
Karl Heyes's avatar
Karl Heyes committed
375 376

    /* get the full uri (with query params if available) */
377
    mountreq = igloo_httpp_getvar(client->parser, igloo_HTTPP_VAR_RAWURI);
378
    if (mountreq == NULL)
379
        mountreq = igloo_httpp_getvar(client->parser, igloo_HTTPP_VAR_URI);
Marvin Scholz's avatar
Marvin Scholz committed
380 381
    mount = util_url_escape(mountreq);
    ipaddr = util_url_escape(client->con->ip);
Karl Heyes's avatar
Karl Heyes committed
382

383
    ret = snprintf(post, sizeof(post),
Philipp Schafft's avatar
Philipp Schafft committed
384
            "action=%s&server=%s&port=%d&client=%lu&mount=%s"
385
            "&user=%s&pass=%s&duration=%lu&ip=%s&agent=%s",
Philipp Schafft's avatar
Philipp Schafft committed
386
            url->removeaction, /* already escaped */
Karl Heyes's avatar
Karl Heyes committed
387
            server, port, client->con->id, mount, username,
388
            password, (long unsigned)duration, ipaddr, user_agent);
Marvin Scholz's avatar
Marvin Scholz committed
389 390 391 392 393 394 395 396

    free(server);
    free(mount);
    free(username);
    free(password);
    free(ipaddr);
    free(user_agent);

397 398 399 400 401 402
    if (ret <= 0 || ret >= (ssize_t)sizeof(post)) {
        ICECAST_LOG_ERROR("Authentication failed for client %p as header POST data is too long.", client);
        auth_user_url_clear(auth_user);
        return AUTH_FAILED;
    }

Marvin Scholz's avatar
Marvin Scholz committed
403 404 405 406
    if (strchr (url->removeurl, '@') == NULL) {
        if (url->userpwd) {
            curl_easy_setopt(url->handle, CURLOPT_USERPWD, url->userpwd);
        } else {
407
            /* auth'd requests may not have a user/pass, but may use query args */
Marvin Scholz's avatar
Marvin Scholz committed
408 409 410 411 412 413 414 415 416
            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, "");
417 418
            }
        }
Marvin Scholz's avatar
Marvin Scholz committed
419
    } else {
420
        /* url has user/pass but libcurl may need to clear any existing settings */
Marvin Scholz's avatar
Marvin Scholz committed
421
        curl_easy_setopt(url->handle, CURLOPT_USERPWD, "");
422
    }
Marvin Scholz's avatar
Marvin Scholz committed
423 424 425
    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);
Karl Heyes's avatar
Karl Heyes committed
426 427

    if (curl_easy_perform (url->handle))
Marvin Scholz's avatar
Marvin Scholz committed
428 429
        ICECAST_LOG_WARN("auth to server %s failed with %s",
            url->removeurl, url->errormsg);
Karl Heyes's avatar
Karl Heyes committed
430

Marvin Scholz's avatar
Marvin Scholz committed
431
    free(userpwd);
432
    auth_user_url_clear(auth_user);
433

Karl Heyes's avatar
Karl Heyes committed
434 435 436 437
    return AUTH_OK;
}


Marvin Scholz's avatar
Marvin Scholz committed
438
static auth_result url_add_client(auth_client *auth_user)
Karl Heyes's avatar
Karl Heyes committed
439
{
Marvin Scholz's avatar
Marvin Scholz committed
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
    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;
Karl Heyes's avatar
Karl Heyes committed
461 462 463 464

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

Marvin Scholz's avatar
Marvin Scholz committed
465 466
    config = config_get_config();
    server = util_url_escape(config->hostname);
Karl Heyes's avatar
Karl Heyes committed
467
    port = config->port;
Marvin Scholz's avatar
Marvin Scholz committed
468
    config_release_config();
469

470
    agent = igloo_httpp_getvar(client->parser, "user-agent");
Marvin Scholz's avatar
Marvin Scholz committed
471 472 473 474 475
    if (agent) {
        user_agent = util_url_escape(agent);
    } else {
        user_agent = strdup("-");
    }
476

Marvin Scholz's avatar
Marvin Scholz committed
477 478 479 480 481
    if (client->username) {
        username = util_url_escape(client->username);
    } else {
        username = strdup("");
    }
482

Marvin Scholz's avatar
Marvin Scholz committed
483 484 485 486 487
    if (client->password) {
        password = util_url_escape(client->password);
    } else {
        password = strdup("");
    }
Karl Heyes's avatar
Karl Heyes committed
488 489

    /* get the full uri (with query params if available) */
490
    mountreq = igloo_httpp_getvar(client->parser, igloo_HTTPP_VAR_RAWURI);
491
    if (mountreq == NULL)
492
        mountreq = igloo_httpp_getvar(client->parser, igloo_HTTPP_VAR_URI);
Marvin Scholz's avatar
Marvin Scholz committed
493 494
    mount = util_url_escape(mountreq);
    ipaddr = util_url_escape(client->con->ip);
Karl Heyes's avatar
Karl Heyes committed
495

Marvin Scholz's avatar
Marvin Scholz committed
496
    post_offset = snprintf(post, sizeof (post),
Philipp Schafft's avatar
Philipp Schafft committed
497
            "action=%s&server=%s&port=%d&client=%lu&mount=%s"
Karl Heyes's avatar
Karl Heyes committed
498
            "&user=%s&pass=%s&ip=%s&agent=%s",
Philipp Schafft's avatar
Philipp Schafft committed
499
            url->addaction, /* already escaped */
Karl Heyes's avatar
Karl Heyes committed
500 501
            server, port, client->con->id, mount, username,
            password, ipaddr, user_agent);
Marvin Scholz's avatar
Marvin Scholz committed
502 503 504 505 506 507 508

    free(server);
    free(mount);
    free(user_agent);
    free(username);
    free(password);
    free(ipaddr);
Karl Heyes's avatar
Karl Heyes committed
509

510 511 512 513 514 515 516

    if (post_offset <= 0 || post_offset >= (ssize_t)sizeof(post)) {
        ICECAST_LOG_ERROR("Authentication failed for client %p as header POST data is too long.", client);
        auth_user_url_clear(auth_user);
        return AUTH_FAILED;
    }

517 518
    pass_headers = NULL;
    if (url->pass_headers)
Marvin Scholz's avatar
Marvin Scholz committed
519 520
        pass_headers = strdup(url->pass_headers);
    if (pass_headers) {
521
        cur_header = pass_headers;
Marvin Scholz's avatar
Marvin Scholz committed
522 523 524
        while (cur_header) {
            next_header = strstr(cur_header, ",");
            if (next_header) {
525
                *next_header=0;
526
                next_header++;
527
            }
528

529
            header_val = igloo_httpp_getvar (client->parser, cur_header);
Marvin Scholz's avatar
Marvin Scholz committed
530
            if (header_val) {
531 532 533
                size_t left = sizeof(post) - post_offset;
                int ret;

534
                header_valesc = util_url_escape (header_val);
535
                ret = snprintf(post + post_offset,
Marvin Scholz's avatar
Marvin Scholz committed
536 537 538 539 540
                                        sizeof(post) - post_offset,
                                        "&%s%s=%s",
                                        url->prefix_headers ? url->prefix_headers : "",
                                        cur_header, header_valesc);
                free(header_valesc);
541 542 543 544 545 546 547 548 549

                if (ret <= 0 || (size_t)ret >= left) {
                    ICECAST_LOG_ERROR("Authentication failed for client %p as header \"%H\" is too long.", client, cur_header);
                    free(pass_headers);
                    auth_user_url_clear(auth_user);
                    return AUTH_FAILED;
                } else {
                    post_offset += ret;
                }
550 551
            }

552
            cur_header = next_header;
553
        }
554
        free(pass_headers);
555 556
    }

Marvin Scholz's avatar
Marvin Scholz committed
557 558 559 560
    if (strchr(url->addurl, '@') == NULL) {
        if (url->userpwd) {
            curl_easy_setopt(url->handle, CURLOPT_USERPWD, url->userpwd);
        } else {
561
            /* auth'd requests may not have a user/pass, but may use query args */
Marvin Scholz's avatar
Marvin Scholz committed
562 563
            if (client->username && client->password) {
                size_t len = strlen(client->username) + strlen(client->password) + 2;
564
                userpwd = malloc (len);
Marvin Scholz's avatar
Marvin Scholz committed
565 566 567 568
                snprintf(userpwd, len, "%s:%s",
                    client->username, client->password);
                curl_easy_setopt(url->handle, CURLOPT_USERPWD, userpwd);
            } else {
569
                curl_easy_setopt (url->handle, CURLOPT_USERPWD, "");
Marvin Scholz's avatar
Marvin Scholz committed
570
            }
571
        }
Marvin Scholz's avatar
Marvin Scholz committed
572
    } else {
573
        /* url has user/pass but libcurl may need to clear any existing settings */
Marvin Scholz's avatar
Marvin Scholz committed
574
        curl_easy_setopt(url->handle, CURLOPT_USERPWD, "");
575
    }
Marvin Scholz's avatar
Marvin Scholz committed
576 577 578
    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);
Karl Heyes's avatar
Karl Heyes committed
579 580
    url->errormsg[0] = '\0';

Philipp Schafft's avatar
Philipp Schafft committed
581
    url->result = AUTH_FAILED;
Marvin Scholz's avatar
Marvin Scholz committed
582
    res = curl_easy_perform(url->handle);
Karl Heyes's avatar
Karl Heyes committed
583

Marvin Scholz's avatar
Marvin Scholz committed
584
    free(userpwd);
585
    auth_user_url_clear(auth_user);
586

Marvin Scholz's avatar
Marvin Scholz committed
587 588 589
    if (res) {
        ICECAST_LOG_WARN("auth to server %s failed with %s",
            url->addurl, url->errormsg);
Karl Heyes's avatar
Karl Heyes committed
590 591 592
        return AUTH_FAILED;
    }
    /* we received a response, lets see what it is */
Philipp Schafft's avatar
Philipp Schafft committed
593
    if (url->result == AUTH_FAILED) {
Marvin Scholz's avatar
Marvin Scholz committed
594 595
        ICECAST_LOG_INFO("client auth (%s) failed with \"%s\"",
            url->addurl, url->errormsg);
596
    }
Philipp Schafft's avatar
Philipp Schafft committed
597
    return url->result;
598 599
}

Marvin Scholz's avatar
Marvin Scholz committed
600 601 602
static auth_result auth_url_adduser(auth_t      *auth,
                                    const char  *username,
                                    const char  *password)
Karl Heyes's avatar
Karl Heyes committed
603 604 605 606
{
    return AUTH_FAILED;
}

Marvin Scholz's avatar
Marvin Scholz committed
607
static auth_result auth_url_deleteuser(auth_t *auth, const char *username)
Karl Heyes's avatar
Karl Heyes committed
608 609 610 611
{
    return AUTH_FAILED;
}

Marvin Scholz's avatar
Marvin Scholz committed
612
static auth_result auth_url_listuser(auth_t *auth, xmlNodePtr srcnode)
Karl Heyes's avatar
Karl Heyes committed
613 614 615 616
{
    return AUTH_FAILED;
}

617
int auth_get_url_auth(auth_t *authenticator, config_options_t *options)
Karl Heyes's avatar
Karl Heyes committed
618
{
Marvin Scholz's avatar
Marvin Scholz committed
619 620 621
    auth_url    *url_info;
    const char  *addaction      = "listener_add";
    const char  *removeaction   = "listener_remove";
Karl Heyes's avatar
Karl Heyes committed
622

Marvin Scholz's avatar
Marvin Scholz committed
623 624 625 626
    authenticator->free         = auth_url_clear;
    authenticator->adduser      = auth_url_adduser;
    authenticator->deleteuser   = auth_url_deleteuser;
    authenticator->listuser     = auth_url_listuser;
Karl Heyes's avatar
Karl Heyes committed
627

Marvin Scholz's avatar
Marvin Scholz committed
628 629
    url_info                    = calloc(1, sizeof(auth_url));
    authenticator->state        = url_info;
630

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

Karl Heyes's avatar
Karl Heyes committed
634
    while(options) {
Philipp Schafft's avatar
Philipp Schafft committed
635
        if(strcmp(options->name, "username") == 0) {
636
            replace_string(&(url_info->username), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
637
        } else if(strcmp(options->name, "password") == 0) {
638
            replace_string(&(url_info->password), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
639
        } else if(strcmp(options->name, "headers") == 0) {
640
            replace_string(&(url_info->pass_headers), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
641
        } else if(strcmp(options->name, "header_prefix") == 0) {
642
            replace_string(&(url_info->prefix_headers), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
643
        } else if(strcmp(options->name, "client_add") == 0) {
644
            replace_string(&(url_info->addurl), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
645 646
        } else if(strcmp(options->name, "client_remove") == 0) {
            authenticator->release_client = url_remove_client;
647
            replace_string(&(url_info->removeurl), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
648 649 650 651 652
        } 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) {
653
            replace_string(&(url_info->auth_header), options->value);
Philipp Schafft's avatar
Philipp Schafft committed
654
        } else if (strcmp(options->name, "timelimit_header") == 0) {
655
            replace_string(&(url_info->timelimit_header), options->value);
656 657
        } else if (strcmp(options->name, "header_auth") == 0) {
            replace_string(&(url_info->header_auth), options->value);
658
            util_strtolower(url_info->header_message);
659 660
        } else if (strcmp(options->name, "header_timelimit") == 0) {
            replace_string(&(url_info->header_timelimit), options->value);
661
            util_strtolower(url_info->header_message);
662 663
        } else if (strcmp(options->name, "header_message") == 0) {
            replace_string(&(url_info->header_message), options->value);
664
            util_strtolower(url_info->header_message);
665 666 667 668 669 670
        } 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);
Philipp Schafft's avatar
Philipp Schafft committed
671 672
        } else {
            ICECAST_LOG_ERROR("Unknown option: %s", options->name);
673
        }
Karl Heyes's avatar
Karl Heyes committed
674 675
        options = options->next;
    }
Philipp Schafft's avatar
Philipp Schafft committed
676 677 678 679

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

680
    url_info->handle = icecast_curl_new(NULL, &url_info->errormsg[0]);
Marvin Scholz's avatar
Marvin Scholz committed
681 682
    if (url_info->handle == NULL) {
        auth_url_clear(authenticator);
Karl Heyes's avatar
Karl Heyes committed
683 684
        return -1;
    }
Philipp Schafft's avatar
Philipp Schafft committed
685

686
    /* default headers */
687 688 689 690 691 692 693 694 695 696 697 698
    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);
699
        url_info->header_timelimit = strdup(DEFAULT_HEADER_NEW_TIMELIMIT);
700
    }
701

Karl Heyes's avatar
Karl Heyes committed
702 703
    if (url_info->auth_header)
        url_info->auth_header_len = strlen (url_info->auth_header);
704 705
    if (url_info->timelimit_header)
        url_info->timelimit_header_len = strlen (url_info->timelimit_header);
Karl Heyes's avatar
Karl Heyes committed
706

Marvin Scholz's avatar
Marvin Scholz committed
707
    curl_easy_setopt(url_info->handle, CURLOPT_HEADERFUNCTION, handle_returned_header);
Karl Heyes's avatar
Karl Heyes committed
708

Marvin Scholz's avatar
Marvin Scholz committed
709 710 711 712 713
    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);
714 715
    }

716
    ICECAST_LOG_INFO("URL based authentication setup");
Karl Heyes's avatar
Karl Heyes committed
717 718
    return 0;
}