admin.c 30.3 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 220 221
            xmlNewChild(srcnode, NULL, "Connected", buf);
            xmlNewChild(srcnode, NULL, "Format", 
                    source->format->format_description);
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 302 303
    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;
    }

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

304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {
        source_t *source;

        mount = "/";
        noauth = 1;
        avl_tree_rlock(global.source_tree);
        source = source_find_mount_raw(mount);
        if (source == NULL) {
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
            avl_tree_unlock(global.source_tree);
            client_send_400(client, "Mount / does not exist");
            return;
        }
        else {
            if (source->shoutcast_compat == 0) {
                ERROR0("Illegal call to change metadata, source not shoutcast compatible");
                avl_tree_unlock (global.source_tree);
                client_send_400 (client, "Illegal metadata call");
                return;
            }
        }
        avl_tree_unlock(global.source_tree);
    }

329 330 331
    if(mount != NULL) {
        source_t *source;

332 333 334
        if (command == COMMAND_BUILDM3U) {
            noauth = 1;
        }
335
        /* This is a mount request, handle it as such */
336 337 338 339 340 341 342 343
        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;
                }
344
            }
345 346 347
        }
        
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
348
        source = source_find_mount_raw(mount);
349

350 351
        if (source == NULL)
        {
352 353
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
354
            avl_tree_unlock(global.source_tree);
355 356
            client_send_400(client, "Source does not exist");
        }
357 358
        else
        {
359 360 361 362 363 364 365 366 367
            if (!source->shoutcast_compat) {
                if (source->running == 0)
                {
                    INFO2("Received admin command %s on unavailable mount \"%s\"",
                            command_string, mount);
                    avl_tree_unlock (global.source_tree);
                    client_send_400 (client, "Source is not available");
                    return;
                }
368
            }
369 370 371
            INFO2("Received admin command %s on mount \"%s\"", 
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
372
            avl_tree_unlock(global.source_tree);
373
        }
374 375 376
    }
    else {

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

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

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

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

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

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

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

550 551 552 553 554 555 556 557
    dest = source_find_mount (dest_source);

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

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

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

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

574
    source_move_clients (source, dest);
575

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

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
586 587
}

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

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

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

    avl_tree_rlock(source->client_tree);

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

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

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

    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
    );
671 672

    free(host);
673 674
    client_destroy(client);
}
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 734 735
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);
}

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

748 749
    source->running = 0;

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

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

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

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

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

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

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

815
    html_success(client, "Fallback configured");
816 817 818 819 820 821
}

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

    DEBUG0("Got metadata update request");

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

829 830
    if ((source->format->type != FORMAT_TYPE_MP3) &&
        (source->format->type != FORMAT_TYPE_NSV))
Karl Heyes's avatar
Karl Heyes committed
831 832
    {
        client_send_400 (client, "Not mp3, cannot update metadata");
833 834 835
        return;
    }

Karl Heyes's avatar
Karl Heyes committed
836 837 838
    if (strcmp (action, "updinfo") != 0)
    {
        client_send_400 (client, "No such action");
839 840
        return;
    }
841 842 843

    state = source->format->_state;

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

846 847
    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
848
    stats_event(source->mount, "title", value);
Karl Heyes's avatar
Karl Heyes committed
849

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

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

857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914
static void command_shoutcast_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
    char *source_pass;
    char *config_source_pass;
    ice_config_t *config;
    mp3_state *state;

    DEBUG0("Got shoutcast metadata update request");

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

    config = config_get_config();
    config_source_pass = strdup(config->source_password);
    config_release_config();

    if ((source->format->type != FORMAT_TYPE_MP3) &&
        (source->format->type != FORMAT_TYPE_NSV))
    {
        client_send_400 (client, "Not mp3 or NSV, cannot update metadata");
        return;
    }

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

    if (strcmp(source_pass, config_source_pass) != 0)
    {
        ERROR0("Invalid source password specified, metadata not updated");
        client_send_400 (client, "Invalid source password");
        return;
    }

    if (config_source_pass) {
        free(config_source_pass);
    }

    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");
}

915 916 917
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

918 919
    DEBUG0("Stats request, sending xml stats");

920 921 922
    stats_get_xml(&doc);
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
923 924 925 926
    client_destroy(client);
    return;
}

Karl Heyes's avatar
Karl Heyes committed
927 928
static void command_list_mounts(client_t *client, int response)
{
929 930
    avl_node *node;
    source_t *source;
931 932 933

    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
934
    avl_tree_rlock (global.source_tree);
935 936
    if (response == PLAINTEXT)
    {
Karl Heyes's avatar
Karl Heyes committed
937 938 939 940 941
        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");

942
        node = avl_get_first(global.source_tree);
Karl Heyes's avatar
Karl Heyes committed
943 944 945 946
        while (node && ret > 0 && ret < remaining)
        {
            remaining -= ret;
            buf += ret;
947
            source = (source_t *)node->key;
Karl Heyes's avatar
Karl Heyes committed
948
            ret = snprintf (buf, remaining, "%s\n", source->mount);
949 950
            node = avl_get_next(node);
        }
Karl Heyes's avatar
Karl Heyes committed
951 952 953 954 955 956 957 958
        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);
959
    }
Karl Heyes's avatar
Karl Heyes committed
960 961 962 963
    else
    {
        xmlDocPtr doc = admin_build_sourcelist(NULL);
        avl_tree_unlock (global.source_tree);
964 965 966 967 968

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
969
    client_destroy(client);
970

971 972 973
    return;
}