admin.c 32.7 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
#include "fserve.h"
36 37 38 39

#include "format.h"

#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 117
int admin_get_command(char *command)
{
118 119 120 121
    if(!strcmp(command, FALLBACK_RAW_REQUEST))
        return COMMAND_RAW_FALLBACK;
    else if(!strcmp(command, FALLBACK_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_FALLBACK;
122 123 124 125
    else if(!strcmp(command, METADATA_RAW_REQUEST))
        return COMMAND_RAW_METADATA_UPDATE;
    else if(!strcmp(command, METADATA_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_METADATA_UPDATE;
126 127
    else if(!strcmp(command, SHOUTCAST_METADATA_REQUEST))
        return COMMAND_SHOUTCAST_METADATA_UPDATE;
128 129 130 131 132
    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))
133
        return COMMAND_RAW_STATS;
134 135
    else if(!strcmp(command, STATS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
136 137
    else if(!strcmp(command, "stats.xml")) /* The old way */
        return COMMAND_RAW_STATS;
138 139 140 141 142
    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))
143
        return COMMAND_RAW_LISTSTREAM;
144 145
    else if(!strcmp(command, STREAMLIST_PLAINTEXT_REQUEST))
        return COMMAND_PLAINTEXT_LISTSTREAM;
146 147 148 149 150 151 152 153 154 155 156 157
    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;
158 159 160 161
    else if(!strcmp(command, MANAGEAUTH_RAW_REQUEST))
        return COMMAND_RAW_MANAGEAUTH;
    else if(!strcmp(command, MANAGEAUTH_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_MANAGEAUTH;
162 163 164 165
    else if(!strcmp(command, UPDATEMETADATA_RAW_REQUEST))
        return COMMAND_RAW_UPDATEMETADATA;
    else if(!strcmp(command, UPDATEMETADATA_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_UPDATEMETADATA;
166 167
    else if(!strcmp(command, BUILDM3U_RAW_REQUEST))
        return COMMAND_BUILDM3U;
168 169 170 171
    else if(!strcmp(command, DEFAULT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
    else if(!strcmp(command, DEFAULT_RAW_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
172 173 174 175
    else
        return COMMAND_ERROR;
}

176
static void command_fallback(client_t *client, source_t *source, int response);
177
static void command_metadata(client_t *client, source_t *source, int response);
178
static void command_shoutcast_metadata(client_t *client, source_t *source);
179 180 181 182 183 184 185 186
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);
187 188
static void command_manageauth(client_t *client, source_t *source,
        int response);
189 190
static void command_buildm3u(client_t *client, source_t *source,
        int response);
191 192
static void command_kill_source(client_t *client, source_t *source,
        int response);
193 194
static void command_updatemetadata(client_t *client, source_t *source,
        int response);
195 196 197
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);
198 199 200
static void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template);

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
        if (source->running || source->on_demand)
231 232 233
        {
            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
            snprintf (buf, sizeof(buf), "%lu", source->listeners);
239
            xmlNewChild(srcnode, NULL, "listeners", buf);
240 241 242 243 244 245 246 247
            if (source->running)
            {
                snprintf (buf, sizeof(buf), "%lu",
                        (unsigned long)(now - source->con->con_time));
                xmlNewChild (srcnode, NULL, "Connected", buf);
                xmlNewChild (srcnode, NULL, "content-type", 
                        source->format->contenttype);
            }
248 249 250 251
            if (source->authenticator) {
                xmlNewChild(srcnode, NULL, "authenticator", 
                    source->authenticator->type);
            }
252
        }
253 254 255 256 257 258 259 260
        node = avl_get_next(node);
    }
    return(doc);
}

void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template)
{
261 262 263 264 265 266
    if (response == RAW)
    {
        xmlChar *buff = NULL;
        int len = 0;
        unsigned int buf_len;
        const char *http = "HTTP/1.0 200 OK\r\n"
267
               "Content-Type: text/xml\r\n"
268 269 270 271
               "Content-Length: ";
        xmlDocDumpMemory(doc, &buff, &len);
        buf_len = strlen (http) + len + 20;
        client->refbuf = refbuf_new (buf_len);
272 273
        len = snprintf (client->refbuf->data, buf_len, "%s%d\r\n\r\n%s", http, len, buff);
        client->refbuf->len = len;
274 275 276
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
277
    }
278 279 280 281 282 283 284 285
    if (response == TRANSFORMED)
    {
        char *fullpath_xslt_template;
        int fullpath_xslt_template_len;
        ice_config_t *config = config_get_config();

        fullpath_xslt_template_len = strlen (config->adminroot_dir) + 
            strlen (xslt_template) + 2;
286 287
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
288
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
289
        config_release_config();
290

291 292 293 294 295
        DEBUG1("Sending XSLT (%s)", fullpath_xslt_template);
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
296 297


298 299 300 301
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;
302
    int noauth = 0;
303

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

312 313 314 315 316 317
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
318

319
    DEBUG1("Got command (%s)", command_string);
320 321 322 323 324 325 326 327 328
    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;
    }

329 330
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

331 332 333 334 335 336 337 338
        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 ();
339
        httpp_set_query_param (client->parser, "mount", config->shoutcast_mount);
340 341
        httpp_setvar (client->parser, HTTPP_VAR_PROTOCOL, "ICY");
        httpp_setvar (client->parser, HTTPP_VAR_ICYPASSWORD, pass);
342
        config_release_config ();
343 344
    }

345 346
    mount = httpp_get_query_param(client->parser, "mount");

347 348 349
    if(mount != NULL) {
        source_t *source;

350 351 352
        if (command == COMMAND_BUILDM3U) {
            noauth = 1;
        }
353
        /* This is a mount request, handle it as such */
354 355 356 357 358 359 360 361
        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;
                }
362
            }
363 364 365
        }
        
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
366
        source = source_find_mount_raw(mount);
367

368 369
        if (source == NULL)
        {
370 371
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
372
            avl_tree_unlock(global.source_tree);
373 374
            client_send_400(client, "Source does not exist");
        }
375 376
        else
        {
377
            if (source->running == 0 && source->on_demand == 0)
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
            {
                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;
393
            }
394 395 396
            INFO2("Received admin command %s on mount \"%s\"", 
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
397
            avl_tree_unlock(global.source_tree);
398
        }
399 400 401
    }
    else {

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
        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;
            }
420 421 422 423 424 425 426 427 428 429
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
430
            command_stats(client, RAW);
431
            break;
432 433
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
434 435
            break;
        case COMMAND_RAW_LISTSTREAM:
436 437
            command_list_mounts(client, RAW);
            break;
438 439 440
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
441 442 443 444 445 446 447 448 449 450 451
        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);
452
            break;
453 454 455 456 457 458 459 460 461 462 463
        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) {
464 465
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
466
            break;
467 468 469 470 471
        case COMMAND_RAW_METADATA_UPDATE:
            command_metadata(client, source, RAW);
            break;
        case COMMAND_TRANSFORMED_METADATA_UPDATE:
            command_metadata(client, source, TRANSFORMED);
472
            break;
473 474 475
        case COMMAND_SHOUTCAST_METADATA_UPDATE:
            command_shoutcast_metadata(client, source);
            break;
476 477 478 479 480 481 482 483
        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);
484
            break;
485 486
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
487
            break;
488 489
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
490
            break;
491 492 493 494 495 496 497 498 499 500 501
        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);
502
            break;
503 504 505 506 507 508
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
509 510 511 512 513 514
        case COMMAND_TRANSFORMED_UPDATEMETADATA:
            command_updatemetadata(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_UPDATEMETADATA:
            command_updatemetadata(client, source, RAW);
            break;
515 516 517
        case COMMAND_BUILDM3U:
            command_buildm3u(client, source, RAW);
            break;
518 519 520
        default:
            WARN0("Mount request not recognised");
            client_send_400(client, "Mount request unknown");
521
            break;
522 523 524 525 526 527 528 529 530 531 532
    }
}

#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);
533 534
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
535

536
static void html_success(client_t *client, char *message)
537 538 539 540 541 542 543 544 545 546 547 548
{
    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);
}

549

550 551
static void command_move_clients(client_t *client, source_t *source,
    int response)
552 553 554
{
    char *dest_source;
    source_t *dest;
555 556 557 558 559 560
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
561
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
562 563 564 565 566 567 568 569 570 571
        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);
        return;
    }
572

573 574 575 576 577 578 579 580
    dest = source_find_mount (dest_source);

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

Karl Heyes's avatar
Karl Heyes committed
581
    if (strcmp (dest->mount, source->mount) == 0)
582 583 584 585 586
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

587
    if (dest->running == 0 && dest->on_demand == 0)
588 589
    {
        client_send_400 (client, "Destination not running");
590 591 592
        return;
    }

593 594
    INFO2 ("source is \"%s\", destination is \"%s\"", source->mount, dest->mount);

595 596 597 598
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

599
    source_move_clients (source, dest);
600

601
    memset(buf, '\000', sizeof(buf));
602 603
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
604 605 606 607 608 609
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
610 611
}

612 613
static void command_show_listeners(client_t *client, source_t *source,
    int response)
614
{
615 616
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
617 618
    avl_node *client_node;
    client_t *current;
619 620
    char buf[22];
    char *userAgent = NULL;
621 622
    time_t now = time(NULL);

623 624 625 626 627
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
628

629
    memset(buf, '\000', sizeof(buf));
630
    snprintf (buf, sizeof(buf), "%lu", source->listeners);
631
    xmlNewChild(srcnode, NULL, "Listeners", buf);
632 633 634 635 636 637

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
638 639 640 641 642 643 644 645 646 647
        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
648
        snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - current->con->con_time));
649 650
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
651
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
652
        xmlNewChild(listenernode, NULL, "ID", buf);
653 654 655
        if (current->username) {
            xmlNewChild(listenernode, NULL, "username", current->username);
        }
656 657 658 659
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
660 661 662
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
663 664
}

665 666 667 668 669 670 671 672 673 674 675 676 677
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();
678
    host = strdup(config->hostname);
679
    port = config->port;
680
    config_release_config();
681 682 683 684 685 686 687 688 689 690 691 692 693

    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
    );
694 695

    free(host);
696 697
    client_destroy(client);
}
698 699


700 701 702 703 704 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
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);
}

760 761
static void command_kill_source(client_t *client, source_t *source,
    int response)
762
{
763 764 765 766 767 768 769 770 771
    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);

772 773
    source->running = 0;

774 775 776
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
777 778
}

779 780
static void command_kill_client(client_t *client, source_t *source,
    int response)
781 782 783 784
{
    char *idtext;
    int id;
    client_t *listener;
785 786 787
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
788 789 790 791 792 793 794

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

795 796 797 798 799
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

800 801 802 803 804 805 806
    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;
807 808 809 810
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "1");
811 812
    }
    else {
813 814 815 816
        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");
817
    }
818 819 820
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
821 822
}

823 824
static void command_fallback(client_t *client, source_t *source,
    int response)
825 826 827 828 829 830 831 832 833 834 835 836
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

837
    html_success(client, "Fallback configured");
838 839
}

840 841
static void command_metadata(client_t *client, source_t *source,
    int response)
842 843
{
    char *action;
844 845
    char *song, *title, *artist;
    format_plugin_t *plugin;
846 847 848 849 850 851
    xmlDocPtr doc;
    xmlNodePtr node;

    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
852 853 854 855

    DEBUG0("Got metadata update request");

    COMMAND_REQUIRE(client, "mode", action);
856 857 858
    COMMAND_OPTIONAL(client, "song", song);
    COMMAND_OPTIONAL(client, "title", title);
    COMMAND_OPTIONAL(client, "artist", artist);
859

Karl Heyes's avatar
Karl Heyes committed
860 861
    if (strcmp (action, "updinfo") != 0)
    {
862 863 864 865 866
        xmlNewChild(node, NULL, "message", "No such action");
        xmlNewChild(node, NULL, "return", "0");
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
867 868
        return;
    }
869

870
    plugin = source->format;
Karl Heyes's avatar
Karl Heyes committed
871

872 873 874 875 876
    if (plugin && plugin->set_tag)
    {
        if (song)
        {
            plugin->set_tag (plugin, "song", song);
877
            INFO2 ("Metadata on mountpoint %s changed to \"%s\"", source->mount, song);
878 879 880 881 882 883 884 885 886 887 888 889 890 891
        }
        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
    {
892 893 894 895 896 897 898
        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);
        return;
899
    }
900 901 902 903 904 905

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

908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923
static void command_shoutcast_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;

    DEBUG0("Got shoutcast metadata update request");

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

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

924 925 926
    if (source->format && source->format->set_tag)
    {
        source->format->set_tag (source->format, "title", value);
927

928 929 930 931 932 933 934 935
        DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
                source->mount, value);
        html_success(client, "Metadata update successful");
    }
    else
    {
        client_send_400 (client, "mountpoint will not accept URL updates");
    }
936 937
}

938 939 940
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

941 942
    DEBUG0("Stats request, sending xml stats");

943
    stats_get_xml(&doc, 1);
944 945
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
946 947 948
    return;
}

Karl Heyes's avatar
Karl Heyes committed
949 950
static void command_list_mounts(client_t *client, int response)
{
951 952
    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
953
    avl_tree_rlock (global.source_tree);
954 955
    if (response == PLAINTEXT)
    {
956 957 958
        char *buf;
        unsigned int remaining = 4096;
        int ret;
959 960
        ice_config_t *config = config_get_config ();
        mount_proxy *mountinfo = config->mounts;
961 962 963 964 965 966 967

        client->refbuf = refbuf_new (remaining);
        buf = client->refbuf->data;
        ret = snprintf (buf, remaining,
                "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");

        while (mountinfo && ret > 0 && ret < remaining)
Karl Heyes's avatar
Karl Heyes committed
968
        {
969 970 971 972 973 974 975 976 977
            mount_proxy *current = mountinfo;
            source_t *source;
            mountinfo = mountinfo->next;

            /* now check that a source is available */
            source = source_find_mount (current->mountname);

            if (source == NULL)
                continue;
978
            if (source->running == 0 && source->on_demand == 0)
979 980
                continue;
            if (source->hidden)
981
                continue;
Karl Heyes's avatar
Karl Heyes committed
982 983
            remaining -= ret;
            buf += ret;
984
            ret = snprintf (buf, remaining, "%s\n", current->mountname);
985
        }
Karl Heyes's avatar
Karl Heyes committed
986
        avl_tree_unlock (global.source_tree);
987 988
        config_release_config();

Karl Heyes's avatar
Karl Heyes committed
989
        /* handle last line */
990
        if (ret > 0 && (unsigned)ret < remaining)
Karl Heyes's avatar
Karl Heyes committed
991 992 993 994
        {
            remaining -= ret;
            buf += ret;
        }
995 996
        client->refbuf->len = 4096 - remaining;
        fserve_add_client (client, NULL);
997
    }
Karl Heyes's avatar
Karl Heyes committed
998 999 1000 1001
    else
    {
        xmlDocPtr doc = admin_build_sourcelist(NULL);
        avl_tree_unlock (global.source_tree);
1002 1003 1004 1005 1006

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
1007 1008
}

1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
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);
}