admin.c 39.9 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
        if (ret == -1) {
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
298
            client_send_error(client, 500, 0, "Header generation failed.");
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
            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.");
315
                    client_send_error(client, 500, 0, "Header generation failed.");
316 317 318 319 320
                    xmlFree(buff);
                    return;
                }
            } else {
                ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
321
                client_send_error(client, 500, 0, "Buffer reallocation failed.");
322 323 324 325
                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
        client_send_error(client, 401, 1, "You need to authenticate\r\n");
365 366 367
        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
                command_string);
381
        client_send_error(client, 400, 0, "Unrecognised command");
382 383 384
        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
        if (pass == NULL)
        {
394
            client_send_error(client, 400, 0, "missing pass parameter");
395 396
            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
                            "request (command: %s)", command_string);
433
                    client_send_error(client, 401, 1, "You need to authenticate\r\n");
434 435 436
                    /* 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
            client_send_error(client, 400, 0, "Source does not exist");
449
        }
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
                        command_string, mount);
457
                client_send_error(client, 400, 0, "Source is not available");
458 459 460 461 462 463
                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
                        "compatible stream");
466
                client_send_error(client, 400, 0, "illegal metadata call");
467
                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
                      "request (command: %s)", command_string);
484
                client_send_error(client, 401, 1, "You need to authenticate\r\n");
485 486 487 488 489
                return;
            }
        }
        else {
            if(!connection_check_admin_pass(client->parser)) {
490
                ICECAST_LOG_INFO("Bad or missing password on admin command "
491
                      "request (command: %s)", command_string);
492
                client_send_error(client, 401, 1, "You need to authenticate\r\n");
493 494
                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
            client_send_error(client, 400, 0, "Unknown admin request");
537 538 539 540 541 542 543 544
            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_error(client, 400, 0, "Mount request unknown");
605
            break;
606 607 608 609 610 611 612
    }
}

#define COMMAND_REQUIRE(client,name,var) \
    do { \
        (var) = httpp_get_query_param((client)->parser, (name)); \
        if((var) == NULL) { \
613
            client_send_error((client), 400, 0, "Missing parameter"); \
614 615 616
            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

    if (ret == -1 || ret >= PER_CLIENT_REFBUF_SIZE) {
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
631
        client_send_error(client, 500, 0, "Header generation failed.");
632 633 634
        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
    dest = source_find_mount (dest_source);

    if (dest == NULL)
    {
672
        client_send_error(client, 400, 0, "No such destination");
673 674 675
        return;
    }

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

682
    if (dest->running == 0 && dest->on_demand == 0)
683
    {
684
        client_send_error(client, 400, 0, "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 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759
static inline xmlNodePtr __add_listener(client_t *client, xmlNodePtr parent, time_t now) {
    const char *tmp;
    xmlNodePtr node;
    char buf[22];

#if 0
    xmlSetProp (node, XMLSTR("id"), XMLSTR(buf));

    xmlNewChild (node, NULL, XMLSTR("lag"), XMLSTR(buf));

#endif

    node = xmlNewChild(parent, NULL, XMLSTR("listener"), NULL);
    if (!node)
        return NULL;

    memset(buf, '\000', sizeof(buf));
    snprintf(buf, sizeof(buf)-1, "%lu", client->con->id);
    xmlNewChild(node, NULL, XMLSTR("ID"), XMLSTR(buf));

    xmlNewChild(node, NULL, XMLSTR("IP"), XMLSTR(client->con->ip));

    tmp = httpp_getvar(client->parser, "user-agent");
    if (tmp)
        xmlNewChild(node, NULL, XMLSTR("UserAgent"), XMLSTR(tmp));

    tmp = httpp_getvar(client->parser, "referer");
    if (tmp)
        xmlNewChild(node, NULL, XMLSTR("Referer"), XMLSTR(tmp));

    memset(buf, '\000', sizeof(buf));
    snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - client->con->con_time));
    xmlNewChild(node, NULL, XMLSTR("Connected"), XMLSTR(buf));

    if (client->username)
        xmlNewChild(node, NULL, XMLSTR("username"), XMLSTR(client->username));

    return node;
}

void admin_add_listeners_to_mount(source_t *source, xmlNodePtr parent) {
    time_t now = time(NULL);
    avl_node *client_node;

    avl_tree_rlock(source->client_tree);
    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        __add_listener((client_t *)client_node->key, parent, now);
        client_node = avl_get_next(client_node);
    }
    avl_tree_unlock(source->client_tree);
}

760 761
static void command_show_listeners(client_t *client, source_t *source,
    int response)
762
{
763
    xmlDocPtr doc;
764
    xmlNodePtr node, srcnode;
765
    char buf[22];
766

767 768 769 770
    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));
771
    xmlDocSetRootElement(doc, node);
772

773
    memset(buf, '\000', sizeof(buf));
774
    snprintf (buf, sizeof(buf), "%lu", source->listeners);
775
    xmlNewChild(srcnode, NULL, XMLSTR("Listeners"), XMLSTR(buf));
776

777
    admin_add_listeners_to_mount(source, srcnode);
778

779 780 781
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
782 783
}

784
static void command_buildm3u(client_t *client,  const char *mount)
785
{
786 787
    const char *username = NULL;
    const char *password = NULL;
788
    ice_config_t *config;
789
    ssize_t ret;
790 791 792 793

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

794 795 796
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE, 0,
                                 0, 200, NULL,
				 "audio/x-mpegurl", NULL,
797
				 NULL, NULL);
798

799 800
    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.");
801
        client_send_error(client, 500, 0, "Header generation failed.");
802 803 804 805
        return;
    }


Karl Heyes's avatar
Karl Heyes committed
806
    config = config_get_config();
807
    snprintf (client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
808 809 810 811
        "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
812 813
        config->hostname,
        config->port,
814
        mount
815
    );
Karl Heyes's avatar
Karl Heyes committed
816
    config_release_config();
817

818
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
819 820
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
821
}
822 823


824 825 826 827 828
static void command_manageauth(client_t *client, source_t *source,
    int response)
{
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, msgnode;
829 830
    const char *action = NULL;
    const char *username = NULL;
831 832
    char *message = NULL;
    int ret = AUTH_OK;
833
    ice_config_t *config = config_get_config ();
834
    mount_proxy *mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
835

836 837
    do
    {
838 839
        if (mountinfo == NULL || mountinfo->auth == NULL)
        {
840
            ICECAST_LOG_WARN("manage auth request for %s but no facility available", source->mount);
841
            break;
842
        }
843 844 845 846
        COMMAND_OPTIONAL(client, "action", action);
        COMMAND_OPTIONAL (client, "username", username);

        if (action == NULL)
847
            action = "list";
848 849 850 851 852 853 854 855

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

            if (username == NULL || password == NULL)
            {
856
                ICECAST_LOG_WARN("manage auth request add for %s but no user/pass", source->mount);
857 858
                break;
            }
859
            ret = mountinfo->auth->adduser(mountinfo->auth, username, password);
860 861 862 863 864 865 866 867 868 869
            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");
            }
        }
870 871 872 873
        if (!strcmp(action, "delete"))
        {
            if (username == NULL)
            {
874
                ICECAST_LOG_WARN("manage auth request delete for %s but no username", source->mount);
875 876
                break;
            }
877
            ret = mountinfo->auth->deleteuser(mountinfo->auth, username);
878 879 880 881 882 883 884 885
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
            }
            if (ret == AUTH_USERDELETED) {
                message = strdup("User deleted");
            }
        }

886 887 888 889
        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));
890

891
        if (message) {
892 893
            msgnode = xmlNewChild(node, NULL, XMLSTR("iceresponse"), NULL);
            xmlNewChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
894
        }
895

896
        xmlDocSetRootElement(doc, node);
897

898 899
        if (mountinfo && mountinfo->auth && mountinfo->auth->listuser)
            mountinfo->auth->listuser (mountinfo->auth, srcnode);
900

901
        config_release_config ();
902

903 904 905 906 907 908 909 910
        admin_send_response(doc, client, response, 
                MANAGEAUTH_TRANSFORMED_REQUEST);
        free (message);
        xmlFreeDoc(doc);
        return;
    } while (0);

    config_release_config ();
911
    client_send_error(client, 400, 0, "missing parameter");
912 913
}

914 915
static void command_kill_source(client_t *client, source_t *source,
    int response)
916
{
917 918 919
    xmlDocPtr doc;
    xmlNodePtr node;

920 921 922 923
    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"));
924 925
    xmlDocSetRootElement(doc, node);

926 927
    source->running = 0;

928 929 930
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
931 932
}

933 934
static void command_kill_client(client_t *client, source_t *source,
    int response)
935
{
936
    const char *idtext;
937 938
    int id;
    client_t *listener;
939 940 941
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
942 943 944 945 946 947 948

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

949 950
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
951
    xmlDocSetRootElement(doc, node);
952
    ICECAST_LOG_DEBUG("Response is %d", response);
953

954
    if(listener != NULL) {
955
        ICECAST_LOG_INFO("Admin request: client %d removed", id);
956 957 958 959 960

        /* This tags it for removal on the next iteration of the main source
         * loop
         */
        listener->con->error = 1;
961 962
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
963 964
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
965 966
    }
    else {
967 968
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d not found", id);
969 970
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
971
    }
972 973 974
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
975 976
}

977 978
static void command_fallback(client_t *client, source_t *source,
    int response)
979
{
980
    const char *fallback;
981 982
    char *old;

983
    ICECAST_LOG_DEBUG("Got fallback request");
984 985 986 987 988 989 990

    COMMAND_REQUIRE(client, "fallback", fallback);

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

991
    html_success(client, "Fallback configured");
992 993
}

994 995
static void command_metadata(client_t *client, source_t *source,
    int response)
996
{
997
    const char *action;
998
    const char *song, *title, *artist, *charset;
999
    format_plugin_t *plugin;
1000 1001
    xmlDocPtr doc;
    xmlNodePtr node;
1002
    int same_ip = 1;
1003

1004 1005
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode (doc, NULL, XMLSTR("iceresponse"), NULL);
1006
    xmlDocSetRootElement(doc, node);
1007

1008
    ICECAST_LOG_DEBUG("Got metadata update request");