admin.c 40.6 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 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
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;
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 201
static void command_manageauth(client_t *client, source_t *source,
        int response);
202
static void command_buildm3u(client_t *client, const char *mount);
203 204
static void command_kill_source(client_t *client, source_t *source,
        int response);
205 206
static void command_updatemetadata(client_t *client, source_t *source,
        int response);
Philipp Schafft's avatar
Philipp Schafft committed
207 208
static void admin_handle_mount_request(client_t *client, source_t *source);
static void admin_handle_general_request(client_t *client);
209

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

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

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

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

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

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

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

Karl Heyes's avatar
Karl Heyes committed
254
            config = config_get_config();
255
            mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
Philipp Schafft's avatar
Philipp Schafft committed
256 257 258 259 260 261
            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)"));
262
            }
Philipp Schafft's avatar
Philipp Schafft committed
263
            acl_release(acl);
264 265
            config_release_config();

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

597

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

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

621 622 623 624
    dest = source_find_mount (dest_source);

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

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

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

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

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

647
    source_move_clients (source, dest);
648

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

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

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

Philipp Schafft's avatar
Philipp Schafft committed
665 666 667 668 669
    /* 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.
     */
670 671 672 673 674 675 676

    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
677
    xmlSetProp(node, XMLSTR("id"), XMLSTR(buf));
678
    xmlNewChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "ID" : "id"), XMLSTR(buf));
679

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

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

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

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

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

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

700 701 702
    return node;
}

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

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

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

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

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

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

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

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

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

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


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

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


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

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

807 808 809 810
        COMMAND_OPTIONAL(client, "action", action);
        COMMAND_OPTIONAL (client, "username", username);

        if (action == NULL)
811
            action = "list";
812 813 814 815

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

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

850
        doc = xmlNewDoc(XMLSTR("1.0"));
851 852 853
        node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
        srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
        xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
854

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

860
        xmlDocSetRootElement(doc, node);
861

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

865
        config_release_config ();
866

867 868 869 870 871 872 873 874
        admin_send_response(doc, client, response, 
                MANAGEAUTH_TRANSFORMED_REQUEST);
        free (message);
        xmlFreeDoc(doc);
        return;
    } while (0);

    config_release_config ();
875
    client_send_error(client, 400, 0, "missing parameter");
876 877
}

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

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

890 891
    source->running = 0;

892
    admin_send_response(doc, client, response,
893 894
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
895 896
}

897 898
static void command_kill_client(client_t *client, source_t *source,
    int response)