admin.c 40.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/* 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).
Philipp Schafft's avatar
Philipp Schafft committed
11
 * Copyright 2012-2014, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
12 13
 */

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

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

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

#include "format.h"

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

#define CATMODULE "admin"

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

/* Mount-specific commands */
54 55 56 57 58 59 60
#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
61 62 63 64

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

/* Global commands */
Karl Heyes's avatar
Karl Heyes committed
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
74
#define COMMAND_RAW_QUEUE_RELOAD            105
Karl Heyes's avatar
Karl Heyes committed
75 76 77
#define COMMAND_TRANSFORMED_LIST_MOUNTS     201
#define COMMAND_TRANSFORMED_STATS           202
#define COMMAND_TRANSFORMED_LISTSTREAM      203
78
#define COMMAND_TRANSFORMED_QUEUE_RELOAD    205
79

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

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

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

Philipp Schafft's avatar
Philipp Schafft committed
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
typedef struct admin_command_tag {
    const int   id;
    const char *name;
    const int   type;
    const int   format;
} admin_command_t;

/*
COMMAND_TRANSFORMED_METADATA_UPDATE -> METADATA_TRANSFORMED_REQUEST
COMMAND_TRANSFORMED_UPDATEMETADATA  -> UPDATEMETADATA_TRANSFORMED_REQUEST
*/

static const admin_command_t commands[] = {
 {COMMAND_RAW_FALLBACK,                FALLBACK_RAW_REQUEST,               ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_FALLBACK,        FALLBACK_TRANSFORMED_REQUEST,       ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_METADATA_UPDATE,         METADATA_RAW_REQUEST,               ADMINTYPE_MOUNT,   RAW},
 {COMMAND_SHOUTCAST_METADATA_UPDATE,   SHOUTCAST_METADATA_REQUEST,         ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_TRANSFORMED_METADATA_UPDATE, METADATA_TRANSFORMED_REQUEST,       ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_SHOW_LISTENERS,          LISTCLIENTS_RAW_REQUEST,            ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_SHOW_LISTENERS,  LISTCLIENTS_TRANSFORMED_REQUEST,    ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_STATS,                   STATS_RAW_REQUEST,                  ADMINTYPE_HYBRID,  RAW},
 {COMMAND_TRANSFORMED_STATS,           STATS_TRANSFORMED_REQUEST,          ADMINTYPE_HYBRID,  TRANSFORMED},
 {COMMAND_RAW_STATS,                   "stats.xml",                        ADMINTYPE_HYBRID,  RAW}, /* The old way */
 {COMMAND_RAW_QUEUE_RELOAD,            QUEUE_RELOAD_RAW_REQUEST,           ADMINTYPE_GENERAL, RAW},
 {COMMAND_TRANSFORMED_QUEUE_RELOAD,    QUEUE_RELOAD_TRANSFORMED_REQUEST,   ADMINTYPE_GENERAL, TRANSFORMED},
 {COMMAND_RAW_LIST_MOUNTS,             LISTMOUNTS_RAW_REQUEST,             ADMINTYPE_GENERAL, RAW},
 {COMMAND_TRANSFORMED_LIST_MOUNTS,     LISTMOUNTS_TRANSFORMED_REQUEST,     ADMINTYPE_GENERAL, TRANSFORMED},
 {COMMAND_RAW_LISTSTREAM,              STREAMLIST_RAW_REQUEST,             ADMINTYPE_GENERAL, RAW},
 {COMMAND_PLAINTEXT_LISTSTREAM,        STREAMLIST_PLAINTEXT_REQUEST,       ADMINTYPE_GENERAL, PLAINTEXT},
 {COMMAND_TRANSFORMED_LISTSTREAM,      STREAMLIST_TRANSFORMED_REQUEST,     ADMINTYPE_GENERAL, TRANSFORMED},
 {COMMAND_RAW_MOVE_CLIENTS,            MOVECLIENTS_RAW_REQUEST,            ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_MOVE_CLIENTS,    MOVECLIENTS_TRANSFORMED_REQUEST,    ADMINTYPE_HYBRID,  TRANSFORMED},
 {COMMAND_RAW_KILL_CLIENT,             KILLCLIENT_RAW_REQUEST,             ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_KILL_CLIENT,     KILLCLIENT_TRANSFORMED_REQUEST,     ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_KILL_SOURCE,             KILLSOURCE_RAW_REQUEST,             ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_KILL_SOURCE,     KILLSOURCE_TRANSFORMED_REQUEST,     ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_MANAGEAUTH,              MANAGEAUTH_RAW_REQUEST,             ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_MANAGEAUTH,      MANAGEAUTH_TRANSFORMED_REQUEST,     ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_UPDATEMETADATA,          UPDATEMETADATA_RAW_REQUEST,         ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_UPDATEMETADATA,  UPDATEMETADATA_TRANSFORMED_REQUEST, ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_BUILDM3U,                    BUILDM3U_RAW_REQUEST,               ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_STATS,           DEFAULT_TRANSFORMED_REQUEST,        ADMINTYPE_HYBRID,  TRANSFORMED},
 {COMMAND_TRANSFORMED_STATS,           DEFAULT_RAW_REQUEST,                ADMINTYPE_HYBRID,  TRANSFORMED},
 {COMMAND_ANY,                         "*",                                ADMINTYPE_GENERAL, TRANSFORMED} /* for ACL framework */
};

int admin_get_command(const char *command) {
    size_t i;

    for (i = 0; i < (sizeof(commands)/sizeof(*commands)); i++)
        if (strcmp(commands[i].name, command) == 0)
            return commands[i].id;

    return COMMAND_ERROR;
}

int admin_get_command_type(int command) {
    size_t i;

    if (command == ADMIN_COMMAND_ERROR || command == COMMAND_ANY)
        return ADMINTYPE_ERROR;

    for (i = 0; i < (sizeof(commands)/sizeof(*commands)); i++)
        if (commands[i].id == command)
            return commands[i].type;

    return ADMINTYPE_ERROR;
187 188
}

189
static void command_fallback(client_t *client, source_t *source, int response);
190
static void command_metadata(client_t *client, source_t *source, int response);
191
static void command_shoutcast_metadata(client_t *client, source_t *source);
192 193 194 195
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);
196
static void command_stats(client_t *client, const char *mount, int response);
197
static void command_queue_reload(client_t *client, int response);
198 199 200
static void command_list_mounts(client_t *client, int response);
static void command_kill_client(client_t *client, source_t *source,
        int response);
201 202
static void command_manageauth(client_t *client, source_t *source,
        int response);
203
static void command_buildm3u(client_t *client, const char *mount);
204 205
static void command_kill_source(client_t *client, source_t *source,
        int response);
206 207
static void command_updatemetadata(client_t *client, source_t *source,
        int response);
Philipp Schafft's avatar
Philipp Schafft committed
208 209
static void admin_handle_mount_request(client_t *client, source_t *source);
static void admin_handle_general_request(client_t *client);
210

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

223 224
    doc = xmlNewDoc (XMLSTR("1.0"));
    xmlnode = xmlNewDocNode (doc, NULL, XMLSTR("icestats"), NULL);
225
    xmlDocSetRootElement(doc, xmlnode);
226

227
    if (mount) {
228
        xmlNewChild (xmlnode, NULL, XMLSTR("current_source"), XMLSTR(mount));
229 230 231 232 233
    }

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

240
        if (source->running || source->on_demand)
241
        {
242 243
            ice_config_t *config;
            mount_proxy *mountinfo;
Philipp Schafft's avatar
Philipp Schafft committed
244
            acl_t *acl = NULL;
245

246 247
            srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
            xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
248

249
            xmlNewChild(srcnode, NULL, XMLSTR("fallback"), 
250
                    (source->fallback_mount != NULL)?
251
                    XMLSTR(source->fallback_mount):XMLSTR(""));
252
            snprintf (buf, sizeof(buf), "%lu", source->listeners);
253
            xmlNewChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
254

Karl Heyes's avatar
Karl Heyes committed
255
            config = config_get_config();
256
            mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
Philipp Schafft's avatar
Philipp Schafft committed
257 258 259 260 261 262
            if (mountinfo)
                acl = auth_stack_get_anonymous_acl(mountinfo->authstack);
            if (!acl)
                auth_stack_get_anonymous_acl(config->authstack);
            if (acl && acl_test_web(acl) == ACL_POLICY_DENY) {
                xmlNewChild(srcnode, NULL, XMLSTR("authenticator"), XMLSTR("(dummy)"));
263
            }
Philipp Schafft's avatar
Philipp Schafft committed
264
            acl_release(acl);
265 266
            config_release_config();

267 268
            if (source->running)
            {
269
                if (source->client) 
Karl Heyes's avatar
Karl Heyes committed
270 271 272
                {
                    snprintf (buf, sizeof(buf), "%lu",
                            (unsigned long)(now - source->con->con_time));
273
                    xmlNewChild (srcnode, NULL, XMLSTR("Connected"), XMLSTR(buf));
Karl Heyes's avatar
Karl Heyes committed
274
                }
275 276
                xmlNewChild (srcnode, NULL, XMLSTR("content-type"), 
                        XMLSTR(source->format->contenttype));
277
            }
278
        }
279 280 281 282 283
        node = avl_get_next(node);
    }
    return(doc);
}

284 285
void admin_send_response (xmlDocPtr doc, client_t *client,
        int response, const char *xslt_template)
286
{
287 288 289 290
    if (response == RAW)
    {
        xmlChar *buff = NULL;
        int len = 0;
291 292 293
        size_t buf_len;
        ssize_t ret;

294
        xmlDocDumpMemory(doc, &buff, &len);
295 296 297 298

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

Karl Heyes's avatar
Karl Heyes committed
300
        client_set_queue (client, NULL);
301
        client->refbuf = refbuf_new (buf_len);
302

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

338 339 340 341
        /* 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;
342 343 344
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
345
    }
346 347 348 349 350 351 352 353
    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;
354 355
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
356
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
357
        config_release_config();
358

359
        ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template);
360 361 362 363
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
364 365


366
void admin_handle_request(client_t *client, const char *uri)
367
{
368
    const char *mount, *command_string;
369

370
    ICECAST_LOG_DEBUG("Admin request (%s)", uri);
371 372
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
373
        ICECAST_LOG_ERROR("Internal error: admin request isn't");
374
        client_send_error(client, 401, 1, "You need to authenticate\r\n");
375 376 377
        return;
    }

378 379 380 381 382 383
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
384

385
    ICECAST_LOG_DEBUG("Got command (%s)", command_string);
386

Philipp Schafft's avatar
Philipp Schafft committed
387
    if (client->admin_command <= 0) {
388
        ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %s",
389
                command_string);
390
        client_send_error(client, 400, 0, "Unrecognised command");
391 392 393
        return;
    }

Philipp Schafft's avatar
Philipp Schafft committed
394 395 396 397 398 399 400
    if (acl_test_admin(client->acl, client->admin_command) != ACL_POLICY_ALLOW) {
        if (client->admin_command == COMMAND_RAW_METADATA_UPDATE &&
            (acl_test_method(client->acl, httpp_req_source) == ACL_POLICY_ALLOW ||
             acl_test_method(client->acl, httpp_req_put)    == ACL_POLICY_ALLOW)) {
            ICECAST_LOG_DEBUG("Granted right to call COMMAND_RAW_METADATA_UPDATE to client because it is allowed to do SOURCE or PUT.");
        } else {
            client_send_error(client, 401, 1, "You need to authenticate\r\n");
401 402
            return;
        }
403 404
    }

405 406
    mount = httpp_get_query_param(client->parser, "mount");

407 408 409
    if(mount != NULL) {
        source_t *source;

410
        /* this request does not require auth but can apply to files on webroot */
Philipp Schafft's avatar
Philipp Schafft committed
411 412
        if (client->admin_command == COMMAND_BUILDM3U) {
            command_buildm3u(client, mount);
413
            return;
414
        }
415

Philipp Schafft's avatar
Philipp Schafft committed
416
        /* This is a mount request, handle it as such */
417
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
418
        source = source_find_mount_raw(mount);
419

420 421
        if (source == NULL)
        {
422
            ICECAST_LOG_WARN("Admin command %s on non-existent source %s", 
423
                    command_string, mount);
424
            avl_tree_unlock(global.source_tree);
425
            client_send_error(client, 400, 0, "Source does not exist");
426
        }
427 428
        else
        {
429
            if (source->running == 0 && source->on_demand == 0)
430 431
            {
                avl_tree_unlock (global.source_tree);
432
                ICECAST_LOG_INFO("Received admin command %s on unavailable mount \"%s\"",
433
                        command_string, mount);
434
                client_send_error(client, 400, 0, "Source is not available");
435 436
                return;
            }
Philipp Schafft's avatar
Philipp Schafft committed
437
            if (client->admin_command == COMMAND_SHOUTCAST_METADATA_UPDATE &&
438 439 440
                    source->shoutcast_compat == 0)
            {
                avl_tree_unlock (global.source_tree);
441
                ICECAST_LOG_ERROR("illegal change of metadata on non-shoutcast "
442
                        "compatible stream");
443
                client_send_error(client, 400, 0, "illegal metadata call");
444
                return;
445
            }
446
            ICECAST_LOG_INFO("Received admin command %s on mount \"%s\"", 
447
                    command_string, mount);
Philipp Schafft's avatar
Philipp Schafft committed
448
            admin_handle_mount_request(client, source);
449
            avl_tree_unlock(global.source_tree);
450
        }
451 452
    }
    else {
Philipp Schafft's avatar
Philipp Schafft committed
453
        admin_handle_general_request(client);
454 455 456
    }
}

Philipp Schafft's avatar
Philipp Schafft committed
457
static void admin_handle_general_request(client_t *client)
458
{
Philipp Schafft's avatar
Philipp Schafft committed
459
    switch(client->admin_command) {
460
        case COMMAND_RAW_STATS:
461
            command_stats(client, NULL, RAW);
462
            break;
463 464 465
        case COMMAND_RAW_QUEUE_RELOAD:
            command_queue_reload(client, RAW);
            break;
466 467
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
468 469
            break;
        case COMMAND_RAW_LISTSTREAM:
470 471
            command_list_mounts(client, RAW);
            break;
472 473 474
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
475
        case COMMAND_TRANSFORMED_STATS:
476
            command_stats(client, NULL, TRANSFORMED);
477
            break;
478 479 480
        case COMMAND_TRANSFORMED_QUEUE_RELOAD:
            command_queue_reload(client, TRANSFORMED);
            break;
481 482 483 484 485 486 487 488
        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);
489
            break;
490
        default:
491
            ICECAST_LOG_WARN("General admin request not recognised");
492
            client_send_error(client, 400, 0, "Unknown admin request");
493 494 495 496
            return;
    }
}

Philipp Schafft's avatar
Philipp Schafft committed
497 498
static void admin_handle_mount_request(client_t *client, source_t *source) {
    switch(client->admin_command) {
499 500 501
        case COMMAND_RAW_STATS:
            command_stats(client, source->mount, RAW);
            break;
502 503
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
504
            break;
505 506 507 508 509
        case COMMAND_RAW_METADATA_UPDATE:
            command_metadata(client, source, RAW);
            break;
        case COMMAND_TRANSFORMED_METADATA_UPDATE:
            command_metadata(client, source, TRANSFORMED);
510
            break;
511 512 513
        case COMMAND_SHOUTCAST_METADATA_UPDATE:
            command_shoutcast_metadata(client, source);
            break;
514 515 516 517 518 519 520 521
        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);
522
            break;
523 524
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
525
            break;
526 527 528
        case COMMAND_TRANSFORMED_STATS:
            command_stats(client, source->mount, TRANSFORMED);
            break;
529 530
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
531
            break;
532 533 534 535 536 537 538 539 540 541 542
        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);
543
            break;
544 545 546 547 548 549
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
550 551 552 553 554 555
        case COMMAND_TRANSFORMED_UPDATEMETADATA:
            command_updatemetadata(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_UPDATEMETADATA:
            command_updatemetadata(client, source, RAW);
            break;
556
        default:
557
            ICECAST_LOG_WARN("Mount request not recognised");
558
            client_send_error(client, 400, 0, "Mount request unknown");
559
            break;
560 561 562 563 564 565 566
    }
}

#define COMMAND_REQUIRE(client,name,var) \
    do { \
        (var) = httpp_get_query_param((client)->parser, (name)); \
        if((var) == NULL) { \
567
            client_send_error((client), 400, 0, "Missing parameter"); \
568 569 570
            return; \
        } \
    } while(0);
571 572
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
573

574
static void html_success(client_t *client, char *message)
575
{
576 577 578 579
    ssize_t ret;

    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE, 0,
                                 0, 200, NULL,
580
				 "text/html", "utf-8",
581
				 "", NULL);
582 583 584

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

589 590 591 592
    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);

593
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
594 595
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
596 597
}

598

599 600
static void command_move_clients(client_t *client, source_t *source,
    int response)
601
{
602
    const char *dest_source;
603
    source_t *dest;
604 605 606 607 608
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

609
    ICECAST_LOG_DEBUG("Doing optional check");
610
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
611 612
        parameters_passed = 1;
    }
613
    ICECAST_LOG_DEBUG("Done optional check (%d)", parameters_passed);
614 615 616 617 618 619 620
    if (!parameters_passed) {
        doc = admin_build_sourcelist(source->mount);
        admin_send_response(doc, client, response, 
             MOVECLIENTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
        return;
    }
621

622 623 624 625
    dest = source_find_mount (dest_source);

    if (dest == NULL)
    {
626
        client_send_error(client, 400, 0, "No such destination");
627 628 629
        return;
    }

Karl Heyes's avatar
Karl Heyes committed
630
    if (strcmp (dest->mount, source->mount) == 0)
631
    {
632
        client_send_error(client, 400, 0, "supplied mountpoints are identical");
633 634 635
        return;
    }

636
    if (dest->running == 0 && dest->on_demand == 0)
637
    {
638
        client_send_error(client, 400, 0, "Destination not running");
639 640 641
        return;
    }

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

644 645
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
646 647
    xmlDocSetRootElement(doc, node);

648
    source_move_clients (source, dest);
649

650
    memset(buf, '\000', sizeof(buf));
651 652
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
653 654
    xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
    xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
655 656 657 658

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
659 660
}

661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
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);
}

714 715
static void command_show_listeners(client_t *client, source_t *source,
    int response)
716
{
717
    xmlDocPtr doc;
718
    xmlNodePtr node, srcnode;
719
    char buf[22];
720

721 722 723 724
    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));
725
    xmlDocSetRootElement(doc, node);
726

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

731
    admin_add_listeners_to_mount(source, srcnode);
732

733 734 735
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
736 737
}

738
static void command_buildm3u(client_t *client,  const char *mount)
739
{
740 741
    const char *username = NULL;
    const char *password = NULL;
742
    ice_config_t *config;
743
    ssize_t ret;
744 745 746 747

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

748 749 750
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE, 0,
                                 0, 200, NULL,
				 "audio/x-mpegurl", NULL,
751
				 NULL, NULL);
752

753 754
    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.");
755
        client_send_error(client, 500, 0, "Header generation failed.");
756 757 758 759
        return;
    }


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

772
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
773 774
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
775
}
776 777


778 779 780 781 782
static void command_manageauth(client_t *client, source_t *source,
    int response)
{
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, msgnode;
783 784
    const char *action = NULL;
    const char *username = NULL;
785 786
    char *message = NULL;
    int ret = AUTH_OK;
787
    ice_config_t *config = config_get_config ();
788
    mount_proxy *mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
Philipp Schafft's avatar
Philipp Schafft committed
789
    auth_t *auth;
790

791 792
    do
    {
Philipp Schafft's avatar
Philipp Schafft committed
793
#if 0
794 795
        if (mountinfo == NULL || mountinfo->auth == NULL)
        {
796
            ICECAST_LOG_WARN("manage auth request for %s but no facility available", source->mount);
797
            break;
798
        }
Philipp Schafft's avatar
Philipp Schafft committed
799 800 801 802 803 804
        auth = mountinfo->auth;
#else
        ICECAST_LOG_WARN("manage auth request for %s but no facility available", source->mount);
        break;
#endif

805 806 807 808
        COMMAND_OPTIONAL(client, "action", action);
        COMMAND_OPTIONAL (client, "username", username);

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

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

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

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

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

858
        xmlDocSetRootElement(doc, node);
859

Philipp Schafft's avatar
Philipp Schafft committed
860 861
        if (auth && auth->listuser)
            auth->listuser(auth, srcnode);
862

863
        config_release_config ();
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 ();
873
    client_send_error(client, 400, 0, "missing parameter");
874 875
}

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

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

888 889
    source->running = 0;

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

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

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

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

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

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

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

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

    COMMAND_REQUIRE(client, "fallback", fallback);

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

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

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

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

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

Philipp Schafft's avatar
Philipp Schafft committed
972 973 974 975
    if (source->parser->req_type == httpp_req_put) {
        ICECAST_LOG_ERROR("Got legacy SOURCE-style metadata update command on source connected with PUT at mountpoint %s", source->mount);
    }

976
    COMMAND_REQUIRE(client, "mode", action);
977 978 979
    COMMAND_OPTIONAL(client, "song", song);
    COMMAND_OPTIONAL(client, "title", title);
    COMMAND_OPTIONAL(client, "artist", artist);
980
    COMMAND_OPTIONAL(client, "charset", charset);
981

Karl Heyes's avatar
Karl Heyes committed
982 983
    if (strcmp (action, "updinfo") != 0)
    {
984 985
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR("No such action"));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
986 987 988
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
989 990
        return;
    }
991

992
    plugin = source->format;
993
    if (source->client && strcmp (client->con->ip, source->client->con->ip) != 0)
Philipp Schafft's avatar
Philipp Schafft committed
994
        if (response == RAW && acl_test_admin(client->acl, COMMAND_RAW_METADATA_UPDATE) != ACL_POLICY_ALLOW)
995
            same_ip = 0;
Karl Heyes's avatar
Karl Heyes committed
996

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