auth.c 15.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
44
45
46
47

static volatile auth_client *clients_to_auth;
static volatile unsigned int auth_pending_count;
static volatile int auth_running;
static mutex_t auth_lock;
static thread_type *auth_thread;


static void auth_client_setup (mount_proxy *mountinfo, client_t *client)
48
{
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
    /* This will look something like "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" */
    char *header = httpp_getvar(client->parser, "authorization");
    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;
74
            }
75
76
77
78
79
80
81
82

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

86
    } while (0);
87

88
89
    client->auth = mountinfo->auth;
    client->auth->refcount++;
90
91
}

Michael Smith's avatar
Michael Smith committed
92

93
94
95
96
97
98
99
100
static void queue_auth_client (auth_client *auth_user)
{
    thread_mutex_lock (&auth_lock);
    auth_user->next = (auth_client *)clients_to_auth;
    clients_to_auth = auth_user;
    auth_pending_count++;
    thread_mutex_unlock (&auth_lock);
}
Michael Smith's avatar
Michael Smith committed
101
102


103
104
105
106
107
108
109
/* 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
110

111
112
113
    authenticator->refcount--;
    if (authenticator->refcount)
        return;
Michael Smith's avatar
Michael Smith committed
114

115
116
    if (authenticator->free)
        authenticator->free (authenticator);
117
    xmlFree (authenticator->type);
118
    free (authenticator);
Michael Smith's avatar
Michael Smith committed
119
120
121
}


122
void auth_client_free (auth_client *auth_user)
Michael Smith's avatar
Michael Smith committed
123
{
124
125
126
127
128
    if (auth_user == NULL)
        return;
    if (auth_user->client)
    {
        client_t *client = auth_user->client;
Michael Smith's avatar
Michael Smith committed
129

130
131
132
133
134
135
136
137
        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
138
139
140
}


141
142
143
144
/* 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
145
{
146
147
148
149
150
151
    client_t *client = auth_user->client;

    if (client->auth->authenticate)
    {
        if (client->auth->authenticate (auth_user) != AUTH_OK)
            return;
Michael Smith's avatar
Michael Smith committed
152
    }
153
154
    if (auth_postprocess_client (auth_user) < 0)
        INFO1 ("client %lu failed", client->con->id);
Michael Smith's avatar
Michael Smith committed
155
156
}

157
158
159
160
161

/* wrapper function are auth thread to authenticate new listener
 * connections
 */
static void auth_remove_listener (auth_client *auth_user)
Michael Smith's avatar
Michael Smith committed
162
{
163
164
165
166
167
168
169
170
    client_t *client = auth_user->client;

    if (client->auth->release_client)
        client->auth->release_client (auth_user);
    auth_release (client->auth);
    client->auth = NULL;
    return;
}
Michael Smith's avatar
Michael Smith committed
171
172


173
174
175
176
177
178
179
180
181
/* The auth thread main loop. */
static void *auth_run_thread (void *arg)
{
    INFO0 ("Authentication thread started");
    while (1)
    {
        if (clients_to_auth)
        {
            auth_client *auth_user;
Michael Smith's avatar
Michael Smith committed
182

183
184
185
186
187
188
            thread_mutex_lock (&auth_lock);
            auth_user = (auth_client*)clients_to_auth;
            clients_to_auth = auth_user->next;
            auth_pending_count--;
            thread_mutex_unlock (&auth_lock);
            auth_user->next = NULL;
Michael Smith's avatar
Michael Smith committed
189

190
191
192
193
            if (auth_user->process)
                auth_user->process (auth_user);
            else
                ERROR0 ("client auth process not set");
Michael Smith's avatar
Michael Smith committed
194

195
            auth_client_free (auth_user);
Michael Smith's avatar
Michael Smith committed
196

197
            continue;
198
        }
199
200
201
202
        /* is there a request to shutdown */
        if (auth_running == 0)
            break;
        thread_sleep (150000);
203
    }
204
205
206
    INFO0 ("Authenication thread shutting down");
    return NULL;
}
Michael Smith's avatar
Michael Smith committed
207
208


209
210
211
212
213
214
215
216
217
218
219
/* 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
220

221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
    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)
        {   
            client_t *client = (client_t *)node->key;
            if (client->username && strcmp (client->username, client->username) == 0)
            {
                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)
        {
            client_t *client = (client_t *)node->key;
            if (client->username && strcmp (client->username, client->username) == 0)
            {
                avl_tree_unlock (source->pending_tree);
                return 0;
Michael Smith's avatar
Michael Smith committed
248
            }
249
            node = avl_get_next (node);
Michael Smith's avatar
Michael Smith committed
250
        }
251
        avl_tree_unlock (source->pending_tree);
Michael Smith's avatar
Michael Smith committed
252
    }
253
    return 1;
Michael Smith's avatar
Michael Smith committed
254
255
}

256
257
258
259
260

/* 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
 */
static int add_client_to_source (source_t *source, client_t *client)
Michael Smith's avatar
Michael Smith committed
261
{
262
    int loop = 10;
263
264
265
266
267
268
269
270
    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
271

272
273
274
275
276
277
278
279
        if (loop && source->fallback_when_full && source->fallback_mount)
        {
            source_t *next = source_find_mount (source->fallback_mount);
            INFO1 ("stream full trying %s", next->mount);
            source = next;
            loop--;
            continue;
        }
280
281
        /* now we fail the client */
        return -1;
Michael Smith's avatar
Michael Smith committed
282

283
284
285
286
287
288
    } while (1);
    /* 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);
    stats_event_inc (NULL, "listener_connections");
Michael Smith's avatar
Michael Smith committed
289

290
291
292
293
    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
294

295
296
297
298
299
300
    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;
        slave_rescan ();
Michael Smith's avatar
Michael Smith committed
301
    }
302
303
304
    DEBUG1 ("Added client to %s", source->mount);
    return 0;
}
Michael Smith's avatar
Michael Smith committed
305
306


307
308
309
310
311
312
313
/* Add listener to the pending lists of either the  source or fserve thread.
 * This can be run from the connection or auth thread context
 */
static int add_authenticated_client (const char *mount, mount_proxy *mountinfo, client_t *client)
{
    int ret = 0;
    source_t *source = NULL;
314

315
316
    avl_tree_rlock (global.source_tree);
    source = source_find_mount (mount);
Michael Smith's avatar
Michael Smith committed
317

318
319
320
321
322
323
    if (source)
    {
        if (client->auth && check_duplicate_logins (source, client) == 0)
        {
            avl_tree_unlock (global.source_tree);
            return -1;
324
        }
325
326
327
328
329
330
        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;
        }
331

332
333
334
335
        ret = add_client_to_source (source, client);
        avl_tree_unlock (global.source_tree);
        if (ret == 0)
            DEBUG0 ("client authenticated, passed to source");
336
    }
337
338
339
340
341
    else
    {
        avl_tree_unlock (global.source_tree);
        fserve_client_create (client, mount);
    }
342
343
    return ret;
}
344
345
346


int auth_postprocess_client (auth_client *auth_user)
347
{
348
349
    int ret;
    ice_config_t *config = config_get_config();
350

351
352
    mount_proxy *mountinfo = config_find_mount (config, auth_user->mount);
    auth_user->client->authenticated = 1;
353

354
355
    ret = add_authenticated_client (auth_user->mount, mountinfo, auth_user->client);
    config_release_config();
356

357
358
359
    if (ret < 0)
        client_send_401 (auth_user->client);
    auth_user->client = NULL;
360

361
    return ret;
362
363
}

364
365
366
367
368

/* Add a listener. Check for any mount information that states any
 * authentication to be used.
 */
void add_client (const char *mount, client_t *client)
369
{
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
    mount_proxy *mountinfo; 
    ice_config_t *config = config_get_config();

    mountinfo = config_find_mount (config, mount);
    if (mountinfo && mountinfo->no_mount)
    {
        config_release_config ();
        client_send_404 (client, "mountpoint unavailable");
        return;
    }
    if (mountinfo && mountinfo->auth)
    {
        auth_client *auth_user;

        if (auth_pending_count > 30)
        {
            config_release_config ();
            WARN0 ("too many clients awaiting authentication");
            client_send_404 (client, "busy, please try again later");
            return;
        }
        auth_client_setup (mountinfo, client);
        config_release_config ();

        if (client->auth == NULL)
        {
            client_send_401 (client);
            return;
398
        }
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
        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
    {
        int ret = add_authenticated_client (mount, mountinfo, client);
        config_release_config ();
        if (ret < 0)
            client_send_404 (client, "stream full");
418
419
420
    }
}

421
422
423
424
425

/* determine whether we need to process this client further. This
 * involves any auth exit, typically for external auth servers.
 */
int release_client (client_t *client)
426
{
427
    if (client->auth)
428
    {
429
430
431
        auth_client *auth_user = calloc (1, sizeof (auth_client));
        if (auth_user == NULL)
            return 0;
432

433
434
435
        auth_user->mount = strdup (httpp_getvar (client->parser, HTTPP_VAR_URI));
        auth_user->process = auth_remove_listener;
        auth_user->client = client;
436

437
438
439
440
441
        queue_auth_client (auth_user);
        return 1;
    }
    return 0;
}
442
443


444
445
446
447
448
static void get_authenticator (auth_t *auth, config_options_t *options)
{
    do
    {
        DEBUG1 ("type is %s", auth->type);
Karl Heyes's avatar
Karl Heyes committed
449
450
451
452
453
454
455
#ifdef HAVE_AUTH_URL
        if (strcmp (auth->type, "url") == 0)
        {
            auth_get_url_auth (auth, options);
            break;
        }
#endif
456
457
458
459
        if (strcmp (auth->type, "htpasswd") == 0)
        {
            auth_get_htpasswd_auth (auth, options);
            break;
460
        }
461
462
463
464
465
466
467
468
469
470
471
        
        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;
472
    }
473
}
474
475


476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
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);
512
    }
513
514
515
516
517
518
519
520
521
    auth->type = xmlGetProp (node, "type");
    get_authenticator (auth, options);
    while (options)
    {
        config_options_t *opt = options;
        options = opt->next;
        xmlFree (opt->name);
        xmlFree (opt->value);
        free (opt);
522
    }
523
524
    return auth;
}
525
526


527
528
529
530
/* called when the stream starts, so that authentication engine can do any
 * cleanup/initialisation.
 */
void auth_stream_start (mount_proxy *mountinfo, const char *mount)
531
{
532
533
534
535
536
537
538
    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);
            auth_user->process = mountinfo->auth->stream_start;
539

540
            queue_auth_client (auth_user);
541
542
543
544
545
        }
    }
}


546
547
548
549
550
551
552
553
554
555
556
557
/* 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);
            auth_user->process = mountinfo->auth->stream_end;
558

559
560
            queue_auth_client (auth_user);
        }
561
    }
562
}
563
564


565
/* these are called at server start and termination */
566

567
568
569
570
571
572
573
void auth_initialise ()
{
    clients_to_auth = NULL;
    auth_pending_count = 0;
    auth_running = 1;
    thread_mutex_create (&auth_lock);
    auth_thread = thread_create ("auth thread", auth_run_thread, NULL, THREAD_ATTACHED);
574
575
}

576
void auth_shutdown ()
577
{
578
579
580
581
582
    if (auth_thread)
    {
        auth_running = 0;
        thread_join (auth_thread);
        INFO0 ("Auth thread has terminated");
583
584
    }
}
585