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

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

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

25
#include "cfgfile.h"
26 27 28 29 30 31 32
#include "connection.h"
#include "refbuf.h"
#include "client.h"
#include "source.h"
#include "global.h"
#include "event.h"
#include "stats.h"
33
#include "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
#define COMMAND_RAW_LIST_MOUNTS             101
#define COMMAND_RAW_STATS                   102
#define COMMAND_RAW_LISTSTREAM              103
#define COMMAND_PLAINTEXT_LISTSTREAM        104
71
#define COMMAND_RAW_QUEUE_RELOAD            105
Karl Heyes's avatar
Karl Heyes committed
72 73 74
#define COMMAND_TRANSFORMED_LIST_MOUNTS     201
#define COMMAND_TRANSFORMED_STATS           202
#define COMMAND_TRANSFORMED_LISTSTREAM      203
75
#define COMMAND_TRANSFORMED_QUEUE_RELOAD    205
76

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

83 84 85
/* Admin commands requiring no auth */
#define COMMAND_BUILDM3U                    501

86 87
#define FALLBACK_RAW_REQUEST "fallbacks"
#define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl"
88
#define SHOUTCAST_METADATA_REQUEST "admin.cgi"
89 90
#define METADATA_RAW_REQUEST "metadata"
#define METADATA_TRANSFORMED_REQUEST "metadata.xsl"
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"
95 96
#define QUEUE_RELOAD_RAW_REQUEST "reloadconfig"
#define QUEUE_RELOAD_TRANSFORMED_REQUEST "reloadconfig.xsl"
97 98 99 100
#define LISTMOUNTS_RAW_REQUEST "listmounts"
#define LISTMOUNTS_TRANSFORMED_REQUEST "listmounts.xsl"
#define STREAMLIST_RAW_REQUEST "streamlist"
#define STREAMLIST_TRANSFORMED_REQUEST "streamlist.xsl"
101
#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
102 103 104 105 106 107 108
#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"
109 110
#define MANAGEAUTH_RAW_REQUEST "manageauth"
#define MANAGEAUTH_TRANSFORMED_REQUEST "manageauth.xsl"
111 112
#define UPDATEMETADATA_RAW_REQUEST "updatemetadata"
#define UPDATEMETADATA_TRANSFORMED_REQUEST "updatemetadata.xsl"
113 114
#define DEFAULT_RAW_REQUEST ""
#define DEFAULT_TRANSFORMED_REQUEST ""
115
#define BUILDM3U_RAW_REQUEST "buildm3u"
116

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

181
static void command_fallback(client_t *client, source_t *source, int response);
182
static void command_metadata(client_t *client, source_t *source, int response);
183
static void command_shoutcast_metadata(client_t *client, source_t *source);
184 185 186 187
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);
188
static void command_stats(client_t *client, const char *mount, int response);
189
static void command_queue_reload(client_t *client, int response);
190 191 192
static void command_list_mounts(client_t *client, int response);
static void command_kill_client(client_t *client, source_t *source,
        int response);
193 194
static void command_manageauth(client_t *client, source_t *source,
        int response);
195
static void command_buildm3u(client_t *client, const char *mount);
196 197
static void command_kill_source(client_t *client, source_t *source,
        int response);
198 199
static void command_updatemetadata(client_t *client, source_t *source,
        int response);
200 201 202
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);
203

204 205 206 207
/* 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)
208 209 210 211 212 213 214 215
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

216 217
    doc = xmlNewDoc (XMLSTR("1.0"));
    xmlnode = xmlNewDocNode (doc, NULL, XMLSTR("icestats"), NULL);
218
    xmlDocSetRootElement(doc, xmlnode);
219

220
    if (mount) {
221
        xmlNewChild (xmlnode, NULL, XMLSTR("current_source"), XMLSTR(mount));
222 223 224 225 226
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
227 228 229 230 231 232
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

233
        if (source->running || source->on_demand)
234
        {
235 236 237
            ice_config_t *config;
            mount_proxy *mountinfo;

238 239
            srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
            xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
240

241
            xmlNewChild(srcnode, NULL, XMLSTR("fallback"), 
242
                    (source->fallback_mount != NULL)?
243
                    XMLSTR(source->fallback_mount):XMLSTR(""));
244
            snprintf (buf, sizeof(buf), "%lu", source->listeners);
245
            xmlNewChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
246

Karl Heyes's avatar
Karl Heyes committed
247
            config = config_get_config();
248
            mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
249 250
            if (mountinfo && mountinfo->auth)
            {
251 252
                xmlNewChild(srcnode, NULL, XMLSTR("authenticator"),
                        XMLSTR(mountinfo->auth->type));
253 254 255
            }
            config_release_config();

256 257
            if (source->running)
            {
258
                if (source->client) 
Karl Heyes's avatar
Karl Heyes committed
259 260 261
                {
                    snprintf (buf, sizeof(buf), "%lu",
                            (unsigned long)(now - source->con->con_time));
262
                    xmlNewChild (srcnode, NULL, XMLSTR("Connected"), XMLSTR(buf));
Karl Heyes's avatar
Karl Heyes committed
263
                }
264 265
                xmlNewChild (srcnode, NULL, XMLSTR("content-type"), 
                        XMLSTR(source->format->contenttype));
266
            }
267
        }
268 269 270 271 272
        node = avl_get_next(node);
    }
    return(doc);
}

273 274
void admin_send_response (xmlDocPtr doc, client_t *client,
        int response, const char *xslt_template)
275
{
276 277 278 279
    if (response == RAW)
    {
        xmlChar *buff = NULL;
        int len = 0;
280 281 282
        size_t buf_len;
        ssize_t ret;

283
        xmlDocDumpMemory(doc, &buff, &len);
284 285 286 287

        buf_len = len + 1024;
        if (buf_len < 4096)
            buf_len = 4096;
288

Karl Heyes's avatar
Karl Heyes committed
289
        client_set_queue (client, NULL);
290
        client->refbuf = refbuf_new (buf_len);
291

292
	ret = util_http_build_header(client->refbuf->data, buf_len, 0,
293
	                             0, 200, NULL,
294
				     "text/xml", "utf-8",
295
				     NULL, NULL);
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
        if (ret == -1) {
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
            client_send_500(client, "Header generation failed.");
            xmlFree(buff);
            return;
        } else if (buf_len < (len + ret + 64)) {
            void *new_data;
            buf_len = ret + len + 64;
            new_data = realloc(client->refbuf->data, buf_len);
            if (new_data) {
                ICECAST_LOG_DEBUG("Client buffer reallocation succeeded.");
                client->refbuf->data = new_data;
                client->refbuf->len = buf_len;
                ret = util_http_build_header(client->refbuf->data, buf_len, 0,
                                             0, 200, NULL,
                                             "text/xml", "utf-8",
                                             NULL, NULL);
                if (ret == -1) {
                    ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
                    client_send_500(client, "Header generation failed.");
                    xmlFree(buff);
                    return;
                }
            } else {
                ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
                client_send_500(client, "Buffer reallocation failed.");
                xmlFree(buff);
                return;
            } 
        }
326

327 328 329 330
        /* FIXME: in this section we hope no function will ever return -1 */
	ret += snprintf (client->refbuf->data + ret, buf_len - ret, "Content-Length: %d\r\n\r\n%s", xmlStrlen(buff), buff);

        client->refbuf->len = ret;
331 332 333
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
334
    }
335 336 337 338 339 340 341 342
    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;
343 344
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
345
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
346
        config_release_config();
347

348
        ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template);
349 350 351 352
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
353 354


355
void admin_handle_request(client_t *client, const char *uri)
356
{
357
    const char *mount, *command_string;
358 359
    int command;

360
    ICECAST_LOG_DEBUG("Admin request (%s)", uri);
361 362
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
363
        ICECAST_LOG_ERROR("Internal error: admin request isn't");
364 365 366 367
        client_send_401(client);
        return;
    }

368 369 370 371 372 373
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
374

375
    ICECAST_LOG_DEBUG("Got command (%s)", command_string);
376 377 378
    command = admin_get_command(command_string);

    if(command < 0) {
379
        ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %s",
380 381 382 383 384
                command_string);
        client_send_400(client, "Unrecognised command");
        return;
    }

385 386
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

387
        ice_config_t *config;
388
        const char *sc_mount;
389
        const char *pass = httpp_get_query_param (client->parser, "pass");
390 391
        listener_t *listener;

392 393 394 395 396
        if (pass == NULL)
        {
            client_send_400 (client, "missing pass parameter");
            return;
        }
397
        global_lock();
398
        config = config_get_config ();
399 400
        sc_mount = config->shoutcast_mount;
        listener = config_get_listen_sock (config, client->con);
401

402 403 404 405
        if (listener && listener->shoutcast_mount)
            sc_mount = listener->shoutcast_mount;

        httpp_set_query_param (client->parser, "mount", sc_mount);
406 407
        httpp_setvar (client->parser, HTTPP_VAR_PROTOCOL, "ICY");
        httpp_setvar (client->parser, HTTPP_VAR_ICYPASSWORD, pass);
408
        config_release_config ();
409
        global_unlock();
410 411
    }

412 413
    mount = httpp_get_query_param(client->parser, "mount");

414 415 416
    if(mount != NULL) {
        source_t *source;

417 418 419 420 421
        /* this request does not require auth but can apply to files on webroot */
        if (command == COMMAND_BUILDM3U)
        {
            command_buildm3u (client, mount);
            return;
422
        }
423
        /* This is a mount request, handle it as such */
424
        if (client->authenticated == 0 && !connection_check_admin_pass(client->parser))
425
        {
426
            switch (client_check_source_auth (client, mount))
427
            {
428 429 430
                case 0:
                    break;
                default:
431
                    ICECAST_LOG_INFO("Bad or missing password on mount modification admin "
432 433 434 435 436
                            "request (command: %s)", command_string);
                    client_send_401(client);
                    /* fall through */
                case 1:
                    return;
437
            }
438
        }
439

440
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
441
        source = source_find_mount_raw(mount);
442

443 444
        if (source == NULL)
        {
445
            ICECAST_LOG_WARN("Admin command %s on non-existent source %s", 
446
                    command_string, mount);
447
            avl_tree_unlock(global.source_tree);
448 449
            client_send_400(client, "Source does not exist");
        }
450 451
        else
        {
452
            if (source->running == 0 && source->on_demand == 0)
453 454
            {
                avl_tree_unlock (global.source_tree);
455
                ICECAST_LOG_INFO("Received admin command %s on unavailable mount \"%s\"",
456 457 458 459 460 461 462 463
                        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);
464
                ICECAST_LOG_ERROR("illegal change of metadata on non-shoutcast "
465 466 467
                        "compatible stream");
                client_send_400 (client, "illegal metadata call");
                return;
468
            }
469
            ICECAST_LOG_INFO("Received admin command %s on mount \"%s\"", 
470 471
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
472
            avl_tree_unlock(global.source_tree);
473
        }
474 475 476
    }
    else {

477 478 479 480 481
        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)) {
482
                ICECAST_LOG_INFO("Bad or missing password on admin command "
483 484 485 486 487 488 489
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
        }
        else {
            if(!connection_check_admin_pass(client->parser)) {
490
                ICECAST_LOG_INFO("Bad or missing password on admin command "
491 492 493 494
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
495 496 497 498 499 500 501 502 503 504
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
505
            command_stats(client, NULL, RAW);
506
            break;
507 508 509
        case COMMAND_RAW_QUEUE_RELOAD:
            command_queue_reload(client, RAW);
            break;
510 511
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
512 513
            break;
        case COMMAND_RAW_LISTSTREAM:
514 515
            command_list_mounts(client, RAW);
            break;
516 517 518
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
519
        case COMMAND_TRANSFORMED_STATS:
520
            command_stats(client, NULL, TRANSFORMED);
521
            break;
522 523 524
        case COMMAND_TRANSFORMED_QUEUE_RELOAD:
            command_queue_reload(client, TRANSFORMED);
            break;
525 526 527 528 529 530 531 532
        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);
533
            break;
534
        default:
535
            ICECAST_LOG_WARN("General admin request not recognised");
536 537 538 539 540 541 542 543 544
            client_send_400(client, "Unknown admin request");
            return;
    }
}

static void admin_handle_mount_request(client_t *client, source_t *source, 
        int command)
{
    switch(command) {
545 546 547
        case COMMAND_RAW_STATS:
            command_stats(client, source->mount, RAW);
            break;
548 549
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
550
            break;
551 552 553 554 555
        case COMMAND_RAW_METADATA_UPDATE:
            command_metadata(client, source, RAW);
            break;
        case COMMAND_TRANSFORMED_METADATA_UPDATE:
            command_metadata(client, source, TRANSFORMED);
556
            break;
557 558 559
        case COMMAND_SHOUTCAST_METADATA_UPDATE:
            command_shoutcast_metadata(client, source);
            break;
560 561 562 563 564 565 566 567
        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);
568
            break;
569 570
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
571
            break;
572 573 574
        case COMMAND_TRANSFORMED_STATS:
            command_stats(client, source->mount, TRANSFORMED);
            break;
575 576
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
577
            break;
578 579 580 581 582 583 584 585 586 587 588
        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);
589
            break;
590 591 592 593 594 595
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
596 597 598 599 600 601
        case COMMAND_TRANSFORMED_UPDATEMETADATA:
            command_updatemetadata(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_UPDATEMETADATA:
            command_updatemetadata(client, source, RAW);
            break;
602
        default:
603
            ICECAST_LOG_WARN("Mount request not recognised");
604
            client_send_400(client, "Mount request unknown");
605
            break;
606 607 608 609 610 611 612 613 614 615 616
    }
}

#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);
617 618
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
619

620
static void html_success(client_t *client, char *message)
621
{
622 623 624 625
    ssize_t ret;

    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE, 0,
                                 0, 200, NULL,
626
				 "text/html", "utf-8",
627
				 "", NULL);
628 629 630 631 632 633 634

    if (ret == -1 || ret >= PER_CLIENT_REFBUF_SIZE) {
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
        client_send_500(client, "Header generation failed.");
        return;
    }

635 636 637 638
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
             "<html><head><title>Admin request successful</title></head>"
	     "<body><p>%s</p></body></html>", message);

639
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
640 641
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
642 643
}

644

645 646
static void command_move_clients(client_t *client, source_t *source,
    int response)
647
{
648
    const char *dest_source;
649
    source_t *dest;
650 651 652 653 654
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

655
    ICECAST_LOG_DEBUG("Doing optional check");
656
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
657 658
        parameters_passed = 1;
    }
659
    ICECAST_LOG_DEBUG("Done optional check (%d)", parameters_passed);
660 661 662 663 664 665 666
    if (!parameters_passed) {
        doc = admin_build_sourcelist(source->mount);
        admin_send_response(doc, client, response, 
             MOVECLIENTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
        return;
    }
667

668 669 670 671 672 673 674 675
    dest = source_find_mount (dest_source);

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

Karl Heyes's avatar
Karl Heyes committed
676
    if (strcmp (dest->mount, source->mount) == 0)
677 678 679 680 681
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

682
    if (dest->running == 0 && dest->on_demand == 0)
683 684
    {
        client_send_400 (client, "Destination not running");
685 686 687
        return;
    }

688
    ICECAST_LOG_INFO("source is \"%s\", destination is \"%s\"", source->mount, dest->mount);
689

690 691
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
692 693
    xmlDocSetRootElement(doc, node);

694
    source_move_clients (source, dest);
695

696
    memset(buf, '\000', sizeof(buf));
697 698
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
699 700
    xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
    xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
701 702 703 704

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
705 706
}

707 708
static void command_show_listeners(client_t *client, source_t *source,
    int response)
709
{
710 711
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
712 713
    avl_node *client_node;
    client_t *current;
714
    char buf[22];
715
    const char *userAgent = NULL;
716 717
    time_t now = time(NULL);

718 719 720 721
    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));
722
    xmlDocSetRootElement(doc, node);
723

724
    memset(buf, '\000', sizeof(buf));
725
    snprintf (buf, sizeof(buf), "%lu", source->listeners);
726
    xmlNewChild(srcnode, NULL, XMLSTR("Listeners"), XMLSTR(buf));
727 728 729 730 731 732

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
733 734
        listenernode = xmlNewChild(srcnode, NULL, XMLSTR("listener"), NULL);
        xmlNewChild(listenernode, NULL, XMLSTR("IP"), XMLSTR(current->con->ip));
735 736
        userAgent = httpp_getvar(current->parser, "user-agent");
        if (userAgent) {
737
            xmlNewChild(listenernode, NULL, XMLSTR("UserAgent"), XMLSTR(userAgent));
738 739
        }
        else {
740
            xmlNewChild(listenernode, NULL, XMLSTR("UserAgent"), XMLSTR("Unknown"));
741 742
        }
        memset(buf, '\000', sizeof(buf));
Karl Heyes's avatar
Karl Heyes committed
743
        snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - current->con->con_time));
744
        xmlNewChild(listenernode, NULL, XMLSTR("Connected"), XMLSTR(buf));
745
        memset(buf, '\000', sizeof(buf));
746
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
747
        xmlNewChild(listenernode, NULL, XMLSTR("ID"), XMLSTR(buf));
748
        if (current->username) {
749
            xmlNewChild(listenernode, NULL, XMLSTR("username"), XMLSTR(current->username));
750
        }
751 752 753 754
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
755 756 757
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
758 759
}

760
static void command_buildm3u(client_t *client,  const char *mount)
761
{
762 763
    const char *username = NULL;
    const char *password = NULL;
764
    ice_config_t *config;
765
    ssize_t ret;
766 767 768 769

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

770 771 772
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE, 0,
                                 0, 200, NULL,
				 "audio/x-mpegurl", NULL,
773
				 NULL, NULL);
774

775 776 777 778 779 780 781
    if (ret == -1 || ret >= (PER_CLIENT_REFBUF_SIZE - 512)) { /* we want at least 512 Byte left for data */
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
        client_send_500(client, "Header generation failed.");
        return;
    }


Karl Heyes's avatar
Karl Heyes committed
782
    config = config_get_config();
783
    snprintf (client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
784 785 786 787
        "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
788 789
        config->hostname,
        config->port,
790
        mount
791
    );
Karl Heyes's avatar
Karl Heyes committed
792
    config_release_config();
793

794
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
795 796
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
797
}
798 799


800 801 802 803 804
static void command_manageauth(client_t *client, source_t *source,
    int response)
{
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, msgnode;
805 806
    const char *action = NULL;
    const char *username = NULL;
807 808
    char *message = NULL;
    int ret = AUTH_OK;
809
    ice_config_t *config = config_get_config ();
810
    mount_proxy *mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
811

812 813
    do
    {
814 815
        if (mountinfo == NULL || mountinfo->auth == NULL)
        {
816
            ICECAST_LOG_WARN("manage auth request for %s but no facility available", source->mount);
817
            break;
818
        }
819 820 821 822
        COMMAND_OPTIONAL(client, "action", action);
        COMMAND_OPTIONAL (client, "username", username);

        if (action == NULL)
823
            action = "list";
824 825 826 827 828 829 830 831

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

            if (username == NULL || password == NULL)
            {
832
                ICECAST_LOG_WARN("manage auth request add for %s but no user/pass", source->mount);
833 834
                break;
            }
835
            ret = mountinfo->auth->adduser(mountinfo->auth, username, password);
836 837 838 839 840 841 842 843 844 845
            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");
            }
        }
846 847 848 849
        if (!strcmp(action, "delete"))
        {
            if (username == NULL)
            {
850
                ICECAST_LOG_WARN("manage auth request delete for %s but no username", source->mount);
851 852
                break;
            }
853
            ret = mountinfo->auth->deleteuser(mountinfo->auth, username);
854 855 856 857 858 859 860 861
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
            }
            if (ret == AUTH_USERDELETED) {
                message = strdup("User deleted");
            }
        }

862 863 864 865
        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));
866

867
        if (message) {
868 869
            msgnode = xmlNewChild(node, NULL, XMLSTR("iceresponse"), NULL);
            xmlNewChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
870
        }
871

872
        xmlDocSetRootElement(doc, node);
873

874 875
        if (mountinfo && mountinfo->auth && mountinfo->auth->listuser)
            mountinfo->auth->listuser (mountinfo->auth, srcnode);
876

877
        config_release_config ();
878

879 880 881 882 883 884 885 886 887
        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");
888 889
}

890 891
static void command_kill_source(client_t *client, source_t *source,
    int response)
892
{
893 894 895
    xmlDocPtr doc;
    xmlNodePtr node;

896 897 898 899
    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"));
900 901
    xmlDocSetRootElement(doc, node);

902 903
    source->running = 0;

904 905 906
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
907 908
}

909 910
static void command_kill_client(client_t *client, source_t *source,
    int response)
911
{
912
    const char *idtext;
913 914
    int id;
    client_t *listener;
915 916 917
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
918 919 920 921 922 923 924

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

925 926
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
927
    xmlDocSetRootElement(doc, node);
928
    ICECAST_LOG_DEBUG("Response is %d", response);
929

930
    if(listener != NULL) {
931
        ICECAST_LOG_INFO("Admin request: client %d removed", id);
932 933 934 935 936

        /* This tags it for removal on the next iteration of the main source
         * loop
         */
        listener->con->error = 1;
937 938
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
939 940
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
941 942
    }
    else {
943 944
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d not found", id);
945 946
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
947
    }
948 949 950
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
951 952
}

953 954
static void command_fallback(client_t *client, source_t *source,
    int response)
955
{
956
    const char *fallback;
957 958
    char *old;

959
    ICECAST_LOG_DEBUG("Got fallback request");
960 961 962 963 964 965 966

    COMMAND_REQUIRE(client, "fallback", fallback);

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

967
    html_success(client, "Fallback configured");
968 969
}

970 971
static void command_metadata(client_t *client, source_t *source,
    int response)
972
{
973
    const char *action;
974
    const char *song, *title, *artist, *charset;
975
    format_plugin_t *plugin;
976 977
    xmlDocPtr doc;
    xmlNodePtr node;
978
    int same_ip = 1;
979

980 981
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode (doc, NULL, XMLSTR("iceresponse"), NULL);
982
    xmlDocSetRootElement(doc, node);
983

984
    ICECAST_LOG_DEBUG("Got metadata update request");
985 986

    COMMAND_REQUIRE(client, "mode", action);
987 988 989
    COMMAND_OPTIONAL(client, "song", song);
    COMMAND_OPTIONAL(client, "title", title);
    COMMAND_OPTIONAL(client, "artist", artist);
990
    COMMAND_OPTIONAL(client, "charset", charset);
991

Karl Heyes's avatar
Karl Heyes committed
992 993
    if (strcmp (action, "updinfo") != 0)
    {
994 995
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR("No such action"));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
996 997 998
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
999 1000
        return;
    }
1001

1002
    plugin = source->format;
1003 1004 1005
    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
1006

1007
    if (same_ip && plugin && plugin->set_tag)
1008 1009 1010
    {
        if (song)
        {
1011
            plugin->set_tag (plugin, "song", song, charset);
1012
            ICECAST_LOG_INFO("Metadata on mountpoint %s changed to \"%s\"", source->mount, song);
1013 1014 1015 1016 1017
        }
        else
        {
            if (artist && title)
            {
1018 1019
                plugin->set_tag (plugin, "title", title, charset);
                plugin->set_tag (plugin, "artist", artist, charset);
1020
                ICECAST_LOG_INFO("Metadata on mountpoint %s changed to \"%s - %s\"",