3 * This program is distributed under the GNU General Public License, version 2.
4 * A copy of this license is included with this source.
6 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
7 * Michael Smith <msmith@xiph.org>,
8 * oddsock <oddsock@xiph.org>,
9 * Karl Heyes <karl@xiph.org>
10 * and others (see AUTHORS for details).
21 #include <libxml/xmlmemory.h>
22 #include <libxml/parser.h>
23 #include <libxml/tree.h>
26 #include "connection.h"
40 #define snprintf _snprintf
43 #define CATMODULE "admin"
45 #define COMMAND_ERROR (-1)
47 /* Mount-specific commands */
48 #define COMMAND_RAW_FALLBACK 1
49 #define COMMAND_METADATA_UPDATE 2
50 #define COMMAND_RAW_SHOW_LISTENERS 3
51 #define COMMAND_RAW_MOVE_CLIENTS 4
53 #define COMMAND_TRANSFORMED_FALLBACK 50
54 #define COMMAND_TRANSFORMED_SHOW_LISTENERS 53
55 #define COMMAND_TRANSFORMED_MOVE_CLIENTS 54
58 #define COMMAND_RAW_LIST_MOUNTS 101
59 #define COMMAND_RAW_STATS 102
60 #define COMMAND_RAW_LISTSTREAM 103
61 #define COMMAND_PLAINTEXT_LISTSTREAM 104
62 #define COMMAND_TRANSFORMED_LIST_MOUNTS 201
63 #define COMMAND_TRANSFORMED_STATS 202
64 #define COMMAND_TRANSFORMED_LISTSTREAM 203
66 /* Client management commands */
67 #define COMMAND_RAW_KILL_CLIENT 301
68 #define COMMAND_RAW_KILL_SOURCE 302
69 #define COMMAND_TRANSFORMED_KILL_CLIENT 401
70 #define COMMAND_TRANSFORMED_KILL_SOURCE 402
72 #define FALLBACK_RAW_REQUEST "fallbacks"
73 #define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl"
74 #define METADATA_REQUEST "metadata"
75 #define LISTCLIENTS_RAW_REQUEST "listclients"
76 #define LISTCLIENTS_TRANSFORMED_REQUEST "listclients.xsl"
77 #define STATS_RAW_REQUEST "stats"
78 #define STATS_TRANSFORMED_REQUEST "stats.xsl"
79 #define LISTMOUNTS_RAW_REQUEST "listmounts"
80 #define LISTMOUNTS_TRANSFORMED_REQUEST "listmounts.xsl"
81 #define STREAMLIST_RAW_REQUEST "streamlist"
82 #define STREAMLIST_TRANSFORMED_REQUEST "streamlist.xsl"
83 #define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
84 #define MOVECLIENTS_RAW_REQUEST "moveclients"
85 #define MOVECLIENTS_TRANSFORMED_REQUEST "moveclients.xsl"
86 #define KILLCLIENT_RAW_REQUEST "killclient"
87 #define KILLCLIENT_TRANSFORMED_REQUEST "killclient.xsl"
88 #define KILLSOURCE_RAW_REQUEST "killsource"
89 #define KILLSOURCE_TRANSFORMED_REQUEST "killsource.xsl"
90 #define ADMIN_XSL_RESPONSE "response.xsl"
91 #define DEFAULT_RAW_REQUEST ""
92 #define DEFAULT_TRANSFORMED_REQUEST ""
97 int admin_get_command(char *command
)
99 if(!strcmp(command
, FALLBACK_RAW_REQUEST
))
100 return COMMAND_RAW_FALLBACK
;
101 else if(!strcmp(command
, FALLBACK_TRANSFORMED_REQUEST
))
102 return COMMAND_TRANSFORMED_FALLBACK
;
103 else if(!strcmp(command
, METADATA_REQUEST
))
104 return COMMAND_METADATA_UPDATE
;
105 else if(!strcmp(command
, LISTCLIENTS_RAW_REQUEST
))
106 return COMMAND_RAW_SHOW_LISTENERS
;
107 else if(!strcmp(command
, LISTCLIENTS_TRANSFORMED_REQUEST
))
108 return COMMAND_TRANSFORMED_SHOW_LISTENERS
;
109 else if(!strcmp(command
, STATS_RAW_REQUEST
))
110 return COMMAND_RAW_STATS
;
111 else if(!strcmp(command
, STATS_TRANSFORMED_REQUEST
))
112 return COMMAND_TRANSFORMED_STATS
;
113 else if(!strcmp(command
, "stats.xml")) /* The old way */
114 return COMMAND_RAW_STATS
;
115 else if(!strcmp(command
, LISTMOUNTS_RAW_REQUEST
))
116 return COMMAND_RAW_LIST_MOUNTS
;
117 else if(!strcmp(command
, LISTMOUNTS_TRANSFORMED_REQUEST
))
118 return COMMAND_TRANSFORMED_LIST_MOUNTS
;
119 else if(!strcmp(command
, STREAMLIST_RAW_REQUEST
))
120 return COMMAND_RAW_LISTSTREAM
;
121 else if(!strcmp(command
, STREAMLIST_PLAINTEXT_REQUEST
))
122 return COMMAND_PLAINTEXT_LISTSTREAM
;
123 else if(!strcmp(command
, MOVECLIENTS_RAW_REQUEST
))
124 return COMMAND_RAW_MOVE_CLIENTS
;
125 else if(!strcmp(command
, MOVECLIENTS_TRANSFORMED_REQUEST
))
126 return COMMAND_TRANSFORMED_MOVE_CLIENTS
;
127 else if(!strcmp(command
, KILLCLIENT_RAW_REQUEST
))
128 return COMMAND_RAW_KILL_CLIENT
;
129 else if(!strcmp(command
, KILLCLIENT_TRANSFORMED_REQUEST
))
130 return COMMAND_TRANSFORMED_KILL_CLIENT
;
131 else if(!strcmp(command
, KILLSOURCE_RAW_REQUEST
))
132 return COMMAND_RAW_KILL_SOURCE
;
133 else if(!strcmp(command
, KILLSOURCE_TRANSFORMED_REQUEST
))
134 return COMMAND_TRANSFORMED_KILL_SOURCE
;
135 else if(!strcmp(command
, DEFAULT_TRANSFORMED_REQUEST
))
136 return COMMAND_TRANSFORMED_STATS
;
137 else if(!strcmp(command
, DEFAULT_RAW_REQUEST
))
138 return COMMAND_TRANSFORMED_STATS
;
140 return COMMAND_ERROR
;
143 static void command_fallback(client_t
*client
, source_t
*source
, int response
);
144 static void command_metadata(client_t
*client
, source_t
*source
);
145 static void command_show_listeners(client_t
*client
, source_t
*source
,
147 static void command_move_clients(client_t
*client
, source_t
*source
,
149 static void command_stats(client_t
*client
, int response
);
150 static void command_list_mounts(client_t
*client
, int response
);
151 static void command_kill_client(client_t
*client
, source_t
*source
,
153 static void command_kill_source(client_t
*client
, source_t
*source
,
155 static void admin_handle_mount_request(client_t
*client
, source_t
*source
,
157 static void admin_handle_general_request(client_t
*client
, int command
);
158 static void admin_send_response(xmlDocPtr doc
, client_t
*client
,
159 int response
, char *xslt_template
);
160 static void html_write(client_t
*client
, char *fmt
, ...);
162 xmlDocPtr
admin_build_sourcelist(char *current_source
)
166 xmlNodePtr xmlnode
, srcnode
;
169 time_t now
= time(NULL
);
171 doc
= xmlNewDoc("1.0");
172 xmlnode
= xmlNewDocNode(doc
, NULL
, "icestats", NULL
);
173 xmlDocSetRootElement(doc
, xmlnode
);
175 if (current_source
) {
176 xmlNewChild(xmlnode
, NULL
, "current_source", current_source
);
179 node
= avl_get_first(global
.source_tree
);
181 source
= (source_t
*)node
->key
;
184 srcnode
= xmlNewChild(xmlnode
, NULL
, "source", NULL
);
185 xmlSetProp(srcnode
, "mount", source
->mount
);
187 xmlNewChild(srcnode
, NULL
, "fallback",
188 (source
->fallback_mount
!= NULL
)?
189 source
->fallback_mount
:"");
190 snprintf(buf
, sizeof(buf
), "%ld", source
->listeners
);
191 xmlNewChild(srcnode
, NULL
, "listeners", buf
);
192 snprintf(buf
, sizeof(buf
), "%ld", now
- source
->con
->con_time
);
193 xmlNewChild(srcnode
, NULL
, "Connected", buf
);
194 xmlNewChild(srcnode
, NULL
, "Format",
195 source
->format
->format_description
);
197 node
= avl_get_next(node
);
202 void admin_send_response(xmlDocPtr doc
, client_t
*client
,
203 int response
, char *xslt_template
)
205 xmlChar
*buff
= NULL
;
207 ice_config_t
*config
;
208 char *fullpath_xslt_template
;
209 int fullpath_xslt_template_len
;
212 client
->respcode
= 200;
213 if (response
== RAW
) {
214 xmlDocDumpMemory(doc
, &buff
, &len
);
215 html_write(client
, "HTTP/1.0 200 OK\r\n"
216 "Content-Length: %d\r\n"
217 "Content-Type: text/xml\r\n"
219 html_write(client
, buff
);
221 if (response
== TRANSFORMED
) {
222 config
= config_get_config();
223 adminwebroot
= config
->adminroot_dir
;
224 config_release_config();
225 fullpath_xslt_template_len
= strlen(adminwebroot
) +
226 strlen(xslt_template
) + 2;
227 fullpath_xslt_template
= malloc(fullpath_xslt_template_len
);
228 memset(fullpath_xslt_template
, '\000', fullpath_xslt_template_len
);
229 snprintf(fullpath_xslt_template
, fullpath_xslt_template_len
, "%s%s%s",
230 adminwebroot
, PATH_SEPARATOR
, xslt_template
);
231 html_write(client
, "HTTP/1.0 200 OK\r\n"
232 "Content-Type: text/html\r\n"
234 DEBUG1("Sending XSLT (%s)", fullpath_xslt_template
);
235 xslt_transform(doc
, fullpath_xslt_template
, client
);
236 free(fullpath_xslt_template
);
242 void admin_handle_request(client_t
*client
, char *uri
)
244 char *mount
, *command_string
;
247 if(strncmp("/admin/", uri
, 7)) {
248 ERROR0("Internal error: admin request isn't");
249 client_send_401(client
);
253 command_string
= uri
+ 7;
255 DEBUG1("Got command (%s)", command_string
);
256 command
= admin_get_command(command_string
);
259 ERROR1("Error parsing command string or unrecognised command: %s",
261 client_send_400(client
, "Unrecognised command");
265 mount
= httpp_get_query_param(client
->parser
, "mount");
270 /* This is a mount request, handle it as such */
271 if(!connection_check_admin_pass(client
->parser
)) {
272 if(!connection_check_source_pass(client
->parser
, mount
)) {
273 INFO1("Bad or missing password on mount modification admin "
274 "request (command: %s)", command_string
);
275 client_send_401(client
);
280 avl_tree_rlock(global
.source_tree
);
281 source
= source_find_mount_raw(mount
);
285 WARN2("Admin command %s on non-existent source %s",
286 command_string
, mount
);
287 avl_tree_unlock(global
.source_tree
);
288 client_send_400(client
, "Source does not exist");
292 if (source
->running
== 0)
294 INFO2("Received admin command %s on unavailable mount \"%s\"",
295 command_string
, mount
);
296 avl_tree_unlock (global
.source_tree
);
297 client_send_400 (client
, "Source is not available");
300 INFO2("Received admin command %s on mount \"%s\"",
301 command_string
, mount
);
302 admin_handle_mount_request(client
, source
, command
);
303 avl_tree_unlock(global
.source_tree
);
308 if (command
== COMMAND_PLAINTEXT_LISTSTREAM
) {
309 /* this request is used by a slave relay to retrieve
310 mounts from the master, so handle this request
311 validating against the relay password */
312 if(!connection_check_relay_pass(client
->parser
)) {
313 INFO1("Bad or missing password on admin command "
314 "request (command: %s)", command_string
);
315 client_send_401(client
);
320 if(!connection_check_admin_pass(client
->parser
)) {
321 INFO1("Bad or missing password on admin command "
322 "request (command: %s)", command_string
);
323 client_send_401(client
);
328 admin_handle_general_request(client
, command
);
332 static void admin_handle_general_request(client_t
*client
, int command
)
335 case COMMAND_RAW_STATS
:
336 command_stats(client
, RAW
);
338 case COMMAND_RAW_LIST_MOUNTS
:
339 command_list_mounts(client
, RAW
);
341 case COMMAND_RAW_LISTSTREAM
:
342 command_list_mounts(client
, RAW
);
344 case COMMAND_PLAINTEXT_LISTSTREAM
:
345 command_list_mounts(client
, PLAINTEXT
);
347 case COMMAND_TRANSFORMED_STATS
:
348 command_stats(client
, TRANSFORMED
);
350 case COMMAND_TRANSFORMED_LIST_MOUNTS
:
351 command_list_mounts(client
, TRANSFORMED
);
353 case COMMAND_TRANSFORMED_LISTSTREAM
:
354 command_list_mounts(client
, TRANSFORMED
);
356 case COMMAND_TRANSFORMED_MOVE_CLIENTS
:
357 command_list_mounts(client
, TRANSFORMED
);
360 WARN0("General admin request not recognised");
361 client_send_400(client
, "Unknown admin request");
366 static void admin_handle_mount_request(client_t
*client
, source_t
*source
,
370 case COMMAND_RAW_FALLBACK
:
371 command_fallback(client
, source
, RAW
);
373 case COMMAND_METADATA_UPDATE
:
374 command_metadata(client
, source
);
376 case COMMAND_RAW_SHOW_LISTENERS
:
377 command_show_listeners(client
, source
, RAW
);
379 case COMMAND_RAW_MOVE_CLIENTS
:
380 command_move_clients(client
, source
, RAW
);
382 case COMMAND_RAW_KILL_CLIENT
:
383 command_kill_client(client
, source
, RAW
);
385 case COMMAND_RAW_KILL_SOURCE
:
386 command_kill_source(client
, source
, RAW
);
388 case COMMAND_TRANSFORMED_FALLBACK
:
389 command_fallback(client
, source
, RAW
);
391 case COMMAND_TRANSFORMED_SHOW_LISTENERS
:
392 command_show_listeners(client
, source
, TRANSFORMED
);
394 case COMMAND_TRANSFORMED_MOVE_CLIENTS
:
395 command_move_clients(client
, source
, TRANSFORMED
);
397 case COMMAND_TRANSFORMED_KILL_CLIENT
:
398 command_kill_client(client
, source
, TRANSFORMED
);
400 case COMMAND_TRANSFORMED_KILL_SOURCE
:
401 command_kill_source(client
, source
, TRANSFORMED
);
404 WARN0("Mount request not recognised");
405 client_send_400(client
, "Mount request unknown");
410 #define COMMAND_REQUIRE(client,name,var) \
412 (var) = httpp_get_query_param((client)->parser, (name)); \
413 if((var) == NULL) { \
414 client_send_400((client), "Missing parameter"); \
418 #define COMMAND_OPTIONAL(client,name,var) \
419 (var) = httpp_get_query_param((client)->parser, (name))
421 static void html_success(client_t
*client
, char *message
)
425 client
->respcode
= 200;
426 bytes
= sock_write(client
->con
->sock
,
427 "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n"
428 "<html><head><title>Admin request successful</title></head>"
429 "<body><p>%s</p></body></html>", message
);
430 if(bytes
> 0) client
->con
->sent_bytes
= bytes
;
431 client_destroy(client
);
434 static void html_write(client_t
*client
, char *fmt
, ...)
440 bytes
= sock_write_fmt(client
->con
->sock
, fmt
, ap
);
442 if(bytes
> 0) client
->con
->sent_bytes
= bytes
;
445 static void command_move_clients(client_t
*client
, source_t
*source
,
453 int parameters_passed
= 0;
455 DEBUG0("Doing optional check");
456 if((COMMAND_OPTIONAL(client
, "destination", dest_source
))) {
457 parameters_passed
= 1;
459 DEBUG1("Done optional check (%d)", parameters_passed
);
460 if (!parameters_passed
) {
461 doc
= admin_build_sourcelist(source
->mount
);
462 admin_send_response(doc
, client
, response
,
463 MOVECLIENTS_TRANSFORMED_REQUEST
);
465 client_destroy(client
);
469 dest
= source_find_mount (dest_source
);
473 client_send_400 (client
, "No such destination");
477 if (strcmp (dest
->mount
, source
->mount
) == 0)
479 client_send_400 (client
, "supplied mountpoints are identical");
483 if (dest
->running
== 0)
485 client_send_400 (client
, "Destination not running");
489 doc
= xmlNewDoc("1.0");
490 node
= xmlNewDocNode(doc
, NULL
, "iceresponse", NULL
);
491 xmlDocSetRootElement(doc
, node
);
493 source_move_clients (source
, dest
);
495 memset(buf
, '\000', sizeof(buf
));
496 snprintf (buf
, sizeof(buf
), "Clients moved from %s to %s",
497 source
->mount
, dest_source
);
498 xmlNewChild(node
, NULL
, "message", buf
);
499 xmlNewChild(node
, NULL
, "return", "1");
501 admin_send_response(doc
, client
, response
,
504 client_destroy(client
);
507 static void command_show_listeners(client_t
*client
, source_t
*source
,
511 xmlNodePtr node
, srcnode
, listenernode
;
512 avl_node
*client_node
;
515 char *userAgent
= NULL
;
516 time_t now
= time(NULL
);
518 doc
= xmlNewDoc("1.0");
519 node
= xmlNewDocNode(doc
, NULL
, "icestats", NULL
);
520 srcnode
= xmlNewChild(node
, NULL
, "source", NULL
);
521 xmlSetProp(srcnode
, "mount", source
->mount
);
522 xmlDocSetRootElement(doc
, node
);
524 memset(buf
, '\000', sizeof(buf
));
525 snprintf(buf
, sizeof(buf
)-1, "%ld", source
->listeners
);
526 xmlNewChild(srcnode
, NULL
, "Listeners", buf
);
528 avl_tree_rlock(source
->client_tree
);
530 client_node
= avl_get_first(source
->client_tree
);
532 current
= (client_t
*)client_node
->key
;
533 listenernode
= xmlNewChild(srcnode
, NULL
, "listener", NULL
);
534 xmlNewChild(listenernode
, NULL
, "IP", current
->con
->ip
);
535 userAgent
= httpp_getvar(current
->parser
, "user-agent");
537 xmlNewChild(listenernode
, NULL
, "UserAgent", userAgent
);
540 xmlNewChild(listenernode
, NULL
, "UserAgent", "Unknown");
542 memset(buf
, '\000', sizeof(buf
));
543 snprintf(buf
, sizeof(buf
)-1, "%ld", now
- current
->con
->con_time
);
544 xmlNewChild(listenernode
, NULL
, "Connected", buf
);
545 memset(buf
, '\000', sizeof(buf
));
546 snprintf(buf
, sizeof(buf
)-1, "%lu", current
->con
->id
);
547 xmlNewChild(listenernode
, NULL
, "ID", buf
);
548 client_node
= avl_get_next(client_node
);
551 avl_tree_unlock(source
->client_tree
);
552 admin_send_response(doc
, client
, response
,
553 LISTCLIENTS_TRANSFORMED_REQUEST
);
555 client_destroy(client
);
558 static void command_kill_source(client_t
*client
, source_t
*source
,
564 doc
= xmlNewDoc("1.0");
565 node
= xmlNewDocNode(doc
, NULL
, "iceresponse", NULL
);
566 xmlNewChild(node
, NULL
, "message", "Source Removed");
567 xmlNewChild(node
, NULL
, "return", "1");
568 xmlDocSetRootElement(doc
, node
);
572 admin_send_response(doc
, client
, response
,
575 client_destroy(client
);
578 static void command_kill_client(client_t
*client
, source_t
*source
,
588 COMMAND_REQUIRE(client
, "id", idtext
);
592 listener
= source_find_client(source
, id
);
594 doc
= xmlNewDoc("1.0");
595 node
= xmlNewDocNode(doc
, NULL
, "iceresponse", NULL
);
596 xmlDocSetRootElement(doc
, node
);
597 DEBUG1("Response is %d", response
);
599 if(listener
!= NULL
) {
600 INFO1("Admin request: client %d removed", id
);
602 /* This tags it for removal on the next iteration of the main source
605 listener
->con
->error
= 1;
606 memset(buf
, '\000', sizeof(buf
));
607 snprintf(buf
, sizeof(buf
)-1, "Client %d removed", id
);
608 xmlNewChild(node
, NULL
, "message", buf
);
609 xmlNewChild(node
, NULL
, "return", "1");
612 memset(buf
, '\000', sizeof(buf
));
613 snprintf(buf
, sizeof(buf
)-1, "Client %d not found", id
);
614 xmlNewChild(node
, NULL
, "message", buf
);
615 xmlNewChild(node
, NULL
, "return", "0");
617 admin_send_response(doc
, client
, response
,
620 client_destroy(client
);
623 static void command_fallback(client_t
*client
, source_t
*source
,
629 DEBUG0("Got fallback request");
631 COMMAND_REQUIRE(client
, "fallback", fallback
);
633 old
= source
->fallback_mount
;
634 source
->fallback_mount
= strdup(fallback
);
637 html_success(client
, "Fallback configured");
640 static void command_metadata(client_t
*client
, source_t
*source
)
644 format_plugin_t
*format
;
650 DEBUG0("Got metadata update request");
652 COMMAND_REQUIRE(client
, "mode", action
);
653 COMMAND_REQUIRE(client
, "song", value
);
655 format
= source
->format
;
656 if (format
->type
!= FORMAT_TYPE_MP3
)
658 client_send_400 (client
, "Not mp3, cannot update metadata");
662 if (strcmp (action
, "updinfo") != 0)
664 client_send_400 (client
, "No such action");
670 format
->set_tag (format
, "title", value
);
673 DEBUG2("Metadata on mountpoint %s changed to \"%s\"",
674 source
->mount
, value
);
675 stats_event(source
->mount
, "title", value
);
678 /* If we get an update on the mountpoint, force a
680 current_time
= time(NULL
);
681 for (i
=0; i
<source
->num_yp_directories
; i
++) {
682 source
->ypdata
[i
]->yp_last_touch
= current_time
-
683 source
->ypdata
[i
]->yp_touch_interval
+ 2;
687 html_success(client
, "Metadata update successful");
690 static void command_stats(client_t
*client
, int response
) {
693 DEBUG0("Stats request, sending xml stats");
696 admin_send_response(doc
, client
, response
, STATS_TRANSFORMED_REQUEST
);
698 client_destroy(client
);
702 static void command_list_mounts(client_t
*client
, int response
) {
707 DEBUG0("List mounts request");
710 if (response
== PLAINTEXT
)
712 node
= avl_get_first(global
.source_tree
);
714 "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");
716 source
= (source_t
*)node
->key
;
717 html_write(client
, "%s\n", source
->mount
);
718 node
= avl_get_next(node
);
723 doc
= admin_build_sourcelist(NULL
);
725 admin_send_response(doc
, client
, response
,
726 LISTMOUNTS_TRANSFORMED_REQUEST
);
729 client_destroy(client
);