admin.c 32.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org, 
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
 *                      Karl Heyes <karl@xiph.org>
 *                      and others (see AUTHORS for details).
 */

13 14 15 16
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

17 18
#include <string.h>
#include <stdlib.h>
19 20
#include <stdarg.h>
#include <time.h>
21 22 23
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
24

25
#include "cfgfile.h"
26 27 28 29 30 31 32
#include "connection.h"
#include "refbuf.h"
#include "client.h"
#include "source.h"
#include "global.h"
#include "event.h"
#include "stats.h"
33
#include "os.h"
34
#include "xslt.h"
35 36

#include "format.h"
37
#include "format_mp3.h"
38 39

#include "logging.h"
40
#include "auth.h"
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
41 42 43
#ifdef _WIN32
#define snprintf _snprintf
#endif
44 45 46

#define CATMODULE "admin"

47 48 49
#define COMMAND_ERROR             (-1)

/* Mount-specific commands */
50
#define COMMAND_RAW_FALLBACK        1
51
#define COMMAND_RAW_METADATA_UPDATE     2
52 53
#define COMMAND_RAW_SHOW_LISTENERS  3
#define COMMAND_RAW_MOVE_CLIENTS    4
54
#define COMMAND_RAW_MANAGEAUTH      5
55
#define COMMAND_SHOUTCAST_METADATA_UPDATE     6
56
#define COMMAND_RAW_UPDATEMETADATA      7
57 58 59 60

#define COMMAND_TRANSFORMED_FALLBACK        50
#define COMMAND_TRANSFORMED_SHOW_LISTENERS  53
#define COMMAND_TRANSFORMED_MOVE_CLIENTS    54
61
#define COMMAND_TRANSFORMED_MANAGEAUTH      55
62 63
#define COMMAND_TRANSFORMED_UPDATEMETADATA  56
#define COMMAND_TRANSFORMED_METADATA_UPDATE 57
64 65

/* Global commands */
Karl Heyes's avatar
Karl Heyes committed
66 67 68 69 70 71 72
#define COMMAND_RAW_LIST_MOUNTS             101
#define COMMAND_RAW_STATS                   102
#define COMMAND_RAW_LISTSTREAM              103
#define COMMAND_PLAINTEXT_LISTSTREAM        104
#define COMMAND_TRANSFORMED_LIST_MOUNTS     201
#define COMMAND_TRANSFORMED_STATS           202
#define COMMAND_TRANSFORMED_LISTSTREAM      203
73

74
/* Client management commands */
Karl Heyes's avatar
Karl Heyes committed
75 76 77 78
#define COMMAND_RAW_KILL_CLIENT             301
#define COMMAND_RAW_KILL_SOURCE             302
#define COMMAND_TRANSFORMED_KILL_CLIENT     401
#define COMMAND_TRANSFORMED_KILL_SOURCE     402
79

80 81 82
/* Admin commands requiring no auth */
#define COMMAND_BUILDM3U                    501

83 84
#define FALLBACK_RAW_REQUEST "fallbacks"
#define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl"
85
#define SHOUTCAST_METADATA_REQUEST "admin.cgi"
86 87
#define METADATA_RAW_REQUEST "metadata"
#define METADATA_TRANSFORMED_REQUEST "metadata.xsl"
88 89 90 91 92 93 94 95
#define LISTCLIENTS_RAW_REQUEST "listclients"
#define LISTCLIENTS_TRANSFORMED_REQUEST "listclients.xsl"
#define STATS_RAW_REQUEST "stats"
#define STATS_TRANSFORMED_REQUEST "stats.xsl"
#define LISTMOUNTS_RAW_REQUEST "listmounts"
#define LISTMOUNTS_TRANSFORMED_REQUEST "listmounts.xsl"
#define STREAMLIST_RAW_REQUEST "streamlist"
#define STREAMLIST_TRANSFORMED_REQUEST "streamlist.xsl"
96
#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
97 98 99 100 101 102 103
#define MOVECLIENTS_RAW_REQUEST "moveclients"
#define MOVECLIENTS_TRANSFORMED_REQUEST "moveclients.xsl"
#define KILLCLIENT_RAW_REQUEST "killclient"
#define KILLCLIENT_TRANSFORMED_REQUEST "killclient.xsl"
#define KILLSOURCE_RAW_REQUEST "killsource"
#define KILLSOURCE_TRANSFORMED_REQUEST "killsource.xsl"
#define ADMIN_XSL_RESPONSE "response.xsl"
104 105
#define MANAGEAUTH_RAW_REQUEST "manageauth"
#define MANAGEAUTH_TRANSFORMED_REQUEST "manageauth.xsl"
106 107
#define UPDATEMETADATA_RAW_REQUEST "updatemetadata"
#define UPDATEMETADATA_TRANSFORMED_REQUEST "updatemetadata.xsl"
108 109
#define DEFAULT_RAW_REQUEST ""
#define DEFAULT_TRANSFORMED_REQUEST ""
110
#define BUILDM3U_RAW_REQUEST "buildm3u"
111 112 113

#define RAW         1
#define TRANSFORMED 2
114
#define PLAINTEXT   3
115 116
int admin_get_command(char *command)
{
117 118 119 120
    if(!strcmp(command, FALLBACK_RAW_REQUEST))
        return COMMAND_RAW_FALLBACK;
    else if(!strcmp(command, FALLBACK_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_FALLBACK;
121 122 123 124
    else if(!strcmp(command, METADATA_RAW_REQUEST))
        return COMMAND_RAW_METADATA_UPDATE;
    else if(!strcmp(command, METADATA_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_METADATA_UPDATE;
125 126
    else if(!strcmp(command, SHOUTCAST_METADATA_REQUEST))
        return COMMAND_SHOUTCAST_METADATA_UPDATE;
127 128 129 130 131
    else if(!strcmp(command, LISTCLIENTS_RAW_REQUEST))
        return COMMAND_RAW_SHOW_LISTENERS;
    else if(!strcmp(command, LISTCLIENTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_SHOW_LISTENERS;
    else if(!strcmp(command, STATS_RAW_REQUEST))
132
        return COMMAND_RAW_STATS;
133 134
    else if(!strcmp(command, STATS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
135 136
    else if(!strcmp(command, "stats.xml")) /* The old way */
        return COMMAND_RAW_STATS;
137 138 139 140 141
    else if(!strcmp(command, LISTMOUNTS_RAW_REQUEST))
        return COMMAND_RAW_LIST_MOUNTS;
    else if(!strcmp(command, LISTMOUNTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_LIST_MOUNTS;
    else if(!strcmp(command, STREAMLIST_RAW_REQUEST))
142
        return COMMAND_RAW_LISTSTREAM;
143 144
    else if(!strcmp(command, STREAMLIST_PLAINTEXT_REQUEST))
        return COMMAND_PLAINTEXT_LISTSTREAM;
145 146 147 148 149 150 151 152 153 154 155 156
    else if(!strcmp(command, MOVECLIENTS_RAW_REQUEST))
        return COMMAND_RAW_MOVE_CLIENTS;
    else if(!strcmp(command, MOVECLIENTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_MOVE_CLIENTS;
    else if(!strcmp(command, KILLCLIENT_RAW_REQUEST))
        return COMMAND_RAW_KILL_CLIENT;
    else if(!strcmp(command, KILLCLIENT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_KILL_CLIENT;
    else if(!strcmp(command, KILLSOURCE_RAW_REQUEST))
        return COMMAND_RAW_KILL_SOURCE;
    else if(!strcmp(command, KILLSOURCE_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_KILL_SOURCE;
157 158 159 160
    else if(!strcmp(command, MANAGEAUTH_RAW_REQUEST))
        return COMMAND_RAW_MANAGEAUTH;
    else if(!strcmp(command, MANAGEAUTH_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_MANAGEAUTH;
161 162 163 164
    else if(!strcmp(command, UPDATEMETADATA_RAW_REQUEST))
        return COMMAND_RAW_UPDATEMETADATA;
    else if(!strcmp(command, UPDATEMETADATA_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_UPDATEMETADATA;
165 166
    else if(!strcmp(command, BUILDM3U_RAW_REQUEST))
        return COMMAND_BUILDM3U;
167 168 169 170
    else if(!strcmp(command, DEFAULT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
    else if(!strcmp(command, DEFAULT_RAW_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
171 172 173 174
    else
        return COMMAND_ERROR;
}

175
static void command_fallback(client_t *client, source_t *source, int response);
176
static void command_metadata(client_t *client, source_t *source, int response);
177
static void command_shoutcast_metadata(client_t *client, source_t *source);
178 179 180 181 182 183 184 185
static void command_show_listeners(client_t *client, source_t *source,
        int response);
static void command_move_clients(client_t *client, source_t *source,
        int response);
static void command_stats(client_t *client, int response);
static void command_list_mounts(client_t *client, int response);
static void command_kill_client(client_t *client, source_t *source,
        int response);
186 187
static void command_manageauth(client_t *client, source_t *source,
        int response);
188 189
static void command_buildm3u(client_t *client, source_t *source,
        int response);
190 191
static void command_kill_source(client_t *client, source_t *source,
        int response);
192 193
static void command_updatemetadata(client_t *client, source_t *source,
        int response);
194 195 196
static void admin_handle_mount_request(client_t *client, source_t *source,
        int command);
static void admin_handle_general_request(client_t *client, int command);
197 198 199 200
static void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template);
static void html_write(client_t *client, char *fmt, ...);

201 202 203 204
/* build an XML doc containing information about currently running sources.
 * If a mountpoint is passed then that source will not be added to the XML
 * doc even if the source is running */
xmlDocPtr admin_build_sourcelist (const char *mount)
205 206 207 208 209 210 211 212 213 214 215
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

    doc = xmlNewDoc("1.0");
    xmlnode = xmlNewDocNode(doc, NULL, "icestats", NULL);
    xmlDocSetRootElement(doc, xmlnode);
216

217 218
    if (mount) {
        xmlNewChild(xmlnode, NULL, "current_source", mount);
219 220 221 222 223
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
224 225 226 227 228 229
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

230 231 232 233
        if (source->running)
        {
            srcnode = xmlNewChild(xmlnode, NULL, "source", NULL);
            xmlSetProp(srcnode, "mount", source->mount);
234

235
            xmlNewChild(srcnode, NULL, "fallback", 
236 237
                    (source->fallback_mount != NULL)?
                    source->fallback_mount:"");
238 239
            snprintf(buf, sizeof(buf), "%ld", source->listeners);
            xmlNewChild(srcnode, NULL, "listeners", buf);
Karl Heyes's avatar
Karl Heyes committed
240 241
            snprintf(buf, sizeof(buf), "%lu",
                    (unsigned long)(now - source->con->con_time));
242
            xmlNewChild(srcnode, NULL, "Connected", buf);
243 244
            xmlNewChild(srcnode, NULL, "content-type", 
                    source->format->contenttype);
245 246 247 248
            if (source->authenticator) {
                xmlNewChild(srcnode, NULL, "authenticator", 
                    source->authenticator->type);
            }
249
        }
250 251 252 253 254 255 256 257
        node = avl_get_next(node);
    }
    return(doc);
}

void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template)
{
258
    xmlChar *buff = NULL;
259 260 261 262 263 264 265 266
    int len = 0;
    ice_config_t *config;
    char *fullpath_xslt_template;
    int fullpath_xslt_template_len;
    char *adminwebroot;

    client->respcode = 200;
    if (response == RAW) {
267
        xmlDocDumpMemory(doc, &buff, &len);
268 269 270 271
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Length: %d\r\n"
               "Content-Type: text/xml\r\n"
               "\r\n", len);
272
        html_write(client, "%s", buff);
273 274 275 276 277 278 279 280 281
    }
    if (response == TRANSFORMED) {
        config = config_get_config();
        adminwebroot = config->adminroot_dir;
        fullpath_xslt_template_len = strlen(adminwebroot) + 
            strlen(xslt_template) + 2;
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
            adminwebroot, PATH_SEPARATOR, xslt_template);
282
        config_release_config();
283 284 285 286 287 288 289 290 291 292 293
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Type: text/html\r\n"
               "\r\n");
        DEBUG1("Sending XSLT (%s)", fullpath_xslt_template);
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
    if (buff) {
        xmlFree(buff);
    }
}
294 295 296 297
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;
298
    int noauth = 0;
299

300 301 302
    DEBUG1("Admin request (%s)", uri);
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
303 304 305 306 307
        ERROR0("Internal error: admin request isn't");
        client_send_401(client);
        return;
    }

308 309 310 311 312 313
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
314

315
    DEBUG1("Got command (%s)", command_string);
316 317 318 319 320 321 322 323 324
    command = admin_get_command(command_string);

    if(command < 0) {
        ERROR1("Error parsing command string or unrecognised command: %s",
                command_string);
        client_send_400(client, "Unrecognised command");
        return;
    }

325 326
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

327 328 329 330 331 332 333 334
        ice_config_t *config;
        char *pass = httpp_get_query_param (client->parser, "pass");
        if (pass == NULL)
        {
            client_send_400 (client, "missing pass parameter");
            return;
        }
        config = config_get_config ();
335
        httpp_set_query_param (client->parser, "mount", config->shoutcast_mount);
336 337
        httpp_setvar (client->parser, HTTPP_VAR_PROTOCOL, "ICY");
        httpp_setvar (client->parser, HTTPP_VAR_ICYPASSWORD, pass);
338
        config_release_config ();
339 340
    }

341 342
    mount = httpp_get_query_param(client->parser, "mount");

343 344 345
    if(mount != NULL) {
        source_t *source;

346 347 348
        if (command == COMMAND_BUILDM3U) {
            noauth = 1;
        }
349
        /* This is a mount request, handle it as such */
350 351 352 353 354 355 356 357
        if (!noauth) {
            if(!connection_check_admin_pass(client->parser)) {
                if(!connection_check_source_pass(client->parser, mount)) {
                    INFO1("Bad or missing password on mount modification admin "
                          "request (command: %s)", command_string);
                    client_send_401(client);
                    return;
                }
358
            }
359 360 361
        }
        
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
362
        source = source_find_mount_raw(mount);
363

364 365
        if (source == NULL)
        {
366 367
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
368
            avl_tree_unlock(global.source_tree);
369 370
            client_send_400(client, "Source does not exist");
        }
371 372
        else
        {
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
            if (source->running == 0)
            {
                avl_tree_unlock (global.source_tree);
                INFO2("Received admin command %s on unavailable mount \"%s\"",
                        command_string, mount);
                client_send_400 (client, "Source is not available");
                return;
            }
            if (command == COMMAND_SHOUTCAST_METADATA_UPDATE &&
                    source->shoutcast_compat == 0)
            {
                avl_tree_unlock (global.source_tree);
                ERROR0 ("illegal change of metadata on non-shoutcast "
                        "compatible stream");
                client_send_400 (client, "illegal metadata call");
                return;
389
            }
390 391 392
            INFO2("Received admin command %s on mount \"%s\"", 
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
393
            avl_tree_unlock(global.source_tree);
394
        }
395 396 397
    }
    else {

398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
        if (command == COMMAND_PLAINTEXT_LISTSTREAM) {
        /* this request is used by a slave relay to retrieve
           mounts from the master, so handle this request
           validating against the relay password */
            if(!connection_check_relay_pass(client->parser)) {
                INFO1("Bad or missing password on admin command "
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
        }
        else {
            if(!connection_check_admin_pass(client->parser)) {
                INFO1("Bad or missing password on admin command "
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
416 417 418 419 420 421 422 423 424 425
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
426
            command_stats(client, RAW);
427
            break;
428 429
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
430 431
            break;
        case COMMAND_RAW_LISTSTREAM:
432 433
            command_list_mounts(client, RAW);
            break;
434 435 436
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
437 438 439 440 441 442 443 444 445 446 447
        case COMMAND_TRANSFORMED_STATS:
            command_stats(client, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_LIST_MOUNTS:
            command_list_mounts(client, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_LISTSTREAM:
            command_list_mounts(client, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_MOVE_CLIENTS:
            command_list_mounts(client, TRANSFORMED);
448
            break;
449 450 451 452 453 454 455 456 457 458 459
        default:
            WARN0("General admin request not recognised");
            client_send_400(client, "Unknown admin request");
            return;
    }
}

static void admin_handle_mount_request(client_t *client, source_t *source, 
        int command)
{
    switch(command) {
460 461
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
462
            break;
463 464 465 466 467
        case COMMAND_RAW_METADATA_UPDATE:
            command_metadata(client, source, RAW);
            break;
        case COMMAND_TRANSFORMED_METADATA_UPDATE:
            command_metadata(client, source, TRANSFORMED);
468
            break;
469 470 471
        case COMMAND_SHOUTCAST_METADATA_UPDATE:
            command_shoutcast_metadata(client, source);
            break;
472 473 474 475 476 477 478 479
        case COMMAND_RAW_SHOW_LISTENERS:
            command_show_listeners(client, source, RAW);
            break;
        case COMMAND_RAW_MOVE_CLIENTS:
            command_move_clients(client, source, RAW);
            break;
        case COMMAND_RAW_KILL_CLIENT:
            command_kill_client(client, source, RAW);
480
            break;
481 482
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
483
            break;
484 485
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
486
            break;
487 488 489 490 491 492 493 494 495 496 497
        case COMMAND_TRANSFORMED_SHOW_LISTENERS:
            command_show_listeners(client, source, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_MOVE_CLIENTS:
            command_move_clients(client, source, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_KILL_CLIENT:
            command_kill_client(client, source, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_KILL_SOURCE:
            command_kill_source(client, source, TRANSFORMED);
498
            break;
499 500 501 502 503 504
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
505 506 507 508 509 510
        case COMMAND_TRANSFORMED_UPDATEMETADATA:
            command_updatemetadata(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_UPDATEMETADATA:
            command_updatemetadata(client, source, RAW);
            break;
511 512 513
        case COMMAND_BUILDM3U:
            command_buildm3u(client, source, RAW);
            break;
514 515 516
        default:
            WARN0("Mount request not recognised");
            client_send_400(client, "Mount request unknown");
517
            break;
518 519 520 521 522 523 524 525 526 527 528
    }
}

#define COMMAND_REQUIRE(client,name,var) \
    do { \
        (var) = httpp_get_query_param((client)->parser, (name)); \
        if((var) == NULL) { \
            client_send_400((client), "Missing parameter"); \
            return; \
        } \
    } while(0);
529 530
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
531

532
static void html_success(client_t *client, char *message)
533 534 535 536 537 538 539 540 541 542 543 544
{
    int bytes;

    client->respcode = 200;
    bytes = sock_write(client->con->sock, 
            "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n" 
            "<html><head><title>Admin request successful</title></head>"
            "<body><p>%s</p></body></html>", message);
    if(bytes > 0) client->con->sent_bytes = bytes;
    client_destroy(client);
}

545 546 547 548 549 550 551 552 553 554 555
static void html_write(client_t *client, char *fmt, ...)
{
    int bytes;
    va_list ap;

    va_start(ap, fmt);
    bytes = sock_write_fmt(client->con->sock, fmt, ap);
    va_end(ap);
    if(bytes > 0) client->con->sent_bytes = bytes;
}

556 557
static void command_move_clients(client_t *client, source_t *source,
    int response)
558 559 560
{
    char *dest_source;
    source_t *dest;
561 562 563 564 565 566
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
567
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
568 569 570 571 572 573 574 575 576 577 578
        parameters_passed = 1;
    }
    DEBUG1("Done optional check (%d)", parameters_passed);
    if (!parameters_passed) {
        doc = admin_build_sourcelist(source->mount);
        admin_send_response(doc, client, response, 
             MOVECLIENTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
        client_destroy(client);
        return;
    }
579

580 581 582 583 584 585 586 587
    dest = source_find_mount (dest_source);

    if (dest == NULL)
    {
        client_send_400 (client, "No such destination");
        return;
    }

Karl Heyes's avatar
Karl Heyes committed
588
    if (strcmp (dest->mount, source->mount) == 0)
589 590 591 592 593
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

594 595 596
    if (dest->running == 0)
    {
        client_send_400 (client, "Destination not running");
597 598 599
        return;
    }

600 601 602 603
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

604
    source_move_clients (source, dest);
605

606
    memset(buf, '\000', sizeof(buf));
607 608
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
609 610 611 612 613 614 615
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
616 617
}

618 619
static void command_show_listeners(client_t *client, source_t *source,
    int response)
620
{
621 622
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
623 624
    avl_node *client_node;
    client_t *current;
625 626
    char buf[22];
    char *userAgent = NULL;
627 628
    time_t now = time(NULL);

629 630 631 632 633
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
634

635
    memset(buf, '\000', sizeof(buf));
636
    snprintf(buf, sizeof(buf)-1, "%ld", source->listeners);
637
    xmlNewChild(srcnode, NULL, "Listeners", buf);
638 639 640 641 642 643

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
644 645 646 647 648 649 650 651 652 653
        listenernode = xmlNewChild(srcnode, NULL, "listener", NULL);
        xmlNewChild(listenernode, NULL, "IP", current->con->ip);
        userAgent = httpp_getvar(current->parser, "user-agent");
        if (userAgent) {
            xmlNewChild(listenernode, NULL, "UserAgent", userAgent);
        }
        else {
            xmlNewChild(listenernode, NULL, "UserAgent", "Unknown");
        }
        memset(buf, '\000', sizeof(buf));
Karl Heyes's avatar
Karl Heyes committed
654
        snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - current->con->con_time));
655 656
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
657
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
658
        xmlNewChild(listenernode, NULL, "ID", buf);
659 660 661
        if (current->username) {
            xmlNewChild(listenernode, NULL, "username", current->username);
        }
662 663 664 665
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
666 667 668
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
669 670 671
    client_destroy(client);
}

672 673 674 675 676 677 678 679 680 681 682 683 684
static void command_buildm3u(client_t *client, source_t *source,
    int response)
{
    char *username = NULL;
    char *password = NULL;
    char *host = NULL;
    int port = 0;
    ice_config_t *config;

    COMMAND_REQUIRE(client, "username", username);
    COMMAND_REQUIRE(client, "password", password);

    config = config_get_config();
685
    host = strdup(config->hostname);
686
    port = config->port;
687
    config_release_config();
688 689 690 691 692 693 694 695 696 697 698 699 700

    client->respcode = 200;
    sock_write(client->con->sock,
        "HTTP/1.0 200 OK\r\n"
        "Content-Type: audio/x-mpegurl\r\n"
        "Content-Disposition = attachment; filename=listen.m3u\r\n\r\n" 
        "http://%s:%s@%s:%d%s\r\n",
        username,
        password,
        host,
        port,
        source->mount
    );
701 702

    free(host);
703 704
    client_destroy(client);
}
705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
static void command_manageauth(client_t *client, source_t *source,
    int response)
{
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, msgnode;
    char *action = NULL;
    char *username = NULL;
    char *password = NULL;
    char *message = NULL;
    int ret = AUTH_OK;

    if((COMMAND_OPTIONAL(client, "action", action))) {
        if (!strcmp(action, "add")) {
            COMMAND_REQUIRE(client, "username", username);
            COMMAND_REQUIRE(client, "password", password);
            ret = auth_adduser(source, username, password);
            if (ret == AUTH_FAILED) {
                message = strdup("User add failed - check the icecast error log");
            }
            if (ret == AUTH_USERADDED) {
                message = strdup("User added");
            }
            if (ret == AUTH_USEREXISTS) {
                message = strdup("User already exists - not added");
            }
        }
        if (!strcmp(action, "delete")) {
            COMMAND_REQUIRE(client, "username", username);
            ret = auth_deleteuser(source, username);
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
            }
            if (ret == AUTH_USERDELETED) {
                message = strdup("User deleted");
            }
        }
    }

    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);

    if (message) {
        msgnode = xmlNewChild(node, NULL, "iceresponse", NULL);
        xmlNewChild(msgnode, NULL, "message", message);
    }

    xmlDocSetRootElement(doc, node);

    auth_get_userlist(source, srcnode);

    admin_send_response(doc, client, response, 
        MANAGEAUTH_TRANSFORMED_REQUEST);
    if (message) {
        free(message);
    }
    xmlFreeDoc(doc);
    client_destroy(client);
}

766 767
static void command_kill_source(client_t *client, source_t *source,
    int response)
768
{
769 770 771 772 773 774 775 776 777
    xmlDocPtr doc;
    xmlNodePtr node;

    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlNewChild(node, NULL, "message", "Source Removed");
    xmlNewChild(node, NULL, "return", "1");
    xmlDocSetRootElement(doc, node);

778 779
    source->running = 0;

780 781 782 783
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
784 785
}

786 787
static void command_kill_client(client_t *client, source_t *source,
    int response)
788 789 790 791
{
    char *idtext;
    int id;
    client_t *listener;
792 793 794
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
795 796 797 798 799 800 801

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

802 803 804 805 806
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

807 808 809 810 811 812 813
    if(listener != NULL) {
        INFO1("Admin request: client %d removed", id);

        /* This tags it for removal on the next iteration of the main source
         * loop
         */
        listener->con->error = 1;
814 815 816 817
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "1");
818 819
    }
    else {
820 821 822 823
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d not found", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "0");
824
    }
825 826 827 828
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
829 830
}

831 832
static void command_fallback(client_t *client, source_t *source,
    int response)
833 834 835 836 837 838 839 840 841 842 843 844
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

    old = source->fallback_mount;
    source->fallback_mount = strdup(fallback);
    free(old);

845
    html_success(client, "Fallback configured");
846 847
}

848 849
static void command_metadata(client_t *client, source_t *source,
    int response)
850 851
{
    char *action;
852 853
    char *song, *title, *artist;
    format_plugin_t *plugin;
854 855 856 857 858 859
    xmlDocPtr doc;
    xmlNodePtr node;

    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
860 861 862 863

    DEBUG0("Got metadata update request");

    COMMAND_REQUIRE(client, "mode", action);
864 865 866
    COMMAND_OPTIONAL(client, "song", song);
    COMMAND_OPTIONAL(client, "title", title);
    COMMAND_OPTIONAL(client, "artist", artist);
867

Karl Heyes's avatar
Karl Heyes committed
868 869
    if (strcmp (action, "updinfo") != 0)
    {
870 871 872 873 874 875
        xmlNewChild(node, NULL, "message", "No such action");
        xmlNewChild(node, NULL, "return", "0");
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
        client_destroy(client);
876 877
        return;
    }
878

879
    plugin = source->format;
Karl Heyes's avatar
Karl Heyes committed
880

881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
    if (plugin && plugin->set_tag)
    {
        if (song)
        {
            plugin->set_tag (plugin, "song", song);
            DEBUG2("Metadata on mountpoint %s changed to \"%s\"", source->mount, song);
        }
        else
        {
            if (artist && title)
            {
                plugin->set_tag (plugin, "title", title);
                plugin->set_tag (plugin, "artist", artist);
                INFO3("Metadata on mountpoint %s changed to \"%s - %s\"",
                        source->mount, artist, title);
            }
        }
    }
    else
    {
901 902 903 904 905 906 907 908
        xmlNewChild(node, NULL, "message", 
            "Mountpoint will not accept URL updates");
        xmlNewChild(node, NULL, "return", "1");
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
        client_destroy(client);
        return;
909
    }
910 911 912 913 914 915 916

    xmlNewChild(node, NULL, "message", "Metadata update successful");
    xmlNewChild(node, NULL, "return", "1");
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
917 918
}

919 920 921 922 923 924 925 926 927 928 929
static void command_shoutcast_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
    mp3_state *state;

    DEBUG0("Got shoutcast metadata update request");

    COMMAND_REQUIRE(client, "mode", action);
    COMMAND_REQUIRE(client, "song", value);

930
    if (source->format->type == FORMAT_TYPE_OGG) {
931
        client_send_400 (client, "Cannot update metadata on vorbis streams");
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951
        return;
    }

    if (strcmp (action, "updinfo") != 0)
    {
        client_send_400 (client, "No such action");
        return;
    }

    state = source->format->_state;

    mp3_set_tag (source->format, "title", value);

    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);


    html_success(client, "Metadata update successful");
}

952 953 954
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

955 956
    DEBUG0("Stats request, sending xml stats");

957
    stats_get_xml(&doc, 1);
958 959
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
960 961 962 963
    client_destroy(client);
    return;
}

Karl Heyes's avatar
Karl Heyes committed
964 965
static void command_list_mounts(client_t *client, int response)
{
966 967
    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
968
    avl_tree_rlock (global.source_tree);
969 970
    if (response == PLAINTEXT)
    {
Karl Heyes's avatar
Karl Heyes committed
971
        char buffer [4096], *buf = buffer;
972 973
        unsigned int remaining = sizeof (buffer);
        int ret = snprintf (buffer, remaining,
Karl Heyes's avatar
Karl Heyes committed
974 975
                "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");

976 977
        avl_node *node = avl_get_first(global.source_tree);
        while (node && ret > 0 && (unsigned)ret < remaining)
Karl Heyes's avatar
Karl Heyes committed
978
        {
979 980 981 982
            source_t *source = (source_t *)node->key;
            node = avl_get_next(node);
            if (source->hidden)
                continue;
Karl Heyes's avatar
Karl Heyes committed
983 984 985
            remaining -= ret;
            buf += ret;
            ret = snprintf (buf, remaining, "%s\n", source->mount);
986
        }
Karl Heyes's avatar
Karl Heyes committed
987 988
        avl_tree_unlock (global.source_tree);
        /* handle last line */
989
        if (ret > 0 && (unsigned)ret < remaining)
Karl Heyes's avatar
Karl Heyes committed
990 991 992 993 994
        {
            remaining -= ret;
            buf += ret;
        }
        sock_write_bytes (client->con->sock, buffer, sizeof (buffer)-remaining);
995
    }
Karl Heyes's avatar
Karl Heyes committed
996 997 998 999
    else
    {
        xmlDocPtr doc = admin_build_sourcelist(NULL);
        avl_tree_unlock (global.source_tree);
1000 1001 1002 1003 1004

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
1005
    client_destroy(client);
1006

1007 1008 1009
    return;
}

1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
static void command_updatemetadata(client_t *client, source_t *source,
    int response)
{
    xmlDocPtr doc;
    xmlNodePtr node, srcnode;

    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);

    admin_send_response(doc, client, response, 
        UPDATEMETADATA_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
    client_destroy(client);
}