Skip to content

Instantly share code, notes, and snippets.

@jcbrand
Created April 21, 2020 10:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcbrand/e51be998d999b0a97b79eebf20dff8a5 to your computer and use it in GitHub Desktop.
Save jcbrand/e51be998d999b0a97b79eebf20dff8a5 to your computer and use it in GitHub Desktop.
Patch Prosody to respond to MUC presence probes
# HG changeset patch
# User JC Brand <jc@opkode.com>
# Date 1587325785 -7200
# Sun Apr 19 21:49:45 2020 +0200
# Node ID 2d0c2fd01ef2954c3f06b466ac966752d281ad61
# Parent 977c9883f625b6765b7c31addf656fcefbd9b6d7
Add support for MUC presence probes
The following patch allows Prosody to respond to `probe` presences and send out the probed occupant's current presence.
This is based on line 17.3 in XEP-0045:
A MUC service MAY handle presence probes sent to the room JID <room@service> or an occupant JID <room@service/nick>
(e.g, these might be sent by an occupant's home server to determine if the room is still online or to synchronize
presence information if the user or the user's server has gone offline temporarily or has started sharing presence again,
as for instance when Stanza Interception and Filtering Technology (XEP-0273) is used).
diff -r 977c9883f625 -r 2d0c2fd01ef2 plugins/muc/hats.lib.lua
--- a/plugins/muc/hats.lib.lua Sun Apr 12 22:57:14 2020 +0200
+++ b/plugins/muc/hats.lib.lua Sun Apr 19 21:49:45 2020 +0200
@@ -6,8 +6,10 @@
-- Strip any hats claimed by the client (to prevent spoofing)
muc_util.add_filtered_namespace(xmlns_hats);
+
module:hook("muc-build-occupant-presence", function (event)
- local aff_data = event.room:get_affiliation_data(event.occupant.bare_jid);
+ local bare_jid = event.occupant and event.occupant.bare_jid or event.bare_jid;
+ local aff_data = event.room:get_affiliation_data(bare_jid);
local hats = aff_data and aff_data.hats;
if not hats then return; end
local hats_el;
diff -r 977c9883f625 -r 2d0c2fd01ef2 plugins/muc/muc.lib.lua
--- a/plugins/muc/muc.lib.lua Sun Apr 12 22:57:14 2020 +0200
+++ b/plugins/muc/muc.lib.lua Sun Apr 19 21:49:45 2020 +0200
@@ -216,9 +216,10 @@
end
end
+
-- Broadcasts an occupant's presence to the whole room
-- Takes the x element that goes into the stanzas
-function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, prev_role, force_unavailable)
+function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, prev_role, force_unavailable, recipient)
local base_x = x.base or x;
-- Build real jid and (optionally) occupant jid template presences
local base_presence do
@@ -238,7 +239,9 @@
reason = reason;
}
module:fire_event("muc-build-occupant-presence", event);
- module:fire_event("muc-broadcast-presence", event);
+ if not recipient then
+ module:fire_event("muc-broadcast-presence", event);
+ end
-- Allow muc-broadcast-presence listeners to change things
nick = event.nick;
@@ -281,19 +284,27 @@
self_p = st.clone(base_presence):add_child(self_x);
end
+ local function get_p(rec_occupant)
+ local pr;
+ if can_see_real_jids(whois, rec_occupant) then
+ pr = get_full_p();
+ elseif occupant.bare_jid == rec_occupant.bare_jid then
+ pr = self_p;
+ else
+ pr = get_anon_p();
+ end
+ return pr
+ end
+
+ if recipient then
+ return self:route_to_occupant(recipient, get_p(recipient));
+ end
+
local broadcast_roles = self:get_presence_broadcast();
-
-- General populace
for occupant_nick, n_occupant in self:each_occupant() do
if occupant_nick ~= occupant.nick then
- local pr;
- if can_see_real_jids(whois, n_occupant) then
- pr = get_full_p();
- elseif occupant.bare_jid == n_occupant.bare_jid then
- pr = self_p;
- else
- pr = get_anon_p();
- end
+ local pr = get_p(n_occupant);
if broadcast_roles[occupant.role or "none"] or force_unavailable then
self:route_to_occupant(n_occupant, pr);
elseif prev_role and broadcast_roles[prev_role] then
@@ -322,18 +333,8 @@
function room_mt:send_occupant_list(to, filter)
local to_bare = jid_bare(to);
- local is_anonymous = false;
- local whois = self:get_whois();
local broadcast_roles = self:get_presence_broadcast();
- if whois ~= "anyone" then
- local affiliation = self:get_affiliation(to);
- if affiliation ~= "admin" and affiliation ~= "owner" then
- local occupant = self:get_occupant_by_real_jid(to);
- if not (occupant and can_see_real_jids(whois, occupant)) then
- is_anonymous = true;
- end
- end
- end
+ local is_anonymous = self:is_anonymous_for(to);
local broadcast_bare_jids = {}; -- Track which bare JIDs we have sent presence for
for occupant_jid, occupant in self:each_occupant() do
broadcast_bare_jids[occupant.bare_jid] = true;
@@ -548,6 +549,52 @@
return true;
end
+
+function room_mt:is_anonymous_for(jid)
+ local is_anonymous = false;
+ local whois = self:get_whois();
+ if whois ~= "anyone" then
+ local affiliation = self:get_affiliation(jid);
+ if affiliation ~= "admin" and affiliation ~= "owner" then
+ local occupant = self:get_occupant_by_real_jid(jid);
+ if not (occupant and can_see_real_jids(whois, occupant)) then
+ is_anonymous = true;
+ end
+ end
+ end
+ return is_anonymous;
+end
+
+
+function room_mt:build_unavailable_presence(from_muc_jid, to_jid)
+ local nick = jid_resource(from_muc_jid);
+ local from_jid = self:get_registered_jid(nick);
+ if (not from_jid) then
+ module:log("debug", "Received presence probe for unavailable nickname that's not registered");
+ return;
+ end
+ local is_anonymous = self:is_anonymous_for(to_jid);
+ local affiliation = self:get_affiliation(from_jid) or "none";
+ local pr = st.presence({ to = to_jid, from = from_muc_jid, type = "unavailable" })
+ :tag("x", { xmlns = 'http://jabber.org/protocol/muc#user' })
+ :tag("item", {
+ affiliation = affiliation;
+ role = "none";
+ nick = nick;
+ jid = not is_anonymous and from_jid or nil }):up()
+ :up();
+
+ local x = pr:get_child("x", "http://jabber.org/protocol/muc");
+ local event = {
+ room = self; stanza = pr; x = x;
+ bare_jid = from_jid;
+ nick = nick;
+ }
+ module:fire_event("muc-build-occupant-presence", event);
+ return event.stanza;
+end
+
+
function room_mt:handle_normal_presence(origin, stanza)
local type = stanza.attr.type;
local real_jid = stanza.attr.from;
@@ -567,6 +614,20 @@
if type == "unavailable" then
if orig_occupant == nil then return true; end -- Unavailable from someone not in the room
-- dest_occupant = nil
+ elseif type == "probe" then
+ local occupant = self:get_occupant_by_nick(stanza.attr.to);
+ if occupant == nil then
+ local from_muc_jid = stanza.attr.to;
+ local to_jid = real_jid;
+ local pr = self:build_unavailable_presence(from_muc_jid, to_jid);
+ if pr then
+ self:route_stanza(pr);
+ end
+ return true;
+ end
+ local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"});
+ self:publicise_occupant_status(occupant, x, nil, nil, nil, nil, false, orig_occupant);
+ return true;
elseif orig_occupant and orig_occupant.nick == stanza.attr.to then -- Just a presence update
log("debug", "presence update for %s from session %s", orig_occupant.nick, real_jid);
dest_occupant = orig_occupant;
@@ -746,7 +807,7 @@
local type = stanza.attr.type;
if type == "error" then -- error, kick em out!
return self:handle_kickable(origin, stanza)
- elseif type == nil or type == "unavailable" then
+ elseif type == nil or type == "unavailable" or type == "probe" then
return self:handle_normal_presence(origin, stanza);
elseif type ~= 'result' then -- bad type
if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences
diff -r 977c9883f625 -r 2d0c2fd01ef2 spec/scansion/muc_presence_probe.scs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/scansion/muc_presence_probe.scs Sun Apr 19 21:49:45 2020 +0200
@@ -0,0 +1,115 @@
+# #1535 Let MUCs respond to presence probes
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+[Client] Mercutio
+ jid: user3@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Disable presences for non-mods
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_presencebroadcast'>
+ <value>moderator</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Romeo probes Juliet
+
+Romeo sends:
+ <presence to="room@conference.localhost/Juliet" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='none' role='participant'/>
+ </x>
+ </presence>
+
+# Romeo makes Mercutio a member and registers his nickname
+
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Mercutio's JID}" nick="Mercutio"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Mercutio's JID}" affiliation='member' />
+ </x>
+ </message>
+
+Romeo receives:
+ <iq from='room@conference.localhost' id='member1' type='result'/>
+
+
+# Romeo probes Mercutio, even though he's unavailable
+
+Romeo sends:
+ <presence to="room@conference.localhost/Mercutio" type="probe">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Mercutio' type="unavailable">
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item nick="Mercutio" affiliation='member' role='none' jid="${Mercutio's JID}" />
+ </x>
+ </presence>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment