admin.c 27.4 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 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Length: %d\r\n"
               "Content-Type: text/xml\r\n"
               "\r\n", len);
        html_write(client, buff);
    }
    if (response == TRANSFORMED) {
        config = config_get_config();
        adminwebroot = config->adminroot_dir;
        config_release_config();
        fullpath_xslt_template_len = strlen(adminwebroot) + 
            strlen(xslt_template) + 2;
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        memset(fullpath_xslt_template, '\000', fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
            adminwebroot, PATH_SEPARATOR, xslt_template);
        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);
    }
}
267 268 269 270
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;
271
    int noauth = 0;
272 273 274 275 276 277 278 279 280

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

    command_string = uri + 7;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

533
    source_move_clients (source, dest);
534

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

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

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

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

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

    avl_tree_rlock(source->client_tree);

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

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

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

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

    free(host);
632 633
    client_destroy(client);
}
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 694
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);
}

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

707 708
    source->running = 0;

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

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

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

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

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

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

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

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

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

    DEBUG0("Got metadata update request");

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

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

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

    state = source->format->_state;

    thread_mutex_lock(&(state->lock));
    free(state->metadata);
    state->metadata = strdup(value);
    state->metadata_age++;
    thread_mutex_unlock(&(state->lock));
807

808 809
    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
810
    stats_event(source->mount, "title", value);
Karl Heyes's avatar
Karl Heyes committed
811

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

816
    html_success(client, "Metadata update successful");
817 818
}

819 820 821
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

822 823
    DEBUG0("Stats request, sending xml stats");

824 825 826
    stats_get_xml(&doc);
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
827 828 829 830
    client_destroy(client);
    return;
}

Karl Heyes's avatar
Karl Heyes committed
831 832
static void command_list_mounts(client_t *client, int response)
{
833 834
    avl_node *node;
    source_t *source;
835 836 837

    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
838
    avl_tree_rlock (global.source_tree);
839 840
    if (response == PLAINTEXT)
    {
Karl Heyes's avatar
Karl Heyes committed
841 842 843 844 845
        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");

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

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
873
    client_destroy(client);
874

875 876 877
    return;
}