admin.c 40.4 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
/* special commands */
#define COMMAND_ERROR                      ADMIN_COMMAND_ERROR
#define COMMAND_ANY                        ADMIN_COMMAND_ANY
51
52

/* Mount-specific commands */
53
54
55
56
57
58
59
#define COMMAND_RAW_FALLBACK               1
#define COMMAND_RAW_METADATA_UPDATE        2
#define COMMAND_RAW_SHOW_LISTENERS         3
#define COMMAND_RAW_MOVE_CLIENTS           4
#define COMMAND_RAW_MANAGEAUTH             5
#define COMMAND_SHOUTCAST_METADATA_UPDATE  6
#define COMMAND_RAW_UPDATEMETADATA         7
60
61
62
63

#define COMMAND_TRANSFORMED_FALLBACK        50
#define COMMAND_TRANSFORMED_SHOW_LISTENERS  53
#define COMMAND_TRANSFORMED_MOVE_CLIENTS    54
64
#define COMMAND_TRANSFORMED_MANAGEAUTH      55
65
66
#define COMMAND_TRANSFORMED_UPDATEMETADATA  56
#define COMMAND_TRANSFORMED_METADATA_UPDATE 57
67
68

/* Global commands */
Karl Heyes's avatar
Karl Heyes committed
69
70
71
72
#define COMMAND_RAW_LIST_MOUNTS             101
#define COMMAND_RAW_STATS                   102
#define COMMAND_RAW_LISTSTREAM              103
#define COMMAND_PLAINTEXT_LISTSTREAM        104
73
#define COMMAND_RAW_QUEUE_RELOAD            105
Karl Heyes's avatar
Karl Heyes committed
74
75
76
#define COMMAND_TRANSFORMED_LIST_MOUNTS     201
#define COMMAND_TRANSFORMED_STATS           202
#define COMMAND_TRANSFORMED_LISTSTREAM      203
77
#define COMMAND_TRANSFORMED_QUEUE_RELOAD    205
78

79
/* Client management commands */
Karl Heyes's avatar
Karl Heyes committed
80
81
82
83
#define COMMAND_RAW_KILL_CLIENT             301
#define COMMAND_RAW_KILL_SOURCE             302
#define COMMAND_TRANSFORMED_KILL_CLIENT     401
#define COMMAND_TRANSFORMED_KILL_SOURCE     402
84

85
86
87
/* Admin commands requiring no auth */
#define COMMAND_BUILDM3U                    501

88
89
#define FALLBACK_RAW_REQUEST "fallbacks"
#define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl"
90
#define SHOUTCAST_METADATA_REQUEST "admin.cgi"
91
92
#define METADATA_RAW_REQUEST "metadata"
#define METADATA_TRANSFORMED_REQUEST "metadata.xsl"
93
94
95
96
#define LISTCLIENTS_RAW_REQUEST "listclients"
#define LISTCLIENTS_TRANSFORMED_REQUEST "listclients.xsl"
#define STATS_RAW_REQUEST "stats"
#define STATS_TRANSFORMED_REQUEST "stats.xsl"
97
98
#define QUEUE_RELOAD_RAW_REQUEST "reloadconfig"
#define QUEUE_RELOAD_TRANSFORMED_REQUEST "reloadconfig.xsl"
99
100
101
102
#define LISTMOUNTS_RAW_REQUEST "listmounts"
#define LISTMOUNTS_TRANSFORMED_REQUEST "listmounts.xsl"
#define STREAMLIST_RAW_REQUEST "streamlist"
#define STREAMLIST_TRANSFORMED_REQUEST "streamlist.xsl"
103
#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
104
105
106
107
108
109
110
#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"
111
112
#define MANAGEAUTH_RAW_REQUEST "manageauth"
#define MANAGEAUTH_TRANSFORMED_REQUEST "manageauth.xsl"
113
114
#define UPDATEMETADATA_RAW_REQUEST "updatemetadata"
#define UPDATEMETADATA_TRANSFORMED_REQUEST "updatemetadata.xsl"
115
116
#define DEFAULT_RAW_REQUEST ""
#define DEFAULT_TRANSFORMED_REQUEST ""
117
#define BUILDM3U_RAW_REQUEST "buildm3u"
118

Philipp Schafft's avatar
Philipp Schafft committed
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
typedef struct admin_command_tag {
    const int   id;
    const char *name;
    const int   type;
    const int   format;
} admin_command_t;

/*
COMMAND_TRANSFORMED_METADATA_UPDATE -> METADATA_TRANSFORMED_REQUEST
COMMAND_TRANSFORMED_UPDATEMETADATA  -> UPDATEMETADATA_TRANSFORMED_REQUEST
*/

static const admin_command_t commands[] = {
 {COMMAND_RAW_FALLBACK,                FALLBACK_RAW_REQUEST,               ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_FALLBACK,        FALLBACK_TRANSFORMED_REQUEST,       ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_METADATA_UPDATE,         METADATA_RAW_REQUEST,               ADMINTYPE_MOUNT,   RAW},
 {COMMAND_SHOUTCAST_METADATA_UPDATE,   SHOUTCAST_METADATA_REQUEST,         ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_TRANSFORMED_METADATA_UPDATE, METADATA_TRANSFORMED_REQUEST,       ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_SHOW_LISTENERS,          LISTCLIENTS_RAW_REQUEST,            ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_SHOW_LISTENERS,  LISTCLIENTS_TRANSFORMED_REQUEST,    ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_STATS,                   STATS_RAW_REQUEST,                  ADMINTYPE_HYBRID,  RAW},
 {COMMAND_TRANSFORMED_STATS,           STATS_TRANSFORMED_REQUEST,          ADMINTYPE_HYBRID,  TRANSFORMED},
 {COMMAND_RAW_STATS,                   "stats.xml",                        ADMINTYPE_HYBRID,  RAW}, /* The old way */
 {COMMAND_RAW_QUEUE_RELOAD,            QUEUE_RELOAD_RAW_REQUEST,           ADMINTYPE_GENERAL, RAW},
 {COMMAND_TRANSFORMED_QUEUE_RELOAD,    QUEUE_RELOAD_TRANSFORMED_REQUEST,   ADMINTYPE_GENERAL, TRANSFORMED},
 {COMMAND_RAW_LIST_MOUNTS,             LISTMOUNTS_RAW_REQUEST,             ADMINTYPE_GENERAL, RAW},
 {COMMAND_TRANSFORMED_LIST_MOUNTS,     LISTMOUNTS_TRANSFORMED_REQUEST,     ADMINTYPE_GENERAL, TRANSFORMED},
 {COMMAND_RAW_LISTSTREAM,              STREAMLIST_RAW_REQUEST,             ADMINTYPE_GENERAL, RAW},
 {COMMAND_PLAINTEXT_LISTSTREAM,        STREAMLIST_PLAINTEXT_REQUEST,       ADMINTYPE_GENERAL, PLAINTEXT},
 {COMMAND_TRANSFORMED_LISTSTREAM,      STREAMLIST_TRANSFORMED_REQUEST,     ADMINTYPE_GENERAL, TRANSFORMED},
 {COMMAND_RAW_MOVE_CLIENTS,            MOVECLIENTS_RAW_REQUEST,            ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_MOVE_CLIENTS,    MOVECLIENTS_TRANSFORMED_REQUEST,    ADMINTYPE_HYBRID,  TRANSFORMED},
 {COMMAND_RAW_KILL_CLIENT,             KILLCLIENT_RAW_REQUEST,             ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_KILL_CLIENT,     KILLCLIENT_TRANSFORMED_REQUEST,     ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_KILL_SOURCE,             KILLSOURCE_RAW_REQUEST,             ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_KILL_SOURCE,     KILLSOURCE_TRANSFORMED_REQUEST,     ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_MANAGEAUTH,              MANAGEAUTH_RAW_REQUEST,             ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_MANAGEAUTH,      MANAGEAUTH_TRANSFORMED_REQUEST,     ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_RAW_UPDATEMETADATA,          UPDATEMETADATA_RAW_REQUEST,         ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_UPDATEMETADATA,  UPDATEMETADATA_TRANSFORMED_REQUEST, ADMINTYPE_MOUNT,   TRANSFORMED},
 {COMMAND_BUILDM3U,                    BUILDM3U_RAW_REQUEST,               ADMINTYPE_MOUNT,   RAW},
 {COMMAND_TRANSFORMED_STATS,           DEFAULT_TRANSFORMED_REQUEST,        ADMINTYPE_HYBRID,  TRANSFORMED},
 {COMMAND_TRANSFORMED_STATS,           DEFAULT_RAW_REQUEST,                ADMINTYPE_HYBRID,  TRANSFORMED},
 {COMMAND_ANY,                         "*",                                ADMINTYPE_GENERAL, TRANSFORMED} /* for ACL framework */
};

int admin_get_command(const char *command) {
    size_t i;

    for (i = 0; i < (sizeof(commands)/sizeof(*commands)); i++)
        if (strcmp(commands[i].name, command) == 0)
            return commands[i].id;

    return COMMAND_ERROR;
}

int admin_get_command_type(int command) {
    size_t i;

    if (command == ADMIN_COMMAND_ERROR || command == COMMAND_ANY)
        return ADMINTYPE_ERROR;

    for (i = 0; i < (sizeof(commands)/sizeof(*commands)); i++)
        if (commands[i].id == command)
            return commands[i].type;

    return ADMINTYPE_ERROR;
186
187
}

188
static void command_fallback(client_t *client, source_t *source, int response);
189
static void command_metadata(client_t *client, source_t *source, int response);
190
static void command_shoutcast_metadata(client_t *client, source_t *source);
191
192
193
194
static void command_show_listeners(client_t *client, source_t *source,
        int response);
static void command_move_clients(client_t *client, source_t *source,
        int response);
195
static void command_stats(client_t *client, const char *mount, int response);
196
static void command_queue_reload(client_t *client, int response);
197
198
199
static void command_list_mounts(client_t *client, int response);
static void command_kill_client(client_t *client, source_t *source,
        int response);
200
201
static void command_manageauth(client_t *client, source_t *source,
        int response);
202
static void command_buildm3u(client_t *client, const char *mount);
203
204
static void command_kill_source(client_t *client, source_t *source,
        int response);
205
206
static void command_updatemetadata(client_t *client, source_t *source,
        int response);
Philipp Schafft's avatar
Philipp Schafft committed
207
208
static void admin_handle_mount_request(client_t *client, source_t *source);
static void admin_handle_general_request(client_t *client);
209

210
211
212
213
/* 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 */
xmlDocPtr admin_build_sourcelist (const char *mount)
214
215
216
217
218
219
220
221
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

222
223
    doc = xmlNewDoc (XMLSTR("1.0"));
    xmlnode = xmlNewDocNode (doc, NULL, XMLSTR("icestats"), NULL);
224
    xmlDocSetRootElement(doc, xmlnode);
225

226
    if (mount) {
227
        xmlNewChild (xmlnode, NULL, XMLSTR("current_source"), XMLSTR(mount));
228
229
230
231
232
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
233
234
235
236
237
238
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

239
        if (source->running || source->on_demand)
240
        {
241
242
            ice_config_t *config;
            mount_proxy *mountinfo;
Philipp Schafft's avatar
Philipp Schafft committed
243
            acl_t *acl = NULL;
244

245
246
            srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
            xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
247

248
            xmlNewChild(srcnode, NULL, XMLSTR("fallback"),
249
                    (source->fallback_mount != NULL)?
250
                    XMLSTR(source->fallback_mount):XMLSTR(""));
251
            snprintf(buf, sizeof(buf), "%lu", source->listeners);
252
            xmlNewChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
253

Karl Heyes's avatar
Karl Heyes committed
254
            config = config_get_config();
255
            mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
Philipp Schafft's avatar
Philipp Schafft committed
256
257
258
259
260
261
            if (mountinfo)
                acl = auth_stack_get_anonymous_acl(mountinfo->authstack);
            if (!acl)
                auth_stack_get_anonymous_acl(config->authstack);
            if (acl && acl_test_web(acl) == ACL_POLICY_DENY) {
                xmlNewChild(srcnode, NULL, XMLSTR("authenticator"), XMLSTR("(dummy)"));
262
            }
Philipp Schafft's avatar
Philipp Schafft committed
263
            acl_release(acl);
264
265
            config_release_config();

266
267
            if (source->running)
            {
268
                if (source->client) 
Karl Heyes's avatar
Karl Heyes committed
269
270
271
                {
                    snprintf (buf, sizeof(buf), "%lu",
                            (unsigned long)(now - source->con->con_time));
272
                    xmlNewChild (srcnode, NULL, XMLSTR("Connected"), XMLSTR(buf));
Karl Heyes's avatar
Karl Heyes committed
273
                }
274
275
                xmlNewChild (srcnode, NULL, XMLSTR("content-type"), 
                        XMLSTR(source->format->contenttype));
276
            }
277
        }
278
279
280
281
282
        node = avl_get_next(node);
    }
    return(doc);
}

283
284
void admin_send_response (xmlDocPtr doc, client_t *client,
        int response, const char *xslt_template)
285
{
286
287
288
289
    if (response == RAW)
    {
        xmlChar *buff = NULL;
        int len = 0;
290
291
292
        size_t buf_len;
        ssize_t ret;

293
        xmlDocDumpMemory(doc, &buff, &len);
294
295
296
297

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

299
300
        client_set_queue(client, NULL);
        client->refbuf = refbuf_new(buf_len);
301

302
	ret = util_http_build_header(client->refbuf->data, buf_len, 0,
303
	                             0, 200, NULL,
304
				     "text/xml", "utf-8",
305
				     NULL, NULL);
306
307
        if (ret == -1) {
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
308
            client_send_error(client, 500, 0, "Header generation failed.");
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
            xmlFree(buff);
            return;
        } else if (buf_len < (len + ret + 64)) {
            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",
                                             NULL, NULL);
                if (ret == -1) {
                    ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
325
                    client_send_error(client, 500, 0, "Header generation failed.");
326
327
328
329
330
                    xmlFree(buff);
                    return;
                }
            } else {
                ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
331
                client_send_error(client, 500, 0, "Buffer reallocation failed.");
332
333
334
335
                xmlFree(buff);
                return;
            } 
        }
336

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

        client->refbuf->len = ret;
341
342
343
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
344
    }
345
346
347
348
349
350
351
352
    if (response == TRANSFORMED)
    {
        char *fullpath_xslt_template;
        int fullpath_xslt_template_len;
        ice_config_t *config = config_get_config();

        fullpath_xslt_template_len = strlen (config->adminroot_dir) + 
            strlen (xslt_template) + 2;
353
354
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
355
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
356
        config_release_config();
357

358
        ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template);
359
360
361
362
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
363
364


365
void admin_handle_request(client_t *client, const char *uri)
366
{
367
    const char *mount, *command_string;
368

369
    ICECAST_LOG_DEBUG("Admin request (%s)", uri);
370
371
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
372
        ICECAST_LOG_ERROR("Internal error: admin request isn't");
373
        client_send_error(client, 401, 1, "You need to authenticate\r\n");
374
375
376
        return;
    }

377
378
379
380
381
382
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
383

384
    ICECAST_LOG_DEBUG("Got command (%s)", command_string);
385

Philipp Schafft's avatar
Philipp Schafft committed
386
    if (client->admin_command <= 0) {
387
        ICECAST_LOG_ERROR("Error parsing command string or unrecognised command: %s",
388
                command_string);
389
        client_send_error(client, 400, 0, "Unrecognised command");
390
391
392
        return;
    }

Philipp Schafft's avatar
Philipp Schafft committed
393
394
395
396
397
398
399
    if (acl_test_admin(client->acl, client->admin_command) != ACL_POLICY_ALLOW) {
        if (client->admin_command == COMMAND_RAW_METADATA_UPDATE &&
            (acl_test_method(client->acl, httpp_req_source) == ACL_POLICY_ALLOW ||
             acl_test_method(client->acl, httpp_req_put)    == ACL_POLICY_ALLOW)) {
            ICECAST_LOG_DEBUG("Granted right to call COMMAND_RAW_METADATA_UPDATE to client because it is allowed to do SOURCE or PUT.");
        } else {
            client_send_error(client, 401, 1, "You need to authenticate\r\n");
400
401
            return;
        }
402
403
    }

404
405
    mount = httpp_get_query_param(client->parser, "mount");

406
407
408
    if(mount != NULL) {
        source_t *source;

409
        /* this request does not require auth but can apply to files on webroot */
Philipp Schafft's avatar
Philipp Schafft committed
410
411
        if (client->admin_command == COMMAND_BUILDM3U) {
            command_buildm3u(client, mount);
412
            return;
413
        }
414

Philipp Schafft's avatar
Philipp Schafft committed
415
        /* This is a mount request, handle it as such */
416
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
417
        source = source_find_mount_raw(mount);
418

419
420
        if (source == NULL)
        {
421
            ICECAST_LOG_WARN("Admin command %s on non-existent source %s", 
422
                    command_string, mount);
423
            avl_tree_unlock(global.source_tree);
424
            client_send_error(client, 400, 0, "Source does not exist");
425
        }
426
427
        else
        {
428
            if (source->running == 0 && source->on_demand == 0)
429
430
            {
                avl_tree_unlock (global.source_tree);
431
                ICECAST_LOG_INFO("Received admin command %s on unavailable mount \"%s\"",
432
                        command_string, mount);
433
                client_send_error(client, 400, 0, "Source is not available");
434
435
                return;
            }
Philipp Schafft's avatar
Philipp Schafft committed
436
            if (client->admin_command == COMMAND_SHOUTCAST_METADATA_UPDATE &&
437
438
439
                    source->shoutcast_compat == 0)
            {
                avl_tree_unlock (global.source_tree);
440
                ICECAST_LOG_ERROR("illegal change of metadata on non-shoutcast "
441
                        "compatible stream");
442
                client_send_error(client, 400, 0, "illegal metadata call");
443
                return;
444
            }
445
            ICECAST_LOG_INFO("Received admin command %s on mount \"%s\"", 
446
                    command_string, mount);
Philipp Schafft's avatar
Philipp Schafft committed
447
            admin_handle_mount_request(client, source);
448
            avl_tree_unlock(global.source_tree);
449
        }
450
451
    }
    else {
Philipp Schafft's avatar
Philipp Schafft committed
452
        admin_handle_general_request(client);
453
454
455
    }
}

Philipp Schafft's avatar
Philipp Schafft committed
456
static void admin_handle_general_request(client_t *client)
457
{
Philipp Schafft's avatar
Philipp Schafft committed
458
    switch(client->admin_command) {
459
        case COMMAND_RAW_STATS:
460
            command_stats(client, NULL, RAW);
461
            break;
462
463
464
        case COMMAND_RAW_QUEUE_RELOAD:
            command_queue_reload(client, RAW);
            break;
465
466
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
467
468
            break;
        case COMMAND_RAW_LISTSTREAM:
469
470
            command_list_mounts(client, RAW);
            break;
471
472
473
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
474
        case COMMAND_TRANSFORMED_STATS:
475
            command_stats(client, NULL, TRANSFORMED);
476
            break;
477
478
479
        case COMMAND_TRANSFORMED_QUEUE_RELOAD:
            command_queue_reload(client, TRANSFORMED);
            break;
480
481
482
483
484
485
486
487
        case COMMAND_TRANSFORMED_LIST_MOUNTS:
            command_list_mounts(client, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_LISTSTREAM:
            command_list_mounts(client, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_MOVE_CLIENTS:
            command_list_mounts(client, TRANSFORMED);
488
            break;
489
        default:
490
            ICECAST_LOG_WARN("General admin request not recognised");
491
            client_send_error(client, 400, 0, "Unknown admin request");
492
493
494
495
            return;
    }
}

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

#define COMMAND_REQUIRE(client,name,var) \
    do { \
        (var) = httpp_get_query_param((client)->parser, (name)); \
        if((var) == NULL) { \
566
            client_send_error((client), 400, 0, "Missing parameter"); \
567
568
569
            return; \
        } \
    } while(0);
570
571
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
572

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

    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE, 0,
                                 0, 200, NULL,
579
				 "text/html", "utf-8",
580
				 "", NULL);
581
582
583

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

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

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

597

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

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

621
622
623
624
    dest = source_find_mount (dest_source);

    if (dest == NULL)
    {
625
        client_send_error(client, 400, 0, "No such destination");
626
627
628
        return;
    }

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

635
    if (dest->running == 0 && dest->on_demand == 0)
636
    {
637
        client_send_error(client, 400, 0, "Destination not running");
638
639
640
        return;
    }

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

643
644
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
645
646
    xmlDocSetRootElement(doc, node);

647
    source_move_clients (source, dest);
648

649
    memset(buf, '\000', sizeof(buf));
650
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
651
        source->mount, dest_source);
652
653
    xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
    xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
654
655
656
657

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
658
659
}

660
661
662
663
664
static inline xmlNodePtr __add_listener(client_t *client, xmlNodePtr parent, time_t now) {
    const char *tmp;
    xmlNodePtr node;
    char buf[22];

Philipp Schafft's avatar
Philipp Schafft committed
665
666
667
668
669
    /* TODO: kh has support for a child node "lag". We should add that.
     * BEFORE RELEASE 2.5.0 REVIEW #2097: Check if we are on-track for lowercasing child nodes.
     * BEFORE RELEASE 2.6.0 TODO #2097: Change case of child nodes to lower case.
     * The case of <ID>, <IP>, <UserAgent> and <Connected> should be converted to lower case.
     */
670
671
672
673
674
675
676

    node = xmlNewChild(parent, NULL, XMLSTR("listener"), NULL);
    if (!node)
        return NULL;

    memset(buf, '\000', sizeof(buf));
    snprintf(buf, sizeof(buf)-1, "%lu", client->con->id);
Philipp Schafft's avatar
Philipp Schafft committed
677
    xmlSetProp(node, XMLSTR("id"), XMLSTR(buf));
678
679
680
681
682
683
684
685
686
687
    xmlNewChild(node, NULL, XMLSTR("ID"), XMLSTR(buf));

    xmlNewChild(node, NULL, XMLSTR("IP"), XMLSTR(client->con->ip));

    tmp = httpp_getvar(client->parser, "user-agent");
    if (tmp)
        xmlNewChild(node, NULL, XMLSTR("UserAgent"), XMLSTR(tmp));

    tmp = httpp_getvar(client->parser, "referer");
    if (tmp)
Philipp Schafft's avatar
Philipp Schafft committed
688
        xmlNewChild(node, NULL, XMLSTR("referer"), XMLSTR(tmp));
689
690
691
692
693
694
695
696

    memset(buf, '\000', sizeof(buf));
    snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - client->con->con_time));
    xmlNewChild(node, NULL, XMLSTR("Connected"), XMLSTR(buf));

    if (client->username)
        xmlNewChild(node, NULL, XMLSTR("username"), XMLSTR(client->username));

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

700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
    return node;
}

void admin_add_listeners_to_mount(source_t *source, xmlNodePtr parent) {
    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) {
        __add_listener((client_t *)client_node->key, parent, now);
        client_node = avl_get_next(client_node);
    }
    avl_tree_unlock(source->client_tree);
}

716
717
static void command_show_listeners(client_t *client, source_t *source,
    int response)
718
{
719
    xmlDocPtr doc;
720
    xmlNodePtr node, srcnode;
721
    char buf[22];
722

723
    doc = xmlNewDoc(XMLSTR("1.0"));
724
725
726
    node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
    srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
    xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
727
    xmlDocSetRootElement(doc, node);
728

729
    memset(buf, '\000', sizeof(buf));
730
    snprintf (buf, sizeof(buf), "%lu", source->listeners);
731
    xmlNewChild(srcnode, NULL, XMLSTR("Listeners"), XMLSTR(buf));
732

733
    admin_add_listeners_to_mount(source, srcnode);
734

735
736
737
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
738
739
}

740
static void command_buildm3u(client_t *client,  const char *mount)
741
{
742
743
    const char *username = NULL;
    const char *password = NULL;
744
    ice_config_t *config;
745
    ssize_t ret;
746
747
748
749

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

750
751
752
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE, 0,
                                 0, 200, NULL,
				 "audio/x-mpegurl", NULL,
753
				 NULL, NULL);
754

755
756
    if (ret == -1 || ret >= (PER_CLIENT_REFBUF_SIZE - 512)) { /* we want at least 512 Byte left for data */
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
757
        client_send_error(client, 500, 0, "Header generation failed.");
758
759
760
761
        return;
    }


Karl Heyes's avatar
Karl Heyes committed
762
    config = config_get_config();
763
764
    snprintf(client->refbuf->data + ret, PER_CLIENT_REFBUF_SIZE - ret,
        "Content-Disposition = attachment; filename=listen.m3u\r\n\r\n"
765
766
767
        "http://%s:%s@%s:%d%s\r\n",
        username,
        password,
Karl Heyes's avatar
Karl Heyes committed
768
769
        config->hostname,
        config->port,
770
        mount
771
    );
Karl Heyes's avatar
Karl Heyes committed
772
    config_release_config();
773

774
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
775
776
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
777
}
778
779


780
781
782
783
784
static void command_manageauth(client_t *client, source_t *source,
    int response)
{
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, msgnode;
785
786
    const char *action = NULL;
    const char *username = NULL;
787
788
    char *message = NULL;
    int ret = AUTH_OK;
789
    ice_config_t *config = config_get_config ();
790
    mount_proxy *mountinfo = config_find_mount (config, source->mount, MOUNT_TYPE_NORMAL);
Philipp Schafft's avatar
Philipp Schafft committed
791
    auth_t *auth;
792

793
794
    do
    {
Philipp Schafft's avatar
Philipp Schafft committed
795
#if 0
796
797
        if (mountinfo == NULL || mountinfo->auth == NULL)
        {
798
            ICECAST_LOG_WARN("manage auth request for %s but no facility available", source->mount);
799
            break;
800
        }
Philipp Schafft's avatar
Philipp Schafft committed
801
802
803
804
805
806
        auth = mountinfo->auth;
#else
        ICECAST_LOG_WARN("manage auth request for %s but no facility available", source->mount);
        break;
#endif

807
808
809
810
        COMMAND_OPTIONAL(client, "action", action);
        COMMAND_OPTIONAL (client, "username", username);

        if (action == NULL)
811
            action = "list";
812
813
814
815

        if (!strcmp(action, "add"))
        {
            const char *password = NULL;
816
            COMMAND_OPTIONAL(client, "password", password);
817
818
819

            if (username == NULL || password == NULL)
            {
820
                ICECAST_LOG_WARN("manage auth request add for %s but no user/pass", source->mount);
821
822
                break;
            }
Philipp Schafft's avatar
Philipp Schafft committed
823
            ret = auth->adduser(auth, username, password);
824
825
826
827
828
829
830
831
832
833
            if (ret == AUTH_FAILED) {
                message = strdup("User add failed - check the icecast error log");
            }
            if (ret == AUTH_USERADDED) {
                message = strdup("User added");
            }
            if (ret == AUTH_USEREXISTS) {
                message = strdup("User already exists - not added");
            }
        }
834
835
836
837
        if (!strcmp(action, "delete"))
        {
            if (username == NULL)
            {
838
                ICECAST_LOG_WARN("manage auth request delete for %s but no username", source->mount);
839
840
                break;
            }
Philipp Schafft's avatar
Philipp Schafft committed
841
            ret = auth->deleteuser(auth, username);
842
843
844
845
846
847
848
849
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
            }
            if (ret == AUTH_USERDELETED) {
                message = strdup("User deleted");
            }
        }

850
        doc = xmlNewDoc(XMLSTR("1.0"));
851
852
853
        node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
        srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
        xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
854

855
        if (message) {
856
857
            msgnode = xmlNewChild(node, NULL, XMLSTR("iceresponse"), NULL);
            xmlNewChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
858
        }
859

860
        xmlDocSetRootElement(doc, node);
861

Philipp Schafft's avatar
Philipp Schafft committed
862
863
        if (auth && auth->listuser)
            auth->listuser(auth, srcnode);
864

865
        config_release_config ();
866

867
868
869
870
871
872
873
874
        admin_send_response(doc, client, response, 
                MANAGEAUTH_TRANSFORMED_REQUEST);
        free (message);
        xmlFreeDoc(doc);
        return;
    } while (0);

    config_release_config ();
875
    client_send_error(client, 400, 0, "missing parameter");
876
877
}

878
879
static void command_kill_source(client_t *client, source_t *source,
    int response)
880
{
881
882
883
    xmlDocPtr doc;
    xmlNodePtr node;

884
885
886
887
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
    xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR("Source Removed"));
    xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
888
889
    xmlDocSetRootElement(doc, node);

890
891
    source->running = 0;

892
    admin_send_response(doc, client, response,
893
894
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
895
896
}

897
898
static void command_kill_client(client_t *client, source_t *source,
    int response)
899
{
900
    const char *idtext;
901
902
    int id;
    client_t *listener;
903
904
905
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
906
907
908
909
910
911
912

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

913
914
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
915
    xmlDocSetRootElement(doc, node);
916
    ICECAST_LOG_DEBUG("Response is %d", response);
917

918
    if(listener != NULL) {
919
        ICECAST_LOG_INFO("Admin request: client %d removed", id);
920
921
922
923
924

        /* This tags it for removal on the next iteration of the main source
         * loop
         */
        listener->con->error = 1;
925
926
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
927
928
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
929
930
    }
    else {
931
932
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d not found", id);
933
934
        xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
        xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("0"));
935
    }
936
937
938
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
939
940
}

941
942
static void command_fallback(client_t *client, source_t *source,
    int response)
943
{
944
    const char *fallback;
945
946
    char *old;

947
    ICECAST_LOG_DEBUG("Got fallback request");
948
949
950
951
952
953
954

    COMMAND_REQUIRE(client, "fallback", fallback);

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

955
    html_success(client, "Fallback configured");
956
957
}

958
959
static void command_metadata(client_t *client, source_t *source,
    int response)
960
{
961
    const char *action;
962
    const char *song, *title, *artist, *charset;
963
    format_plugin_t *plugin;
964
965
    xmlDocPtr doc;
    xmlNodePtr node;
966
    int same_ip = 1;
967

968
    doc = xmlNewDoc(XMLSTR("1.0"));
969
    node = xmlNewDocNode (doc, NULL, XMLSTR("iceresponse"), NULL);
970
    xmlDocSetRootElement(doc, node);
971

972
    ICECAST_LOG_DEBUG("Got metadata update request");
973

Philipp Schafft's avatar
Philipp Schafft committed
974
975
976
977
    if (source->parser->req_type == httpp_req_put) {
        ICECAST_LOG_ERROR("Got legacy SOURCE-style metadata update command on source connected with PUT at mountpoint %s", source->mount);
    }

978
    COMMAND_REQUIRE(client, "mode", action);
Karl Heyes's avatar