connection.c 47.7 KB
Newer Older
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,
7 8 9 10
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
 *                      Karl Heyes <karl@xiph.org>
 *                      and others (see AUTHORS for details).
Philipp Schafft's avatar
Philipp Schafft committed
11 12
 * Copyright 2011,      Dave 'justdave' Miller <justdave@mozilla.com>,
 * Copyright 2011-2014, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
13 14
 */

15
/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */
16 17 18 19
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

Jack Moffitt's avatar
Jack Moffitt committed
20 21
#include <stdio.h>
#include <stdlib.h>
22
#include <errno.h>
Jack Moffitt's avatar
Jack Moffitt committed
23
#include <string.h>
24 25 26
#ifdef HAVE_POLL
#include <sys/poll.h>
#endif
27
#include <sys/types.h>
28 29

#ifndef _WIN32
Jack Moffitt's avatar
Jack Moffitt committed
30 31
#include <sys/socket.h>
#include <netinet/in.h>
32
#else
33
#include <winsock2.h>
34
#endif
Jack Moffitt's avatar
Jack Moffitt committed
35

36
#include "compat.h"
Jack Moffitt's avatar
Jack Moffitt committed
37

Marvin Scholz's avatar
Marvin Scholz committed
38 39 40 41
#include "common/thread/thread.h"
#include "common/avl/avl.h"
#include "common/net/sock.h"
#include "common/httpp/httpp.h"
Jack Moffitt's avatar
Jack Moffitt committed
42

43
#include "cfgfile.h"
Jack Moffitt's avatar
Jack Moffitt committed
44 45 46 47 48 49 50
#include "global.h"
#include "util.h"
#include "connection.h"
#include "refbuf.h"
#include "client.h"
#include "stats.h"
#include "logging.h"
51
#include "xslt.h"
52
#include "fserve.h"
53
#include "sighandler.h"
54 55

#include "yp.h"
Jack Moffitt's avatar
Jack Moffitt committed
56
#include "source.h"
57
#include "format.h"
58
#include "format_mp3.h"
59
#include "admin.h"
Michael Smith's avatar
Michael Smith committed
60
#include "auth.h"
61
#include "matchfile.h"
Jack Moffitt's avatar
Jack Moffitt committed
62 63 64

#define CATMODULE "connection"

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
/* Two different major types of source authentication.
   Shoutcast style is used only by the Shoutcast DSP
   and is a crazy version of HTTP.  It looks like :
     Source Client -> Connects to port + 1
     Source Client -> sends encoder password (plaintext)\r\n
     Icecast -> reads encoder password, if ok, sends OK2\r\n, else disconnects
     Source Client -> reads OK2\r\n, then sends http-type request headers
                      that contain the stream details (icy-name, etc..)
     Icecast -> reads headers, stores them
     Source Client -> starts sending MP3 data
     Source Client -> periodically updates metadata via admin.cgi call

   Icecast auth style uses HTTP and Basic Authorization.
*/

80 81 82 83 84
typedef struct client_queue_tag {
    client_t *client;
    int offset;
    int stream_offset;
    int shoutcast;
85
    char *shoutcast_mount;
86 87
    struct client_queue_tag *next;
} client_queue_t;
Jack Moffitt's avatar
Jack Moffitt committed
88 89

typedef struct _thread_queue_tag {
90 91
    thread_type *thread_id;
    struct _thread_queue_tag *next;
Jack Moffitt's avatar
Jack Moffitt committed
92 93
} thread_queue_t;

94
static spin_t _connection_lock; // protects _current_id, _con_queue, _con_queue_tail
95
static volatile unsigned long _current_id = 0;
Jack Moffitt's avatar
Jack Moffitt committed
96 97
static int _initialized = 0;

98 99
static volatile client_queue_t *_req_queue = NULL, **_req_queue_tail = &_req_queue;
static volatile client_queue_t *_con_queue = NULL, **_con_queue_tail = &_con_queue;
100 101 102 103 104
static int ssl_ok;
#ifdef HAVE_OPENSSL
static SSL_CTX *ssl_ctx;
#endif

105
/* filtering client connection based on IP */
106
static matchfile_t *banned_ip, *allowed_ip;
107

108
rwlock_t _source_shutdown_rwlock;
Jack Moffitt's avatar
Jack Moffitt committed
109

110
static void _handle_connection(void);
Jack Moffitt's avatar
Jack Moffitt committed
111 112 113

void connection_initialize(void)
{
Marvin Scholz's avatar
Marvin Scholz committed
114 115
    if (_initialized)
        return;
116

117
    thread_spin_create (&_connection_lock);
118
    thread_mutex_create(&move_clients_mutex);
119
    thread_rwlock_create(&_source_shutdown_rwlock);
120
    thread_cond_create(&global.shutdown_cond);
121 122 123 124
    _req_queue = NULL;
    _req_queue_tail = &_req_queue;
    _con_queue = NULL;
    _con_queue_tail = &_con_queue;
Jack Moffitt's avatar
Jack Moffitt committed
125

126
    _initialized = 1;
Jack Moffitt's avatar
Jack Moffitt committed
127 128 129 130
}

void connection_shutdown(void)
{
Marvin Scholz's avatar
Marvin Scholz committed
131 132
    if (!_initialized)
        return;
133

134 135 136
#ifdef HAVE_OPENSSL
    SSL_CTX_free (ssl_ctx);
#endif
137 138 139
    matchfile_release(banned_ip);
    matchfile_release(allowed_ip);
 
140
    thread_cond_destroy(&global.shutdown_cond);
141
    thread_rwlock_destroy(&_source_shutdown_rwlock);
142
    thread_spin_destroy (&_connection_lock);
143
    thread_mutex_destroy(&move_clients_mutex);
Jack Moffitt's avatar
Jack Moffitt committed
144

145
    _initialized = 0;
Jack Moffitt's avatar
Jack Moffitt committed
146 147 148 149
}

static unsigned long _next_connection_id(void)
{
150
    unsigned long id;
Jack Moffitt's avatar
Jack Moffitt committed
151

152
    thread_spin_lock(&_connection_lock);
153
    id = _current_id++;
154
    thread_spin_unlock(&_connection_lock);
Jack Moffitt's avatar
Jack Moffitt committed
155

156
    return id;
Jack Moffitt's avatar
Jack Moffitt committed
157 158
}

159 160

#ifdef HAVE_OPENSSL
Marvin Scholz's avatar
Marvin Scholz committed
161
static void get_ssl_certificate(ice_config_t *config)
162
{
163
    SSL_METHOD *method;
164
    long ssl_opts;
165
    config->tls_ok = ssl_ok = 0;
166

167 168
    SSL_load_error_strings(); /* readable error messages */
    SSL_library_init(); /* initialize library */
169 170

    method = SSLv23_server_method();
Marvin Scholz's avatar
Marvin Scholz committed
171 172
    ssl_ctx = SSL_CTX_new(method);
    ssl_opts = SSL_CTX_get_options(ssl_ctx);
173
#ifdef SSL_OP_NO_COMPRESSION
Marvin Scholz's avatar
Marvin Scholz committed
174
    SSL_CTX_set_options(ssl_ctx, ssl_opts|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION);
175
#else
Marvin Scholz's avatar
Marvin Scholz committed
176
    SSL_CTX_set_options(ssl_ctx, ssl_opts|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
177
#endif
178

Marvin Scholz's avatar
Marvin Scholz committed
179
    do {
180 181
        if (config->cert_file == NULL)
            break;
Marvin Scholz's avatar
Marvin Scholz committed
182
        if (SSL_CTX_use_certificate_chain_file (ssl_ctx, config->cert_file) <= 0) {
183
            ICECAST_LOG_WARN("Invalid cert file %s", config->cert_file);
184 185
            break;
        }
Marvin Scholz's avatar
Marvin Scholz committed
186
        if (SSL_CTX_use_PrivateKey_file (ssl_ctx, config->cert_file, SSL_FILETYPE_PEM) <= 0) {
187
            ICECAST_LOG_WARN("Invalid private key file %s", config->cert_file);
188 189
            break;
        }
Marvin Scholz's avatar
Marvin Scholz committed
190
        if (!SSL_CTX_check_private_key (ssl_ctx)) {
191
            ICECAST_LOG_ERROR("Invalid %s - Private key does not match cert public key", config->cert_file);
192 193
            break;
        }
Marvin Scholz's avatar
Marvin Scholz committed
194
        if (SSL_CTX_set_cipher_list(ssl_ctx, config->cipher_list) <= 0) {
195 196
            ICECAST_LOG_WARN("Invalid cipher list: %s", config->cipher_list);
        }
197
        config->tls_ok = ssl_ok = 1;
198 199
        ICECAST_LOG_INFO("Certificate found at %s", config->cert_file);
        ICECAST_LOG_INFO("Using ciphers %s", config->cipher_list);
200
        return;
201
    } while (0);
202
    ICECAST_LOG_INFO("No TLS capability on any configured ports");
203 204 205 206 207 208
}


/* handlers for reading and writing a connection_t when there is ssl
 * configured on the listening port
 */
Marvin Scholz's avatar
Marvin Scholz committed
209
static int connection_read_ssl(connection_t *con, void *buf, size_t len)
210
{
Marvin Scholz's avatar
Marvin Scholz committed
211
    int bytes = SSL_read(con->ssl, buf, len);
212

Marvin Scholz's avatar
Marvin Scholz committed
213 214
    if (bytes < 0) {
        switch (SSL_get_error(con->ssl, bytes)) {
215 216
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_WRITE:
217
            return -1;
218 219 220 221 222 223
        }
        con->error = 1;
    }
    return bytes;
}

Marvin Scholz's avatar
Marvin Scholz committed
224
static int connection_send_ssl(connection_t *con, const void *buf, size_t len)
225 226 227
{
    int bytes = SSL_write (con->ssl, buf, len);

Marvin Scholz's avatar
Marvin Scholz committed
228 229
    if (bytes < 0) {
        switch (SSL_get_error(con->ssl, bytes)){
230 231 232 233 234
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_WRITE:
                return -1;
        }
        con->error = 1;
Marvin Scholz's avatar
Marvin Scholz committed
235
    } else {
236
        con->sent_bytes += bytes;
Marvin Scholz's avatar
Marvin Scholz committed
237
    }
238 239 240 241 242
    return bytes;
}
#else

/* SSL not compiled in, so at least log it */
243
static void get_ssl_certificate(ice_config_t *config)
244 245
{
    ssl_ok = 0;
246 247
    ICECAST_LOG_INFO("No TLS capability. "
                     "Rebuild Icecast with openSSL support to enable this.");
248 249 250 251 252 253 254
}
#endif /* HAVE_OPENSSL */


/* handlers (default) for reading and writing a connection_t, no encrpytion
 * used just straight access to the socket
 */
Marvin Scholz's avatar
Marvin Scholz committed
255
static int connection_read(connection_t *con, void *buf, size_t len)
256
{
Marvin Scholz's avatar
Marvin Scholz committed
257
    int bytes = sock_read_bytes(con->sock, buf, len);
258 259
    if (bytes == 0)
        con->error = 1;
Marvin Scholz's avatar
Marvin Scholz committed
260
    if (bytes == -1 && !sock_recoverable(sock_error()))
261 262 263 264
        con->error = 1;
    return bytes;
}

Marvin Scholz's avatar
Marvin Scholz committed
265
static int connection_send(connection_t *con, const void *buf, size_t len)
266
{
Marvin Scholz's avatar
Marvin Scholz committed
267 268 269
    int bytes = sock_write_bytes(con->sock, buf, len);
    if (bytes < 0) {
        if (!sock_recoverable(sock_error()))
270
            con->error = 1;
Marvin Scholz's avatar
Marvin Scholz committed
271
    } else {
272
        con->sent_bytes += bytes;
Marvin Scholz's avatar
Marvin Scholz committed
273
    }
274 275 276 277
    return bytes;
}


278
/* return 0 if the passed ip address is not to be handled by icecast, non-zero otherwise */
279 280 281 282
static int accept_ip_address(char *ip) {
    if (matchfile_match(banned_ip, ip) > 0) {
        ICECAST_LOG_DEBUG("%s is banned", ip);
        return 0;
283
    }
284 285 286 287 288 289 290 291

    if (matchfile_match(allowed_ip, ip) > 0) {
        ICECAST_LOG_DEBUG("%s is allowed", ip);
        return 1;
    } else if (allowed_ip) {
        /* we are not on allow list but there is one, so reject */
        ICECAST_LOG_DEBUG("%s is not allowed", ip);
        return 0;
292
    }
293 294

    /* default: allow */
295 296 297 298
    return 1;
}


299 300
connection_t *connection_create (sock_t sock, sock_t serversock, char *ip)
{
301
    connection_t *con;
302
    con = (connection_t *)calloc(1, sizeof(connection_t));
Marvin Scholz's avatar
Marvin Scholz committed
303 304
    if (con) {
        con->sock       = sock;
305
        con->serversock = serversock;
Marvin Scholz's avatar
Marvin Scholz committed
306 307 308 309 310
        con->con_time   = time(NULL);
        con->id         = _next_connection_id();
        con->ip         = ip;
        con->read       = connection_read;
        con->send       = connection_send;
311
    }
312

313
    return con;
314 315
}

316 317
/* prepare connection for interacting over a SSL connection
 */
318
void connection_uses_ssl(connection_t *con)
319 320
{
#ifdef HAVE_OPENSSL
321 322 323
    if (con->ssl)
        return;

324 325
    con->read = connection_read_ssl;
    con->send = connection_send_ssl;
Marvin Scholz's avatar
Marvin Scholz committed
326 327 328
    con->ssl = SSL_new(ssl_ctx);
    SSL_set_accept_state(con->ssl);
    SSL_set_fd(con->ssl, con->sock);
329 330 331
#endif
}

332 333 334 335 336
ssize_t connection_read_bytes(connection_t *con, void *buf, size_t len)
{
    return con->read(con, buf, len);
}

337
static sock_t wait_for_serversock(int timeout)
338 339
{
#ifdef HAVE_POLL
340
    struct pollfd ufds [global.server_sockets];
341 342 343 344 345 346 347 348 349 350
    int i, ret;

    for(i=0; i < global.server_sockets; i++) {
        ufds[i].fd = global.serversock[i];
        ufds[i].events = POLLIN;
        ufds[i].revents = 0;
    }

    ret = poll(ufds, global.server_sockets, timeout);
    if(ret < 0) {
351
        return SOCK_ERROR;
Marvin Scholz's avatar
Marvin Scholz committed
352
    } else if(ret == 0) {
353
        return SOCK_ERROR;
Marvin Scholz's avatar
Marvin Scholz committed
354
    } else {
355
        int dst;
356
        for(i=0; i < global.server_sockets; i++) {
357
            if(ufds[i].revents & POLLIN)
358
                return ufds[i].fd;
Marvin Scholz's avatar
Marvin Scholz committed
359 360
            if(ufds[i].revents & (POLLHUP|POLLERR|POLLNVAL)) {
                if (ufds[i].revents & (POLLHUP|POLLERR)) {
361
                    sock_close (global.serversock[i]);
362
                    ICECAST_LOG_WARN("Had to close a listening socket");
363
                }
364
                global.serversock[i] = SOCK_ERROR;
365
            }
366
        }
367
        /* remove any closed sockets */
Marvin Scholz's avatar
Marvin Scholz committed
368
        for(i=0, dst=0; i < global.server_sockets; i++) {
369
            if (global.serversock[i] == SOCK_ERROR)
370
            continue;
371
            if (i!=dst)
372
            global.serversock[dst] = global.serversock[i];
373 374 375
            dst++;
        }
        global.server_sockets = dst;
376
        return SOCK_ERROR;
377 378 379 380 381
    }
#else
    fd_set rfds;
    struct timeval tv, *p=NULL;
    int i, ret;
382
    sock_t max = SOCK_ERROR;
383 384 385 386 387

    FD_ZERO(&rfds);

    for(i=0; i < global.server_sockets; i++) {
        FD_SET(global.serversock[i], &rfds);
388
        if (max == SOCK_ERROR || global.serversock[i] > max)
389 390 391 392 393
            max = global.serversock[i];
    }

    if(timeout >= 0) {
        tv.tv_sec = timeout/1000;
394
        tv.tv_usec = (timeout % 1000) * 1000;
395 396 397 398 399
        p = &tv;
    }

    ret = select(max+1, &rfds, NULL, NULL, p);
    if(ret < 0) {
400
        return SOCK_ERROR;
Marvin Scholz's avatar
Marvin Scholz committed
401
    } else if(ret == 0) {
402
        return SOCK_ERROR;
Marvin Scholz's avatar
Marvin Scholz committed
403
    } else {
404 405 406 407
        for(i=0; i < global.server_sockets; i++) {
            if(FD_ISSET(global.serversock[i], &rfds))
                return global.serversock[i];
        }
408
        return SOCK_ERROR; /* Should be impossible, stop compiler warnings */
409 410 411 412
    }
#endif
}

413
static connection_t *_accept_connection(int duration)
Jack Moffitt's avatar
Jack Moffitt committed
414
{
415
    sock_t sock, serversock;
416
    char *ip;
Jack Moffitt's avatar
Jack Moffitt committed
417

418
    serversock = wait_for_serversock (duration);
419
    if (serversock == SOCK_ERROR)
420
        return NULL;
Jack Moffitt's avatar
Jack Moffitt committed
421

422 423
    /* malloc enough room for a full IP address (including ipv6) */
    ip = (char *)malloc(MAX_ADDR_LEN);
Jack Moffitt's avatar
Jack Moffitt committed
424

425
    sock = sock_accept(serversock, ip, MAX_ADDR_LEN);
Marvin Scholz's avatar
Marvin Scholz committed
426
    if (sock != SOCK_ERROR) {
427
        connection_t *con = NULL;
428
        /* Make any IPv4 mapped IPv6 address look like a normal IPv4 address */
Marvin Scholz's avatar
Marvin Scholz committed
429 430
        if (strncmp(ip, "::ffff:", 7) == 0)
            memmove(ip, ip+7, strlen (ip+7)+1);
Jack Moffitt's avatar
Jack Moffitt committed
431

Marvin Scholz's avatar
Marvin Scholz committed
432 433
        if (accept_ip_address(ip))
            con = connection_create(sock, serversock, ip);
434 435
        if (con)
            return con;
Marvin Scholz's avatar
Marvin Scholz committed
436 437 438
        sock_close(sock);
    } else {
        if (!sock_recoverable(sock_error())) {
439
            ICECAST_LOG_WARN("accept() failed with error %d: %s", sock_error(), strerror(sock_error()));
Marvin Scholz's avatar
Marvin Scholz committed
440
            thread_sleep(500000);
441
        }
442 443 444
    }
    free(ip);
    return NULL;
Jack Moffitt's avatar
Jack Moffitt committed
445 446 447
}


448 449 450 451
/* add client to connection queue. At this point some header information
 * has been collected, so we now pass it onto the connection thread for
 * further processing
 */
452
static void _add_connection(client_queue_t *node)
Jack Moffitt's avatar
Jack Moffitt committed
453
{
454
    thread_spin_lock(&_connection_lock);
455
    *_con_queue_tail = node;
456 457
    _con_queue_tail = (volatile client_queue_t **) &node->next;
    thread_spin_unlock(&_connection_lock);
Jack Moffitt's avatar
Jack Moffitt committed
458 459 460
}


461 462 463 464 465 466
/* this returns queued clients for the connection thread. headers are
 * already provided, but need to be parsed.
 */
static client_queue_t *_get_connection(void)
{
    client_queue_t *node = NULL;
Jack Moffitt's avatar
Jack Moffitt committed
467

Marvin Scholz's avatar
Marvin Scholz committed
468
    thread_spin_lock(&_connection_lock);
469

Marvin Scholz's avatar
Marvin Scholz committed
470
    if (_con_queue){
471 472 473 474
        node = (client_queue_t *)_con_queue;
        _con_queue = node->next;
        if (_con_queue == NULL)
            _con_queue_tail = &_con_queue;
475
        node->next = NULL;
476
    }
477

Marvin Scholz's avatar
Marvin Scholz committed
478
    thread_spin_unlock(&_connection_lock);
479 480
    return node;
}
Jack Moffitt's avatar
Jack Moffitt committed
481 482


483
/* run along queue checking for any data that has come in or a timeout */
484
static void process_request_queue (void)
485 486
{
    client_queue_t **node_ref = (client_queue_t **)&_req_queue;
Marvin Scholz's avatar
Marvin Scholz committed
487
    ice_config_t *config = config_get_config();
488 489
    int timeout = config->header_timeout;
    config_release_config();
Jack Moffitt's avatar
Jack Moffitt committed
490

Marvin Scholz's avatar
Marvin Scholz committed
491
    while (*node_ref) {
492 493 494 495
        client_queue_t *node = *node_ref;
        client_t *client = node->client;
        int len = PER_CLIENT_REFBUF_SIZE - 1 - node->offset;
        char *buf = client->refbuf->data + node->offset;
Jack Moffitt's avatar
Jack Moffitt committed
496

Marvin Scholz's avatar
Marvin Scholz committed
497 498
        if (len > 0) {
            if (client->con->con_time + timeout <= time(NULL)) {
499
                len = 0;
Marvin Scholz's avatar
Marvin Scholz committed
500 501 502
            } else {
                len = client_read_bytes(client, buf, len);
            }
503
        }
Jack Moffitt's avatar
Jack Moffitt committed
504

Marvin Scholz's avatar
Marvin Scholz committed
505
        if (len > 0) {
506 507 508
            int pass_it = 1;
            char *ptr;

509 510
            /* handle \n, \r\n and nsvcap which for some strange reason has
             * EOL as \r\r\n */
511
            node->offset += len;
Marvin Scholz's avatar
Marvin Scholz committed
512 513 514
            client->refbuf->data[node->offset] = '\000';
            do {
                if (node->shoutcast == 1) {
515
                    /* password line */
516 517
                    if (strstr (client->refbuf->data, "\r\r\n") != NULL)
                        break;
518 519 520 521 522 523 524
                    if (strstr (client->refbuf->data, "\r\n") != NULL)
                        break;
                    if (strstr (client->refbuf->data, "\n") != NULL)
                        break;
                }
                /* stream_offset refers to the start of any data sent after the
                 * http style headers, we don't want to lose those */
Marvin Scholz's avatar
Marvin Scholz committed
525 526
                ptr = strstr(client->refbuf->data, "\r\r\n\r\r\n");
                if (ptr) {
527 528 529
                    node->stream_offset = (ptr+6) - client->refbuf->data;
                    break;
                }
Marvin Scholz's avatar
Marvin Scholz committed
530 531
                ptr = strstr(client->refbuf->data, "\r\n\r\n");
                if (ptr) {
532 533 534
                    node->stream_offset = (ptr+4) - client->refbuf->data;
                    break;
                }
Marvin Scholz's avatar
Marvin Scholz committed
535 536
                ptr = strstr(client->refbuf->data, "\n\n");
                if (ptr) {
537 538 539 540 541
                    node->stream_offset = (ptr+2) - client->refbuf->data;
                    break;
                }
                pass_it = 0;
            } while (0);
Jack Moffitt's avatar
Jack Moffitt committed
542

Marvin Scholz's avatar
Marvin Scholz committed
543
            if (pass_it) {
544 545 546 547
                if ((client_queue_t **)_req_queue_tail == &(node->next))
                    _req_queue_tail = (volatile client_queue_t **)node_ref;
                *node_ref = node->next;
                node->next = NULL;
Marvin Scholz's avatar
Marvin Scholz committed
548
                _add_connection(node);
549
                continue;
550
            }
Marvin Scholz's avatar
Marvin Scholz committed
551 552
        } else {
            if (len == 0 || client->con->error) {
553 554 555
                if ((client_queue_t **)_req_queue_tail == &node->next)
                    _req_queue_tail = (volatile client_queue_t **)node_ref;
                *node_ref = node->next;
Marvin Scholz's avatar
Marvin Scholz committed
556 557
                client_destroy(client);
                free(node);
558 559 560 561
                continue;
            }
        }
        node_ref = &node->next;
562
    }
563
    _handle_connection();
Jack Moffitt's avatar
Jack Moffitt committed
564 565
}

566

567 568 569
/* add node to the queue of requests. This is where the clients are when
 * initial http details are read.
 */
Marvin Scholz's avatar
Marvin Scholz committed
570
static void _add_request_queue(client_queue_t *node)
571 572 573
{
    *_req_queue_tail = node;
    _req_queue_tail = (volatile client_queue_t **)&node->next;
Jack Moffitt's avatar
Jack Moffitt committed
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
static client_queue_t *create_client_node(client_t *client)
{
    client_queue_t *node = calloc (1, sizeof (client_queue_t));
    ice_config_t *config;
    listener_t *listener;

    if (!node)
        return NULL;

    node->client = client;

    config = config_get_config();
    listener = config_get_listen_sock(config, client->con);

    if (listener) {
        if (listener->shoutcast_compat)
            node->shoutcast = 1;
        if (listener->ssl && ssl_ok)
            connection_uses_ssl(client->con);
        if (listener->shoutcast_mount)
            node->shoutcast_mount = strdup(listener->shoutcast_mount);
    }

    config_release_config();

    return node;
}
603

604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
void connection_queue(connection_t *con)
{
    client_queue_t *node;
    client_t *client = NULL;

    global_lock();
    if (client_create(&client, con, NULL) < 0) {
        global_unlock();
        client_send_error(client, 403, 1, "Icecast connection limit reached");
        /* don't be too eager as this is an imposed hard limit */
        thread_sleep(400000);
        return;
    }

    /* setup client for reading incoming http */
    client->refbuf->data[PER_CLIENT_REFBUF_SIZE-1] = '\000';

    if (sock_set_blocking(client->con->sock, 0) || sock_set_nodelay(client->con->sock)) {
        global_unlock();
623
        ICECAST_LOG_WARN("Failed to set tcp options on client connection, dropping");
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638
        client_destroy(client);
        return;
    }
    node = create_client_node(client);
    global_unlock();

    if (node == NULL) {
        client_destroy(client);
        return;
    }

    _add_request_queue(node);
    stats_event_inc(NULL, "connections");
}

Marvin Scholz's avatar
Marvin Scholz committed
639
void connection_accept_loop(void)
Jack Moffitt's avatar
Jack Moffitt committed
640
{
641
    connection_t *con;
642
    ice_config_t *config;
643
    int duration = 300;
644

Marvin Scholz's avatar
Marvin Scholz committed
645 646 647
    config = config_get_config();
    get_ssl_certificate(config);
    config_release_config();
Jack Moffitt's avatar
Jack Moffitt committed
648

Marvin Scholz's avatar
Marvin Scholz committed
649
    while (global.running == ICECAST_RUNNING) {
650
        con = _accept_connection (duration);
651

Marvin Scholz's avatar
Marvin Scholz committed
652
        if (con) {
653
            connection_queue(con);
654
            duration = 5;
Marvin Scholz's avatar
Marvin Scholz committed
655
        } else {
656 657
            if (_req_queue == NULL)
                duration = 300; /* use longer timeouts when nothing waiting */
658
        }
Marvin Scholz's avatar
Marvin Scholz committed
659
        process_request_queue();
660
    }
Jack Moffitt's avatar
Jack Moffitt committed
661

662 663 664
    /* Give all the other threads notification to shut down */
    thread_cond_broadcast(&global.shutdown_cond);

665 666 667
    /* wait for all the sources to shutdown */
    thread_rwlock_wlock(&_source_shutdown_rwlock);
    thread_rwlock_unlock(&_source_shutdown_rwlock);
Jack Moffitt's avatar
Jack Moffitt committed
668 669
}

670 671 672

/* Called when activating a source. Verifies that the source count is not
 * exceeded and applies any initial parameters.
673
 */
Marvin Scholz's avatar
Marvin Scholz committed
674
int connection_complete_source(source_t *source, int response)
675
{
676
    ice_config_t *config;
677

Marvin Scholz's avatar
Marvin Scholz committed
678
    global_lock();
679
    ICECAST_LOG_DEBUG("sources count is %d", global.sources);
680

681
    config = config_get_config();
Marvin Scholz's avatar
Marvin Scholz committed
682
    if (global.sources < config->source_limit) {
683
        const char *contenttype;
684
        mount_proxy *mountinfo;
685 686 687 688
        format_type_t format_type;

        /* setup format handler */
        contenttype = httpp_getvar (source->parser, "content-type");
Marvin Scholz's avatar
Marvin Scholz committed
689 690
        if (contenttype != NULL) {
            format_type = format_get_type(contenttype);
691

Marvin Scholz's avatar
Marvin Scholz committed
692
            if (format_type == FORMAT_ERROR) {
693
                config_release_config();
694
                global_unlock();
695
                if (response) {
696
                    client_send_error(source->client, 403, 1, "Content-type not supported");
697 698
                    source->client = NULL;
                }
699
                ICECAST_LOG_WARN("Content-type \"%s\" not supported, dropping source", contenttype);
700 701
                return -1;
            }
702 703 704 705
        } else if (source->parser->req_type == httpp_req_put) {
            config_release_config();
            global_unlock();
            if (response) {
706
                client_send_error(source->client, 403, 1, "No Content-type given");
707 708 709 710 711 712 713 714
                source->client = NULL;
            }
            ICECAST_LOG_ERROR("Content-type not given in PUT request, dropping source");
            return -1;
        } else {
            ICECAST_LOG_ERROR("No content-type header, falling back to backwards compatibility mode "
                    "for icecast 1.x relays. Assuming content is mp3. This behaviour is deprecated "
                    "and the source client will NOT work with future Icecast versions!");
715
            format_type = FORMAT_TYPE_GENERIC;
716 717
        }

Marvin Scholz's avatar
Marvin Scholz committed
718
        if (format_get_plugin (format_type, source) < 0) {
719 720
            global_unlock();
            config_release_config();
Marvin Scholz's avatar
Marvin Scholz committed
721
            if (response) {
722
                client_send_error(source->client, 403, 1, "internal format allocation problem");
723 724
                source->client = NULL;
            }
725
            ICECAST_LOG_WARN("plugin format failed for \"%s\"", source->mount);
726 727 728
            return -1;
        }

729
        global.sources++;
Marvin Scholz's avatar
Marvin Scholz committed
730
        stats_event_args(NULL, "sources", "%d", global.sources);
731
        global_unlock();
732

733
        source->running = 1;
Marvin Scholz's avatar
Marvin Scholz committed
734 735
        mountinfo = config_find_mount(config, source->mount, MOUNT_TYPE_NORMAL);
        source_update_settings(config, source, mountinfo);
736
        config_release_config();
737
        slave_rebuild_mounts();
738 739

        source->shutdown_rwlock = &_source_shutdown_rwlock;
740
        ICECAST_LOG_DEBUG("source is ready to start");
741 742 743

        return 0;
    }
744
    ICECAST_LOG_WARN("Request to add source when maximum source limit "
Marvin Scholz's avatar
Marvin Scholz committed
745
        "reached %d", global.sources);
746 747 748 749

    global_unlock();
    config_release_config();

Marvin Scholz's avatar
Marvin Scholz committed
750
    if (response) {
751
        client_send_error(source->client, 403, 1, "too many sources connected");
752 753
        source->client = NULL;
    }
754 755 756 757

    return -1;
}

Marvin Scholz's avatar
Marvin Scholz committed
758
static inline void source_startup(client_t *client, const char *uri)
759 760
{
    source_t *source;
Marvin Scholz's avatar
Marvin Scholz committed
761
    source = source_reserve(uri);
762

Marvin Scholz's avatar
Marvin Scholz committed
763
    if (source) {
764
        source->client = client;
765 766
        source->parser = client->parser;
        source->con = client->con;
Marvin Scholz's avatar
Marvin Scholz committed
767 768 769
        if (connection_complete_source(source, 1) < 0) {
            source_clear_source(source);
            source_free_source(source);
770 771 772
            return;
        }
        client->respcode = 200;
Philipp Schafft's avatar
Philipp Schafft committed
773 774 775 776 777
        if (client->protocol == ICECAST_PROTOCOL_SHOUTCAST) {
            client->respcode = 200;
            /* send this non-blocking but if there is only a partial write
             * then leave to header timeout */
            sock_write (client->con->sock, "OK2\r\nicy-caps:11\r\n\r\n");
778
            source->shoutcast_compat = 1;
Marvin Scholz's avatar
Marvin Scholz committed
779
            source_client_callback(client, source);
Philipp Schafft's avatar
Philipp Schafft committed
780
        } else {
Marvin Scholz's avatar
Marvin Scholz committed
781
            refbuf_t *ok = refbuf_new(PER_CLIENT_REFBUF_SIZE);
782
            const char *expectcontinue;
783
            const char *transfer_encoding;
784 785
            int status_to_send = 200;

786 787 788 789 790 791 792 793 794
            transfer_encoding = httpp_getvar(source->parser, "transfer-encoding");
            if (transfer_encoding && strcasecmp(transfer_encoding, HTTPP_ENCODING_IDENTITY) != 0) {
                client->encoding = httpp_encoding_new(transfer_encoding);
                if (!client->encoding) {
                    client_send_error(client, 501, 1, "Unimplemented");
                    return;
                }
            }

795 796 797 798 799 800 801
            /* For PUT support we check for 100-continue and send back a 100 to stay in spec */
            expectcontinue = httpp_getvar (source->parser, "expect");

            if (expectcontinue != NULL) {
#ifdef HAVE_STRCASESTR
                if (strcasestr (expectcontinue, "100-continue") != NULL)
#else
802
                ICECAST_LOG_WARN("OS doesn't support case insensitive substring checks...");
803 804 805 806 807 808 809
                if (strstr (expectcontinue, "100-continue") != NULL)
#endif
                {
                    status_to_send = 100;
                }
            }

810
            client->respcode = 200;
811
            util_http_build_header(ok->data, PER_CLIENT_REFBUF_SIZE, 0, 0, status_to_send, NULL, NULL, NULL, "", NULL, client);
Marvin Scholz's avatar
Marvin Scholz committed
812
            ok->len = strlen(ok->data);
813 814 815
            /* we may have unprocessed data read in, so don't overwrite it */
            ok->associated = client->refbuf;
            client->refbuf = ok;
Marvin Scholz's avatar
Marvin Scholz committed
816
            fserve_add_client_callback(client, source_client_callback, source);
817
        }
Marvin Scholz's avatar
Marvin Scholz committed
818
    } else {
819
        client_send_error(client, 403, 1, "Mountpoint in use");
820
        ICECAST_LOG_WARN("Mountpoint %s in use", uri);
821
    }
822 823
}

Philipp Schafft's avatar
Philipp Schafft committed
824
/* only called for native icecast source clients */
Marvin Scholz's avatar
Marvin Scholz committed
825
static void _handle_source_request(client_t *client, const char *uri)
Jack Moffitt's avatar
Jack Moffitt committed
826
{
827 828
    ICECAST_LOG_INFO("Source logging in at mountpoint \"%s\" from %s as role %s",
        uri, client->con->ip, client->role);
829

Marvin Scholz's avatar
Marvin Scholz committed
830
    if (uri[0] != '/') {
Philipp Schafft's avatar
Philipp Schafft committed
831 832
        ICECAST_LOG_WARN("source mountpoint not starting with /");
        client_send_error(client, 400, 1, "source mountpoint not starting with /");
833
        return;
834
    }
835

Philipp Schafft's avatar
Philipp Schafft committed
836 837 838 839
    source_startup(client, uri);
}


Marvin Scholz's avatar
Marvin Scholz committed
840
static void _handle_stats_request(client_t *client, char *uri)
Philipp Schafft's avatar
Philipp Schafft committed
841 842 843
{
    stats_event_inc(NULL, "stats_connections");

844
    client->respcode = 200;
845
    snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
Marvin Scholz's avatar
Marvin Scholz committed
846 847 848
        "HTTP/1.0 200 OK\r\n\r\n");
    client->refbuf->len = strlen(client->refbuf->data);
    fserve_add_client_callback(client, stats_callback, NULL);
849 850
}

Philipp Schafft's avatar
Philipp Schafft committed
851 852 853
/* 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
 */
Marvin Scholz's avatar
Marvin Scholz committed
854
static int __add_listener_to_source(source_t *source, client_t *client)
855
{
Philipp Schafft's avatar
Philipp Schafft committed
856
    size_t loop = 10;
857

Marvin Scholz's avatar
Marvin Scholz committed
858
    do {
Philipp Schafft's avatar
Philipp Schafft committed
859
        ICECAST_LOG_DEBUG("max on %s is %ld (cur %lu)", source->mount,
Marvin Scholz's avatar
Marvin Scholz committed
860
            source->max_listeners, source->listeners);
Philipp Schafft's avatar
Philipp Schafft committed
861 862 863 864
        if (source->max_listeners == -1)
            break;
        if (source->listeners < (unsigned long)source->max_listeners)
            break;
865

Marvin Scholz's avatar
Marvin Scholz committed
866
        if (loop && source->fallback_when_full && source->fallback_mount) {
Philipp Schafft's avatar
Philipp Schafft committed
867 868 869
            source_t *next = source_find_mount (source->fallback_mount);
            if (!next) {
                ICECAST_LOG_ERROR("Fallback '%s' for full source '%s' not found",
Marvin Scholz's avatar
Marvin Scholz committed
870
                    source->mount, source->fallback_mount);
Philipp Schafft's avatar
Philipp Schafft committed
871 872
                return -1;
            }
873
            ICECAST_LOG_INFO("stream full, trying %s", next->mount);
Philipp Schafft's avatar
Philipp Schafft committed
874 875 876 877 878 879 880 881 882 883 884
            source = next;
            loop--;
            continue;
        }
        /* now we fail the client */
        return -1;
    } while (1);

    client->write_to_client = format_generic_write_to_client;
    client->check_buffer = format_check_http_buffer;
    client->refbuf->len = PER_CLIENT_REFBUF_SIZE;
Marvin Scholz's avatar
Marvin Scholz committed
885
    memset(client->refbuf->data, 0, PER_CLIENT_REFBUF_SIZE);
Philipp Schafft's avatar
Philipp Schafft committed
886 887

    /* lets add the client to the active list */
Marvin Scholz's avatar
Marvin Scholz committed
888 889 890
    avl_tree_wlock(source->pending_tree);
    avl_insert(source->pending_tree, client);
    avl_tree_unlock(source->pending_tree);
Philipp Schafft's avatar
Philipp Schafft committed
891

Marvin Scholz's avatar
Marvin Scholz committed
892
    if (source->running == 0 && source->on_demand) {
Philipp Schafft's avatar
Philipp Schafft committed
893 894 895
        /* enable on-demand relay to start, wake up the slave thread */
        ICECAST_LOG_DEBUG("kicking off on-demand relay");
        source->on_demand_req = 1;
896
    }
Philipp Schafft's avatar
Philipp Schafft committed
897 898 899
    ICECAST_LOG_DEBUG("Added client to %s", source->mount);
    return 0;
}
900

Philipp Schafft's avatar
Philipp Schafft committed
901 902 903 904 905 906 907 908 909 910 911 912
/* count the number of clients on a mount with same username and same role as the given one */
static inline ssize_t __count_user_role_on_mount (source_t *source, client_t *client) {
    ssize_t ret = 0;
    avl_node *node;

    avl_tree_rlock(source->client_tree);
    node = avl_get_first(source->client_tree);
    while (node) {
        client_t *existing_client = (client_t *)node->key;
        if (existing_client->username && client->username &&
            strcmp(existing_client->username, client->username) == 0 &&
            existing_client->role && client->role &&
Marvin Scholz's avatar
Marvin Scholz committed
913
            strcmp(existing_client->role, client->role) == 0) {
Philipp Schafft's avatar
Philipp Schafft committed
914
            ret++;
Marvin Scholz's avatar
Marvin Scholz committed
915
        }
Philipp Schafft's avatar
Philipp Schafft committed
916
        node = avl_get_next(node);
917
    }
Philipp Schafft's avatar
Philipp Schafft committed
918 919 920 921
    avl_tree_unlock(source->client_tree);

    avl_tree_rlock(source->pending_tree);
    node = avl_get_first(source->pending_tree);
Marvin Scholz's avatar
Marvin Scholz committed
922
    while (node) {
Philipp Schafft's avatar
Philipp Schafft committed
923 924 925 926
        client_t *existing_client = (client_t *)node->key;
        if (existing_client->username && client->username &&
            strcmp(existing_client->username, client->username) == 0 &&
            existing_client->role && client->role &&
Marvin Scholz's avatar
Marvin Scholz committed
927
            strcmp(existing_client->role, client->role) == 0){
Philipp Schafft's avatar
Philipp Schafft committed
928
            ret++;
Marvin Scholz's avatar
Marvin Scholz committed
929
        }
Philipp Schafft's avatar
Philipp Schafft committed
930
        node = avl_get_next(node);
931
    }
Philipp Schafft's avatar
Philipp Schafft committed
932 933 934 935
    avl_tree_unlock(source->pending_tree);
    return ret;
}

Marvin Scholz's avatar
Marvin Scholz committed
936
static void _handle_get_request(client_t *client, char *uri) {
Philipp Schafft's avatar
Philipp Schafft committed
937 938 939 940 941 942 943 944 945
    source_t *source = NULL;

    ICECAST_LOG_DEBUG("Got client %p with URI %H", client, uri);

    /* there are several types of HTTP GET clients
     * media clients, which are looking for a source (eg, URI = /stream.ogg),
     * stats clients, which are looking for /admin/stats.xml and
     * fserve clients, which are looking for static files.
     */
946 947

    stats_event_inc(NULL, "client_connections");
948

949
    /* Dispatch all admin requests */
950 951
    if ((strcmp(uri, "/admin.cgi") == 0) ||
        (strncmp(uri, "/admin/", 7) == 0)) {
Philipp Schafft's avatar
Philipp Schafft committed
952
        ICECAST_LOG_DEBUG("Client %p requesting admin interface.", client);
953
        admin_handle_request(client, uri);
Michael Smith's avatar
Michael Smith committed
954 955
        return;
    }
Philipp Schafft's avatar
Philipp Schafft committed
956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984

    /* this is a web/ request. let's check if we are allowed to do that. */
    if (acl_test_web(client->acl) != ACL_POLICY_ALLOW) {
        /* doesn't seem so, sad client :( */
        if (client->protocol == ICECAST_PROTOCOL_SHOUTCAST) {
            client_destroy(client);
        } else {
            client_send_error(client, 401, 1, "You need to authenticate\r\n");
        }
        return;
    }

    if (util_check_valid_extension(uri) == XSLT_CONTENT) {
        /* If the file exists, then transform it, otherwise, write a 404 */
        ICECAST_LOG_DEBUG("Stats request, sending XSL transformed stats");
        stats_transform_xslt(client, uri);
        return;
    }

    avl_tree_rlock(global.source_tree);
    /* let's see if this is a source or just a random fserve file */
    source = source_find_mount(uri);
    if (source) {
        /* true mount */
        int in_error = 0;
        ssize_t max_connections_per_user = acl_get_max_connections_per_user(client->acl);
        /* check for duplicate_logins */
        if (max_connections_per_user > 0) { /* -1 = not set (-> default=unlimited), 0 = unlimited */
            if (max_connections_per_user <= __count_user_role_on_mount(source, client)) {
Marvin Scholz's avatar
Marvin Scholz committed
985 986
                client_send_error(client, 403, 1, "Reached limit of concurrent "
                    "connections on those credentials");
Philipp Schafft's avatar
Philipp Schafft committed
987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
                in_error = 1;
            }
        }


        /* Set max listeni