diff --git a/admin/Makefile.am b/admin/Makefile.am
index 087a6de3..4183ca7b 100644
--- a/admin/Makefile.am
+++ b/admin/Makefile.am
@@ -17,6 +17,7 @@ nobase_dist_admin_DATA = \
dashboard.xsl \
fallbacks.xsl \
showlog.xsl \
+ listensocketlist.xsl \
includes/confirm.xsl \
includes/footer.xsl \
includes/head.xsl \
@@ -25,6 +26,7 @@ nobase_dist_admin_DATA = \
includes/mountnav.xsl \
includes/player.xsl \
includes/playlist.xsl \
+ includes/authlist.xsl \
includes/web-page.xsl \
ui/confirmdeleteuser.xsl \
ui/confirmkillclient.xsl \
diff --git a/admin/includes/authlist.xsl b/admin/includes/authlist.xsl
new file mode 100644
index 00000000..9b576b13
--- /dev/null
+++ b/admin/includes/authlist.xsl
@@ -0,0 +1,24 @@
+
+
+
+
+ - Role
+
+
+
+ of type
+
+
+
+ (Manage)
+
+
+ (List)
+
+
+
+
+
+
+
+
diff --git a/admin/includes/header.xsl b/admin/includes/header.xsl
index 3cdf4f7d..04de7fd7 100644
--- a/admin/includes/header.xsl
+++ b/admin/includes/header.xsl
@@ -11,6 +11,7 @@
Dashboard
Server status
Mountpoint list
+ Listen Socket list
Logfiles
diff --git a/admin/listensocketlist.xsl b/admin/listensocketlist.xsl
new file mode 100644
index 00000000..65476e33
--- /dev/null
+++ b/admin/listensocketlist.xsl
@@ -0,0 +1,94 @@
+
+
+
+ Listen Sockets
+
+
+
+
+
+ Listen Socket
+ Overview
+
+
+
+ Key |
+ Value |
+
+
+
+
+
+ ID |
+ |
+
+
+
+
+ On behalf of |
+ |
+
+
+
+ Type |
+ |
+
+
+ Family |
+ |
+
+
+
+
+ Config
+
+
+
+ Key |
+ Value |
+
+
+
+
+
+
+ |
+ |
+
+
+
+
+
+
+
+ Header
+
+
+
+ Type |
+ Name |
+ Value |
+ Status |
+
+
+
+
+
+ |
+ |
+ |
+ |
+
+
+
+
+
+
+
+ Authentication
+
+
+
+
+
+
diff --git a/admin/listmounts.xsl b/admin/listmounts.xsl
index eb37eca4..84677fd7 100644
--- a/admin/listmounts.xsl
+++ b/admin/listmounts.xsl
@@ -3,34 +3,10 @@
+
Active Mountpoints
-
-
-
-
- - Role
-
-
-
- of type
-
-
-
- (Manage)
-
-
- (List)
-
-
-
-
-
-
-
-
-
diff --git a/admin/stats.xsl b/admin/stats.xsl
index 9e64d0f9..e18323d5 100644
--- a/admin/stats.xsl
+++ b/admin/stats.xsl
@@ -4,36 +4,12 @@
+
Server status
-
-
-
-
- - Role
-
-
-
- of type
-
-
-
- (Manage)
-
-
- (List)
-
-
-
-
-
-
-
-
-
Server status
diff --git a/src/admin.c b/src/admin.c
index 93d17f19..d886440c 100644
--- a/src/admin.c
+++ b/src/admin.c
@@ -28,10 +28,13 @@
#include
#endif
+#include "common/net/sock.h"
+
#include "admin.h"
#include "compat.h"
#include "cfgfile.h"
#include "connection.h"
+#include "listensocket.h"
#include "refbuf.h"
#include "client.h"
#include "source.h"
@@ -95,6 +98,8 @@
#define STREAMLIST_HTML_REQUEST "streamlist.xsl"
#define STREAMLIST_JSON_REQUEST "streamlist.json"
#define STREAMLIST_PLAINTEXT_REQUEST "streamlist.txt"
+#define LISTENSOCKETLIST_RAW_REQUEST "listensocketlist"
+#define LISTENSOCKETLIST_HTML_REQUEST "listensocketlist.xsl"
#define MOVECLIENTS_RAW_REQUEST "moveclients"
#define MOVECLIENTS_HTML_REQUEST "moveclients.xsl"
#define MOVECLIENTS_JSON_REQUEST "moveclients.json"
@@ -145,6 +150,7 @@ static void command_stats (client_t *client, source_t *source, adm
static void command_public_stats (client_t *client, source_t *source, admin_format_t response);
static void command_queue_reload (client_t *client, source_t *source, admin_format_t response);
static void command_list_mounts (client_t *client, source_t *source, admin_format_t response);
+static void command_list_listen_sockets (client_t *client, source_t *source, admin_format_t response);
static void command_move_clients (client_t *client, source_t *source, admin_format_t response);
static void command_kill_client (client_t *client, source_t *source, admin_format_t response);
static void command_kill_source (client_t *client, source_t *source, admin_format_t response);
@@ -183,6 +189,8 @@ static const admin_command_handler_t handlers[] = {
{ STREAMLIST_PLAINTEXT_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_PLAINTEXT, ADMINSAFE_SAFE, command_list_mounts, NULL},
{ STREAMLIST_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, ADMINSAFE_SAFE, command_list_mounts, NULL},
{ STREAMLIST_JSON_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_JSON, ADMINSAFE_SAFE, command_list_mounts, NULL},
+ { LISTENSOCKETLIST_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, ADMINSAFE_SAFE, command_list_listen_sockets, NULL},
+ { LISTENSOCKETLIST_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, ADMINSAFE_SAFE, command_list_listen_sockets, NULL},
{ MOVECLIENTS_RAW_REQUEST, ADMINTYPE_MOUNT, ADMIN_FORMAT_RAW, ADMINSAFE_HYBRID, command_move_clients, NULL},
{ MOVECLIENTS_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, ADMINSAFE_HYBRID, command_move_clients, NULL},
{ MOVECLIENTS_JSON_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_JSON, ADMINSAFE_HYBRID, command_move_clients, NULL},
@@ -1311,6 +1319,124 @@ static void command_list_mounts(client_t *client, source_t *source, admin_format
}
}
+static void command_list_listen_sockets(client_t *client, source_t *source, admin_format_t response)
+{
+ reportxml_t *report = client_get_empty_reportxml();
+ listensocket_t ** sockets;
+ size_t i;
+
+ global_lock();
+ sockets = listensocket_container_list_sockets(global.listensockets);
+ global_unlock();
+
+ for (i = 0; sockets[i]; i++) {
+ const listener_t * listener = listensocket_get_listener(sockets[i]);
+ reportxml_node_t * incident = client_add_empty_incident(report, "ee231290-81c6-484a-836c-20a00ad09898", NULL, NULL);
+ reportxml_node_t * resource = reportxml_node_new(REPORTXML_NODE_TYPE_RESOURCE, NULL, NULL, NULL);
+ reportxml_node_t * config = reportxml_node_new(REPORTXML_NODE_TYPE_VALUE, NULL, NULL, NULL);
+
+ reportxml_node_set_attribute(resource, "type", "result");
+ reportxml_node_set_attribute(config, "type", "structure");
+ reportxml_node_set_attribute(config, "member", "config");
+
+ reportxml_node_add_child(resource, config);
+ reportxml_node_add_child(incident, resource);
+ refobject_unref(incident);
+
+ reportxml_helper_add_value_enum(resource, "type", listensocket_type_to_string(listener->type));
+ reportxml_helper_add_value_enum(resource, "family", sock_family_to_string(listensocket_get_family(sockets[i])));
+ reportxml_helper_add_value_string(resource, "id", listener->id);
+ reportxml_helper_add_value_string(resource, "on_behalf_of", listener->on_behalf_of);
+
+ if (listener->port > 0) {
+ reportxml_helper_add_value_int(config, "port", listener->port);
+ } else {
+ reportxml_helper_add_value(config, "int", "port", NULL);
+ }
+
+ if (listener->so_sndbuf) {
+ reportxml_helper_add_value_int(config, "so_sndbuf", listener->so_sndbuf);
+ } else {
+ reportxml_helper_add_value(config, "int", "so_sndbuf", NULL);
+ }
+
+ if (listener->listen_backlog > 0) {
+ reportxml_helper_add_value_int(config, "listen_backlog", listener->listen_backlog);
+ } else {
+ reportxml_helper_add_value(config, "int", "listen_backlog", NULL);
+ }
+
+ reportxml_helper_add_value_string(config, "bind_address", listener->bind_address);
+ reportxml_helper_add_value_boolean(config, "shoutcast_compat", listener->shoutcast_compat);
+ reportxml_helper_add_value_string(config, "shoutcast_mount", listener->shoutcast_mount);
+ reportxml_helper_add_value_enum(config, "tlsmode", listensocket_tlsmode_to_string(listener->tls));
+
+ if (listener->authstack) {
+ reportxml_node_t * extension = reportxml_node_new(REPORTXML_NODE_TYPE_EXTENSION, NULL, NULL, NULL);
+ xmlNodePtr xmlroot = xmlNewNode(NULL, XMLSTR("icestats"));
+
+ reportxml_node_set_attribute(extension, "application", ADMIN_ICESTATS_LEGACY_EXTENSION_APPLICATION);
+ reportxml_node_add_child(resource, extension);
+
+ xmlSetProp(xmlroot, XMLSTR("xmlns"), XMLSTR(XMLNS_LEGACY_STATS));
+
+ stats_add_authstack(listener->authstack, xmlroot);
+
+ reportxml_node_add_xml_child(extension, xmlroot);
+ refobject_unref(extension);
+ xmlFreeNode(xmlroot);
+ }
+
+ if (listener->http_headers) {
+ reportxml_node_t * headers = reportxml_node_new(REPORTXML_NODE_TYPE_VALUE, NULL, NULL, NULL);
+ ice_config_http_header_t *cur;
+
+ reportxml_node_set_attribute(headers, "member", "headers");
+ reportxml_node_set_attribute(headers, "type", "unordered-list");
+ reportxml_node_add_child(resource, headers);
+
+ for (cur = listener->http_headers; cur; cur = cur->next) {
+ reportxml_node_t * header = reportxml_node_new(REPORTXML_NODE_TYPE_VALUE, NULL, NULL, NULL);
+ reportxml_node_set_attribute(header, "type", "structure");
+ reportxml_node_add_child(headers, header);
+
+ switch (cur->type) {
+ case HTTP_HEADER_TYPE_STATIC:
+ reportxml_helper_add_value_enum(header, "type", "static");
+ break;
+ case HTTP_HEADER_TYPE_CORS:
+ reportxml_helper_add_value_enum(header, "type", "cors");
+ break;
+ }
+
+ reportxml_helper_add_value_string(header, "name", cur->name);
+ reportxml_helper_add_value_string(header, "value", cur->value);
+
+ if (cur->status > 100) {
+ reportxml_helper_add_value_int(header, "status", cur->status > 100);
+ } else {
+ reportxml_helper_add_value(header, "int", "status", NULL);
+ }
+
+ reportxml_helper_add_value_string(config, "shoutcast_mount", listener->shoutcast_mount);
+ refobject_unref(header);
+ }
+
+ refobject_unref(headers);
+ }
+
+ refobject_unref(config);
+ refobject_unref(resource);
+ listensocket_release_listener(sockets[i]);
+ refobject_unref(sockets[i]);
+ }
+
+ free(sockets);
+
+ client_send_reportxml(client, report, DOCUMENT_DOMAIN_ADMIN, LISTENSOCKETLIST_HTML_REQUEST, response, 200, NULL);
+ refobject_unref(report);
+}
+
static void command_updatemetadata(client_t *client,
source_t *source,
admin_format_t response)
@@ -1495,6 +1621,7 @@ static void command_dashboard (client_t *client, source_t *source, adm
bool has_many_clients;
bool has_too_many_clients;
bool has_legacy_sources;
+ bool inet6_enabled;
resource = reportxml_node_new(REPORTXML_NODE_TYPE_RESOURCE, NULL, NULL, NULL);
@@ -1520,13 +1647,14 @@ static void command_dashboard (client_t *client, source_t *source, adm
has_many_clients = global.clients > ((75 * config->client_limit) / 100);
has_too_many_clients = global.clients > ((90 * config->client_limit) / 100);
has_legacy_sources = global.sources_legacy > 0;
+ inet6_enabled = listensocket_container_is_family_included(global.listensockets, SOCK_FAMILY_INET6);
global_unlock();
reportxml_node_add_child(resource, node);
refobject_unref(node);
if (config->config_problems || has_too_many_clients) {
status = command_dashboard__atbest(status, ADMIN_DASHBOARD_STATUS_ERROR);
- } else if (!has_sources || has_many_clients) {
+ } else if (!has_sources || has_many_clients || !inet6_enabled) {
status = command_dashboard__atbest(status, ADMIN_DASHBOARD_STATUS_WARNING);
}
@@ -1535,6 +1663,13 @@ static void command_dashboard (client_t *client, source_t *source, adm
__reportxml_add_maintenance(reportnode, config->reportxml_db, "c704804e-d3b9-4544-898b-d477078135de", "warning", "Developer logging is active. This mode is not for production.", NULL);
#endif
+ if (!inet6_enabled) {
+ __reportxml_add_maintenance(reportnode, config->reportxml_db, "f90219e1-bd07-4b54-b1ee-0ba6a0289a15", "warning", "IPv6 not enabled.", NULL);
+ if (sock_is_ipv4_mapped_supported()) {
+ __reportxml_add_maintenance(reportnode, config->reportxml_db, "709ab43b-251d-49a5-a4fe-c749eaabf17c", "info", "IPv4-mapped IPv6 is available on this system.", NULL);
+ }
+ }
+
if (config->config_problems & CONFIG_PROBLEM_HOSTNAME)
__reportxml_add_maintenance(reportnode, config->reportxml_db, "c4f25c51-2720-4b38-a806-19ef024b5289", "warning", "Hostname is not set to anything useful in .", NULL);
if (config->config_problems & CONFIG_PROBLEM_LOCATION)
diff --git a/src/common b/src/common
index c5a38dc1..bb12858a 160000
--- a/src/common
+++ b/src/common
@@ -1 +1 @@
-Subproject commit c5a38dc195cd02b94a6541fda2c61cc1f7cdb62a
+Subproject commit bb12858abe04bd78f674858bba206ae21d9b6c17
diff --git a/src/listensocket.c b/src/listensocket.c
index 95300fef..ab2fd1cb 100644
--- a/src/listensocket.c
+++ b/src/listensocket.c
@@ -37,6 +37,7 @@
struct listensocket_container_tag {
refobject_base_t __base;
mutex_t lock;
+ bool prefer_inet6;
listensocket_t **sock;
int *sockref;
size_t sock_len;
@@ -60,6 +61,8 @@ static listensocket_t * listensocket_new(const listener_t *listener);
static int listensocket_apply_config(listensocket_t *self);
static int listensocket_apply_config__unlocked(listensocket_t *self);
static int listensocket_set_update(listensocket_t *self, const listener_t *listener);
+static int listensocket_refsock(listensocket_t *self, bool prefer_inet6);
+static int listensocket_unrefsock(listensocket_t *self);
#ifdef HAVE_POLL
static inline int listensocket__poll_fill(listensocket_t *self, struct pollfd *p);
#else
@@ -271,11 +274,15 @@ int listensocket_container_configure_and_setup(listensoc
{
void (*cb)(size_t count, void *userdata);
int ret;
+ bool prefer_inet6;
if (!self)
return -1;
+ prefer_inet6 = sock_is_ipv4_mapped_supported(); /* test before we enter lock to minimise locked time */
+
thread_mutex_lock(&self->lock);
+ self->prefer_inet6 = prefer_inet6;
cb = self->sockcount_cb;
self->sockcount_cb = NULL;
@@ -295,11 +302,15 @@ int listensocket_container_configure_and_setup(listensoc
int listensocket_container_setup(listensocket_container_t *self)
{
int ret;
+ bool prefer_inet6;
if (!self)
return -1;
+ prefer_inet6 = sock_is_ipv4_mapped_supported(); /* test before we enter lock to minimise locked time */
+
thread_mutex_lock(&self->lock);
+ self->prefer_inet6 = prefer_inet6;
ret = listensocket_container_setup__unlocked(self);
thread_mutex_unlock(&self->lock);
@@ -320,7 +331,7 @@ static int listensocket_container_setup__unlocked(listensocket_container_t *self
self->sockref[i] = 0;
}
} else if (!self->sockref[i] && type != LISTENER_TYPE_VIRTUAL) {
- if (listensocket_refsock(self->sock[i]) == 0) {
+ if (listensocket_refsock(self->sock[i], self->prefer_inet6) == 0) {
self->sockref[i] = 1;
} else {
ICECAST_LOG_DEBUG("Can not ref socket.");
@@ -506,6 +517,48 @@ listensocket_t * listensocket_container_get_by_id(listensocket_container_t *self
return NULL;
}
+listensocket_t ** listensocket_container_list_sockets(listensocket_container_t *self)
+{
+ listensocket_t **res;
+ size_t idx = 0;
+ size_t i;
+
+ thread_mutex_lock(&self->lock);
+ res = calloc(self->sock_len + 1, sizeof(*res));
+ if (!res) {
+ thread_mutex_unlock(&self->lock);
+ return NULL;
+ }
+
+ for (i = 0; i < self->sock_len; i++) {
+ if (self->sock[i] != NULL) {
+ refobject_ref(res[idx++] = self->sock[i]);
+ }
+ }
+
+ thread_mutex_unlock(&self->lock);
+
+ return res;
+}
+
+bool listensocket_container_is_family_included(listensocket_container_t *self, sock_family_t family)
+{
+ size_t i;
+
+ thread_mutex_lock(&self->lock);
+ for (i = 0; i < self->sock_len; i++) {
+ if (self->sock[i] != NULL) {
+ if (listensocket_get_family(self->sock[i]) == family) {
+ thread_mutex_unlock(&self->lock);
+ return true;
+ }
+ }
+ }
+ thread_mutex_unlock(&self->lock);
+
+ return false;
+}
+
/* ---------------------------------------------------------------------------- */
static void __listensocket_free(refobject_t self, void **userdata)
@@ -634,7 +687,7 @@ static int listensocket_set_update(listensocket_t *self, const list
return 0;
}
-int listensocket_refsock(listensocket_t *self)
+static int listensocket_refsock(listensocket_t *self, bool prefer_inet6)
{
if (!self)
return -1;
@@ -647,7 +700,7 @@ int listensocket_refsock(listensocket_t *self)
}
thread_rwlock_rlock(&self->listener_rwlock);
- self->sock = sock_get_server_socket(self->listener->port, self->listener->bind_address);
+ self->sock = sock_get_server_socket(self->listener->port, self->listener->bind_address, self->listener->bind_address ? false : prefer_inet6);
thread_rwlock_unlock(&self->listener_rwlock);
if (self->sock == SOCK_ERROR) {
thread_mutex_unlock(&self->lock);
@@ -675,7 +728,7 @@ int listensocket_refsock(listensocket_t *self)
return 0;
}
-int listensocket_unrefsock(listensocket_t *self)
+static int listensocket_unrefsock(listensocket_t *self)
{
if (!self)
return -1;
@@ -799,6 +852,20 @@ listener_type_t listensocket_get_type(listensocket_t *self)
return ret;
}
+sock_family_t listensocket_get_family(listensocket_t *self)
+{
+ sock_family_t ret;
+
+ if (!self)
+ return SOCK_FAMILY__ERROR;
+
+ thread_mutex_lock(&self->lock);
+ ret = sock_get_family(self->sock);
+ thread_mutex_unlock(&self->lock);
+
+ return ret;
+}
+
#ifdef HAVE_POLL
static inline int listensocket__poll_fill(listensocket_t *self, struct pollfd *p)
{
@@ -858,3 +925,45 @@ static inline int listensocket__select_isset(listensocket_t *self, fd_set *set)
}
#endif
+
+/* ---------------------------------------------------------------------------- */
+
+const char * listensocket_type_to_string(listener_type_t type)
+{
+ switch (type) {
+ case LISTENER_TYPE_ERROR:
+ return NULL;
+ break;
+ case LISTENER_TYPE_NORMAL:
+ return "normal";
+ break;
+ case LISTENER_TYPE_VIRTUAL:
+ return "virtual";
+ break;
+ }
+
+ return NULL;
+}
+
+const char * listensocket_tlsmode_to_string(tlsmode_t mode)
+{
+ switch (mode) {
+ case ICECAST_TLSMODE_DISABLED:
+ return "disabled";
+ break;
+ case ICECAST_TLSMODE_AUTO:
+ return "auto";
+ break;
+ case ICECAST_TLSMODE_AUTO_NO_PLAIN:
+ return "auto_no_plain";
+ break;
+ case ICECAST_TLSMODE_RFC2817:
+ return "rfc2817";
+ break;
+ case ICECAST_TLSMODE_RFC2818:
+ return "rfc2818";
+ break;
+ }
+
+ return NULL;
+}
diff --git a/src/listensocket.h b/src/listensocket.h
index cb7974df..a37344a7 100644
--- a/src/listensocket.h
+++ b/src/listensocket.h
@@ -9,6 +9,10 @@
#ifndef __LISTENSOCKET_H__
#define __LISTENSOCKET_H__
+#include
+
+#include "common/net/sock.h"
+
#include "icecasttypes.h"
#include "refobject.h"
#include "cfgfile.h"
@@ -23,14 +27,18 @@ connection_t * listensocket_container_accept(listensocket_container
int listensocket_container_set_sockcount_cb(listensocket_container_t *self, void (*cb)(size_t count, void *userdata), void *userdata);
ssize_t listensocket_container_sockcount(listensocket_container_t *self);
listensocket_t * listensocket_container_get_by_id(listensocket_container_t *self, const char *id);
+listensocket_t ** listensocket_container_list_sockets(listensocket_container_t *self);
+bool listensocket_container_is_family_included(listensocket_container_t *self, sock_family_t family);
REFOBJECT_FORWARD_TYPE(listensocket_t);
-int listensocket_refsock(listensocket_t *self);
-int listensocket_unrefsock(listensocket_t *self);
connection_t * listensocket_accept(listensocket_t *self, listensocket_container_t *container);
const listener_t * listensocket_get_listener(listensocket_t *self);
int listensocket_release_listener(listensocket_t *self);
listener_type_t listensocket_get_type(listensocket_t *self);
+sock_family_t listensocket_get_family(listensocket_t *self);
+
+const char * listensocket_type_to_string(listener_type_t type);
+const char * listensocket_tlsmode_to_string(tlsmode_t mode);
#endif
diff --git a/src/stats.c b/src/stats.c
index b2113b7b..f2717172 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -825,7 +825,8 @@ static int _send_event_to_client(stats_event_t *event, client_t *client)
return 0;
}
-static inline void __add_authstack (auth_stack_t *stack, xmlNodePtr parent) {
+void stats_add_authstack(auth_stack_t *stack, xmlNodePtr parent)
+{
xmlNodePtr authentication;
authentication = xmlNewTextChild(parent, NULL, XMLSTR("authentication"), NULL);
@@ -866,7 +867,7 @@ static xmlNodePtr _dump_stats_to_doc (xmlNodePtr root, unsigned int flags, const
flags &= ~(STATS_XML_FLAG_SHOW_LISTENERS|STATS_XML_FLAG_SHOW_HIDDEN);
} else {
config = config_get_config();
- __add_authstack(config->authstack, root);
+ stats_add_authstack(config->authstack, root);
config_release_config();
}
@@ -941,7 +942,7 @@ static xmlNodePtr _dump_stats_to_doc (xmlNodePtr root, unsigned int flags, const
config = config_get_config();
mountproxy = config_find_mount(config, source->source, MOUNT_TYPE_NORMAL);
if (mountproxy)
- __add_authstack(mountproxy->authstack, xmlnode);
+ stats_add_authstack(mountproxy->authstack, xmlnode);
config_release_config();
}
}
diff --git a/src/stats.h b/src/stats.h
index 1bdefb73..b149fead 100644
--- a/src/stats.h
+++ b/src/stats.h
@@ -103,5 +103,7 @@ void stats_sendxml(client_t *client);
xmlDocPtr stats_get_xml(unsigned int flags, const char *show_mount, client_t *client);
char *stats_get_value(const char *source, const char *name);
+void stats_add_authstack(auth_stack_t *stack, xmlNodePtr parent);
+
#endif /* __STATS_H__ */