admin.c 39 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

Marvin Scholz's avatar
Marvin Scholz committed
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
#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"
95

Marvin Scholz's avatar
Marvin Scholz committed
96 97 98 99 100 101 102 103 104 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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
typedef void (*request_function_ptr)(client_t *, source_t *, int);

typedef struct admin_command_handler {
    const char                     *route;
    const int                       type;
    const int                       format;
    const request_function_ptr      function;
} admin_command_handler_t;

static void command_fallback            (client_t *client, source_t *source, int response);
static void command_metadata            (client_t *client, source_t *source, int response);
static void command_shoutcast_metadata  (client_t *client, source_t *source, int response);
static void command_show_listeners      (client_t *client, source_t *source, int response);
static void command_stats               (client_t *client, source_t *source, int response);
static void command_queue_reload        (client_t *client, source_t *source, int response);
static void command_list_mounts         (client_t *client, source_t *source, int response);
static void command_move_clients        (client_t *client, source_t *source, int response);
static void command_kill_client         (client_t *client, source_t *source, int response);
static void command_kill_source         (client_t *client, source_t *source, int response);
static void command_manageauth          (client_t *client, source_t *source, int response);
static void command_updatemetadata      (client_t *client, source_t *source, int response);
static void command_buildm3u            (client_t *client, source_t *source, int response);

static const admin_command_handler_t handlers[] = {
    { "*",                                  ADMINTYPE_GENERAL,      TRANSFORMED,    NULL }, /* for ACL framework */
    { FALLBACK_RAW_REQUEST,                 ADMINTYPE_MOUNT,        RAW,            command_fallback },
    { FALLBACK_TRANSFORMED_REQUEST,         ADMINTYPE_MOUNT,        TRANSFORMED,    command_fallback },
    { METADATA_RAW_REQUEST,                 ADMINTYPE_MOUNT,        RAW,            command_metadata },
    { METADATA_TRANSFORMED_REQUEST,         ADMINTYPE_MOUNT,        TRANSFORMED,    command_metadata },
    { SHOUTCAST_METADATA_REQUEST,           ADMINTYPE_MOUNT,        TRANSFORMED,    command_shoutcast_metadata },
    { LISTCLIENTS_RAW_REQUEST,              ADMINTYPE_MOUNT,        RAW,            command_show_listeners },
    { LISTCLIENTS_TRANSFORMED_REQUEST,      ADMINTYPE_MOUNT,        TRANSFORMED,    command_show_listeners },
    { STATS_RAW_REQUEST,                    ADMINTYPE_HYBRID,       RAW,            command_stats },
    { STATS_TRANSFORMED_REQUEST,            ADMINTYPE_HYBRID,       TRANSFORMED,    command_stats },
    { "stats.xml",                          ADMINTYPE_HYBRID,       RAW,            command_stats },
    { QUEUE_RELOAD_RAW_REQUEST,             ADMINTYPE_GENERAL,      RAW,            command_queue_reload },
    { QUEUE_RELOAD_TRANSFORMED_REQUEST,     ADMINTYPE_GENERAL,      TRANSFORMED,    command_queue_reload },
    { LISTMOUNTS_RAW_REQUEST,               ADMINTYPE_GENERAL,      RAW,            command_list_mounts },
    { LISTMOUNTS_TRANSFORMED_REQUEST,       ADMINTYPE_GENERAL,      TRANSFORMED,    command_list_mounts },
    { STREAMLIST_RAW_REQUEST,               ADMINTYPE_GENERAL,      RAW,            command_list_mounts },
    { STREAMLIST_PLAINTEXT_REQUEST,         ADMINTYPE_GENERAL,      PLAINTEXT,      command_list_mounts },
    { STREAMLIST_TRANSFORMED_REQUEST,       ADMINTYPE_GENERAL,      TRANSFORMED,    command_list_mounts },
    { MOVECLIENTS_RAW_REQUEST,              ADMINTYPE_MOUNT,        RAW,            command_move_clients },
    { MOVECLIENTS_TRANSFORMED_REQUEST,      ADMINTYPE_HYBRID,       TRANSFORMED,    command_move_clients },
    { KILLCLIENT_RAW_REQUEST,               ADMINTYPE_MOUNT,        RAW,            command_kill_client },
    { KILLCLIENT_TRANSFORMED_REQUEST,       ADMINTYPE_MOUNT,        TRANSFORMED,    command_kill_client },
    { KILLSOURCE_RAW_REQUEST,               ADMINTYPE_MOUNT,        RAW,            command_kill_source },
    { KILLSOURCE_TRANSFORMED_REQUEST,       ADMINTYPE_MOUNT,        TRANSFORMED,    command_kill_source },
    { MANAGEAUTH_RAW_REQUEST,               ADMINTYPE_GENERAL,      RAW,            command_manageauth },
    { MANAGEAUTH_TRANSFORMED_REQUEST,       ADMINTYPE_GENERAL,      TRANSFORMED,    command_manageauth },
    { UPDATEMETADATA_RAW_REQUEST,           ADMINTYPE_MOUNT,        RAW,            command_updatemetadata },
    { UPDATEMETADATA_TRANSFORMED_REQUEST,   ADMINTYPE_MOUNT,        TRANSFORMED,    command_updatemetadata },
    { BUILDM3U_RAW_REQUEST,                 ADMINTYPE_MOUNT,        RAW,            command_buildm3u },
    { DEFAULT_TRANSFORMED_REQUEST,          ADMINTYPE_HYBRID,       TRANSFORMED,    command_stats },
    { DEFAULT_RAW_REQUEST,                  ADMINTYPE_HYBRID,       TRANSFORMED,    command_stats }
Philipp Schafft's avatar
Philipp Schafft committed
151 152
};

Marvin Scholz's avatar
Marvin Scholz committed
153 154
#define HANDLERS_COUNT (sizeof(handlers)/sizeof(*handlers))

Marvin Scholz's avatar
Marvin Scholz committed
155 156
int admin_get_command(const char *command)
{
Philipp Schafft's avatar
Philipp Schafft committed
157 158
    size_t i;

Marvin Scholz's avatar
Marvin Scholz committed
159 160 161
    for (i = 0; i < HANDLERS_COUNT; i++)
        if (strcmp(handlers[i].route, command) == 0)
            return i;
Philipp Schafft's avatar
Philipp Schafft committed
162 163 164 165

    return COMMAND_ERROR;
}

Marvin Scholz's avatar
Marvin Scholz committed
166 167 168
/* Get the command handler for command or NULL
 */
const admin_command_handler_t* admin_get_handler(int command)
Marvin Scholz's avatar
Marvin Scholz committed
169
{
Marvin Scholz's avatar
Marvin Scholz committed
170 171
    if (command > 0 && command < HANDLERS_COUNT)
        return &handlers[command];
Philipp Schafft's avatar
Philipp Schafft committed
172

Marvin Scholz's avatar
Marvin Scholz committed
173 174 175 176 177 178 179 180 181
    return NULL;
}

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

Marvin Scholz's avatar
Marvin Scholz committed
183 184
    if (handler != NULL)
        return handler->type;
Philipp Schafft's avatar
Philipp Schafft committed
185 186

    return ADMINTYPE_ERROR;
187 188
}

189 190 191
/* 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
192
xmlDocPtr admin_build_sourcelist(const char *mount)
193 194 195 196 197 198 199 200
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

201 202
    doc = xmlNewDoc (XMLSTR("1.0"));
    xmlnode = xmlNewDocNode (doc, NULL, XMLSTR("icestats"), NULL);
203
    xmlDocSetRootElement(doc, xmlnode);
204

205
    if (mount) {
206
        xmlNewTextChild (xmlnode, NULL, XMLSTR("current_source"), XMLSTR(mount));
207 208 209 210 211
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
212 213 214 215 216 217
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

218
        if (source->running || source->on_demand)
219
        {
220 221
            ice_config_t *config;
            mount_proxy *mountinfo;
Philipp Schafft's avatar
Philipp Schafft committed
222
            acl_t *acl = NULL;
223

224 225
            srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
            xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
226

227
            xmlNewTextChild(srcnode, NULL, XMLSTR("fallback"),
228
                    (source->fallback_mount != NULL)?
229
                    XMLSTR(source->fallback_mount):XMLSTR(""));
230
            snprintf(buf, sizeof(buf), "%lu", source->listeners);
231
            xmlNewTextChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
232

Karl Heyes's avatar
Karl Heyes committed
233
            config = config_get_config();
Marvin Scholz's avatar
Marvin Scholz committed
234
            mountinfo = config_find_mount(config, source->mount, MOUNT_TYPE_NORMAL);
Philipp Schafft's avatar
Philipp Schafft committed
235
            if (mountinfo)
236
                acl = auth_stack_get_anonymous_acl(mountinfo->authstack, httpp_req_get);
Philipp Schafft's avatar
Philipp Schafft committed
237
            if (!acl)
238
                acl = auth_stack_get_anonymous_acl(config->authstack, httpp_req_get);
Philipp Schafft's avatar
Philipp Schafft committed
239
            if (acl && acl_test_web(acl) == ACL_POLICY_DENY) {
240
                xmlNewTextChild(srcnode, NULL, XMLSTR("authenticator"), XMLSTR("(dummy)"));
241
            }
Philipp Schafft's avatar
Philipp Schafft committed
242
            acl_release(acl);
243 244
            config_release_config();

Marvin Scholz's avatar
Marvin Scholz committed
245 246 247 248
            if (source->running) {
                if (source->client) {
                    snprintf(buf, sizeof(buf), "%lu",
                        (unsigned long)(now - source->con->con_time));
249
                    xmlNewTextChild(srcnode, NULL, XMLSTR("Connected"), XMLSTR(buf));
Karl Heyes's avatar
Karl Heyes committed
250
                }
251
                xmlNewTextChild(srcnode, NULL, XMLSTR("content-type"),
Marvin Scholz's avatar
Marvin Scholz committed
252
                    XMLSTR(source->format->contenttype));
253
            }
254
        }
255 256 257 258 259
        node = avl_get_next(node);
    }
    return(doc);
}

Marvin Scholz's avatar
Marvin Scholz committed
260 261 262 263
void admin_send_response(xmlDocPtr     doc,
                         client_t      *client,
                         int           response,
                         const char    *xslt_template)
264
{
Marvin Scholz's avatar
Marvin Scholz committed
265
    if (response == RAW) {
266 267
        xmlChar *buff = NULL;
        int len = 0;
268 269 270
        size_t buf_len;
        ssize_t ret;

271
        xmlDocDumpMemory(doc, &buff, &len);
272 273 274 275

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

277 278
        client_set_queue(client, NULL);
        client->refbuf = refbuf_new(buf_len);
279

280 281 282
        ret = util_http_build_header(client->refbuf->data, buf_len, 0,
                                     0, 200, NULL,
                                     "text/xml", "utf-8",
283
                                     NULL, NULL, client);
Philipp Schafft's avatar
Philipp Schafft committed
284
        if (ret < 0) {
285
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
286
            client_send_error(client, 500, 0, "Header generation failed.");
287 288
            xmlFree(buff);
            return;
Philipp Schafft's avatar
Philipp Schafft committed
289
        } else if (buf_len < (size_t)(len + ret + 64)) {
290 291 292 293 294 295 296 297 298 299
            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",
300
                                             NULL, NULL, client);
301 302
                if (ret == -1) {
                    ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
303
                    client_send_error(client, 500, 0, "Header generation failed.");
304 305 306 307 308
                    xmlFree(buff);
                    return;
                }
            } else {
                ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
309
                client_send_error(client, 500, 0, "Buffer reallocation failed.");
310 311
                xmlFree(buff);
                return;
312
            }
313
        }
314

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

        client->refbuf->len = ret;
319 320 321
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
322
    }
Marvin Scholz's avatar
Marvin Scholz committed
323
    if (response == TRANSFORMED) {
324 325 326 327
        char *fullpath_xslt_template;
        int fullpath_xslt_template_len;
        ice_config_t *config = config_get_config();

328
        fullpath_xslt_template_len = strlen (config->adminroot_dir) +
329
            strlen (xslt_template) + 2;
330 331
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
332
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
333
        config_release_config();
334

335
        ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template);
336 337 338 339
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
340

341
void admin_handle_request(client_t *client, const char *uri)
342
{
Marvin Scholz's avatar
Marvin Scholz committed
343 344 345
    const char *mount;
    const admin_command_handler_t* handler;
    source_t *source = NULL;
346

Marvin Scholz's avatar
Marvin Scholz committed
347
    ICECAST_LOG_DEBUG("Got admin request '%s'", uri);
348

Marvin Scholz's avatar
Marvin Scholz committed
349
    handler = admin_get_handler(client->admin_command);
350

Marvin Scholz's avatar
Marvin Scholz committed
351 352 353 354
    /* Check if admin command is valid */
    if (handler == NULL) {
        ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %H",
                uri);
355
        client_send_error(client, 400, 0, "Unrecognised command");
356 357 358
        return;
    }

Marvin Scholz's avatar
Marvin Scholz committed
359
    /* Check ACL */
Philipp Schafft's avatar
Philipp Schafft committed
360
    if (acl_test_admin(client->acl, client->admin_command) != ACL_POLICY_ALLOW) {
Marvin Scholz's avatar
Marvin Scholz committed
361 362 363

        /* ACL disallows, check exceptions */
        if ((handler->function == command_metadata && handler->format == RAW) &&
Philipp Schafft's avatar
Philipp Schafft committed
364 365
            (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
366 367
            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
368 369
        } else {
            client_send_error(client, 401, 1, "You need to authenticate\r\n");
370 371
            return;
        }
372 373
    }

374 375
    mount = httpp_get_query_param(client->parser, "mount");

Marvin Scholz's avatar
Marvin Scholz committed
376
    /* Find mountpoint source */
377
    if(mount != NULL) {
378

Philipp Schafft's avatar
Philipp Schafft committed
379
        /* This is a mount request, handle it as such */
380
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
381
        source = source_find_mount_raw(mount);
382

Marvin Scholz's avatar
Marvin Scholz committed
383
        /* No Source found */
Marvin Scholz's avatar
Marvin Scholz committed
384
        if (source == NULL) {
385
            avl_tree_unlock(global.source_tree);
Marvin Scholz's avatar
Marvin Scholz committed
386 387
            ICECAST_LOG_WARN("Admin command \"%H\" on non-existent source \"%H\"",
                    uri, mount);
388
            client_send_error(client, 400, 0, "Source does not exist");
Marvin Scholz's avatar
Marvin Scholz committed
389 390 391
            return;
        } /* No Source running */
        else if (source->running == 0 && source->on_demand == 0) {
392
            avl_tree_unlock(global.source_tree);
Marvin Scholz's avatar
Marvin Scholz committed
393 394 395 396
            ICECAST_LOG_INFO("Received admin command \"%H\" on unavailable mount \"%H\"",
                    uri, mount);
            client_send_error(client, 400, 0, "Source is not available");
            return;
397
        }
Marvin Scholz's avatar
Marvin Scholz committed
398 399
        ICECAST_LOG_INFO("Received admin command %H on mount '%s'",
                    uri, mount);
400 401
    }

Marvin Scholz's avatar
Marvin Scholz committed
402 403
    if (handler->type == ADMINTYPE_MOUNT && !source) {
        client_send_error(client, 400, 0, "Mount parameter mandatory");
Marvin Scholz's avatar
Marvin Scholz committed
404
        return;
405 406
    }

Marvin Scholz's avatar
Marvin Scholz committed
407 408 409
    handler->function(client, source, handler->format);
    if (source) {
        avl_tree_unlock(global.source_tree);
410
    }
Marvin Scholz's avatar
Marvin Scholz committed
411
    return;
412 413
}

414
static void html_success(client_t *client, char *message)
415
{
416 417
    ssize_t ret;

Marvin Scholz's avatar
Marvin Scholz committed
418 419
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
420
                                 "text/html", "utf-8",
421
                                 "", NULL, client);
422 423 424

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

429
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
430 431
        "<html><head><title>Admin request successful</title></head>"
        "<body><p>%s</p></body></html>", message);
432

433
    client->respcode = 200;
Marvin Scholz's avatar
Marvin Scholz committed
434 435
    client->refbuf->len = strlen(client->refbuf->data);
    fserve_add_client(client, NULL);
436 437
}

438

Marvin Scholz's avatar
Marvin Scholz committed
439 440 441
static void command_move_clients(client_t   *client,
                                 source_t   *source,
                                 int        response)
442
{
443
    const char *dest_source;
444
    source_t *dest;
445 446 447 448 449
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

450
    ICECAST_LOG_DEBUG("Doing optional check");
451
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
452 453
        parameters_passed = 1;
    }
454
    ICECAST_LOG_DEBUG("Done optional check (%d)", parameters_passed);
455 456
    if (!parameters_passed) {
        doc = admin_build_sourcelist(source->mount);
457
        admin_send_response(doc, client, response,
458 459 460 461
             MOVECLIENTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
        return;
    }
462

Marvin Scholz's avatar
Marvin Scholz committed
463
    dest = source_find_mount(dest_source);
464

Marvin Scholz's avatar
Marvin Scholz committed
465
    if (dest == NULL) {
466
        client_send_error(client, 400, 0, "No such destination");
467 468 469
        return;
    }

Marvin Scholz's avatar
Marvin Scholz committed
470
    if (strcmp(dest->mount, source->mount) == 0) {
471
        client_send_error(client, 400, 0, "supplied mountpoints are identical");
472 473 474
        return;
    }

Marvin Scholz's avatar
Marvin Scholz committed
475
    if (dest->running == 0 && dest->on_demand == 0) {
476
        client_send_error(client, 400, 0, "Destination not running");
477 478 479
        return;
    }

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

Marvin Scholz's avatar
Marvin Scholz committed
482
    doc = xmlNewDoc(XMLSTR("1.0"));
483
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
484 485
    xmlDocSetRootElement(doc, node);

Marvin Scholz's avatar
Marvin Scholz committed
486
    source_move_clients(source, dest);
487

Marvin Scholz's avatar
Marvin Scholz committed
488
    snprintf(buf, sizeof(buf), "Clients moved from %s to %s",
489
        source->mount, dest_source);
490 491
    xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
    xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
492

Marvin Scholz's avatar
Marvin Scholz committed
493
    admin_send_response(doc, client, response, ADMIN_XSL_RESPONSE);
494
    xmlFreeDoc(doc);
495 496
}

Marvin Scholz's avatar
Marvin Scholz committed
497 498 499 500 501
static inline xmlNodePtr __add_listener(client_t        *client,
                                        xmlNodePtr      parent,
                                        time_t          now,
                                        operation_mode  mode)
{
502 503 504 505
    const char *tmp;
    xmlNodePtr node;
    char buf[22];

Philipp Schafft's avatar
Philipp Schafft committed
506
    /* TODO: kh has support for a child node "lag". We should add that.
507 508
     * 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
509
     */
510 511 512 513 514 515 516

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

520
    xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "IP" : "ip"), XMLSTR(client->con->ip));
521 522 523

    tmp = httpp_getvar(client->parser, "user-agent");
    if (tmp)
524
        xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "UserAgent" : "useragent"), XMLSTR(tmp));
525 526 527

    tmp = httpp_getvar(client->parser, "referer");
    if (tmp)
528
        xmlNewTextChild(node, NULL, XMLSTR("referer"), XMLSTR(tmp));
529 530

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

    if (client->username)
534
        xmlNewTextChild(node, NULL, XMLSTR("username"), XMLSTR(client->username));
535

Philipp Schafft's avatar
Philipp Schafft committed
536
    if (client->role)
537
        xmlNewTextChild(node, NULL, XMLSTR("role"), XMLSTR(client->role));
Philipp Schafft's avatar
Philipp Schafft committed
538

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

541 542 543 544 545 546 547 548 549
    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;
    }

550 551 552
    return node;
}

Marvin Scholz's avatar
Marvin Scholz committed
553 554 555 556
void admin_add_listeners_to_mount(source_t          *source,
                                  xmlNodePtr        parent,
                                  operation_mode    mode)
{
557 558 559 560 561 562
    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) {
563
        __add_listener((client_t *)client_node->key, parent, now, mode);
564 565 566 567 568
        client_node = avl_get_next(client_node);
    }
    avl_tree_unlock(source->client_tree);
}

Marvin Scholz's avatar
Marvin Scholz committed
569 570 571
static void command_show_listeners(client_t *client,
                                   source_t *source,
                                   int      response)
572
{
573
    xmlDocPtr doc;
574
    xmlNodePtr node, srcnode;
575
    char buf[22];
576

577
    doc = xmlNewDoc(XMLSTR("1.0"));
578 579 580
    node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
    srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
    xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
581
    xmlDocSetRootElement(doc, node);
582

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

588
    admin_add_listeners_to_mount(source, srcnode, client->mode);
589

590
    admin_send_response(doc, client, response,
591 592
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
593 594
}

Marvin Scholz's avatar
Marvin Scholz committed
595
static void command_buildm3u(client_t *client, source_t *source, int format)
596
{
Marvin Scholz's avatar
Marvin Scholz committed
597
    const char *mount = source->mount;
598 599
    const char *username = NULL;
    const char *password = NULL;
600
    ice_config_t *config;
601
    ssize_t ret;
602 603 604 605

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

Marvin Scholz's avatar
Marvin Scholz committed
606 607
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
608
                                 "audio/x-mpegurl", NULL,
609
                                 NULL, NULL, client);
610

Marvin Scholz's avatar
Marvin Scholz committed
611 612
    if (ret == -1 || ret >= (PER_CLIENT_REFBUF_SIZE - 512)) {
        /* we want at least 512 Byte left for data */
613
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
614
        client_send_error(client, 500, 0, "Header generation failed.");
615 616 617 618
        return;
    }


Karl Heyes's avatar
Karl Heyes committed
619
    config = config_get_config();
620
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
621
        "Content-Disposition: attachment; filename=listen.m3u\r\n\r\n"
622 623 624
        "http://%s:%s@%s:%d%s\r\n",
        username,
        password,
Karl Heyes's avatar
Karl Heyes committed
625 626
        config->hostname,
        config->port,
627
        mount
628
    );
Karl Heyes's avatar
Karl Heyes committed
629
    config_release_config();
630

631
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
632 633
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
634
}
635

Marvin Scholz's avatar
Marvin Scholz committed
636 637
xmlNodePtr admin_add_role_to_authentication(auth_t *auth, xmlNodePtr parent)
{
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656
    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;
}
657

Marvin Scholz's avatar
Marvin Scholz committed
658
static void command_manageauth(client_t *client, source_t *source, int response)
Marvin Scholz's avatar
Marvin Scholz committed
659
{
660
    xmlDocPtr doc;
661
    xmlNodePtr node, rolenode, usersnode, msgnode;
662 663
    const char *action = NULL;
    const char *username = NULL;
664
    const char *idstring = NULL;
665 666
    char *message = NULL;
    int ret = AUTH_OK;
667 668 669 670
    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
671
    auth_t *auth;
672

Marvin Scholz's avatar
Marvin Scholz committed
673
    do {
674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
        /* 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";
697
            break;
698
        }
Philipp Schafft's avatar
Philipp Schafft committed
699

700
        COMMAND_OPTIONAL(client, "action", action);
701
        COMMAND_OPTIONAL(client, "username", username);
702 703

        if (action == NULL)
704
            action = "list";
705

Marvin Scholz's avatar
Marvin Scholz committed
706
        if (!strcmp(action, "add")) {
707
            const char *password = NULL;
708
            COMMAND_OPTIONAL(client, "password", password);
709

710 711
            if (username == NULL || password == NULL) {
                ICECAST_LOG_WARN("manage auth request add for %lu but no user/pass", id);
712 713
                break;
            }
714 715 716 717 718 719

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

Philipp Schafft's avatar
Philipp Schafft committed
720
            ret = auth->adduser(auth, username, password);
721 722
            if (ret == AUTH_FAILED) {
                message = strdup("User add failed - check the icecast error log");
723
            } else if (ret == AUTH_USERADDED) {
724
                message = strdup("User added");
725
            } else if (ret == AUTH_USEREXISTS) {
726 727 728
                message = strdup("User already exists - not added");
            }
        }
Marvin Scholz's avatar
Marvin Scholz committed
729
        if (!strcmp(action, "delete")) {
730 731 732 733 734 735 736
            if (username == NULL) {
                ICECAST_LOG_WARN("manage auth request delete for %lu but no username", id);
                break;
            }

            if (!auth->deleteuser) {
                error_message = "Deleting users from role not supported by role";
737 738
                break;
            }
739

Philipp Schafft's avatar
Philipp Schafft committed
740
            ret = auth->deleteuser(auth, username);
741 742
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
743
            } else if (ret == AUTH_USERDELETED) {
744 745 746 747
                message = strdup("User deleted");
            }
        }

748
        doc = xmlNewDoc(XMLSTR("1.0"));
749
        node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
750

751
        rolenode = admin_add_role_to_authentication(auth, node);
752

753
        if (message) {
754
            msgnode = xmlNewChild(node, NULL, XMLSTR("iceresponse"), NULL);
755
            xmlNewTextChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
756
        }
757

758
        xmlDocSetRootElement(doc, node);
759

760 761 762 763
        if (auth && auth->listuser) {
            usersnode = xmlNewChild(rolenode, NULL, XMLSTR("users"), NULL);
            auth->listuser(auth, usersnode);
        }
764

765 766
        config_release_config();
        auth_release(auth);
767

768
        admin_send_response(doc, client, response,
Marvin Scholz's avatar
Marvin Scholz committed
769 770
            MANAGEAUTH_TRANSFORMED_REQUEST);
        free(message);
771 772 773 774
        xmlFreeDoc(doc);
        return;
    } while (0);

775 776 777
    config_release_config();
    auth_release(auth);
    client_send_error(client, error_code, 0, error_message);
778 779
}

Marvin Scholz's avatar
Marvin Scholz committed
780 781 782
static void command_kill_source(client_t *client,
                                source_t *source,
                                int      response)
783
{
784 785 786
    xmlDocPtr doc;
    xmlNodePtr node;

787 788
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
789 790
    xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR("Source Removed"));
    xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
791 792
    xmlDocSetRootElement(doc, node);

793 794
    source->running = 0;

795
    admin_send_response(doc, client, response,
796 797
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
798 799
}

Marvin Scholz's avatar
Marvin Scholz committed
800 801 802
static void command_kill_client(client_t *client,
                                source_t *source,
                                int      response)
803
{
804
    const char *idtext;
805 806
    int id;
    client_t *listener;
807 808 809
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
810 811 812 813 814 815 816

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

Marvin Scholz's avatar
Marvin Scholz committed
817
    doc = xmlNewDoc(XMLSTR("1.0"));
818
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
819
    xmlDocSetRootElement(doc, node);
820
    ICECAST_LOG_DEBUG("Response is %d", response);
821

822
    if(listener != NULL) {
823
        ICECAST_LOG_INFO("Admin request: client %d removed", id);
824 825 826 827 828

        /* This tags it for removal on the next iteration of the main source
         * loop
         */
        listener->con->error = 1;
829 830
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
831 832
        xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
833 834
    }
    else {
835 836
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d not found", id);
837 838
        xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
839
    }
840
    admin_send_response(doc, client, response,
841 842
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
843 844
}

Marvin Scholz's avatar
Marvin Scholz committed
845 846 847
static void command_fallback(client_t *client,
                             source_t *source,
                             int      response)
848
{
849
    const char *fallback;
850 851
    char *old;

852
    ICECAST_LOG_DEBUG("Got fallback request");
853 854 855 856 857 858 859

    COMMAND_REQUIRE(client, "fallback", fallback);

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

860
    html_success(client, "Fallback configured");
861 862
}

Marvin Scholz's avatar
Marvin Scholz committed
863 864 865
static void command_metadata(client_t *client,
                             source_t *source,
                             int      response)
866
{
867
    const char *action;
868
    const char *song, *title, *artist, *charset;
869
    format_plugin_t *plugin;
870 871
    xmlDocPtr doc;
    xmlNodePtr node;
872
    int same_ip = 1;
873

874
    doc = xmlNewDoc(XMLSTR("1.0"));
Marvin Scholz's avatar
Marvin Scholz committed
875
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
876
    xmlDocSetRootElement(doc, node);
Michael Smith's avatar