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

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

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

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

#include "format.h"

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

#define CATMODULE "admin"

48 49 50
/* special commands */
#define COMMAND_ERROR                      ADMIN_COMMAND_ERROR
#define COMMAND_ANY                        ADMIN_COMMAND_ANY
51

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

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

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

79
/* Client management commands (block 301-399 and 401-499) */
Karl Heyes's avatar
Karl Heyes committed
80 81 82 83
#define COMMAND_RAW_KILL_CLIENT             301
#define COMMAND_RAW_KILL_SOURCE             302
#define COMMAND_TRANSFORMED_KILL_CLIENT     401
#define COMMAND_TRANSFORMED_KILL_SOURCE     402
84

85
/* Admin commands requiring no auth (block 501-599) */
86 87
#define COMMAND_BUILDM3U                    501

88 89 90 91
/* Experimental features (e.g. in feature branches) (block 801-899) */

/* Private features (in branches not for merge with master) (block 901-999) */

Marvin Scholz's avatar
Marvin Scholz committed
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
#define FALLBACK_RAW_REQUEST                "fallbacks"
#define FALLBACK_TRANSFORMED_REQUEST        "fallbacks.xsl"
#define SHOUTCAST_METADATA_REQUEST          "admin.cgi"
#define METADATA_RAW_REQUEST                "metadata"
#define METADATA_TRANSFORMED_REQUEST        "metadata.xsl"
#define LISTCLIENTS_RAW_REQUEST             "listclients"
#define LISTCLIENTS_TRANSFORMED_REQUEST     "listclients.xsl"
#define STATS_RAW_REQUEST                   "stats"
#define STATS_TRANSFORMED_REQUEST           "stats.xsl"
#define QUEUE_RELOAD_RAW_REQUEST            "reloadconfig"
#define QUEUE_RELOAD_TRANSFORMED_REQUEST    "reloadconfig.xsl"
#define LISTMOUNTS_RAW_REQUEST              "listmounts"
#define LISTMOUNTS_TRANSFORMED_REQUEST      "listmounts.xsl"
#define STREAMLIST_RAW_REQUEST              "streamlist"
#define STREAMLIST_TRANSFORMED_REQUEST      "streamlist.xsl"
#define STREAMLIST_PLAINTEXT_REQUEST        "streamlist.txt"
#define MOVECLIENTS_RAW_REQUEST             "moveclients"
#define MOVECLIENTS_TRANSFORMED_REQUEST     "moveclients.xsl"
#define KILLCLIENT_RAW_REQUEST              "killclient"
#define KILLCLIENT_TRANSFORMED_REQUEST      "killclient.xsl"
#define KILLSOURCE_RAW_REQUEST              "killsource"
#define KILLSOURCE_TRANSFORMED_REQUEST      "killsource.xsl"
#define ADMIN_XSL_RESPONSE                  "response.xsl"
#define MANAGEAUTH_RAW_REQUEST              "manageauth"
#define MANAGEAUTH_TRANSFORMED_REQUEST      "manageauth.xsl"
#define UPDATEMETADATA_RAW_REQUEST          "updatemetadata"
#define UPDATEMETADATA_TRANSFORMED_REQUEST  "updatemetadata.xsl"
#define DEFAULT_RAW_REQUEST                 ""
#define DEFAULT_TRANSFORMED_REQUEST         ""
#define BUILDM3U_RAW_REQUEST                "buildm3u"
122

Philipp Schafft's avatar
Philipp Schafft committed
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
typedef struct admin_command_tag {
    const int   id;
    const char *name;
    const int   type;
    const int   format;
} admin_command_t;

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

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

Marvin Scholz's avatar
Marvin Scholz committed
169 170
int admin_get_command(const char *command)
{
Philipp Schafft's avatar
Philipp Schafft committed
171 172 173 174 175 176 177 178 179
    size_t i;

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

    return COMMAND_ERROR;
}

Marvin Scholz's avatar
Marvin Scholz committed
180 181
int admin_get_command_type(int command)
{
Philipp Schafft's avatar
Philipp Schafft committed
182 183 184 185 186 187 188 189 190 191
    size_t i;

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

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

    return ADMINTYPE_ERROR;
192 193
}

194
static void command_fallback(client_t *client, source_t *source, int response);
195
static void command_metadata(client_t *client, source_t *source, int response);
196
static void command_shoutcast_metadata(client_t *client, source_t *source);
197 198 199 200
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);
201
static void command_stats(client_t *client, const char *mount, int response);
202
static void command_queue_reload(client_t *client, int response);
203 204 205
static void command_list_mounts(client_t *client, int response);
static void command_kill_client(client_t *client, source_t *source,
        int response);
206
static void command_manageauth(client_t *client, int response);
207
static void command_buildm3u(client_t *client, const char *mount);
208 209
static void command_kill_source(client_t *client, source_t *source,
        int response);
210 211
static void command_updatemetadata(client_t *client, source_t *source,
        int response);
Philipp Schafft's avatar
Philipp Schafft committed
212 213
static void admin_handle_mount_request(client_t *client, source_t *source);
static void admin_handle_general_request(client_t *client);
214

215 216 217
/* build an XML doc containing information about currently running sources.
 * If a mountpoint is passed then that source will not be added to the XML
 * doc even if the source is running */
Marvin Scholz's avatar
Marvin Scholz committed
218
xmlDocPtr admin_build_sourcelist(const char *mount)
219 220 221 222 223 224 225 226
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

227 228
    doc = xmlNewDoc (XMLSTR("1.0"));
    xmlnode = xmlNewDocNode (doc, NULL, XMLSTR("icestats"), NULL);
229
    xmlDocSetRootElement(doc, xmlnode);
230

231
    if (mount) {
232
        xmlNewChild (xmlnode, NULL, XMLSTR("current_source"), XMLSTR(mount));
233 234 235 236 237
    }

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
238 239 240 241 242 243
        if (mount && strcmp (mount, source->mount) == 0)
        {
            node = avl_get_next (node);
            continue;
        }

244
        if (source->running || source->on_demand)
245
        {
246 247
            ice_config_t *config;
            mount_proxy *mountinfo;
Philipp Schafft's avatar
Philipp Schafft committed
248
            acl_t *acl = NULL;
249

250 251
            srcnode = xmlNewChild(xmlnode, NULL, XMLSTR("source"), NULL);
            xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
252

253
            xmlNewChild(srcnode, NULL, XMLSTR("fallback"),
254
                    (source->fallback_mount != NULL)?
255
                    XMLSTR(source->fallback_mount):XMLSTR(""));
256
            snprintf(buf, sizeof(buf), "%lu", source->listeners);
257
            xmlNewChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf));
258

Karl Heyes's avatar
Karl Heyes committed
259
            config = config_get_config();
Marvin Scholz's avatar
Marvin Scholz committed
260
            mountinfo = config_find_mount(config, source->mount, MOUNT_TYPE_NORMAL);
Philipp Schafft's avatar
Philipp Schafft committed
261
            if (mountinfo)
262
                acl = auth_stack_get_anonymous_acl(mountinfo->authstack, httpp_req_get);
Philipp Schafft's avatar
Philipp Schafft committed
263
            if (!acl)
264
                acl = auth_stack_get_anonymous_acl(config->authstack, httpp_req_get);
Philipp Schafft's avatar
Philipp Schafft committed
265 266
            if (acl && acl_test_web(acl) == ACL_POLICY_DENY) {
                xmlNewChild(srcnode, NULL, XMLSTR("authenticator"), XMLSTR("(dummy)"));
267
            }
Philipp Schafft's avatar
Philipp Schafft committed
268
            acl_release(acl);
269 270
            config_release_config();

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

Marvin Scholz's avatar
Marvin Scholz committed
286 287 288 289
void admin_send_response(xmlDocPtr     doc,
                         client_t      *client,
                         int           response,
                         const char    *xslt_template)
290
{
Marvin Scholz's avatar
Marvin Scholz committed
291
    if (response == RAW) {
292 293
        xmlChar *buff = NULL;
        int len = 0;
294 295 296
        size_t buf_len;
        ssize_t ret;

297
        xmlDocDumpMemory(doc, &buff, &len);
298 299 300 301

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

303 304
        client_set_queue(client, NULL);
        client->refbuf = refbuf_new(buf_len);
305

306 307 308
        ret = util_http_build_header(client->refbuf->data, buf_len, 0,
                                     0, 200, NULL,
                                     "text/xml", "utf-8",
309
                                     NULL, NULL, client);
Philipp Schafft's avatar
Philipp Schafft committed
310
        if (ret < 0) {
311
            ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
312
            client_send_error(client, 500, 0, "Header generation failed.");
313 314
            xmlFree(buff);
            return;
Philipp Schafft's avatar
Philipp Schafft committed
315
        } else if (buf_len < (size_t)(len + ret + 64)) {
316 317 318 319 320 321 322 323 324 325
            void *new_data;
            buf_len = ret + len + 64;
            new_data = realloc(client->refbuf->data, buf_len);
            if (new_data) {
                ICECAST_LOG_DEBUG("Client buffer reallocation succeeded.");
                client->refbuf->data = new_data;
                client->refbuf->len = buf_len;
                ret = util_http_build_header(client->refbuf->data, buf_len, 0,
                                             0, 200, NULL,
                                             "text/xml", "utf-8",
326
                                             NULL, NULL, client);
327 328
                if (ret == -1) {
                    ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
329
                    client_send_error(client, 500, 0, "Header generation failed.");
330 331 332 333 334
                    xmlFree(buff);
                    return;
                }
            } else {
                ICECAST_LOG_ERROR("Client buffer reallocation failed. Dropping client.");
335
                client_send_error(client, 500, 0, "Buffer reallocation failed.");
336 337
                xmlFree(buff);
                return;
338
            }
339
        }
340

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

        client->refbuf->len = ret;
345 346 347
        xmlFree(buff);
        client->respcode = 200;
        fserve_add_client (client, NULL);
348
    }
Marvin Scholz's avatar
Marvin Scholz committed
349
    if (response == TRANSFORMED) {
350 351 352 353
        char *fullpath_xslt_template;
        int fullpath_xslt_template_len;
        ice_config_t *config = config_get_config();

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

361
        ICECAST_LOG_DEBUG("Sending XSLT (%s)", fullpath_xslt_template);
362 363 364 365
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
}
366 367


368
void admin_handle_request(client_t *client, const char *uri)
369
{
370
    const char *mount, *command_string;
371

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

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

387
    ICECAST_LOG_DEBUG("Got command (%s)", command_string);
388

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

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

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

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

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

Philipp Schafft's avatar
Philipp Schafft committed
419
        /* This is a mount request, handle it as such */
420
        avl_tree_rlock(global.source_tree);
Michael Smith's avatar
Michael Smith committed
421
        source = source_find_mount_raw(mount);
422

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

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

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

Marvin Scholz's avatar
Marvin Scholz committed
561 562 563 564 565 566 567
#define COMMAND_REQUIRE(client,name,var)                                \
    do {                                                                \
        (var) = httpp_get_query_param((client)->parser, (name));        \
        if((var) == NULL) {                                             \
            client_send_error((client), 400, 0, "Missing parameter");   \
            return;                                                     \
        }                                                               \
568
    } while(0);
Marvin Scholz's avatar
Marvin Scholz committed
569

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

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

Marvin Scholz's avatar
Marvin Scholz committed
577 578
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
579
                                 "text/html", "utf-8",
580
                                 "", NULL, client);
581 582 583

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

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

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

597

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

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

Marvin Scholz's avatar
Marvin Scholz committed
622
    dest = source_find_mount(dest_source);
623

Marvin Scholz's avatar
Marvin Scholz committed
624
    if (dest == NULL) {
625
        client_send_error(client, 400, 0, "No such destination");
626 627 628
        return;
    }

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

Marvin Scholz's avatar
Marvin Scholz committed
634
    if (dest->running == 0 && dest->on_demand == 0) {
635
        client_send_error(client, 400, 0, "Destination not running");
636 637 638
        return;
    }

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

Marvin Scholz's avatar
Marvin Scholz committed
641
    doc = xmlNewDoc(XMLSTR("1.0"));
642
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
643 644
    xmlDocSetRootElement(doc, node);

Marvin Scholz's avatar
Marvin Scholz committed
645
    source_move_clients(source, dest);
646

647
    memset(buf, '\000', sizeof(buf));
Marvin Scholz's avatar
Marvin Scholz committed
648
    snprintf(buf, sizeof(buf), "Clients moved from %s to %s",
649
        source->mount, dest_source);
650 651
    xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf));
    xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
652

Marvin Scholz's avatar
Marvin Scholz committed
653
    admin_send_response(doc, client, response, ADMIN_XSL_RESPONSE);
654
    xmlFreeDoc(doc);
655 656
}

Marvin Scholz's avatar
Marvin Scholz committed
657 658 659 660 661
static inline xmlNodePtr __add_listener(client_t        *client,
                                        xmlNodePtr      parent,
                                        time_t          now,
                                        operation_mode  mode)
{
662 663 664 665
    const char *tmp;
    xmlNodePtr node;
    char buf[22];

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

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

    memset(buf, '\000', sizeof(buf));
    snprintf(buf, sizeof(buf)-1, "%lu", client->con->id);
Philipp Schafft's avatar
Philipp Schafft committed
678
    xmlSetProp(node, XMLSTR("id"), XMLSTR(buf));
679
    xmlNewChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "ID" : "id"), XMLSTR(buf));
680

681
    xmlNewChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "IP" : "ip"), XMLSTR(client->con->ip));
682 683 684

    tmp = httpp_getvar(client->parser, "user-agent");
    if (tmp)
685
        xmlNewChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "UserAgent" : "useragent"), XMLSTR(tmp));
686 687 688

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

    memset(buf, '\000', sizeof(buf));
    snprintf(buf, sizeof(buf), "%lu", (unsigned long)(now - client->con->con_time));
693
    xmlNewChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "Connected" : "connected"), XMLSTR(buf));
694 695 696 697

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

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

701 702 703
    return node;
}

Marvin Scholz's avatar
Marvin Scholz committed
704 705 706 707
void admin_add_listeners_to_mount(source_t          *source,
                                  xmlNodePtr        parent,
                                  operation_mode    mode)
{
708 709 710 711 712 713
    time_t now = time(NULL);
    avl_node *client_node;

    avl_tree_rlock(source->client_tree);
    client_node = avl_get_first(source->client_tree);
    while(client_node) {
714
        __add_listener((client_t *)client_node->key, parent, now, mode);
715 716 717 718 719
        client_node = avl_get_next(client_node);
    }
    avl_tree_unlock(source->client_tree);
}

Marvin Scholz's avatar
Marvin Scholz committed
720 721 722
static void command_show_listeners(client_t *client,
                                   source_t *source,
                                   int      response)
723
{
724
    xmlDocPtr doc;
725
    xmlNodePtr node, srcnode;
726
    char buf[22];
727

728
    doc = xmlNewDoc(XMLSTR("1.0"));
729 730 731
    node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
    srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL);
    xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount));
732
    xmlDocSetRootElement(doc, node);
733

734
    memset(buf, '\000', sizeof(buf));
735
    snprintf (buf, sizeof(buf), "%lu", source->listeners);
736
    xmlNewChild(srcnode, NULL, XMLSTR("Listeners"), XMLSTR(buf));
737

738
    admin_add_listeners_to_mount(source, srcnode, client->mode);
739

740
    admin_send_response(doc, client, response,
741 742
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
743 744
}

Marvin Scholz's avatar
Marvin Scholz committed
745
static void command_buildm3u(client_t *client, const char *mount)
746
{
747 748
    const char *username = NULL;
    const char *password = NULL;
749
    ice_config_t *config;
750
    ssize_t ret;
751 752 753 754

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

Marvin Scholz's avatar
Marvin Scholz committed
755 756
    ret = util_http_build_header(client->refbuf->data, PER_CLIENT_REFBUF_SIZE,
                                 0, 0, 200, NULL,
757
                                 "audio/x-mpegurl", NULL,
758
                                 NULL, NULL, client);
759

Marvin Scholz's avatar
Marvin Scholz committed
760 761
    if (ret == -1 || ret >= (PER_CLIENT_REFBUF_SIZE - 512)) {
        /* we want at least 512 Byte left for data */
762
        ICECAST_LOG_ERROR("Dropping client as we can not build response headers.");
763
        client_send_error(client, 500, 0, "Header generation failed.");
764 765 766 767
        return;
    }


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

780
    client->respcode = 200;
Karl Heyes's avatar
Karl Heyes committed
781 782
    client->refbuf->len = strlen (client->refbuf->data);
    fserve_add_client (client, NULL);
783
}
784

Marvin Scholz's avatar
Marvin Scholz committed
785 786
xmlNodePtr admin_add_role_to_authentication(auth_t *auth, xmlNodePtr parent)
{
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
    xmlNodePtr rolenode = xmlNewChild(parent, NULL, XMLSTR("role"), NULL);
    char idbuf[32];

    snprintf(idbuf, sizeof(idbuf), "%lu", auth->id);
    xmlSetProp(rolenode, XMLSTR("id"), XMLSTR(idbuf));

    if (auth->type)
        xmlSetProp(rolenode, XMLSTR("type"), XMLSTR(auth->type));
    if (auth->role)
        xmlSetProp(rolenode, XMLSTR("name"), XMLSTR(auth->role));
    if (auth->management_url)
        xmlSetProp(rolenode, XMLSTR("management-url"), XMLSTR(auth->management_url));

    xmlSetProp(rolenode, XMLSTR("can-adduser"), XMLSTR(auth->adduser ? "true" : "false"));
    xmlSetProp(rolenode, XMLSTR("can-deleteuser"), XMLSTR(auth->deleteuser ? "true" : "false"));
    xmlSetProp(rolenode, XMLSTR("can-listuser"), XMLSTR(auth->listuser ? "true" : "false"));

    return rolenode;
}
806

Marvin Scholz's avatar
Marvin Scholz committed
807 808
static void command_manageauth(client_t *client, int response)
{
809
    xmlDocPtr doc;
810
    xmlNodePtr node, rolenode, usersnode, msgnode;
811 812
    const char *action = NULL;
    const char *username = NULL;
813
    const char *idstring = NULL;
814 815
    char *message = NULL;
    int ret = AUTH_OK;
816 817 818 819
    int error_code = 400;
    const char *error_message = "missing parameter";
    long unsigned int id;
    ice_config_t *config = config_get_config();
Philipp Schafft's avatar
Philipp Schafft committed
820
    auth_t *auth;
821

Marvin Scholz's avatar
Marvin Scholz committed
822
    do {
823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845
        /* get id */
        COMMAND_REQUIRE(client, "id", idstring);
        id = atol(idstring);

        /* no find a auth_t for that id by looking up the config */
        /* globals first */
        auth = auth_stack_getbyid(config->authstack, id);
        /* now mounts */
        if (!auth) {
            mount_proxy *mount = config->mounts;
            while (mount) {
                auth = auth_stack_getbyid(mount->authstack, id);
                if (auth)
                    break;
                mount = mount->next;
            }
        }

        /* check if we found one */
        if (auth == NULL) {
            ICECAST_LOG_WARN("Client requested mangement for unknown role %lu", id);
            error_code = 404;
            error_message = "Role not found";
846
            break;
847
        }
Philipp Schafft's avatar
Philipp Schafft committed
848

849
        COMMAND_OPTIONAL(client, "action", action);
850
        COMMAND_OPTIONAL(client, "username", username);
851 852

        if (action == NULL)
853
            action = "list";
854

Marvin Scholz's avatar
Marvin Scholz committed
855
        if (!strcmp(action, "add")) {
856
            const char *password = NULL;
857
            COMMAND_OPTIONAL(client, "password", password);
858

859 860
            if (username == NULL || password == NULL) {
                ICECAST_LOG_WARN("manage auth request add for %lu but no user/pass", id);
861 862
                break;
            }
863 864 865 866 867 868

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

Philipp Schafft's avatar
Philipp Schafft committed
869
            ret = auth->adduser(auth, username, password);
870 871
            if (ret == AUTH_FAILED) {
                message = strdup("User add failed - check the icecast error log");
872
            } else if (ret == AUTH_USERADDED) {
873
                message = strdup("User added");
874
            } else if (ret == AUTH_USEREXISTS) {
875 876 877
                message = strdup("User already exists - not added");
            }
        }
Marvin Scholz's avatar
Marvin Scholz committed
878
        if (!strcmp(action, "delete")) {
879 880 881 882 883 884 885
            if (username == NULL) {
                ICECAST_LOG_WARN("manage auth request delete for %lu but no username", id);
                break;
            }

            if (!auth->deleteuser) {
                error_message = "Deleting users from role not supported by role";
886 887
                break;
            }
888

Philipp Schafft's avatar
Philipp Schafft committed
889
            ret = auth->deleteuser(auth, username);
890 891
            if (ret == AUTH_FAILED) {
                message = strdup("User delete failed - check the icecast error log");
892
            } else if (ret == AUTH_USERDELETED) {
893 894 895 896
                message = strdup("User deleted");
            }
        }

897
        doc = xmlNewDoc(XMLSTR("1.0"));
898
        node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL);
899

900
        rolenode = admin_add_role_to_authentication(auth, node);
901

902
        if (message) {
903 904
            msgnode = xmlNewChild(node, NULL, XMLSTR("iceresponse"), NULL);
            xmlNewChild(msgnode, NULL, XMLSTR("message"), XMLSTR(message));
905
        }
906

907
        xmlDocSetRootElement(doc, node);
908

909 910 911 912
        if (auth && auth->listuser) {
            usersnode = xmlNewChild(rolenode, NULL, XMLSTR("users"), NULL);
            auth->listuser(auth, usersnode);
        }
913

914 915
        config_release_config();
        auth_release(auth);
916

917
        admin_send_response(doc, client, response,
Marvin Scholz's avatar
Marvin Scholz committed
918 919
            MANAGEAUTH_TRANSFORMED_REQUEST);
        free(message);
920 921 922 923
        xmlFreeDoc(doc);
        return;
    } while (0);

924 925 926
    config_release_config();
    auth_release(auth);
    client_send_error(client, error_code, 0, error_message);
927 928
}

Marvin Scholz's avatar
Marvin Scholz committed
929 930 931
static void command_kill_source(client_t *client,
                                source_t *source,
                                int      response)
932
{
933 934 935
    xmlDocPtr doc;
    xmlNodePtr node;

936 937 938 939
    doc = xmlNewDoc (XMLSTR("1.0"));
    node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL);
    xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR("Source Removed"));
    xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1"));
940 941
    xmlDocSetRootElement(doc, node);