# HG changeset patch # User Menel # Date 1777027213 -7200 # Node ID 7eb114975cec95379ae4c612f4dfa8c93706e920 # Parent 01d8bfbfc435ec5d2a37363bd5b5e39907433ed7 mod_admin_web2: Fork of admin_web for prosody 13.0. It is a fork because it changes access rights and might be insecure. Mark as alpha. diff -r 01d8bfbfc435 -r 7eb114975cec mod_admin_web2/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_admin_web2/README.md Fri Apr 24 12:40:13 2026 +0200 @@ -0,0 +1,67 @@ +--- +labels: +- 'Stage-Alpha' +summary: Web administration interface +rockspec: + build: + platforms: + unix: + type: command + build_command: cd admin_web2; sh get_deps.sh + install_command: | + cd admin_web2; + cp mod_admin_web2.lua $(LUADIR); + cp -r www_files $(PREFIX); +... + +Introduction +============ + +This module is a fork of [mod_admin_web], because that on is incompatible with prosody 13.0. +It was made with only small changes but also without much understanding of lua and the internal workigs of prosody. Use with care. Restrict access from the web. +Patches welcome. + +This module provides a basic web-administration interface. It currently +gives you access to Ad-Hoc commands on any virtual host or component. It +also provides a live list of S2S and C2S connections. (buggy, not showing all of them) + +**New:** You need to be the global instance operator to access this module. Smiplifying access rights. +**Known Issue:** If you define it on more then one host or globally, it might be nessesary to reload the module to have access to all hosts from within one interface. + +Installation +============ + +1. Copy the admin\_web directory into a directory Prosody will check + for plugins. (cf. [Installing + modules](http://prosody.im/doc/installing_modules)) +2. **Execute the contained `get_deps.sh` script from within the admin\_web + directory.** (Requires wget, tar, and a basic shell) + +Configuration Details +===================== + +"admin\_web" needs to be added to the modules\_enabled table of the host +you want to load this module on. + +By default the interface will then be reachable under +`http://example.com:5280/admin`, or `https://example.com:5281/admin`. + +The module will automatically enable two other modules if they aren't +already: mod\_bosh (used to connect to the server from the web), and +mod\_admin\_adhoc (which provides admin commands over XMPP). + + VirtualHost "example.com" + modules_enabled = { + ..... + "admin_web"; + ..... + } + +Compatibility +============= + + --------- ---------------------------------- + 13.0 Works + 0.12 not supported, use [mod_admin_web] + \<= 0.11 not supported + --------- ---------------------------------- diff -r 01d8bfbfc435 -r 7eb114975cec mod_admin_web2/admin_web2/get_deps.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_admin_web2/admin_web2/get_deps.sh Fri Apr 24 12:40:13 2026 +0200 @@ -0,0 +1,23 @@ +#!/bin/sh +JQUERY_VERSION="1.10.2" +STROPHE_VERSION="1.1.2" +BOOTSTRAP_VERSION="1.4.0" +ADHOC_COMMITISH="87bfedccdb91e2ff7cfb165e989e5259c155b513" + +cd www_files/js + +rm -f jquery-$JQUERY_VERSION.min.js +wget http://code.jquery.com/jquery-$JQUERY_VERSION.min.js || exit 1 + +rm -f adhoc.js +wget -O adhoc.js "http://git.babelmonkeys.de/?p=adhocweb.git;a=blob_plain;f=js/adhoc.js;hb=$ADHOC_COMMITISH" || exit 1 + +rm -f strophe.min.js +wget https://raw.github.com/strophe/strophe.im/gh-pages/strophejs/downloads/strophejs-$STROPHE_VERSION.tar.gz && + tar xzf strophejs-$STROPHE_VERSION.tar.gz strophejs-$STROPHE_VERSION/strophe.min.js && + mv strophejs-$STROPHE_VERSION/strophe.min.js . && + rm -r strophejs-$STROPHE_VERSION strophejs-$STROPHE_VERSION.tar.gz || exit 1 + +cd ../css +rm -f bootstrap-$BOOTSTRAP_VERSION.min.css +wget https://raw.github.com/twbs/bootstrap/v$BOOTSTRAP_VERSION/bootstrap.min.css -O bootstrap-$BOOTSTRAP_VERSION.min.css || exit 1 diff -r 01d8bfbfc435 -r 7eb114975cec mod_admin_web2/admin_web2/mod_admin_web2.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_admin_web2/admin_web2/mod_admin_web2.lua Fri Apr 24 12:40:13 2026 +0200 @@ -0,0 +1,332 @@ +-- Copyright (C) 2010 Florian Zeitz +-- +-- This file is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +-- +-- +-- +-- + +-- +-- +-- / +-- +-- +-- / +-- + +module:default_permission("prosody:operator", ":web-operator") + +local st = require "util.stanza"; +local uuid_generate = require "util.uuid".generate; +local pubsub = require "util.pubsub"; +local jid_bare = require "util.jid".bare; + +local hosts = prosody.hosts; +local incoming_s2s = prosody.incoming_s2s; + +module:set_global(); + +local service = {}; + +local xmlns_adminsub = "http://prosody.im/adminsub"; +local xmlns_c2s_session = "http://prosody.im/streams/c2s"; +local xmlns_s2s_session = "http://prosody.im/streams/s2s"; + +local idmap = {}; + +local function add_client(session, host) + local name = session.full_jid; + local id = idmap[name]; + if not id then + id = uuid_generate(); + idmap[name] = id; + end + local item = st.stanza("item", { id = id }):tag("session", {xmlns = xmlns_c2s_session, jid = name}):up(); + if session.secure then + local encrypted = item:tag("encrypted"); + local sock = session.conn and session.conn.socket and session.conn:socket() + local info = sock and sock.info and sock:info(); + for k, v in pairs(info or {}) do + encrypted:tag("info", { name = k }):text(tostring(v)):up(); + end + end + if session.compressed then + item:tag("compressed"):up(); + end + service[host]:publish(xmlns_c2s_session, host, id, item); + module:log("debug", "Added client %s", name); +end + +local function del_client(session, host) + local name = session.full_jid; + local id = idmap[name]; + if id then + local notifier = st.stanza("retract", { id = id }); + service[host]:retract(xmlns_c2s_session, host, id, notifier); + end +end + +local function add_host(session, type, host) + local name = (type == "out" and session.to_host) or (type == "in" and session.from_host); + local id = idmap[name.."_"..type]; + if not id then + id = uuid_generate(); + idmap[name.."_"..type] = id; + end + local item = st.stanza("item", { id = id }):tag("session", {xmlns = xmlns_s2s_session, jid = name}) + :tag(type):up(); + if session.secure then + local encrypted = item:tag("encrypted"); + + local sock = session.conn and session.conn.socket and session.conn:socket() + local info = sock and sock.info and sock:info(); + for k, v in pairs(info or {}) do + encrypted:tag("info", { name = k }):text(tostring(v)):up(); + end + + if session.cert_identity_status == "valid" then + encrypted:tag("valid"); + else + encrypted:tag("invalid"); + end + end + if session.compressed then + item:tag("compressed"):up(); + end + service[host]:publish(xmlns_s2s_session, host, id, item); + module:log("debug", "Added host %s s2s%s", name, type); +end + +local function del_host(session, type, host) + local name = (type == "out" and session.to_host) or (type == "in" and session.from_host); + local id = idmap[name.."_"..type]; + if id then + local notifier = st.stanza("retract", { id = id }); + service[host]:retract(xmlns_s2s_session, host, id, notifier); + end +end + +function module.add_host(module) + -- Dependencies + module:depends("bosh"); + module:depends("admin_adhoc"); + module:depends("http"); + + local http_files = require "net.http.files"; + local serve_file = http_files.serve { + path = module:get_directory() .. "/www_files"; + }; + + -- Setup HTTP server + module:provides("http", { + title = "Admin Interface"; + name = "admin"; + route = { + ["GET"] = function(event) + event.response.headers.location = event.request.path .. "/"; + return 301; + end; + ["GET /*"] = serve_file; + } + }); + + -- Setup adminsub service + local function simple_broadcast(kind, node, jids, item) + if item then + item = st.clone(item); + item.attr.xmlns = nil; -- Clear the pubsub namespace + end + local message = st.message({ from = module.host, type = "headline" }) + :tag("event", { xmlns = xmlns_adminsub .. "#event" }) + :tag(kind, { node = node }) + :add_child(item); + for jid in pairs(jids) do + module:log("debug", "Sending notification to %s", jid); + message.attr.to = jid; + module:send(message); + end + end + + service[module.host] = pubsub.new({ + broadcaster = simple_broadcast; + normalize_jid = jid_bare; +-- what was this for? prosody:operator has full rights everywhere, this shoud be fine? +-- get_affiliation = function(jid) return get_affiliation(jid, module.host) end; + get_affiliation = function() return "owner" end; + capabilities = { + member = { + create = false; + publish = false; + retract = false; + get_nodes = true; + + subscribe = true; + unsubscribe = true; + get_subscription = true; + get_subscriptions = true; + get_items = true; + + subscribe_other = false; + unsubscribe_other = false; + get_subscription_other = false; + get_subscriptions_other = false; + + be_subscribed = true; + be_unsubscribed = true; + + set_affiliation = false; + }; + + owner = { + create = true; + publish = true; + retract = true; + get_nodes = true; + + subscribe = true; + unsubscribe = true; + get_subscription = true; + get_subscriptions = true; + get_items = true; + + subscribe_other = true; + unsubscribe_other = true; + get_subscription_other = true; + get_subscriptions_other = true; + + be_subscribed = true; + be_unsubscribed = true; + + set_affiliation = true; + }; + }; + }); + + -- Create node for s2s sessions + local ok, err = service[module.host]:create(xmlns_s2s_session, true); + if not ok then + module:log("warn", "Could not create node %s: %s", xmlns_s2s_session, err); + else + service[module.host]:set_affiliation(xmlns_s2s_session, true, module.host, "owner") + end + + -- Add outgoing s2s sessions + for _, session in pairs(hosts[module.host].s2sout) do + if session.type ~= "s2sout_unauthed" then + add_host(session, "out", module.host); + end + end + + -- Add incoming s2s sessions + for session in pairs(incoming_s2s) do + if session.to_host == module.host then + add_host(session, "in", module.host); + end + end + + -- Create node for c2s sessions + ok, err = service[module.host]:create(xmlns_c2s_session, true); + if not ok then + module:log("warn", "Could not create node %s: %s", xmlns_c2s_session, tostring(err)); + else + service[module.host]:set_affiliation(xmlns_c2s_session, true, module.host, "owner") + end + + -- Add c2s sessions + for _, user in pairs(hosts[module.host].sessions or {}) do + for _, session in pairs(user.sessions or {}) do + add_client(session, module.host); + end + end + + -- Register adminsub handler + module:hook("iq/host/http://prosody.im/adminsub:adminsub", function(event) + -- luacheck: ignore 431/ok + local origin, stanza = event.origin, event.stanza; + local adminsub = stanza.tags[1]; + local action = adminsub.tags[1]; + local reply; + if action.name == "subscribe" then + local ok, ret = service[module.host]:add_subscription(action.attr.node, stanza.attr.from, stanza.attr.from); + if ok then + reply = st.reply(stanza) + :tag("adminsub", { xmlns = xmlns_adminsub }); + else + reply = st.error_reply(stanza, "cancel", ret); + end + elseif action.name == "unsubscribe" then + local ok, ret = service[module.host]:remove_subscription(action.attr.node, stanza.attr.from, stanza.attr.from); + if ok then + reply = st.reply(stanza) + :tag("adminsub", { xmlns = xmlns_adminsub }); + else + reply = st.error_reply(stanza, "cancel", ret); + end + elseif action.name == "items" then + local node = action.attr.node; + local ok, ret = service[module.host]:get_items(node, stanza.attr.from); + if not ok then + origin.send(st.error_reply(stanza, "cancel", ret)); + return true; + end + + local data = st.stanza("items", { node = node }); + for _, entry in pairs(ret) do + data:add_child(entry); + end + if data then + reply = st.reply(stanza) + :tag("adminsub", { xmlns = xmlns_adminsub }) + :add_child(data); + else + reply = st.error_reply(stanza, "cancel", "item-not-found"); + end + elseif action.name == "adminfor" then + local data = st.stanza("adminfor"); + for host_name in pairs(hosts) do + -- new Permission API: + if module:context(host_name):may(":web-operator", stanza.attr.from) then + data:tag("item"):text(host_name):up(); + end + end + reply = st.reply(stanza) + :tag("adminsub", { xmlns = xmlns_adminsub }) + :add_child(data); + else + reply = st.error_reply(stanza, "feature-not-implemented"); + end + origin.send(reply); + return true; + end); + + -- Add/remove c2s sessions + module:hook("resource-bind", function(event) + add_client(event.session, module.host); + end); + + module:hook("resource-unbind", function(event) + del_client(event.session, module.host); + service[module.host]:remove_subscription(xmlns_c2s_session, module.host, event.session.full_jid); + service[module.host]:remove_subscription(xmlns_s2s_session, module.host, event.session.full_jid); + end); + + -- Add/remove s2s sessions + module:hook("s2sout-established", function(event) + add_host(event.session, "out", module.host); + end); + + module:hook("s2sin-established", function(event) + add_host(event.session, "in", module.host); + end); + + module:hook("s2sout-destroyed", function(event) + del_host(event.session, "out", module.host); + end); + + module:hook("s2sin-destroyed", function(event) + del_host(event.session, "in", module.host); + end); +end diff -r 01d8bfbfc435 -r 7eb114975cec mod_admin_web2/admin_web2/www_files/css/style.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_admin_web2/admin_web2/www_files/css/style.css Fri Apr 24 12:40:13 2026 +0200 @@ -0,0 +1,114 @@ +body { + margin: 0 +} + +a { + color: #0000FF +} + +#adhocCommands > ul { + margin: 0 +} + +.btn { + margin-right: 0.3em +} + +.btn:last-child { + margin-right: 0 +} + +#log_container { + clear: both; + display: none +} + +#adhocCommands { + border-right: solid 1px +} + +#adhocCommands li { + list-style: inside +} + +#login { + float: left; + margin: 1em 2em 0 1em; + padding-right: 1em; + border: solid 1px; + background: #eef0f2; + color: #000000 +} + +#main { + display: none; + margin: 1em +} + +#main p { + margin: 0 +} + +#top { + clear: both; + width: 100%; + padding: 0; +} + +@media screen and (min-width: 757px) { + #header { + background: url(../images/blue_orange.png) repeat-x + } +} + +#header img { + max-width: 100%; + height: auto +} + +#menu { + display: none; + color: #454748; + font-size: 1.1em; + background: #eef0f2; + width: 100%; +} + +#menu ul { + display: inline; + list-style-type: none; + margin: 0; + padding: 0.5em 0 +} + +#menu li { + display: inline; + padding: 0 0.5em +} + +#menu a { + color: #454748; + text-decoration: none +} + +#menu li a:hover { + color: #6197DF; + text-decoration: underline +} + +#selector { + display: inline-block +} + +#s2sList h2, #c2sList h2 { + color: #4b8ade; + margin: 0 +} + +#s2sList li, #c2sList li { + cursor: pointer +} + +#host { + margin: 0.25em; +} diff -r 01d8bfbfc435 -r 7eb114975cec mod_admin_web2/admin_web2/www_files/images/blue_orange.png Binary file mod_admin_web2/admin_web2/www_files/images/blue_orange.png has changed diff -r 01d8bfbfc435 -r 7eb114975cec mod_admin_web2/admin_web2/www_files/images/compressed.png Binary file mod_admin_web2/admin_web2/www_files/images/compressed.png has changed diff -r 01d8bfbfc435 -r 7eb114975cec mod_admin_web2/admin_web2/www_files/images/encrypted.png Binary file mod_admin_web2/admin_web2/www_files/images/encrypted.png has changed diff -r 01d8bfbfc435 -r 7eb114975cec mod_admin_web2/admin_web2/www_files/images/prosody.png Binary file mod_admin_web2/admin_web2/www_files/images/prosody.png has changed diff -r 01d8bfbfc435 -r 7eb114975cec mod_admin_web2/admin_web2/www_files/images/secure.png Binary file mod_admin_web2/admin_web2/www_files/images/secure.png has changed diff -r 01d8bfbfc435 -r 7eb114975cec mod_admin_web2/admin_web2/www_files/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_admin_web2/admin_web2/www_files/index.html Fri Apr 24 12:40:13 2026 +0200 @@ -0,0 +1,90 @@ + + + + +Prosody Webadmin + + + + + + + + + + +
+ + +
+ +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+

Incoming S2S connections:

+
    +
    +
    +

    Outgoing S2S connections:

    +
      +
      +
      +
      +
      +
      +
      +

      Client connections:

      +
        +
        +
        +
        +
        + +
        + Status Log : +
        +
        + + diff -r 01d8bfbfc435 -r 7eb114975cec mod_admin_web2/admin_web2/www_files/js/main.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_admin_web2/admin_web2/www_files/js/main.js Fri Apr 24 12:40:13 2026 +0200 @@ -0,0 +1,255 @@ +var BOSH_SERVICE = '/http-bind/'; +var show_log = false; + +Strophe.addNamespace('C2SSTREAM', 'http://prosody.im/streams/c2s'); +Strophe.addNamespace('S2SSTREAM', 'http://prosody.im/streams/s2s'); +Strophe.addNamespace('ADMINSUB', 'http://prosody.im/adminsub'); +Strophe.addNamespace('CAPS', 'http://jabber.org/protocol/caps'); + +var localJID = null; +var connection = null; + +var adminsubHost = null; +var adhocControl = new Adhoc('#adhocDisplay', function() {}); + +function log(msg) { + var entry = $('
        ').append(document.createTextNode(msg)); + $('#log').append(entry); +} + +function rawInput(data) { + log('RECV: ' + data); +} + +function rawOutput(data) { + log('SENT: ' + data); +} + +function _cbNewS2S(e) { + var items, item, entry, tmp, retract, id, jid, infos, info, metadata; + items = e.getElementsByTagName('item'); + for (i = 0; i < items.length; i++) { + item = items[i]; + id = item.attributes.getNamedItem('id').value; + jid = item.getElementsByTagName('session')[0].attributes.getNamedItem('jid').value; + infos = item.getElementsByTagName('info'); + + entry = $('
      • ' + jid + '
      • '); + if (tmp = item.getElementsByTagName('encrypted')[0]) { + if (tmp.getElementsByTagName('valid')[0]) { + entry.append(' (secure) (encrypted)'); + } else { + entry.append(' (encrypted)'); + } + } + if (item.getElementsByTagName('compressed')[0]) { + entry.append(' (compressed)'); + } + metadata = $('