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

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

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

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

#include "format.h"

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

#define CATMODULE "admin"

47
48
49
#define COMMAND_ERROR             (-1)

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

#define COMMAND_TRANSFORMED_FALLBACK        50
#define COMMAND_TRANSFORMED_SHOW_LISTENERS  53
#define COMMAND_TRANSFORMED_MOVE_CLIENTS    54
61
#define COMMAND_TRANSFORMED_MANAGEAUTH      55
62
63
#define COMMAND_TRANSFORMED_UPDATEMETADATA  56
#define COMMAND_TRANSFORMED_METADATA_UPDATE 57
64
65

/* Global commands */
Karl Heyes's avatar
Karl Heyes committed
66
67
68
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
#define COMMAND_TRANSFORMED_LIST_MOUNTS     201
#define COMMAND_TRANSFORMED_STATS           202
#define COMMAND_TRANSFORMED_LISTSTREAM      203
73

74
/* Client management commands */
Karl Heyes's avatar
Karl Heyes committed
75
76
77
78
#define COMMAND_RAW_KILL_CLIENT             301
#define COMMAND_RAW_KILL_SOURCE             302
#define COMMAND_TRANSFORMED_KILL_CLIENT     401
#define COMMAND_TRANSFORMED_KILL_SOURCE     402
79

80
81
82
/* Admin commands requiring no auth */
#define COMMAND_BUILDM3U                    501

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

#define RAW         1
#define TRANSFORMED 2
114
#define PLAINTEXT   3
115

116
117
int admin_get_command(char *command)
{
118
119
120
121
    if(!strcmp(command, FALLBACK_RAW_REQUEST))
        return COMMAND_RAW_FALLBACK;
    else if(!strcmp(command, FALLBACK_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_FALLBACK;
122
123
124
125
    else if(!strcmp(command, METADATA_RAW_REQUEST))
        return COMMAND_RAW_METADATA_UPDATE;
    else if(!strcmp(command, METADATA_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_METADATA_UPDATE;
126
127
    else if(!strcmp(command, SHOUTCAST_METADATA_REQUEST))
        return COMMAND_SHOUTCAST_METADATA_UPDATE;
128
129
130
131
132
    else if(!strcmp(command, LISTCLIENTS_RAW_REQUEST))
        return COMMAND_RAW_SHOW_LISTENERS;
    else if(!strcmp(command, LISTCLIENTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_SHOW_LISTENERS;
    else if(!strcmp(command, STATS_RAW_REQUEST))
133
        return COMMAND_RAW_STATS;
134
135
    else if(!strcmp(command, STATS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
136
137
    else if(!strcmp(command, "stats.xml")) /* The old way */
        return COMMAND_RAW_STATS;
138
139
140
141
142
    else if(!strcmp(command, LISTMOUNTS_RAW_REQUEST))
        return COMMAND_RAW_LIST_MOUNTS;
    else if(!strcmp(command, LISTMOUNTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_LIST_MOUNTS;
    else if(!strcmp(command, STREAMLIST_RAW_REQUEST))
143
        return COMMAND_RAW_LISTSTREAM;
144
145
    else if(!strcmp(command, STREAMLIST_PLAINTEXT_REQUEST))
        return COMMAND_PLAINTEXT_LISTSTREAM;
146
147
148
149
150
151
152
153
154
155
156
157
    else if(!strcmp(command, MOVECLIENTS_RAW_REQUEST))
        return COMMAND_RAW_MOVE_CLIENTS;
    else if(!strcmp(command, MOVECLIENTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_MOVE_CLIENTS;
    else if(!strcmp(command, KILLCLIENT_RAW_REQUEST))
        return COMMAND_RAW_KILL_CLIENT;
    else if(!strcmp(command, KILLCLIENT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_KILL_CLIENT;
    else if(!strcmp(command, KILLSOURCE_RAW_REQUEST))
        return COMMAND_RAW_KILL_SOURCE;
    else if(!strcmp(command, KILLSOURCE_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_KILL_SOURCE;
158
159
160
161
    else if(!strcmp(command, MANAGEAUTH_RAW_REQUEST))
        return COMMAND_RAW_MANAGEAUTH;
    else if(!strcmp(command, MANAGEAUTH_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_MANAGEAUTH;
162
163
164
165
    else if(!strcmp(command, UPDATEMETADATA_RAW_REQUEST))
        return COMMAND_RAW_UPDATEMETADATA;
    else if(!strcmp(command, UPDATEMETADATA_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_UPDATEMETADATA;
166
167
    else if(!strcmp(command, BUILDM3U_RAW_REQUEST))
        return COMMAND_BUILDM3U;
168
169
170
171
    else if(!strcmp(command, DEFAULT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
    else if(!strcmp(command, DEFAULT_RAW_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
172
173
174
175
    else
        return COMMAND_ERROR;
}

176
static void command_fallback(client_t *client, source_t *source, int response);
177
static void command_metadata(client_t *client, source_t *source, int response);
178
static void command_shoutcast_metadata(client_t *client, source_t *source);
179
180
181
182
183
184
185
186
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);
static void command_stats(client_t *client, int response);
static void command_list_mounts(client_t *client, int response);
static void command_kill_client(client_t *client, source_t *source,
        int response);
187
188
static void command_manageauth(client_t *client, source_t *source,
        int response);
189
190
static void command_buildm3u(client_t *client, source_t *source,
        int response);
191
192
static void command_kill_source(client_t *client, source_t *source,
        int response);
193
194
static void command_updatemetadata(client_t *client, source_t *source,
        int response);
195
196
197
static void admin_handle_mount_request(client_t *client, source_t *source,
        int command);
static void admin_handle_general_request(client_t *client, int command);
198
199
200
static void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template);

201
202
203
204
/* 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)
205
206
207
208
209
210
211
212
213
214
215
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

    doc = xmlNewDoc("1.0");
    xmlnode = xmlNewDocNode(doc, NULL, "icestats", NULL);
    xmlDocSetRootElement(doc, xmlnode);
216

217
218
    if (mount) {
        xmlNewChild(xmlnode, NULL, "current_source", mount);
219
220
221
222
223
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
224
225
226
227
228
229
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

230
        if (source->running || source->on_demand)
231
        {
232
233
234
            ice_config_t *config;
            mount_proxy *mountinfo;

235
236
            srcnode = xmlNewChild(xmlnode, NULL, "source", NULL);
            xmlSetProp(srcnode, "mount", source->mount);
237

238
            xmlNewChild(srcnode, NULL, "fallback", 
239
240
                    (source->fallback_mount != NULL)?
                    source->fallback_mount:"");
241
            snprintf (buf, sizeof(buf), "%lu", source->listeners);
242
            xmlNewChild(srcnode, NULL, "listeners", buf);
243

Karl Heyes's avatar
Karl Heyes committed
244
            config = config_get_config();
245
246
247
248
249
250
251
252
            mountinfo = config_find_mount (config, source->mount);
            if (mountinfo && mountinfo->auth)
            {
                xmlNewChild(srcnode, NULL, "authenticator",
                        mountinfo->auth->type);
            }
            config_release_config();

253
254
            if (source->running)
            {
255
                if (source->client) 
Karl Heyes's avatar
Karl Heyes committed
256
257
258
259
260
                {
                    snprintf (buf, sizeof(buf), "%lu",
                            (unsigned long)(now - source->con->con_time));
                    xmlNewChild (srcnode, NULL, "Connected", buf);
                }
261
262
263
                xmlNewChild (srcnode, NULL, "content-type", 
                        source->format->contenttype);
            }
264
        }
265
266
267
268
269
        node = avl_get_next(node);
    }
    return(doc);
}

270
static void admin_send_response(xmlDocPtr doc, client_t *client, 
271
272
        int response, char *xslt_template)
{
273
274
275
276
277
278
    if (response == RAW)
    {
        xmlChar *buff = NULL;
        int len = 0;
        unsigned int buf_len;
        const char *http = "HTTP/1.0 200 OK\r\n"
279
               "Content-Type: text/xml\r\n"
280
281
282
               "Content-Length: ";
        xmlDocDumpMemory(doc, &buff, &len);
        buf_len = strlen (http) + len + 20;
Karl Heyes's avatar
Karl Heyes committed
283
        client_set_queue (client, NULL);
284
        client->refbuf = refbuf_new (buf_len);
285
286
        len = snprintf (client->refbuf->data, buf_len, "%s%d\r\n\r\n%s", http, len, buff);
        client->refbuf->len = len;
287
288
289
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
290
    }
291
292
293
294
295
296
297
298
    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;
299
300
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
301
            config->adminroot_dir, PATH_SEPARATOR, xslt_template);
302
        config_release_config();
303

304
305
306
307
308
        DEBUG1("Sending XSLT (%s)", fullpath_xslt_template);
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
309
310


311
312
313
314
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;
315
    int noauth = 0;
316

317
318
319
    DEBUG1("Admin request (%s)", uri);
    if (!((strcmp(uri, "/admin.cgi") == 0) ||
         (strncmp("/admin/", uri, 7) == 0))) {
320
321
322
323
324
        ERROR0("Internal error: admin request isn't");
        client_send_401(client);
        return;
    }

325
326
327
328
329
330
    if (strcmp(uri, "/admin.cgi") == 0) {
        command_string = uri + 1;
    }
    else {
        command_string = uri + 7;
    }
331

332
    DEBUG1("Got command (%s)", command_string);
333
334
335
336
337
338
339
340
341
    command = admin_get_command(command_string);

    if(command < 0) {
        ERROR1("Error parsing command string or unrecognised command: %s",
                command_string);
        client_send_400(client, "Unrecognised command");
        return;
    }

342
343
    if (command == COMMAND_SHOUTCAST_METADATA_UPDATE) {

344
345
346
347
348
349
350
351
        ice_config_t *config;
        char *pass = httpp_get_query_param (client->parser, "pass");
        if (pass == NULL)
        {
            client_send_400 (client, "missing pass parameter");
            return;
        }
        config = config_get_config ();
352
        httpp_set_query_param (client->parser, "mount", config->shoutcast_mount);
353
354
        httpp_setvar (client->parser, HTTPP_VAR_PROTOCOL, "ICY");
        httpp_setvar (client->parser, HTTPP_VAR_ICYPASSWORD, pass);
355
        config_release_config ();
356
357
    }

358
359
    mount = httpp_get_query_param(client->parser, "mount");

360
361
362
    if(mount != NULL) {
        source_t *source;

363
364
365
        if (command == COMMAND_BUILDM3U) {
            noauth = 1;
        }
366
        /* This is a mount request, handle it as such */
367
368
369
370
371
372
373
374
        if (!noauth) {
            if(!connection_check_admin_pass(client->parser)) {
                if(!connection_check_source_pass(client->parser, mount)) {
                    INFO1("Bad or missing password on mount modification admin "
                          "request (command: %s)", command_string);
                    client_send_401(client);
                    return;
                }
375
            }
376
377
378
        }
        
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
379
        source = source_find_mount_raw(mount);
380

381
382
        if (source == NULL)
        {
383
384
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
385
            avl_tree_unlock(global.source_tree);
386
387
            client_send_400(client, "Source does not exist");
        }
388
389
        else
        {
390
            if (source->running == 0 && source->on_demand == 0)
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
            {
                avl_tree_unlock (global.source_tree);
                INFO2("Received admin command %s on unavailable mount \"%s\"",
                        command_string, mount);
                client_send_400 (client, "Source is not available");
                return;
            }
            if (command == COMMAND_SHOUTCAST_METADATA_UPDATE &&
                    source->shoutcast_compat == 0)
            {
                avl_tree_unlock (global.source_tree);
                ERROR0 ("illegal change of metadata on non-shoutcast "
                        "compatible stream");
                client_send_400 (client, "illegal metadata call");
                return;
406
            }
407
408
409
            INFO2("Received admin command %s on mount \"%s\"", 
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
410
            avl_tree_unlock(global.source_tree);
411
        }
412
413
414
    }
    else {

415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
        if (command == COMMAND_PLAINTEXT_LISTSTREAM) {
        /* this request is used by a slave relay to retrieve
           mounts from the master, so handle this request
           validating against the relay password */
            if(!connection_check_relay_pass(client->parser)) {
                INFO1("Bad or missing password on admin command "
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
        }
        else {
            if(!connection_check_admin_pass(client->parser)) {
                INFO1("Bad or missing password on admin command "
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
433
434
435
436
437
438
439
440
441
442
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
443
            command_stats(client, RAW);
444
            break;
445
446
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
447
448
            break;
        case COMMAND_RAW_LISTSTREAM:
449
450
            command_list_mounts(client, RAW);
            break;
451
452
453
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
454
455
456
457
458
459
460
461
462
463
464
        case COMMAND_TRANSFORMED_STATS:
            command_stats(client, TRANSFORMED);
            break;
        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);
465
            break;
466
467
468
469
470
471
472
473
474
475
476
        default:
            WARN0("General admin request not recognised");
            client_send_400(client, "Unknown admin request");
            return;
    }
}

static void admin_handle_mount_request(client_t *client, source_t *source, 
        int command)
{
    switch(command) {
477
478
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
479
            break;
480
481
482
483
484
        case COMMAND_RAW_METADATA_UPDATE:
            command_metadata(client, source, RAW);
            break;
        case COMMAND_TRANSFORMED_METADATA_UPDATE:
            command_metadata(client, source, TRANSFORMED);
485
            break;
486
487
488
        case COMMAND_SHOUTCAST_METADATA_UPDATE:
            command_shoutcast_metadata(client, source);
            break;
489
490
491
492
493
494
495
496
        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);
497
            break;
498
499
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
500
            break;
501
502
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
503
            break;
504
505
506
507
508
509
510
511
512
513
514
        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);
515
            break;
516
517
518
519
520
521
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
522
523
524
525
526
527
        case COMMAND_TRANSFORMED_UPDATEMETADATA:
            command_updatemetadata(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_UPDATEMETADATA:
            command_updatemetadata(client, source, RAW);
            break;
528
529
530
        case COMMAND_BUILDM3U:
            command_buildm3u(client, source, RAW);
            break;
531
532
533
        default:
            WARN0("Mount request not recognised");
            client_send_400(client, "Mount request unknown");
534
            break;
535
536
537
538
539
540
541
542
543
544
545
    }
}

#define COMMAND_REQUIRE(client,name,var) \
    do { \
        (var) = httpp_get_query_param((client)->parser, (name)); \
        if((var) == NULL) { \
            client_send_400((client), "Missing parameter"); \
            return; \
        } \
    } while(0);
546
547
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
548

549
static void html_success(client_t *client, char *message)
550
551
{
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
552
    snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
553
554
555
            "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n" 
            "<html><head><title>Admin request successful</title></head>"
            "<body><p>%s</p></body></html>", message);
Karl Heyes's avatar
Karl Heyes committed
556
557
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
558
559
}

560

561
562
static void command_move_clients(client_t *client, source_t *source,
    int response)
563
564
565
{
    char *dest_source;
    source_t *dest;
566
567
568
569
570
571
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
572
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
573
574
575
576
577
578
579
580
581
582
        parameters_passed = 1;
    }
    DEBUG1("Done optional check (%d)", parameters_passed);
    if (!parameters_passed) {
        doc = admin_build_sourcelist(source->mount);
        admin_send_response(doc, client, response, 
             MOVECLIENTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
        return;
    }
583

584
585
586
587
588
589
590
591
    dest = source_find_mount (dest_source);

    if (dest == NULL)
    {
        client_send_400 (client, "No such destination");
        return;
    }

Karl Heyes's avatar
Karl Heyes committed
592
    if (strcmp (dest->mount, source->mount) == 0)
593
594
595
596
597
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

598
    if (dest->running == 0 && dest->on_demand == 0)
599
600
    {
        client_send_400 (client, "Destination not running");
601
602
603
        return;
    }

604
605
    INFO2 ("source is \"%s\", destination is \"%s\"", source->mount, dest->mount);

606
607
608
609
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

610
    source_move_clients (source, dest);
611

612
    memset(buf, '\000', sizeof(buf));
613
614
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
615
616
617
618
619
620
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
621
622
}

623
624
static void command_show_listeners(client_t *client, source_t *source,
    int response)
625
{
626
627
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
628
629
    avl_node *client_node;
    client_t *current;
630
631
    char buf[22];
    char *userAgent = NULL;
632
633
    time_t now = time(NULL);

634
635
636
637
638
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
639

640
    memset(buf, '\000', sizeof(buf));
641
    snprintf (buf, sizeof(buf), "%lu", source->listeners);
642
    xmlNewChild(srcnode, NULL, "Listeners", buf);
643
644
645
646
647
648

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
649
650
651
652
653
654
655
656
657
658
        listenernode = xmlNewChild(srcnode, NULL, "listener", NULL);
        xmlNewChild(listenernode, NULL, "IP", current->con->ip);
        userAgent = httpp_getvar(current->parser, "user-agent");
        if (userAgent) {
            xmlNewChild(listenernode, NULL, "UserAgent", userAgent);
        }
        else {
            xmlNewChild(listenernode, NULL, "UserAgent", "Unknown");
        }
        memset(buf, '\000', sizeof(buf));
Karl Heyes's avatar
Karl Heyes committed
659
        snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - current->con->con_time));
660
661
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
662
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
663
        xmlNewChild(listenernode, NULL, "ID", buf);
664
665
666
        if (current->username) {
            xmlNewChild(listenernode, NULL, "username", current->username);
        }
667
668
669
670
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
671
672
673
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
674
675
}

676
677
678
679
680
681
682
683
684
685
686
static void command_buildm3u(client_t *client, source_t *source,
    int response)
{
    char *username = NULL;
    char *password = NULL;
    ice_config_t *config;

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

    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
687
    config = config_get_config();
688
    snprintf (client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
689
690
691
692
693
694
        "HTTP/1.0 200 OK\r\n"
        "Content-Type: audio/x-mpegurl\r\n"
        "Content-Disposition = attachment; filename=listen.m3u\r\n\r\n" 
        "http://%s:%s@%s:%d%s\r\n",
        username,
        password,
Karl Heyes's avatar
Karl Heyes committed
695
696
        config->hostname,
        config->port,
697
698
        source->mount
    );
Karl Heyes's avatar
Karl Heyes committed
699
    config_release_config();
700

Karl Heyes's avatar
Karl Heyes committed
701
702
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
703
}
704
705


706
707
708
709
710
711
712
713
714
715
static void command_manageauth(client_t *client, source_t *source,
    int response)
{
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, msgnode;
    char *action = NULL;
    char *username = NULL;
    char *password = NULL;
    char *message = NULL;
    int ret = AUTH_OK;
716
717
    ice_config_t *config = config_get_config ();
    mount_proxy *mountinfo = config_find_mount (config, source->mount);
718
719

    if((COMMAND_OPTIONAL(client, "action", action))) {
720
721
722
723
724
725
726
        if (mountinfo == NULL || mountinfo->auth == NULL)
        {
            WARN1 ("manage auth request for %s but no facility available", source->mount);
            config_release_config ();
            client_send_404 (client, "no such auth facility");
            return;
        }
727
728
729
        if (!strcmp(action, "add")) {
            COMMAND_REQUIRE(client, "username", username);
            COMMAND_REQUIRE(client, "password", password);
730
            ret = mountinfo->auth->adduser(mountinfo->auth, username, password);
731
732
733
734
735
736
737
738
739
740
741
742
            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");
            }
        }
        if (!strcmp(action, "delete")) {
            COMMAND_REQUIRE(client, "username", username);
743
            ret = mountinfo->auth->deleteuser(mountinfo->auth, username);
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
            }
            if (ret == AUTH_USERDELETED) {
                message = strdup("User deleted");
            }
        }
    }

    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);

    if (message) {
        msgnode = xmlNewChild(node, NULL, "iceresponse", NULL);
        xmlNewChild(msgnode, NULL, "message", message);
    }

    xmlDocSetRootElement(doc, node);

765
766
767
768
    if (mountinfo && mountinfo->auth && mountinfo->auth->listuser)
        mountinfo->auth->listuser (mountinfo->auth, srcnode);

    config_release_config ();
769
770
771
772
773
774
775
776
777

    admin_send_response(doc, client, response, 
        MANAGEAUTH_TRANSFORMED_REQUEST);
    if (message) {
        free(message);
    }
    xmlFreeDoc(doc);
}

778
779
static void command_kill_source(client_t *client, source_t *source,
    int response)
780
{
781
782
783
784
785
786
787
788
789
    xmlDocPtr doc;
    xmlNodePtr node;

    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlNewChild(node, NULL, "message", "Source Removed");
    xmlNewChild(node, NULL, "return", "1");
    xmlDocSetRootElement(doc, node);

790
791
    source->running = 0;

792
793
794
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
795
796
}

797
798
static void command_kill_client(client_t *client, source_t *source,
    int response)
799
800
801
802
{
    char *idtext;
    int id;
    client_t *listener;
803
804
805
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
806
807
808
809
810
811
812

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

813
814
815
816
817
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

818
819
820
821
822
823
824
    if(listener != NULL) {
        INFO1("Admin request: client %d removed", id);

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

841
842
static void command_fallback(client_t *client, source_t *source,
    int response)
843
844
845
846
847
848
849
850
851
852
853
854
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

855
    html_success(client, "Fallback configured");
856
857
}

858
859
static void command_metadata(client_t *client, source_t *source,
    int response)
860
861
{
    char *action;
862
863
    char *song, *title, *artist;
    format_plugin_t *plugin;
864
865
866
867
868
869
    xmlDocPtr doc;
    xmlNodePtr node;

    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
870
871
872
873

    DEBUG0("Got metadata update request");

    COMMAND_REQUIRE(client, "mode", action);
874
875
876
    COMMAND_OPTIONAL(client, "song", song);
    COMMAND_OPTIONAL(client, "title", title);
    COMMAND_OPTIONAL(client, "artist", artist);
877

Karl Heyes's avatar
Karl Heyes committed
878
879
    if (strcmp (action, "updinfo") != 0)
    {
880
881
882
883
884
        xmlNewChild(node, NULL, "message", "No such action");
        xmlNewChild(node, NULL, "return", "0");
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
885
886
        return;
    }
887

888
    plugin = source->format;
Karl Heyes's avatar
Karl Heyes committed
889

890
891
892
893
894
    if (plugin && plugin->set_tag)
    {
        if (song)
        {
            plugin->set_tag (plugin, "song", song);
895
            INFO2 ("Metadata on mountpoint %s changed to \"%s\"", source->mount, song);
896
897
898
899
900
901
902
903
904
905
906
907
908
909
        }
        else
        {
            if (artist && title)
            {
                plugin->set_tag (plugin, "title", title);
                plugin->set_tag (plugin, "artist", artist);
                INFO3("Metadata on mountpoint %s changed to \"%s - %s\"",
                        source->mount, artist, title);
            }
        }
    }
    else
    {
910
911
912
913
914
915
916
        xmlNewChild(node, NULL, "message", 
            "Mountpoint will not accept URL updates");
        xmlNewChild(node, NULL, "return", "1");
        admin_send_response(doc, client, response, 
            ADMIN_XSL_RESPONSE);
        xmlFreeDoc(doc);
        return;
917
    }
918
919
920
921
922
923

    xmlNewChild(node, NULL, "message", "Metadata update successful");
    xmlNewChild(node, NULL, "return", "1");
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
924
925
}

926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
static void command_shoutcast_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;

    DEBUG0("Got shoutcast metadata update request");

    COMMAND_REQUIRE(client, "mode", action);
    COMMAND_REQUIRE(client, "song", value);

    if (strcmp (action, "updinfo") != 0)
    {
        client_send_400 (client, "No such action");
        return;
    }

942
943
944
    if (source->format && source->format->set_tag)
    {
        source->format->set_tag (source->format, "title", value);
945

946
947
948
949
950
951
952
953
        DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
                source->mount, value);
        html_success(client, "Metadata update successful");
    }
    else
    {
        client_send_400 (client, "mountpoint will not accept URL updates");
    }
954
955
}

956
957
958
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

959
960
    DEBUG0("Stats request, sending xml stats");

961
    stats_get_xml(&doc, 1);
962
963
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
964
965
966
    return;
}

Karl Heyes's avatar
Karl Heyes committed
967
968
static void command_list_mounts(client_t *client, int response)
{
969
970
    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
971
    avl_tree_rlock (global.source_tree);
972
973
    if (response == PLAINTEXT)
    {
974
        char *buf;
Karl Heyes's avatar
Karl Heyes committed
975
        int remaining = PER_CLIENT_REFBUF_SIZE;
976
        int ret;
977
978
        ice_config_t *config = config_get_config ();
        mount_proxy *mountinfo = config->mounts;
979
980
981
982
983
984

        buf = client->refbuf->data;
        ret = snprintf (buf, remaining,
                "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");

        while (mountinfo && ret > 0 && ret < remaining)
Karl Heyes's avatar
Karl Heyes committed
985
        {
986
987
988
989
990
991
992
993
994
            mount_proxy *current = mountinfo;
            source_t *source;
            mountinfo = mountinfo->next;

            /* now check that a source is available */
            source = source_find_mount (current->mountname);

            if (source == NULL)
                continue;
995
            if (source->running == 0 && source->on_demand == 0)
996
997
                continue;
            if (source->hidden)
998
                continue;
Karl Heyes's avatar
Karl Heyes committed
999
1000
            remaining -= ret;
            buf += ret;
For faster browsing, not all history is shown. View entire blame