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

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

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

25
#include "cfgfile.h"
26 27 28 29 30 31 32
#include "connection.h"
#include "refbuf.h"
#include "client.h"
#include "source.h"
#include "global.h"
#include "event.h"
#include "stats.h"
33
#include "compat.h"
34
#include "xslt.h"
35
#include "fserve.h"
36
#include "admin.h"
37 38 39 40

#include "format.h"

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

#define CATMODULE "admin"

48 49 50
/* special commands */
#define COMMAND_ERROR                      ADMIN_COMMAND_ERROR
#define COMMAND_ANY                        ADMIN_COMMAND_ANY
51 52

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

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

/* Global commands */
Karl Heyes's avatar
Karl Heyes committed
69 70 71 72
#define COMMAND_RAW_LIST_MOUNTS             101
#define COMMAND_RAW_STATS                   102
#define COMMAND_RAW_LISTSTREAM              103
#define COMMAND_PLAINTEXT_LISTSTREAM        104
73
#define COMMAND_RAW_QUEUE_RELOAD            105
Karl Heyes's avatar
Karl Heyes committed
74 75 76
#define COMMAND_TRANSFORMED_LIST_MOUNTS     201
#define COMMAND_TRANSFORMED_STATS           202
#define COMMAND_TRANSFORMED_LISTSTREAM      203
77
#define COMMAND_TRANSFORMED_QUEUE_RELOAD    205
78

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

85 86 87
/* Admin commands requiring no auth */
#define COMMAND_BUILDM3U                    501

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

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

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

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

220 221
    doc = xmlNewDoc (XMLSTR("1.0"));
    xmlnode = xmlNewDocNode (doc, NULL, XMLSTR("icestats"), NULL);
222
    xmlDocSetRootElement(doc, xmlnode);
223

224
    if (mount) {
225
        xmlNewChild (xmlnode, NULL, XMLSTR("current_source"), XMLSTR(mount));
226 227 228 229 230
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
231 232 233 234 235 236
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

237
        if (source->running || source->on_demand)
238
        {
239 240 241
            ice_config_t *config;
            mount_proxy *mountinfo;

242 243
            srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
            xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
244

245
            xmlNewChild(srcnode, NULL, XMLSTR("fallback"), 
246
                    (source->fallback_mount != NULL)?
247
                    XMLSTR(source->fallback_mount):XMLSTR(""));
248
            snprintf (buf, sizeof(buf), "%lu", source->listeners);
249
            xmlNewChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
250

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

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

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

287
        xmlDocDumpMemory(doc, &buff, &len);
288 289 290 291

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

Karl Heyes's avatar
Karl Heyes committed
293
        client_set_queue (client, NULL);
294
        client->refbuf = refbuf_new (buf_len);
295

296
	ret = util_http_build_header(client->refbuf->data, buf_len, 0,
297
	                             0, 200, NULL,
298
				     "text/xml", "utf-8",
299
				     NULL, NULL);
300 301
        if (ret == -1) {
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
302
            client_send_error(client, 500, 0, "Header generation failed.");
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
            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.");
319
                    client_send_error(client, 500, 0, "Header generation failed.");
320 321 322 323 324
                    xmlFree(buff);
                    return;
                }
            } else {
                ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
325
                client_send_error(client, 500, 0, "Buffer reallocation failed.");
326 327 328 329
                xmlFree(buff);
                return;
            } 
        }
330

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

352
        ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template);
353 354 355 356
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
357 358


359
void admin_handle_request(client_t *client, const char *uri)
360
{
361
    const char *mount, *command_string;
362 363
    int command;

364
    ICECAST_LOG_DEBUG("Admin request (%s)", uri);
365 366
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
367
        ICECAST_LOG_ERROR("Internal error: admin request isn't");
368
        client_send_error(client, 401, 1, "You need to authenticate\r\n");
369 370 371
        return;
    }

372 373 374 375 376 377
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
378

379
    ICECAST_LOG_DEBUG("Got command (%s)", command_string);
380 381
    command = admin_get_command(command_string);

382
    if(command <= 0) {
383
        ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %s",
384
                command_string);
385
        client_send_error(client, 400, 0, "Unrecognised command");
386 387 388
        return;
    }

389 390
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

391
        ice_config_t *config;
392
        const char *sc_mount;
393
        const char *pass = httpp_get_query_param (client->parser, "pass");
394 395
        listener_t *listener;

396 397
        if (pass == NULL)
        {
398
            client_send_error(client, 400, 0, "missing pass parameter");
399 400
            return;
        }
401
        global_lock();
402
        config = config_get_config ();
403 404
        sc_mount = config->shoutcast_mount;
        listener = config_get_listen_sock (config, client->con);
405

406 407 408 409
        if (listener && listener->shoutcast_mount)
            sc_mount = listener->shoutcast_mount;

        httpp_set_query_param (client->parser, "mount", sc_mount);
410 411
        httpp_setvar (client->parser, HTTPP_VAR_PROTOCOL, "ICY");
        httpp_setvar (client->parser, HTTPP_VAR_ICYPASSWORD, pass);
412
        config_release_config ();
413
        global_unlock();
414 415
    }

416 417
    mount = httpp_get_query_param(client->parser, "mount");

418 419 420
    if(mount != NULL) {
        source_t *source;

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

444
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
445
        source = source_find_mount_raw(mount);
446

447 448
        if (source == NULL)
        {
449
            ICECAST_LOG_WARN("Admin command %s on non-existent source %s", 
450
                    command_string, mount);
451
            avl_tree_unlock(global.source_tree);
452
            client_send_error(client, 400, 0, "Source does not exist");
453
        }
454 455
        else
        {
456
            if (source->running == 0 && source->on_demand == 0)
457 458
            {
                avl_tree_unlock (global.source_tree);
459
                ICECAST_LOG_INFO("Received admin command %s on unavailable mount \"%s\"",
460
                        command_string, mount);
461
                client_send_error(client, 400, 0, "Source is not available");
462 463 464 465 466 467
                return;
            }
            if (command == COMMAND_SHOUTCAST_METADATA_UPDATE &&
                    source->shoutcast_compat == 0)
            {
                avl_tree_unlock (global.source_tree);
468
                ICECAST_LOG_ERROR("illegal change of metadata on non-shoutcast "
469
                        "compatible stream");
470
                client_send_error(client, 400, 0, "illegal metadata call");
471
                return;
472
            }
473
            ICECAST_LOG_INFO("Received admin command %s on mount \"%s\"", 
474 475
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
476
            avl_tree_unlock(global.source_tree);
477
        }
478 479 480
    }
    else {

481 482 483 484 485
        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)) {
486
                ICECAST_LOG_INFO("Bad or missing password on admin command "
487
                      "request (command: %s)", command_string);
488
                client_send_error(client, 401, 1, "You need to authenticate\r\n");
489 490 491 492 493
                return;
            }
        }
        else {
            if(!connection_check_admin_pass(client->parser)) {
494
                ICECAST_LOG_INFO("Bad or missing password on admin command "
495
                      "request (command: %s)", command_string);
496
                client_send_error(client, 401, 1, "You need to authenticate\r\n");
497 498
                return;
            }
499 500 501 502 503 504 505 506 507 508
        }
        
        admin_handle_general_request(client, command);
    }
}

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

static void admin_handle_mount_request(client_t *client, source_t *source, 
        int command)
{
    switch(command) {
549 550 551
        case COMMAND_RAW_STATS:
            command_stats(client, source->mount, RAW);
            break;
552 553
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
554
            break;
555 556 557 558 559
        case COMMAND_RAW_METADATA_UPDATE:
            command_metadata(client, source, RAW);
            break;
        case COMMAND_TRANSFORMED_METADATA_UPDATE:
            command_metadata(client, source, TRANSFORMED);
560
            break;
561 562 563
        case COMMAND_SHOUTCAST_METADATA_UPDATE:
            command_shoutcast_metadata(client, source);
            break;
564 565 566 567 568 569 570 571
        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);
572
            break;
573 574
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
575
            break;
576 577 578
        case COMMAND_TRANSFORMED_STATS:
            command_stats(client, source->mount, TRANSFORMED);
            break;
579 580
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
581
            break;
582 583 584 585 586 587 588 589 590 591 592
        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);
593
            break;
594 595 596 597 598 599
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
600 601 602 603 604 605
        case COMMAND_TRANSFORMED_UPDATEMETADATA:
            command_updatemetadata(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_UPDATEMETADATA:
            command_updatemetadata(client, source, RAW);
            break;
606
        default:
607
            ICECAST_LOG_WARN("Mount request not recognised");
608
            client_send_error(client, 400, 0, "Mount request unknown");
609
            break;
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) { \
617
            client_send_error((client), 400, 0, "Missing parameter"); \
618 619 620
            return; \
        } \
    } while(0);
621 622
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
623

624
static void html_success(client_t *client, char *message)
625
{
626 627 628 629
    ssize_t ret;

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

    if (ret == -1 || ret >= PER_CLIENT_REFBUF_SIZE) {
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
635
        client_send_error(client, 500, 0, "Header generation failed.");
636 637 638
        return;
    }

639 640 641 642
    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);

643
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
644 645
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
646 647
}

648

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

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

672 673 674 675
    dest = source_find_mount (dest_source);

    if (dest == NULL)
    {
676
        client_send_error(client, 400, 0, "No such destination");
677 678 679
        return;
    }

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

686
    if (dest->running == 0 && dest->on_demand == 0)
687
    {
688
        client_send_error(client, 400, 0, "Destination not running");
689 690 691
        return;
    }

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

694 695
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
696 697
    xmlDocSetRootElement(doc, node);

698
    source_move_clients (source, dest);
699

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

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
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 760 761 762 763
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);
}

764 765
static void command_show_listeners(client_t *client, source_t *source,
    int response)
766
{
767
    xmlDocPtr doc;
768
    xmlNodePtr node, srcnode;
769
    char buf[22];
770

771 772 773 774
    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));
775
    xmlDocSetRootElement(doc, node);
776

777
    memset(buf, '\000', sizeof(buf));
778
    snprintf (buf, sizeof(buf), "%lu", source->listeners);
779
    xmlNewChild(srcnode, NULL, XMLSTR("Listeners"), XMLSTR(buf));
780

781
    admin_add_listeners_to_mount(source, srcnode);
782

783 784 785
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
786 787
}

788
static void command_buildm3u(client_t *client,  const char *mount)
789
{
790 791
    const char *username = NULL;
    const char *password = NULL;
792
    ice_config_t *config;
793
    ssize_t ret;
794 795 796 797

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

798 799 800
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE, 0,
                                 0, 200, NULL,
				 "audio/x-mpegurl", NULL,
801
				 NULL, NULL);
802

803 804
    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.");
805
        client_send_error(client, 500, 0, "Header generation failed.");
806 807 808 809
        return;
    }


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

822
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
823 824
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
825
}
826 827


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

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

        if (action == NULL)
851
            action = "list";
852 853 854 855 856 857 858 859

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

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

890 891 892 893
        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));
894

895
        if (message) {
896 897
            msgnode = xmlNewChild(node, NULL, XMLSTR("iceresponse"), NULL);
            xmlNewChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
898
        }
899

900
        xmlDocSetRootElement(doc, node);
901

902 903
        if (mountinfo && mountinfo->auth && mountinfo->auth->listuser)
            mountinfo->auth->listuser (mountinfo->auth, srcnode);
904

905
        config_release_config ();
906

907 908 909 910 911 912 913 914
        admin_send_response(doc, client, response, 
                MANAGEAUTH_TRANSFORMED_REQUEST);
        free (message);
        xmlFreeDoc(doc);
        return;
    } while (0);

    config_release_config ();
915
    client_send_error(client, 400, 0, "missing parameter");
916 917
}

918 919
static void command_kill_source(client_t *client, source_t *source,
    int response)
920
{