admin.c 21 KB
Newer Older
1
2
#include <string.h>
#include <stdlib.h>
3
4
#include <stdarg.h>
#include <time.h>
5
6
7
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
8
9
10
11
12
13
14
15
16

#include "config.h"
#include "connection.h"
#include "refbuf.h"
#include "client.h"
#include "source.h"
#include "global.h"
#include "event.h"
#include "stats.h"
17
#include "os.h"
18
#include "xslt.h"
19
20
21
22
23

#include "format.h"
#include "format_mp3.h"

#include "logging.h"
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
24
25
26
#ifdef _WIN32
#define snprintf _snprintf
#endif
27
28
29

#define CATMODULE "admin"

30
31
32
#define COMMAND_ERROR             (-1)

/* Mount-specific commands */
33
#define COMMAND_RAW_FALLBACK        1
34
#define COMMAND_METADATA_UPDATE     2
35
36
37
38
39
40
#define COMMAND_RAW_SHOW_LISTENERS  3
#define COMMAND_RAW_MOVE_CLIENTS    4

#define COMMAND_TRANSFORMED_FALLBACK        50
#define COMMAND_TRANSFORMED_SHOW_LISTENERS  53
#define COMMAND_TRANSFORMED_MOVE_CLIENTS    54
41
42

/* Global commands */
43
#define COMMAND_RAW_LIST_MOUNTS   101
44
#define COMMAND_RAW_STATS         102
45
#define COMMAND_RAW_LISTSTREAM    103
46
47
48
#define COMMAND_TRANSFORMED_LIST_MOUNTS   201
#define COMMAND_TRANSFORMED_STATS         202
#define COMMAND_TRANSFORMED_LISTSTREAM    203
49

50
/* Client management commands */
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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

#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"
#define MOVECLIENTS_RAW_REQUEST "moveclients"
#define MOVECLIENTS_TRANSFORMED_REQUEST "moveclients.xsl"
#define KILLCLIENT_RAW_REQUEST "killclient"
#define KILLCLIENT_TRANSFORMED_REQUEST "killclient.xsl"
#define KILLSOURCE_RAW_REQUEST "killsource"
#define KILLSOURCE_TRANSFORMED_REQUEST "killsource.xsl"
#define ADMIN_XSL_RESPONSE "response.xsl"
#define DEFAULT_RAW_REQUEST ""
#define DEFAULT_TRANSFORMED_REQUEST ""

#define RAW         1
#define TRANSFORMED 2
79
80
int admin_get_command(char *command)
{
81
82
83
84
85
    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))
86
        return COMMAND_METADATA_UPDATE;
87
88
89
90
91
    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))
92
        return COMMAND_RAW_STATS;
93
94
    else if(!strcmp(command, STATS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
95
96
    else if(!strcmp(command, "stats.xml")) /* The old way */
        return COMMAND_RAW_STATS;
97
98
99
100
101
    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))
102
        return COMMAND_RAW_LISTSTREAM;
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
    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;
    else if(!strcmp(command, DEFAULT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
    else if(!strcmp(command, DEFAULT_RAW_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
119
120
121
122
    else
        return COMMAND_ERROR;
}

123
static void command_fallback(client_t *client, source_t *source, int response);
124
static void command_metadata(client_t *client, source_t *source);
125
126
127
128
129
130
131
132
133
134
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);
static void command_kill_source(client_t *client, source_t *source,
        int response);
135
136
137
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);
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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);
154

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
    if (current_source) {
        xmlNewChild(xmlnode, NULL, "current_source", current_source);
    }

    avl_tree_rlock(global.source_tree);

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
        srcnode = xmlNewChild(xmlnode, NULL, "source", NULL);
        xmlSetProp(srcnode, "mount", source->mount);

        xmlNewChild(srcnode, NULL, "fallback", 
                    (source->fallback_mount != NULL)?
                    source->fallback_mount:"");
        memset(buf, '\000', sizeof(buf));
171
        snprintf(buf, sizeof(buf)-1, "%ld", source->listeners);
172
173
        xmlNewChild(srcnode, NULL, "listeners", buf);
        memset(buf, '\000', sizeof(buf));
174
        snprintf(buf, sizeof(buf)-1, "%ld", now - source->con->con_time);
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
        xmlNewChild(srcnode, NULL, "Connected", buf);
        xmlNewChild(srcnode, NULL, "Format", 
            source->format->format_description);
        node = avl_get_next(node);
    }
    avl_tree_unlock(global.source_tree);
    return(doc);
}

void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template)
{
    char *buff = NULL;
    int len = 0;
    ice_config_t *config;
    char *fullpath_xslt_template;
    int fullpath_xslt_template_len;
    char *adminwebroot;

    client->respcode = 200;
    if (response == RAW) {
        xmlDocDumpMemory(doc, (xmlChar **)&buff, &len);
        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);
    }
}
224
225
226
227
228
229
230
231
232
233
234
235
236
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;

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

    command_string = uri + 7;

237
    DEBUG1("Got command (%s)", command_string);
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
    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;

        /* This is a mount request, handle it as such */
253
254
        if(!connection_check_admin_pass(client->parser)) {
            if(!connection_check_source_pass(client->parser, mount)) {
255
                INFO1("Bad or missing password on mount modification admin "
256
257
258
259
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
        }
        
        avl_tree_rlock(global.source_tree);
        source = source_find_mount(mount);
        avl_tree_unlock(global.source_tree);

        if(source == NULL) {
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
            client_send_400(client, "Source does not exist");
            return;
        }

        INFO2("Received admin command %s on mount \"%s\"", 
                command_string, mount);

        admin_handle_mount_request(client, source, command);
    }
    else {

        if(!connection_check_admin_pass(client->parser)) {
281
            INFO1("Bad or missing password on admin command "
282
283
284
285
286
287
288
289
290
291
292
293
294
                  "request (command: %s)", command_string);
            client_send_401(client);
            return;
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
295
            command_stats(client, RAW);
296
            break;
297
298
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
299
300
            break;
        case COMMAND_RAW_LISTSTREAM:
301
302
303
304
305
306
307
308
309
310
311
312
313
            command_list_mounts(client, RAW);
            break;
        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);
314
            break;
315
316
317
318
319
320
321
322
323
324
325
        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) {
326
327
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
328
329
330
331
            break;
        case COMMAND_METADATA_UPDATE:
            command_metadata(client, source);
            break;
332
333
334
335
336
337
338
339
        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);
340
            break;
341
342
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
343
            break;
344
345
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
346
            break;
347
348
349
350
351
352
353
354
355
356
357
        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);
358
            break;
359
360
361
        default:
            WARN0("Mount request not recognised");
            client_send_400(client, "Mount request unknown");
362
            break;
363
364
365
366
367
368
369
370
371
372
373
    }
}

#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);
374
375
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
376

377
static void html_success(client_t *client, char *message)
378
379
380
381
382
383
384
385
386
387
388
389
{
    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);
}

390
391
392
393
394
395
396
397
398
399
400
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;
}

401
402
static void command_move_clients(client_t *client, source_t *source,
    int response)
403
404
405
406
407
{
    char *dest_source;
    source_t *dest;
    avl_node *client_node;
    client_t *current;
408
409
410
411
412
413
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
414
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
415
416
417
418
419
420
421
422
423
424
425
        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;
    }
426
427
428
429
430
431
432
433
434
435
    
    avl_tree_rlock(global.source_tree);
    dest = source_find_mount(dest_source);
    avl_tree_unlock(global.source_tree);

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

436
437
438
439
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

440
441
442
443
444
445
446
447
448
449
450
451
    avl_tree_wlock(source->client_tree);
    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;

        avl_tree_wlock(dest->pending_tree);
        avl_insert(dest->pending_tree, current);
        avl_tree_unlock(dest->pending_tree);

        client_node = avl_get_next(client_node);

        avl_delete(source->client_tree, current, source_remove_client);
452
        source->listeners--;
453
454
455
456
    }

    avl_tree_unlock(source->client_tree);
        
457
458
459
460
461
462
463
464
465
466
    memset(buf, '\000', sizeof(buf));
    snprintf(buf, sizeof(buf)-1, "Clients moved from %s to %s", dest_source, 
        source->mount);
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
467
468
}

469
470
static void command_show_listeners(client_t *client, source_t *source,
    int response)
471
{
472
473
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
474
475
    avl_node *client_node;
    client_t *current;
476
477
    char buf[22];
    char *userAgent = NULL;
478
479
    time_t now = time(NULL);

480
481
482
483
484
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
485

486
    memset(buf, '\000', sizeof(buf));
487
    snprintf(buf, sizeof(buf)-1, "%ld", source->listeners);
488
    xmlNewChild(srcnode, NULL, "Listeners", buf);
489
490
491
492
493
494

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
495
496
497
498
499
500
501
502
503
504
        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));
505
        snprintf(buf, sizeof(buf)-1, "%ld", now - current->con->con_time);
506
507
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
508
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
509
        xmlNewChild(listenernode, NULL, "ID", buf);
510
511
512
513
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
514
515
516
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
517
518
519
    client_destroy(client);
}

520
521
static void command_kill_source(client_t *client, source_t *source,
    int response)
522
{
523
524
525
526
527
528
529
530
531
    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);

532
533
    source->running = 0;

534
535
536
537
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
538
539
}

540
541
static void command_kill_client(client_t *client, source_t *source,
    int response)
542
543
544
545
{
    char *idtext;
    int id;
    client_t *listener;
546
547
548
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
549
550
551
552
553
554
555

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

556
557
558
559
560
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

561
562
563
564
565
566
567
    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;
568
569
570
571
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "1");
572
573
    }
    else {
574
575
576
577
        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");
578
    }
579
580
581
582
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
583
584
}

585
586
static void command_fallback(client_t *client, source_t *source,
    int response)
587
588
589
590
591
592
593
594
595
596
597
598
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

599
    html_success(client, "Fallback configured");
600
601
602
603
604
605
606
}

static void command_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
    mp3_state *state;
607
608
609
610
#ifdef USE_YP
    int i;
    time_t current_time;
#endif
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635

    DEBUG0("Got metadata update request");

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

    if(source->format->type != FORMAT_TYPE_MP3) {
        client_send_400(client, "Not mp3, cannot update metadata");
        return;
    }

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

    state = source->format->_state;

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

636
637
    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
638
    stats_event(source->mount, "title", value);
639
640
641
642
643
644
645
646
647
648
#ifdef USE_YP
    /* If we get an update on the mountpoint, force a
       yp touch */
    current_time = time(NULL);
    for (i=0; i<source->num_yp_directories; i++) {
        source->ypdata[i]->yp_last_touch = current_time - 
            source->ypdata[i]->yp_touch_interval + 2;
    }
#endif

649

650
    html_success(client, "Metadata update successful");
651
652
}

653
654
655
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

656
657
    DEBUG0("Stats request, sending xml stats");

658
659
660
    stats_get_xml(&doc);
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
661
662
663
664
    client_destroy(client);
    return;
}

665
666
static void command_list_mounts(client_t *client, int response) {
    xmlDocPtr doc;
667
668
669

    DEBUG0("List mounts request");

670
    doc = admin_build_sourcelist(NULL);
671

672
673
    admin_send_response(doc, client, response, LISTMOUNTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
674
675
676
677
    client_destroy(client);
    return;
}