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

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

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

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

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

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

#define CATMODULE "admin"

47 48 49
#define COMMAND_ERROR             (-1)

/* Mount-specific commands */
50
#define COMMAND_RAW_FALLBACK        1
51
#define COMMAND_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 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

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

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

77 78 79
/* Admin commands requiring no auth */
#define COMMAND_BUILDM3U                    501

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

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

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

xmlDocPtr admin_build_sourcelist(char *current_source)
{
    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);
199

200 201 202 203 204 205 206
    if (current_source) {
        xmlNewChild(xmlnode, NULL, "current_source", current_source);
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
207 208 209 210
        if (source->running)
        {
            srcnode = xmlNewChild(xmlnode, NULL, "source", NULL);
            xmlSetProp(srcnode, "mount", source->mount);
211

212
            xmlNewChild(srcnode, NULL, "fallback", 
213 214
                    (source->fallback_mount != NULL)?
                    source->fallback_mount:"");
215 216
            snprintf(buf, sizeof(buf), "%ld", source->listeners);
            xmlNewChild(srcnode, NULL, "listeners", buf);
Karl Heyes's avatar
Karl Heyes committed
217 218
            snprintf(buf, sizeof(buf), "%lu",
                    (unsigned long)(now - source->con->con_time));
219
            xmlNewChild(srcnode, NULL, "Connected", buf);
220 221
            xmlNewChild(srcnode, NULL, "content-type", 
                    source->format->contenttype);
222 223 224 225
            if (source->authenticator) {
                xmlNewChild(srcnode, NULL, "authenticator", 
                    source->authenticator->type);
            }
226
        }
227 228 229 230 231 232 233 234
        node = avl_get_next(node);
    }
    return(doc);
}

void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template)
{
235
    xmlChar *buff = NULL;
236 237 238 239 240 241 242 243
    int len = 0;
    ice_config_t *config;
    char *fullpath_xslt_template;
    int fullpath_xslt_template_len;
    char *adminwebroot;

    client->respcode = 200;
    if (response == RAW) {
244
        xmlDocDumpMemory(doc, &buff, &len);
245 246 247 248
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Length: %d\r\n"
               "Content-Type: text/xml\r\n"
               "\r\n", len);
249
        html_write(client, "%s", buff);
250 251 252 253 254 255 256 257 258
    }
    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);
259
        config_release_config();
260 261 262 263 264 265 266 267 268 269 270
        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);
    }
}
271 272 273 274
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;
275
    int noauth = 0;
276

277 278 279
    DEBUG1("Admin request (%s)", uri);
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
280 281 282 283 284
        ERROR0("Internal error: admin request isn't");
        client_send_401(client);
        return;
    }

285 286 287 288 289 290
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
291

292
    DEBUG1("Got command (%s)", command_string);
293 294 295 296 297 298 299 300 301
    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;
    }

302 303
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

304 305 306 307 308 309 310 311
        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 ();
312
        httpp_set_query_param (client->parser, "mount", config->shoutcast_mount);
313 314
        httpp_setvar (client->parser, HTTPP_VAR_PROTOCOL, "ICY");
        httpp_setvar (client->parser, HTTPP_VAR_ICYPASSWORD, pass);
315
        config_release_config ();
316 317
    }

318 319
    mount = httpp_get_query_param(client->parser, "mount");

320 321 322
    if(mount != NULL) {
        source_t *source;

323 324 325
        if (command == COMMAND_BUILDM3U) {
            noauth = 1;
        }
326
        /* This is a mount request, handle it as such */
327 328 329 330 331 332 333 334
        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;
                }
335
            }
336 337 338
        }
        
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
339
        source = source_find_mount_raw(mount);
340

341 342
        if (source == NULL)
        {
343 344
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
345
            avl_tree_unlock(global.source_tree);
346 347
            client_send_400(client, "Source does not exist");
        }
348 349
        else
        {
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
            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;
366
            }
367 368 369
            INFO2("Received admin command %s on mount \"%s\"", 
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
370
            avl_tree_unlock(global.source_tree);
371
        }
372 373 374
    }
    else {

375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
        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;
            }
393 394 395 396 397 398 399 400 401 402
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
403
            command_stats(client, RAW);
404
            break;
405 406
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
407 408
            break;
        case COMMAND_RAW_LISTSTREAM:
409 410
            command_list_mounts(client, RAW);
            break;
411 412 413
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
414 415 416 417 418 419 420 421 422 423 424
        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);
425
            break;
426 427 428 429 430 431 432 433 434 435 436
        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) {
437 438
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
439 440 441 442
            break;
        case COMMAND_METADATA_UPDATE:
            command_metadata(client, source);
            break;
443 444 445
        case COMMAND_SHOUTCAST_METADATA_UPDATE:
            command_shoutcast_metadata(client, source);
            break;
446 447 448 449 450 451 452 453
        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);
454
            break;
455 456
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
457
            break;
458 459
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
460
            break;
461 462 463 464 465 466 467 468 469 470 471
        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);
472
            break;
473 474 475 476 477 478
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
479 480 481
        case COMMAND_BUILDM3U:
            command_buildm3u(client, source, RAW);
            break;
482 483 484
        default:
            WARN0("Mount request not recognised");
            client_send_400(client, "Mount request unknown");
485
            break;
486 487 488 489 490 491 492 493 494 495 496
    }
}

#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);
497 498
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
499

500
static void html_success(client_t *client, char *message)
501 502 503 504 505 506 507 508 509 510 511 512
{
    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);
}

513 514 515 516 517 518 519 520 521 522 523
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;
}

524 525
static void command_move_clients(client_t *client, source_t *source,
    int response)
526 527 528
{
    char *dest_source;
    source_t *dest;
529 530 531 532 533 534
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
535
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
536 537 538 539 540 541 542 543 544 545 546
        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;
    }
547

548 549 550 551 552 553 554 555
    dest = source_find_mount (dest_source);

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

Karl Heyes's avatar
Karl Heyes committed
556
    if (strcmp (dest->mount, source->mount) == 0)
557 558 559 560 561
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

562 563 564
    if (dest->running == 0)
    {
        client_send_400 (client, "Destination not running");
565 566 567
        return;
    }

568 569 570 571
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

572
    source_move_clients (source, dest);
573

574
    memset(buf, '\000', sizeof(buf));
575 576
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
577 578 579 580 581 582 583
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
584 585
}

586 587
static void command_show_listeners(client_t *client, source_t *source,
    int response)
588
{
589 590
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
591 592
    avl_node *client_node;
    client_t *current;
593 594
    char buf[22];
    char *userAgent = NULL;
595 596
    time_t now = time(NULL);

597 598 599 600 601
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
602

603
    memset(buf, '\000', sizeof(buf));
604
    snprintf(buf, sizeof(buf)-1, "%ld", source->listeners);
605
    xmlNewChild(srcnode, NULL, "Listeners", buf);
606 607 608 609 610 611

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
612 613 614 615 616 617 618 619 620 621
        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));
622
        snprintf(buf, sizeof(buf)-1, "%ld", now - current->con->con_time);
623 624
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
625
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
626
        xmlNewChild(listenernode, NULL, "ID", buf);
627 628 629
        if (current->username) {
            xmlNewChild(listenernode, NULL, "username", current->username);
        }
630 631 632 633
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
634 635 636
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
637 638 639
    client_destroy(client);
}

640 641 642 643 644 645 646 647 648 649 650 651 652
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();
653
    host = strdup(config->hostname);
654
    port = config->port;
655
    config_release_config();
656 657 658 659 660 661 662 663 664 665 666 667 668

    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
    );
669 670

    free(host);
671 672
    client_destroy(client);
}
673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 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
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);
}

734 735
static void command_kill_source(client_t *client, source_t *source,
    int response)
736
{
737 738 739 740 741 742 743 744 745
    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);

746 747
    source->running = 0;

748 749 750 751
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
752 753
}

754 755
static void command_kill_client(client_t *client, source_t *source,
    int response)
756 757 758 759
{
    char *idtext;
    int id;
    client_t *listener;
760 761 762
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
763 764 765 766 767 768 769

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

770 771 772 773 774
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

775 776 777 778 779 780 781
    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;
782 783 784 785
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "1");
786 787
    }
    else {
788 789 790 791
        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");
792
    }
793 794 795 796
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
797 798
}

799 800
static void command_fallback(client_t *client, source_t *source,
    int response)
801 802 803 804 805 806 807 808 809 810 811 812
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

813
    html_success(client, "Fallback configured");
814 815 816 817 818 819
}

static void command_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
820
    mp3_state *state;
821 822 823 824 825 826

    DEBUG0("Got metadata update request");

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

827 828
    if (source->format->type == FORMAT_TYPE_VORBIS) {
        client_send_400 (client, "Cannot update metadata on vorbis streams");
829 830 831
        return;
    }

Karl Heyes's avatar
Karl Heyes committed
832 833 834
    if (strcmp (action, "updinfo") != 0)
    {
        client_send_400 (client, "No such action");
835 836
        return;
    }
837 838 839

    state = source->format->_state;

Karl Heyes's avatar
Karl Heyes committed
840
    mp3_set_tag (source->format, "title", value);
841

842 843
    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
844
    stats_event(source->mount, "title", value);
Karl Heyes's avatar
Karl Heyes committed
845

846 847 848
    /* At this point, we assume that the metadata passed in
       is encoded in UTF-8 */
    logging_playlist(source->mount, value, source->listeners);
849 850
    /* If we get an update on the mountpoint, force a
       yp touch */
851
    yp_touch (source->mount);
852

853
    html_success(client, "Metadata update successful");
854 855
}

856 857 858 859 860 861 862 863 864 865 866
static void command_shoutcast_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
    mp3_state *state;

    DEBUG0("Got shoutcast metadata update request");

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

867 868
    if (source->format->type == FORMAT_TYPE_VORBIS) {
        client_send_400 (client, "Cannot update metadata on vorbis streams");
869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892
        return;
    }

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

    state = source->format->_state;

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

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

    /* If we get an update on the mountpoint, force a
       yp touch */
    yp_touch (source->mount);

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

893 894 895
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

896 897
    DEBUG0("Stats request, sending xml stats");

898 899 900
    stats_get_xml(&doc);
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
901 902 903 904
    client_destroy(client);
    return;
}

Karl Heyes's avatar
Karl Heyes committed
905 906
static void command_list_mounts(client_t *client, int response)
{
907 908
    avl_node *node;
    source_t *source;
909 910 911

    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
912
    avl_tree_rlock (global.source_tree);
913 914
    if (response == PLAINTEXT)
    {
Karl Heyes's avatar
Karl Heyes committed
915 916 917 918 919
        char buffer [4096], *buf = buffer;
        unsigned remaining = sizeof (buffer);
        int ret = sprintf (buffer,
                "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");

920
        node = avl_get_first(global.source_tree);
Karl Heyes's avatar
Karl Heyes committed
921 922 923 924
        while (node && ret > 0 && ret < remaining)
        {
            remaining -= ret;
            buf += ret;
925
            source = (source_t *)node->key;
Karl Heyes's avatar
Karl Heyes committed
926
            ret = snprintf (buf, remaining, "%s\n", source->mount);
927 928
            node = avl_get_next(node);
        }
Karl Heyes's avatar
Karl Heyes committed
929 930 931 932 933 934 935 936
        avl_tree_unlock (global.source_tree);
        /* handle last line */
        if (ret > 0 && ret < remaining)
        {
            remaining -= ret;
            buf += ret;
        }
        sock_write_bytes (client->con->sock, buffer, sizeof (buffer)-remaining);
937
    }
Karl Heyes's avatar
Karl Heyes committed
938 939 940 941
    else
    {
        xmlDocPtr doc = admin_build_sourcelist(NULL);
        avl_tree_unlock (global.source_tree);
942 943 944 945 946

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
947
    client_destroy(client);
948

949 950 951
    return;
}