Comparison

plugins/muc/muc.lib.lua @ 11245:43b43e7156b8

MUC: Add support for presence probes (fixes #1535) 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).
author JC Brand <jc@opkode.com>
date Sun, 19 Apr 2020 21:49:45 +0200
parent 11236:b1d7027be61e
child 11246:ab189e707705
comparison
equal deleted inserted replaced
11244:8a1f88241cb1 11245:43b43e7156b8
214 elseif whois == "moderators" then 214 elseif whois == "moderators" then
215 return valid_roles[occupant.role or "none"] >= valid_roles.moderator; 215 return valid_roles[occupant.role or "none"] >= valid_roles.moderator;
216 end 216 end
217 end 217 end
218 218
219
219 -- Broadcasts an occupant's presence to the whole room 220 -- Broadcasts an occupant's presence to the whole room
220 -- Takes the x element that goes into the stanzas 221 -- Takes the x element that goes into the stanzas
221 function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, prev_role, force_unavailable) 222 function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, prev_role, force_unavailable, recipient)
222 local base_x = x.base or x; 223 local base_x = x.base or x;
223 -- Build real jid and (optionally) occupant jid template presences 224 -- Build real jid and (optionally) occupant jid template presences
224 local base_presence do 225 local base_presence do
225 -- Try to use main jid's presence 226 -- Try to use main jid's presence
226 local pr = occupant:get_presence(); 227 local pr = occupant:get_presence();
236 room = self; stanza = base_presence; x = base_x; 237 room = self; stanza = base_presence; x = base_x;
237 occupant = occupant; nick = nick; actor = actor; 238 occupant = occupant; nick = nick; actor = actor;
238 reason = reason; 239 reason = reason;
239 } 240 }
240 module:fire_event("muc-build-occupant-presence", event); 241 module:fire_event("muc-build-occupant-presence", event);
241 module:fire_event("muc-broadcast-presence", event); 242 if not recipient then
243 module:fire_event("muc-broadcast-presence", event);
244 end
242 245
243 -- Allow muc-broadcast-presence listeners to change things 246 -- Allow muc-broadcast-presence listeners to change things
244 nick = event.nick; 247 nick = event.nick;
245 actor = event.actor; 248 actor = event.actor;
246 reason = event.reason; 249 reason = event.reason;
279 self_x = st.clone(x.self or base_x); 282 self_x = st.clone(x.self or base_x);
280 self:build_item_list(occupant, self_x, false, nick, actor_nick, nil, reason); 283 self:build_item_list(occupant, self_x, false, nick, actor_nick, nil, reason);
281 self_p = st.clone(base_presence):add_child(self_x); 284 self_p = st.clone(base_presence):add_child(self_x);
282 end 285 end
283 286
287 local function get_p(rec_occupant)
288 local pr;
289 if can_see_real_jids(whois, rec_occupant) then
290 pr = get_full_p();
291 elseif occupant.bare_jid == rec_occupant.bare_jid then
292 pr = self_p;
293 else
294 pr = get_anon_p();
295 end
296 return pr
297 end
298
299 if recipient then
300 return self:route_to_occupant(recipient, get_p(recipient));
301 end
302
284 local broadcast_roles = self:get_presence_broadcast(); 303 local broadcast_roles = self:get_presence_broadcast();
285
286 -- General populace 304 -- General populace
287 for occupant_nick, n_occupant in self:each_occupant() do 305 for occupant_nick, n_occupant in self:each_occupant() do
288 if occupant_nick ~= occupant.nick then 306 if occupant_nick ~= occupant.nick then
289 local pr; 307 local pr = get_p(n_occupant);
290 if can_see_real_jids(whois, n_occupant) then
291 pr = get_full_p();
292 elseif occupant.bare_jid == n_occupant.bare_jid then
293 pr = self_p;
294 else
295 pr = get_anon_p();
296 end
297 if broadcast_roles[occupant.role or "none"] or force_unavailable then 308 if broadcast_roles[occupant.role or "none"] or force_unavailable then
298 self:route_to_occupant(n_occupant, pr); 309 self:route_to_occupant(n_occupant, pr);
299 elseif prev_role and broadcast_roles[prev_role] then 310 elseif prev_role and broadcast_roles[prev_role] then
300 pr.attr.type = 'unavailable'; 311 pr.attr.type = 'unavailable';
301 self:route_to_occupant(n_occupant, pr); 312 self:route_to_occupant(n_occupant, pr);
321 end 332 end
322 end 333 end
323 334
324 function room_mt:send_occupant_list(to, filter) 335 function room_mt:send_occupant_list(to, filter)
325 local to_bare = jid_bare(to); 336 local to_bare = jid_bare(to);
326 local is_anonymous = false;
327 local whois = self:get_whois();
328 local broadcast_roles = self:get_presence_broadcast(); 337 local broadcast_roles = self:get_presence_broadcast();
329 if whois ~= "anyone" then 338 local is_anonymous = self:is_anonymous_for(to);
330 local affiliation = self:get_affiliation(to);
331 if affiliation ~= "admin" and affiliation ~= "owner" then
332 local occupant = self:get_occupant_by_real_jid(to);
333 if not (occupant and can_see_real_jids(whois, occupant)) then
334 is_anonymous = true;
335 end
336 end
337 end
338 local broadcast_bare_jids = {}; -- Track which bare JIDs we have sent presence for 339 local broadcast_bare_jids = {}; -- Track which bare JIDs we have sent presence for
339 for occupant_jid, occupant in self:each_occupant() do 340 for occupant_jid, occupant in self:each_occupant() do
340 broadcast_bare_jids[occupant.bare_jid] = true; 341 broadcast_bare_jids[occupant.bare_jid] = true;
341 if filter == nil or filter(occupant_jid, occupant) then 342 if filter == nil or filter(occupant_jid, occupant) then
342 local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); 343 local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'});
547 origin = origin; 548 origin = origin;
548 }); 549 });
549 return true; 550 return true;
550 end 551 end
551 552
553
554 function room_mt:is_anonymous_for(jid)
555 local is_anonymous = false;
556 local whois = self:get_whois();
557 if whois ~= "anyone" then
558 local affiliation = self:get_affiliation(jid);
559 if affiliation ~= "admin" and affiliation ~= "owner" then
560 local occupant = self:get_occupant_by_real_jid(jid);
561 if not (occupant and can_see_real_jids(whois, occupant)) then
562 is_anonymous = true;
563 end
564 end
565 end
566 return is_anonymous;
567 end
568
569
570 function room_mt:build_unavailable_presence(from_muc_jid, to_jid)
571 local nick = jid_resource(from_muc_jid);
572 local from_jid = self:get_registered_jid(nick);
573 if (not from_jid) then
574 module:log("debug", "Received presence probe for unavailable nickname that's not registered");
575 return;
576 end
577 local is_anonymous = self:is_anonymous_for(to_jid);
578 local affiliation = self:get_affiliation(from_jid) or "none";
579 local pr = st.presence({ to = to_jid, from = from_muc_jid, type = "unavailable" })
580 :tag("x", { xmlns = 'http://jabber.org/protocol/muc#user' })
581 :tag("item", {
582 affiliation = affiliation;
583 role = "none";
584 nick = nick;
585 jid = not is_anonymous and from_jid or nil }):up()
586 :up();
587
588 local x = pr:get_child("x", "http://jabber.org/protocol/muc");
589 local event = {
590 room = self; stanza = pr; x = x;
591 bare_jid = from_jid;
592 nick = nick;
593 }
594 module:fire_event("muc-build-occupant-presence", event);
595 return event.stanza;
596 end
597
598
552 function room_mt:handle_normal_presence(origin, stanza) 599 function room_mt:handle_normal_presence(origin, stanza)
553 local type = stanza.attr.type; 600 local type = stanza.attr.type;
554 local real_jid = stanza.attr.from; 601 local real_jid = stanza.attr.from;
555 local bare_jid = jid_bare(real_jid); 602 local bare_jid = jid_bare(real_jid);
556 local orig_occupant = self:get_occupant_by_real_jid(real_jid); 603 local orig_occupant = self:get_occupant_by_real_jid(real_jid);
566 local is_first_dest_session; 613 local is_first_dest_session;
567 local dest_occupant; 614 local dest_occupant;
568 if type == "unavailable" then 615 if type == "unavailable" then
569 if orig_occupant == nil then return true; end -- Unavailable from someone not in the room 616 if orig_occupant == nil then return true; end -- Unavailable from someone not in the room
570 -- dest_occupant = nil 617 -- dest_occupant = nil
618 elseif type == "probe" then
619 local occupant = self:get_occupant_by_nick(stanza.attr.to);
620 if occupant == nil then
621 local from_muc_jid = stanza.attr.to;
622 local to_jid = real_jid;
623 local pr = self:build_unavailable_presence(from_muc_jid, to_jid);
624 if pr then
625 self:route_stanza(pr);
626 end
627 return true;
628 end
629 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"});
630 self:publicise_occupant_status(occupant, x, nil, nil, nil, nil, false, orig_occupant);
631 return true;
571 elseif orig_occupant and orig_occupant.nick == stanza.attr.to then -- Just a presence update 632 elseif orig_occupant and orig_occupant.nick == stanza.attr.to then -- Just a presence update
572 log("debug", "presence update for %s from session %s", orig_occupant.nick, real_jid); 633 log("debug", "presence update for %s from session %s", orig_occupant.nick, real_jid);
573 dest_occupant = orig_occupant; 634 dest_occupant = orig_occupant;
574 else 635 else
575 local dest_jid = stanza.attr.to; 636 local dest_jid = stanza.attr.to;
745 806
746 function room_mt:handle_presence_to_occupant(origin, stanza) 807 function room_mt:handle_presence_to_occupant(origin, stanza)
747 local type = stanza.attr.type; 808 local type = stanza.attr.type;
748 if type == "error" then -- error, kick em out! 809 if type == "error" then -- error, kick em out!
749 return self:handle_kickable(origin, stanza) 810 return self:handle_kickable(origin, stanza)
750 elseif type == nil or type == "unavailable" then 811 elseif type == nil or type == "unavailable" or type == "probe" then
751 return self:handle_normal_presence(origin, stanza); 812 return self:handle_normal_presence(origin, stanza);
752 elseif type ~= 'result' then -- bad type 813 elseif type ~= 'result' then -- bad type
753 if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences 814 if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences
754 origin.send(st.error_reply(stanza, "modify", "bad-request", nil, self.jid)); -- FIXME correct error? 815 origin.send(st.error_reply(stanza, "modify", "bad-request", nil, self.jid)); -- FIXME correct error?
755 end 816 end