admin.c 27.4 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
36

#include "format.h"
37
#include "format_mp3.h"
38
39

#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_METADATA_UPDATE     2
52
53
#define COMMAND_RAW_SHOW_LISTENERS  3
#define COMMAND_RAW_MOVE_CLIENTS    4
54
#define COMMAND_RAW_MANAGEAUTH      5
55
56
57
58

#define COMMAND_TRANSFORMED_FALLBACK        50
#define COMMAND_TRANSFORMED_SHOW_LISTENERS  53
#define COMMAND_TRANSFORMED_MOVE_CLIENTS    54
59
#define COMMAND_TRANSFORMED_MANAGEAUTH      55
60
61

/* Global commands */
Karl Heyes's avatar
Karl Heyes committed
62
63
64
65
66
67
68
#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
69

70
/* Client management commands */
Karl Heyes's avatar
Karl Heyes committed
71
72
73
74
#define COMMAND_RAW_KILL_CLIENT             301
#define COMMAND_RAW_KILL_SOURCE             302
#define COMMAND_TRANSFORMED_KILL_CLIENT     401
#define COMMAND_TRANSFORMED_KILL_SOURCE     402
75

76
77
78
/* Admin commands requiring no auth */
#define COMMAND_BUILDM3U                    501

79
80
81
82
83
84
85
86
87
88
89
#define FALLBACK_RAW_REQUEST "fallbacks"
#define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl"
#define METADATA_REQUEST "metadata"
#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"
90
#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
91
92
93
94
95
96
97
#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"
98
99
#define MANAGEAUTH_RAW_REQUEST "manageauth"
#define MANAGEAUTH_TRANSFORMED_REQUEST "manageauth.xsl"
100
101
#define DEFAULT_RAW_REQUEST ""
#define DEFAULT_TRANSFORMED_REQUEST ""
102
#define BUILDM3U_RAW_REQUEST "buildm3u"
103
104
105

#define RAW         1
#define TRANSFORMED 2
106
#define PLAINTEXT   3
107
108
int admin_get_command(char *command)
{
109
110
111
112
113
    if(!strcmp(command, FALLBACK_RAW_REQUEST))
        return COMMAND_RAW_FALLBACK;
    else if(!strcmp(command, FALLBACK_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_FALLBACK;
    else if(!strcmp(command, METADATA_REQUEST))
114
        return COMMAND_METADATA_UPDATE;
115
116
117
118
119
    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))
120
        return COMMAND_RAW_STATS;
121
122
    else if(!strcmp(command, STATS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
123
124
    else if(!strcmp(command, "stats.xml")) /* The old way */
        return COMMAND_RAW_STATS;
125
126
127
128
129
    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))
130
        return COMMAND_RAW_LISTSTREAM;
131
132
    else if(!strcmp(command, STREAMLIST_PLAINTEXT_REQUEST))
        return COMMAND_PLAINTEXT_LISTSTREAM;
133
134
135
136
137
138
139
140
141
142
143
144
    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;
145
146
147
148
    else if(!strcmp(command, MANAGEAUTH_RAW_REQUEST))
        return COMMAND_RAW_MANAGEAUTH;
    else if(!strcmp(command, MANAGEAUTH_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_MANAGEAUTH;
149
150
    else if(!strcmp(command, BUILDM3U_RAW_REQUEST))
        return COMMAND_BUILDM3U;
151
152
153
154
    else if(!strcmp(command, DEFAULT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
    else if(!strcmp(command, DEFAULT_RAW_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
155
156
157
158
    else
        return COMMAND_ERROR;
}

159
static void command_fallback(client_t *client, source_t *source, int response);
160
static void command_metadata(client_t *client, source_t *source);
161
162
163
164
165
166
167
168
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);
169
170
static void command_manageauth(client_t *client, source_t *source,
        int response);
171
172
static void command_buildm3u(client_t *client, source_t *source,
        int response);
173
174
static void command_kill_source(client_t *client, source_t *source,
        int response);
175
176
177
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);
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
static void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template);
static void html_write(client_t *client, char *fmt, ...);

xmlDocPtr admin_build_sourcelist(char *current_source)
{
    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);
194

195
196
197
198
199
200
201
    if (current_source) {
        xmlNewChild(xmlnode, NULL, "current_source", current_source);
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
202
203
204
205
        if (source->running)
        {
            srcnode = xmlNewChild(xmlnode, NULL, "source", NULL);
            xmlSetProp(srcnode, "mount", source->mount);
206

207
            xmlNewChild(srcnode, NULL, "fallback", 
208
209
                    (source->fallback_mount != NULL)?
                    source->fallback_mount:"");
210
211
            snprintf(buf, sizeof(buf), "%ld", source->listeners);
            xmlNewChild(srcnode, NULL, "listeners", buf);
Karl Heyes's avatar
Karl Heyes committed
212
213
            snprintf(buf, sizeof(buf), "%lu",
                    (unsigned long)(now - source->con->con_time));
214
215
216
            xmlNewChild(srcnode, NULL, "Connected", buf);
            xmlNewChild(srcnode, NULL, "Format", 
                    source->format->format_description);
217
218
219
220
            if (source->authenticator) {
                xmlNewChild(srcnode, NULL, "authenticator", 
                    source->authenticator->type);
            }
221
        }
222
223
224
225
226
227
228
229
        node = avl_get_next(node);
    }
    return(doc);
}

void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template)
{
230
    xmlChar *buff = NULL;
231
232
233
234
235
236
237
238
    int len = 0;
    ice_config_t *config;
    char *fullpath_xslt_template;
    int fullpath_xslt_template_len;
    char *adminwebroot;

    client->respcode = 200;
    if (response == RAW) {
239
        xmlDocDumpMemory(doc, &buff, &len);
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Length: %d\r\n"
               "Content-Type: text/xml\r\n"
               "\r\n", len);
        html_write(client, buff);
    }
    if (response == TRANSFORMED) {
        config = config_get_config();
        adminwebroot = config->adminroot_dir;
        config_release_config();
        fullpath_xslt_template_len = strlen(adminwebroot) + 
            strlen(xslt_template) + 2;
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        memset(fullpath_xslt_template, '\000', fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
            adminwebroot, PATH_SEPARATOR, xslt_template);
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Type: text/html\r\n"
               "\r\n");
        DEBUG1("Sending XSLT (%s)", fullpath_xslt_template);
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
    if (buff) {
        xmlFree(buff);
    }
}
267
268
269
270
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;
271
    int noauth = 0;
272
273
274
275
276
277
278
279
280

    if(strncmp("/admin/", uri, 7)) {
        ERROR0("Internal error: admin request isn't");
        client_send_401(client);
        return;
    }

    command_string = uri + 7;

281
    DEBUG1("Got command (%s)", command_string);
282
283
284
285
286
287
288
289
290
291
292
293
294
295
    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;
    }

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

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

296
297
298
        if (command == COMMAND_BUILDM3U) {
            noauth = 1;
        }
299
        /* This is a mount request, handle it as such */
300
301
302
303
304
305
306
307
        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;
                }
308
            }
309
310
311
        }
        
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
312
        source = source_find_mount_raw(mount);
313

314
315
        if (source == NULL)
        {
316
317
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
318
            avl_tree_unlock(global.source_tree);
319
320
            client_send_400(client, "Source does not exist");
        }
321
322
        else
        {
323
324
325
326
327
328
329
330
            if (source->running == 0)
            {
                INFO2("Received admin command %s on unavailable mount \"%s\"",
                        command_string, mount);
                avl_tree_unlock (global.source_tree);
                client_send_400 (client, "Source is not available");
                return;
            }
331
332
333
            INFO2("Received admin command %s on mount \"%s\"", 
                    command_string, mount);
            admin_handle_mount_request(client, source, command);
334
            avl_tree_unlock(global.source_tree);
335
        }
336
337
338
    }
    else {

339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
        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;
            }
357
358
359
360
361
362
363
364
365
366
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
367
            command_stats(client, RAW);
368
            break;
369
370
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
371
372
            break;
        case COMMAND_RAW_LISTSTREAM:
373
374
            command_list_mounts(client, RAW);
            break;
375
376
377
        case COMMAND_PLAINTEXT_LISTSTREAM:
            command_list_mounts(client, PLAINTEXT);
            break;
378
379
380
381
382
383
384
385
386
387
388
        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);
389
            break;
390
391
392
393
394
395
396
397
398
399
400
        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) {
401
402
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
403
404
405
406
            break;
        case COMMAND_METADATA_UPDATE:
            command_metadata(client, source);
            break;
407
408
409
410
411
412
413
414
        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);
415
            break;
416
417
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
418
            break;
419
420
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
421
            break;
422
423
424
425
426
427
428
429
430
431
432
        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);
433
            break;
434
435
436
437
438
439
        case COMMAND_TRANSFORMED_MANAGEAUTH:
            command_manageauth(client, source, TRANSFORMED);
            break;
        case COMMAND_RAW_MANAGEAUTH:
            command_manageauth(client, source, RAW);
            break;
440
441
442
        case COMMAND_BUILDM3U:
            command_buildm3u(client, source, RAW);
            break;
443
444
445
        default:
            WARN0("Mount request not recognised");
            client_send_400(client, "Mount request unknown");
446
            break;
447
448
449
450
451
452
453
454
455
456
457
    }
}

#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);
458
459
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
460

461
static void html_success(client_t *client, char *message)
462
463
464
465
466
467
468
469
470
471
472
473
{
    int bytes;

    client->respcode = 200;
    bytes = sock_write(client->con->sock, 
            "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);
    if(bytes > 0) client->con->sent_bytes = bytes;
    client_destroy(client);
}

474
475
476
477
478
479
480
481
482
483
484
static void html_write(client_t *client, char *fmt, ...)
{
    int bytes;
    va_list ap;

    va_start(ap, fmt);
    bytes = sock_write_fmt(client->con->sock, fmt, ap);
    va_end(ap);
    if(bytes > 0) client->con->sent_bytes = bytes;
}

485
486
static void command_move_clients(client_t *client, source_t *source,
    int response)
487
488
489
{
    char *dest_source;
    source_t *dest;
490
491
492
493
494
495
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
496
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
497
498
499
500
501
502
503
504
505
506
507
        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);
        client_destroy(client);
        return;
    }
508

509
510
511
512
513
514
515
516
    dest = source_find_mount (dest_source);

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

Karl Heyes's avatar
Karl Heyes committed
517
    if (strcmp (dest->mount, source->mount) == 0)
518
519
520
521
522
    {
        client_send_400 (client, "supplied mountpoints are identical");
        return;
    }

523
524
525
    if (dest->running == 0)
    {
        client_send_400 (client, "Destination not running");
526
527
528
        return;
    }

529
530
531
532
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

533
    source_move_clients (source, dest);
534

535
    memset(buf, '\000', sizeof(buf));
536
537
    snprintf (buf, sizeof(buf), "Clients moved from %s to %s",
            source->mount, dest_source);
538
539
540
541
542
543
544
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
545
546
}

547
548
static void command_show_listeners(client_t *client, source_t *source,
    int response)
549
{
550
551
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
552
553
    avl_node *client_node;
    client_t *current;
554
555
    char buf[22];
    char *userAgent = NULL;
556
557
    time_t now = time(NULL);

558
559
560
561
562
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
563

564
    memset(buf, '\000', sizeof(buf));
565
    snprintf(buf, sizeof(buf)-1, "%ld", source->listeners);
566
    xmlNewChild(srcnode, NULL, "Listeners", buf);
567
568
569
570
571
572

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
573
574
575
576
577
578
579
580
581
582
        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));
583
        snprintf(buf, sizeof(buf)-1, "%ld", now - current->con->con_time);
584
585
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
586
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
587
        xmlNewChild(listenernode, NULL, "ID", buf);
588
589
590
        if (current->username) {
            xmlNewChild(listenernode, NULL, "username", current->username);
        }
591
592
593
594
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
595
596
597
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
598
599
600
    client_destroy(client);
}

601
602
603
604
605
606
607
608
609
610
611
612
613
static void command_buildm3u(client_t *client, source_t *source,
    int response)
{
    char *username = NULL;
    char *password = NULL;
    char *host = NULL;
    int port = 0;
    ice_config_t *config;

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

    config = config_get_config();
614
    host = strdup(config->hostname);
615
    port = config->port;
616
    config_release_config();
617
618
619
620
621
622
623
624
625
626
627
628
629

    client->respcode = 200;
    sock_write(client->con->sock,
        "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,
        host,
        port,
        source->mount
    );
630
631

    free(host);
632
633
    client_destroy(client);
}
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
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;

    if((COMMAND_OPTIONAL(client, "action", action))) {
        if (!strcmp(action, "add")) {
            COMMAND_REQUIRE(client, "username", username);
            COMMAND_REQUIRE(client, "password", password);
            ret = auth_adduser(source, username, password);
            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);
            ret = auth_deleteuser(source, username);
            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);

    auth_get_userlist(source, srcnode);

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

695
696
static void command_kill_source(client_t *client, source_t *source,
    int response)
697
{
698
699
700
701
702
703
704
705
706
    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);

707
708
    source->running = 0;

709
710
711
712
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
713
714
}

715
716
static void command_kill_client(client_t *client, source_t *source,
    int response)
717
718
719
720
{
    char *idtext;
    int id;
    client_t *listener;
721
722
723
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
724
725
726
727
728
729
730

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

731
732
733
734
735
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

736
737
738
739
740
741
742
    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;
743
744
745
746
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "1");
747
748
    }
    else {
749
750
751
752
        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");
753
    }
754
755
756
757
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
758
759
}

760
761
static void command_fallback(client_t *client, source_t *source,
    int response)
762
763
764
765
766
767
768
769
770
771
772
773
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

774
    html_success(client, "Fallback configured");
775
776
777
778
779
780
}

static void command_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
781
    mp3_state *state;
782
783
784
785
786
787

    DEBUG0("Got metadata update request");

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

788
    if (source->format->type != FORMAT_TYPE_MP3)
Karl Heyes's avatar
Karl Heyes committed
789
790
    {
        client_send_400 (client, "Not mp3, cannot update metadata");
791
792
793
        return;
    }

Karl Heyes's avatar
Karl Heyes committed
794
795
796
    if (strcmp (action, "updinfo") != 0)
    {
        client_send_400 (client, "No such action");
797
798
        return;
    }
799
800
801
802
803
804
805
806

    state = source->format->_state;

    thread_mutex_lock(&(state->lock));
    free(state->metadata);
    state->metadata = strdup(value);
    state->metadata_age++;
    thread_mutex_unlock(&(state->lock));
807

808
809
    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
810
    stats_event(source->mount, "title", value);
Karl Heyes's avatar
Karl Heyes committed
811

812
813
    /* If we get an update on the mountpoint, force a
       yp touch */
814
    yp_touch (source->mount);
815

816
    html_success(client, "Metadata update successful");
817
818
}

819
820
821
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

822
823
    DEBUG0("Stats request, sending xml stats");

824
825
826
    stats_get_xml(&doc);
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
827
828
829
830
    client_destroy(client);
    return;
}

Karl Heyes's avatar
Karl Heyes committed
831
832
static void command_list_mounts(client_t *client, int response)
{
833
834
    avl_node *node;
    source_t *source;
835
836
837

    DEBUG0("List mounts request");

Karl Heyes's avatar
Karl Heyes committed
838
    avl_tree_rlock (global.source_tree);
839
840
    if (response == PLAINTEXT)
    {
Karl Heyes's avatar
Karl Heyes committed
841
842
843
844
845
        char buffer [4096], *buf = buffer;
        unsigned remaining = sizeof (buffer);
        int ret = sprintf (buffer,
                "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");

846
        node = avl_get_first(global.source_tree);
Karl Heyes's avatar
Karl Heyes committed
847
848
849
850
        while (node && ret > 0 && ret < remaining)
        {
            remaining -= ret;
            buf += ret;
851
            source = (source_t *)node->key;
Karl Heyes's avatar
Karl Heyes committed
852
            ret = snprintf (buf, remaining, "%s\n", source->mount);
853
854
            node = avl_get_next(node);
        }
Karl Heyes's avatar
Karl Heyes committed
855
856
857
858
859
860
861
862
        avl_tree_unlock (global.source_tree);
        /* handle last line */
        if (ret > 0 && ret < remaining)
        {
            remaining -= ret;
            buf += ret;
        }
        sock_write_bytes (client->con->sock, buffer, sizeof (buffer)-remaining);
863
    }
Karl Heyes's avatar
Karl Heyes committed
864
865
866
867
    else
    {
        xmlDocPtr doc = admin_build_sourcelist(NULL);
        avl_tree_unlock (global.source_tree);
868
869
870
871
872

        admin_send_response(doc, client, response, 
            LISTMOUNTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
    }
873
    client_destroy(client);
874

875
876
877
    return;
}