admin.c 40.1 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
#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
#include "errors.h"
38 39 40 41

#include "format.h"

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

#define CATMODULE "admin"

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

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

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

Marvin Scholz's avatar
Marvin Scholz committed
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 95
#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"
96

97
typedef void (*request_function_ptr)(client_t *, source_t *, admin_format_t);
Marvin Scholz's avatar
Marvin Scholz committed
98 99 100 101 102 103 104 105

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

106 107 108 109 110 111 112 113 114 115 116 117 118
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);
Marvin Scholz's avatar
Marvin Scholz committed
119 120

static const admin_command_handler_t handlers[] = {
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
    { "*",                                  ADMINTYPE_GENERAL,      ADMIN_FORMAT_TRANSFORMED,    NULL }, /* for ACL framework */
    { FALLBACK_RAW_REQUEST,                 ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,            command_fallback },
    { FALLBACK_TRANSFORMED_REQUEST,         ADMINTYPE_MOUNT,        ADMIN_FORMAT_TRANSFORMED,    command_fallback },
    { METADATA_RAW_REQUEST,                 ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,            command_metadata },
    { METADATA_TRANSFORMED_REQUEST,         ADMINTYPE_MOUNT,        ADMIN_FORMAT_TRANSFORMED,    command_metadata },
    { SHOUTCAST_METADATA_REQUEST,           ADMINTYPE_MOUNT,        ADMIN_FORMAT_TRANSFORMED,    command_shoutcast_metadata },
    { LISTCLIENTS_RAW_REQUEST,              ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,            command_show_listeners },
    { LISTCLIENTS_TRANSFORMED_REQUEST,      ADMINTYPE_MOUNT,        ADMIN_FORMAT_TRANSFORMED,    command_show_listeners },
    { STATS_RAW_REQUEST,                    ADMINTYPE_HYBRID,       ADMIN_FORMAT_RAW,            command_stats },
    { STATS_TRANSFORMED_REQUEST,            ADMINTYPE_HYBRID,       ADMIN_FORMAT_TRANSFORMED,    command_stats },
    { "stats.xml",                          ADMINTYPE_HYBRID,       ADMIN_FORMAT_RAW,            command_stats },
    { QUEUE_RELOAD_RAW_REQUEST,             ADMINTYPE_GENERAL,      ADMIN_FORMAT_RAW,            command_queue_reload },
    { QUEUE_RELOAD_TRANSFORMED_REQUEST,     ADMINTYPE_GENERAL,      ADMIN_FORMAT_TRANSFORMED,    command_queue_reload },
    { LISTMOUNTS_RAW_REQUEST,               ADMINTYPE_GENERAL,      ADMIN_FORMAT_RAW,            command_list_mounts },
    { LISTMOUNTS_TRANSFORMED_REQUEST,       ADMINTYPE_GENERAL,      ADMIN_FORMAT_TRANSFORMED,    command_list_mounts },
    { STREAMLIST_RAW_REQUEST,               ADMINTYPE_GENERAL,      ADMIN_FORMAT_RAW,            command_list_mounts },
    { STREAMLIST_PLAINTEXT_REQUEST,         ADMINTYPE_GENERAL,      ADMIN_FORMAT_PLAINTEXT,      command_list_mounts },
    { STREAMLIST_TRANSFORMED_REQUEST,       ADMINTYPE_GENERAL,      ADMIN_FORMAT_TRANSFORMED,    command_list_mounts },
    { MOVECLIENTS_RAW_REQUEST,              ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,            command_move_clients },
    { MOVECLIENTS_TRANSFORMED_REQUEST,      ADMINTYPE_HYBRID,       ADMIN_FORMAT_TRANSFORMED,    command_move_clients },
    { KILLCLIENT_RAW_REQUEST,               ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,            command_kill_client },
    { KILLCLIENT_TRANSFORMED_REQUEST,       ADMINTYPE_MOUNT,        ADMIN_FORMAT_TRANSFORMED,    command_kill_client },
    { KILLSOURCE_RAW_REQUEST,               ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,            command_kill_source },
    { KILLSOURCE_TRANSFORMED_REQUEST,       ADMINTYPE_MOUNT,        ADMIN_FORMAT_TRANSFORMED,    command_kill_source },
    { MANAGEAUTH_RAW_REQUEST,               ADMINTYPE_GENERAL,      ADMIN_FORMAT_RAW,            command_manageauth },
    { MANAGEAUTH_TRANSFORMED_REQUEST,       ADMINTYPE_GENERAL,      ADMIN_FORMAT_TRANSFORMED,    command_manageauth },
    { UPDATEMETADATA_RAW_REQUEST,           ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,            command_updatemetadata },
    { UPDATEMETADATA_TRANSFORMED_REQUEST,   ADMINTYPE_MOUNT,        ADMIN_FORMAT_TRANSFORMED,    command_updatemetadata },
    { BUILDM3U_RAW_REQUEST,                 ADMINTYPE_MOUNT,        ADMIN_FORMAT_RAW,            command_buildm3u },
    { DEFAULT_TRANSFORMED_REQUEST,          ADMINTYPE_HYBRID,       ADMIN_FORMAT_TRANSFORMED,    command_stats },
    { DEFAULT_RAW_REQUEST,                  ADMINTYPE_HYBRID,       ADMIN_FORMAT_TRANSFORMED,    command_stats }
Philipp Schafft's avatar
Philipp Schafft committed
152 153
};

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

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

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

    return COMMAND_ERROR;
}

Marvin Scholz's avatar
Marvin Scholz committed
167 168 169
/* 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
170
{
Marvin Scholz's avatar
Marvin Scholz committed
171 172
    if (command > 0 && command < HANDLERS_COUNT)
        return &handlers[command];
Philipp Schafft's avatar
Philipp Schafft committed
173

Marvin Scholz's avatar
Marvin Scholz committed
174 175 176 177 178 179 180 181 182
    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
183

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

    return ADMINTYPE_ERROR;
188 189
}

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

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

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

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

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

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

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

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

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

261 262 263 264
void admin_send_response(xmlDocPtr       doc,
                         client_t       *client,
                         admin_format_t  response,
                         const char     *xslt_template)
265
{
266
    if (response == ADMIN_FORMAT_RAW) {
267 268
        xmlChar *buff = NULL;
        int len = 0;
269 270 271
        size_t buf_len;
        ssize_t ret;

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

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

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

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

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

        client->refbuf->len = ret;
320 321 322
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
323
    }
324
    if (response == ADMIN_FORMAT_TRANSFORMED) {
325
        char *fullpath_xslt_template;
326
        size_t fullpath_xslt_template_len;
327 328
        ice_config_t *config = config_get_config();

329
        fullpath_xslt_template_len = strlen(config->adminroot_dir) + strlen(xslt_template) + strlen(PATH_SEPARATOR) + 1;
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
    admin_format_t format;
347

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

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

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

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

        /* ACL disallows, check exceptions */
364
        if ((handler->function == command_metadata && handler->format == ADMIN_FORMAT_RAW) &&
Philipp Schafft's avatar
Philipp Schafft committed
365 366
            (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
367 368
            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
369
        } else {
370
            client_send_error_by_id(client, ICECAST_ERROR_GEN_CLIENT_NEEDS_TO_AUTHENTICATE);
371 372
            return;
        }
373 374
    }

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

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

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

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

Marvin Scholz's avatar
Marvin Scholz committed
403
    if (handler->type == ADMINTYPE_MOUNT && !source) {
404
        client_send_error_by_id(client, ICECAST_ERROR_ADMIN_MISSING_PARAMETER);
Marvin Scholz's avatar
Marvin Scholz committed
405
        return;
406 407
    }

408 409 410 411 412 413 414
    if (handler->format == ADMIN_FORMAT_AUTO) {
        format = client_get_admin_format_by_content_negotiation(client);
    } else {
        format = handler->format;
    }

    handler->function(client, source, format);
Marvin Scholz's avatar
Marvin Scholz committed
415 416
    if (source) {
        avl_tree_unlock(global.source_tree);
417
    }
Marvin Scholz's avatar
Marvin Scholz committed
418
    return;
419 420
}

421
static void html_success(client_t *client, char *message)
422
{
423 424
    ssize_t ret;

Marvin Scholz's avatar
Marvin Scholz committed
425 426
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
427
                                 "text/html", "utf-8",
428
                                 "", NULL, client);
429 430 431

    if (ret == -1 || ret >= PER_CLIENT_REFBUF_SIZE) {
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
432
        client_send_error_by_id(client, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
433 434 435
        return;
    }

436
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
437 438
        "<html><head><title>Admin request successful</title></head>"
        "<body><p>%s</p></body></html>", message);
439

440
    client->respcode = 200;
Marvin Scholz's avatar
Marvin Scholz committed
441 442
    client->refbuf->len = strlen(client->refbuf->data);
    fserve_add_client(client, NULL);
443 444
}

445

Marvin Scholz's avatar
Marvin Scholz committed
446 447
static void command_move_clients(client_t   *client,
                                 source_t   *source,
448
                                 admin_format_t response)
449
{
450
    const char *dest_source;
451
    source_t *dest;
452 453 454 455 456
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

457
    ICECAST_LOG_DEBUG("Doing optional check");
458
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
459 460
        parameters_passed = 1;
    }
461
    ICECAST_LOG_DEBUG("Done optional check (%d)", parameters_passed);
462 463
    if (!parameters_passed) {
        doc = admin_build_sourcelist(source->mount);
464
        admin_send_response(doc, client, response,
465 466 467 468
             MOVECLIENTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
        return;
    }
469

Marvin Scholz's avatar
Marvin Scholz committed
470
    dest = source_find_mount(dest_source);
471

Marvin Scholz's avatar
Marvin Scholz committed
472
    if (dest == NULL) {
473
        client_send_error_by_id(client, ICECAST_ERROR_ADMIN_NO_SUCH_DESTINATION);
474 475 476
        return;
    }

Marvin Scholz's avatar
Marvin Scholz committed
477
    if (strcmp(dest->mount, source->mount) == 0) {
478
        client_send_error_by_id(client, ICECAST_ERROR_ADMIN_SUPPLIED_MOUNTPOINTS_ARE_IDENTICAL);
479 480 481
        return;
    }

Marvin Scholz's avatar
Marvin Scholz committed
482
    if (dest->running == 0 && dest->on_demand == 0) {
483
        client_send_error_by_id(client, ICECAST_ERROR_ADMIN_DEST_NOT_RUNNING);
484 485 486
        return;
    }

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

Marvin Scholz's avatar
Marvin Scholz committed
489
    doc = xmlNewDoc(XMLSTR("1.0"));
490
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
491 492
    xmlDocSetRootElement(doc, node);

Marvin Scholz's avatar
Marvin Scholz committed
493
    source_move_clients(source, dest);
494

Marvin Scholz's avatar
Marvin Scholz committed
495
    snprintf(buf, sizeof(buf), "Clients moved from %s to %s",
496
        source->mount, dest_source);
497 498
    xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
    xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
499

Marvin Scholz's avatar
Marvin Scholz committed
500
    admin_send_response(doc, client, response, ADMIN_XSL_RESPONSE);
501
    xmlFreeDoc(doc);
502 503
}

Marvin Scholz's avatar
Marvin Scholz committed
504 505 506 507 508
static inline xmlNodePtr __add_listener(client_t        *client,
                                        xmlNodePtr      parent,
                                        time_t          now,
                                        operation_mode  mode)
{
509 510 511 512
    const char *tmp;
    xmlNodePtr node;
    char buf[22];

Philipp Schafft's avatar
Philipp Schafft committed
513
    /* TODO: kh has support for a child node "lag". We should add that.
514 515
     * 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
516
     */
517 518 519 520 521 522 523

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

527
    xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "IP" : "ip"), XMLSTR(client->con->ip));
528 529 530

    tmp = httpp_getvar(client->parser, "user-agent");
    if (tmp)
531
        xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "UserAgent" : "useragent"), XMLSTR(tmp));
532 533 534

    tmp = httpp_getvar(client->parser, "referer");
    if (tmp)
535
        xmlNewTextChild(node, NULL, XMLSTR("referer"), XMLSTR(tmp));
536 537

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

    if (client->username)
541
        xmlNewTextChild(node, NULL, XMLSTR("username"), XMLSTR(client->username));
542

Philipp Schafft's avatar
Philipp Schafft committed
543
    if (client->role)
544
        xmlNewTextChild(node, NULL, XMLSTR("role"), XMLSTR(client->role));
Philipp Schafft's avatar
Philipp Schafft committed
545

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

548 549 550 551 552 553 554 555 556
    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;
    }

557 558 559
    return node;
}

Marvin Scholz's avatar
Marvin Scholz committed
560 561 562 563
void admin_add_listeners_to_mount(source_t          *source,
                                  xmlNodePtr        parent,
                                  operation_mode    mode)
{
564 565 566 567 568 569
    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) {
570
        __add_listener((client_t *)client_node->key, parent, now, mode);
571 572 573 574 575
        client_node = avl_get_next(client_node);
    }
    avl_tree_unlock(source->client_tree);
}

Marvin Scholz's avatar
Marvin Scholz committed
576 577
static void command_show_listeners(client_t *client,
                                   source_t *source,
578
                                   admin_format_t response)
579
{
580
    xmlDocPtr doc;
581
    xmlNodePtr node, srcnode;
582
    char buf[22];
583

584
    doc = xmlNewDoc(XMLSTR("1.0"));
585 586 587
    node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
    srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
    xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
588
    xmlDocSetRootElement(doc, node);
589

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

595
    admin_add_listeners_to_mount(source, srcnode, client->mode);
596

597
    admin_send_response(doc, client, response,
598 599
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
600 601
}

602
static void command_buildm3u(client_t *client, source_t *source, admin_format_t format)
603
{
Marvin Scholz's avatar
Marvin Scholz committed
604
    const char *mount = source->mount;
605 606
    const char *username = NULL;
    const char *password = NULL;
607
    ice_config_t *config;
608
    ssize_t ret;
609 610 611 612

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

Marvin Scholz's avatar
Marvin Scholz committed
613 614
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
615
                                 "audio/x-mpegurl", NULL,
616
                                 NULL, NULL, client);
617

Marvin Scholz's avatar
Marvin Scholz committed
618 619
    if (ret == -1 || ret >= (PER_CLIENT_REFBUF_SIZE - 512)) {
        /* we want at least 512 Byte left for data */
620
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
621
        client_send_error_by_id(client, ICECAST_ERROR_GEN_HEADER_GEN_FAILED);
622 623 624 625
        return;
    }


Karl Heyes's avatar
Karl Heyes committed
626
    config = config_get_config();
627
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
628
        "Content-Disposition: attachment; filename=listen.m3u\r\n\r\n"
629 630 631
        "http://%s:%s@%s:%d%s\r\n",
        username,
        password,
Karl Heyes's avatar
Karl Heyes committed
632 633
        config->hostname,
        config->port,
634
        mount
635
    );
Karl Heyes's avatar
Karl Heyes committed
636
    config_release_config();
637

638
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
639 640
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
641
}
642

Marvin Scholz's avatar
Marvin Scholz committed
643 644
xmlNodePtr admin_add_role_to_authentication(auth_t *auth, xmlNodePtr parent)
{
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
    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;
}
664

665
static void command_manageauth(client_t *client, source_t *source, admin_format_t response)
Marvin Scholz's avatar
Marvin Scholz committed
666
{
667
    xmlDocPtr doc;
668
    xmlNodePtr node, rolenode, usersnode, msgnode;
669 670
    const char *action = NULL;
    const char *username = NULL;
671
    const char *idstring = NULL;
672 673
    char *message = NULL;
    int ret = AUTH_OK;
674
    int error_id = ICECAST_ERROR_ADMIN_missing_parameter;
675 676
    long unsigned int id;
    ice_config_t *config = config_get_config();
Philipp Schafft's avatar
Philipp Schafft committed
677
    auth_t *auth;
678

Marvin Scholz's avatar
Marvin Scholz committed
679
    do {
680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
        /* 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);
701
            error_id = ICECAST_ERROR_ADMIN_ROLEMGN_ROLE_NOT_FOUND;
702
            break;
703
        }
Philipp Schafft's avatar
Philipp Schafft committed
704

705
        COMMAND_OPTIONAL(client, "action", action);
706
        COMMAND_OPTIONAL(client, "username", username);
707 708

        if (action == NULL)
709
            action = "list";
710

Marvin Scholz's avatar
Marvin Scholz committed
711
        if (!strcmp(action, "add")) {
712
            const char *password = NULL;
713
            COMMAND_OPTIONAL(client, "password", password);
714

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

            if (!auth->adduser) {
721
                error_id = ICECAST_ERROR_ADMIN_ROLEMGN_ADD_NOSYS;
722 723 724
                break;
            }

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

            if (!auth->deleteuser) {
741
                error_id = ICECAST_ERROR_ADMIN_ROLEMGN_DELETE_NOSYS;
742 743
                break;
            }
744

Philipp Schafft's avatar
Philipp Schafft committed
745
            ret = auth->deleteuser(auth, username);
746 747
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
748
            } else if (ret == AUTH_USERDELETED) {
749 750 751 752
                message = strdup("User deleted");
            }
        }

753
        doc = xmlNewDoc(XMLSTR("1.0"));
754
        node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
755

756
        rolenode = admin_add_role_to_authentication(auth, node);
757

758
        if (message) {
759
            msgnode = xmlNewChild(node, NULL, XMLSTR("iceresponse"), NULL);
760
            xmlNewTextChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
761
        }
762

763
        xmlDocSetRootElement(doc, node);
764

765 766 767 768
        if (auth && auth->listuser) {
            usersnode = xmlNewChild(rolenode, NULL, XMLSTR("users"), NULL);
            auth->listuser(auth, usersnode);
        }
769

770 771
        config_release_config();
        auth_release(auth);
772

773
        admin_send_response(doc, client, response,
Marvin Scholz's avatar
Marvin Scholz committed
774 775
            MANAGEAUTH_TRANSFORMED_REQUEST);
        free(message);
776 777 778 779
        xmlFreeDoc(doc);
        return;
    } while (0);

780 781
    config_release_config();
    auth_release(auth);
782
    client_send_error_by_id(client, error_id);
783 784
}

Marvin Scholz's avatar
Marvin Scholz committed
785 786
static void command_kill_source(client_t *client,
                                source_t *source,
787
                                admin_format_t response)
788
{
789 790 791
    xmlDocPtr doc;
    xmlNodePtr node;

792 793
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
794 795
    xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR("Source Removed"));
    xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
796 797
    xmlDocSetRootElement(doc, node);

798 799
    source->running = 0;

800
    admin_send_response(doc, client, response,
801 802
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
803 804
}

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

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

Marvin Scholz's avatar
Marvin Scholz committed
822
    doc = xmlNewDoc(XMLSTR("1.0"));
823
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
824
    xmlDocSetRootElement(doc, node);
825
    ICECAST_LOG_DEBUG("Response is %d", response);
826

827
    if(listener != NULL) {
828
        ICECAST_LOG_INFO("Admin request: client %d removed", id);
829 830 831 832 833

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

Marvin Scholz's avatar
Marvin Scholz committed
850 851
static void command_fallback(client_t *client,
                             source_t *source,
852