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"
Michael Smith's avatar
Michael Smith committed
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
    }
Michael Smith's avatar
Michael Smith committed
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;
Michael Smith's avatar
Michael Smith committed
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