admin.c 35.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 "compat.h"
34
#include "xslt.h"
35
#include "fserve.h"
36
#include "admin.h"
37 38 39 40

#include "format.h"

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

#define CATMODULE "admin"

48 49 50
#define COMMAND_ERROR             (-1)

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

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

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

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

81 82 83
/* Admin commands requiring no auth */
#define COMMAND_BUILDM3U                    501

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

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

173
static void command_fallback(client_t *client, source_t *source, int response);
174
static void command_metadata(client_t *client, source_t *source, int response);
175
static void command_shoutcast_metadata(client_t *client, source_t *source);
176 177 178 179
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);
180
static void command_stats(client_t *client, const char *mount, int response);
181 182 183
static void command_list_mounts(client_t *client, int response);
static void command_kill_client(client_t *client, source_t *source,
        int response);
184 185
static void command_manageauth(client_t *client, source_t *source,
        int response);
186
static void command_buildm3u(client_t *client, const char *mount);
187 188
static void command_kill_source(client_t *client, source_t *source,
        int response);
189 190
static void command_updatemetadata(client_t *client, source_t *source,
        int response);
191 192 193
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);
194

195 196 197 198
/* 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)
199 200 201 202 203 204 205 206
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

207 208
    doc = xmlNewDoc (XMLSTR("1.0"));
    xmlnode = xmlNewDocNode (doc, NULL, XMLSTR("icestats"), NULL);
209
    xmlDocSetRootElement(doc, xmlnode);
210

211
    if (mount) {
212
        xmlNewChild (xmlnode, NULL, XMLSTR("current_source"), XMLSTR(mount));
213 214 215 216 217
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
218 219 220 221 222 223
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

224
        if (source->running || source->on_demand)
225
        {
226 227 228
            ice_config_t *config;
            mount_proxy *mountinfo;

229 230
            srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
            xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
231

232
            xmlNewChild(srcnode, NULL, XMLSTR("fallback"), 
233
                    (source->fallback_mount != NULL)?
234
                    XMLSTR(source->fallback_mount):XMLSTR(""));
235
            snprintf (buf, sizeof(buf), "%lu", source->listeners);
236
            xmlNewChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
237

Karl Heyes's avatar
Karl Heyes committed
238
            config = config_get_config();
239 240 241
            mountinfo = config_find_mount (config, source->mount);
            if (mountinfo && mountinfo->auth)
            {
242 243
                xmlNewChild(srcnode, NULL, XMLSTR("authenticator"),
                        XMLSTR(mountinfo->auth->type));
244 245 246
            }
            config_release_config();

247 248
            if (source->running)
            {
249
                if (source->client) 
Karl Heyes's avatar
Karl Heyes committed
250 251 252
                {
                    snprintf (buf, sizeof(buf), "%lu",
                            (unsigned long)(now - source->con->con_time));
253
                    xmlNewChild (srcnode, NULL, XMLSTR("Connected"), XMLSTR(buf));
Karl Heyes's avatar
Karl Heyes committed
254
                }
255 256
                xmlNewChild (srcnode, NULL, XMLSTR("content-type"), 
                        XMLSTR(source->format->contenttype));
257
            }
258
        }
259 260 261 262 263
        node = avl_get_next(node);
    }
    return(doc);
}

264 265
void admin_send_response (xmlDocPtr doc, client_t *client,
        int response, const char *xslt_template)
266
{
267 268 269 270 271 272
    if (response == RAW)
    {
        xmlChar *buff = NULL;
        int len = 0;
        unsigned int buf_len;
        const char *http = "HTTP/1.0 200 OK\r\n"
273
               "Content-Type: text/xml\r\n"
274 275 276
               "Content-Length: ";
        xmlDocDumpMemory(doc, &buff, &len);
        buf_len = strlen (http) + len + 20;
Karl Heyes's avatar
Karl Heyes committed
277
        client_set_queue (client, NULL);
278
        client->refbuf = refbuf_new (buf_len);
279 280
        len = snprintf (client->refbuf->data, buf_len, "%s%d\r\n\r\n%s", http, len, buff);
        client->refbuf->len = len;
281 282 283
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
284
    }
285 286 287 288 289 290 291 292
    if (response == TRANSFORMED)
    {
        char *fullpath_xslt_template;
        int fullpath_xslt_template_len;
        ice_config_t *config = config_get_config();

        fullpath_xslt_template_len = strlen (config->adminroot_dir) + 
            strlen (xslt_template) + 2;
293 294
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
295
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
296
        config_release_config();
297

298 299 300 301 302
        DEBUG1("Sending XSLT (%s)", fullpath_xslt_template);
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
303 304


305
void admin_handle_request(client_t *client, const char *uri)
306
{
307
    const char *mount, *command_string;
308 309
    int command;

310 311 312
    DEBUG1("Admin request (%s)", uri);
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
313 314 315 316 317
        ERROR0("Internal error: admin request isn't");
        client_send_401(client);
        return;
    }

318 319 320 321 322 323
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
324

325
    DEBUG1("Got command (%s)", command_string);
326 327 328 329 330 331 332 333 334
    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;
    }

335 336
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

337
        ice_config_t *config;
338
        const char *sc_mount;
339
        const char *pass = httpp_get_query_param (client->parser, "pass");
340 341
        listener_t *listener;

342 343 344 345 346
        if (pass == NULL)
        {
            client_send_400 (client, "missing pass parameter");
            return;
        }
347
        global_lock();
348
        config = config_get_config ();
349 350
        sc_mount = config->shoutcast_mount;
        listener = config_get_listen_sock (config, client->con);
351

352 353 354 355
        if (listener && listener->shoutcast_mount)
            sc_mount = listener->shoutcast_mount;

        httpp_set_query_param (client->parser, "mount", sc_mount);
356 357
        httpp_setvar (client->parser, HTTPP_VAR_PROTOCOL, "ICY");
        httpp_setvar (client->parser, HTTPP_VAR_ICYPASSWORD, pass);
358
        config_release_config ();
359
        global_unlock();
360 361
    }

362 363
    mount = httpp_get_query_param(client->parser, "mount");

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

367 368 369 370 371
        /* this request does not require auth but can apply to files on webroot */
        if (command == COMMAND_BUILDM3U)
        {
            command_buildm3u (client, mount);
            return;
372
        }
373
        /* This is a mount request, handle it as such */
374
        if (client->authenticated == 0 && !connection_check_admin_pass(client->parser))
375
        {
376
            switch (client_check_source_auth (client, mount))
377
            {
378 379 380 381 382 383 384 385 386
                case 0:
                    break;
                default:
                    INFO1("Bad or missing password on mount modification admin "
                            "request (command: %s)", command_string);
                    client_send_401(client);
                    /* fall through */
                case 1:
                    return;
387
            }
388
        }
389

390
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
391
        source = source_find_mount_raw(mount);
392

393 394
        if (source == NULL)
        {
395 396
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
397
            avl_tree_unlock(global.source_tree);
398 399
            client_send_400(client, "Source does not exist");
        }
400 401
        else
        {
402
            if (source->running == 0 && source->on_demand == 0)
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
            {
                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;
418
            }
419 420 421
            INFO2("Received admin command %s on mount \"%s\"", 
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
422
            avl_tree_unlock(global.source_tree);
423
        }
424 425 426
    }
    else {

427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
        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;
            }
445 446 447 448 449 450 451 452 453 454
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
455
            command_stats(client, NULL, RAW);
456
            break;
457 458
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
459 460
            break;
        case COMMAND_RAW_LISTSTREAM:
461 462
            command_list_mounts(client, RAW);
            break;
463 464 465
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
466
        case COMMAND_TRANSFORMED_STATS:
467
            command_stats(client, NULL, TRANSFORMED);
468 469 470 471 472 473 474 475 476
            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);
477
            break;
478 479 480 481 482 483 484 485 486 487 488
        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) {
489 490 491
        case COMMAND_RAW_STATS:
            command_stats(client, source->mount, RAW);
            break;
492 493
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
494
            break;
495 496 497 498 499
        case COMMAND_RAW_METADATA_UPDATE:
            command_metadata(client, source, RAW);
            break;
        case COMMAND_TRANSFORMED_METADATA_UPDATE:
            command_metadata(client, source, TRANSFORMED);
500
            break;
501 502 503
        case COMMAND_SHOUTCAST_METADATA_UPDATE:
            command_shoutcast_metadata(client, source);
            break;
504 505 506 507 508 509 510 511
        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);
512
            break;
513 514
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
515
            break;
516 517 518
        case COMMAND_TRANSFORMED_STATS:
            command_stats(client, source->mount, TRANSFORMED);
            break;
519 520
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
521
            break;
522 523 524 525 526 527 528 529 530 531 532
        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);
533
            break;
534 535 536 537 538 539
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
540 541 542 543 544 545
        case COMMAND_TRANSFORMED_UPDATEMETADATA:
            command_updatemetadata(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_UPDATEMETADATA:
            command_updatemetadata(client, source, RAW);
            break;
546 547 548
        default:
            WARN0("Mount request not recognised");
            client_send_400(client, "Mount request unknown");
549
            break;
550 551 552 553 554 555 556 557 558 559 560
    }
}

#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);
561 562
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
563

564
static void html_success(client_t *client, char *message)
565 566
{
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
567
    snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
568 569 570
            "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);
Karl Heyes's avatar
Karl Heyes committed
571 572
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
573 574
}

575

576 577
static void command_move_clients(client_t *client, source_t *source,
    int response)
578
{
579
    const char *dest_source;
580
    source_t *dest;
581 582 583 584 585 586
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
587
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
588 589 590 591 592 593 594 595 596 597
        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);
        return;
    }
598

599 600 601 602 603 604 605 606
    dest = source_find_mount (dest_source);

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

Karl Heyes's avatar
Karl Heyes committed
607
    if (strcmp (dest->mount, source->mount) == 0)
608 609 610 611 612
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

613
    if (dest->running == 0 && dest->on_demand == 0)
614 615
    {
        client_send_400 (client, "Destination not running");
616 617 618
        return;
    }

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

621 622
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
623 624
    xmlDocSetRootElement(doc, node);

625
    source_move_clients (source, dest);
626

627
    memset(buf, '\000', sizeof(buf));
628 629
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
630 631
    xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
    xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
632 633 634 635

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
636 637
}

638 639
static void command_show_listeners(client_t *client, source_t *source,
    int response)
640
{
641 642
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
643 644
    avl_node *client_node;
    client_t *current;
645
    char buf[22];
646
    const char *userAgent = NULL;
647 648
    time_t now = time(NULL);

649 650 651 652
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
    srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
    xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
653
    xmlDocSetRootElement(doc, node);
654

655
    memset(buf, '\000', sizeof(buf));
656
    snprintf (buf, sizeof(buf), "%lu", source->listeners);
657
    xmlNewChild(srcnode, NULL, XMLSTR("Listeners"), XMLSTR(buf));
658 659 660 661 662 663

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
664 665
        listenernode = xmlNewChild(srcnode, NULL, XMLSTR("listener"), NULL);
        xmlNewChild(listenernode, NULL, XMLSTR("IP"), XMLSTR(current->con->ip));
666 667
        userAgent = httpp_getvar(current->parser, "user-agent");
        if (userAgent) {
668
            xmlNewChild(listenernode, NULL, XMLSTR("UserAgent"), XMLSTR(userAgent));
669 670
        }
        else {
671
            xmlNewChild(listenernode, NULL, XMLSTR("UserAgent"), XMLSTR("Unknown"));
672 673
        }
        memset(buf, '\000', sizeof(buf));
Karl Heyes's avatar
Karl Heyes committed
674
        snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - current->con->con_time));
675
        xmlNewChild(listenernode, NULL, XMLSTR("Connected"), XMLSTR(buf));
676
        memset(buf, '\000', sizeof(buf));
677
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
678
        xmlNewChild(listenernode, NULL, XMLSTR("ID"), XMLSTR(buf));
679
        if (current->username) {
680
            xmlNewChild(listenernode, NULL, XMLSTR("username"), XMLSTR(current->username));
681
        }
682 683 684 685
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
686 687 688
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
689 690
}

691
static void command_buildm3u(client_t *client,  const char *mount)
692
{
693 694
    const char *username = NULL;
    const char *password = NULL;
695 696 697 698 699 700
    ice_config_t *config;

    COMMAND_REQUIRE(client, "username", username);
    COMMAND_REQUIRE(client, "password", password);

    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
701
    config = config_get_config();
702
    snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
703 704 705 706 707 708
        "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,
Karl Heyes's avatar
Karl Heyes committed
709 710
        config->hostname,
        config->port,
711
        mount
712
    );
Karl Heyes's avatar
Karl Heyes committed
713
    config_release_config();
714

Karl Heyes's avatar
Karl Heyes committed
715 716
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
717
}
718 719


720 721 722 723 724
static void command_manageauth(client_t *client, source_t *source,
    int response)
{
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, msgnode;
725 726
    const char *action = NULL;
    const char *username = NULL;
727 728
    char *message = NULL;
    int ret = AUTH_OK;
729 730
    ice_config_t *config = config_get_config ();
    mount_proxy *mountinfo = config_find_mount (config, source->mount);
731

732 733
    do
    {
734 735 736
        if (mountinfo == NULL || mountinfo->auth == NULL)
        {
            WARN1 ("manage auth request for %s but no facility available", source->mount);
737
            break;
738
        }
739 740 741 742
        COMMAND_OPTIONAL(client, "action", action);
        COMMAND_OPTIONAL (client, "username", username);

        if (action == NULL)
743
            action = "list";
744 745 746 747 748 749 750 751 752 753 754

        if (!strcmp(action, "add"))
        {
            const char *password = NULL;
            COMMAND_OPTIONAL (client, "password", password);

            if (username == NULL || password == NULL)
            {
                WARN1 ("manage auth request add for %s but no user/pass", source->mount);
                break;
            }
755
            ret = mountinfo->auth->adduser(mountinfo->auth, username, password);
756 757 758 759 760 761 762 763 764 765
            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");
            }
        }
766 767 768 769 770 771 772
        if (!strcmp(action, "delete"))
        {
            if (username == NULL)
            {
                WARN1 ("manage auth request delete for %s but no username", source->mount);
                break;
            }
773
            ret = mountinfo->auth->deleteuser(mountinfo->auth, username);
774 775 776 777 778 779 780 781
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
            }
            if (ret == AUTH_USERDELETED) {
                message = strdup("User deleted");
            }
        }

782 783 784 785
        doc = xmlNewDoc (XMLSTR("1.0"));
        node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
        srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
        xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
786

787
        if (message) {
788 789
            msgnode = xmlNewChild(node, NULL, XMLSTR("iceresponse"), NULL);
            xmlNewChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
790
        }
791

792
        xmlDocSetRootElement(doc, node);
793

794 795
        if (mountinfo && mountinfo->auth && mountinfo->auth->listuser)
            mountinfo->auth->listuser (mountinfo->auth, srcnode);
796

797
        config_release_config ();
798

799 800 801 802 803 804 805 806 807
        admin_send_response(doc, client, response, 
                MANAGEAUTH_TRANSFORMED_REQUEST);
        free (message);
        xmlFreeDoc(doc);
        return;
    } while (0);

    config_release_config ();
    client_send_400 (client, "missing parameter");
808 809
}

810 811
static void command_kill_source(client_t *client, source_t *source,
    int response)
812
{
813 814 815
    xmlDocPtr doc;
    xmlNodePtr node;

816 817 818 819
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
    xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR("Source Removed"));
    xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
820 821
    xmlDocSetRootElement(doc, node);

822 823
    source->running = 0;

824 825 826
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
827 828
}

829 830
static void command_kill_client(client_t *client, source_t *source,
    int response)
831
{
832
    const char *idtext;
833 834
    int id;
    client_t *listener;
835 836 837
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
838 839 840 841 842 843 844

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

845 846
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
847 848 849
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

850 851 852 853 854 855 856
    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;
857 858
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
859 860
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
861 862
    }
    else {
863 864
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d not found", id);
865 866
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
867
    }
868 869 870
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
871 872
}

873 874
static void command_fallback(client_t *client, source_t *source,
    int response)
875
{
876
    const char *fallback;
877 878 879 880 881 882 883 884 885 886
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

887
    html_success(client, "Fallback configured");
888 889
}

890 891
static void command_metadata(client_t *client, source_t *source,
    int response)
892
{
893
    const char *action;
894
    const char *song, *title, *artist, *charset;
895
    format_plugin_t *plugin;
896 897
    xmlDocPtr doc;
    xmlNodePtr node;
898
    int same_ip = 1;
899

900 901
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode (doc, NULL, XMLSTR("iceresponse"), NULL);
902
    xmlDocSetRootElement(doc, node);
903 904 905 906

    DEBUG0("Got metadata update request");

    COMMAND_REQUIRE(client, "mode", action);
907 908 909
    COMMAND_OPTIONAL(client, "song", song);
    COMMAND_OPTIONAL(client, "title", title);
    COMMAND_OPTIONAL(client, "artist", artist);
910
    COMMAND_OPTIONAL(client, "charset", charset);
911

Karl Heyes's avatar
Karl Heyes committed
912 913
    if (strcmp (action, "updinfo") != 0)
    {
914 915
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR("No such action"));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
916 917 918
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
919 920
        return;
    }
921

922
    plugin = source->format;
923 924 925
    if (source->client && strcmp (client->con->ip, source->client->con->ip) != 0)
        if (response == RAW && connection_check_admin_pass (client->parser) == 0)
            same_ip = 0;
Karl Heyes's avatar
Karl Heyes committed
926

927
    if (same_ip && plugin && plugin->set_tag)
928 929 930
    {
        if (song)
        {
931
            plugin->set_tag (plugin, "song", song, charset);
932
            INFO2 ("Metadata on mountpoint %s changed to \"%s\"", source->mount, song);
933 934 935 936 937
        }
        else
        {
            if (artist && title)
            {
938 939
                plugin->set_tag (plugin, "title", title, charset);
                plugin->set_tag (plugin, "artist", artist, charset);
940 941 942 943
                INFO3("Metadata on mountpoint %s changed to \"%s - %s\"",
                        source->mount, artist, title);
            }
        }
944 945
        /* updates are now done, let them be pushed into the stream */
        plugin->set_tag (plugin, NULL, NULL, NULL);
946 947 948
    }
    else
    {
949 950 951
        xmlNewChild(node, NULL, XMLSTR("message"), 
            XMLSTR("Mountpoint will not accept URL updates"));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
952 953 954 955
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
        return;
956
    }
957

958 959
    xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR("Metadata update successful"));
    xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
960 961 962
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
963 964
}

965 966
static void command_shoutcast_metadata(client_t *client, source_t *source)
{
967 968
    const char *action;
    const char *value;
969
    int same_ip = 1;
970 971 972 973 974 975 976 977 978 979 980

    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;
    }
981 982 983
    if (source->client && strcmp (client->con->ip, source->client->con->ip) != 0)
        if (connection_check_admin_pass (client->parser) == 0)
            same_ip = 0;
984

985
    if (same_ip && source->format && source->format->set_tag)
986
    {
987
        source->format->set_tag (source->format, "title", value, NULL);
988
        source->format->set_tag (source->format, NULL, NULL, NULL);
989

990 991 992 993 994 995 996 997
        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");
    }
998 999
}

1000
static void command_stats(client_t *client, const char *mount, int response) {
1001 1002
    xmlDocPtr doc;

1003 1004
    DEBUG0("Stats request, sending xml stats");

1005
    doc = stats_get_xml(1, mount);
1006 1007
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
1008 1009 1010
    return;
}

Karl Heyes's avatar
Karl Heyes committed
1011 1012
static void command_list_mounts(client_t *client, int response)
{
1013 1014
    DEBUG0("List mounts request");

1015 1016
    if (response == PLAINTEXT)
    {
1017
        snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
1018
                "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");
1019
        client->refbuf->len = strlen (client->refbuf->data);
1020 1021 1022
        client->respcode = 200;

        client->refbuf->next = stats_get_streams ();
1023
        fserve_add_client (client, NULL);
1024
    }
Karl Heyes's avatar
Karl Heyes committed
1025 1026
    else
    {
1027
        xmlDocPtr doc;
1028
        avl_tree_rlock (global.source_tree);
1029
        doc = admin_build_sourcelist(NULL);
Karl Heyes's avatar
Karl Heyes committed
1030
        avl_tree_unlock (global.source_tree);
1031 1032 1033 1034 1035

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
1036 1037
}

1038 1039 1040 1041 1042 1043
static void command_updatemetadata(client_t *client, source_t *source,
    int response)
{
    xmlDocPtr doc;
    xmlNodePtr node, srcnode;

1044 1045 1046 1047
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode (doc, NULL, XMLSTR("icestats"), NULL);
    srcnode = xmlNewChild (node, NULL, XMLSTR("source"), NULL);
    xmlSetProp (srcnode, XMLSTR("mount"), XMLSTR(source->mount));
1048 1049 1050 1051 1052 1053
    xmlDocSetRootElement(doc, node);

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