auth.c 18.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/* 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).
 */

Michael Smith's avatar
Michael Smith committed
13 14 15 16 17 18 19 20 21 22 23 24 25 26
/** 
 * Client authentication functions
 */

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

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

#include "auth.h"
27
#include "auth_htpasswd.h"
Karl Heyes's avatar
Karl Heyes committed
28
#include "auth_url.h"
Michael Smith's avatar
Michael Smith committed
29 30 31
#include "source.h"
#include "client.h"
#include "cfgfile.h"
32
#include "stats.h"
Michael Smith's avatar
Michael Smith committed
33
#include "httpp/httpp.h"
34
#include "fserve.h"
Michael Smith's avatar
Michael Smith committed
35 36 37

#include "logging.h"
#define CATMODULE "auth"
38

39 40 41 42 43

static mutex_t auth_lock;


static void auth_client_setup (mount_proxy *mountinfo, client_t *client)
44
{
45
    /* This will look something like "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" */
46
    const char *header = httpp_getvar(client->parser, "authorization");
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
    char *userpass, *tmp;
    char *username, *password;

    do
    {
        if (header == NULL)
            break;

        if (strncmp(header, "Basic ", 6) == 0)
        {
            userpass = util_base64_decode (header+6);
            if (userpass == NULL)
            {
                WARN1("Base64 decode of Authorization header \"%s\" failed",
                        header+6);
                break;
            }

            tmp = strchr(userpass, ':');
            if (tmp == NULL)
            { 
                free (userpass);
                break;
70
            }
71 72 73 74 75 76 77 78

            *tmp = 0;
            username = userpass;
            password = tmp+1;
            client->username = strdup (username);
            client->password = strdup (password);
            free (userpass);
            break;
79
        }
80
        INFO1 ("unhandled authorization header: %s", header);
81

82
    } while (0);
83

84
    thread_mutex_lock (&mountinfo->auth->lock);
85 86
    client->auth = mountinfo->auth;
    client->auth->refcount++;
87
    DEBUG2 ("...refcount on auth_t %s is %d", client->auth->mount, client->auth->refcount);
88
    thread_mutex_unlock (&mountinfo->auth->lock);
89 90
}

Michael Smith's avatar
Michael Smith committed
91

92 93
static void queue_auth_client (auth_client *auth_user)
{
94 95 96 97 98 99 100 101 102 103 104 105
    auth_t *auth;

    if (auth_user == NULL)
        return;
    auth = auth_user->client->auth;
    thread_mutex_lock (&auth->lock);
    auth_user->next = NULL;
    *auth->tailp = auth_user;
    auth->tailp = &auth_user->next;
    auth->pending_count++;
    INFO2 ("auth on %s has %d pending", auth->mount, auth->pending_count);
    thread_mutex_unlock (&auth->lock);
106
}
Michael Smith's avatar
Michael Smith committed
107 108


109 110 111 112 113 114 115
/* release the auth. It is referred to by multiple structures so this is
 * refcounted and only actual freed after the last use
 */
void auth_release (auth_t *authenticator)
{
    if (authenticator == NULL)
        return;
Michael Smith's avatar
Michael Smith committed
116

117
    thread_mutex_lock (&authenticator->lock);
118
    authenticator->refcount--;
119
    DEBUG2 ("...refcount on auth_t %s is now %d", authenticator->mount, authenticator->refcount);
120
    if (authenticator->refcount)
121 122
    {
        thread_mutex_unlock (&authenticator->lock);
123
        return;
124
    }
Michael Smith's avatar
Michael Smith committed
125

126 127 128 129
    /* cleanup auth thread attached to this auth */
    authenticator->running = 0;
    thread_join (authenticator->thread);

130 131
    if (authenticator->free)
        authenticator->free (authenticator);
132
    xmlFree (authenticator->type);
133 134
    thread_mutex_unlock (&authenticator->lock);
    thread_mutex_destroy (&authenticator->lock);
135
    free (authenticator->mount);
136
    free (authenticator);
Michael Smith's avatar
Michael Smith committed
137 138 139
}


140
static void auth_client_free (auth_client *auth_user)
Michael Smith's avatar
Michael Smith committed
141
{
142 143 144 145 146
    if (auth_user == NULL)
        return;
    if (auth_user->client)
    {
        client_t *client = auth_user->client;
Michael Smith's avatar
Michael Smith committed
147

148 149 150 151 152 153 154 155
        if (client->respcode)
            client_destroy (client);
        else
            client_send_401 (client);
        auth_user->client = NULL;
    }
    free (auth_user->mount);
    free (auth_user);
Michael Smith's avatar
Michael Smith committed
156 157 158
}


159 160 161 162 163 164 165 166 167 168 169 170 171
/* verify that the listener is still connected. */
static int is_listener_connected (client_t *client)
{
    int ret = 1;
    if (client)
    {
        if (sock_active (client->con->sock) == 0)
            ret = 0;
    }
    return ret;
}


172 173 174 175
/* wrapper function for auth thread to authenticate new listener
 * connection details
 */
static void auth_new_listener (auth_client *auth_user)
Michael Smith's avatar
Michael Smith committed
176
{
177 178
    client_t *client = auth_user->client;

179 180 181 182 183 184 185 186
    /* make sure there is still a client at this point, a slow backend request
     * can be avoided if client has disconnected */
    if (is_listener_connected (client) == 0)
    {
        DEBUG0 ("listener is no longer connected");
        client->respcode = 400;
        return;
    }
187 188 189 190
    if (client->auth->authenticate)
    {
        if (client->auth->authenticate (auth_user) != AUTH_OK)
            return;
Michael Smith's avatar
Michael Smith committed
191
    }
192
    if (auth_postprocess_listener (auth_user) < 0)
193
        INFO1 ("client %lu failed", client->con->id);
Michael Smith's avatar
Michael Smith committed
194 195
}

196

197
/* wrapper function for auth thread to drop listener connections
198 199
 */
static void auth_remove_listener (auth_client *auth_user)
Michael Smith's avatar
Michael Smith committed
200
{
201 202
    client_t *client = auth_user->client;

203 204
    if (client->auth->release_listener)
        client->auth->release_listener (auth_user);
205 206
    auth_release (client->auth);
    client->auth = NULL;
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
    /* client is going, so auth is not an issue at this point */
    client->authenticated = 0;
}


/* Callback from auth thread to handle a stream start event, this applies
 * to both source clients and relays.
 */
static void stream_start_callback (auth_client *auth_user)
{
    auth_t *auth = auth_user->client->auth;

    if (auth->stream_start)
        auth->stream_start (auth_user);
}


/* Callback from auth thread to handle a stream start event, this applies
 * to both source clients and relays.
 */
static void stream_end_callback (auth_client *auth_user)
{
    auth_t *auth = auth_user->client->auth;

    if (auth->stream_end)
        auth->stream_end (auth_user);
233
}
Michael Smith's avatar
Michael Smith committed
234 235


236 237 238
/* The auth thread main loop. */
static void *auth_run_thread (void *arg)
{
239 240
    auth_t *auth = arg;

241
    INFO0 ("Authentication thread started");
242
    while (auth->running)
243
    {
244 245
        /* usually no clients are waiting, so don't bother taking locks */
        if (auth->head)
246 247
        {
            auth_client *auth_user;
Michael Smith's avatar
Michael Smith committed
248

249 250 251 252 253 254 255 256 257 258 259 260 261 262
            /* may become NULL before lock taken */
            thread_mutex_lock (&auth->lock);
            auth_user = (auth_client*)auth->head;
            if (auth_user == NULL)
            {
                thread_mutex_unlock (&auth->lock);
                continue;
            }
            DEBUG2 ("%d client(s) pending on %s", auth->pending_count, auth->mount);
            auth->head = auth_user->next;
            if (auth->head == NULL)
                auth->tailp = &auth->head;
            auth->pending_count--;
            thread_mutex_unlock (&auth->lock);
263
            auth_user->next = NULL;
Michael Smith's avatar
Michael Smith committed
264

265 266 267 268
            if (auth_user->process)
                auth_user->process (auth_user);
            else
                ERROR0 ("client auth process not set");
Michael Smith's avatar
Michael Smith committed
269

270
            auth_client_free (auth_user);
Michael Smith's avatar
Michael Smith committed
271

272
            continue;
273
        }
274
        thread_sleep (150000);
275
    }
276 277 278
    INFO0 ("Authenication thread shutting down");
    return NULL;
}
Michael Smith's avatar
Michael Smith committed
279 280


281 282 283 284 285 286 287 288 289 290 291
/* Check whether this client is currently on this mount, the client may be
 * on either the active or pending lists.
 * return 1 if ok to add or 0 to prevent
 */
static int check_duplicate_logins (source_t *source, client_t *client)
{
    auth_t *auth = client->auth;

    /* allow multiple authenticated relays */
    if (client->username == NULL)
        return 1;
Michael Smith's avatar
Michael Smith committed
292

293 294 295 296 297 298 299 300
    if (auth && auth->allow_duplicate_users == 0)
    {
        avl_node *node;

        avl_tree_rlock (source->client_tree);
        node = avl_get_first (source->client_tree);
        while (node)
        {   
301 302 303
            client_t *existing_client = (client_t *)node->key;
            if (existing_client->username && 
                    strcmp (existing_client->username, client->username) == 0)
304 305 306 307 308 309 310 311 312 313 314 315
            {
                avl_tree_unlock (source->client_tree);
                return 0;
            }
            node = avl_get_next (node);
        }       
        avl_tree_unlock (source->client_tree);

        avl_tree_rlock (source->pending_tree);
        node = avl_get_first (source->pending_tree);
        while (node)
        {
316 317 318
            client_t *existing_client = (client_t *)node->key;
            if (existing_client->username && 
                    strcmp (existing_client->username, client->username) == 0)
319 320 321
            {
                avl_tree_unlock (source->pending_tree);
                return 0;
Michael Smith's avatar
Michael Smith committed
322
            }
323
            node = avl_get_next (node);
Michael Smith's avatar
Michael Smith committed
324
        }
325
        avl_tree_unlock (source->pending_tree);
Michael Smith's avatar
Michael Smith committed
326
    }
327
    return 1;
Michael Smith's avatar
Michael Smith committed
328 329
}

330 331 332 333

/* if 0 is returned then the client should not be touched, however if -1
 * is returned then the caller is responsible for handling the client
 */
334
static int add_listener_to_source (source_t *source, client_t *client)
Michael Smith's avatar
Michael Smith committed
335
{
336
    int loop = 10;
337 338 339 340 341 342 343 344
    do
    {
        DEBUG3 ("max on %s is %ld (cur %lu)", source->mount,
                source->max_listeners, source->listeners);
        if (source->max_listeners == -1)
            break;
        if (source->listeners < (unsigned long)source->max_listeners)
            break;
Michael Smith's avatar
Michael Smith committed
345

346 347 348
        if (loop && source->fallback_when_full && source->fallback_mount)
        {
            source_t *next = source_find_mount (source->fallback_mount);
349
            if (!next) {
Michael Smith's avatar
Michael Smith committed
350
                ERROR2("Fallback '%s' for full source '%s' not found", 
351 352 353 354
                        source->mount, source->fallback_mount);
                return -1;
            }

355 356 357 358 359
            INFO1 ("stream full trying %s", next->mount);
            source = next;
            loop--;
            continue;
        }
360 361
        /* now we fail the client */
        return -1;
Michael Smith's avatar
Michael Smith committed
362

363
    } while (1);
Michael Smith's avatar
Michael Smith committed
364

365 366 367 368
    client->write_to_client = format_generic_write_to_client;
    client->check_buffer = format_check_http_buffer;
    client->refbuf->len = PER_CLIENT_REFBUF_SIZE;
    memset (client->refbuf->data, 0, PER_CLIENT_REFBUF_SIZE);
Michael Smith's avatar
Michael Smith committed
369

370 371 372 373 374
    /* lets add the client to the active list */
    avl_tree_wlock (source->pending_tree);
    avl_insert (source->pending_tree, client);
    avl_tree_unlock (source->pending_tree);

375 376 377 378 379
    if (source->running == 0 && source->on_demand)
    {
        /* enable on-demand relay to start, wake up the slave thread */
        DEBUG0("kicking off on-demand relay");
        source->on_demand_req = 1;
Michael Smith's avatar
Michael Smith committed
380
    }
381 382 383
    DEBUG1 ("Added client to %s", source->mount);
    return 0;
}
Michael Smith's avatar
Michael Smith committed
384 385


386 387 388
/* Add listener to the pending lists of either the  source or fserve thread.
 * This can be run from the connection or auth thread context
 */
389
static int add_authenticated_listener (const char *mount, mount_proxy *mountinfo, client_t *client)
390 391 392
{
    int ret = 0;
    source_t *source = NULL;
393

394 395 396 397 398 399 400 401 402 403 404
    /* Here we are parsing the URI request to see if the extension is .xsl, if
     * so, then process this request as an XSLT request
     */
    if (util_check_valid_extension (mount) == XSLT_CONTENT)
    {
        /* If the file exists, then transform it, otherwise, write a 404 */
        DEBUG0("Stats request, sending XSL transformed stats");
        stats_transform_xslt (client, mount);
        return 0;
    }

405 406
    avl_tree_rlock (global.source_tree);
    source = source_find_mount (mount);
Michael Smith's avatar
Michael Smith committed
407

408 409 410 411 412 413
    if (source)
    {
        if (client->auth && check_duplicate_logins (source, client) == 0)
        {
            avl_tree_unlock (global.source_tree);
            return -1;
414
        }
415 416 417 418 419 420
        if (mountinfo)
        {
            /* set a per-mount disconnect time if auth hasn't set one already */
            if (mountinfo->max_listener_duration && client->con->discon_time == 0)
                client->con->discon_time = time(NULL) + mountinfo->max_listener_duration;
        }
421

422
        ret = add_listener_to_source (source, client);
423 424 425
        avl_tree_unlock (global.source_tree);
        if (ret == 0)
            DEBUG0 ("client authenticated, passed to source");
426
    }
427 428 429 430 431
    else
    {
        avl_tree_unlock (global.source_tree);
        fserve_client_create (client, mount);
    }
432 433
    return ret;
}
434 435


436
int auth_postprocess_listener (auth_client *auth_user)
437
{
438
    int ret;
439
    client_t *client = auth_user->client;
440
    ice_config_t *config = config_get_config();
441

442
    mount_proxy *mountinfo = config_find_mount (config, auth_user->mount);
443
    client->authenticated = 1;
444

445
    ret = add_authenticated_listener (auth_user->mount, mountinfo, client);
446
    config_release_config();
447

448 449 450
    if (ret < 0)
        client_send_401 (auth_user->client);
    auth_user->client = NULL;
451

452
    return ret;
453 454
}

455 456 457 458

/* Add a listener. Check for any mount information that states any
 * authentication to be used.
 */
459
void auth_add_listener (const char *mount, client_t *client)
460
{
461 462 463 464 465 466 467
    mount_proxy *mountinfo; 
    ice_config_t *config = config_get_config();

    mountinfo = config_find_mount (config, mount);
    if (mountinfo && mountinfo->no_mount)
    {
        config_release_config ();
468
        client_send_403 (client, "mountpoint unavailable");
469 470 471 472 473 474
        return;
    }
    if (mountinfo && mountinfo->auth)
    {
        auth_client *auth_user;

475
        if (mountinfo->auth->pending_count > 100)
476 477 478
        {
            config_release_config ();
            WARN0 ("too many clients awaiting authentication");
479
            client_send_403 (client, "busy, please try again later");
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
            return;
        }
        auth_client_setup (mountinfo, client);
        config_release_config ();

        auth_user = calloc (1, sizeof (auth_client));
        if (auth_user == NULL)
        {
            client_send_401 (client);
            return;
        }
        auth_user->mount = strdup (mount);
        auth_user->process = auth_new_listener;
        auth_user->client = client;

        INFO0 ("adding client for authentication");
        queue_auth_client (auth_user);
    }
    else
    {
500
        int ret = add_authenticated_listener (mount, mountinfo, client);
501 502
        config_release_config ();
        if (ret < 0)
503
            client_send_403 (client, "max listeners reached");
504 505 506
    }
}

507 508 509 510

/* determine whether we need to process this client further. This
 * involves any auth exit, typically for external auth servers.
 */
511
int auth_release_listener (client_t *client)
512
{
513
    if (client->auth)
514
    {
515 516 517
        auth_client *auth_user = calloc (1, sizeof (auth_client));
        if (auth_user == NULL)
            return 0;
518

519 520 521
        auth_user->mount = strdup (httpp_getvar (client->parser, HTTPP_VAR_URI));
        auth_user->process = auth_remove_listener;
        auth_user->client = client;
522

523 524 525 526 527
        queue_auth_client (auth_user);
        return 1;
    }
    return 0;
}
528 529


Karl Heyes's avatar
Karl Heyes committed
530
static int get_authenticator (auth_t *auth, config_options_t *options)
531
{
532 533 534 535 536
    if (auth->type == NULL)
    {
        WARN0 ("no authentication type defined");
        return -1;
    }
537 538 539
    do
    {
        DEBUG1 ("type is %s", auth->type);
540

Karl Heyes's avatar
Karl Heyes committed
541 542
        if (strcmp (auth->type, "url") == 0)
        {
543
#ifdef HAVE_AUTH_URL
Karl Heyes's avatar
Karl Heyes committed
544 545
            if (auth_get_url_auth (auth, options) < 0)
                return -1;
546 547
#else
            ERROR0 ("Auth URL disabled");
Karl Heyes's avatar
Karl Heyes committed
548
            return -1;
549
#endif
Karl Heyes's avatar
Karl Heyes committed
550
        }
551 552
        if (strcmp (auth->type, "htpasswd") == 0)
        {
Karl Heyes's avatar
Karl Heyes committed
553 554
            if (auth_get_htpasswd_auth (auth, options) < 0)
                return -1;
555
            break;
556
        }
Karl Heyes's avatar
Karl Heyes committed
557

558
        ERROR1("Unrecognised authenticator type: \"%s\"", auth->type);
Karl Heyes's avatar
Karl Heyes committed
559
        return -1;
560 561 562 563 564 565 566
    } while (0);

    while (options)
    {
        if (strcmp(options->name, "allow_duplicate_users") == 0)
            auth->allow_duplicate_users = atoi (options->value);
        options = options->next;
567
    }
Karl Heyes's avatar
Karl Heyes committed
568
    return 0;
569
}
570 571


572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
auth_t *auth_get_authenticator (xmlNodePtr node)
{
    auth_t *auth = calloc (1, sizeof (auth_t));
    config_options_t *options = NULL, **next_option = &options;
    xmlNodePtr option;

    if (auth == NULL)
        return NULL;

    option = node->xmlChildrenNode;
    while (option)
    {
        xmlNodePtr current = option;
        option = option->next;
        if (strcmp (current->name, "option") == 0)
        {
            config_options_t *opt = calloc (1, sizeof (config_options_t));
            opt->name = xmlGetProp (current, "name");
            if (opt->name == NULL)
            {
                free(opt);
                continue;
            }
            opt->value = xmlGetProp (current, "value");
            if (opt->value == NULL)
            {
                xmlFree (opt->name);
                free (opt);
                continue;
            }
            *next_option = opt;
            next_option = &opt->next;
        }
        else
            if (strcmp (current->name, "text") != 0)
                WARN1 ("unknown auth setting (%s)", current->name);
608
    }
609
    auth->type = xmlGetProp (node, "type");
Karl Heyes's avatar
Karl Heyes committed
610 611 612 613 614 615 616 617 618 619 620 621 622 623
    if (get_authenticator (auth, options) < 0)
    {
        xmlFree (auth->type);
        free (auth);
        auth = NULL;
    }
    else
    {
        auth->tailp = &auth->head;
        thread_mutex_create (&auth->lock);
        auth->refcount = 1;
        auth->running = 1;
        auth->thread = thread_create ("auth thread", auth_run_thread, auth, THREAD_ATTACHED);
    }
624

625 626 627 628 629 630 631
    while (options)
    {
        config_options_t *opt = options;
        options = opt->next;
        xmlFree (opt->name);
        xmlFree (opt->value);
        free (opt);
632
    }
633 634
    return auth;
}
635 636


637 638 639 640
/* called when the stream starts, so that authentication engine can do any
 * cleanup/initialisation.
 */
void auth_stream_start (mount_proxy *mountinfo, const char *mount)
641
{
642 643 644 645 646 647
    if (mountinfo && mountinfo->auth && mountinfo->auth->stream_start)
    {
        auth_client *auth_user = calloc (1, sizeof (auth_client));
        if (auth_user)
        {
            auth_user->mount = strdup (mount);
648
            auth_user->process = stream_start_callback;
649

650
            queue_auth_client (auth_user);
651 652 653 654 655
        }
    }
}


656 657 658 659 660 661 662 663 664 665 666
/* Called when the stream ends so that the authentication engine can do
 * any authentication cleanup
 */
void auth_stream_end (mount_proxy *mountinfo, const char *mount)
{
    if (mountinfo && mountinfo->auth && mountinfo->auth->stream_end)
    {
        auth_client *auth_user = calloc (1, sizeof (auth_client));
        if (auth_user)
        {
            auth_user->mount = strdup (mount);
667
            auth_user->process = stream_end_callback;
668

669 670
            queue_auth_client (auth_user);
        }
671
    }
672
}
673 674


675
/* these are called at server start and termination */
676

677
void auth_initialise (void)
678 679
{
    thread_mutex_create (&auth_lock);
680 681
}

682
void auth_shutdown (void)
683
{
684 685
    thread_mutex_destroy (&auth_lock);
    INFO0 ("Auth shutdown");
686
}
687