admin.c 27.2 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 56 57 58

#define COMMAND_TRANSFORMED_FALLBACK        50
#define COMMAND_TRANSFORMED_SHOW_LISTENERS  53
#define COMMAND_TRANSFORMED_MOVE_CLIENTS    54
59
#define COMMAND_TRANSFORMED_MANAGEAUTH      55
60 61

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

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

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

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

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

159
static void command_fallback(client_t *client, source_t *source, int response);
160
static void command_metadata(client_t *client, source_t *source);
161 162 163 164 165 166 167 168
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);
169 170
static void command_manageauth(client_t *client, source_t *source,
        int response);
171 172
static void command_buildm3u(client_t *client, source_t *source,
        int response);
173 174
static void command_kill_source(client_t *client, source_t *source,
        int response);
175 176 177
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);
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
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);
194

195 196 197 198 199 200 201
    if (current_source) {
        xmlNewChild(xmlnode, NULL, "current_source", current_source);
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
202 203 204 205
        if (source->running)
        {
            srcnode = xmlNewChild(xmlnode, NULL, "source", NULL);
            xmlSetProp(srcnode, "mount", source->mount);
206

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

void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template)
{
230
    xmlChar *buff = NULL;
231 232 233 234 235 236 237 238
    int len = 0;
    ice_config_t *config;
    char *fullpath_xslt_template;
    int fullpath_xslt_template_len;
    char *adminwebroot;

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

    if(strncmp("/admin/", uri, 7)) {
        ERROR0("Internal error: admin request isn't");
        client_send_401(client);
        return;
    }

    command_string = uri + 7;

280
    DEBUG1("Got command (%s)", command_string);
281 282 283 284 285 286 287 288 289 290 291 292 293 294
    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");

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

295 296 297
        if (command == COMMAND_BUILDM3U) {
            noauth = 1;
        }
298
        /* This is a mount request, handle it as such */
299 300 301 302 303 304 305 306
        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;
                }
307
            }
308 309 310
        }
        
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
311
        source = source_find_mount_raw(mount);
312

313 314
        if (source == NULL)
        {
315 316
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
317
            avl_tree_unlock(global.source_tree);
318 319
            client_send_400(client, "Source does not exist");
        }
320 321
        else
        {
322 323 324 325 326 327 328 329
            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;
            }
330 331 332
            INFO2("Received admin command %s on mount \"%s\"", 
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
333
            avl_tree_unlock(global.source_tree);
334
        }
335 336 337
    }
    else {

338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
        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;
            }
356 357 358 359 360 361 362 363 364 365
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
366
            command_stats(client, RAW);
367
            break;
368 369
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
370 371
            break;
        case COMMAND_RAW_LISTSTREAM:
372 373
            command_list_mounts(client, RAW);
            break;
374 375 376
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
377 378 379 380 381 382 383 384 385 386 387
        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);
388
            break;
389 390 391 392 393 394 395 396 397 398 399
        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) {
400 401
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
402 403 404 405
            break;
        case COMMAND_METADATA_UPDATE:
            command_metadata(client, source);
            break;
406 407 408 409 410 411 412 413
        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);
414
            break;
415 416
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
417
            break;
418 419
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
420
            break;
421 422 423 424 425 426 427 428 429 430 431
        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);
432
            break;
433 434 435 436 437 438
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
439 440 441
        case COMMAND_BUILDM3U:
            command_buildm3u(client, source, RAW);
            break;
442 443 444
        default:
            WARN0("Mount request not recognised");
            client_send_400(client, "Mount request unknown");
445
            break;
446 447 448 449 450 451 452 453 454 455 456
    }
}

#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);
457 458
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
459

460
static void html_success(client_t *client, char *message)
461 462 463 464 465 466 467 468 469 470 471 472
{
    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);
}

473 474 475 476 477 478 479 480 481 482 483
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;
}

484 485
static void command_move_clients(client_t *client, source_t *source,
    int response)
486 487 488
{
    char *dest_source;
    source_t *dest;
489 490 491 492 493 494
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
495
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
496 497 498 499 500 501 502 503 504 505 506
        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;
    }
507

508 509 510 511 512 513 514 515
    dest = source_find_mount (dest_source);

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

Karl Heyes's avatar
Karl Heyes committed
516
    if (strcmp (dest->mount, source->mount) == 0)
517 518 519 520 521
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

522 523 524
    if (dest->running == 0)
    {
        client_send_400 (client, "Destination not running");
525 526 527
        return;
    }

528 529 530 531
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

532
    source_move_clients (source, dest);
533

534
    memset(buf, '\000', sizeof(buf));
535 536
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
537 538 539 540 541 542 543
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
544 545
}

546 547
static void command_show_listeners(client_t *client, source_t *source,
    int response)
548
{
549 550
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
551 552
    avl_node *client_node;
    client_t *current;
553 554
    char buf[22];
    char *userAgent = NULL;
555 556
    time_t now = time(NULL);

557 558 559 560 561
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
562

563
    memset(buf, '\000', sizeof(buf));
564
    snprintf(buf, sizeof(buf)-1, "%ld", source->listeners);
565
    xmlNewChild(srcnode, NULL, "Listeners", buf);
566 567 568 569 570 571

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
572 573 574 575 576 577 578 579 580 581
        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));
582
        snprintf(buf, sizeof(buf)-1, "%ld", now - current->con->con_time);
583 584
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
585
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
586
        xmlNewChild(listenernode, NULL, "ID", buf);
587 588 589
        if (current->username) {
            xmlNewChild(listenernode, NULL, "username", current->username);
        }
590 591 592 593
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
594 595 596
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
597 598 599
    client_destroy(client);
}

600 601 602 603 604 605 606 607 608 609 610 611 612
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();
613
    host = strdup(config->hostname);
614
    port = config->port;
615
    config_release_config();
616 617 618 619 620 621 622 623 624 625 626 627 628

    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
    );
629 630

    free(host);
631 632
    client_destroy(client);
}
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 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
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);
}

694 695
static void command_kill_source(client_t *client, source_t *source,
    int response)
696
{
697 698 699 700 701 702 703 704 705
    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);

706 707
    source->running = 0;

708 709 710 711
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
712 713
}

714 715
static void command_kill_client(client_t *client, source_t *source,
    int response)
716 717 718 719
{
    char *idtext;
    int id;
    client_t *listener;
720 721 722
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
723 724 725 726 727 728 729

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

730 731 732 733 734
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

735 736 737 738 739 740 741
    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;
742 743 744 745
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "1");
746 747
    }
    else {
748 749 750 751
        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");
752
    }
753 754 755 756
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
757 758
}

759 760
static void command_fallback(client_t *client, source_t *source,
    int response)
761 762 763 764 765 766 767 768 769 770 771 772
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

773
    html_success(client, "Fallback configured");
774 775 776 777 778 779
}

static void command_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
780
    mp3_state *state;
781 782 783 784 785 786

    DEBUG0("Got metadata update request");

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

787
    if (source->format->type != FORMAT_TYPE_MP3)
Karl Heyes's avatar
Karl Heyes committed
788 789
    {
        client_send_400 (client, "Not mp3, cannot update metadata");
790 791 792
        return;
    }

Karl Heyes's avatar
Karl Heyes committed
793 794 795
    if (strcmp (action, "updinfo") != 0)
    {
        client_send_400 (client, "No such action");
796 797
        return;
    }
798 799 800

    state = source->format->_state;

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

803 804
    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
805
    stats_event(source->mount, "title", value);
Karl Heyes's avatar
Karl Heyes committed
806

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

811
    html_success(client, "Metadata update successful");
812 813
}

814 815 816
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

817 818
    DEBUG0("Stats request, sending xml stats");

819 820 821
    stats_get_xml(&doc);
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
822 823 824 825
    client_destroy(client);
    return;
}

Karl Heyes's avatar
Karl Heyes committed
826 827
static void command_list_mounts(client_t *client, int response)
{
828 829
    avl_node *node;
    source_t *source;
830 831 832

    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
833
    avl_tree_rlock (global.source_tree);
834 835
    if (response == PLAINTEXT)
    {
Karl Heyes's avatar
Karl Heyes committed
836 837 838 839 840
        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");

841
        node = avl_get_first(global.source_tree);
Karl Heyes's avatar
Karl Heyes committed
842 843 844 845
        while (node && ret > 0 && ret < remaining)
        {
            remaining -= ret;
            buf += ret;
846
            source = (source_t *)node->key;
Karl Heyes's avatar
Karl Heyes committed
847
            ret = snprintf (buf, remaining, "%s\n", source->mount);
848 849
            node = avl_get_next(node);
        }
Karl Heyes's avatar
Karl Heyes committed
850 851 852 853 854 855 856 857
        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);
858
    }
Karl Heyes's avatar
Karl Heyes committed
859 860 861 862
    else
    {
        xmlDocPtr doc = admin_build_sourcelist(NULL);
        avl_tree_unlock (global.source_tree);
863 864 865 866 867

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
868
    client_destroy(client);
869

870 871 872
    return;
}