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
#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 272 273 274 275
               "Content-Length: ";
        xmlDocDumpMemory(doc, &buff, &len);
        buf_len = strlen (http) + len + 20;
        client->refbuf = refbuf_new (buf_len);
        snprintf (client->refbuf->data, buf_len, "%s%d\r\n\r\n%s", http, len, buff);
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
276
    }
277 278 279 280 281 282 283 284
    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;
285 286
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
287
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
288
        config_release_config();
289

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


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

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

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

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

328 329
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

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

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

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

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

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

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

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

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

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

548

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

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

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

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

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

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

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

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

598
    source_move_clients (source, dest);
599

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

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

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

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

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

    avl_tree_rlock(source->client_tree);

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

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

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

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

    free(host);
695 696
    client_destroy(client);
}
697 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
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);
}

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

771 772
    source->running = 0;

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

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

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

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

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

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

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

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

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

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

    DEBUG0("Got metadata update request");

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

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

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

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

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

907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922
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;
    }

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

927 928 929 930 931 932 933 934
        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");
    }
935 936
}

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

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

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

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

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

        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
967
        {
968 969 970 971 972 973 974 975 976
            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;
977
            if (source->running == 0 && source->on_demand == 0)
978 979
                continue;
            if (source->hidden)
980
                continue;
Karl Heyes's avatar
Karl Heyes committed
981 982
            remaining -= ret;
            buf += ret;
983
            ret = snprintf (buf, remaining, "%s\n", current->mountname);
984
        }
Karl Heyes's avatar
Karl Heyes committed
985
        avl_tree_unlock (global.source_tree);
986 987
        config_release_config();

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

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

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