admin.c 32.9 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 37 38

#include "format.h"

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

#define CATMODULE "admin"

46 47 48
#define COMMAND_ERROR             (-1)

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

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

/* Global commands */
Karl Heyes's avatar
Karl Heyes committed
65 66 67 68 69 70 71
#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
72

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

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

82 83
#define FALLBACK_RAW_REQUEST "fallbacks"
#define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl"
84
#define SHOUTCAST_METADATA_REQUEST "admin.cgi"
85 86
#define METADATA_RAW_REQUEST "metadata"
#define METADATA_TRANSFORMED_REQUEST "metadata.xsl"
87 88 89 90 91 92 93 94
#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"
95
#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
96 97 98 99 100 101 102
#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"
103 104
#define MANAGEAUTH_RAW_REQUEST "manageauth"
#define MANAGEAUTH_TRANSFORMED_REQUEST "manageauth.xsl"
105 106
#define UPDATEMETADATA_RAW_REQUEST "updatemetadata"
#define UPDATEMETADATA_TRANSFORMED_REQUEST "updatemetadata.xsl"
107 108
#define DEFAULT_RAW_REQUEST ""
#define DEFAULT_TRANSFORMED_REQUEST ""
109
#define BUILDM3U_RAW_REQUEST "buildm3u"
110 111 112

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

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

200 201 202 203
/* 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)
204 205 206 207 208 209 210 211 212 213 214
{
    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);
215

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

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

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

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

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

    client->respcode = 200;
    if (response == RAW) {
266
        xmlDocDumpMemory(doc, &buff, &len);
267 268 269 270
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Length: %d\r\n"
               "Content-Type: text/xml\r\n"
               "\r\n", len);
271
        html_write(client, "%s", buff);
272 273 274 275 276 277 278 279 280
    }
    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);
281
        config_release_config();
282 283 284 285 286 287 288 289 290 291 292
        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);
    }
}
293 294 295 296
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;
297
    int noauth = 0;
298

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

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

314
    DEBUG1("Got command (%s)", command_string);
315 316 317 318 319 320 321 322 323
    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;
    }

324 325
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

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

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

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

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

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

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

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

#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);
528 529
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
530

531
static void html_success(client_t *client, char *message)
532 533 534 535 536 537 538 539 540 541 542 543
{
    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);
}

544 545 546 547 548 549 550 551 552 553 554
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;
}

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

    DEBUG0("Doing optional check");
566
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
567 568 569 570 571 572 573 574 575 576 577
        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;
    }
578

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

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

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

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

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

603
    source_move_clients (source, dest);
604

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

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

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

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

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

    avl_tree_rlock(source->client_tree);

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

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

671 672 673 674 675 676 677 678 679 680 681 682 683
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();
684
    host = strdup(config->hostname);
685
    port = config->port;
686
    config_release_config();
687 688 689 690 691 692 693 694 695 696 697 698 699

    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
    );
700 701

    free(host);
702 703
    client_destroy(client);
}
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 760 761 762 763 764
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);
}

765 766
static void command_kill_source(client_t *client, source_t *source,
    int response)
767
{
768 769 770 771 772 773 774 775 776
    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);

777 778
    source->running = 0;

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

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

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

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

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

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

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

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

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

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

    DEBUG0("Got metadata update request");

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

Karl Heyes's avatar
Karl Heyes committed
867 868
    if (strcmp (action, "updinfo") != 0)
    {
869 870 871 872 873 874
        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);
875 876
        return;
    }
877

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

880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
    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
    {
900 901 902 903 904 905 906 907
        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;
908
    }
909 910 911 912 913 914 915

    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);
916 917
}

918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
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;
    }

934 935 936
    if (source->format && source->format->set_tag)
    {
        source->format->set_tag (source->format, "title", value);
937

938 939 940 941 942 943 944 945
        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");
    }
946 947
}

948 949 950
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

951 952
    DEBUG0("Stats request, sending xml stats");

953
    stats_get_xml(&doc, 1);
954 955
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
956 957 958 959
    client_destroy(client);
    return;
}

Karl Heyes's avatar
Karl Heyes committed
960 961
static void command_list_mounts(client_t *client, int response)
{
962 963
    DEBUG0("List mounts request");

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

972 973 974
        ice_config_t *config = config_get_config ();
        mount_proxy *mountinfo = config->mounts;
        while (mountinfo)
Karl Heyes's avatar
Karl Heyes committed
975
        {
976 977 978 979 980 981 982 983 984 985 986 987
            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;
            if (source->running == 0)
                continue;
            if (source->hidden)
988
                continue;
Karl Heyes's avatar
Karl Heyes committed
989 990
            remaining -= ret;
            buf += ret;
991
            ret = snprintf (buf, remaining, "%s\n", current->mountname);
992
        }
Karl Heyes's avatar
Karl Heyes committed
993
        avl_tree_unlock (global.source_tree);
994 995
        config_release_config();

Karl Heyes's avatar
Karl Heyes committed
996
        /* handle last line */
997
        if (ret > 0 && (unsigned)ret < remaining)
Karl Heyes's avatar
Karl Heyes committed
998 999 1000 1001 1002
        {
            remaining -= ret;
            buf += ret;
        }
        sock_write_bytes (client->con->sock, buffer, sizeof (buffer)-remaining);
1003
    }
Karl Heyes's avatar
Karl Heyes committed
1004 1005 1006 1007
    else
    {
        xmlDocPtr doc = admin_build_sourcelist(NULL);
        avl_tree_unlock (global.source_tree);
1008 1009 1010 1011 1012

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
1013
    client_destroy(client);
1014

1015 1016 1017
    return;
}

1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034
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);
}