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

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

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

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

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

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

#define CATMODULE "admin"

47 48 49
#define COMMAND_ERROR             (-1)

/* Mount-specific commands */
50
#define COMMAND_RAW_FALLBACK        1
51
#define COMMAND_METADATA_UPDATE     2
52 53
#define COMMAND_RAW_SHOW_LISTENERS  3
#define COMMAND_RAW_MOVE_CLIENTS    4
54
#define COMMAND_RAW_MANAGEAUTH      5
55 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 79 80 81 82 83 84 85 86

#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"
87
#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
88 89 90 91 92 93 94
#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"
95 96
#define MANAGEAUTH_RAW_REQUEST "manageauth"
#define MANAGEAUTH_TRANSFORMED_REQUEST "manageauth.xsl"
97 98 99 100 101
#define DEFAULT_RAW_REQUEST ""
#define DEFAULT_TRANSFORMED_REQUEST ""

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

153
static void command_fallback(client_t *client, source_t *source, int response);
154
static void command_metadata(client_t *client, source_t *source);
155 156 157 158 159 160 161 162
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);
163 164
static void command_manageauth(client_t *client, source_t *source,
        int response);
165 166
static void command_kill_source(client_t *client, source_t *source,
        int response);
167 168 169
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);
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
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);
186

187 188 189 190 191 192 193
    if (current_source) {
        xmlNewChild(xmlnode, NULL, "current_source", current_source);
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
194 195 196 197
        if (source->running)
        {
            srcnode = xmlNewChild(xmlnode, NULL, "source", NULL);
            xmlSetProp(srcnode, "mount", source->mount);
198

199
            xmlNewChild(srcnode, NULL, "fallback", 
200 201
                    (source->fallback_mount != NULL)?
                    source->fallback_mount:"");
202 203
            snprintf(buf, sizeof(buf), "%ld", source->listeners);
            xmlNewChild(srcnode, NULL, "listeners", buf);
Karl Heyes's avatar
Karl Heyes committed
204 205
            snprintf(buf, sizeof(buf), "%lu",
                    (unsigned long)(now - source->con->con_time));
206 207 208
            xmlNewChild(srcnode, NULL, "Connected", buf);
            xmlNewChild(srcnode, NULL, "Format", 
                    source->format->format_description);
209 210 211 212
            if (source->authenticator) {
                xmlNewChild(srcnode, NULL, "authenticator", 
                    source->authenticator->type);
            }
213
        }
214 215 216 217 218 219 220 221
        node = avl_get_next(node);
    }
    return(doc);
}

void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template)
{
222
    xmlChar *buff = NULL;
223 224 225 226 227 228 229 230
    int len = 0;
    ice_config_t *config;
    char *fullpath_xslt_template;
    int fullpath_xslt_template_len;
    char *adminwebroot;

    client->respcode = 200;
    if (response == RAW) {
231
        xmlDocDumpMemory(doc, &buff, &len);
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
        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);
    }
}
259 260 261 262 263 264 265 266 267 268 269 270 271
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;

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

    command_string = uri + 7;

272
    DEBUG1("Got command (%s)", command_string);
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
    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;

        /* This is a mount request, handle it as such */
288 289
        if(!connection_check_admin_pass(client->parser)) {
            if(!connection_check_source_pass(client->parser, mount)) {
290
                INFO1("Bad or missing password on mount modification admin "
291 292 293 294
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
295 296 297
        }
        
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
298
        source = source_find_mount_raw(mount);
299

300 301
        if (source == NULL)
        {
302 303
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
304
            avl_tree_unlock(global.source_tree);
305 306
            client_send_400(client, "Source does not exist");
        }
307 308
        else
        {
309 310 311 312 313 314 315 316
            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;
            }
317 318 319
            INFO2("Received admin command %s on mount \"%s\"", 
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
320
            avl_tree_unlock(global.source_tree);
321
        }
322 323 324
    }
    else {

325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
        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;
            }
343 344 345 346 347 348 349 350 351 352
        }
        
        admin_handle_general_request(client, command);
    }
}

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

#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);
441 442
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
443

444
static void html_success(client_t *client, char *message)
445 446 447 448 449 450 451 452 453 454 455 456
{
    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);
}

457 458 459 460 461 462 463 464 465 466 467
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;
}

468 469
static void command_move_clients(client_t *client, source_t *source,
    int response)
470 471 472
{
    char *dest_source;
    source_t *dest;
473 474 475 476 477 478
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
479
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
480 481 482 483 484 485 486 487 488 489 490
        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;
    }
491

492 493 494 495 496 497 498 499
    dest = source_find_mount (dest_source);

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

Karl Heyes's avatar
Karl Heyes committed
500
    if (strcmp (dest->mount, source->mount) == 0)
501 502 503 504 505
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

506 507 508
    if (dest->running == 0)
    {
        client_send_400 (client, "Destination not running");
509 510 511
        return;
    }

512 513 514 515
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

516
    source_move_clients (source, dest);
517

518
    memset(buf, '\000', sizeof(buf));
519 520
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
521 522 523 524 525 526 527
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
528 529
}

530 531
static void command_show_listeners(client_t *client, source_t *source,
    int response)
532
{
533 534
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
535 536
    avl_node *client_node;
    client_t *current;
537 538
    char buf[22];
    char *userAgent = NULL;
539 540
    time_t now = time(NULL);

541 542 543 544 545
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
546

547
    memset(buf, '\000', sizeof(buf));
548
    snprintf(buf, sizeof(buf)-1, "%ld", source->listeners);
549
    xmlNewChild(srcnode, NULL, "Listeners", buf);
550 551 552 553 554 555

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
556 557 558 559 560 561 562 563 564 565
        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));
566
        snprintf(buf, sizeof(buf)-1, "%ld", now - current->con->con_time);
567 568
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
569
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
570
        xmlNewChild(listenernode, NULL, "ID", buf);
571 572 573
        if (current->username) {
            xmlNewChild(listenernode, NULL, "username", current->username);
        }
574 575 576 577
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
578 579 580
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
581 582 583
    client_destroy(client);
}

584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
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);
}

645 646
static void command_kill_source(client_t *client, source_t *source,
    int response)
647
{
648 649 650 651 652 653 654 655 656
    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);

657 658
    source->running = 0;

659 660 661 662
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
663 664
}

665 666
static void command_kill_client(client_t *client, source_t *source,
    int response)
667 668 669 670
{
    char *idtext;
    int id;
    client_t *listener;
671 672 673
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
674 675 676 677 678 679 680

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

681 682 683 684 685
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

686 687 688 689 690 691 692
    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;
693 694 695 696
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "1");
697 698
    }
    else {
699 700 701 702
        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");
703
    }
704 705 706 707
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
708 709
}

710 711
static void command_fallback(client_t *client, source_t *source,
    int response)
712 713 714 715 716 717 718 719 720 721 722 723
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

724
    html_success(client, "Fallback configured");
725 726 727 728 729 730
}

static void command_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
731
    mp3_state *state;
732 733 734 735
#ifdef USE_YP
    int i;
    time_t current_time;
#endif
736 737 738 739 740 741

    DEBUG0("Got metadata update request");

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

742
    if (source->format->type != FORMAT_TYPE_MP3)
Karl Heyes's avatar
Karl Heyes committed
743 744
    {
        client_send_400 (client, "Not mp3, cannot update metadata");
745 746 747
        return;
    }

Karl Heyes's avatar
Karl Heyes committed
748 749 750
    if (strcmp (action, "updinfo") != 0)
    {
        client_send_400 (client, "No such action");
751 752
        return;
    }
753 754 755 756 757 758 759 760

    state = source->format->_state;

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

762 763
    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
764
    stats_event(source->mount, "title", value);
Karl Heyes's avatar
Karl Heyes committed
765

766 767 768 769 770 771 772 773 774 775
#ifdef USE_YP
    /* If we get an update on the mountpoint, force a
       yp touch */
    current_time = time(NULL);
    for (i=0; i<source->num_yp_directories; i++) {
        source->ypdata[i]->yp_last_touch = current_time - 
            source->ypdata[i]->yp_touch_interval + 2;
    }
#endif

776
    html_success(client, "Metadata update successful");
777 778
}

779 780 781
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

782 783
    DEBUG0("Stats request, sending xml stats");

784 785 786
    stats_get_xml(&doc);
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
787 788 789 790
    client_destroy(client);
    return;
}

Karl Heyes's avatar
Karl Heyes committed
791 792
static void command_list_mounts(client_t *client, int response)
{
793 794
    avl_node *node;
    source_t *source;
795 796 797

    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
798
    avl_tree_rlock (global.source_tree);
799 800
    if (response == PLAINTEXT)
    {
Karl Heyes's avatar
Karl Heyes committed
801 802 803 804 805
        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");

806
        node = avl_get_first(global.source_tree);
Karl Heyes's avatar
Karl Heyes committed
807 808 809 810
        while (node && ret > 0 && ret < remaining)
        {
            remaining -= ret;
            buf += ret;
811
            source = (source_t *)node->key;
Karl Heyes's avatar
Karl Heyes committed
812
            ret = snprintf (buf, remaining, "%s\n", source->mount);
813 814
            node = avl_get_next(node);
        }
Karl Heyes's avatar
Karl Heyes committed
815 816 817 818 819 820 821 822
        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);
823
    }
Karl Heyes's avatar
Karl Heyes committed
824 825 826 827
    else
    {
        xmlDocPtr doc = admin_build_sourcelist(NULL);
        avl_tree_unlock (global.source_tree);
828 829 830 831 832

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
833
    client_destroy(client);
834

835 836 837
    return;
}