admin.c 44.8 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).
11
 * Copyright 2012-2018, 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 27
#include "admin.h"
#include "compat.h"
28
#include "cfgfile.h"
29 30 31 32 33 34
#include "connection.h"
#include "refbuf.h"
#include "client.h"
#include "source.h"
#include "global.h"
#include "stats.h"
35
#include "xslt.h"
36
#include "fserve.h"
37
#include "errors.h"
38 39 40 41

#include "format.h"

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

#define CATMODULE "admin"

50 51
#define ADMIN_MAX_COMMAND_TABLES        8

52 53 54
/* Helper macros */
#define COMMAND_REQUIRE(client,name,var)                                \
    do {                                                                \
55
        (var) = httpp_get_param((client)->parser, (name));        \
56
        if((var) == NULL) {                                             \
57
            client_send_error_by_id(client, ICECAST_ERROR_ADMIN_MISSING_PARAMETER); \
58 59 60 61 62
            return;                                                     \
        }                                                               \
    } while(0);

#define COMMAND_OPTIONAL(client,name,var) \
63
(var) = httpp_get_param((client)->parser, (name))
64

Marvin Scholz's avatar
Marvin Scholz committed
65
#define FALLBACK_RAW_REQUEST                "fallbacks"
66
#define FALLBACK_HTML_REQUEST               "fallbacks.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
67 68
#define SHOUTCAST_METADATA_REQUEST          "admin.cgi"
#define METADATA_RAW_REQUEST                "metadata"
69
#define METADATA_HTML_REQUEST               "metadata.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
70
#define LISTCLIENTS_RAW_REQUEST             "listclients"
71
#define LISTCLIENTS_HTML_REQUEST            "listclients.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
72
#define STATS_RAW_REQUEST                   "stats"
73
#define STATS_HTML_REQUEST                  "stats.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
74
#define QUEUE_RELOAD_RAW_REQUEST            "reloadconfig"
75
#define QUEUE_RELOAD_HTML_REQUEST           "reloadconfig.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
76
#define LISTMOUNTS_RAW_REQUEST              "listmounts"
77
#define LISTMOUNTS_HTML_REQUEST             "listmounts.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
78
#define STREAMLIST_RAW_REQUEST              "streamlist"
79
#define STREAMLIST_HTML_REQUEST             "streamlist.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
80 81
#define STREAMLIST_PLAINTEXT_REQUEST        "streamlist.txt"
#define MOVECLIENTS_RAW_REQUEST             "moveclients"
82
#define MOVECLIENTS_HTML_REQUEST            "moveclients.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
83
#define KILLCLIENT_RAW_REQUEST              "killclient"
84
#define KILLCLIENT_HTML_REQUEST             "killclient.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
85
#define KILLSOURCE_RAW_REQUEST              "killsource"
86
#define KILLSOURCE_HTML_REQUEST             "killsource.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
87 88
#define ADMIN_XSL_RESPONSE                  "response.xsl"
#define MANAGEAUTH_RAW_REQUEST              "manageauth"
89
#define MANAGEAUTH_HTML_REQUEST             "manageauth.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
90
#define UPDATEMETADATA_RAW_REQUEST          "updatemetadata"
91
#define UPDATEMETADATA_HTML_REQUEST         "updatemetadata.xsl"
Marvin Scholz's avatar
Marvin Scholz committed
92
#define DEFAULT_RAW_REQUEST                 ""
93
#define DEFAULT_HTML_REQUEST                ""
Marvin Scholz's avatar
Marvin Scholz committed
94
#define BUILDM3U_RAW_REQUEST                "buildm3u"
95

96 97 98 99 100 101
typedef struct {
    const char *prefix;
    size_t length;
    const admin_command_handler_t *handlers;
} admin_command_table_t;

102 103 104 105 106 107 108 109 110 111 112 113 114
static void command_fallback            (client_t *client, source_t *source, admin_format_t response);
static void command_metadata            (client_t *client, source_t *source, admin_format_t response);
static void command_shoutcast_metadata  (client_t *client, source_t *source, admin_format_t response);
static void command_show_listeners      (client_t *client, source_t *source, admin_format_t response);
static void command_stats               (client_t *client, source_t *source, admin_format_t response);
static void command_queue_reload        (client_t *client, source_t *source, admin_format_t response);
static void command_list_mounts         (client_t *client, source_t *source, admin_format_t response);
static void command_move_clients        (client_t *client, source_t *source, admin_format_t response);
static void command_kill_client         (client_t *client, source_t *source, admin_format_t response);
static void command_kill_source         (client_t *client, source_t *source, admin_format_t response);
static void command_manageauth          (client_t *client, source_t *source, admin_format_t response);
static void command_updatemetadata      (client_t *client, source_t *source, admin_format_t response);
static void command_buildm3u            (client_t *client, source_t *source, admin_format_t response);
115 116

static const admin_command_handler_t handlers[] = {
117 118 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
    { "*",                                  ADMINTYPE_GENERAL,      ADMIN_FORMAT_HTML,          NULL, NULL}, /* for ACL framework */
    { FALLBACK_RAW_REQUEST,                 ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,           command_fallback, NULL},
    { FALLBACK_HTML_REQUEST,                ADMINTYPE_MOUNT,        ADMIN_FORMAT_HTML,          command_fallback, NULL},
    { METADATA_RAW_REQUEST,                 ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,           command_metadata, NULL},
    { METADATA_HTML_REQUEST,                ADMINTYPE_MOUNT,        ADMIN_FORMAT_HTML,          command_metadata, NULL},
    { SHOUTCAST_METADATA_REQUEST,           ADMINTYPE_MOUNT,        ADMIN_FORMAT_HTML,          command_shoutcast_metadata, NULL},
    { LISTCLIENTS_RAW_REQUEST,              ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,           command_show_listeners, NULL},
    { LISTCLIENTS_HTML_REQUEST,             ADMINTYPE_MOUNT,        ADMIN_FORMAT_HTML,          command_show_listeners, NULL},
    { STATS_RAW_REQUEST,                    ADMINTYPE_HYBRID,       ADMIN_FORMAT_RAW,           command_stats, NULL},
    { STATS_HTML_REQUEST,                   ADMINTYPE_HYBRID,       ADMIN_FORMAT_HTML,          command_stats, NULL},
    { "stats.xml",                          ADMINTYPE_HYBRID,       ADMIN_FORMAT_RAW,           command_stats, NULL},
    { QUEUE_RELOAD_RAW_REQUEST,             ADMINTYPE_GENERAL,      ADMIN_FORMAT_RAW,           command_queue_reload, NULL},
    { QUEUE_RELOAD_HTML_REQUEST,            ADMINTYPE_GENERAL,      ADMIN_FORMAT_HTML,          command_queue_reload, NULL},
    { LISTMOUNTS_RAW_REQUEST,               ADMINTYPE_GENERAL,      ADMIN_FORMAT_RAW,           command_list_mounts, NULL},
    { LISTMOUNTS_HTML_REQUEST,              ADMINTYPE_GENERAL,      ADMIN_FORMAT_HTML,          command_list_mounts, NULL},
    { STREAMLIST_RAW_REQUEST,               ADMINTYPE_GENERAL,      ADMIN_FORMAT_RAW,           command_list_mounts, NULL},
    { STREAMLIST_PLAINTEXT_REQUEST,         ADMINTYPE_GENERAL,      ADMIN_FORMAT_PLAINTEXT,     command_list_mounts, NULL},
    { STREAMLIST_HTML_REQUEST,              ADMINTYPE_GENERAL,      ADMIN_FORMAT_HTML,          command_list_mounts, NULL},
    { MOVECLIENTS_RAW_REQUEST,              ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,           command_move_clients, NULL},
    { MOVECLIENTS_HTML_REQUEST,             ADMINTYPE_HYBRID,       ADMIN_FORMAT_HTML,          command_move_clients, NULL},
    { KILLCLIENT_RAW_REQUEST,               ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,           command_kill_client, NULL},
    { KILLCLIENT_HTML_REQUEST,              ADMINTYPE_MOUNT,        ADMIN_FORMAT_HTML,          command_kill_client, NULL},
    { KILLSOURCE_RAW_REQUEST,               ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,           command_kill_source, NULL},
    { KILLSOURCE_HTML_REQUEST,              ADMINTYPE_MOUNT,        ADMIN_FORMAT_HTML,          command_kill_source, NULL},
    { MANAGEAUTH_RAW_REQUEST,               ADMINTYPE_GENERAL,      ADMIN_FORMAT_RAW,           command_manageauth, NULL},
    { MANAGEAUTH_HTML_REQUEST,              ADMINTYPE_GENERAL,      ADMIN_FORMAT_HTML,          command_manageauth, NULL},
    { UPDATEMETADATA_RAW_REQUEST,           ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,           command_updatemetadata, NULL},
    { UPDATEMETADATA_HTML_REQUEST,          ADMINTYPE_MOUNT,        ADMIN_FORMAT_HTML,          command_updatemetadata, NULL},
    { BUILDM3U_RAW_REQUEST,                 ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,           command_buildm3u, NULL},
    { DEFAULT_HTML_REQUEST,                 ADMINTYPE_HYBRID,       ADMIN_FORMAT_HTML,          command_stats, NULL},
    { DEFAULT_RAW_REQUEST,                  ADMINTYPE_HYBRID,       ADMIN_FORMAT_HTML,          command_stats, NULL}
Philipp Schafft's avatar
Philipp Schafft committed
148 149
};

150
static admin_command_table_t command_tables[ADMIN_MAX_COMMAND_TABLES] = {
151 152 153
    {.prefix = NULL, .length = (sizeof(handlers)/sizeof(*handlers)), .handlers = handlers},
};

154 155 156 157 158 159 160 161 162 163 164
static inline int __is_command_table_valid(const admin_command_table_t * table)
{
    if (table == NULL)
        return 0;

    if (table->length == 0 || table->handlers == NULL)
        return 0;

    return 1;
}

165 166 167 168 169 170 171
static inline const admin_command_table_t * admin_get_table(admin_command_id_t command)
{
    size_t t = (command & 0x00FF0000) >> 16;

    if (t >= (sizeof(command_tables)/sizeof(*command_tables)))
        return NULL;

172 173 174
    if (!__is_command_table_valid(&(command_tables[t])))
        return NULL;

175 176 177 178 179 180 181 182 183 184 185 186 187
    return &(command_tables[t]);
}

static inline const admin_command_table_t * admin_get_table_by_prefix(const char *command)
{
    const char *end;
    size_t i;
    size_t len;

    end = strchr(command, '/');

    if (end == NULL) {
        for (i = 0; i < (sizeof(command_tables)/sizeof(*command_tables)); i++)
188
            if (command_tables[i].prefix == NULL && __is_command_table_valid(&(command_tables[i])))
189 190 191 192 193 194 195 196
                return &(command_tables[i]);

        return NULL;
    }

    len = end - command;

    for (i = 0; i < (sizeof(command_tables)/sizeof(*command_tables)); i++) {
197 198 199
        if (!__is_command_table_valid(&(command_tables[i])))
            continue;

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
        if (command_tables[i].prefix != NULL && strlen(command_tables[i].prefix) == len && strncmp(command_tables[i].prefix, command, len) == 0) {
            return &(command_tables[i]);
        }
    }

    return NULL;
}

static inline admin_command_id_t admin_get_command_by_table_and_index(const admin_command_table_t *table, size_t index)
{
    size_t t = table - command_tables;

    if (t >= (sizeof(command_tables)/sizeof(*command_tables)))
        return ADMIN_COMMAND_ERROR;

    if (index > 0x0FFFF)
        return ADMIN_COMMAND_ERROR;

218 219 220
    if (!__is_command_table_valid(table))
        return ADMIN_COMMAND_ERROR;

221 222 223 224 225 226 227
    return (t << 16) | index;
}

static inline size_t admin_get_index_by_command(admin_command_id_t command)
{
    return command & 0x0FFFF;
}
228

229
admin_command_id_t admin_get_command(const char *command)
Marvin Scholz's avatar
Marvin Scholz committed
230
{
Philipp Schafft's avatar
Philipp Schafft committed
231
    size_t i;
232 233
    const admin_command_table_t *table = admin_get_table_by_prefix(command);
    const char *suffix;
Philipp Schafft's avatar
Philipp Schafft committed
234

235
    if (table == NULL)
236
        return ADMIN_COMMAND_ERROR;
237 238 239 240 241 242 243 244 245

    suffix = strchr(command, '/');
    if (suffix != NULL) {
        suffix++;
    } else {
        suffix = command;
    }

    for (i = 0; i < table->length; i++)
246
        if (resourcematch_match(table->handlers[i].route, suffix, NULL) == RESOURCEMATCH_MATCH)
247
            return admin_get_command_by_table_and_index(table, i);
Philipp Schafft's avatar
Philipp Schafft committed
248

249
    return ADMIN_COMMAND_ERROR;
Philipp Schafft's avatar
Philipp Schafft committed
250 251
}

252 253
/* Get the command handler for command or NULL
 */
254
const admin_command_handler_t* admin_get_handler(admin_command_id_t command)
Marvin Scholz's avatar
Marvin Scholz committed
255
{
256 257
    const admin_command_table_t *table = admin_get_table(command);
    size_t index = admin_get_index_by_command(command);
Philipp Schafft's avatar
Philipp Schafft committed
258

259 260 261 262 263 264 265
    if (table == NULL)
        return NULL;

    if (index >= table->length)
        return NULL;

    return &(table->handlers[index]);
266 267 268 269 270
}

/* Get the command type for command
 * If the command is invalid, ADMINTYPE_ERROR is returned.
 */
271
int admin_get_command_type(admin_command_id_t command)
272 273
{
    const admin_command_handler_t* handler = admin_get_handler(command);
Philipp Schafft's avatar
Philipp Schafft committed
274

275 276
    if (handler != NULL)
        return handler->type;
Philipp Schafft's avatar
Philipp Schafft committed
277 278

    return ADMINTYPE_ERROR;
279 280
}

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
int admin_command_table_register(const char *prefix, size_t handlers_length, const admin_command_handler_t *handlers)
{
    size_t i;

    if (prefix == NULL || handlers_length == 0 || handlers == NULL)
        return -1;

    for (i = 0; i < (sizeof(command_tables)/sizeof(*command_tables)); i++) {
        if (__is_command_table_valid(&(command_tables[i])))
            continue;

        command_tables[i].prefix    = prefix;
        command_tables[i].length    = handlers_length;
        command_tables[i].handlers  = handlers;

        return 0;
    }

    return -1;
}

int admin_command_table_unregister(const char *prefix)
{
    size_t i;

    for (i = 0; i < (sizeof(command_tables)/sizeof(*command_tables)); i++) {
        if (command_tables[i].prefix != NULL && strcmp(command_tables[i].prefix, prefix) == 0) {
            memset(&(command_tables[i]), 0, sizeof(command_tables[i]));
            return 0;
        }
    }

    return -1;
}

316 317 318 319 320 321 322 323 324 325 326
/* build an XML root node including some common tags */
xmlNodePtr admin_build_rootnode(xmlDocPtr doc, const char *name)
{
    xmlNodePtr rootnode = xmlNewDocNode(doc, NULL, XMLSTR(name), NULL);
    xmlNodePtr modules = module_container_get_modulelist_as_xml(global.modulecontainer);

    xmlAddChild(rootnode, modules);

    return rootnode;
}

327 328 329
/* 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
330
xmlDocPtr admin_build_sourcelist(const char *mount)
331 332 333 334 335 336 337 338
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

339
    doc = xmlNewDoc (XMLSTR("1.0"));
340
    xmlnode = admin_build_rootnode(doc, "icestats");
341
    xmlDocSetRootElement(doc, xmlnode);
342

343
    if (mount) {
344
        xmlNewTextChild (xmlnode, NULL, XMLSTR("current_source"), XMLSTR(mount));
345 346 347 348 349
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
350 351 352 353 354 355
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

356
        if (source->running || source->on_demand)
357
        {
358 359
            ice_config_t *config;
            mount_proxy *mountinfo;
Philipp Schafft's avatar
Philipp Schafft committed
360
            acl_t *acl = NULL;
361

362 363
            srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
            xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
364

365
            xmlNewTextChild(srcnode, NULL, XMLSTR("fallback"),
366
                    (source->fallback_mount != NULL)?
367
                    XMLSTR(source->fallback_mount):XMLSTR(""));
368
            snprintf(buf, sizeof(buf), "%lu", source->listeners);
369
            xmlNewTextChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
370

Karl Heyes's avatar
Karl Heyes committed
371
            config = config_get_config();
Marvin Scholz's avatar
Marvin Scholz committed
372
            mountinfo = config_find_mount(config, source->mount, MOUNT_TYPE_NORMAL);
Philipp Schafft's avatar
Philipp Schafft committed
373
            if (mountinfo)
374
                acl = auth_stack_get_anonymous_acl(mountinfo->authstack, httpp_req_get);
Philipp Schafft's avatar
Philipp Schafft committed
375
            if (!acl)
376
                acl = auth_stack_get_anonymous_acl(config->authstack, httpp_req_get);
Philipp Schafft's avatar
Philipp Schafft committed
377
            if (acl && acl_test_web(acl) == ACL_POLICY_DENY) {
378
                xmlNewTextChild(srcnode, NULL, XMLSTR("authenticator"), XMLSTR("(dummy)"));
379
            }
Philipp Schafft's avatar
Philipp Schafft committed
380
            acl_release(acl);
381 382
            config_release_config();

Marvin Scholz's avatar
Marvin Scholz committed
383 384 385 386
            if (source->running) {
                if (source->client) {
                    snprintf(buf, sizeof(buf), "%lu",
                        (unsigned long)(now - source->con->con_time));
387
                    xmlNewTextChild(srcnode, NULL, XMLSTR("Connected"), XMLSTR(buf));
Karl Heyes's avatar
Karl Heyes committed
388
                }
389
                xmlNewTextChild(srcnode, NULL, XMLSTR("content-type"),
Marvin Scholz's avatar
Marvin Scholz committed
390
                    XMLSTR(source->format->contenttype));
391
            }
392
        }
393 394 395 396 397
        node = avl_get_next(node);
    }
    return(doc);
}

398 399 400 401
void admin_send_response(xmlDocPtr       doc,
                         client_t       *client,
                         admin_format_t  response,
                         const char     *xslt_template)
402
{
403
    if (response == ADMIN_FORMAT_RAW) {
404 405
        xmlChar *buff = NULL;
        int len = 0;
406 407 408
        size_t buf_len;
        ssize_t ret;

409
        xmlDocDumpMemory(doc, &buff, &len);
410 411 412 413

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

415 416
        client_set_queue(client, NULL);
        client->refbuf = refbuf_new(buf_len);
417

418 419 420
        ret = util_http_build_header(client->refbuf->data, buf_len, 0,
                                     0, 200, NULL,
                                     "text/xml", "utf-8",
421
                                     NULL, NULL, client);
422
        if (ret < 0) {
423
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
424
            client_send_error_by_id(client, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
425 426
            xmlFree(buff);
            return;
427
        } else if (buf_len < (size_t)(len + ret + 64)) {
428 429 430 431 432 433 434 435 436 437
            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",
438
                                             NULL, NULL, client);
439 440
                if (ret == -1) {
                    ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
441
                    client_send_error_by_id(client, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
442 443 444 445 446
                    xmlFree(buff);
                    return;
                }
            } else {
                ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
447
                client_send_error_by_id(client, ICECAST_ERROR_GEN_BUFFER_REALLOC);
448 449
                xmlFree(buff);
                return;
450
            }
451
        }
452

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

        client->refbuf->len = ret;
457 458 459
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
460
    }
461
    if (response == ADMIN_FORMAT_HTML) {
462
        char *fullpath_xslt_template;
463
        size_t fullpath_xslt_template_len;
464 465
        ice_config_t *config = config_get_config();

466
        fullpath_xslt_template_len = strlen(config->adminroot_dir) + strlen(xslt_template) + strlen(PATH_SEPARATOR) + 1;
467 468
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
469
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
470
        config_release_config();
471

472
        ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template);
473
        xslt_transform(doc, fullpath_xslt_template, client, 200, NULL);
474 475 476
        free(fullpath_xslt_template);
    }
}
477

478
void admin_handle_request(client_t *client, const char *uri)
479
{
480 481 482
    const char *mount;
    const admin_command_handler_t* handler;
    source_t *source = NULL;
483
    admin_format_t format;
484

485
    ICECAST_LOG_DEBUG("Got admin request '%s'", uri);
486

487
    handler = admin_get_handler(client->admin_command);
488

489
    /* Check if admin command is valid */
490
    if (handler == NULL || (handler->function == NULL && handler->function_with_parameters == NULL)) {
491 492
        ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %H",
                uri);
493
        client_send_error_by_id(client, ICECAST_ERROR_ADMIN_UNRECOGNISED_COMMAND);
494 495 496
        return;
    }

497
    /* Check ACL */
Philipp Schafft's avatar
Philipp Schafft committed
498
    if (acl_test_admin(client->acl, client->admin_command) != ACL_POLICY_ALLOW) {
499 500

        /* ACL disallows, check exceptions */
501
        if ((handler->function == command_metadata && handler->format == ADMIN_FORMAT_RAW) &&
Philipp Schafft's avatar
Philipp Schafft committed
502 503
            (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
504 505
            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
506
        } else {
507
            ICECAST_LOG_DEBUG("Client needs to authenticate.");
508
            client_send_error_by_id(client, ICECAST_ERROR_GEN_CLIENT_NEEDS_TO_AUTHENTICATE);
509 510
            return;
        }
511 512
    }

513
    COMMAND_OPTIONAL(client, "mount", mount);
514

515
    /* Find mountpoint source */
516
    if(mount != NULL) {
517

Philipp Schafft's avatar
Philipp Schafft committed
518
        /* This is a mount request, handle it as such */
519
        avl_tree_rlock(global.source_tree);
520
        source = source_find_mount_raw(mount);
521

522
        /* No Source found */
Marvin Scholz's avatar
Marvin Scholz committed
523
        if (source == NULL) {
524
            avl_tree_unlock(global.source_tree);
525 526
            ICECAST_LOG_WARN("Admin command \"%H\" on non-existent source \"%H\"",
                    uri, mount);
527
            client_send_error_by_id(client, ICECAST_ERROR_ADMIN_SOURCE_DOES_NOT_EXIST);
528 529 530
            return;
        } /* No Source running */
        else if (source->running == 0 && source->on_demand == 0) {
531
            avl_tree_unlock(global.source_tree);
532 533
            ICECAST_LOG_INFO("Received admin command \"%H\" on unavailable mount \"%H\"",
                    uri, mount);
534
            client_send_error_by_id(client, ICECAST_ERROR_ADMIN_SOURCE_IS_NOT_AVAILABLE);
535
            return;
536
        }
537 538
        ICECAST_LOG_INFO("Received admin command %H on mount '%s'",
                    uri, mount);
539 540
    }

541
    if (handler->type == ADMINTYPE_MOUNT && !source) {
542
        client_send_error_by_id(client, ICECAST_ERROR_ADMIN_MISSING_PARAMETER);
Marvin Scholz's avatar
Marvin Scholz committed
543
        return;
544 545
    }

546 547 548 549 550 551
    if (handler->format == ADMIN_FORMAT_AUTO) {
        format = client_get_admin_format_by_content_negotiation(client);
    } else {
        format = handler->format;
    }

552 553
    switch (client->parser->req_type) {
        case httpp_req_get:
554
        case httpp_req_post:
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
            if (handler->function) {
                handler->function(client, source, format);
            } else {
                resourcematch_extract_t *extract = NULL;
                const char *suffix = strchr(uri, '/');

                if (!suffix) {
                    client_send_error_by_id(client, ICECAST_ERROR_ADMIN_UNRECOGNISED_COMMAND);
                } else {
                    suffix++;

                    if (resourcematch_match(handler->route, suffix, &extract) == RESOURCEMATCH_MATCH) {
                        handler->function_with_parameters(client, source, format, extract);
                        resourcematch_extract_free(extract);
                    } else {
                        client_send_error_by_id(client, ICECAST_ERROR_ADMIN_UNRECOGNISED_COMMAND);
                    }
                }
            }
574 575 576 577 578 579 580 581 582 583
        break;
        case httpp_req_options:
            client_send_204(client);
        break;
        default:
            ICECAST_LOG_ERROR("Wrong request type from client");
            client_send_error_by_id(client, ICECAST_ERROR_CON_UNKNOWN_REQUEST);
        break;
    }

584 585
    if (source) {
        avl_tree_unlock(global.source_tree);
586
    }
587
    return;
588 589
}

590
static void html_success(client_t *client, char *message)
591
{
592 593
    ssize_t ret;

Marvin Scholz's avatar
Marvin Scholz committed
594 595
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
596
                                 "text/html", "utf-8",
597
                                 "", NULL, client);
598 599 600

    if (ret == -1 || ret >= PER_CLIENT_REFBUF_SIZE) {
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
601
        client_send_error_by_id(client, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
602 603 604
        return;
    }

605
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
606 607
        "<html><head><title>Admin request successful</title></head>"
        "<body><p>%s</p></body></html>", message);
608

609
    client->respcode = 200;
Marvin Scholz's avatar
Marvin Scholz committed
610 611
    client->refbuf->len = strlen(client->refbuf->data);
    fserve_add_client(client, NULL);
612 613
}

614

Marvin Scholz's avatar
Marvin Scholz committed
615 616
static void command_move_clients(client_t   *client,
                                 source_t   *source,
617
                                 admin_format_t response)
618
{
619
    const char *dest_source;
620
    source_t *dest;
621 622 623 624 625
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

626
    ICECAST_LOG_DEBUG("Doing optional check");
627
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
628 629
        parameters_passed = 1;
    }
630
    ICECAST_LOG_DEBUG("Done optional check (%d)", parameters_passed);
631 632
    if (!parameters_passed) {
        doc = admin_build_sourcelist(source->mount);
633
        admin_send_response(doc, client, response,
634
             MOVECLIENTS_HTML_REQUEST);
635 636 637
        xmlFreeDoc(doc);
        return;
    }
638

Marvin Scholz's avatar
Marvin Scholz committed
639
    dest = source_find_mount(dest_source);
640

Marvin Scholz's avatar
Marvin Scholz committed
641
    if (dest == NULL) {
642
        client_send_error_by_id(client, ICECAST_ERROR_ADMIN_NO_SUCH_DESTINATION);
643 644 645
        return;
    }

Marvin Scholz's avatar
Marvin Scholz committed
646
    if (strcmp(dest->mount, source->mount) == 0) {
647
        client_send_error_by_id(client, ICECAST_ERROR_ADMIN_SUPPLIED_MOUNTPOINTS_ARE_IDENTICAL);
648 649 650
        return;
    }

Marvin Scholz's avatar
Marvin Scholz committed
651
    if (dest->running == 0 && dest->on_demand == 0) {
652
        client_send_error_by_id(client, ICECAST_ERROR_ADMIN_DEST_NOT_RUNNING);
653 654 655
        return;
    }

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

Marvin Scholz's avatar
Marvin Scholz committed
658
    doc = xmlNewDoc(XMLSTR("1.0"));
659
    node = admin_build_rootnode(doc, "iceresponse");
660 661
    xmlDocSetRootElement(doc, node);

Marvin Scholz's avatar
Marvin Scholz committed
662
    source_move_clients(source, dest);
663

Marvin Scholz's avatar
Marvin Scholz committed
664
    snprintf(buf, sizeof(buf), "Clients moved from %s to %s",
665
        source->mount, dest_source);
666 667
    xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
    xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
668

Marvin Scholz's avatar
Marvin Scholz committed
669
    admin_send_response(doc, client, response, ADMIN_XSL_RESPONSE);
670
    xmlFreeDoc(doc);
671 672
}

Marvin Scholz's avatar
Marvin Scholz committed
673 674 675 676 677
static inline xmlNodePtr __add_listener(client_t        *client,
                                        xmlNodePtr      parent,
                                        time_t          now,
                                        operation_mode  mode)
{
678 679 680 681
    const char *tmp;
    xmlNodePtr node;
    char buf[22];

Philipp Schafft's avatar
Philipp Schafft committed
682
    /* TODO: kh has support for a child node "lag". We should add that.
683 684
     * 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
685
     */
686 687 688 689 690 691 692

    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
693
    xmlSetProp(node, XMLSTR("id"), XMLSTR(buf));
694
    xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "ID" : "id"), XMLSTR(buf));
695

696
    xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "IP" : "ip"), XMLSTR(client->con->ip));
697 698 699

    tmp = httpp_getvar(client->parser, "user-agent");
    if (tmp)
700
        xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "UserAgent" : "useragent"), XMLSTR(tmp));
701 702 703

    tmp = httpp_getvar(client->parser, "referer");
    if (tmp)
704
        xmlNewTextChild(node, NULL, XMLSTR("referer"), XMLSTR(tmp));
705

706 707 708 709
    tmp = httpp_getvar(client->parser, "host");
    if (tmp)
        xmlNewTextChild(node, NULL, XMLSTR("host"), XMLSTR(tmp));

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

    if (client->username)
714
        xmlNewTextChild(node, NULL, XMLSTR("username"), XMLSTR(client->username));
715

Philipp Schafft's avatar
Philipp Schafft committed
716
    if (client->role)
717
        xmlNewTextChild(node, NULL, XMLSTR("role"), XMLSTR(client->role));
Philipp Schafft's avatar
Philipp Schafft committed
718

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

721 722 723 724 725 726 727 728 729
    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;
    }

730 731 732
    return node;
}

Marvin Scholz's avatar
Marvin Scholz committed
733 734 735 736
void admin_add_listeners_to_mount(source_t          *source,
                                  xmlNodePtr        parent,
                                  operation_mode    mode)
{
737 738 739 740 741 742
    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) {
743
        __add_listener((client_t *)client_node->key, parent, now, mode);
744 745 746 747 748
        client_node = avl_get_next(client_node);
    }
    avl_tree_unlock(source->client_tree);
}

Marvin Scholz's avatar
Marvin Scholz committed
749 750
static void command_show_listeners(client_t *client,
                                   source_t *source,
751
                                   admin_format_t response)
752
{
753
    xmlDocPtr doc;
754
    xmlNodePtr node, srcnode;
755
    char buf[22];
756

757
    doc = xmlNewDoc(XMLSTR("1.0"));
758
    node = admin_build_rootnode(doc, "icestats");
759 760
    srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
    xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
761
    xmlDocSetRootElement(doc, node);
762

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

768
    admin_add_listeners_to_mount(source, srcnode, client->mode);
769

770
    admin_send_response(doc, client, response,
771
        LISTCLIENTS_HTML_REQUEST);
772
    xmlFreeDoc(doc);
773 774
}

775
static void command_buildm3u(client_t *client, source_t *source, admin_format_t format)
776
{
777
    const char *mount = source->mount;
778 779
    const char *username = NULL;
    const char *password = NULL;
780
    ssize_t ret;
781 782 783 784

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

Marvin Scholz's avatar
Marvin Scholz committed
785 786
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
787
                                 "audio/x-mpegurl", NULL,
788
                                 NULL, NULL, client);
789

Marvin Scholz's avatar
Marvin Scholz committed
790 791
    if (ret == -1 || ret >= (PER_CLIENT_REFBUF_SIZE - 512)) {
        /* we want at least 512 Byte left for data */
792
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
793
        client_send_error_by_id(client, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
794 795 796 797
        return;
    }


798
    client_get_baseurl(client, NULL, client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret, username, password, "Content-Disposition: attachment; filename=listen.m3u\r\n\r\n", mount, "\r\n");
799

800
    client->respcode = 200;
801 802
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
803
}
804

Marvin Scholz's avatar
Marvin Scholz committed
805 806
xmlNodePtr admin_add_role_to_authentication(auth_t *auth, xmlNodePtr parent)
{
807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825
    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;
}
826

827
static void command_manageauth(client_t *client, source_t *source, admin_format_t response)
Marvin Scholz's avatar
Marvin Scholz committed
828
{
829
    xmlDocPtr doc;
830
    xmlNodePtr node, rolenode, usersnode, msgnode;
831 832
    const char *action = NULL;
    const char *username = NULL;
833
    const char *idstring = NULL;
834 835
    char *message = NULL;
    int ret = AUTH_OK;
836
    int error_id = ICECAST_ERROR_ADMIN_missing_parameter;
837 838
    long unsigned int id;
    ice_config_t *config = config_get_config();
Philipp Schafft's avatar
Philipp Schafft committed
839
    auth_t *auth;
840

Marvin Scholz's avatar
Marvin Scholz committed
841
    do {
842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
        /* 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);
863
            error_id = ICECAST_ERROR_ADMIN_ROLEMGN_ROLE_NOT_FOUND;
864
            break;
865
        }
Philipp Schafft's avatar
Philipp Schafft committed
866

867
        COMMAND_OPTIONAL(client, "action", action);
868
        COMMAND_OPTIONAL(client, "username", username);
869 870

        if (action == NULL)
871
            action = "list";
872

Marvin Scholz's avatar
Marvin Scholz committed
873
        if (!strcmp(action, "add")) {
874
            const char *password = NULL;
875
            COMMAND_OPTIONAL(client, "password", password);
876

877 878
            if (username == NULL || password == NULL) {
                ICECAST_LOG_WARN("manage auth request add for %lu but no user/pass", id);
879 880
                break;
            }
881 882

            if (!auth->adduser) {
883
                error_id = ICECAST_ERROR_ADMIN_ROLEMGN_ADD_NOSYS;
884 885 886
                break;
            }

Philipp Schafft's avatar
Philipp Schafft committed
887
            ret = auth->adduser(auth, username, password);
888 889
            if (ret == AUTH_FAILED) {
                message = strdup("User add failed - check the icecast error log");
890
            } else if (ret == AUTH_USERADDED) {
891
                message = strdup("User added");
892
            } else if (ret == AUTH_USEREXISTS) {
893 894 895
                message = strdup("User already exists - not added");
            }
        }
Marvin Scholz's avatar
Marvin Scholz committed
896
        if (!strcmp(action, "delete")) {
897 898 899 900 901 902
            if (username == NULL) {
                ICECAST_LOG_WARN("manage auth request delete for %lu but no username", id);
                break;
            }

            if (!auth->deleteuser) {
903
                error_id = ICECAST_ERROR_ADMIN_ROLEMGN_DELETE_NOSYS;
904 905
                break;
            }
906

Philipp Schafft's avatar
Philipp Schafft committed
907
            ret = auth->deleteuser(auth, username);
908 909
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
910
            } else if (ret == AUTH_USERDELETED) {
911 912 913 914
                message = strdup("User deleted");
            }
        }

915
        doc = xmlNewDoc(XMLSTR("1.0"));
916
        node = admin_build_rootnode(doc, "icestats");
917

918
        rolenode = admin_add_role_to_authentication(auth, node);
919

920
        if (message) {
921
            msgnode = admin_build_rootnode(doc, "iceresponse");
922
            xmlNewTextChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
923
        }
924

925
        xmlDocSetRootElement(doc, node);
926

927 928 929 930
        if (auth && auth->listuser) {
            usersnode = xmlNewChild(rolenode, NULL, XMLSTR("users"), NULL);
            auth->listuser(auth, usersnode);
        }
931

932 933
        config_release_config();
        auth_release(auth);
934

935
        admin_send_response(doc, client, response,
936
            MANAGEAUTH_HTML_REQUEST);
Marvin Scholz's avatar
Marvin Scholz committed
937
        free(message);
938 939 940 941
        xmlFreeDoc(doc);
        return;
    } while (0);

942 943
    config_release_config();
    auth_release(auth);
944
    client_send_error_by_id(client, error_id);
945 946
}

Marvin Scholz's avatar
Marvin Scholz committed
947 948
static void command_kill_source(client_t *client,
                                source_t *source,
949
                                admin_format_t response)
950
{
951 952 953
    xmlDocPtr doc;
    xmlNodePtr node;

954
    doc = xmlNewDoc (XMLSTR("1.0"));
955
    node = admin_build_rootnode(doc, "iceresponse");
956 957
    xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR("Source Removed"));
    xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
958 959
    xmlDocSetRootElement(doc, node);

960 961
    source->running = 0;

962
    admin_send_response(doc, client, response,
963 964
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
965 966
}

Marvin Scholz's avatar
Marvin Scholz committed
967 968
static void command_kill_client(client_t *client,
                                source_t *source,
969
                                admin_format_t response)
970
{
971
    const char *idtext;
972 973
    int id;
    client_t *listener;
974 975 976
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
977 978 979 980 981 982 983

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

Marvin Scholz's avatar
Marvin Scholz committed
984
    doc = xmlNewDoc(XMLSTR("1.0"));
985
    node = admin_build_rootnode(doc, "iceresponse");
986
    xmlDocSetRootElement(doc, node);
987
    ICECAST_LOG_DEBUG("Response is %d", response);
988

989
    if(listener != NULL) {
990
        ICECAST_LOG_INFO("Admin request: client %d removed", id);
991 992 993 994 995

        /* This tags it for removal on the next iteration of the main source
         * loop
         */
        listener->con->error = 1;
996 997
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
998 999
        xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
1000 1001
    }
    else {
1002 1003
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d not found", id);
1004 1005
        xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
1006
    }
1007
    admin_send_response(doc, client, response,
1008 1009
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
1010 1011
}

Marvin Scholz's avatar
Marvin Scholz committed
1012 1013
static void command_fallback(client_t *client,
                             source_t *source,
1014
                             admin_format_t response)
1015
{
1016
    const char *fallback;
1017 1018
    char *old;

1019
    ICECAST_LOG_DEBUG("Got fallback request");
1020 1021 1022 1023 1024 1025 1026

    COMMAND_REQUIRE(client, "fallback", fallback);

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

1027
    html_success(client, "Fallback configured");
1028 1029
}

Marvin Scholz's avatar
Marvin Scholz committed
1030 1031
static void command_metadata(client_t *client,
                             source_t *source,
1032
                             admin_format_t response)
1033
{
1034
    const char *action;
1035
    const char *song, *title, *artist, *charset;
1036
    format_plugin_t *plugin;
1037 1038
    xmlDocPtr doc;
    xmlNodePtr node;
1039
    int same_ip = 1;
1040

1041
    doc = xmlNewDoc(XMLSTR("1.0"));
1042
    node = admin_build_rootnode(doc, "iceresponse");
1043
    xmlDocSetRootElement(doc, node);
1044

1045
    ICECAST_LOG_DEBUG("Got metadata update request");
1046

1047
    if (source->parser && source->parser->req_type == httpp_req_put) {
Marvin Scholz's avatar
Marvin Scholz committed
1048 1049
        ICECAST_LOG_ERROR("Got legacy SOURCE-style metadata update command on "
            "source connected with PUT at mountpoint %s", source->mount);
Philipp Schafft's avatar
Philipp Schafft committed
1050 1051
    }

1052
    COMMAND_REQUIRE(client, "mode", action);
1053 1054 1055
    COMMAND_OPTIONAL(client, "song", song);
    COMMAND_OPTIONAL(client, "title", title);
    COMMAND_OPTIONAL(client, "artist", artist);
1056
    COMMAND_OPTIONAL(client, "charset", charset);
1057

Marvin Scholz's avatar
Marvin Scholz committed
1058
    if (strcmp (action, "updinfo") != 0) {
1059 1060
        xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR("No such action"));
        xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
Marvin Scholz's avatar
Marvin Scholz committed
1061
        admin_send_response(doc, client, response, ADMIN_XSL_RESPONSE);
1062
        xmlFreeDoc(doc);
1063 1064
        return;
    }
1065

1066
    plugin = source->format;
Marvin Scholz's avatar
Marvin Scholz committed
1067
    if (source->client && strcmp(client->con->ip, source->client->con->ip) != 0)
1068
        if (response == ADMIN_FORMAT_RAW && acl_test_admin(client->acl, client->admin_command) != ACL_POLICY_ALLOW)
1069
            same_ip = 0;
Karl Heyes's avatar
Karl Heyes committed
1070

Marvin Scholz's avatar
Marvin Scholz committed
1071 1072
    if (same_ip && plugin && plugin->set_tag) {
        if (song) {
1073
            plugin->set_tag (plugin, "song", song, charset);
1074
            ICECAST_LOG_INFO("Metadata on mountpoint %s changed to \"%s\"", source->mount, song);
Marvin Scholz's avatar
Marvin Scholz committed
1075 1076 1077 1078
        } else {
            if (artist && title) {
                plugin->set_tag(plugin, "title", title, charset);
                plugin->set_tag(plugin, "artist", artist, charset);
1079
                ICECAST_LOG_INFO("Metadata on mountpoint %s changed to \"%s - %s\"",
Marvin Scholz's avatar
Marvin Scholz committed
1080
                    source->mount, artist, title);
1081 1082
            }
        }
1083 1084
        /* updates are now done, let them be pushed into the stream */
        plugin->set_tag (plugin, NULL, NULL, NULL);
Marvin Scholz's avatar
Marvin Scholz committed
1085
    } else {
1086
        xmlNewTextChild(node, NULL, XMLSTR("message"),
1087
            XMLSTR("Mountpoint will not accept URL updates"));
1088
        xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
1089
        admin_send_response(doc, client, response,
1090 1091 1092
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
        return;
1093
    }
1094