admin.c 44.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 51 52 53 54 55 56 57 58 59 60
/* Helper macros */
#define COMMAND_REQUIRE(client,name,var)                                \
    do {                                                                \
        (var) = httpp_get_query_param((client)->parser, (name));        \
        if((var) == NULL) {                                             \
            client_send_error((client), 400, 0, "Missing parameter");   \
            return;                                                     \
        }                                                               \
    } while(0);

#define COMMAND_OPTIONAL(client,name,var) \
(var) = httpp_get_query_param((client)->parser, (name))

61 62 63
/* special commands */
#define COMMAND_ERROR                      ADMIN_COMMAND_ERROR
#define COMMAND_ANY                        ADMIN_COMMAND_ANY
64

65
/* Mount-specific commands (block 1-49 and 50-99) */
66 67 68 69 70 71 72
#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
73 74 75 76

#define COMMAND_TRANSFORMED_FALLBACK        50
#define COMMAND_TRANSFORMED_SHOW_LISTENERS  53
#define COMMAND_TRANSFORMED_MOVE_CLIENTS    54
77
#define COMMAND_TRANSFORMED_MANAGEAUTH      55
78 79
#define COMMAND_TRANSFORMED_UPDATEMETADATA  56
#define COMMAND_TRANSFORMED_METADATA_UPDATE 57
80

81
/* Global commands (block 101-199 and 201-299) */
Karl Heyes's avatar
Karl Heyes committed
82 83 84 85
#define COMMAND_RAW_LIST_MOUNTS             101
#define COMMAND_RAW_STATS                   102
#define COMMAND_RAW_LISTSTREAM              103
#define COMMAND_PLAINTEXT_LISTSTREAM        104
86
#define COMMAND_RAW_QUEUE_RELOAD            105
Karl Heyes's avatar
Karl Heyes committed
87 88 89
#define COMMAND_TRANSFORMED_LIST_MOUNTS     201
#define COMMAND_TRANSFORMED_STATS           202
#define COMMAND_TRANSFORMED_LISTSTREAM      203
90
#define COMMAND_TRANSFORMED_QUEUE_RELOAD    205
91

92
/* Client management commands (block 301-399 and 401-499) */
Karl Heyes's avatar
Karl Heyes committed
93 94 95 96
#define COMMAND_RAW_KILL_CLIENT             301
#define COMMAND_RAW_KILL_SOURCE             302
#define COMMAND_TRANSFORMED_KILL_CLIENT     401
#define COMMAND_TRANSFORMED_KILL_SOURCE     402
97

98
/* Admin commands requiring no auth (block 501-599) */
99 100
#define COMMAND_BUILDM3U                    501

101 102 103 104
/* Experimental features (e.g. in feature branches) (block 801-899) */

/* Private features (in branches not for merge with master) (block 901-999) */

Marvin Scholz's avatar
Marvin Scholz committed
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
#define FALLBACK_RAW_REQUEST                "fallbacks"
#define FALLBACK_TRANSFORMED_REQUEST        "fallbacks.xsl"
#define SHOUTCAST_METADATA_REQUEST          "admin.cgi"
#define METADATA_RAW_REQUEST                "metadata"
#define METADATA_TRANSFORMED_REQUEST        "metadata.xsl"
#define LISTCLIENTS_RAW_REQUEST             "listclients"
#define LISTCLIENTS_TRANSFORMED_REQUEST     "listclients.xsl"
#define STATS_RAW_REQUEST                   "stats"
#define STATS_TRANSFORMED_REQUEST           "stats.xsl"
#define QUEUE_RELOAD_RAW_REQUEST            "reloadconfig"
#define QUEUE_RELOAD_TRANSFORMED_REQUEST    "reloadconfig.xsl"
#define LISTMOUNTS_RAW_REQUEST              "listmounts"
#define LISTMOUNTS_TRANSFORMED_REQUEST      "listmounts.xsl"
#define STREAMLIST_RAW_REQUEST              "streamlist"
#define STREAMLIST_TRANSFORMED_REQUEST      "streamlist.xsl"
#define STREAMLIST_PLAINTEXT_REQUEST        "streamlist.txt"
#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"
#define MANAGEAUTH_RAW_REQUEST              "manageauth"
#define MANAGEAUTH_TRANSFORMED_REQUEST      "manageauth.xsl"
#define UPDATEMETADATA_RAW_REQUEST          "updatemetadata"
#define UPDATEMETADATA_TRANSFORMED_REQUEST  "updatemetadata.xsl"
#define DEFAULT_RAW_REQUEST                 ""
#define DEFAULT_TRANSFORMED_REQUEST         ""
#define BUILDM3U_RAW_REQUEST                "buildm3u"
135

Philipp Schafft's avatar
Philipp Schafft committed
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
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},
172 173
 {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
174 175 176 177 178 179 180 181
 {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 */
};

Marvin Scholz's avatar
Marvin Scholz committed
182 183
int admin_get_command(const char *command)
{
Philipp Schafft's avatar
Philipp Schafft committed
184 185 186 187 188 189 190 191 192
    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;
}

Marvin Scholz's avatar
Marvin Scholz committed
193 194
int admin_get_command_type(int command)
{
Philipp Schafft's avatar
Philipp Schafft committed
195 196 197 198 199 200 201 202 203 204
    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;
205 206
}

207
static void command_fallback(client_t *client, source_t *source, int response);
208
static void command_metadata(client_t *client, source_t *source, int response);
209
static void command_shoutcast_metadata(client_t *client, source_t *source);
210 211 212 213
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);
214
static void command_stats(client_t *client, const char *mount, int response);
215
static void command_queue_reload(client_t *client, int response);
216 217 218
static void command_list_mounts(client_t *client, int response);
static void command_kill_client(client_t *client, source_t *source,
        int response);
219
static void command_manageauth(client_t *client, int response);
220
static void command_buildm3u(client_t *client, const char *mount);
221 222
static void command_kill_source(client_t *client, source_t *source,
        int response);
223 224
static void command_updatemetadata(client_t *client, source_t *source,
        int response);
Philipp Schafft's avatar
Philipp Schafft committed
225 226
static void admin_handle_mount_request(client_t *client, source_t *source);
static void admin_handle_general_request(client_t *client);
227

228 229 230
/* 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 */
Marvin Scholz's avatar
Marvin Scholz committed
231
xmlDocPtr admin_build_sourcelist(const char *mount)
232 233 234 235 236 237 238 239
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

240 241
    doc = xmlNewDoc (XMLSTR("1.0"));
    xmlnode = xmlNewDocNode (doc, NULL, XMLSTR("icestats"), NULL);
242
    xmlDocSetRootElement(doc, xmlnode);
243

244
    if (mount) {
245
        xmlNewTextChild (xmlnode, NULL, XMLSTR("current_source"), XMLSTR(mount));
246 247 248 249 250
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
251 252 253 254 255 256
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

257
        if (source->running || source->on_demand)
258
        {
259 260
            ice_config_t *config;
            mount_proxy *mountinfo;
Philipp Schafft's avatar
Philipp Schafft committed
261
            acl_t *acl = NULL;
262

263 264
            srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
            xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
265

266
            xmlNewTextChild(srcnode, NULL, XMLSTR("fallback"),
267
                    (source->fallback_mount != NULL)?
268
                    XMLSTR(source->fallback_mount):XMLSTR(""));
269
            snprintf(buf, sizeof(buf), "%lu", source->listeners);
270
            xmlNewTextChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
271

Karl Heyes's avatar
Karl Heyes committed
272
            config = config_get_config();
Marvin Scholz's avatar
Marvin Scholz committed
273
            mountinfo = config_find_mount(config, source->mount, MOUNT_TYPE_NORMAL);
Philipp Schafft's avatar
Philipp Schafft committed
274
            if (mountinfo)
275
                acl = auth_stack_get_anonymous_acl(mountinfo->authstack, httpp_req_get);
Philipp Schafft's avatar
Philipp Schafft committed
276
            if (!acl)
277
                acl = auth_stack_get_anonymous_acl(config->authstack, httpp_req_get);
Philipp Schafft's avatar
Philipp Schafft committed
278
            if (acl && acl_test_web(acl) == ACL_POLICY_DENY) {
279
                xmlNewTextChild(srcnode, NULL, XMLSTR("authenticator"), XMLSTR("(dummy)"));
280
            }
Philipp Schafft's avatar
Philipp Schafft committed
281
            acl_release(acl);
282 283
            config_release_config();

Marvin Scholz's avatar
Marvin Scholz committed
284 285 286 287
            if (source->running) {
                if (source->client) {
                    snprintf(buf, sizeof(buf), "%lu",
                        (unsigned long)(now - source->con->con_time));
288
                    xmlNewTextChild(srcnode, NULL, XMLSTR("Connected"), XMLSTR(buf));
Karl Heyes's avatar
Karl Heyes committed
289
                }
290
                xmlNewTextChild(srcnode, NULL, XMLSTR("content-type"),
Marvin Scholz's avatar
Marvin Scholz committed
291
                    XMLSTR(source->format->contenttype));
292
            }
293
        }
294 295 296 297 298
        node = avl_get_next(node);
    }
    return(doc);
}

Marvin Scholz's avatar
Marvin Scholz committed
299 300 301 302
void admin_send_response(xmlDocPtr     doc,
                         client_t      *client,
                         int           response,
                         const char    *xslt_template)
303
{
Marvin Scholz's avatar
Marvin Scholz committed
304
    if (response == RAW) {
305 306
        xmlChar *buff = NULL;
        int len = 0;
307 308 309
        size_t buf_len;
        ssize_t ret;

310
        xmlDocDumpMemory(doc, &buff, &len);
311 312 313 314

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

316 317
        client_set_queue(client, NULL);
        client->refbuf = refbuf_new(buf_len);
318

319 320 321
        ret = util_http_build_header(client->refbuf->data, buf_len, 0,
                                     0, 200, NULL,
                                     "text/xml", "utf-8",
322
                                     NULL, NULL, client);
Philipp Schafft's avatar
Philipp Schafft committed
323
        if (ret < 0) {
324
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
325
            client_send_error(client, 500, 0, "Header generation failed.");
326 327
            xmlFree(buff);
            return;
Philipp Schafft's avatar
Philipp Schafft committed
328
        } else if (buf_len < (size_t)(len + ret + 64)) {
329 330 331 332 333 334 335 336 337 338
            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",
339
                                             NULL, NULL, client);
340 341
                if (ret == -1) {
                    ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
342
                    client_send_error(client, 500, 0, "Header generation failed.");
343 344 345 346 347
                    xmlFree(buff);
                    return;
                }
            } else {
                ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
348
                client_send_error(client, 500, 0, "Buffer reallocation failed.");
349 350
                xmlFree(buff);
                return;
351
            }
352
        }
353

354
        /* FIXME: in this section we hope no function will ever return -1 */
355
        ret += snprintf (client->refbuf->data + ret, buf_len - ret, "Content-Length: %d\r\n\r\n%s", xmlStrlen(buff), buff);
356 357

        client->refbuf->len = ret;
358 359 360
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
361
    }
Marvin Scholz's avatar
Marvin Scholz committed
362
    if (response == TRANSFORMED) {
363 364 365 366
        char *fullpath_xslt_template;
        int fullpath_xslt_template_len;
        ice_config_t *config = config_get_config();

367
        fullpath_xslt_template_len = strlen (config->adminroot_dir) +
368
            strlen (xslt_template) + 2;
369 370
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
371
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
372
        config_release_config();
373

374
        ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template);
375 376 377 378
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
379 380


381
void admin_handle_request(client_t *client, const char *uri)
382
{
383
    const char *mount, *command_string;
384

385
    ICECAST_LOG_DEBUG("Admin request (%s)", uri);
386 387
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
388
        ICECAST_LOG_ERROR("Internal error: admin request isn't");
389
        client_send_error(client, 401, 1, "You need to authenticate\r\n");
390 391 392
        return;
    }

393 394 395 396 397 398
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
399

400
    ICECAST_LOG_DEBUG("Got command (%s)", command_string);
401

Philipp Schafft's avatar
Philipp Schafft committed
402
    if (client->admin_command <= 0) {
403
        ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %s",
404
                command_string);
405
        client_send_error(client, 400, 0, "Unrecognised command");
406 407 408
        return;
    }

Philipp Schafft's avatar
Philipp Schafft committed
409 410 411 412
    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)) {
Marvin Scholz's avatar
Marvin Scholz committed
413 414
            ICECAST_LOG_DEBUG("Granted right to call COMMAND_RAW_METADATA_UPDATE to "
                "client because it is allowed to do SOURCE or PUT.");
Philipp Schafft's avatar
Philipp Schafft committed
415 416
        } else {
            client_send_error(client, 401, 1, "You need to authenticate\r\n");
417 418
            return;
        }
419 420
    }

421 422
    mount = httpp_get_query_param(client->parser, "mount");

423 424 425
    if(mount != NULL) {
        source_t *source;

426
        /* this request does not require auth but can apply to files on webroot */
Philipp Schafft's avatar
Philipp Schafft committed
427 428
        if (client->admin_command == COMMAND_BUILDM3U) {
            command_buildm3u(client, mount);
429
            return;
430
        }
431

Philipp Schafft's avatar
Philipp Schafft committed
432
        /* This is a mount request, handle it as such */
433
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
434
        source = source_find_mount_raw(mount);
435

Marvin Scholz's avatar
Marvin Scholz committed
436
        if (source == NULL) {
437
            ICECAST_LOG_WARN("Admin command %s on non-existent source %s",
438
                    command_string, mount);
439
            avl_tree_unlock(global.source_tree);
440
            client_send_error(client, 400, 0, "Source does not exist");
Marvin Scholz's avatar
Marvin Scholz committed
441 442 443
        } else {
            if (source->running == 0 && source->on_demand == 0) {
                avl_tree_unlock(global.source_tree);
444
                ICECAST_LOG_INFO("Received admin command %s on unavailable mount \"%s\"",
445
                        command_string, mount);
446
                client_send_error(client, 400, 0, "Source is not available");
447 448
                return;
            }
Philipp Schafft's avatar
Philipp Schafft committed
449
            if (client->admin_command == COMMAND_SHOUTCAST_METADATA_UPDATE &&
Marvin Scholz's avatar
Marvin Scholz committed
450 451
                    source->shoutcast_compat == 0) {
                avl_tree_unlock(global.source_tree);
452
                ICECAST_LOG_ERROR("illegal change of metadata on non-shoutcast "
453
                        "compatible stream");
454
                client_send_error(client, 400, 0, "illegal metadata call");
455
                return;
456
            }
457
            ICECAST_LOG_INFO("Received admin command %s on mount \"%s\"",
458
                    command_string, mount);
Philipp Schafft's avatar
Philipp Schafft committed
459
            admin_handle_mount_request(client, source);
460
            avl_tree_unlock(global.source_tree);
461
        }
Marvin Scholz's avatar
Marvin Scholz committed
462
    } else {
Philipp Schafft's avatar
Philipp Schafft committed
463
        admin_handle_general_request(client);
464 465 466
    }
}

Philipp Schafft's avatar
Philipp Schafft committed
467
static void admin_handle_general_request(client_t *client)
468
{
Philipp Schafft's avatar
Philipp Schafft committed
469
    switch(client->admin_command) {
470
        case COMMAND_RAW_STATS:
471
            command_stats(client, NULL, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
472
        break;
473 474
        case COMMAND_RAW_QUEUE_RELOAD:
            command_queue_reload(client, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
475
        break;
476 477
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
478
        break;
479
        case COMMAND_RAW_LISTSTREAM:
480
            command_list_mounts(client, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
481
        break;
482 483
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
Marvin Scholz's avatar
Marvin Scholz committed
484
        break;
485
        case COMMAND_TRANSFORMED_STATS:
486
            command_stats(client, NULL, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
487
        break;
488 489
        case COMMAND_TRANSFORMED_QUEUE_RELOAD:
            command_queue_reload(client, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
490
        break;
491 492
        case COMMAND_TRANSFORMED_LIST_MOUNTS:
            command_list_mounts(client, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
493
        break;
494 495
        case COMMAND_TRANSFORMED_LISTSTREAM:
            command_list_mounts(client, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
496
        break;
497 498
        case COMMAND_TRANSFORMED_MOVE_CLIENTS:
            command_list_mounts(client, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
499
        break;
500 501
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
502
        break;
503 504
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
505
        break;
506
        default:
507
            ICECAST_LOG_WARN("General admin request not recognised");
508
            client_send_error(client, 400, 0, "Unknown admin request");
Marvin Scholz's avatar
Marvin Scholz committed
509
        return;
510 511 512
    }
}

Marvin Scholz's avatar
Marvin Scholz committed
513 514
static void admin_handle_mount_request(client_t *client, source_t *source)
{
Philipp Schafft's avatar
Philipp Schafft committed
515
    switch(client->admin_command) {
516 517
        case COMMAND_RAW_STATS:
            command_stats(client, source->mount, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
518
        break;
519 520
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
521
        break;
522 523
        case COMMAND_RAW_METADATA_UPDATE:
            command_metadata(client, source, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
524
        break;
525 526
        case COMMAND_TRANSFORMED_METADATA_UPDATE:
            command_metadata(client, source, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
527
        break;
528 529
        case COMMAND_SHOUTCAST_METADATA_UPDATE:
            command_shoutcast_metadata(client, source);
Marvin Scholz's avatar
Marvin Scholz committed
530
        break;
531 532
        case COMMAND_RAW_SHOW_LISTENERS:
            command_show_listeners(client, source, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
533
        break;
534 535
        case COMMAND_RAW_MOVE_CLIENTS:
            command_move_clients(client, source, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
536
        break;
537 538
        case COMMAND_RAW_KILL_CLIENT:
            command_kill_client(client, source, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
539
        break;
540 541
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
542
        break;
543 544
        case COMMAND_TRANSFORMED_STATS:
            command_stats(client, source->mount, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
545
        break;
546 547
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
548
        break;
549 550
        case COMMAND_TRANSFORMED_SHOW_LISTENERS:
            command_show_listeners(client, source, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
551
        break;
552 553
        case COMMAND_TRANSFORMED_MOVE_CLIENTS:
            command_move_clients(client, source, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
554
        break;
555 556
        case COMMAND_TRANSFORMED_KILL_CLIENT:
            command_kill_client(client, source, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
557
        break;
558 559
        case COMMAND_TRANSFORMED_KILL_SOURCE:
            command_kill_source(client, source, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
560
        break;
561 562
        case COMMAND_TRANSFORMED_UPDATEMETADATA:
            command_updatemetadata(client, source, TRANSFORMED);
Marvin Scholz's avatar
Marvin Scholz committed
563
        break;
564 565
        case COMMAND_RAW_UPDATEMETADATA:
            command_updatemetadata(client, source, RAW);
Marvin Scholz's avatar
Marvin Scholz committed
566
        break;
567
        default:
568
            ICECAST_LOG_WARN("Mount request not recognised");
569
            client_send_error(client, 400, 0, "Mount request unknown");
Marvin Scholz's avatar
Marvin Scholz committed
570
        break;
571 572 573
    }
}

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

Marvin Scholz's avatar
Marvin Scholz committed
578 579
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
580
                                 "text/html", "utf-8",
581
                                 "", NULL, client);
582 583 584

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

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

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

598

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

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

Marvin Scholz's avatar
Marvin Scholz committed
623
    dest = source_find_mount(dest_source);
624

Marvin Scholz's avatar
Marvin Scholz committed
625
    if (dest == NULL) {
626
        client_send_error(client, 400, 0, "No such destination");
627 628 629
        return;
    }

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

Marvin Scholz's avatar
Marvin Scholz committed
635
    if (dest->running == 0 && dest->on_demand == 0) {
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

Marvin Scholz's avatar
Marvin Scholz committed
642
    doc = xmlNewDoc(XMLSTR("1.0"));
643
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
644 645
    xmlDocSetRootElement(doc, node);

Marvin Scholz's avatar
Marvin Scholz committed
646
    source_move_clients(source, dest);
647

Marvin Scholz's avatar
Marvin Scholz committed
648
    snprintf(buf, sizeof(buf), "Clients moved from %s to %s",
649
        source->mount, dest_source);
650 651
    xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
    xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
652

Marvin Scholz's avatar
Marvin Scholz committed
653
    admin_send_response(doc, client, response, ADMIN_XSL_RESPONSE);
654
    xmlFreeDoc(doc);
655 656
}

Marvin Scholz's avatar
Marvin Scholz committed
657 658 659 660 661
static inline xmlNodePtr __add_listener(client_t        *client,
                                        xmlNodePtr      parent,
                                        time_t          now,
                                        operation_mode  mode)
{
662 663 664 665
    const char *tmp;
    xmlNodePtr node;
    char buf[22];

Philipp Schafft's avatar
Philipp Schafft committed
666
    /* TODO: kh has support for a child node "lag". We should add that.
667 668
     * BEFORE RELEASE NEXT DOCUMENT #2097: Changed case of child nodes to lower case.
     * The case of <ID>, <IP>, <UserAgent> and <Connected> got changed to lower case.
Philipp Schafft's avatar
Philipp Schafft committed
669
     */
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
    xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "ID" : "id"), XMLSTR(buf));
679

680
    xmlNewTextChild(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
        xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "UserAgent" : "useragent"), XMLSTR(tmp));
685 686 687

    tmp = httpp_getvar(client->parser, "referer");
    if (tmp)
688
        xmlNewTextChild(node, NULL, XMLSTR("referer"), XMLSTR(tmp));
689 690

    snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - client->con->con_time));
691
    xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "Connected" : "connected"), XMLSTR(buf));
692 693

    if (client->username)
694
        xmlNewTextChild(node, NULL, XMLSTR("username"), XMLSTR(client->username));
695

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

699
    xmlNewTextChild(node, NULL, XMLSTR("tls"), XMLSTR(client->con->tls ? "true" : "false"));
700

701 702 703 704 705 706 707 708 709
    switch (client->protocol) {
        case ICECAST_PROTOCOL_HTTP:
            xmlNewTextChild(node, NULL, XMLSTR("protocol"), XMLSTR("http"));
        break;
        case ICECAST_PROTOCOL_SHOUTCAST:
            xmlNewTextChild(node, NULL, XMLSTR("protocol"), XMLSTR("icy"));
        break;
    }

710 711 712
    return node;
}

Marvin Scholz's avatar
Marvin Scholz committed
713 714 715 716
void admin_add_listeners_to_mount(source_t          *source,
                                  xmlNodePtr        parent,
                                  operation_mode    mode)
{
717 718 719 720 721 722
    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) {
723
        __add_listener((client_t *)client_node->key, parent, now, mode);
724 725 726 727 728
        client_node = avl_get_next(client_node);
    }
    avl_tree_unlock(source->client_tree);
}

Marvin Scholz's avatar
Marvin Scholz committed
729 730 731
static void command_show_listeners(client_t *client,
                                   source_t *source,
                                   int      response)
732
{
733
    xmlDocPtr doc;
734
    xmlNodePtr node, srcnode;
735
    char buf[22];
736

737
    doc = xmlNewDoc(XMLSTR("1.0"));
738 739 740
    node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
    srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
    xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
741
    xmlDocSetRootElement(doc, node);
742

743
    memset(buf, '\000', sizeof(buf));
744
    snprintf (buf, sizeof(buf), "%lu", source->listeners);
745
    /* BEFORE RELEASE NEXT DOCUMENT #2097: Changed "Listeners" to lower case. */
746
    xmlNewTextChild(srcnode, NULL, XMLSTR(client->mode == OMODE_LEGACY ? "Listeners" : "listeners"), XMLSTR(buf));
747

748
    admin_add_listeners_to_mount(source, srcnode, client->mode);
749

750
    admin_send_response(doc, client, response,
751 752
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
753 754
}

Marvin Scholz's avatar
Marvin Scholz committed
755
static void command_buildm3u(client_t *client, const char *mount)
756
{
757 758
    const char *username = NULL;
    const char *password = NULL;
759
    ice_config_t *config;
760
    ssize_t ret;
761 762 763 764

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

Marvin Scholz's avatar
Marvin Scholz committed
765 766
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
767
                                 "audio/x-mpegurl", NULL,
768
                                 NULL, NULL, client);
769

Marvin Scholz's avatar
Marvin Scholz committed
770 771
    if (ret == -1 || ret >= (PER_CLIENT_REFBUF_SIZE - 512)) {
        /* we want at least 512 Byte left for data */
772
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
773
        client_send_error(client, 500, 0, "Header generation failed.");
774 775 776 777
        return;
    }


Karl Heyes's avatar
Karl Heyes committed
778
    config = config_get_config();
779
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
780
        "Content-Disposition: attachment; filename=listen.m3u\r\n\r\n"
781 782 783
        "http://%s:%s@%s:%d%s\r\n",
        username,
        password,
Karl Heyes's avatar
Karl Heyes committed
784 785
        config->hostname,
        config->port,
786
        mount
787
    );
Karl Heyes's avatar
Karl Heyes committed
788
    config_release_config();
789

790
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
791 792
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
793
}
794

Marvin Scholz's avatar
Marvin Scholz committed
795 796
xmlNodePtr admin_add_role_to_authentication(auth_t *auth, xmlNodePtr parent)
{
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
    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;
}
816

Marvin Scholz's avatar
Marvin Scholz committed
817 818
static void command_manageauth(client_t *client, int response)
{
819
    xmlDocPtr doc;
820
    xmlNodePtr node, rolenode, usersnode, msgnode;
821 822
    const char *action = NULL;
    const char *username = NULL;
823
    const char *idstring = NULL;
824 825
    char *message = NULL;
    int ret = AUTH_OK;
826 827 828 829
    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
830
    auth_t *auth;
831

Marvin Scholz's avatar
Marvin Scholz committed
832
    do {
833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855
        /* 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";