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

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

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

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

#include "format.h"

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

#define CATMODULE "admin"

46 47 48
#define COMMAND_ERROR             (-1)

/* Mount-specific commands */
49
#define COMMAND_RAW_FALLBACK        1
50
#define COMMAND_RAW_METADATA_UPDATE     2
51 52
#define COMMAND_RAW_SHOW_LISTENERS  3
#define COMMAND_RAW_MOVE_CLIENTS    4
53
#define COMMAND_RAW_MANAGEAUTH      5
54
#define COMMAND_SHOUTCAST_METADATA_UPDATE     6
55
#define COMMAND_RAW_UPDATEMETADATA      7
56 57 58 59

#define COMMAND_TRANSFORMED_FALLBACK        50
#define COMMAND_TRANSFORMED_SHOW_LISTENERS  53
#define COMMAND_TRANSFORMED_MOVE_CLIENTS    54
60
#define COMMAND_TRANSFORMED_MANAGEAUTH      55
61 62
#define COMMAND_TRANSFORMED_UPDATEMETADATA  56
#define COMMAND_TRANSFORMED_METADATA_UPDATE 57
63 64

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

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

79 80 81
/* Admin commands requiring no auth */
#define COMMAND_BUILDM3U                    501

82 83
#define FALLBACK_RAW_REQUEST "fallbacks"
#define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl"
84
#define SHOUTCAST_METADATA_REQUEST "admin.cgi"
85 86
#define METADATA_RAW_REQUEST "metadata"
#define METADATA_TRANSFORMED_REQUEST "metadata.xsl"
87 88 89 90 91 92 93 94
#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"
95
#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
96 97 98 99 100 101 102
#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"
103 104
#define MANAGEAUTH_RAW_REQUEST "manageauth"
#define MANAGEAUTH_TRANSFORMED_REQUEST "manageauth.xsl"
105 106
#define UPDATEMETADATA_RAW_REQUEST "updatemetadata"
#define UPDATEMETADATA_TRANSFORMED_REQUEST "updatemetadata.xsl"
107 108
#define DEFAULT_RAW_REQUEST ""
#define DEFAULT_TRANSFORMED_REQUEST ""
109
#define BUILDM3U_RAW_REQUEST "buildm3u"
110 111 112

#define RAW         1
#define TRANSFORMED 2
113
#define PLAINTEXT   3
114 115
int admin_get_command(char *command)
{
116 117 118 119
    if(!strcmp(command, FALLBACK_RAW_REQUEST))
        return COMMAND_RAW_FALLBACK;
    else if(!strcmp(command, FALLBACK_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_FALLBACK;
120 121 122 123
    else if(!strcmp(command, METADATA_RAW_REQUEST))
        return COMMAND_RAW_METADATA_UPDATE;
    else if(!strcmp(command, METADATA_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_METADATA_UPDATE;
124 125
    else if(!strcmp(command, SHOUTCAST_METADATA_REQUEST))
        return COMMAND_SHOUTCAST_METADATA_UPDATE;
126 127 128 129 130
    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))
131
        return COMMAND_RAW_STATS;
132 133
    else if(!strcmp(command, STATS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
134 135
    else if(!strcmp(command, "stats.xml")) /* The old way */
        return COMMAND_RAW_STATS;
136 137 138 139 140
    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))
141
        return COMMAND_RAW_LISTSTREAM;
142 143
    else if(!strcmp(command, STREAMLIST_PLAINTEXT_REQUEST))
        return COMMAND_PLAINTEXT_LISTSTREAM;
144 145 146 147 148 149 150 151 152 153 154 155
    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;
156 157 158 159
    else if(!strcmp(command, MANAGEAUTH_RAW_REQUEST))
        return COMMAND_RAW_MANAGEAUTH;
    else if(!strcmp(command, MANAGEAUTH_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_MANAGEAUTH;
160 161 162 163
    else if(!strcmp(command, UPDATEMETADATA_RAW_REQUEST))
        return COMMAND_RAW_UPDATEMETADATA;
    else if(!strcmp(command, UPDATEMETADATA_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_UPDATEMETADATA;
164 165
    else if(!strcmp(command, BUILDM3U_RAW_REQUEST))
        return COMMAND_BUILDM3U;
166 167 168 169
    else if(!strcmp(command, DEFAULT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
    else if(!strcmp(command, DEFAULT_RAW_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
170 171 172 173
    else
        return COMMAND_ERROR;
}

174
static void command_fallback(client_t *client, source_t *source, int response);
175
static void command_metadata(client_t *client, source_t *source, int response);
176
static void command_shoutcast_metadata(client_t *client, source_t *source);
177 178 179 180 181 182 183 184
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);
185 186
static void command_manageauth(client_t *client, source_t *source,
        int response);
187 188
static void command_buildm3u(client_t *client, source_t *source,
        int response);
189 190
static void command_kill_source(client_t *client, source_t *source,
        int response);
191 192
static void command_updatemetadata(client_t *client, source_t *source,
        int response);
193 194 195
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);
196 197 198 199
static void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template);
static void html_write(client_t *client, char *fmt, ...);

200 201 202 203
/* build an XML doc containing information about currently running sources.
 * If a mountpoint is passed then that source will not be added to the XML
 * doc even if the source is running */
xmlDocPtr admin_build_sourcelist (const char *mount)
204 205 206 207 208 209 210 211 212 213 214
{
    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);
215

216 217
    if (mount) {
        xmlNewChild(xmlnode, NULL, "current_source", mount);
218 219 220 221 222
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
223 224 225 226 227 228
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

229
        if (source->running || source->on_demand)
230 231 232
        {
            srcnode = xmlNewChild(xmlnode, NULL, "source", NULL);
            xmlSetProp(srcnode, "mount", source->mount);
233

234
            xmlNewChild(srcnode, NULL, "fallback", 
235 236
                    (source->fallback_mount != NULL)?
                    source->fallback_mount:"");
237
            snprintf (buf, sizeof(buf), "%lu", source->listeners);
238
            xmlNewChild(srcnode, NULL, "listeners", buf);
239 240 241 242 243 244 245 246
            if (source->running)
            {
                snprintf (buf, sizeof(buf), "%lu",
                        (unsigned long)(now - source->con->con_time));
                xmlNewChild (srcnode, NULL, "Connected", buf);
                xmlNewChild (srcnode, NULL, "content-type", 
                        source->format->contenttype);
            }
247 248 249 250
            if (source->authenticator) {
                xmlNewChild(srcnode, NULL, "authenticator", 
                    source->authenticator->type);
            }
251
        }
252 253 254 255 256 257 258 259
        node = avl_get_next(node);
    }
    return(doc);
}

void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template)
{
260
    xmlChar *buff = NULL;
261 262 263 264 265 266 267 268
    int len = 0;
    ice_config_t *config;
    char *fullpath_xslt_template;
    int fullpath_xslt_template_len;
    char *adminwebroot;

    client->respcode = 200;
    if (response == RAW) {
269
        xmlDocDumpMemory(doc, &buff, &len);
270 271 272 273
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Length: %d\r\n"
               "Content-Type: text/xml\r\n"
               "\r\n", len);
274
        html_write(client, "%s", buff);
275 276 277 278 279 280 281 282 283
    }
    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);
284
        config_release_config();
285 286 287 288 289 290 291 292 293 294 295
        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);
    }
}
296 297 298 299
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;
300
    int noauth = 0;
301

302 303 304
    DEBUG1("Admin request (%s)", uri);
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
305 306 307 308 309
        ERROR0("Internal error: admin request isn't");
        client_send_401(client);
        return;
    }

310 311 312 313 314 315
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
316

317
    DEBUG1("Got command (%s)", command_string);
318 319 320 321 322 323 324 325 326
    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;
    }

327 328
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

329 330 331 332 333 334 335 336
        ice_config_t *config;
        char *pass = httpp_get_query_param (client->parser, "pass");
        if (pass == NULL)
        {
            client_send_400 (client, "missing pass parameter");
            return;
        }
        config = config_get_config ();
337
        httpp_set_query_param (client->parser, "mount", config->shoutcast_mount);
338 339
        httpp_setvar (client->parser, HTTPP_VAR_PROTOCOL, "ICY");
        httpp_setvar (client->parser, HTTPP_VAR_ICYPASSWORD, pass);
340
        config_release_config ();
341 342
    }

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

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

348 349 350
        if (command == COMMAND_BUILDM3U) {
            noauth = 1;
        }
351
        /* This is a mount request, handle it as such */
352 353 354 355 356 357 358 359
        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;
                }
360
            }
361 362 363
        }
        
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
364
        source = source_find_mount_raw(mount);
365

366 367
        if (source == NULL)
        {
368 369
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
370
            avl_tree_unlock(global.source_tree);
371 372
            client_send_400(client, "Source does not exist");
        }
373 374
        else
        {
375
            if (source->running == 0 && source->on_demand == 0)
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
            {
                avl_tree_unlock (global.source_tree);
                INFO2("Received admin command %s on unavailable mount \"%s\"",
                        command_string, mount);
                client_send_400 (client, "Source is not available");
                return;
            }
            if (command == COMMAND_SHOUTCAST_METADATA_UPDATE &&
                    source->shoutcast_compat == 0)
            {
                avl_tree_unlock (global.source_tree);
                ERROR0 ("illegal change of metadata on non-shoutcast "
                        "compatible stream");
                client_send_400 (client, "illegal metadata call");
                return;
391
            }
392 393 394
            INFO2("Received admin command %s on mount \"%s\"", 
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
395
            avl_tree_unlock(global.source_tree);
396
        }
397 398 399
    }
    else {

400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
        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;
            }
418 419 420 421 422 423 424 425 426 427
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
428
            command_stats(client, RAW);
429
            break;
430 431
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
432 433
            break;
        case COMMAND_RAW_LISTSTREAM:
434 435
            command_list_mounts(client, RAW);
            break;
436 437 438
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
439 440 441 442 443 444 445 446 447 448 449
        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);
450
            break;
451 452 453 454 455 456 457 458 459 460 461
        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) {
462 463
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
464
            break;
465 466 467 468 469
        case COMMAND_RAW_METADATA_UPDATE:
            command_metadata(client, source, RAW);
            break;
        case COMMAND_TRANSFORMED_METADATA_UPDATE:
            command_metadata(client, source, TRANSFORMED);
470
            break;
471 472 473
        case COMMAND_SHOUTCAST_METADATA_UPDATE:
            command_shoutcast_metadata(client, source);
            break;
474 475 476 477 478 479 480 481
        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);
482
            break;
483 484
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
485
            break;
486 487
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
488
            break;
489 490 491 492 493 494 495 496 497 498 499
        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);
500
            break;
501 502 503 504 505 506
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
507 508 509 510 511 512
        case COMMAND_TRANSFORMED_UPDATEMETADATA:
            command_updatemetadata(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_UPDATEMETADATA:
            command_updatemetadata(client, source, RAW);
            break;
513 514 515
        case COMMAND_BUILDM3U:
            command_buildm3u(client, source, RAW);
            break;
516 517 518
        default:
            WARN0("Mount request not recognised");
            client_send_400(client, "Mount request unknown");
519
            break;
520 521 522 523 524 525 526 527 528 529 530
    }
}

#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);
531 532
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
533

534
static void html_success(client_t *client, char *message)
535 536 537 538 539 540 541 542 543 544 545 546
{
    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);
}

547 548 549 550 551 552 553 554 555 556 557
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;
}

558 559
static void command_move_clients(client_t *client, source_t *source,
    int response)
560 561 562
{
    char *dest_source;
    source_t *dest;
563 564 565 566 567 568
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
569
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
570 571 572 573 574 575 576 577 578 579 580
        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;
    }
581

582 583 584 585 586 587 588 589
    dest = source_find_mount (dest_source);

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

Karl Heyes's avatar
Karl Heyes committed
590
    if (strcmp (dest->mount, source->mount) == 0)
591 592 593 594 595
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

596
    if (dest->running == 0 && dest->on_demand == 0)
597 598
    {
        client_send_400 (client, "Destination not running");
599 600 601
        return;
    }

602 603
    INFO2 ("source is \"%s\", destination is \"%s\"", source->mount, dest->mount);

604 605 606 607
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

608
    source_move_clients (source, dest);
609

610
    memset(buf, '\000', sizeof(buf));
611 612
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
613 614 615 616 617 618 619
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
620 621
}

622 623
static void command_show_listeners(client_t *client, source_t *source,
    int response)
624
{
625 626
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
627 628
    avl_node *client_node;
    client_t *current;
629 630
    char buf[22];
    char *userAgent = NULL;
631 632
    time_t now = time(NULL);

633 634 635 636 637
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
638

639
    memset(buf, '\000', sizeof(buf));
640
    snprintf (buf, sizeof(buf), "%lu", source->listeners);
641
    xmlNewChild(srcnode, NULL, "Listeners", buf);
642 643 644 645 646 647

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
648 649 650 651 652 653 654 655 656 657
        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));
Karl Heyes's avatar
Karl Heyes committed
658
        snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - current->con->con_time));
659 660
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
661
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
662
        xmlNewChild(listenernode, NULL, "ID", buf);
663 664 665
        if (current->username) {
            xmlNewChild(listenernode, NULL, "username", current->username);
        }
666 667 668 669
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
670 671 672
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
673 674 675
    client_destroy(client);
}

676 677 678 679 680 681 682 683 684 685 686 687 688
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();
689
    host = strdup(config->hostname);
690
    port = config->port;
691
    config_release_config();
692 693 694 695 696 697 698 699 700 701 702 703 704

    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
    );
705 706

    free(host);
707 708
    client_destroy(client);
}
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
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);
}

770 771
static void command_kill_source(client_t *client, source_t *source,
    int response)
772
{
773 774 775 776 777 778 779 780 781
    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);

782 783
    source->running = 0;

784 785 786 787
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
788 789
}

790 791
static void command_kill_client(client_t *client, source_t *source,
    int response)
792 793 794 795
{
    char *idtext;
    int id;
    client_t *listener;
796 797 798
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
799 800 801 802 803 804 805

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

806 807 808 809 810
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

811 812 813 814 815 816 817
    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;
818 819 820 821
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "1");
822 823
    }
    else {
824 825 826 827
        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");
828
    }
829 830 831 832
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
833 834
}

835 836
static void command_fallback(client_t *client, source_t *source,
    int response)
837 838 839 840 841 842 843 844 845 846 847 848
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

849
    html_success(client, "Fallback configured");
850 851
}

852 853
static void command_metadata(client_t *client, source_t *source,
    int response)
854 855
{
    char *action;
856 857
    char *song, *title, *artist;
    format_plugin_t *plugin;
858 859 860 861 862 863
    xmlDocPtr doc;
    xmlNodePtr node;

    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
864 865 866 867

    DEBUG0("Got metadata update request");

    COMMAND_REQUIRE(client, "mode", action);
868 869 870
    COMMAND_OPTIONAL(client, "song", song);
    COMMAND_OPTIONAL(client, "title", title);
    COMMAND_OPTIONAL(client, "artist", artist);
871

Karl Heyes's avatar
Karl Heyes committed
872 873
    if (strcmp (action, "updinfo") != 0)
    {
874 875 876 877 878 879
        xmlNewChild(node, NULL, "message", "No such action");
        xmlNewChild(node, NULL, "return", "0");
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
        client_destroy(client);
880 881
        return;
    }
882

883
    plugin = source->format;
Karl Heyes's avatar
Karl Heyes committed
884

885 886 887 888 889
    if (plugin && plugin->set_tag)
    {
        if (song)
        {
            plugin->set_tag (plugin, "song", song);
890
            INFO2 ("Metadata on mountpoint %s changed to \"%s\"", source->mount, song);
891 892 893 894 895 896 897 898 899 900 901 902 903 904
        }
        else
        {
            if (artist && title)
            {
                plugin->set_tag (plugin, "title", title);
                plugin->set_tag (plugin, "artist", artist);
                INFO3("Metadata on mountpoint %s changed to \"%s - %s\"",
                        source->mount, artist, title);
            }
        }
    }
    else
    {
905 906 907 908 909 910 911 912
        xmlNewChild(node, NULL, "message", 
            "Mountpoint will not accept URL updates");
        xmlNewChild(node, NULL, "return", "1");
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
        client_destroy(client);
        return;
913
    }
914 915 916 917 918 919 920

    xmlNewChild(node, NULL, "message", "Metadata update successful");
    xmlNewChild(node, NULL, "return", "1");
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
921 922
}

923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938
static void command_shoutcast_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;

    DEBUG0("Got shoutcast metadata update request");

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

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

939 940 941
    if (source->format && source->format->set_tag)
    {
        source->format->set_tag (source->format, "title", value);
942

943 944 945 946 947 948 949 950
        DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
                source->mount, value);
        html_success(client, "Metadata update successful");
    }
    else
    {
        client_send_400 (client, "mountpoint will not accept URL updates");
    }
951 952
}

953 954 955
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

956 957
    DEBUG0("Stats request, sending xml stats");

958
    stats_get_xml(&doc, 1);
959 960
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
961 962 963 964
    client_destroy(client);
    return;
}

Karl Heyes's avatar
Karl Heyes committed
965 966
static void command_list_mounts(client_t *client, int response)
{
967 968
    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
969
    avl_tree_rlock (global.source_tree);
970 971
    if (response == PLAINTEXT)
    {
Karl Heyes's avatar
Karl Heyes committed
972
        char buffer [4096], *buf = buffer;
973 974
        unsigned int remaining = sizeof (buffer);
        int ret = snprintf (buffer, remaining,
Karl Heyes's avatar
Karl Heyes committed
975 976
                "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");

977 978 979
        ice_config_t *config = config_get_config ();
        mount_proxy *mountinfo = config->mounts;
        while (mountinfo)
Karl Heyes's avatar
Karl Heyes committed
980
        {
981 982 983 984 985 986 987 988 989
            mount_proxy *current = mountinfo;
            source_t *source;
            mountinfo = mountinfo->next;

            /* now check that a source is available */
            source = source_find_mount (current->mountname);

            if (source == NULL)
                continue;
990
            if (source->running == 0 && source->on_demand == 0)
991 992
                continue;
            if (source->hidden)
993
                continue;
Karl Heyes's avatar
Karl Heyes committed
994 995
            remaining -= ret;
            buf += ret;
996
            ret = snprintf (buf, remaining, "%s\n", current->mountname);
997
        }
Karl Heyes's avatar
Karl Heyes committed
998
        avl_tree_unlock (global.source_tree);
999 1000
        config_release_config();

Karl Heyes's avatar
Karl Heyes committed
1001
        /* handle last line */
1002
        if (ret > 0 && (unsigned)ret < remaining)
Karl Heyes's avatar
Karl Heyes committed
1003 1004 1005 1006 1007
        {
            remaining -= ret;
            buf += ret;
        }
        sock_write_bytes (client->con->sock, buffer, sizeof (buffer)-remaining);
1008
    }
Karl Heyes's avatar
Karl Heyes committed
1009 1010 1011 1012
    else
    {
        xmlDocPtr doc = admin_build_sourcelist(NULL);
        avl_tree_unlock (global.source_tree);
1013 1014 1015 1016 1017

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
1018
    client_destroy(client);
1019

1020 1021 1022
    return;
}

1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
static void command_updatemetadata(client_t *client, source_t *source,
    int response)
{
    xmlDocPtr doc;
    xmlNodePtr node, srcnode;

    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);

    admin_send_response(doc, client, response, 
        UPDATEMETADATA_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
    client_destroy(client);
}