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

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

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

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

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

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

#define CATMODULE "admin"

47 48 49
#define COMMAND_ERROR             (-1)

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

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

/* Global commands */
Karl Heyes's avatar
Karl Heyes committed
63 64 65 66 67 68 69
#define COMMAND_RAW_LIST_MOUNTS             101
#define COMMAND_RAW_STATS                   102
#define COMMAND_RAW_LISTSTREAM              103
#define COMMAND_PLAINTEXT_LISTSTREAM        104
#define COMMAND_TRANSFORMED_LIST_MOUNTS     201
#define COMMAND_TRANSFORMED_STATS           202
#define COMMAND_TRANSFORMED_LISTSTREAM      203
70

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

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

80 81
#define FALLBACK_RAW_REQUEST "fallbacks"
#define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl"
82
#define SHOUTCAST_METADATA_REQUEST "admin.cgi"
83 84 85 86 87 88 89 90 91
#define METADATA_REQUEST "metadata"
#define LISTCLIENTS_RAW_REQUEST "listclients"
#define LISTCLIENTS_TRANSFORMED_REQUEST "listclients.xsl"
#define STATS_RAW_REQUEST "stats"
#define STATS_TRANSFORMED_REQUEST "stats.xsl"
#define LISTMOUNTS_RAW_REQUEST "listmounts"
#define LISTMOUNTS_TRANSFORMED_REQUEST "listmounts.xsl"
#define STREAMLIST_RAW_REQUEST "streamlist"
#define STREAMLIST_TRANSFORMED_REQUEST "streamlist.xsl"
92
#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
93 94 95 96 97 98 99
#define MOVECLIENTS_RAW_REQUEST "moveclients"
#define MOVECLIENTS_TRANSFORMED_REQUEST "moveclients.xsl"
#define KILLCLIENT_RAW_REQUEST "killclient"
#define KILLCLIENT_TRANSFORMED_REQUEST "killclient.xsl"
#define KILLSOURCE_RAW_REQUEST "killsource"
#define KILLSOURCE_TRANSFORMED_REQUEST "killsource.xsl"
#define ADMIN_XSL_RESPONSE "response.xsl"
100 101
#define MANAGEAUTH_RAW_REQUEST "manageauth"
#define MANAGEAUTH_TRANSFORMED_REQUEST "manageauth.xsl"
102 103
#define DEFAULT_RAW_REQUEST ""
#define DEFAULT_TRANSFORMED_REQUEST ""
104
#define BUILDM3U_RAW_REQUEST "buildm3u"
105 106 107

#define RAW         1
#define TRANSFORMED 2
108
#define PLAINTEXT   3
109 110
int admin_get_command(char *command)
{
111 112 113 114 115
    if(!strcmp(command, FALLBACK_RAW_REQUEST))
        return COMMAND_RAW_FALLBACK;
    else if(!strcmp(command, FALLBACK_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_FALLBACK;
    else if(!strcmp(command, METADATA_REQUEST))
116
        return COMMAND_METADATA_UPDATE;
117 118
    else if(!strcmp(command, SHOUTCAST_METADATA_REQUEST))
        return COMMAND_SHOUTCAST_METADATA_UPDATE;
119 120 121 122 123
    else if(!strcmp(command, LISTCLIENTS_RAW_REQUEST))
        return COMMAND_RAW_SHOW_LISTENERS;
    else if(!strcmp(command, LISTCLIENTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_SHOW_LISTENERS;
    else if(!strcmp(command, STATS_RAW_REQUEST))
124
        return COMMAND_RAW_STATS;
125 126
    else if(!strcmp(command, STATS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
127 128
    else if(!strcmp(command, "stats.xml")) /* The old way */
        return COMMAND_RAW_STATS;
129 130 131 132 133
    else if(!strcmp(command, LISTMOUNTS_RAW_REQUEST))
        return COMMAND_RAW_LIST_MOUNTS;
    else if(!strcmp(command, LISTMOUNTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_LIST_MOUNTS;
    else if(!strcmp(command, STREAMLIST_RAW_REQUEST))
134
        return COMMAND_RAW_LISTSTREAM;
135 136
    else if(!strcmp(command, STREAMLIST_PLAINTEXT_REQUEST))
        return COMMAND_PLAINTEXT_LISTSTREAM;
137 138 139 140 141 142 143 144 145 146 147 148
    else if(!strcmp(command, MOVECLIENTS_RAW_REQUEST))
        return COMMAND_RAW_MOVE_CLIENTS;
    else if(!strcmp(command, MOVECLIENTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_MOVE_CLIENTS;
    else if(!strcmp(command, KILLCLIENT_RAW_REQUEST))
        return COMMAND_RAW_KILL_CLIENT;
    else if(!strcmp(command, KILLCLIENT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_KILL_CLIENT;
    else if(!strcmp(command, KILLSOURCE_RAW_REQUEST))
        return COMMAND_RAW_KILL_SOURCE;
    else if(!strcmp(command, KILLSOURCE_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_KILL_SOURCE;
149 150 151 152
    else if(!strcmp(command, MANAGEAUTH_RAW_REQUEST))
        return COMMAND_RAW_MANAGEAUTH;
    else if(!strcmp(command, MANAGEAUTH_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_MANAGEAUTH;
153 154
    else if(!strcmp(command, BUILDM3U_RAW_REQUEST))
        return COMMAND_BUILDM3U;
155 156 157 158
    else if(!strcmp(command, DEFAULT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
    else if(!strcmp(command, DEFAULT_RAW_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
159 160 161 162
    else
        return COMMAND_ERROR;
}

163
static void command_fallback(client_t *client, source_t *source, int response);
164
static void command_metadata(client_t *client, source_t *source);
165
static void command_shoutcast_metadata(client_t *client, source_t *source);
166 167 168 169 170 171 172 173
static void command_show_listeners(client_t *client, source_t *source,
        int response);
static void command_move_clients(client_t *client, source_t *source,
        int response);
static void command_stats(client_t *client, int response);
static void command_list_mounts(client_t *client, int response);
static void command_kill_client(client_t *client, source_t *source,
        int response);
174 175
static void command_manageauth(client_t *client, source_t *source,
        int response);
176 177
static void command_buildm3u(client_t *client, source_t *source,
        int response);
178 179
static void command_kill_source(client_t *client, source_t *source,
        int response);
180 181 182
static void admin_handle_mount_request(client_t *client, source_t *source,
        int command);
static void admin_handle_general_request(client_t *client, int command);
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
static void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template);
static void html_write(client_t *client, char *fmt, ...);

xmlDocPtr admin_build_sourcelist(char *current_source)
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

    doc = xmlNewDoc("1.0");
    xmlnode = xmlNewDocNode(doc, NULL, "icestats", NULL);
    xmlDocSetRootElement(doc, xmlnode);
199

200 201 202 203 204 205 206
    if (current_source) {
        xmlNewChild(xmlnode, NULL, "current_source", current_source);
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
207 208 209 210
        if (source->running)
        {
            srcnode = xmlNewChild(xmlnode, NULL, "source", NULL);
            xmlSetProp(srcnode, "mount", source->mount);
211

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

void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template)
{
235
    xmlChar *buff = NULL;
236 237 238 239 240 241 242 243
    int len = 0;
    ice_config_t *config;
    char *fullpath_xslt_template;
    int fullpath_xslt_template_len;
    char *adminwebroot;

    client->respcode = 200;
    if (response == RAW) {
244
        xmlDocDumpMemory(doc, &buff, &len);
245 246 247 248
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Length: %d\r\n"
               "Content-Type: text/xml\r\n"
               "\r\n", len);
249
        html_write(client, "%s", buff);
250 251 252 253 254 255 256 257 258
    }
    if (response == TRANSFORMED) {
        config = config_get_config();
        adminwebroot = config->adminroot_dir;
        fullpath_xslt_template_len = strlen(adminwebroot) + 
            strlen(xslt_template) + 2;
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
            adminwebroot, PATH_SEPARATOR, xslt_template);
259
        config_release_config();
260 261 262 263 264 265 266 267 268 269 270
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Type: text/html\r\n"
               "\r\n");
        DEBUG1("Sending XSLT (%s)", fullpath_xslt_template);
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
    if (buff) {
        xmlFree(buff);
    }
}
271 272 273 274
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;
275
    int noauth = 0;
276

277 278 279
    DEBUG1("Admin request (%s)", uri);
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
280 281 282 283 284
        ERROR0("Internal error: admin request isn't");
        client_send_401(client);
        return;
    }

285 286 287 288 289 290
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
291

292
    DEBUG1("Got command (%s)", command_string);
293 294 295 296 297 298 299 300 301
    command = admin_get_command(command_string);

    if(command < 0) {
        ERROR1("Error parsing command string or unrecognised command: %s",
                command_string);
        client_send_400(client, "Unrecognised command");
        return;
    }

302 303
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

304 305 306
        ice_config_t *config = config_get_config ();
        httpp_set_query_param (client->parser, "mount", config->shoutcast_mount);
        config_release_config ();
307 308 309
        noauth = 1;
    }

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

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

315 316 317
        if (command == COMMAND_BUILDM3U) {
            noauth = 1;
        }
318
        /* This is a mount request, handle it as such */
319 320 321 322 323 324 325 326
        if (!noauth) {
            if(!connection_check_admin_pass(client->parser)) {
                if(!connection_check_source_pass(client->parser, mount)) {
                    INFO1("Bad or missing password on mount modification admin "
                          "request (command: %s)", command_string);
                    client_send_401(client);
                    return;
                }
327
            }
328 329 330
        }
        
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
331
        source = source_find_mount_raw(mount);
332

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

360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
        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;
            }
378 379 380 381 382 383 384 385 386 387
        }
        
        admin_handle_general_request(client, command);
    }
}

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

#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);
482 483
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
484

485
static void html_success(client_t *client, char *message)
486 487 488 489 490 491 492 493 494 495 496 497
{
    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);
}

498 499 500 501 502 503 504 505 506 507 508
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;
}

509 510
static void command_move_clients(client_t *client, source_t *source,
    int response)
511 512 513
{
    char *dest_source;
    source_t *dest;
514 515 516 517 518 519
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
520
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
521 522 523 524 525 526 527 528 529 530 531
        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;
    }
532

533 534 535 536 537 538 539 540
    dest = source_find_mount (dest_source);

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

Karl Heyes's avatar
Karl Heyes committed
541
    if (strcmp (dest->mount, source->mount) == 0)
542 543 544 545 546
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

547 548 549
    if (dest->running == 0)
    {
        client_send_400 (client, "Destination not running");
550 551 552
        return;
    }

553 554 555 556
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

557
    source_move_clients (source, dest);
558

559
    memset(buf, '\000', sizeof(buf));
560 561
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
562 563 564 565 566 567 568
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
569 570
}

571 572
static void command_show_listeners(client_t *client, source_t *source,
    int response)
573
{
574 575
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
576 577
    avl_node *client_node;
    client_t *current;
578 579
    char buf[22];
    char *userAgent = NULL;
580 581
    time_t now = time(NULL);

582 583 584 585 586
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
587

588
    memset(buf, '\000', sizeof(buf));
589
    snprintf(buf, sizeof(buf)-1, "%ld", source->listeners);
590
    xmlNewChild(srcnode, NULL, "Listeners", buf);
591 592 593 594 595 596

    avl_tree_rlock(source->client_tree);

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

    avl_tree_unlock(source->client_tree);
619 620 621
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
622 623 624
    client_destroy(client);
}

625 626 627 628 629 630 631 632 633 634 635 636 637
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();
638
    host = strdup(config->hostname);
639
    port = config->port;
640
    config_release_config();
641 642 643 644 645 646 647 648 649 650 651 652 653

    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
    );
654 655

    free(host);
656 657
    client_destroy(client);
}
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 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718
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);
}

719 720
static void command_kill_source(client_t *client, source_t *source,
    int response)
721
{
722 723 724 725 726 727 728 729 730
    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);

731 732
    source->running = 0;

733 734 735 736
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
737 738
}

739 740
static void command_kill_client(client_t *client, source_t *source,
    int response)
741 742 743 744
{
    char *idtext;
    int id;
    client_t *listener;
745 746 747
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
748 749 750 751 752 753 754

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

755 756 757 758 759
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

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

784 785
static void command_fallback(client_t *client, source_t *source,
    int response)
786 787 788 789 790 791 792 793 794 795 796 797
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

798
    html_success(client, "Fallback configured");
799 800 801 802 803 804
}

static void command_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
805
    mp3_state *state;
806 807 808 809 810 811

    DEBUG0("Got metadata update request");

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

812 813
    if ((source->format->type != FORMAT_TYPE_MP3) &&
        (source->format->type != FORMAT_TYPE_NSV))
Karl Heyes's avatar
Karl Heyes committed
814 815
    {
        client_send_400 (client, "Not mp3, cannot update metadata");
816 817 818
        return;
    }

Karl Heyes's avatar
Karl Heyes committed
819 820 821
    if (strcmp (action, "updinfo") != 0)
    {
        client_send_400 (client, "No such action");
822 823
        return;
    }
824 825 826

    state = source->format->_state;

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

829 830
    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
831
    stats_event(source->mount, "title", value);
Karl Heyes's avatar
Karl Heyes committed
832

833 834 835
    /* At this point, we assume that the metadata passed in
       is encoded in UTF-8 */
    logging_playlist(source->mount, value, source->listeners);
836 837
    /* If we get an update on the mountpoint, force a
       yp touch */
838
    yp_touch (source->mount);
839

840
    html_success(client, "Metadata update successful");
841 842
}

843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
static void command_shoutcast_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
    char *source_pass;
    char *config_source_pass;
    ice_config_t *config;
    mp3_state *state;

    DEBUG0("Got shoutcast metadata update request");

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

    config = config_get_config();
    config_source_pass = strdup(config->source_password);
    config_release_config();

    if ((source->format->type != FORMAT_TYPE_MP3) &&
        (source->format->type != FORMAT_TYPE_NSV))
    {
        client_send_400 (client, "Not mp3 or NSV, cannot update metadata");
        return;
    }

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

    if (strcmp(source_pass, config_source_pass) != 0)
    {
        ERROR0("Invalid source password specified, metadata not updated");
        client_send_400 (client, "Invalid source password");
        return;
    }

    if (config_source_pass) {
        free(config_source_pass);
    }

    state = source->format->_state;

    mp3_set_tag (source->format, "title", value);

    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
    stats_event(source->mount, "title", value);

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

    html_success(client, "Metadata update successful");
}

901 902 903
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

904 905
    DEBUG0("Stats request, sending xml stats");

906 907 908
    stats_get_xml(&doc);
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
909 910 911 912
    client_destroy(client);
    return;
}

Karl Heyes's avatar
Karl Heyes committed
913 914
static void command_list_mounts(client_t *client, int response)
{
915 916
    avl_node *node;
    source_t *source;
917 918 919

    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
920
    avl_tree_rlock (global.source_tree);
921 922
    if (response == PLAINTEXT)
    {
Karl Heyes's avatar
Karl Heyes committed
923 924 925 926 927
        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");

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

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
955
    client_destroy(client);
956

957 958 959
    return;
}