admin.c 30.1 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
    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
        ice_config_t *config = config_get_config ();
        httpp_set_query_param (client->parser, "mount", config->shoutcast_mount);
        config_release_config ();
307 308 309
        noauth = 1;
    }

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

312 313 314
    if(mount != NULL) {
        source_t *source;

315 316 317
        if (command == COMMAND_BUILDM3U) {
            noauth = 1;
        }
318
        /* This is a mount request, handle it as such */
319 320 321 322 323 324 325 326
        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;
                }
327
            }
328 329 330
        }
        
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
331
        source = source_find_mount_raw(mount);
332

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

367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
        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;
            }
385 386 387 388 389 390 391 392 393 394
        }
        
        admin_handle_general_request(client, command);
    }
}

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

#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);
489 490
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
491

492
static void html_success(client_t *client, char *message)
493 494 495 496 497 498 499 500 501 502 503 504
{
    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);
}

505 506 507 508 509 510 511 512 513 514 515
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;
}

516 517
static void command_move_clients(client_t *client, source_t *source,
    int response)
518 519 520
{
    char *dest_source;
    source_t *dest;
521 522 523 524 525 526
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
527
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
528 529 530 531 532 533 534 535 536 537 538
        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;
    }
539

540 541 542 543 544 545 546 547
    dest = source_find_mount (dest_source);

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

Karl Heyes's avatar
Karl Heyes committed
548
    if (strcmp (dest->mount, source->mount) == 0)
549 550 551 552 553
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

554 555 556
    if (dest->running == 0)
    {
        client_send_400 (client, "Destination not running");
557 558 559
        return;
    }

560 561 562 563
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

564
    source_move_clients (source, dest);
565

566
    memset(buf, '\000', sizeof(buf));
567 568
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
569 570 571 572 573 574 575
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
576 577
}

578 579
static void command_show_listeners(client_t *client, source_t *source,
    int response)
580
{
581 582
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
583 584
    avl_node *client_node;
    client_t *current;
585 586
    char buf[22];
    char *userAgent = NULL;
587 588
    time_t now = time(NULL);

589 590 591 592 593
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
594

595
    memset(buf, '\000', sizeof(buf));
596
    snprintf(buf, sizeof(buf)-1, "%ld", source->listeners);
597
    xmlNewChild(srcnode, NULL, "Listeners", buf);
598 599 600 601 602 603

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
604 605 606 607 608 609 610 611 612 613
        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));
614
        snprintf(buf, sizeof(buf)-1, "%ld", now - current->con->con_time);
615 616
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
617
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
618
        xmlNewChild(listenernode, NULL, "ID", buf);
619 620 621
        if (current->username) {
            xmlNewChild(listenernode, NULL, "username", current->username);
        }
622 623 624 625
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
626 627 628
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
629 630 631
    client_destroy(client);
}

632 633 634 635 636 637 638 639 640 641 642 643 644
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();
645
    host = strdup(config->hostname);
646
    port = config->port;
647
    config_release_config();
648 649 650 651 652 653 654 655 656 657 658 659 660

    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
    );
661 662

    free(host);
663 664
    client_destroy(client);
}
665 666 667 668 669 670 671 672 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
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);
}

726 727
static void command_kill_source(client_t *client, source_t *source,
    int response)
728
{
729 730 731 732 733 734 735 736 737
    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);

738 739
    source->running = 0;

740 741 742 743
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
744 745
}

746 747
static void command_kill_client(client_t *client, source_t *source,
    int response)
748 749 750 751
{
    char *idtext;
    int id;
    client_t *listener;
752 753 754
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
755 756 757 758 759 760 761

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

762 763 764 765 766
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

767 768 769 770 771 772 773
    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;
774 775 776 777
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "1");
778 779
    }
    else {
780 781 782 783
        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");
784
    }
785 786 787 788
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
789 790
}

791 792
static void command_fallback(client_t *client, source_t *source,
    int response)
793 794 795 796 797 798 799 800 801 802 803 804
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

805
    html_success(client, "Fallback configured");
806 807 808 809 810 811
}

static void command_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
812
    mp3_state *state;
813 814 815 816 817 818

    DEBUG0("Got metadata update request");

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

819 820
    if ((source->format->type != FORMAT_TYPE_MP3) &&
        (source->format->type != FORMAT_TYPE_NSV))
Karl Heyes's avatar
Karl Heyes committed
821 822
    {
        client_send_400 (client, "Not mp3, cannot update metadata");
823 824 825
        return;
    }

Karl Heyes's avatar
Karl Heyes committed
826 827 828
    if (strcmp (action, "updinfo") != 0)
    {
        client_send_400 (client, "No such action");
829 830
        return;
    }
831 832 833

    state = source->format->_state;

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

836 837
    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
838
    stats_event(source->mount, "title", value);
Karl Heyes's avatar
Karl Heyes committed
839

840 841 842
    /* At this point, we assume that the metadata passed in
       is encoded in UTF-8 */
    logging_playlist(source->mount, value, source->listeners);
843 844
    /* If we get an update on the mountpoint, force a
       yp touch */
845
    yp_touch (source->mount);
846

847
    html_success(client, "Metadata update successful");
848 849
}

850 851 852 853 854 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
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");
}

908 909 910
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

911 912
    DEBUG0("Stats request, sending xml stats");

913 914 915
    stats_get_xml(&doc);
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
916 917 918 919
    client_destroy(client);
    return;
}

Karl Heyes's avatar
Karl Heyes committed
920 921
static void command_list_mounts(client_t *client, int response)
{
922 923
    avl_node *node;
    source_t *source;
924 925 926

    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
927
    avl_tree_rlock (global.source_tree);
928 929
    if (response == PLAINTEXT)
    {
Karl Heyes's avatar
Karl Heyes committed
930 931 932 933 934
        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");

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

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
962
    client_destroy(client);
963

964 965 966
    return;
}