auth.c 17.5 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
    avl_tree_rlock (global.source_tree);
    source = source_find_mount (mount);
Michael Smith's avatar
Michael Smith committed
396

397 398 399 400 401 402
    if (source)
    {
        if (client->auth && check_duplicate_logins (source, client) == 0)
        {
            avl_tree_unlock (global.source_tree);
            return -1;
403
        }
404 405 406 407 408 409
        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;
        }
410

411
        ret = add_listener_to_source (source, client);
412 413 414
        avl_tree_unlock (global.source_tree);
        if (ret == 0)
            DEBUG0 ("client authenticated, passed to source");
415
    }
416 417 418 419 420
    else
    {
        avl_tree_unlock (global.source_tree);
        fserve_client_create (client, mount);
    }
421 422
    return ret;
}
423 424


425
int auth_postprocess_listener (auth_client *auth_user)
426
{
427
    int ret;
428
    client_t *client = auth_user->client;
429
    ice_config_t *config = config_get_config();
430

431
    mount_proxy *mountinfo = config_find_mount (config, auth_user->mount);
432
    client->authenticated = 1;
433

434
    ret = add_authenticated_listener (auth_user->mount, mountinfo, client);
435
    config_release_config();
436

437 438 439
    if (ret < 0)
        client_send_401 (auth_user->client);
    auth_user->client = NULL;
440

441
    return ret;
442 443
}

444 445 446 447

/* Add a listener. Check for any mount information that states any
 * authentication to be used.
 */
448
void auth_add_listener (const char *mount, client_t *client)
449
{
450 451 452 453 454 455 456
    mount_proxy *mountinfo; 
    ice_config_t *config = config_get_config();

    mountinfo = config_find_mount (config, mount);
    if (mountinfo && mountinfo->no_mount)
    {
        config_release_config ();
457
        client_send_403 (client, "mountpoint unavailable");
458 459 460 461 462 463
        return;
    }
    if (mountinfo && mountinfo->auth)
    {
        auth_client *auth_user;

464
        if (mountinfo->auth->pending_count > 100)
465 466 467
        {
            config_release_config ();
            WARN0 ("too many clients awaiting authentication");
468
            client_send_403 (client, "busy, please try again later");
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
            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
    {
489
        int ret = add_authenticated_listener (mount, mountinfo, client);
490 491
        config_release_config ();
        if (ret < 0)
492
            client_send_403 (client, "max listeners reached");
493 494 495
    }
}

496 497 498 499

/* determine whether we need to process this client further. This
 * involves any auth exit, typically for external auth servers.
 */
500
int auth_release_listener (client_t *client)
501
{
502
    if (client->auth)
503
    {
504 505 506
        auth_client *auth_user = calloc (1, sizeof (auth_client));
        if (auth_user == NULL)
            return 0;
507

508 509 510
        auth_user->mount = strdup (httpp_getvar (client->parser, HTTPP_VAR_URI));
        auth_user->process = auth_remove_listener;
        auth_user->client = client;
511

512 513 514 515 516
        queue_auth_client (auth_user);
        return 1;
    }
    return 0;
}
517 518


519 520 521 522 523
static void get_authenticator (auth_t *auth, config_options_t *options)
{
    do
    {
        DEBUG1 ("type is %s", auth->type);
524

Karl Heyes's avatar
Karl Heyes committed
525 526
        if (strcmp (auth->type, "url") == 0)
        {
527
#ifdef HAVE_AUTH_URL
Karl Heyes's avatar
Karl Heyes committed
528
            auth_get_url_auth (auth, options);
529 530 531
#else
            ERROR0 ("Auth URL disabled");
#endif
Karl Heyes's avatar
Karl Heyes committed
532 533
            break;
        }
534 535 536 537
        if (strcmp (auth->type, "htpasswd") == 0)
        {
            auth_get_htpasswd_auth (auth, options);
            break;
538
        }
539 540 541 542 543 544 545 546 547 548 549
        
        ERROR1("Unrecognised authenticator type: \"%s\"", auth->type);
        return;
    } while (0);

    auth->refcount = 1;
    while (options)
    {
        if (strcmp(options->name, "allow_duplicate_users") == 0)
            auth->allow_duplicate_users = atoi (options->value);
        options = options->next;
550
    }
551
}
552 553


554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
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);
590
    }
591 592
    auth->type = xmlGetProp (node, "type");
    get_authenticator (auth, options);
593
    auth->tailp = &auth->head;
594
    thread_mutex_create (&auth->lock);
595 596 597 598

    auth->running = 1;
    auth->thread = thread_create ("auth thread", auth_run_thread, auth, THREAD_ATTACHED);

599 600 601 602 603 604 605
    while (options)
    {
        config_options_t *opt = options;
        options = opt->next;
        xmlFree (opt->name);
        xmlFree (opt->value);
        free (opt);
606
    }
607 608
    return auth;
}
609 610


611 612 613 614
/* called when the stream starts, so that authentication engine can do any
 * cleanup/initialisation.
 */
void auth_stream_start (mount_proxy *mountinfo, const char *mount)
615
{
616 617 618 619 620 621
    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);
622
            auth_user->process = stream_start_callback;
623

624
            queue_auth_client (auth_user);
625 626 627 628 629
        }
    }
}


630 631 632 633 634 635 636 637 638 639 640
/* 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);
641
            auth_user->process = stream_end_callback;
642

643 644
            queue_auth_client (auth_user);
        }
645
    }
646
}
647 648


649
/* these are called at server start and termination */
650

651
void auth_initialise (void)
652 653
{
    thread_mutex_create (&auth_lock);
654 655
}

656
void auth_shutdown (void)
657
{
658 659
    thread_mutex_destroy (&auth_lock);
    INFO0 ("Auth shutdown");
660
}
661