admin.c 38 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
            mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
240 241
            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
    if (response == RAW)
    {
        xmlChar *buff = NULL;
        int len = 0;
271 272 273
        size_t buf_len;
        ssize_t ret;

274
        xmlDocDumpMemory(doc, &buff, &len);
275 276 277 278

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

Karl Heyes's avatar
Karl Heyes committed
280
        client_set_queue (client, NULL);
281
        client->refbuf = refbuf_new (buf_len);
282

283
	ret = util_http_build_header(client->refbuf->data, buf_len, 0,
284
	                             0, 200, NULL,
285
				     "text/xml", "utf-8",
286
				     NULL, NULL);
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
        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;
            } 
        }
317

318 319 320 321
        /* 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;
322 323 324
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
325
    }
326 327 328 329 330 331 332 333
    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;
334 335
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
336
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
337
        config_release_config();
338

339
        ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template);
340 341 342 343
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
344 345


346
void admin_handle_request(client_t *client, const char *uri)
347
{
348
    const char *mount, *command_string;
349 350
    int command;

351
    ICECAST_LOG_DEBUG("Admin request (%s)", uri);
352 353
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
354
        ICECAST_LOG_ERROR("Internal error: admin request isn't");
355 356 357 358
        client_send_401(client);
        return;
    }

359 360 361 362 363 364
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
365

366
    ICECAST_LOG_DEBUG("Got command (%s)", command_string);
367 368 369
    command = admin_get_command(command_string);

    if(command < 0) {
370
        ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %s",
371 372 373 374 375
                command_string);
        client_send_400(client, "Unrecognised command");
        return;
    }

376 377
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

378
        ice_config_t *config;
379
        const char *sc_mount;
380
        const char *pass = httpp_get_query_param (client->parser, "pass");
381 382
        listener_t *listener;

383 384 385 386 387
        if (pass == NULL)
        {
            client_send_400 (client, "missing pass parameter");
            return;
        }
388
        global_lock();
389
        config = config_get_config ();
390 391
        sc_mount = config->shoutcast_mount;
        listener = config_get_listen_sock (config, client->con);
392

393 394 395 396
        if (listener && listener->shoutcast_mount)
            sc_mount = listener->shoutcast_mount;

        httpp_set_query_param (client->parser, "mount", sc_mount);
397 398
        httpp_setvar (client->parser, HTTPP_VAR_PROTOCOL, "ICY");
        httpp_setvar (client->parser, HTTPP_VAR_ICYPASSWORD, pass);
399
        config_release_config ();
400
        global_unlock();
401 402
    }

403 404
    mount = httpp_get_query_param(client->parser, "mount");

405 406 407
    if(mount != NULL) {
        source_t *source;

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

431
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
432
        source = source_find_mount_raw(mount);
433

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

468 469 470 471 472
        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)) {
473
                ICECAST_LOG_INFO("Bad or missing password on admin command "
474 475 476 477 478 479 480
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
        }
        else {
            if(!connection_check_admin_pass(client->parser)) {
481
                ICECAST_LOG_INFO("Bad or missing password on admin command "
482 483 484 485
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
486 487 488 489 490 491 492 493 494 495
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
496
            command_stats(client, NULL, RAW);
497
            break;
498 499
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
500 501
            break;
        case COMMAND_RAW_LISTSTREAM:
502 503
            command_list_mounts(client, RAW);
            break;
504 505 506
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
507
        case COMMAND_TRANSFORMED_STATS:
508
            command_stats(client, NULL, TRANSFORMED);
509 510 511 512 513 514 515 516 517
            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);
518
            break;
519
        default:
520
            ICECAST_LOG_WARN("General admin request not recognised");
521 522 523 524 525 526 527 528 529
            client_send_400(client, "Unknown admin request");
            return;
    }
}

static void admin_handle_mount_request(client_t *client, source_t *source, 
        int command)
{
    switch(command) {
530 531 532
        case COMMAND_RAW_STATS:
            command_stats(client, source->mount, RAW);
            break;
533 534
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
535
            break;
536 537 538 539 540
        case COMMAND_RAW_METADATA_UPDATE:
            command_metadata(client, source, RAW);
            break;
        case COMMAND_TRANSFORMED_METADATA_UPDATE:
            command_metadata(client, source, TRANSFORMED);
541
            break;
542 543 544
        case COMMAND_SHOUTCAST_METADATA_UPDATE:
            command_shoutcast_metadata(client, source);
            break;
545 546 547 548 549 550 551 552
        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);
553
            break;
554 555
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
556
            break;
557 558 559
        case COMMAND_TRANSFORMED_STATS:
            command_stats(client, source->mount, TRANSFORMED);
            break;
560 561
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
562
            break;
563 564 565 566 567 568 569 570 571 572 573
        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);
574
            break;
575 576 577 578 579 580
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
581 582 583 584 585 586
        case COMMAND_TRANSFORMED_UPDATEMETADATA:
            command_updatemetadata(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_UPDATEMETADATA:
            command_updatemetadata(client, source, RAW);
            break;
587
        default:
588
            ICECAST_LOG_WARN("Mount request not recognised");
589
            client_send_400(client, "Mount request unknown");
590
            break;
591 592 593 594 595 596 597 598 599 600 601
    }
}

#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);
602 603
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
604

605
static void html_success(client_t *client, char *message)
606
{
607 608 609 610
    ssize_t ret;

    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE, 0,
                                 0, 200, NULL,
611
				 "text/html", "utf-8",
612
				 "", NULL);
613 614 615 616 617 618 619

    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;
    }

620 621 622 623
    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);

624
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
625 626
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
627 628
}

629

630 631
static void command_move_clients(client_t *client, source_t *source,
    int response)
632
{
633
    const char *dest_source;
634
    source_t *dest;
635 636 637 638 639
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

640
    ICECAST_LOG_DEBUG("Doing optional check");
641
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
642 643
        parameters_passed = 1;
    }
644
    ICECAST_LOG_DEBUG("Done optional check (%d)", parameters_passed);
645 646 647 648 649 650 651
    if (!parameters_passed) {
        doc = admin_build_sourcelist(source->mount);
        admin_send_response(doc, client, response, 
             MOVECLIENTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
        return;
    }
652

653 654 655 656 657 658 659 660
    dest = source_find_mount (dest_source);

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

Karl Heyes's avatar
Karl Heyes committed
661
    if (strcmp (dest->mount, source->mount) == 0)
662 663 664 665 666
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

667
    if (dest->running == 0 && dest->on_demand == 0)
668 669
    {
        client_send_400 (client, "Destination not running");
670 671 672
        return;
    }

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

675 676
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
677 678
    xmlDocSetRootElement(doc, node);

679
    source_move_clients (source, dest);
680

681
    memset(buf, '\000', sizeof(buf));
682 683
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
684 685
    xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
    xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
686 687 688 689

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
690 691
}

692 693
static void command_show_listeners(client_t *client, source_t *source,
    int response)
694
{
695 696
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
697 698
    avl_node *client_node;
    client_t *current;
699
    char buf[22];
700
    const char *userAgent = NULL;
701 702
    time_t now = time(NULL);

703 704 705 706
    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));
707
    xmlDocSetRootElement(doc, node);
708

709
    memset(buf, '\000', sizeof(buf));
710
    snprintf (buf, sizeof(buf), "%lu", source->listeners);
711
    xmlNewChild(srcnode, NULL, XMLSTR("Listeners"), XMLSTR(buf));
712 713 714 715 716 717

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
718 719
        listenernode = xmlNewChild(srcnode, NULL, XMLSTR("listener"), NULL);
        xmlNewChild(listenernode, NULL, XMLSTR("IP"), XMLSTR(current->con->ip));
720 721
        userAgent = httpp_getvar(current->parser, "user-agent");
        if (userAgent) {
722
            xmlNewChild(listenernode, NULL, XMLSTR("UserAgent"), XMLSTR(userAgent));
723 724
        }
        else {
725
            xmlNewChild(listenernode, NULL, XMLSTR("UserAgent"), XMLSTR("Unknown"));
726 727
        }
        memset(buf, '\000', sizeof(buf));
Karl Heyes's avatar
Karl Heyes committed
728
        snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - current->con->con_time));
729
        xmlNewChild(listenernode, NULL, XMLSTR("Connected"), XMLSTR(buf));
730
        memset(buf, '\000', sizeof(buf));
731
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
732
        xmlNewChild(listenernode, NULL, XMLSTR("ID"), XMLSTR(buf));
733
        if (current->username) {
734
            xmlNewChild(listenernode, NULL, XMLSTR("username"), XMLSTR(current->username));
735
        }
736 737 738 739
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
740 741 742
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
743 744
}

745
static void command_buildm3u(client_t *client,  const char *mount)
746
{
747 748
    const char *username = NULL;
    const char *password = NULL;
749
    ice_config_t *config;
750
    ssize_t ret;
751 752 753 754

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

755 756 757
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE, 0,
                                 0, 200, NULL,
				 "audio/x-mpegurl", NULL,
758
				 NULL, NULL);
759

760 761 762 763 764 765 766
    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
767
    config = config_get_config();
768
    snprintf (client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
769 770 771 772
        "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
773 774
        config->hostname,
        config->port,
775
        mount
776
    );
Karl Heyes's avatar
Karl Heyes committed
777
    config_release_config();
778

779
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
780 781
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
782
}
783 784


785 786 787 788 789
static void command_manageauth(client_t *client, source_t *source,
    int response)
{
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, msgnode;
790 791
    const char *action = NULL;
    const char *username = NULL;
792 793
    char *message = NULL;
    int ret = AUTH_OK;
794
    ice_config_t *config = config_get_config ();
795
    mount_proxy *mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
796

797 798
    do
    {
799 800
        if (mountinfo == NULL || mountinfo->auth == NULL)
        {
801
            ICECAST_LOG_WARN("manage auth request for %s but no facility available", source->mount);
802
            break;
803
        }
804 805 806 807
        COMMAND_OPTIONAL(client, "action", action);
        COMMAND_OPTIONAL (client, "username", username);

        if (action == NULL)
808
            action = "list";
809 810 811 812 813 814 815 816

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

            if (username == NULL || password == NULL)
            {
817
                ICECAST_LOG_WARN("manage auth request add for %s but no user/pass", source->mount);
818 819
                break;
            }
820
            ret = mountinfo->auth->adduser(mountinfo->auth, username, password);
821 822 823 824 825 826 827 828 829 830
            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");
            }
        }
831 832 833 834
        if (!strcmp(action, "delete"))
        {
            if (username == NULL)
            {
835
                ICECAST_LOG_WARN("manage auth request delete for %s but no username", source->mount);
836 837
                break;
            }
838
            ret = mountinfo->auth->deleteuser(mountinfo->auth, username);
839 840 841 842 843 844 845 846
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
            }
            if (ret == AUTH_USERDELETED) {
                message = strdup("User deleted");
            }
        }

847 848 849 850
        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));
851

852
        if (message) {
853 854
            msgnode = xmlNewChild(node, NULL, XMLSTR("iceresponse"), NULL);
            xmlNewChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
855
        }
856

857
        xmlDocSetRootElement(doc, node);
858

859 860
        if (mountinfo && mountinfo->auth && mountinfo->auth->listuser)
            mountinfo->auth->listuser (mountinfo->auth, srcnode);
861

862
        config_release_config ();
863

864 865 866 867 868 869 870 871 872
        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");
873 874
}

875 876
static void command_kill_source(client_t *client, source_t *source,
    int response)
877
{
878 879 880
    xmlDocPtr doc;
    xmlNodePtr node;

881 882 883 884
    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"));
885 886
    xmlDocSetRootElement(doc, node);

887 888
    source->running = 0;

889 890 891
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
892 893
}

894 895
static void command_kill_client(client_t *client, source_t *source,
    int response)
896
{
897
    const char *idtext;
898 899
    int id;
    client_t *listener;
900 901 902
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
903 904 905 906 907 908 909

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

910 911
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
912
    xmlDocSetRootElement(doc, node);
913
    ICECAST_LOG_DEBUG("Response is %d", response);
914

915
    if(listener != NULL) {
916
        ICECAST_LOG_INFO("Admin request: client %d removed", id);
917 918 919 920 921

        /* This tags it for removal on the next iteration of the main source
         * loop
         */
        listener->con->error = 1;
922 923
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
924 925
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
926 927
    }
    else {
928 929
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d not found", id);
930 931
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
932
    }
933 934 935
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
936 937
}

938 939
static void command_fallback(client_t *client, source_t *source,
    int response)
940
{
941
    const char *fallback;
942 943
    char *old;

944
    ICECAST_LOG_DEBUG("Got fallback request");
945 946 947 948 949 950 951

    COMMAND_REQUIRE(client, "fallback", fallback);

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

952
    html_success(client, "Fallback configured");
953 954
}

955 956
static void command_metadata(client_t *client, source_t *source,
    int response)
957
{
958
    const char *action;
959
    const char *song, *title, *artist, *charset;
960
    format_plugin_t *plugin;
961 962
    xmlDocPtr doc;
    xmlNodePtr node;
963
    int same_ip = 1;
964

965 966
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode (doc, NULL, XMLSTR("iceresponse"), NULL);
967
    xmlDocSetRootElement(doc, node);
968

969
    ICECAST_LOG_DEBUG("Got metadata update request");
970 971

    COMMAND_REQUIRE(client, "mode", action);
972 973 974
    COMMAND_OPTIONAL(client, "song", song);
    COMMAND_OPTIONAL(client, "title", title);
    COMMAND_OPTIONAL(client, "artist", artist);
975
    COMMAND_OPTIONAL(client, "charset", charset);
976

Karl Heyes's avatar
Karl Heyes committed
977 978
    if (strcmp (action, "updinfo") != 0)
    {
979 980
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR("No such action"));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
981 982 983
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
984 985
        return;
    }
986

987
    plugin = source->format;
988 989 990
    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
991

992
    if (same_ip && plugin && plugin->set_tag)
993 994 995
    {
        if (song)
        {
996
            plugin->set_tag (plugin, "song", song, charset);
997
            ICECAST_LOG_INFO("Metadata on mountpoint %s changed to \"%s\"", source->mount, song);
998 999 1000 1001 1002
        }
        else
        {
            if (artist && title)
            {
1003 1004
                plugin->set_tag (plugin, "title", title, charset);
                plugin->set_tag (plugin, "artist", artist, charset);
1005
                ICECAST_LOG_INFO("Metadata on mountpoint %s changed to \"%s - %s\"",
1006 1007 1008
                        source->mount, artist, title);
            }
        }
1009 1010
        /* updates are now done, let them be pushed into the stream */
        plugin->set_tag (plugin, NULL, NULL, NULL);
1011 1012 1013
    }
    else
    {
1014 1015 1016
        xmlNewChild(node, NULL, XMLSTR("message"), 
            XMLSTR("Mountpoint will not accept URL updates"));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
1017 1018 1019 1020
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
        return;
1021
    }
1022

1023 1024
    xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR("Metadata update successful"));
    xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));