shout.c 32 KB
Newer Older
1
/* -*- c-basic-offset: 8; -*- */
2 3
/* shout.c: Implementation of public libshout interface shout.h
 *
Philipp Schafft's avatar
Philipp Schafft committed
4
 *  Copyright (C) 2002-2004 the Icecast team <team@icecast.org>,
5
 *  Copyright (C) 2012-2019 Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 21
 *
 * $Id$
22
 */
23

24
#ifdef HAVE_CONFIG_H
25
#   include <config.h>
26 27
#endif

28
#include <stdarg.h>
Jack Moffitt's avatar
Jack Moffitt committed
29
#include <stdio.h>
30
#include <stdlib.h>
Jack Moffitt's avatar
Jack Moffitt committed
31
#include <string.h>
32 33 34
#ifdef HAVE_STRINGS_H
#   include <strings.h>
#endif
Michael Smith's avatar
Michael Smith committed
35
#include <errno.h>
Jack Moffitt's avatar
Jack Moffitt committed
36

37
#include <shout/shout.h>
38

Marvin Scholz's avatar
Marvin Scholz committed
39
#include <common/net/sock.h>
40 41
#include <common/timing/timing.h>
#include <common/httpp/httpp.h>
Jack Moffitt's avatar
Jack Moffitt committed
42

43
#include "shout_private.h"
44
#include "util.h"
Jack Moffitt's avatar
Jack Moffitt committed
45

brendan's avatar
brendan committed
46
#ifdef _MSC_VER
47 48 49 50 51
#   ifndef va_copy
#       define va_copy(ap1, ap2) memcpy(&ap1, &ap2, sizeof(va_list))
#   endif
#   define vsnprintf      _vsnprintf
#   define inline         _inline
52 53
#endif

54
/* -- local prototypes -- */
55
static int shout_cb_connection_callback(shout_connection_t *con, shout_event_t event, void *userdata, va_list ap);
56
static int try_connect(shout_t *self);
Philipp Schafft's avatar
Philipp Schafft committed
57

58 59 60
/* -- static data -- */
static int _initialized = 0;

61
/* -- public functions -- */
Jack Moffitt's avatar
Jack Moffitt committed
62

63 64
void shout_init(void)
{
65
    if (_initialized)
66
        return;
67

68 69
    sock_initialize();
    _initialized = 1;
70 71 72 73
}

void shout_shutdown(void)
{
74
    if (!_initialized)
75
        return;
76

77 78
    sock_shutdown();
    _initialized = 0;
79 80
}

81 82
shout_t *shout_new(void)
{
83
    shout_t *self;
Jack Moffitt's avatar
Jack Moffitt committed
84

85 86 87
    /* in case users haven't done this explicitly. Should we error
     * if not initialized instead? */
    shout_init();
Jack Moffitt's avatar
Jack Moffitt committed
88

89 90 91 92 93 94 95 96
    if (!(self = (shout_t*)calloc(1, sizeof(shout_t)))) {
        return NULL;
    }

    if (shout_set_host(self, LIBSHOUT_DEFAULT_HOST) != SHOUTERR_SUCCESS) {
        shout_free(self);
        return NULL;
    }
97

98 99 100 101
    if (shout_set_user(self, LIBSHOUT_DEFAULT_USER) != SHOUTERR_SUCCESS) {
        shout_free(self);
        return NULL;
    }
102

103 104 105 106
    if (shout_set_agent(self, LIBSHOUT_DEFAULT_USERAGENT) != SHOUTERR_SUCCESS) {
        shout_free(self);
        return NULL;
    }
107

108 109 110 111
    if (!(self->audio_info = _shout_util_dict_new())) {
        shout_free(self);
        return NULL;
    }
112

113 114 115 116
    if (!(self->meta = _shout_util_dict_new())) {
        shout_free(self);
        return NULL;
    }
117

118 119 120 121
    if (shout_set_meta(self, "name", "no name") != SHOUTERR_SUCCESS) {
        shout_free(self);
        return NULL;
    }
Philipp Schafft's avatar
Philipp Schafft committed
122

Philipp Schafft's avatar
Philipp Schafft committed
123
#ifdef HAVE_OPENSSL
124 125 126 127
    if (shout_set_allowed_ciphers(self, LIBSHOUT_DEFAULT_ALLOWED_CIPHERS) != SHOUTERR_SUCCESS) {
        shout_free(self);
        return NULL;
    }
128

129
    self->tls_mode      = SHOUT_TLS_AUTO;
Philipp Schafft's avatar
Philipp Schafft committed
130
#endif
Jack Moffitt's avatar
Jack Moffitt committed
131

132 133
    self->port      = LIBSHOUT_DEFAULT_PORT;
    self->format    = LIBSHOUT_DEFAULT_FORMAT;
134
    self->usage     = LIBSHOUT_DEFAULT_USAGE;
135
    self->protocol  = LIBSHOUT_DEFAULT_PROTOCOL;
Jack Moffitt's avatar
Jack Moffitt committed
136

137
    return self;
Jack Moffitt's avatar
Jack Moffitt committed
138 139
}

140
void shout_free(shout_t *self)
Jack Moffitt's avatar
Jack Moffitt committed
141
{
142 143
    if (!self)
        return;
144

145
    if (!self->connection)
146
        return;
Philipp Schafft's avatar
Philipp Schafft committed
147

148 149 150 151 152 153 154 155 156 157 158 159 160 161
    if (self->host)
        free(self->host);
    if (self->password)
        free(self->password);
    if (self->mount)
        free(self->mount);
    if (self->user)
        free(self->user);
    if (self->useragent)
        free(self->useragent);
    if (self->audio_info)
        _shout_util_dict_free (self->audio_info);
    if (self->meta)
        _shout_util_dict_free (self->meta);
162

Philipp Schafft's avatar
Philipp Schafft committed
163
#ifdef HAVE_OPENSSL
164 165 166 167 168 169 170 171
    if (self->ca_directory)
        free(self->ca_directory);
    if (self->ca_file)
        free(self->ca_file);
    if (self->allowed_ciphers)
        free(self->allowed_ciphers);
    if (self->client_certificate)
        free(self->client_certificate);
Philipp Schafft's avatar
Philipp Schafft committed
172 173
#endif

174
    free(self);
Jack Moffitt's avatar
Jack Moffitt committed
175 176
}

177
int shout_open(shout_t *self)
Jack Moffitt's avatar
Jack Moffitt committed
178
{
179
    /* sanity check */
180
    if (!self)
181
        return SHOUTERR_INSANE;
182
    if (self->connection)
183
        return SHOUTERR_CONNECTED;
184
    if (!self->host || !self->password || !self->port)
185
        return self->error = SHOUTERR_INSANE;
186
    if (self->format == SHOUT_FORMAT_OGG &&  (self->protocol != SHOUT_PROTOCOL_HTTP && self->protocol != SHOUT_PROTOCOL_ROARAUDIO))
187
        return self->error = SHOUTERR_UNSUPPORTED;
188

189
    return self->error = try_connect(self);
Jack Moffitt's avatar
Jack Moffitt committed
190 191
}

192 193

int shout_close(shout_t *self)
Jack Moffitt's avatar
Jack Moffitt committed
194
{
195
    if (!self)
196
        return SHOUTERR_INSANE;
197

198
    if (!self->connection)
199
        return self->error = SHOUTERR_UNCONNECTED;
Jack Moffitt's avatar
Jack Moffitt committed
200

201
    if (self->connection && self->connection->current_message_state == SHOUT_MSGSTATE_SENDING1 && self->close)
202
        self->close(self);
Jack Moffitt's avatar
Jack Moffitt committed
203

204
    shout_connection_unref(self->connection);
205
    self->connection = NULL;
206 207
    self->starttime = 0;
    self->senttime = 0;
Jack Moffitt's avatar
Jack Moffitt committed
208

209
    return self->error = SHOUTERR_SUCCESS;
210
}
Jack Moffitt's avatar
Jack Moffitt committed
211

212 213
int shout_send(shout_t *self, const unsigned char *data, size_t len)
{
214
    if (!self)
215
        return SHOUTERR_INSANE;
216

217
    if (!self->connection || self->connection->current_message_state != SHOUT_MSGSTATE_SENDING1)
218
        return self->error = SHOUTERR_UNCONNECTED;
219

220
    if (self->starttime <= 0)
221
        self->starttime = timing_get_time();
222

223
    if (!len)
224
        return shout_connection_iter(self->connection, self);
225

226
    return self->send(self, data, len);
227 228 229 230
}

ssize_t shout_send_raw(shout_t *self, const unsigned char *data, size_t len)
{
231 232
    ssize_t ret;

233
    if (!self)
234
        return SHOUTERR_INSANE;
235

236
    if (!self->connection || self->connection->current_message_state != SHOUT_MSGSTATE_SENDING1)
237
        return SHOUTERR_UNCONNECTED;
238

239 240 241 242
    ret = shout_connection_send(self->connection, self, data, len);
    if (ret < 0)
       shout_connection_transfer_error(self->connection, self);
    return ret;
Jack Moffitt's avatar
Jack Moffitt committed
243 244
}

245 246
ssize_t shout_queuelen(shout_t *self)
{
247
    if (!self)
248
        return -1;
249

250
    return shout_connection_get_sendq(self->connection, self);
251 252 253
}


254
void shout_sync(shout_t *self)
Jack Moffitt's avatar
Jack Moffitt committed
255
{
256
    int64_t sleep;
Jack Moffitt's avatar
Jack Moffitt committed
257

258
    if (!self)
259
        return;
260

261
    if (self->senttime == 0)
262
        return;
Jack Moffitt's avatar
Jack Moffitt committed
263

264
    sleep = self->senttime / 1000 - (timing_get_time() - self->starttime);
265
    if (sleep > 0)
266
        timing_sleep((uint64_t)sleep);
267

Jack Moffitt's avatar
Jack Moffitt committed
268 269
}

270 271
int shout_delay(shout_t *self)
{
272

273
    if (!self)
274
        return 0;
275

276
    if (self->senttime == 0)
277
        return 0;
278

279
    return self->senttime / 1000 - (timing_get_time() - self->starttime);
280
}
281

brendan's avatar
brendan committed
282
shout_metadata_t *shout_metadata_new(void)
Michael Smith's avatar
Michael Smith committed
283
{
284
    return _shout_util_dict_new();
Michael Smith's avatar
Michael Smith committed
285 286
}

brendan's avatar
brendan committed
287 288
void shout_metadata_free(shout_metadata_t *self)
{
289
    if (!self)
290
        return;
brendan's avatar
brendan committed
291

292
    _shout_util_dict_free(self);
brendan's avatar
brendan committed
293 294 295 296
}

int shout_metadata_add(shout_metadata_t *self, const char *name, const char *value)
{
297
    if (!self || !name)
298
        return SHOUTERR_INSANE;
brendan's avatar
brendan committed
299

300
    return _shout_util_dict_set(self, name, value);
brendan's avatar
brendan committed
301 302 303 304
}

int shout_set_metadata(shout_t *self, shout_metadata_t *metadata)
{
305 306 307 308 309
    shout_connection_t *connection;
    shout_http_plan_t plan;
    size_t param_len;
    char *param = NULL;
    char *encvalue;
310 311
    char *encpassword;
    char *encmount;
312
    const char *param_template;
313 314
    int ret;
    int error;
315

316
    if (!self || !metadata)
317
        return SHOUTERR_INSANE;
brendan's avatar
brendan committed
318

319 320 321
    encvalue = _shout_util_dict_urlencode(metadata, '&');
    if (!encvalue)
        return self->error = SHOUTERR_MALLOC;
322

323 324 325
    memset(&plan, 0, sizeof(plan));

    plan.is_source = 0;
326

327
    switch (self->protocol) {
328
        case SHOUT_PROTOCOL_ICY:
329 330 331 332 333
            if (!(encpassword = _shout_util_url_encode(self->password))) {
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }

334
            param_template = "mode=updinfo&pass=%s&%s";
335
            param_len = strlen(param_template) + strlen(encvalue) + 1 + strlen(encpassword);
336 337
            param = malloc(param_len);
            if (!param) {
338
                free(encpassword);
339 340 341
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }
342 343
            snprintf(param, param_len, param_template, encpassword, encvalue);
            free(encpassword);
344 345 346 347 348 349

            plan.param = param;
            plan.fake_ua = 1;
            plan.auth = 0;
            plan.method = "GET";
            plan.resource = "/admin.cgi";
350
        break;
351
        case SHOUT_PROTOCOL_HTTP:
352 353 354 355 356
            if (!(encmount = _shout_util_url_encode(self->mount))) {
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }

357
            param_template = "mode=updinfo&mount=%s&%s";
358
            param_len = strlen(param_template) + strlen(encvalue) + 1 + strlen(encmount);
359 360
            param = malloc(param_len);
            if (!param) {
361
                free(encmount);
362 363 364
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }
365 366
            snprintf(param, param_len, param_template, encmount, encvalue);
            free(encmount);
367 368 369 370

            plan.param = param;
            plan.auth = 1;
            plan.resource = "/admin/metadata";
371
        break;
372
        case SHOUT_PROTOCOL_XAUDIOCAST:
373 374 375 376 377 378 379 380 381 382
            if (!(encmount = _shout_util_url_encode(self->mount))) {
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }
            if (!(encpassword = _shout_util_url_encode(self->password))) {
                free(encmount);
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }

383
            param_template = "mode=updinfo&pass=%s&mount=%s&%s";
384
            param_len = strlen(param_template) + strlen(encvalue) + 1 + strlen(encpassword) + strlen(self->mount);
385 386
            param = malloc(param_len);
            if (!param) {
387 388
                free(encpassword);
                free(encmount);
389 390 391
                free(encvalue);
                return self->error = SHOUTERR_MALLOC;
            }
392 393 394
            snprintf(param, param_len, param_template, encpassword, encmount, encvalue);
            free(encpassword);
            free(encmount);
395 396 397 398 399

            plan.param = param;
            plan.auth = 0;
            plan.method = "GET";
            plan.resource = "/admin.cgi";
400
        break;
401 402 403 404
        default:
            free(encvalue);
            return self->error = SHOUTERR_UNSUPPORTED;
        break;
405
    }
406

407
    free(encvalue);
408

409 410 411 412 413
    connection = shout_connection_new(self, shout_http_impl, &plan);
    if (!connection) {
        free(param);
        return self->error = SHOUTERR_MALLOC;
    }
414

415 416
    shout_connection_set_callback(self->connection, shout_cb_connection_callback, self);

417
#ifdef HAVE_OPENSSL
418
    shout_connection_select_tlsmode(connection, self->tls_mode);
419
#endif
420
    shout_connection_set_nonblocking(connection, SHOUT_BLOCKING_FULL);
421

422
    connection->target_message_state = SHOUT_MSGSTATE_PARSED_FINAL;
423 424

    shout_connection_connect(connection, self);
425

426
    ret = shout_connection_iter(connection, self);
427
    error = shout_connection_get_error(connection);
428

429
    shout_connection_unref(connection);
430

431
    free(param);
432

433 434 435 436 437
    if (ret == 0) {
        return SHOUTERR_SUCCESS;
    } else {
        return error;
    }
brendan's avatar
brendan committed
438 439 440 441 442
}

/* getters/setters */
const char *shout_version(int *major, int *minor, int *patch)
{
443
    if (major)
444
        *major = LIBSHOUT_MAJOR;
445
    if (minor)
446
        *minor = LIBSHOUT_MINOR;
447
    if (patch)
448
        *patch = LIBSHOUT_MICRO;
brendan's avatar
brendan committed
449

450
    return VERSION;
brendan's avatar
brendan committed
451 452 453 454
}

int shout_get_errno(shout_t *self)
{
455
    return self->error;
brendan's avatar
brendan committed
456
}
Michael Smith's avatar
Michael Smith committed
457

458
const char *shout_get_error(shout_t *self)
Jack Moffitt's avatar
Jack Moffitt committed
459
{
460
    if (!self)
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
        return "Invalid shout_t";

    switch (self->error) {
    case SHOUTERR_SUCCESS:
        return "No error";
    case SHOUTERR_INSANE:
        return "Nonsensical arguments";
    case SHOUTERR_NOCONNECT:
        return "Couldn't connect";
    case SHOUTERR_NOLOGIN:
        return "Login failed";
    case SHOUTERR_SOCKET:
        return "Socket error";
    case SHOUTERR_MALLOC:
        return "Out of memory";
    case SHOUTERR_CONNECTED:
        return "Cannot set parameter while connected";
    case SHOUTERR_UNCONNECTED:
        return "Not connected";
    case SHOUTERR_BUSY:
        return "Socket is busy";
    case SHOUTERR_UNSUPPORTED:
        return "This libshout doesn't support the requested option";
    case SHOUTERR_NOTLS:
        return "TLS requested but not supported by peer";
    case SHOUTERR_TLSBADCERT:
        return "TLS connection can not be established because of bad certificate";
    case SHOUTERR_RETRY:
        return "Please retry current operation.";
    default:
        return "Unknown error";
    }
Jack Moffitt's avatar
Jack Moffitt committed
493
}
494

495 496 497 498 499
/* Returns:
 *   SHOUTERR_CONNECTED if the connection is open,
 *   SHOUTERR_UNCONNECTED if it has not yet been opened,
 *   or an error from try_connect, including SHOUTERR_BUSY
 */
brendan's avatar
brendan committed
500 501
int shout_get_connected(shout_t *self)
{
502
    int rc;
503

504
    if (!self)
505
        return SHOUTERR_INSANE;
506

507
    if (self->connection && self->connection->current_message_state == SHOUT_MSGSTATE_SENDING1)
508
        return SHOUTERR_CONNECTED;
509
    if (self->connection && self->connection->current_message_state != SHOUT_MSGSTATE_SENDING1) {
510
        if ((rc = try_connect(self)) == SHOUTERR_SUCCESS)
511 512 513
            return SHOUTERR_CONNECTED;
        return rc;
    }
514

515
    return SHOUTERR_UNCONNECTED;
brendan's avatar
brendan committed
516 517
}

518 519
int shout_set_host(shout_t *self, const char *host)
{
520
    if (!self)
521
        return SHOUTERR_INSANE;
522

523
    if (self->connection)
524
        return self->error = SHOUTERR_CONNECTED;
525

526
    if (self->host)
527
        free(self->host);
528

529
    if ( !(self->host = _shout_util_strdup(host)) )
530
        return self->error = SHOUTERR_MALLOC;
531

532
    return self->error = SHOUTERR_SUCCESS;
533 534 535 536
}

const char *shout_get_host(shout_t *self)
{
537
    if (!self)
538
        return NULL;
539

540
    return self->host;
541 542 543 544
}

int shout_set_port(shout_t *self, unsigned short port)
{
545
    if (!self)
546
        return SHOUTERR_INSANE;
547

548
    if (self->connection)
549
        return self->error = SHOUTERR_CONNECTED;
550

551
    self->port = port;
552

553
    return self->error = SHOUTERR_SUCCESS;
554 555 556 557
}

unsigned short shout_get_port(shout_t *self)
{
558
    if (!self)
559
        return 0;
560

561
    return self->port;
562 563 564 565
}

int shout_set_password(shout_t *self, const char *password)
{
566
    if (!self)
567
        return SHOUTERR_INSANE;
568

569
    if (self->connection)
570
        return self->error = SHOUTERR_CONNECTED;
571

572
    if (self->password)
573
        free(self->password);
574

575
    if ( !(self->password = _shout_util_strdup(password)) )
576
        return self->error = SHOUTERR_MALLOC;
577

578
    return self->error = SHOUTERR_SUCCESS;
579 580 581 582
}

const char* shout_get_password(shout_t *self)
{
583
    if (!self)
584
        return NULL;
585

586
    return self->password;
587 588 589 590
}

int shout_set_mount(shout_t *self, const char *mount)
{
591
    size_t len;
592

593
    if (!self || !mount)
594
        return SHOUTERR_INSANE;
595

596
    if (self->connection)
597 598
        return self->error = SHOUTERR_CONNECTED;

599
    if (self->mount)
600
        free(self->mount);
601

602
    len = strlen(mount) + 1;
603
    if (mount[0] != '/')
604
        len++;
605

606
    if ( !(self->mount = malloc(len)) )
607
        return self->error = SHOUTERR_MALLOC;
608

609
    snprintf(self->mount, len, "%s%s", mount[0] == '/' ? "" : "/", mount);
610

611
    return self->error = SHOUTERR_SUCCESS;
612 613 614 615
}

const char *shout_get_mount(shout_t *self)
{
616
    if (!self)
617
        return NULL;
618

619
    return self->mount;
620 621 622 623
}

int shout_set_name(shout_t *self, const char *name)
{
624
    return shout_set_meta(self, "name", name);
625 626 627 628
}

const char *shout_get_name(shout_t *self)
{
629
    return shout_get_meta(self, "name");
630 631 632 633
}

int shout_set_url(shout_t *self, const char *url)
{
634
    return shout_set_meta(self, "url", url);
635 636 637 638
}

const char *shout_get_url(shout_t *self)
{
639
    return shout_get_meta(self, "url");
640 641 642 643
}

int shout_set_genre(shout_t *self, const char *genre)
{
644
    return shout_set_meta(self, "genre", genre);
645 646 647 648
}

const char *shout_get_genre(shout_t *self)
{
649
    return shout_get_meta(self, "genre");
650 651
}

652 653
int shout_set_agent(shout_t *self, const char *agent)
{
654
    if (!self)
655
        return SHOUTERR_INSANE;
656

657
    if (self->connection)
658
        return self->error = SHOUTERR_CONNECTED;
659

660
    if (self->useragent)
661
        free(self->useragent);
662

663
    if ( !(self->useragent = _shout_util_strdup(agent)) )
664
        return self->error = SHOUTERR_MALLOC;
665

666
    return self->error = SHOUTERR_SUCCESS;
667 668 669 670
}

const char *shout_get_agent(shout_t *self)
{
671
    if (!self)
672
        return NULL;
673

674
    return self->useragent;
675 676 677
}


678 679
int shout_set_user(shout_t *self, const char *username)
{
680
    if (!self)
681
        return SHOUTERR_INSANE;
682

683
    if (self->connection)
684
        return self->error = SHOUTERR_CONNECTED;
685

686
    if (self->user)
687
        free(self->user);
688

689
    if ( !(self->user = _shout_util_strdup(username)) )
690
        return self->error = SHOUTERR_MALLOC;
691

692
    return self->error = SHOUTERR_SUCCESS;
693 694 695 696
}

const char *shout_get_user(shout_t *self)
{
697
    if (!self)
698
        return NULL;
699

700
    return self->user;
701 702
}

703 704
int shout_set_description(shout_t *self, const char *description)
{
705
    return shout_set_meta(self, "description", description);
706 707 708 709
}

const char *shout_get_description(shout_t *self)
{
710
    return shout_get_meta(self, "description");
711 712
}

713 714
int shout_set_dumpfile(shout_t *self, const char *dumpfile)
{
715
    if (!self)
716
        return SHOUTERR_INSANE;
717

718
    if (self->connection)
719
        return SHOUTERR_CONNECTED;
720

721
    if (self->dumpfile)
722
        free(self->dumpfile);
723

724
    if ( !(self->dumpfile = _shout_util_strdup(dumpfile)) )
725
        return self->error = SHOUTERR_MALLOC;
726

727
    return self->error = SHOUTERR_SUCCESS;
728 729 730 731
}

const char *shout_get_dumpfile(shout_t *self)
{
732
    if (!self)
733
        return NULL;