From b6f7f8fba7ba81c5a0ab5477a1d103b7279bf38f Mon Sep 17 00:00:00 2001 From: bgrozev Date: Mon, 11 Jan 2021 15:45:00 -0600 Subject: [PATCH] Remove the "focus" external component, use client_proxy instead. (#8381) * feat: Add mod_client_proxy and mod_roster_command. Taken from prosody-modules 4317:456b9f608fcf with the mod_roster_command patch applied. * feat: Use mod_client_proxy to proxy to jicofo. --- debian/jitsi-meet-prosody.postinst | 15 ++ .../prosody.cfg.lua-jvb.example | 5 +- .../prosody-plugins/mod_client_proxy.lua | 202 ++++++++++++++++++ .../prosody-plugins/mod_roster_command.lua | 165 ++++++++++++++ .../prosody-plugins/mod_roster_command.patch | 47 ++++ 5 files changed, 432 insertions(+), 2 deletions(-) create mode 100644 resources/prosody-plugins/mod_client_proxy.lua create mode 100644 resources/prosody-plugins/mod_roster_command.lua create mode 100644 resources/prosody-plugins/mod_roster_command.patch diff --git a/debian/jitsi-meet-prosody.postinst b/debian/jitsi-meet-prosody.postinst index cf92c87ba8..5beb04a78f 100644 --- a/debian/jitsi-meet-prosody.postinst +++ b/debian/jitsi-meet-prosody.postinst @@ -142,6 +142,21 @@ case "$1" in echo -e " admins = { \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\", \"jvb@auth.$JVB_HOSTNAME\" }" >> $PROSODY_HOST_CONFIG fi + # Convert the old focus component config to the new one. + # Old: + # Component "focus.jitmeet.example.com" + # component_secret = "focusSecret" + # New: + # Component "focus.jitmeet.example.com" "client_proxy" + # target_address = "focus@auth.jitmeet.example.com" + if grep -q "Component \"focus.$JVB_HOSTNAME\"" $PROSODY_HOST_CONFIG && ! grep "Component \"focus.$JVB_HOSTNAME\" \"client_proxy\"" $PROSODY_HOST_CONFIG ;then + sed -i -e "s/Component \"focus.$JVB_HOSTNAME\"/Component \"focus.$JVB_HOSTNAME\" \"client_proxy\"\n target_address = \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\"/" $PROSODY_HOST_CONFIG + PROSODY_CONFIG_PRESENT="false" + fi + + # Make sure the focus@auth user's roster includes the proxy component (this is idempotent) + prosodyctl mod_roster_command subscribe focus.$JVB_HOSTNAME $JICOFO_AUTH_USER@auth.$JVB_HOSTNAME + if [ ! -f /var/lib/prosody/$JVB_HOSTNAME.crt ]; then # prosodyctl takes care for the permissions # echo for using all default values diff --git a/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example b/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example index a46f4f207c..db19ab12f7 100644 --- a/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example +++ b/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example @@ -77,8 +77,9 @@ Component "internal.auth.jitmeet.example.com" "muc" VirtualHost "auth.jitmeet.example.com" authentication = "internal_plain" -Component "focus.jitmeet.example.com" - component_secret = "focusSecret" +-- Proxy to jicofo's user JID, so that it doesn't have to register as a component. +Component "focus.jitmeet.example.com" "client_proxy" + target_address = "focusUser@auth.jitmeet.example.com" Component "speakerstats.jitmeet.example.com" "speakerstats_component" muc_component = "conference.jitmeet.example.com" diff --git a/resources/prosody-plugins/mod_client_proxy.lua b/resources/prosody-plugins/mod_client_proxy.lua new file mode 100644 index 0000000000..93ca767a0c --- /dev/null +++ b/resources/prosody-plugins/mod_client_proxy.lua @@ -0,0 +1,202 @@ +if module:get_host_type() ~= "component" then + error("proxy_component should be loaded as component", 0); +end + +local jid_split = require "util.jid".split; +local jid_bare = require "util.jid".bare; +local jid_prep = require "util.jid".prep; +local st = require "util.stanza"; +local array = require "util.array"; + +local target_address = module:get_option_string("target_address"); + +sessions = array{}; +local sessions = sessions; + +local function handle_target_presence(stanza) + local type = stanza.attr.type; + module:log("debug", "received presence from destination: %s", type) + local _, _, resource = jid_split(stanza.attr.from); + if type == "error" then + -- drop all known sessions + for k in pairs(sessions) do + sessions[k] = nil + end + module:log( + "debug", + "received error presence, dropping all target sessions", + resource + ) + elseif type == "unavailable" then + for k in pairs(sessions) do + if sessions[k] == resource then + sessions[k] = nil + module:log( + "debug", + "dropped target session: %s", + resource + ) + break + end + end + elseif not type then + -- available + local found = false; + for k in pairs(sessions) do + if sessions[k] == resource then + found = true; + break + end + end + if not found then + module:log( + "debug", + "registered new target session: %s", + resource + ) + sessions:push(resource) + end + end +end + +local function handle_from_target(stanza) + local type = stanza.attr.type + module:log( + "debug", + "non-presence stanza from target: name = %s, type = %s", + stanza.name, + type + ) + if stanza.name == "iq" then + if type == "error" or type == "result" then + -- de-NAT message + local _, _, denatted_to_unprepped = jid_split(stanza.attr.to); + local denatted_to = jid_prep(denatted_to_unprepped); + if not denatted_to then + module:log( + "debug", + "cannot de-NAT stanza, invalid to: %s", + denatted_to_unprepped + ) + return + end + local denatted_from = module:get_host(); + + module:log( + "debug", + "de-NAT-ed stanza: from: %s -> %s, to: %s -> %s", + stanza.attr.from, + denatted_from, + stanza.attr.to, + denatted_to + ) + + stanza.attr.from = denatted_from + stanza.attr.to = denatted_to + + module:send(stanza) + else + -- FIXME: we don’t support NATing outbund requests atm. + module:send(st.error_reply(stanza, "cancel", "feature-not-implemented")) + end + elseif stanza.name == "message" then + -- not implemented yet, we need a way to ensure that routing doesn’t + -- break + module:send(st.error_reply(stanza, "cancel", "feature-not-implemented")) + end +end + +local function handle_to_target(stanza) + local type = stanza.attr.type; + module:log( + "debug", + "stanza to target: name = %s, type = %s", + stanza.name, type + ) + if stanza.name == "presence" then + if type ~= "error" then + module:send(st.error_reply(stanza, "cancel", "bad-request")) + return + end + elseif stanza.name == "iq" then + if type == "get" or type == "set" then + if #sessions == 0 then + -- no sessions available to send to + module:log("debug", "no sessions to send to!") + module:send(st.error_reply(stanza, "cancel", "service-unavailable")) + return + end + + -- find a target session + local target_session = sessions:random() + local target = target_address .. "/" .. target_session + + -- encode sender JID in resource + local natted_from = module:get_host() .. "/" .. stanza.attr.from; + + module:log( + "debug", + "NAT-ed stanza: from: %s -> %s, to: %s -> %s", + stanza.attr.from, + natted_from, + stanza.attr.to, + target + ) + + stanza.attr.from = natted_from + stanza.attr.to = target + + module:send(stanza) + end + -- FIXME: handle and forward result/error correctly + elseif stanza.name == "message" then + -- not implemented yet, we need a way to ensure that routing doesn’t + -- break + module:send(st.error_reply(stanza, "cancel", "feature-not-implemented")) + end +end + +local function stanza_handler(event) + local origin, stanza = event.origin, event.stanza + module:log("debug", "received stanza from %s session", origin.type) + + local bare_from = jid_bare(stanza.attr.from); + local _, _, to = jid_split(stanza.attr.to); + if bare_from == target_address then + -- from our target, to whom? + if not to then + -- directly to component + if stanza.name == "presence" then + handle_target_presence(stanza) + else + module:send(st.error_reply(stanza, "cancel", "bad-request")) + return true + end + else + -- to someone else + handle_from_target(stanza) + end + else + handle_to_target(stanza) + end + return true +end + +module:hook("iq/bare", stanza_handler, -1); +module:hook("message/bare", stanza_handler, -1); +module:hook("presence/bare", stanza_handler, -1); +module:hook("iq/full", stanza_handler, -1); +module:hook("message/full", stanza_handler, -1); +module:hook("presence/full", stanza_handler, -1); +module:hook("iq/host", stanza_handler, -1); +module:hook("message/host", stanza_handler, -1); +module:hook("presence/host", stanza_handler, -1); + +module:log("debug", "loaded proxy on %s", module:get_host()) + +subscription_request = st.presence({ + type = "subscribe", + to = target_address, + from = module:get_host()} +) +module:send(subscription_request) diff --git a/resources/prosody-plugins/mod_roster_command.lua b/resources/prosody-plugins/mod_roster_command.lua new file mode 100644 index 0000000000..dc6e67ec47 --- /dev/null +++ b/resources/prosody-plugins/mod_roster_command.lua @@ -0,0 +1,165 @@ +----------------------------------------------------------- +-- mod_roster_command: Manage rosters through prosodyctl +-- version 0.02 +----------------------------------------------------------- +-- Copyright (C) 2011 Matthew Wild +-- Copyright (C) 2011 Adam Nielsen +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +----------------------------------------------------------- + +if module.host ~= "*" then + module:log("error", "Do not load this module in Prosody, for correct usage see: https://modules.prosody.im/mod_roster_command.html"); + return; +end + + +-- Workaround for lack of util.startup... +local prosody = _G.prosody; +local hosts = prosody.hosts; +prosody.bare_sessions = prosody.bare_sessions or {}; +_G.bare_sessions = _G.bare_sessions or prosody.bare_sessions; + +local usermanager = require "core.usermanager"; +local rostermanager = require "core.rostermanager"; +local storagemanager = require "core.storagemanager"; +local jid = require "util.jid"; +local warn = require"util.prosodyctl".show_warning; + +-- Make a *one-way* subscription. User will see when contact is online, +-- contact will not see when user is online. +function subscribe(user_jid, contact_jid) + local user_username, user_host = jid.split(user_jid); + local contact_username, contact_host = jid.split(contact_jid); + if not hosts[user_host] then + warn("The host '%s' is not configured for this server.", user_host); + return; + end + if hosts[user_host].users.name == "null" then + storagemanager.initialize_host(user_host); + usermanager.initialize_host(user_host); + end + -- Update user's roster to say subscription request is pending. Bare hosts (e.g. components) don't have rosters. + if user_username ~= nil then + rostermanager.set_contact_pending_out(user_username, user_host, contact_jid); + end + if hosts[contact_host] then + if contact_host ~= user_host and hosts[contact_host].users.name == "null" then + storagemanager.initialize_host(contact_host); + usermanager.initialize_host(contact_host); + end + -- Update contact's roster to say subscription request is pending... + rostermanager.set_contact_pending_in(contact_username, contact_host, user_jid); + -- Update contact's roster to say subscription request approved... + rostermanager.subscribed(contact_username, contact_host, user_jid); + -- Update user's roster to say subscription request approved. Bare hosts (e.g. components) don't have rosters. + if user_username ~= nil then + rostermanager.process_inbound_subscription_approval(user_username, user_host, contact_jid); + end + end +end + +-- Make a mutual subscription between jid1 and jid2. Each JID will see +-- when the other one is online. +function subscribe_both(jid1, jid2) + subscribe(jid1, jid2); + subscribe(jid2, jid1); +end + +-- Unsubscribes user from contact (not contact from user, if subscribed). +function unsubscribe(user_jid, contact_jid) + local user_username, user_host = jid.split(user_jid); + local contact_username, contact_host = jid.split(contact_jid); + if not hosts[user_host] then + warn("The host '%s' is not configured for this server.", user_host); + return; + end + if hosts[user_host].users.name == "null" then + storagemanager.initialize_host(user_host); + usermanager.initialize_host(user_host); + end + -- Update user's roster to say subscription is cancelled... + rostermanager.unsubscribe(user_username, user_host, contact_jid); + if hosts[contact_host] then + if contact_host ~= user_host and hosts[contact_host].users.name == "null" then + storagemanager.initialize_host(contact_host); + usermanager.initialize_host(contact_host); + end + -- Update contact's roster to say subscription is cancelled... + rostermanager.unsubscribed(contact_username, contact_host, user_jid); + end +end + +-- Cancel any subscription in either direction. +function unsubscribe_both(jid1, jid2) + unsubscribe(jid1, jid2); + unsubscribe(jid2, jid1); +end + +-- Set the name shown and group used in the contact list +function rename(user_jid, contact_jid, contact_nick, contact_group) + local user_username, user_host = jid.split(user_jid); + if not hosts[user_host] then + warn("The host '%s' is not configured for this server.", user_host); + return; + end + if hosts[user_host].users.name == "null" then + storagemanager.initialize_host(user_host); + usermanager.initialize_host(user_host); + end + + -- Load user's roster and find the contact + local roster = rostermanager.load_roster(user_username, user_host); + local item = roster[contact_jid]; + if item then + if contact_nick then + item.name = contact_nick; + end + if contact_group then + item.groups = {}; -- Remove from all current groups + item.groups[contact_group] = true; + end + rostermanager.save_roster(user_username, user_host, roster); + end +end + +function remove(user_jid, contact_jid) + unsubscribe_both(user_jid, contact_jid); + local user_username, user_host = jid.split(user_jid); + local roster = rostermanager.load_roster(user_username, user_host); + roster[contact_jid] = nil; + rostermanager.save_roster(user_username, user_host, roster); +end + +function module.command(arg) + local command = arg[1]; + if not command then + warn("Valid subcommands: (un)subscribe(_both) | rename"); + return 0; + end + table.remove(arg, 1); + if command == "subscribe" then + subscribe(arg[1], arg[2]); + return 0; + elseif command == "subscribe_both" then + subscribe_both(arg[1], arg[2]); + return 0; + elseif command == "unsubscribe" then + unsubscribe(arg[1], arg[2]); + return 0; + elseif command == "unsubscribe_both" then + unsubscribe_both(arg[1], arg[2]); + return 0; + elseif command == "remove" then + remove(arg[1], arg[2]); + return 0; + elseif command == "rename" then + rename(arg[1], arg[2], arg[3], arg[4]); + return 0; + else + warn("Unknown command: %s", command); + return 1; + end + return 0; +end diff --git a/resources/prosody-plugins/mod_roster_command.patch b/resources/prosody-plugins/mod_roster_command.patch new file mode 100644 index 0000000000..19edb46b9f --- /dev/null +++ b/resources/prosody-plugins/mod_roster_command.patch @@ -0,0 +1,47 @@ +# HG changeset patch +# User Boris Grozev +# Date 1609874100 21600 +# Tue Jan 05 13:15:00 2021 -0600 +# Node ID f646babfc401494ff33f2126ef6c4df541ebf846 +# Parent 456b9f608fcf9667cfba1bd7bf9eba2151af50d0 +mod_roster_command: Fix subscription when the "user JID" is a bare domain. + +Do not attempt to update the roster when the user is bare domain (e.g. a +component), since they don't have rosters and the attempt results in an error: + +$ prosodyctl mod_roster_command subscribe proxy.example.com contact@example.com +xxxxxxxxxxFailed to execute command: Error: /usr/lib/prosody/core/rostermanager.lua:104: attempt to concatenate local 'username' (a nil value) +stack traceback: + /usr/lib/prosody/core/rostermanager.lua:104: in function 'load_roster' + /usr/lib/prosody/core/rostermanager.lua:305: in function 'set_contact_pending_out' + mod_roster_command.lua:44: in function 'subscribe' + +diff -r 456b9f608fcf -r f646babfc401 mod_roster_command/mod_roster_command.lua +--- a/mod_roster_command/mod_roster_command.lua Tue Jan 05 13:49:50 2021 +0000 ++++ b/mod_roster_command/mod_roster_command.lua Tue Jan 05 13:15:00 2021 -0600 +@@ -40,8 +40,10 @@ + storagemanager.initialize_host(user_host); + usermanager.initialize_host(user_host); + end +- -- Update user's roster to say subscription request is pending... +- rostermanager.set_contact_pending_out(user_username, user_host, contact_jid); ++ -- Update user's roster to say subscription request is pending. Bare hosts (e.g. components) don't have rosters. ++ if user_username ~= nil then ++ rostermanager.set_contact_pending_out(user_username, user_host, contact_jid); ++ end + if hosts[contact_host] then + if contact_host ~= user_host and hosts[contact_host].users.name == "null" then + storagemanager.initialize_host(contact_host); +@@ -51,8 +53,10 @@ + rostermanager.set_contact_pending_in(contact_username, contact_host, user_jid); + -- Update contact's roster to say subscription request approved... + rostermanager.subscribed(contact_username, contact_host, user_jid); +- -- Update user's roster to say subscription request approved... +- rostermanager.process_inbound_subscription_approval(user_username, user_host, contact_jid); ++ -- Update user's roster to say subscription request approved. Bare hosts (e.g. components) don't have rosters. ++ if user_username ~= nil then ++ rostermanager.process_inbound_subscription_approval(user_username, user_host, contact_jid); ++ end + end + end +