auth.c 19 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

static mutex_t auth_lock;


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

    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;
71
            }
72 73 74 75 76 77 78 79

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

83
    } while (0);
84

85 86 87 88
    auth_user = calloc (1, sizeof(auth_client));
    auth_user->mount = strdup (mount);
    auth_user->client = client;
    return auth_user;
89 90
}

Michael Smith's avatar
Michael Smith committed
91

92
static void queue_auth_client (auth_client *auth_user, mount_proxy *mountinfo)
93
{
94 95
    auth_t *auth;

96
    if (auth_user == NULL)
97 98
        return;
    auth_user->next = NULL;
99 100 101 102 103 104 105 106 107 108
    if (mountinfo)
    {
        auth = mountinfo->auth;
        thread_mutex_lock (&auth->lock);
        if (auth_user->client)
            auth_user->client->auth = auth;
        auth->refcount++;
    }
    else
    {
109 110 111 112 113
        if (auth_user->client == NULL || auth_user->client->auth == NULL)
        {
            WARN1 ("internal state is incorrect for %p", auth_user->client);
            return;
        }
114 115 116 117
        auth = auth_user->client->auth;
        thread_mutex_lock (&auth->lock);
    }
    DEBUG2 ("...refcount on auth_t %s is now %d", auth->mount, auth->refcount);
118 119 120 121 122
    *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);
123
}
Michael Smith's avatar
Michael Smith committed
124 125


126 127 128 129 130 131 132
/* 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
133

134
    thread_mutex_lock (&authenticator->lock);
135
    authenticator->refcount--;
136
    DEBUG2 ("...refcount on auth_t %s is now %d", authenticator->mount, authenticator->refcount);
137
    if (authenticator->refcount)
138 139
    {
        thread_mutex_unlock (&authenticator->lock);
140
        return;
141
    }
Michael Smith's avatar
Michael Smith committed
142

143 144 145 146
    /* cleanup auth thread attached to this auth */
    authenticator->running = 0;
    thread_join (authenticator->thread);

147 148
    if (authenticator->free)
        authenticator->free (authenticator);
149
    xmlFree (authenticator->type);
150 151
    thread_mutex_unlock (&authenticator->lock);
    thread_mutex_destroy (&authenticator->lock);
152
    free (authenticator->mount);
153
    free (authenticator);
Michael Smith's avatar
Michael Smith committed
154 155 156
}


157
static void auth_client_free (auth_client *auth_user)
Michael Smith's avatar
Michael Smith committed
158
{
159 160 161 162 163
    if (auth_user == NULL)
        return;
    if (auth_user->client)
    {
        client_t *client = auth_user->client;
Michael Smith's avatar
Michael Smith committed
164

165 166 167 168 169 170 171 172
        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
173 174 175
}


176 177 178 179 180 181 182 183 184 185 186 187 188
/* 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;
}


189 190 191
/* wrapper function for auth thread to authenticate new listener
 * connection details
 */
192
static void auth_new_listener (auth_t *auth, auth_client *auth_user)
Michael Smith's avatar
Michael Smith committed
193
{
194 195
    client_t *client = auth_user->client;

196 197 198 199 200 201
    /* 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;
202 203
        auth_release (client->auth);
        client->auth = NULL;
204 205
        return;
    }
206
    if (auth->authenticate)
207
    {
208
        if (auth->authenticate (auth_user) != AUTH_OK)
209 210 211
        {
            auth_release (client->auth);
            client->auth = NULL;
212
            return;
213
        }
Michael Smith's avatar
Michael Smith committed
214
    }
215
    if (auth_postprocess_listener (auth_user) < 0)
216 217 218
    {
        auth_release (client->auth);
        client->auth = NULL;
219
        INFO1 ("client %lu failed", client->con->id);
220
    }
Michael Smith's avatar
Michael Smith committed
221 222
}

223

224
/* wrapper function for auth thread to drop listener connections
225
 */
226
static void auth_remove_listener (auth_t *auth, auth_client *auth_user)
Michael Smith's avatar
Michael Smith committed
227
{
228 229
    client_t *client = auth_user->client;

230 231
    if (client->auth->release_listener)
        client->auth->release_listener (auth_user);
232 233
    auth_release (client->auth);
    client->auth = NULL;
234 235 236 237 238 239 240 241
    /* 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.
 */
242
static void stream_start_callback (auth_t *auth, auth_client *auth_user)
243 244 245
{
    if (auth->stream_start)
        auth->stream_start (auth_user);
246
    auth_release (auth);
247 248 249 250 251 252
}


/* Callback from auth thread to handle a stream start event, this applies
 * to both source clients and relays.
 */
253
static void stream_end_callback (auth_t *auth, auth_client *auth_user)
254 255 256
{
    if (auth->stream_end)
        auth->stream_end (auth_user);
257
    auth_release (auth);
258
}
Michael Smith's avatar
Michael Smith committed
259 260


261 262 263
/* The auth thread main loop. */
static void *auth_run_thread (void *arg)
{
264 265
    auth_t *auth = arg;

266
    INFO0 ("Authentication thread started");
267
    while (auth->running)
268
    {
269 270
        /* usually no clients are waiting, so don't bother taking locks */
        if (auth->head)
271 272
        {
            auth_client *auth_user;
Michael Smith's avatar
Michael Smith committed
273

274 275 276 277 278 279 280 281 282 283 284 285 286 287
            /* 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);
288
            auth_user->next = NULL;
Michael Smith's avatar
Michael Smith committed
289

290
            if (auth_user->process)
291
                auth_user->process (auth, auth_user);
292 293
            else
                ERROR0 ("client auth process not set");
Michael Smith's avatar
Michael Smith committed
294

295
            auth_client_free (auth_user);
Michael Smith's avatar
Michael Smith committed
296

297
            continue;
298
        }
299
        thread_sleep (150000);
300
    }
301 302 303
    INFO0 ("Authenication thread shutting down");
    return NULL;
}
Michael Smith's avatar
Michael Smith committed
304 305


306 307 308 309
/* 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
 */
310
static int check_duplicate_logins (source_t *source, client_t *client, auth_t *auth)
311 312 313 314
{
    /* allow multiple authenticated relays */
    if (client->username == NULL)
        return 1;
Michael Smith's avatar
Michael Smith committed
315

316 317 318 319 320 321 322 323
    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)
        {   
324 325 326
            client_t *existing_client = (client_t *)node->key;
            if (existing_client->username && 
                    strcmp (existing_client->username, client->username) == 0)
327 328 329 330 331 332 333 334 335 336 337 338
            {
                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)
        {
339 340 341
            client_t *existing_client = (client_t *)node->key;
            if (existing_client->username && 
                    strcmp (existing_client->username, client->username) == 0)
342 343 344
            {
                avl_tree_unlock (source->pending_tree);
                return 0;
Michael Smith's avatar
Michael Smith committed
345
            }
346
            node = avl_get_next (node);
Michael Smith's avatar
Michael Smith committed
347
        }
348
        avl_tree_unlock (source->pending_tree);
Michael Smith's avatar
Michael Smith committed
349
    }
350
    return 1;
Michael Smith's avatar
Michael Smith committed
351 352
}

353 354 355 356

/* 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
 */
357
static int add_listener_to_source (source_t *source, client_t *client)
Michael Smith's avatar
Michael Smith committed
358
{
359
    int loop = 10;
360 361 362 363 364 365 366 367
    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
368

369 370 371
        if (loop && source->fallback_when_full && source->fallback_mount)
        {
            source_t *next = source_find_mount (source->fallback_mount);
372
            if (!next) {
Michael Smith's avatar
Michael Smith committed
373
                ERROR2("Fallback '%s' for full source '%s' not found", 
374 375 376 377
                        source->mount, source->fallback_mount);
                return -1;
            }

378 379 380 381 382
            INFO1 ("stream full trying %s", next->mount);
            source = next;
            loop--;
            continue;
        }
383 384
        /* now we fail the client */
        return -1;
Michael Smith's avatar
Michael Smith committed
385

386
    } while (1);
Michael Smith's avatar
Michael Smith committed
387

388 389 390 391
    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
392

393 394 395 396 397
    /* 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);

398 399 400 401 402
    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
403
    }
404 405 406
    DEBUG1 ("Added client to %s", source->mount);
    return 0;
}
Michael Smith's avatar
Michael Smith committed
407 408


409 410 411
/* Add listener to the pending lists of either the  source or fserve thread.
 * This can be run from the connection or auth thread context
 */
412
static int add_authenticated_listener (const char *mount, mount_proxy *mountinfo, client_t *client)
413 414 415
{
    int ret = 0;
    source_t *source = NULL;
416

417 418
    client->authenticated = 1;

419 420 421 422 423 424 425 426 427 428 429
    /* 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;
    }

430 431
    avl_tree_rlock (global.source_tree);
    source = source_find_mount (mount);
Michael Smith's avatar
Michael Smith committed
432

433 434
    if (source)
    {
435 436
        if (mountinfo)
        {
Karl Heyes's avatar
Karl Heyes committed
437 438 439 440 441 442
            if (check_duplicate_logins (source, client, mountinfo->auth) == 0)
            {
                avl_tree_unlock (global.source_tree);
                return -1;
            }

443 444 445 446
            /* 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;
        }
447

448
        ret = add_listener_to_source (source, client);
449 450 451
        avl_tree_unlock (global.source_tree);
        if (ret == 0)
            DEBUG0 ("client authenticated, passed to source");
452
    }
453 454 455 456 457
    else
    {
        avl_tree_unlock (global.source_tree);
        fserve_client_create (client, mount);
    }
458 459
    return ret;
}
460 461


462
int auth_postprocess_listener (auth_client *auth_user)
463
{
464
    int ret;
465
    client_t *client = auth_user->client;
466
    ice_config_t *config = config_get_config();
467

468
    mount_proxy *mountinfo = config_find_mount (config, auth_user->mount);
469

470
    ret = add_authenticated_listener (auth_user->mount, mountinfo, client);
471
    config_release_config();
472

473 474 475
    if (ret < 0)
        client_send_401 (auth_user->client);
    auth_user->client = NULL;
476

477
    return ret;
478 479
}

480 481 482 483

/* Add a listener. Check for any mount information that states any
 * authentication to be used.
 */
484
void auth_add_listener (const char *mount, client_t *client)
485
{
486 487 488 489 490 491 492
    mount_proxy *mountinfo; 
    ice_config_t *config = config_get_config();

    mountinfo = config_find_mount (config, mount);
    if (mountinfo && mountinfo->no_mount)
    {
        config_release_config ();
493
        client_send_403 (client, "mountpoint unavailable");
494 495
        return;
    }
496
    if (mountinfo && mountinfo->auth)
497 498 499
    {
        auth_client *auth_user;

500
        if (mountinfo->auth->pending_count > 100)
501 502 503
        {
            config_release_config ();
            WARN0 ("too many clients awaiting authentication");
504
            client_send_403 (client, "busy, please try again later");
505 506
            return;
        }
507
        auth_user = auth_client_setup (mount, client);
508 509
        auth_user->process = auth_new_listener;
        INFO0 ("adding client for authentication");
510 511
        queue_auth_client (auth_user, mountinfo);
        config_release_config ();
512 513 514
    }
    else
    {
515
        int ret = add_authenticated_listener (mount, mountinfo, client);
516 517
        config_release_config ();
        if (ret < 0)
518
            client_send_403 (client, "max listeners reached");
519 520 521
    }
}

522 523 524 525

/* determine whether we need to process this client further. This
 * involves any auth exit, typically for external auth servers.
 */
526
int auth_release_listener (client_t *client)
527
{
528
    if (client->authenticated)
529
    {
530
        const char *mount = httpp_getvar (client->parser, HTTPP_VAR_URI);
531

532 533 534
        /* drop any queue reference here, we do not want a race between the source thread
         * and the auth/fserve thread */
        client_set_queue (client, NULL);
535

536 537 538 539 540 541 542 543
        if (mount && client->auth && client->auth->release_listener)
        {
            auth_client *auth_user = auth_client_setup (mount, client);
            auth_user->process = auth_remove_listener;
            queue_auth_client (auth_user, NULL);
            return 1;
        }
        client->authenticated = 0;
544 545 546
    }
    return 0;
}
547 548


Karl Heyes's avatar
Karl Heyes committed
549
static int get_authenticator (auth_t *auth, config_options_t *options)
550
{
551 552 553 554 555
    if (auth->type == NULL)
    {
        WARN0 ("no authentication type defined");
        return -1;
    }
556 557 558
    do
    {
        DEBUG1 ("type is %s", auth->type);
559

Karl Heyes's avatar
Karl Heyes committed
560 561
        if (strcmp (auth->type, "url") == 0)
        {
562
#ifdef HAVE_AUTH_URL
Karl Heyes's avatar
Karl Heyes committed
563 564
            if (auth_get_url_auth (auth, options) < 0)
                return -1;
565
            break;
566 567
#else
            ERROR0 ("Auth URL disabled");
Karl Heyes's avatar
Karl Heyes committed
568
            return -1;
569
#endif
Karl Heyes's avatar
Karl Heyes committed
570
        }
571 572
        if (strcmp (auth->type, "htpasswd") == 0)
        {
Karl Heyes's avatar
Karl Heyes committed
573 574
            if (auth_get_htpasswd_auth (auth, options) < 0)
                return -1;
575
            break;
576
        }
Karl Heyes's avatar
Karl Heyes committed
577

578
        ERROR1("Unrecognised authenticator type: \"%s\"", auth->type);
Karl Heyes's avatar
Karl Heyes committed
579
        return -1;
580 581 582 583
    } while (0);

    while (options)
    {
584 585
        if (strcmp (options->name, "allow_duplicate_users") == 0)
            auth->allow_duplicate_users = atoi ((char*)options->value);
586
        options = options->next;
587
    }
Karl Heyes's avatar
Karl Heyes committed
588
    return 0;
589
}
590 591


592 593 594 595 596 597 598 599 600 601 602 603 604 605
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;
606
        if (xmlStrcmp (current->name, XMLSTR("option")) == 0)
607 608
        {
            config_options_t *opt = calloc (1, sizeof (config_options_t));
609
            opt->name = (char *)xmlGetProp (current, XMLSTR("name"));
610 611 612 613 614
            if (opt->name == NULL)
            {
                free(opt);
                continue;
            }
615
            opt->value = (char *)xmlGetProp (current, XMLSTR("value"));
616 617 618 619 620 621 622 623 624 625
            if (opt->value == NULL)
            {
                xmlFree (opt->name);
                free (opt);
                continue;
            }
            *next_option = opt;
            next_option = &opt->next;
        }
        else
626
            if (xmlStrcmp (current->name, XMLSTR("text")) != 0)
627
                WARN1 ("unknown auth setting (%s)", current->name);
628
    }
629
    auth->type = (char*)xmlGetProp (node, XMLSTR("type"));
Karl Heyes's avatar
Karl Heyes committed
630 631 632 633 634 635 636 637 638 639 640 641 642 643
    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);
    }
644

645 646 647 648 649 650 651
    while (options)
    {
        config_options_t *opt = options;
        options = opt->next;
        xmlFree (opt->name);
        xmlFree (opt->value);
        free (opt);
652
    }
653 654
    return auth;
}
655 656


657 658 659 660
/* called when the stream starts, so that authentication engine can do any
 * cleanup/initialisation.
 */
void auth_stream_start (mount_proxy *mountinfo, const char *mount)
661
{
662 663 664 665 666 667
    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);
668
            auth_user->process = stream_start_callback;
669

670
            queue_auth_client (auth_user, mountinfo);
671 672 673 674 675
        }
    }
}


676 677 678 679 680 681 682 683 684 685 686
/* 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);
687
            auth_user->process = stream_end_callback;
688

689
            queue_auth_client (auth_user, mountinfo);
690
        }
691
    }
692
}
693 694


695
/* these are called at server start and termination */
696

697
void auth_initialise (void)
698 699
{
    thread_mutex_create (&auth_lock);
700 701
}

702
void auth_shutdown (void)
703
{
704 705
    thread_mutex_destroy (&auth_lock);
    INFO0 ("Auth shutdown");
706
}
707