connection.c 47.1 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
    return bytes;
}

277 278
connection_t *connection_create (sock_t sock, sock_t serversock, char *ip)
{
279
    connection_t *con;
280
    con = (connection_t *)calloc(1, sizeof(connection_t));
Marvin Scholz's avatar
Marvin Scholz committed
281 282
    if (con) {
        con->sock       = sock;
283
        con->serversock = serversock;
Marvin Scholz's avatar
Marvin Scholz committed
284 285 286 287 288
        con->con_time   = time(NULL);
        con->id         = _next_connection_id();
        con->ip         = ip;
        con->read       = connection_read;
        con->send       = connection_send;
289
    }
290

291
    return con;
292 293
}

294 295
/* prepare connection for interacting over a SSL connection
 */
296
void connection_uses_ssl(connection_t *con)
297 298
{
#ifdef HAVE_OPENSSL
299 300 301
    if (con->ssl)
        return;

302 303
    con->read = connection_read_ssl;
    con->send = connection_send_ssl;
Marvin Scholz's avatar
Marvin Scholz committed
304 305 306
    con->ssl = SSL_new(ssl_ctx);
    SSL_set_accept_state(con->ssl);
    SSL_set_fd(con->ssl, con->sock);
307 308 309
#endif
}

310 311 312 313 314
ssize_t connection_read_bytes(connection_t *con, void *buf, size_t len)
{
    return con->read(con, buf, len);
}

315
static sock_t wait_for_serversock(int timeout)
316 317
{
#ifdef HAVE_POLL
318
    struct pollfd ufds [global.server_sockets];
319 320 321 322 323 324 325 326 327 328
    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) {
329
        return SOCK_ERROR;
Marvin Scholz's avatar
Marvin Scholz committed
330
    } else if(ret == 0) {
331
        return SOCK_ERROR;
Marvin Scholz's avatar
Marvin Scholz committed
332
    } else {
333
        int dst;
334
        for(i=0; i < global.server_sockets; i++) {
335
            if(ufds[i].revents & POLLIN)
336
                return ufds[i].fd;
Marvin Scholz's avatar
Marvin Scholz committed
337 338
            if(ufds[i].revents & (POLLHUP|POLLERR|POLLNVAL)) {
                if (ufds[i].revents & (POLLHUP|POLLERR)) {
339
                    sock_close (global.serversock[i]);
340
                    ICECAST_LOG_WARN("Had to close a listening socket");
341
                }
342
                global.serversock[i] = SOCK_ERROR;
343
            }
344
        }
345
        /* remove any closed sockets */
Marvin Scholz's avatar
Marvin Scholz committed
346
        for(i=0, dst=0; i < global.server_sockets; i++) {
347
            if (global.serversock[i] == SOCK_ERROR)
348
            continue;
349
            if (i!=dst)
350
            global.serversock[dst] = global.serversock[i];
351 352 353
            dst++;
        }
        global.server_sockets = dst;
354
        return SOCK_ERROR;
355 356 357 358 359
    }
#else
    fd_set rfds;
    struct timeval tv, *p=NULL;
    int i, ret;
360
    sock_t max = SOCK_ERROR;
361 362 363 364 365

    FD_ZERO(&rfds);

    for(i=0; i < global.server_sockets; i++) {
        FD_SET(global.serversock[i], &rfds);
366
        if (max == SOCK_ERROR || global.serversock[i] > max)
367 368 369 370 371
            max = global.serversock[i];
    }

    if(timeout >= 0) {
        tv.tv_sec = timeout/1000;
372
        tv.tv_usec = (timeout % 1000) * 1000;
373 374 375 376 377
        p = &tv;
    }

    ret = select(max+1, &rfds, NULL, NULL, p);
    if(ret < 0) {
378
        return SOCK_ERROR;
Marvin Scholz's avatar
Marvin Scholz committed
379
    } else if(ret == 0) {
380
        return SOCK_ERROR;
Marvin Scholz's avatar
Marvin Scholz committed
381
    } else {
382 383 384 385
        for(i=0; i < global.server_sockets; i++) {
            if(FD_ISSET(global.serversock[i], &rfds))
                return global.serversock[i];
        }
386
        return SOCK_ERROR; /* Should be impossible, stop compiler warnings */
387 388 389 390
    }
#endif
}

391
static connection_t *_accept_connection(int duration)
Jack Moffitt's avatar
Jack Moffitt committed
392
{
393
    sock_t sock, serversock;
394
    char *ip;
Jack Moffitt's avatar
Jack Moffitt committed
395

396
    serversock = wait_for_serversock (duration);
397
    if (serversock == SOCK_ERROR)
398
        return NULL;
Jack Moffitt's avatar
Jack Moffitt committed
399

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

403
    sock = sock_accept(serversock, ip, MAX_ADDR_LEN);
Marvin Scholz's avatar
Marvin Scholz committed
404
    if (sock != SOCK_ERROR) {
405
        connection_t *con = NULL;
406
        /* Make any IPv4 mapped IPv6 address look like a normal IPv4 address */
Marvin Scholz's avatar
Marvin Scholz committed
407 408
        if (strncmp(ip, "::ffff:", 7) == 0)
            memmove(ip, ip+7, strlen (ip+7)+1);
Jack Moffitt's avatar
Jack Moffitt committed
409

410 411
        if (matchfile_match_allow_deny(allowed_ip, banned_ip, ip))
            con = connection_create (sock, serversock, ip);
412 413
        if (con)
            return con;
Marvin Scholz's avatar
Marvin Scholz committed
414 415 416
        sock_close(sock);
    } else {
        if (!sock_recoverable(sock_error())) {
417
            ICECAST_LOG_WARN("accept() failed with error %d: %s", sock_error(), strerror(sock_error()));
Marvin Scholz's avatar
Marvin Scholz committed
418
            thread_sleep(500000);
419
        }
420 421 422
    }
    free(ip);
    return NULL;
Jack Moffitt's avatar
Jack Moffitt committed
423 424 425
}


426 427 428 429
/* 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
 */
430
static void _add_connection(client_queue_t *node)
Jack Moffitt's avatar
Jack Moffitt committed
431
{
432
    thread_spin_lock(&_connection_lock);
433
    *_con_queue_tail = node;
434 435
    _con_queue_tail = (volatile client_queue_t **) &node->next;
    thread_spin_unlock(&_connection_lock);
Jack Moffitt's avatar
Jack Moffitt committed
436 437 438
}


439 440 441 442 443 444
/* 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
445

Marvin Scholz's avatar
Marvin Scholz committed
446
    thread_spin_lock(&_connection_lock);
447

Marvin Scholz's avatar
Marvin Scholz committed
448
    if (_con_queue){
449 450 451 452
        node = (client_queue_t *)_con_queue;
        _con_queue = node->next;
        if (_con_queue == NULL)
            _con_queue_tail = &_con_queue;
453
        node->next = NULL;
454
    }
455

Marvin Scholz's avatar
Marvin Scholz committed
456
    thread_spin_unlock(&_connection_lock);
457 458
    return node;
}
Jack Moffitt's avatar
Jack Moffitt committed
459 460


461
/* run along queue checking for any data that has come in or a timeout */
462
static void process_request_queue (void)
463 464
{
    client_queue_t **node_ref = (client_queue_t **)&_req_queue;
Marvin Scholz's avatar
Marvin Scholz committed
465
    ice_config_t *config = config_get_config();
466 467
    int timeout = config->header_timeout;
    config_release_config();
Jack Moffitt's avatar
Jack Moffitt committed
468

Marvin Scholz's avatar
Marvin Scholz committed
469
    while (*node_ref) {
470 471 472 473
        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
474

Marvin Scholz's avatar
Marvin Scholz committed
475 476
        if (len > 0) {
            if (client->con->con_time + timeout <= time(NULL)) {
477
                len = 0;
Marvin Scholz's avatar
Marvin Scholz committed
478 479 480
            } else {
                len = client_read_bytes(client, buf, len);
            }
481
        }
Jack Moffitt's avatar
Jack Moffitt committed
482

Marvin Scholz's avatar
Marvin Scholz committed
483
        if (len > 0) {
484 485 486
            int pass_it = 1;
            char *ptr;

487 488
            /* handle \n, \r\n and nsvcap which for some strange reason has
             * EOL as \r\r\n */
489
            node->offset += len;
Marvin Scholz's avatar
Marvin Scholz committed
490 491 492
            client->refbuf->data[node->offset] = '\000';
            do {
                if (node->shoutcast == 1) {
493
                    /* password line */
494 495
                    if (strstr (client->refbuf->data, "\r\r\n") != NULL)
                        break;
496 497 498 499 500 501 502
                    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
503 504
                ptr = strstr(client->refbuf->data, "\r\r\n\r\r\n");
                if (ptr) {
505 506 507
                    node->stream_offset = (ptr+6) - client->refbuf->data;
                    break;
                }
Marvin Scholz's avatar
Marvin Scholz committed
508 509
                ptr = strstr(client->refbuf->data, "\r\n\r\n");
                if (ptr) {
510 511 512
                    node->stream_offset = (ptr+4) - client->refbuf->data;
                    break;
                }
Marvin Scholz's avatar
Marvin Scholz committed
513 514
                ptr = strstr(client->refbuf->data, "\n\n");
                if (ptr) {
515 516 517 518 519
                    node->stream_offset = (ptr+2) - client->refbuf->data;
                    break;
                }
                pass_it = 0;
            } while (0);
Jack Moffitt's avatar
Jack Moffitt committed
520

Marvin Scholz's avatar
Marvin Scholz committed
521
            if (pass_it) {
522 523 524 525
                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
526
                _add_connection(node);
527
                continue;
528
            }
Marvin Scholz's avatar
Marvin Scholz committed
529 530
        } else {
            if (len == 0 || client->con->error) {
531 532 533
                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
534 535
                client_destroy(client);
                free(node);
536 537 538 539
                continue;
            }
        }
        node_ref = &node->next;
540
    }
541
    _handle_connection();
Jack Moffitt's avatar
Jack Moffitt committed
542 543
}

544

545 546 547
/* 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
548
static void _add_request_queue(client_queue_t *node)
549 550 551
{
    *_req_queue_tail = node;
    _req_queue_tail = (volatile client_queue_t **)&node->next;
Jack Moffitt's avatar
Jack Moffitt committed
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
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;
}
581

582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
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();
601
        ICECAST_LOG_WARN("Failed to set tcp options on client connection, dropping");
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
        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
617
void connection_accept_loop(void)
Jack Moffitt's avatar
Jack Moffitt committed
618
{
619
    connection_t *con;
620
    ice_config_t *config;
621
    int duration = 300;
622

Marvin Scholz's avatar
Marvin Scholz committed
623 624 625
    config = config_get_config();
    get_ssl_certificate(config);
    config_release_config();
Jack Moffitt's avatar
Jack Moffitt committed
626

Marvin Scholz's avatar
Marvin Scholz committed
627
    while (global.running == ICECAST_RUNNING) {
628
        con = _accept_connection (duration);
629

Marvin Scholz's avatar
Marvin Scholz committed
630
        if (con) {
631
            connection_queue(con);
632
            duration = 5;
Marvin Scholz's avatar
Marvin Scholz committed
633
        } else {
634 635
            if (_req_queue == NULL)
                duration = 300; /* use longer timeouts when nothing waiting */
636
        }
Marvin Scholz's avatar
Marvin Scholz committed
637
        process_request_queue();
638
    }
Jack Moffitt's avatar
Jack Moffitt committed
639

640 641 642
    /* Give all the other threads notification to shut down */
    thread_cond_broadcast(&global.shutdown_cond);

643 644 645
    /* 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
646 647
}

648 649 650

/* Called when activating a source. Verifies that the source count is not
 * exceeded and applies any initial parameters.
651
 */
Marvin Scholz's avatar
Marvin Scholz committed
652
int connection_complete_source(source_t *source, int response)
653
{
654
    ice_config_t *config;
655

Marvin Scholz's avatar
Marvin Scholz committed
656
    global_lock();
657
    ICECAST_LOG_DEBUG("sources count is %d", global.sources);
658

659
    config = config_get_config();
Marvin Scholz's avatar
Marvin Scholz committed
660
    if (global.sources < config->source_limit) {
661
        const char *contenttype;
662
        mount_proxy *mountinfo;
663 664 665 666
        format_type_t format_type;

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

Marvin Scholz's avatar
Marvin Scholz committed
670
            if (format_type == FORMAT_ERROR) {
671
                config_release_config();
672
                global_unlock();
673
                if (response) {
674
                    client_send_error(source->client, 403, 1, "Content-type not supported");
675 676
                    source->client = NULL;
                }
677
                ICECAST_LOG_WARN("Content-type \"%s\" not supported, dropping source", contenttype);
678 679
                return -1;
            }
680 681 682 683
        } else if (source->parser->req_type == httpp_req_put) {
            config_release_config();
            global_unlock();
            if (response) {
684
                client_send_error(source->client, 403, 1, "No Content-type given");
685 686 687 688 689 690 691 692
                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!");
693
            format_type = FORMAT_TYPE_GENERIC;
694 695
        }

Marvin Scholz's avatar
Marvin Scholz committed
696
        if (format_get_plugin (format_type, source) < 0) {
697 698
            global_unlock();
            config_release_config();
Marvin Scholz's avatar
Marvin Scholz committed
699
            if (response) {
700
                client_send_error(source->client, 403, 1, "internal format allocation problem");
701 702
                source->client = NULL;
            }
703
            ICECAST_LOG_WARN("plugin format failed for \"%s\"", source->mount);
704 705 706
            return -1;
        }

707
        global.sources++;
Marvin Scholz's avatar
Marvin Scholz committed
708
        stats_event_args(NULL, "sources", "%d", global.sources);
709
        global_unlock();
710

711
        source->running = 1;
Marvin Scholz's avatar
Marvin Scholz committed
712 713
        mountinfo = config_find_mount(config, source->mount, MOUNT_TYPE_NORMAL);
        source_update_settings(config, source, mountinfo);
714
        config_release_config();
715
        slave_rebuild_mounts();
716 717

        source->shutdown_rwlock = &_source_shutdown_rwlock;
718
        ICECAST_LOG_DEBUG("source is ready to start");
719 720 721

        return 0;
    }
722
    ICECAST_LOG_WARN("Request to add source when maximum source limit "
Marvin Scholz's avatar
Marvin Scholz committed
723
        "reached %d", global.sources);
724 725 726 727

    global_unlock();
    config_release_config();

Marvin Scholz's avatar
Marvin Scholz committed
728
    if (response) {
729
        client_send_error(source->client, 403, 1, "too many sources connected");
730 731
        source->client = NULL;
    }
732 733 734 735

    return -1;
}

Marvin Scholz's avatar
Marvin Scholz committed
736
static inline void source_startup(client_t *client, const char *uri)
737 738
{
    source_t *source;
Marvin Scholz's avatar
Marvin Scholz committed
739
    source = source_reserve(uri);
740

Marvin Scholz's avatar
Marvin Scholz committed
741
    if (source) {
742
        source->client = client;
743 744
        source->parser = client->parser;
        source->con = client->con;
Marvin Scholz's avatar
Marvin Scholz committed
745 746 747
        if (connection_complete_source(source, 1) < 0) {
            source_clear_source(source);
            source_free_source(source);
748 749 750
            return;
        }
        client->respcode = 200;
Philipp Schafft's avatar
Philipp Schafft committed
751 752 753 754 755
        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");
756
            source->shoutcast_compat = 1;
Marvin Scholz's avatar
Marvin Scholz committed
757
            source_client_callback(client, source);
Philipp Schafft's avatar
Philipp Schafft committed
758
        } else {
Marvin Scholz's avatar
Marvin Scholz committed
759
            refbuf_t *ok = refbuf_new(PER_CLIENT_REFBUF_SIZE);
760
            const char *expectcontinue;
761
            const char *transfer_encoding;
762 763
            int status_to_send = 200;

764 765 766 767 768 769 770 771 772
            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;
                }
            }

773 774 775 776 777 778 779
            /* 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
780
                ICECAST_LOG_WARN("OS doesn't support case insensitive substring checks...");
781 782 783 784 785 786 787
                if (strstr (expectcontinue, "100-continue") != NULL)
#endif
                {
                    status_to_send = 100;
                }
            }

788
            client->respcode = 200;
789
            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
790
            ok->len = strlen(ok->data);
791 792 793
            /* 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
794
            fserve_add_client_callback(client, source_client_callback, source);
795
        }
Marvin Scholz's avatar
Marvin Scholz committed
796
    } else {
797
        client_send_error(client, 403, 1, "Mountpoint in use");
798
        ICECAST_LOG_WARN("Mountpoint %s in use", uri);
799
    }
800 801
}

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

Marvin Scholz's avatar
Marvin Scholz committed
808
    if (uri[0] != '/') {
Philipp Schafft's avatar
Philipp Schafft committed
809 810
        ICECAST_LOG_WARN("source mountpoint not starting with /");
        client_send_error(client, 400, 1, "source mountpoint not starting with /");
811
        return;
812
    }
813

Philipp Schafft's avatar
Philipp Schafft committed
814 815 816 817
    source_startup(client, uri);
}


Marvin Scholz's avatar
Marvin Scholz committed
818
static void _handle_stats_request(client_t *client, char *uri)
Philipp Schafft's avatar
Philipp Schafft committed
819 820 821
{
    stats_event_inc(NULL, "stats_connections");

822
    client->respcode = 200;
823
    snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
Marvin Scholz's avatar
Marvin Scholz committed
824 825 826
        "HTTP/1.0 200 OK\r\n\r\n");
    client->refbuf->len = strlen(client->refbuf->data);
    fserve_add_client_callback(client, stats_callback, NULL);
827 828
}

Philipp Schafft's avatar
Philipp Schafft committed
829 830 831
/* 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
832
static int __add_listener_to_source(source_t *source, client_t *client)
833
{
Philipp Schafft's avatar
Philipp Schafft committed
834
    size_t loop = 10;
835

Marvin Scholz's avatar
Marvin Scholz committed
836
    do {
Philipp Schafft's avatar
Philipp Schafft committed
837
        ICECAST_LOG_DEBUG("max on %s is %ld (cur %lu)", source->mount,
Marvin Scholz's avatar
Marvin Scholz committed
838
            source->max_listeners, source->listeners);
Philipp Schafft's avatar
Philipp Schafft committed
839 840 841 842
        if (source->max_listeners == -1)
            break;
        if (source->listeners < (unsigned long)source->max_listeners)
            break;
843

Marvin Scholz's avatar
Marvin Scholz committed
844
        if (loop && source->fallback_when_full && source->fallback_mount) {
Philipp Schafft's avatar
Philipp Schafft committed
845 846 847
            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
848
                    source->mount, source->fallback_mount);
Philipp Schafft's avatar
Philipp Schafft committed
849 850
                return -1;
            }
851
            ICECAST_LOG_INFO("stream full, trying %s", next->mount);
Philipp Schafft's avatar
Philipp Schafft committed
852 853 854 855 856 857 858 859 860 861 862
            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
863
    memset(client->refbuf->data, 0, PER_CLIENT_REFBUF_SIZE);
Philipp Schafft's avatar
Philipp Schafft committed
864 865

    /* lets add the client to the active list */
Marvin Scholz's avatar
Marvin Scholz committed
866 867 868
    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
869

Marvin Scholz's avatar
Marvin Scholz committed
870
    if (source->running == 0 && source->on_demand) {
Philipp Schafft's avatar
Philipp Schafft committed
871 872 873
        /* enable on-demand relay to start, wake up the slave thread */
        ICECAST_LOG_DEBUG("kicking off on-demand relay");
        source->on_demand_req = 1;
874
    }
Philipp Schafft's avatar
Philipp Schafft committed
875 876 877
    ICECAST_LOG_DEBUG("Added client to %s", source->mount);
    return 0;
}
878

Philipp Schafft's avatar
Philipp Schafft committed
879 880 881 882 883 884 885 886 887 888 889 890
/* 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
891
            strcmp(existing_client->role, client->role) == 0) {
Philipp Schafft's avatar
Philipp Schafft committed
892
            ret++;
Marvin Scholz's avatar
Marvin Scholz committed
893
        }
Philipp Schafft's avatar
Philipp Schafft committed
894
        node = avl_get_next(node);
895
    }
Philipp Schafft's avatar
Philipp Schafft committed
896 897 898 899
    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
900
    while (node) {
Philipp Schafft's avatar
Philipp Schafft committed
901 902 903 904
        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
905
            strcmp(existing_client->role, client->role) == 0){
Philipp Schafft's avatar
Philipp Schafft committed
906
            ret++;
Marvin Scholz's avatar
Marvin Scholz committed
907
        }
Philipp Schafft's avatar
Philipp Schafft committed
908
        node = avl_get_next(node);
909
    }
Philipp Schafft's avatar
Philipp Schafft committed
910 911 912 913
    avl_tree_unlock(source->pending_tree);
    return ret;
}

Marvin Scholz's avatar
Marvin Scholz committed
914
static void _handle_get_request(client_t *client, char *uri) {
Philipp Schafft's avatar
Philipp Schafft committed
915 916 917 918 919 920 921 922 923
    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.
     */
924 925

    stats_event_inc(NULL, "client_connections");
926

927
    /* Dispatch all admin requests */
928 929
    if ((strcmp(uri, "/admin.cgi") == 0) ||
        (strncmp(uri, "/admin/", 7) == 0)) {
Philipp Schafft's avatar
Philipp Schafft committed
930
        ICECAST_LOG_DEBUG("Client %p requesting admin interface.", client);
931
        admin_handle_request(client, uri);
Michael Smith's avatar
Michael Smith committed
932 933
        return;
    }
Philipp Schafft's avatar
Philipp Schafft committed
934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962

    /* 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
963 964
                client_send_error(client, 403, 1, "Reached limit of concurrent "
                    "connections on those credentials");
Philipp Schafft's avatar
Philipp Schafft committed
965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992
                in_error = 1;
            }
        }


        /* Set max listening duration in case not already set. */
        if (!in_error && client->con->discon_time == 0) {
            time_t connection_duration = acl_get_max_connection_duration(client->acl);
            if (connection_duration == -1) {
                ice_config_t *config = config_get_config();
                mount_proxy *mount = config_find_mount(config, source->mount, MOUNT_TYPE_NORMAL);
                if (mount && mount->max_listener_duration)
                    connection_duration = mount->max_listener_duration;
                config_release_config();
            }

            if (connection_duration > 0) /* -1 = not set (-> default=unlimited), 0 = unlimited */
                client->con->discon_time = connection_duration + time(NULL);
        }
        if (!in_error && __add_listener_to_source(source, client) == -1) {
            client_send_error(client, 403, 1, "Rejecting client for whatever reason");
        }
        avl_tree_unlock(global.source_tree);
    } else {
        /* file */
        avl_tree_unlock(global.source_tree);
        fserve_client_create(client, uri);
    }
993 994
}

Marvin Scholz's avatar
Marvin Scholz committed
995
static void _handle_shoutcast_compatible(client_queue_t *node)
996
{
997 998 999
    char *http_compliant;
    int http_compliant_len = 0;
    http_parser_t *parser;
Philipp Schafft's avatar
Philipp Schafft committed
1000
    const char *shoutcast_mount;
1001
    client_t *client = node->client;