admin.c 42 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
static void command_manageauth(client_t *client, int response) {
780
    xmlDocPtr doc;
781
    xmlNodePtr node, rolenode, usersnode, msgnode;
782 783
    const char *action = NULL;
    const char *username = NULL;
784
    const char *idstring = NULL;
785 786
    char *message = NULL;
    int ret = AUTH_OK;
787 788 789 790
    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
791
    auth_t *auth;
792
    char idbuf[32];
793

794 795
    do
    {
796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
        /* 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";
819
            break;
820
        }
Philipp Schafft's avatar
Philipp Schafft committed
821

822
        COMMAND_OPTIONAL(client, "action", action);
823
        COMMAND_OPTIONAL(client, "username", username);
824 825

        if (action == NULL)
826
            action = "list";
827 828 829 830

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

833 834
            if (username == NULL || password == NULL) {
                ICECAST_LOG_WARN("manage auth request add for %lu but no user/pass", id);
835 836
                break;
            }
837 838 839 840 841 842

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

Philipp Schafft's avatar
Philipp Schafft committed
843
            ret = auth->adduser(auth, username, password);
844 845
            if (ret == AUTH_FAILED) {
                message = strdup("User add failed - check the icecast error log");
846
            } else if (ret == AUTH_USERADDED) {
847
                message = strdup("User added");
848
            } else if (ret == AUTH_USEREXISTS) {
849 850 851
                message = strdup("User already exists - not added");
            }
        }
852 853
        if (!strcmp(action, "delete"))
        {
854 855 856 857 858 859 860
            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";
861 862
                break;
            }
863

Philipp Schafft's avatar
Philipp Schafft committed
864
            ret = auth->deleteuser(auth, username);
865 866
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
867
            } else if (ret == AUTH_USERDELETED) {
868 869 870 871
                message = strdup("User deleted");
            }
        }

872
        doc = xmlNewDoc(XMLSTR("1.0"));
873
        node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
874 875 876 877 878 879 880 881 882 883 884 885 886
        rolenode = xmlNewChild(node, NULL, XMLSTR("role"), NULL);

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

        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"));
887

888
        if (message) {
889 890
            msgnode = xmlNewChild(node, NULL, XMLSTR("iceresponse"), NULL);
            xmlNewChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
891
        }
892

893
        xmlDocSetRootElement(doc, node);
894

895 896 897 898
        if (auth && auth->listuser) {
            usersnode = xmlNewChild(rolenode, NULL, XMLSTR("users"), NULL);
            auth->listuser(auth, usersnode);
        }
899

900 901
        config_release_config();
        auth_release(auth);
902

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

910 911 912
    config_release_config();
    auth_release(auth);
    client_send_error(client, error_code, 0, error_message);
913 914
}

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

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

927 928
    source->running = 0;

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

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

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

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

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

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

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

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

    COMMAND_REQUIRE(client, "fallback", fallback);

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

992
    html_success(client, "Fallback configured");