admin.c 38.3 KB
Newer Older
1
2
3
4
5
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
6
 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
7
8
9
10
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
 *                      Karl Heyes <karl@xiph.org>
 *                      and others (see AUTHORS for details).
Philipp Schafft's avatar
Philipp Schafft committed
11
 * Copyright 2012-2014, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
12
13
 */

14
15
16
17
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

18
19
#include <string.h>
#include <stdlib.h>
20
21
#include <stdarg.h>
#include <time.h>
22
23
24
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
25

26
#include "cfgfile.h"
27
28
29
30
31
32
#include "connection.h"
#include "refbuf.h"
#include "client.h"
#include "source.h"
#include "global.h"
#include "stats.h"
33
#include "compat.h"
34
#include "xslt.h"
35
#include "fserve.h"
36
#include "admin.h"
37
38
39
40

#include "format.h"

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

#define CATMODULE "admin"

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

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

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

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 *, source_t *, int);
static void command_metadata            (client_t *, source_t *, int);
static void command_shoutcast_metadata  (client_t *, source_t *, int);
static void command_show_listeners      (client_t *, source_t *, int);
static void command_stats               (client_t *, source_t *, int);
static void command_queue_reload        (client_t *, source_t *, int);
static void command_list_mounts         (client_t *, source_t *, int);
static void command_move_clients        (client_t *, source_t *, int);
static void command_kill_client         (client_t *, source_t *, int);
static void command_kill_source         (client_t *, source_t *, int);
static void command_manageauth          (client_t *, source_t *, int);
static void command_updatemetadata      (client_t *, source_t *, int);
static void command_buildm3u            (client_t *, source_t *, int);

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
int admin_get_command_type(int command)
{
Philipp Schafft's avatar
Philipp Schafft committed
168
169
170
    if (command == ADMIN_COMMAND_ERROR || command == COMMAND_ANY)
        return ADMINTYPE_ERROR;

Marvin Scholz's avatar
Marvin Scholz committed
171
172
    if (command < HANDLERS_COUNT)
        return handlers[command].type;
Philipp Schafft's avatar
Philipp Schafft committed
173
174

    return ADMINTYPE_ERROR;
175
176
}

177
178
179
/* 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
180
xmlDocPtr admin_build_sourcelist(const char *mount)
181
182
183
184
185
186
187
188
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

189
190
    doc = xmlNewDoc (XMLSTR("1.0"));
    xmlnode = xmlNewDocNode (doc, NULL, XMLSTR("icestats"), NULL);
191
    xmlDocSetRootElement(doc, xmlnode);
192

193
    if (mount) {
194
        xmlNewTextChild (xmlnode, NULL, XMLSTR("current_source"), XMLSTR(mount));
195
196
197
198
199
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
200
201
202
203
204
205
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

206
        if (source->running || source->on_demand)
207
        {
208
209
            ice_config_t *config;
            mount_proxy *mountinfo;
Philipp Schafft's avatar
Philipp Schafft committed
210
            acl_t *acl = NULL;
211

212
213
            srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
            xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
214

215
            xmlNewTextChild(srcnode, NULL, XMLSTR("fallback"),
216
                    (source->fallback_mount != NULL)?
217
                    XMLSTR(source->fallback_mount):XMLSTR(""));
218
            snprintf(buf, sizeof(buf), "%lu", source->listeners);
219
            xmlNewTextChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
220

Karl Heyes's avatar
Karl Heyes committed
221
            config = config_get_config();
Marvin Scholz's avatar
Marvin Scholz committed
222
            mountinfo = config_find_mount(config, source->mount, MOUNT_TYPE_NORMAL);
Philipp Schafft's avatar
Philipp Schafft committed
223
            if (mountinfo)
224
                acl = auth_stack_get_anonymous_acl(mountinfo->authstack, httpp_req_get);
Philipp Schafft's avatar
Philipp Schafft committed
225
            if (!acl)
226
                acl = auth_stack_get_anonymous_acl(config->authstack, httpp_req_get);
Philipp Schafft's avatar
Philipp Schafft committed
227
            if (acl && acl_test_web(acl) == ACL_POLICY_DENY) {
228
                xmlNewTextChild(srcnode, NULL, XMLSTR("authenticator"), XMLSTR("(dummy)"));
229
            }
Philipp Schafft's avatar
Philipp Schafft committed
230
            acl_release(acl);
231
232
            config_release_config();

Marvin Scholz's avatar
Marvin Scholz committed
233
234
235
236
            if (source->running) {
                if (source->client) {
                    snprintf(buf, sizeof(buf), "%lu",
                        (unsigned long)(now - source->con->con_time));
237
                    xmlNewTextChild(srcnode, NULL, XMLSTR("Connected"), XMLSTR(buf));
Karl Heyes's avatar
Karl Heyes committed
238
                }
239
                xmlNewTextChild(srcnode, NULL, XMLSTR("content-type"),
Marvin Scholz's avatar
Marvin Scholz committed
240
                    XMLSTR(source->format->contenttype));
241
            }
242
        }
243
244
245
246
247
        node = avl_get_next(node);
    }
    return(doc);
}

Marvin Scholz's avatar
Marvin Scholz committed
248
249
250
251
void admin_send_response(xmlDocPtr     doc,
                         client_t      *client,
                         int           response,
                         const char    *xslt_template)
252
{
Marvin Scholz's avatar
Marvin Scholz committed
253
    if (response == RAW) {
254
255
        xmlChar *buff = NULL;
        int len = 0;
256
257
258
        size_t buf_len;
        ssize_t ret;

259
        xmlDocDumpMemory(doc, &buff, &len);
260
261
262
263

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

265
266
        client_set_queue(client, NULL);
        client->refbuf = refbuf_new(buf_len);
267

268
269
270
        ret = util_http_build_header(client->refbuf->data, buf_len, 0,
                                     0, 200, NULL,
                                     "text/xml", "utf-8",
271
                                     NULL, NULL, client);
Philipp Schafft's avatar
Philipp Schafft committed
272
        if (ret < 0) {
273
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
274
            client_send_error(client, 500, 0, "Header generation failed.");
275
276
            xmlFree(buff);
            return;
Philipp Schafft's avatar
Philipp Schafft committed
277
        } else if (buf_len < (size_t)(len + ret + 64)) {
278
279
280
281
282
283
284
285
286
287
            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",
288
                                             NULL, NULL, client);
289
290
                if (ret == -1) {
                    ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
291
                    client_send_error(client, 500, 0, "Header generation failed.");
292
293
294
295
296
                    xmlFree(buff);
                    return;
                }
            } else {
                ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
297
                client_send_error(client, 500, 0, "Buffer reallocation failed.");
298
299
                xmlFree(buff);
                return;
300
            }
301
        }
302

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

        client->refbuf->len = ret;
307
308
309
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
310
    }
Marvin Scholz's avatar
Marvin Scholz committed
311
    if (response == TRANSFORMED) {
312
313
314
315
        char *fullpath_xslt_template;
        int fullpath_xslt_template_len;
        ice_config_t *config = config_get_config();

316
        fullpath_xslt_template_len = strlen (config->adminroot_dir) +
317
            strlen (xslt_template) + 2;
318
319
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
320
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
321
        config_release_config();
322

323
        ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template);
324
325
326
327
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
328

329
void admin_handle_request(client_t *client, const char *uri)
330
{
Marvin Scholz's avatar
Marvin Scholz committed
331
332
    const char *mount;
    const admin_command_handler_t* handler;
333

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

Marvin Scholz's avatar
Marvin Scholz committed
336
    /* Check if admin command is valid */
Philipp Schafft's avatar
Philipp Schafft committed
337
    if (client->admin_command <= 0) {
338
        ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %s",
Marvin Scholz's avatar
Marvin Scholz committed
339
                uri);
340
        client_send_error(client, 400, 0, "Unrecognised command");
341
342
343
        return;
    }

Marvin Scholz's avatar
Marvin Scholz committed
344
345
346
    handler = &handlers[client->admin_command];

    /* Check ACL */
Philipp Schafft's avatar
Philipp Schafft committed
347
    if (acl_test_admin(client->acl, client->admin_command) != ACL_POLICY_ALLOW) {
Marvin Scholz's avatar
Marvin Scholz committed
348
349
350

        /* ACL disallows, check exceptions */
        if ((handler->function == command_metadata && handler->format == RAW) &&
Philipp Schafft's avatar
Philipp Schafft committed
351
352
            (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
353
354
            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
355
356
        } else {
            client_send_error(client, 401, 1, "You need to authenticate\r\n");
357
358
            return;
        }
359
360
    }

361
362
    mount = httpp_get_query_param(client->parser, "mount");

Marvin Scholz's avatar
Marvin Scholz committed
363
    source_t *source = NULL;
364

Marvin Scholz's avatar
Marvin Scholz committed
365
366
    /* Find mountpoint source */
    if(mount != NULL) {
367

Philipp Schafft's avatar
Philipp Schafft committed
368
        /* This is a mount request, handle it as such */
369
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
370
        source = source_find_mount_raw(mount);
371

Marvin Scholz's avatar
Marvin Scholz committed
372
        /* No Source found */
Marvin Scholz's avatar
Marvin Scholz committed
373
        if (source == NULL) {
374
            avl_tree_unlock(global.source_tree);
Marvin Scholz's avatar
Marvin Scholz committed
375
376
            ICECAST_LOG_WARN("Admin command '%s' on non-existent source '%s'",
                    uri, mount);
377
            client_send_error(client, 400, 0, "Source does not exist");
Marvin Scholz's avatar
Marvin Scholz committed
378
379
380
            return;
        } /* No Source running */
        else if (source->running == 0 && source->on_demand == 0) {
381
            avl_tree_unlock(global.source_tree);
Marvin Scholz's avatar
Marvin Scholz committed
382
383
384
385
            ICECAST_LOG_INFO("Received admin command '%s' on unavailable mount '%s'",
                    uri, mount);
            client_send_error(client, 400, 0, "Source is not available");
            return;
386
        }
Marvin Scholz's avatar
Marvin Scholz committed
387
388
        ICECAST_LOG_INFO("Received admin command %s on mount '%s'",
                    uri, mount);
389
390
    }

Marvin Scholz's avatar
Marvin Scholz committed
391
392
    if (handler->type == ADMINTYPE_MOUNT && !source) {
        client_send_error(client, 400, 0, "Mount parameter mandatory");
Marvin Scholz's avatar
Marvin Scholz committed
393
        return;
394
395
    }

Marvin Scholz's avatar
Marvin Scholz committed
396
397
398
    handler->function(client, source, handler->format);
    if (source) {
        avl_tree_unlock(global.source_tree);
399
    }
Marvin Scholz's avatar
Marvin Scholz committed
400
    return;
401
402
}

403
static void html_success(client_t *client, char *message)
404
{
405
406
    ssize_t ret;

Marvin Scholz's avatar
Marvin Scholz committed
407
408
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
409
                                 "text/html", "utf-8",
410
                                 "", NULL, client);
411
412
413

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

418
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
419
420
        "<html><head><title>Admin request successful</title></head>"
        "<body><p>%s</p></body></html>", message);
421

422
    client->respcode = 200;
Marvin Scholz's avatar
Marvin Scholz committed
423
424
    client->refbuf->len = strlen(client->refbuf->data);
    fserve_add_client(client, NULL);
425
426
}

427

Marvin Scholz's avatar
Marvin Scholz committed
428
429
430
static void command_move_clients(client_t   *client,
                                 source_t   *source,
                                 int        response)
431
{
432
    const char *dest_source;
433
    source_t *dest;
434
435
436
437
438
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

439
    ICECAST_LOG_DEBUG("Doing optional check");
440
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
441
442
        parameters_passed = 1;
    }
443
    ICECAST_LOG_DEBUG("Done optional check (%d)", parameters_passed);
444
445
    if (!parameters_passed) {
        doc = admin_build_sourcelist(source->mount);
446
        admin_send_response(doc, client, response,
447
448
449
450
             MOVECLIENTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
        return;
    }
451

Marvin Scholz's avatar
Marvin Scholz committed
452
    dest = source_find_mount(dest_source);
453

Marvin Scholz's avatar
Marvin Scholz committed
454
    if (dest == NULL) {
455
        client_send_error(client, 400, 0, "No such destination");
456
457
458
        return;
    }

Marvin Scholz's avatar
Marvin Scholz committed
459
    if (strcmp(dest->mount, source->mount) == 0) {
460
        client_send_error(client, 400, 0, "supplied mountpoints are identical");
461
462
463
        return;
    }

Marvin Scholz's avatar
Marvin Scholz committed
464
    if (dest->running == 0 && dest->on_demand == 0) {
465
        client_send_error(client, 400, 0, "Destination not running");
466
467
468
        return;
    }

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

Marvin Scholz's avatar
Marvin Scholz committed
471
    doc = xmlNewDoc(XMLSTR("1.0"));
472
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
473
474
    xmlDocSetRootElement(doc, node);

Marvin Scholz's avatar
Marvin Scholz committed
475
    source_move_clients(source, dest);
476

Marvin Scholz's avatar
Marvin Scholz committed
477
    snprintf(buf, sizeof(buf), "Clients moved from %s to %s",
478
        source->mount, dest_source);
479
480
    xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
    xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
481

Marvin Scholz's avatar
Marvin Scholz committed
482
    admin_send_response(doc, client, response, ADMIN_XSL_RESPONSE);
483
    xmlFreeDoc(doc);
484
485
}

Marvin Scholz's avatar
Marvin Scholz committed
486
487
488
489
490
static inline xmlNodePtr __add_listener(client_t        *client,
                                        xmlNodePtr      parent,
                                        time_t          now,
                                        operation_mode  mode)
{
491
492
493
494
    const char *tmp;
    xmlNodePtr node;
    char buf[22];

Philipp Schafft's avatar
Philipp Schafft committed
495
    /* TODO: kh has support for a child node "lag". We should add that.
496
497
     * 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
498
     */
499
500
501
502
503
504
505

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

509
    xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "IP" : "ip"), XMLSTR(client->con->ip));
510
511
512

    tmp = httpp_getvar(client->parser, "user-agent");
    if (tmp)
513
        xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "UserAgent" : "useragent"), XMLSTR(tmp));
514
515
516

    tmp = httpp_getvar(client->parser, "referer");
    if (tmp)
517
        xmlNewTextChild(node, NULL, XMLSTR("referer"), XMLSTR(tmp));
518
519

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

    if (client->username)
523
        xmlNewTextChild(node, NULL, XMLSTR("username"), XMLSTR(client->username));
524

Philipp Schafft's avatar
Philipp Schafft committed
525
    if (client->role)
526
        xmlNewTextChild(node, NULL, XMLSTR("role"), XMLSTR(client->role));
Philipp Schafft's avatar
Philipp Schafft committed
527

528
#ifdef HAVE_OPENSSL
529
    xmlNewTextChild(node, NULL, XMLSTR("tls"), XMLSTR(client->con->ssl ? "true" : "false"));
530
#else
531
    xmlNewTextChild(node, NULL, XMLSTR("tls"), XMLSTR("false"));
532
#endif
533

534
535
536
    return node;
}

Marvin Scholz's avatar
Marvin Scholz committed
537
538
539
540
void admin_add_listeners_to_mount(source_t          *source,
                                  xmlNodePtr        parent,
                                  operation_mode    mode)
{
541
542
543
544
545
546
    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) {
547
        __add_listener((client_t *)client_node->key, parent, now, mode);
548
549
550
551
552
        client_node = avl_get_next(client_node);
    }
    avl_tree_unlock(source->client_tree);
}

Marvin Scholz's avatar
Marvin Scholz committed
553
554
555
static void command_show_listeners(client_t *client,
                                   source_t *source,
                                   int      response)
556
{
557
    xmlDocPtr doc;
558
    xmlNodePtr node, srcnode;
559
    char buf[22];
560

561
    doc = xmlNewDoc(XMLSTR("1.0"));
562
563
564
    node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
    srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
    xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
565
    xmlDocSetRootElement(doc, node);
566

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

572
    admin_add_listeners_to_mount(source, srcnode, client->mode);
573

574
    admin_send_response(doc, client, response,
575
576
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
577
578
}

Marvin Scholz's avatar
Marvin Scholz committed
579
static void command_buildm3u(client_t *client, source_t *source, int format)
580
{
Marvin Scholz's avatar
Marvin Scholz committed
581
    const char *mount = source->mount;
582
583
    const char *username = NULL;
    const char *password = NULL;
584
    ice_config_t *config;
585
    ssize_t ret;
586
587
588
589

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

Marvin Scholz's avatar
Marvin Scholz committed
590
591
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
592
                                 "audio/x-mpegurl", NULL,
593
                                 NULL, NULL, client);
594

Marvin Scholz's avatar
Marvin Scholz committed
595
596
    if (ret == -1 || ret >= (PER_CLIENT_REFBUF_SIZE - 512)) {
        /* we want at least 512 Byte left for data */
597
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
598
        client_send_error(client, 500, 0, "Header generation failed.");
599
600
601
602
        return;
    }


Karl Heyes's avatar
Karl Heyes committed
603
    config = config_get_config();
604
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
605
        "Content-Disposition: attachment; filename=listen.m3u\r\n\r\n"
606
607
608
        "http://%s:%s@%s:%d%s\r\n",
        username,
        password,
Karl Heyes's avatar
Karl Heyes committed
609
610
        config->hostname,
        config->port,
611
        mount
612
    );
Karl Heyes's avatar
Karl Heyes committed
613
    config_release_config();
614

615
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
616
617
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
618
}
619

Marvin Scholz's avatar
Marvin Scholz committed
620
621
xmlNodePtr admin_add_role_to_authentication(auth_t *auth, xmlNodePtr parent)
{
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
    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;
}
641

Marvin Scholz's avatar
Marvin Scholz committed
642
static void command_manageauth(client_t *client, source_t *source, int response)
Marvin Scholz's avatar
Marvin Scholz committed
643
{
644
    xmlDocPtr doc;
645
    xmlNodePtr node, rolenode, usersnode, msgnode;
646
647
    const char *action = NULL;
    const char *username = NULL;
648
    const char *idstring = NULL;
649
650
    char *message = NULL;
    int ret = AUTH_OK;
651
652
653
654
    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
655
    auth_t *auth;
656

Marvin Scholz's avatar
Marvin Scholz committed
657
    do {
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
        /* 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";
681
            break;
682
        }
Philipp Schafft's avatar
Philipp Schafft committed
683

684
        COMMAND_OPTIONAL(client, "action", action);
685
        COMMAND_OPTIONAL(client, "username", username);
686
687

        if (action == NULL)
688
            action = "list";
689

Marvin Scholz's avatar
Marvin Scholz committed
690
        if (!strcmp(action, "add")) {
691
            const char *password = NULL;
692
            COMMAND_OPTIONAL(client, "password", password);
693

694
695
            if (username == NULL || password == NULL) {
                ICECAST_LOG_WARN("manage auth request add for %lu but no user/pass", id);
696
697
                break;
            }
698
699
700
701
702
703

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

Philipp Schafft's avatar
Philipp Schafft committed
704
            ret = auth->adduser(auth, username, password);
705
706
            if (ret == AUTH_FAILED) {
                message = strdup("User add failed - check the icecast error log");
707
            } else if (ret == AUTH_USERADDED) {
708
                message = strdup("User added");
709
            } else if (ret == AUTH_USEREXISTS) {
710
711
712
                message = strdup("User already exists - not added");
            }
        }
Marvin Scholz's avatar
Marvin Scholz committed
713
        if (!strcmp(action, "delete")) {
714
715
716
717
718
719
720
            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";
721
722
                break;
            }
723

Philipp Schafft's avatar
Philipp Schafft committed
724
            ret = auth->deleteuser(auth, username);
725
726
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
727
            } else if (ret == AUTH_USERDELETED) {
728
729
730
731
                message = strdup("User deleted");
            }
        }

732
        doc = xmlNewDoc(XMLSTR("1.0"));
733
        node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
734

735
        rolenode = admin_add_role_to_authentication(auth, node);
736

737
        if (message) {
738
            msgnode = xmlNewChild(node, NULL, XMLSTR("iceresponse"), NULL);
739
            xmlNewTextChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
740
        }
741

742
        xmlDocSetRootElement(doc, node);
743

744
745
746
747
        if (auth && auth->listuser) {
            usersnode = xmlNewChild(rolenode, NULL, XMLSTR("users"), NULL);
            auth->listuser(auth, usersnode);
        }
748

749
750
        config_release_config();
        auth_release(auth);
751

752
        admin_send_response(doc, client, response,
Marvin Scholz's avatar
Marvin Scholz committed
753
754
            MANAGEAUTH_TRANSFORMED_REQUEST);
        free(message);
755
756
757
758
        xmlFreeDoc(doc);
        return;
    } while (0);

759
760
761
    config_release_config();
    auth_release(auth);
    client_send_error(client, error_code, 0, error_message);
762
763
}

Marvin Scholz's avatar
Marvin Scholz committed
764
765
766
static void command_kill_source(client_t *client,
                                source_t *source,
                                int      response)
767
{
768
769
770
    xmlDocPtr doc;
    xmlNodePtr node;

771
772
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
773
774
    xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR("Source Removed"));
    xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
775
776
    xmlDocSetRootElement(doc, node);

777
778
    source->running = 0;

779
    admin_send_response(doc, client, response,
780
781
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
782
783
}

Marvin Scholz's avatar
Marvin Scholz committed
784
785
786
static void command_kill_client(client_t *client,
                                source_t *source,
                                int      response)
787
{
788
    const char *idtext;
789
790
    int id;
    client_t *listener;
791
792
793
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
794
795
796
797
798
799
800

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

Marvin Scholz's avatar
Marvin Scholz committed
801
    doc = xmlNewDoc(XMLSTR("1.0"));
802
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
803
    xmlDocSetRootElement(doc, node);
804
    ICECAST_LOG_DEBUG("Response is %d", response);
805

806
    if(listener != NULL) {
807
        ICECAST_LOG_INFO("Admin request: client %d removed", id);
808
809
810
811
812

        /* This tags it for removal on the next iteration of the main source
         * loop
         */
        listener->con->error = 1;
813
814
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
815
816
        xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
817
818
    }
    else {
819
820
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d not found", id);
821
822
        xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
823
    }
824
    admin_send_response(doc, client, response,
825
826
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
827
828
}

Marvin Scholz's avatar
Marvin Scholz committed
829
830
831
static void command_fallback(client_t *client,
                             source_t *source,
                             int      response)
832
{
833
    const char *fallback;
834
835
    char *old;

836
    ICECAST_LOG_DEBUG("Got fallback request");
837
838
839
840
841
842
843

    COMMAND_REQUIRE(client, "fallback", fallback);

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

844
    html_success(client, "Fallback configured");
845
846
}

Marvin Scholz's avatar
Marvin Scholz committed
847
848
849
static void command_metadata(client_t *client,
                             source_t *source,
                             int      response)
850
{
851
    const char *action;
852
    const char *song, *title, *artist, *charset;
853
    format_plugin_t *plugin;
854
855
    xmlDocPtr doc;
    xmlNodePtr node;
856
    int same_ip = 1;
857

858
    doc = xmlNewDoc(XMLSTR("1.0"));
Marvin Scholz's avatar
Marvin Scholz committed
859
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
860
    xmlDocSetRootElement(doc, node);
861

862
    ICECAST_LOG_DEBUG("Got metadata update request");
863

Philipp Schafft's avatar
Philipp Schafft committed
864
    if (source->parser->req_type == httpp_req_put) {
Marvin Scholz's avatar
Marvin Scholz committed
865
866
        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
867
868
    }

869
    COMMAND_REQUIRE(client, "mode", action);
870
871
872
    COMMAND_OPTIONAL(client, "song", song);
    COMMAND_OPTIONAL(client, "title", title);
    COMMAND_OPTIONAL(client, "artist", artist);
873
    COMMAND_OPTIONAL(client, "charset", charset);
874

Marvin Scholz's avatar
Marvin Scholz committed
875
    if (strcmp (action, "updinfo") != 0) {
876
877
        xmlNewTextChild(node, NULL, XMLSTR("message"), XMLSTR("No such action"));
        xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
Marvin Scholz's avatar
Marvin Scholz committed
878
        admin_send_response(doc, client, response, ADMIN_XSL_RESPONSE);
879
        xmlFreeDoc(doc);
880
881
        return;
    }
882

883
    plugin = source->format;
Marvin Scholz's avatar
Marvin Scholz committed
884
    if (source->client && strcmp(client->con->ip, source->client->con->ip) != 0)
Marvin Scholz's avatar
Marvin Scholz committed
885
        if (response == RAW && acl_test_admin(client->acl, client->admin_command) != ACL_POLICY_ALLOW)
886
            same_ip = 0;
Karl Heyes's avatar
Karl Heyes committed
887

Marvin Scholz's avatar
Marvin Scholz committed
888
889
    if (same_ip && plugin && plugin->set_tag) {
        if (song) {
890
            plugin->set_tag (plugin, "song", song, charset);
891
            ICECAST_LOG_INFO("Metadata on mountpoint %s changed to \"%s\"", source->mount, song);
Marvin Scholz's avatar
Marvin Scholz committed
892
893
894
895
        } else {
            if (artist && title) {
                plugin->set_tag(plugin, "title", title, charset);
                plugin->set_tag(plugin, "artist", artist, charset);
896
                ICECAST_LOG_INFO("Metadata on mountpoint %s changed to \"%s - %s\"",
Marvin Scholz's avatar
Marvin Scholz committed
897
                    source->mount, artist, title);
898
899
            }
        }
900
901
        /* 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
902
    } else {
903
        xmlNewTextChild(node, NULL, XMLSTR("message"),
904
            XMLSTR("Mountpoint will not accept URL updates"));
905
        xmlNewTextChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
906
        admin_send_response(doc, client, response,