admin.c 42.3 KB
Newer Older
1 2 3 4 5
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
6
 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
7 8 9 10
 *                      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
#include "connection.h"
#include "refbuf.h"
#include "client.h"
#include "source.h"
#include "global.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

Philipp Schafft's avatar
Philipp Schafft committed
119 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
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},
155 156
 {COMMAND_RAW_MANAGEAUTH,              MANAGEAUTH_RAW_REQUEST,             ADMINTYPE_GENERAL, RAW},
 {COMMAND_TRANSFORMED_MANAGEAUTH,      MANAGEAUTH_TRANSFORMED_REQUEST,     ADMINTYPE_GENERAL, TRANSFORMED},
Philipp Schafft's avatar
Philipp Schafft committed
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
 {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;
186 187
}

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

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

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

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

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

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

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

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

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

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

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

292
        xmlDocDumpMemory(doc, &buff, &len);
293 294 295 296

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

298 299
        client_set_queue(client, NULL);
        client->refbuf = refbuf_new(buf_len);
300

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

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

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


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

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

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

383
    ICECAST_LOG_DEBUG("Got command (%s)", command_string);
384

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

Philipp Schafft's avatar
Philipp Schafft committed
392 393 394 395 396 397 398
    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");
399 400
            return;
        }
401 402
    }

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

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

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

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

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

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

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

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

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

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

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

587
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
588 589
        "<html><head><title>Admin request successful</title></head>"
        "<body><p>%s</p></body></html>", message);
590

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

596

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

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

620 621 622 623
    dest = source_find_mount (dest_source);

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

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

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

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

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

646
    source_move_clients (source, dest);
647

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

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
657 658
}

659
static inline xmlNodePtr __add_listener(client_t *client, xmlNodePtr parent, time_t now, operation_mode mode) {
660 661 662 663
    const char *tmp;
    xmlNodePtr node;
    char buf[22];

Philipp Schafft's avatar
Philipp Schafft committed
664 665 666 667 668
    /* TODO: kh has support for a child node "lag". We should add that.
     * BEFORE RELEASE 2.5.0 REVIEW #2097: Check if we are on-track for lowercasing child nodes.
     * BEFORE RELEASE 2.6.0 TODO #2097: Change case of child nodes to lower case.
     * The case of <ID>, <IP>, <UserAgent> and <Connected> should be converted to lower case.
     */
669 670 671 672 673 674 675

    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);
Philipp Schafft's avatar
Philipp Schafft committed
676
    xmlSetProp(node, XMLSTR("id"), XMLSTR(buf));
677
    xmlNewChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "ID" : "id"), XMLSTR(buf));
678

679
    xmlNewChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "IP" : "ip"), XMLSTR(client->con->ip));
680 681 682

    tmp = httpp_getvar(client->parser, "user-agent");
    if (tmp)
683
        xmlNewChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "UserAgent" : "useragent"), XMLSTR(tmp));
684 685 686

    tmp = httpp_getvar(client->parser, "referer");
    if (tmp)
Philipp Schafft's avatar
Philipp Schafft committed
687
        xmlNewChild(node, NULL, XMLSTR("referer"), XMLSTR(tmp));
688 689 690

    memset(buf, '\000', sizeof(buf));
    snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - client->con->con_time));
691
    xmlNewChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "Connected" : "connected"), XMLSTR(buf));
692 693 694 695

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

Philipp Schafft's avatar
Philipp Schafft committed
696 697 698
    if (client->role)
        xmlNewChild(node, NULL, XMLSTR("role"), XMLSTR(client->role));

699 700 701
    return node;
}

702
void admin_add_listeners_to_mount(source_t *source, xmlNodePtr parent, operation_mode mode) {
703 704 705 706 707 708
    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) {
709
        __add_listener((client_t *)client_node->key, parent, now, mode);
710 711 712 713 714
        client_node = avl_get_next(client_node);
    }
    avl_tree_unlock(source->client_tree);
}

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

722
    doc = xmlNewDoc(XMLSTR("1.0"));
723 724 725
    node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
    srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
    xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
726
    xmlDocSetRootElement(doc, node);
727

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

732
    admin_add_listeners_to_mount(source, srcnode, client->mode);
733

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

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

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

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

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


Karl Heyes's avatar
Karl Heyes committed
761
    config = config_get_config();
762
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
763
        "Content-Disposition: attachment; filename=listen.m3u\r\n\r\n"
764 765 766
        "http://%s:%s@%s:%d%s\r\n",
        username,
        password,
Karl Heyes's avatar
Karl Heyes committed
767 768
        config->hostname,
        config->port,
769
        mount
770
    );
Karl Heyes's avatar
Karl Heyes committed
771
    config_release_config();
772

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

778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797
xmlNodePtr admin_add_role_to_authentication(auth_t *auth, xmlNodePtr parent) {
    xmlNodePtr rolenode = xmlNewChild(parent, NULL, XMLSTR("role"), NULL);
    char idbuf[32];

    snprintf(idbuf, sizeof(idbuf), "%lu", auth->id);
    xmlSetProp(rolenode, XMLSTR("id"), XMLSTR(idbuf));

    if (auth->type)
        xmlSetProp(rolenode, XMLSTR("type"), XMLSTR(auth->type));
    if (auth->role)
        xmlSetProp(rolenode, XMLSTR("name"), XMLSTR(auth->role));
    if (auth->management_url)
        xmlSetProp(rolenode, XMLSTR("management-url"), XMLSTR(auth->management_url));

    xmlSetProp(rolenode, XMLSTR("can-adduser"), XMLSTR(auth->adduser ? "true" : "false"));
    xmlSetProp(rolenode, XMLSTR("can-deleteuser"), XMLSTR(auth->deleteuser ? "true" : "false"));
    xmlSetProp(rolenode, XMLSTR("can-listuser"), XMLSTR(auth->listuser ? "true" : "false"));

    return rolenode;
}
798

799
static void command_manageauth(client_t *client, int response) {
800
    xmlDocPtr doc;
801
    xmlNodePtr node, rolenode, usersnode, msgnode;
802 803
    const char *action = NULL;
    const char *username = NULL;
804
    const char *idstring = NULL;
805 806
    char *message = NULL;
    int ret = AUTH_OK;
807 808 809 810
    int error_code = 400;
    const char *error_message = "missing parameter";
    long unsigned int id;
    ice_config_t *config = config_get_config();
Philipp Schafft's avatar
Philipp Schafft committed
811
    auth_t *auth;
812

813 814
    do
    {
815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837
        /* get id */
        COMMAND_REQUIRE(client, "id", idstring);
        id = atol(idstring);

        /* no find a auth_t for that id by looking up the config */
        /* globals first */
        auth = auth_stack_getbyid(config->authstack, id);
        /* now mounts */
        if (!auth) {
            mount_proxy *mount = config->mounts;
            while (mount) {
                auth = auth_stack_getbyid(mount->authstack, id);
                if (auth)
                    break;
                mount = mount->next;
            }
        }

        /* check if we found one */
        if (auth == NULL) {
            ICECAST_LOG_WARN("Client requested mangement for unknown role %lu", id);
            error_code = 404;
            error_message = "Role not found";
838
            break;
839
        }
Philipp Schafft's avatar
Philipp Schafft committed
840

841
        COMMAND_OPTIONAL(client, "action", action);
842
        COMMAND_OPTIONAL(client, "username", username);
843 844

        if (action == NULL)
845
            action = "list";
846 847 848 849

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

852 853
            if (username == NULL || password == NULL) {
                ICECAST_LOG_WARN("manage auth request add for %lu but no user/pass", id);
854 855
                break;
            }
856 857 858 859 860 861

            if (!auth->adduser) {
                error_message = "Adding users to role not supported by role";
                break;
            }

Philipp Schafft's avatar
Philipp Schafft committed
862
            ret = auth->adduser(auth, username, password);
863 864
            if (ret == AUTH_FAILED) {
                message = strdup("User add failed - check the icecast error log");
865
            } else if (ret == AUTH_USERADDED) {
866
                message = strdup("User added");
867
            } else if (ret == AUTH_USEREXISTS) {
868 869 870
                message = strdup("User already exists - not added");
            }
        }
871 872
        if (!strcmp(action, "delete"))
        {
873 874 875 876 877 878 879
            if (username == NULL) {
                ICECAST_LOG_WARN("manage auth request delete for %lu but no username", id);
                break;
            }

            if (!auth->deleteuser) {
                error_message = "Deleting users from role not supported by role";
880 881
                break;
            }
882

Philipp Schafft's avatar
Philipp Schafft committed
883
            ret = auth->deleteuser(auth, username);
884 885
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
886
            } else if (ret == AUTH_USERDELETED) {
887 888 889 890
                message = strdup("User deleted");
            }
        }

891
        doc = xmlNewDoc(XMLSTR("1.0"));
892
        node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
893

894
        rolenode = admin_add_role_to_authentication(auth, node);
895

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