Comparison

plugins/muc/muc.lib.lua @ 10563:e8db377a2983

Merge 0.11->trunk
author Kim Alvefur <zash@zash.se>
date Tue, 24 Dec 2019 00:39:45 +0100
parent 10553:4d8119ffd433
child 10662:46373b97e648
comparison
equal deleted inserted replaced
10562:670afc079f68 10563:e8db377a2983
21 local jid_resource = require "util.jid".resource; 21 local jid_resource = require "util.jid".resource;
22 local resourceprep = require "util.encodings".stringprep.resourceprep; 22 local resourceprep = require "util.encodings".stringprep.resourceprep;
23 local st = require "util.stanza"; 23 local st = require "util.stanza";
24 local base64 = require "util.encodings".base64; 24 local base64 = require "util.encodings".base64;
25 local md5 = require "util.hashes".md5; 25 local md5 = require "util.hashes".md5;
26 local new_id = require "util.id".medium;
26 27
27 local log = module._log; 28 local log = module._log;
28 29
29 local occupant_lib = module:require "muc/occupant" 30 local occupant_lib = module:require "muc/occupant"
30 local muc_util = module:require "muc/util"; 31 local muc_util = module:require "muc/util";
37 function room_mt:__tostring() 38 function room_mt:__tostring()
38 return "MUC room ("..self.jid..")"; 39 return "MUC room ("..self.jid..")";
39 end 40 end
40 41
41 function room_mt.save() 42 function room_mt.save()
42 -- overriden by mod_muc.lua 43 -- overridden by mod_muc.lua
43 end 44 end
44 45
45 function room_mt:get_occupant_jid(real_jid) 46 function room_mt:get_occupant_jid(real_jid)
46 return self._jid_nick[real_jid] 47 return self._jid_nick[real_jid]
47 end 48 end
215 end 216 end
216 end 217 end
217 218
218 -- Broadcasts an occupant's presence to the whole room 219 -- Broadcasts an occupant's presence to the whole room
219 -- Takes the x element that goes into the stanzas 220 -- Takes the x element that goes into the stanzas
220 function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason) 221 function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason, prev_role, force_unavailable)
221 local base_x = x.base or x; 222 local base_x = x.base or x;
222 -- Build real jid and (optionally) occupant jid template presences 223 -- Build real jid and (optionally) occupant jid template presences
223 local base_presence do 224 local base_presence do
224 -- Try to use main jid's presence 225 -- Try to use main jid's presence
225 local pr = occupant:get_presence(); 226 local pr = occupant:get_presence();
226 if pr and (occupant.role ~= nil or pr.attr.type == "unavailable") then 227 if pr and (occupant.role ~= nil or pr.attr.type == "unavailable") and not force_unavailable then
227 base_presence = st.clone(pr); 228 base_presence = st.clone(pr);
228 else -- user is leaving but didn't send a leave presence. make one for them 229 else -- user is leaving but didn't send a leave presence. make one for them
229 base_presence = st.presence {from = occupant.nick; type = "unavailable";}; 230 base_presence = st.presence {from = occupant.nick; type = "unavailable";};
230 end 231 end
231 end 232 end
277 self_x = st.clone(x.self or base_x); 278 self_x = st.clone(x.self or base_x);
278 self:build_item_list(occupant, self_x, false, nick, actor_nick, nil, reason); 279 self:build_item_list(occupant, self_x, false, nick, actor_nick, nil, reason);
279 self_p = st.clone(base_presence):add_child(self_x); 280 self_p = st.clone(base_presence):add_child(self_x);
280 end 281 end
281 282
282 -- General populance 283 local broadcast_roles = self:get_presence_broadcast();
284
285 -- General populace
283 for occupant_nick, n_occupant in self:each_occupant() do 286 for occupant_nick, n_occupant in self:each_occupant() do
284 if occupant_nick ~= occupant.nick then 287 if occupant_nick ~= occupant.nick then
285 local pr; 288 local pr;
286 if can_see_real_jids(whois, n_occupant) then 289 if can_see_real_jids(whois, n_occupant) then
287 pr = get_full_p(); 290 pr = get_full_p();
288 elseif occupant.bare_jid == n_occupant.bare_jid then 291 elseif occupant.bare_jid == n_occupant.bare_jid then
289 pr = self_p; 292 pr = self_p;
290 else 293 else
291 pr = get_anon_p(); 294 pr = get_anon_p();
292 end 295 end
293 self:route_to_occupant(n_occupant, pr); 296 if broadcast_roles[occupant.role or "none"] or force_unavailable then
297 self:route_to_occupant(n_occupant, pr);
298 elseif prev_role and broadcast_roles[prev_role] then
299 pr.attr.type = 'unavailable';
300 self:route_to_occupant(n_occupant, pr);
301 end
302
294 end 303 end
295 end 304 end
296 305
297 -- Presences for occupant itself 306 -- Presences for occupant itself
298 self_x:tag("status", {code = "110";}):up(); 307 self_x:tag("status", {code = "110";}):up();
312 321
313 function room_mt:send_occupant_list(to, filter) 322 function room_mt:send_occupant_list(to, filter)
314 local to_bare = jid_bare(to); 323 local to_bare = jid_bare(to);
315 local is_anonymous = false; 324 local is_anonymous = false;
316 local whois = self:get_whois(); 325 local whois = self:get_whois();
326 local broadcast_roles = self:get_presence_broadcast();
317 if whois ~= "anyone" then 327 if whois ~= "anyone" then
318 local affiliation = self:get_affiliation(to); 328 local affiliation = self:get_affiliation(to);
319 if affiliation ~= "admin" and affiliation ~= "owner" then 329 if affiliation ~= "admin" and affiliation ~= "owner" then
320 local occupant = self:get_occupant_by_real_jid(to); 330 local occupant = self:get_occupant_by_real_jid(to);
321 if not (occupant and can_see_real_jids(whois, occupant)) then 331 if not (occupant and can_see_real_jids(whois, occupant)) then
328 local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); 338 local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'});
329 self:build_item_list(occupant, x, is_anonymous and to_bare ~= occupant.bare_jid); -- can always see your own jids 339 self:build_item_list(occupant, x, is_anonymous and to_bare ~= occupant.bare_jid); -- can always see your own jids
330 local pres = st.clone(occupant:get_presence()); 340 local pres = st.clone(occupant:get_presence());
331 pres.attr.to = to; 341 pres.attr.to = to;
332 pres:add_child(x); 342 pres:add_child(x);
333 self:route_stanza(pres); 343 if to_bare == occupant.bare_jid or broadcast_roles[occupant.role or "none"] then
344 self:route_stanza(pres);
345 end
334 end 346 end
335 end 347 end
336 end 348 end
337 349
338 function room_mt:get_disco_info(stanza) 350 function room_mt:get_disco_info(stanza)
371 383
372 function room_mt:handle_kickable(origin, stanza) -- luacheck: ignore 212 384 function room_mt:handle_kickable(origin, stanza) -- luacheck: ignore 212
373 local real_jid = stanza.attr.from; 385 local real_jid = stanza.attr.from;
374 local occupant = self:get_occupant_by_real_jid(real_jid); 386 local occupant = self:get_occupant_by_real_jid(real_jid);
375 if occupant == nil then return nil; end 387 if occupant == nil then return nil; end
376 local type, condition, text = stanza:get_error(); 388 local _, condition, text = stanza:get_error();
377 local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error"); 389 local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error");
378 if text and self:get_whois() == "anyone" then 390 if text and self:get_whois() == "anyone" then
379 error_message = error_message..": "..text; 391 error_message = error_message..": "..text;
380 end 392 end
381 occupant:set_session(real_jid, st.presence({type="unavailable"}) 393 occupant:set_session(real_jid, st.presence({type="unavailable"})
389 if is_last_session then 401 if is_last_session then
390 x:tag("status", {code = "333"}); 402 x:tag("status", {code = "333"});
391 end 403 end
392 self:publicise_occupant_status(new_occupant or occupant, x); 404 self:publicise_occupant_status(new_occupant or occupant, x);
393 if is_last_session then 405 if is_last_session then
394 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); 406 module:fire_event("muc-occupant-left", {
407 room = self;
408 nick = occupant.nick;
409 occupant = occupant;
410 });
395 end 411 end
396 return true; 412 return true;
397 end 413 end
398 414
399 -- Give the room creator owner affiliation 415 -- Give the room creator owner affiliation
404 -- check if user is banned 420 -- check if user is banned
405 module:hook("muc-occupant-pre-join", function(event) 421 module:hook("muc-occupant-pre-join", function(event)
406 local room, stanza = event.room, event.stanza; 422 local room, stanza = event.room, event.stanza;
407 local affiliation = room:get_affiliation(stanza.attr.from); 423 local affiliation = room:get_affiliation(stanza.attr.from);
408 if affiliation == "outcast" then 424 if affiliation == "outcast" then
409 local reply = st.error_reply(stanza, "auth", "forbidden"):up(); 425 local reply = st.error_reply(stanza, "auth", "forbidden", nil, room.jid):up();
410 reply.tags[1].attr.code = "403"; 426 reply.tags[1].attr.code = "403";
411 event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); 427 event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
412 return true; 428 return true;
413 end 429 end
414 end, -10); 430 end, -10);
415 431
416 module:hook("muc-occupant-pre-join", function(event) 432 module:hook("muc-occupant-pre-join", function(event)
433 local room = event.room;
417 local nick = jid_resource(event.occupant.nick); 434 local nick = jid_resource(event.occupant.nick);
418 if not nick:find("%S") then 435 if not nick:find("%S") then
419 event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden")); 436 event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden", room.jid));
420 return true; 437 return true;
421 end 438 end
422 end, 1); 439 end, 1);
423 440
424 module:hook("muc-occupant-pre-change", function(event) 441 module:hook("muc-occupant-pre-change", function(event)
442 local room = event.room;
425 if not jid_resource(event.dest_occupant.nick):find("%S") then 443 if not jid_resource(event.dest_occupant.nick):find("%S") then
426 event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden")); 444 event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden", room.jid));
427 return true; 445 return true;
428 end 446 end
429 end, 1); 447 end, 1);
430 448
449 module:hook("muc-occupant-pre-join", function(event)
450 local room = event.room;
451 local nick = jid_resource(event.occupant.nick);
452 if not resourceprep(nick, true) then -- strict
453 event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation", room.jid));
454 return true;
455 end
456 end, 2);
457
458 module:hook("muc-occupant-pre-change", function(event)
459 local room = event.room;
460 local nick = jid_resource(event.dest_occupant.nick);
461 if not resourceprep(nick, true) then -- strict
462 event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation", room.jid));
463 return true;
464 end
465 end, 2);
466
431 function room_mt:handle_first_presence(origin, stanza) 467 function room_mt:handle_first_presence(origin, stanza)
432 if not stanza:get_child("x", "http://jabber.org/protocol/muc") then
433 module:log("debug", "Room creation without <x>, possibly desynced");
434
435 origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
436 return true;
437 end
438
439 local real_jid = stanza.attr.from; 468 local real_jid = stanza.attr.from;
440 local dest_jid = stanza.attr.to; 469 local dest_jid = stanza.attr.to;
441 local bare_jid = jid_bare(real_jid); 470 local bare_jid = jid_bare(real_jid);
442 if module:fire_event("muc-room-pre-create", { 471 if module:fire_event("muc-room-pre-create", {
443 room = self; 472 room = self;
503 local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc"); 532 local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc");
504 533
505 if orig_occupant == nil and not muc_x and stanza.attr.type == nil then 534 if orig_occupant == nil and not muc_x and stanza.attr.type == nil then
506 module:log("debug", "Attempted join without <x>, possibly desynced"); 535 module:log("debug", "Attempted join without <x>, possibly desynced");
507 origin.send(st.error_reply(stanza, "cancel", "item-not-found", 536 origin.send(st.error_reply(stanza, "cancel", "item-not-found",
508 "You must join the room before sending presence updates")); 537 "You are not currently connected to this chat", self.jid));
509 return true; 538 return true;
510 end 539 end
511 540
512 local is_first_dest_session; 541 local is_first_dest_session;
513 local dest_occupant; 542 local dest_occupant;
565 -- Check for nick conflicts 594 -- Check for nick conflicts
566 if dest_occupant ~= nil and not is_first_dest_session 595 if dest_occupant ~= nil and not is_first_dest_session
567 and bare_jid ~= jid_bare(dest_occupant.bare_jid) then 596 and bare_jid ~= jid_bare(dest_occupant.bare_jid) then
568 -- new nick or has different bare real jid 597 -- new nick or has different bare real jid
569 log("debug", "%s couldn't join due to nick conflict: %s", real_jid, dest_occupant.nick); 598 log("debug", "%s couldn't join due to nick conflict: %s", real_jid, dest_occupant.nick);
570 local reply = st.error_reply(stanza, "cancel", "conflict"):up(); 599 local reply = st.error_reply(stanza, "cancel", "conflict", nil, self.jid):up();
571 reply.tags[1].attr.code = "409"; 600 reply.tags[1].attr.code = "409";
572 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); 601 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
573 return true; 602 return true;
574 end 603 end
575 604
611 -- self:build_item_list(orig_occupant, x, false, dest_nick); 640 -- self:build_item_list(orig_occupant, x, false, dest_nick);
612 add_item(x, self:get_affiliation(bare_jid), orig_occupant.role, real_jid, dest_nick); 641 add_item(x, self:get_affiliation(bare_jid), orig_occupant.role, real_jid, dest_nick);
613 x:tag("status", {code = "303";}):up(); 642 x:tag("status", {code = "303";}):up();
614 x:tag("status", {code = "110";}):up(); 643 x:tag("status", {code = "110";}):up();
615 self:route_stanza(generated_unavail:add_child(x)); 644 self:route_stanza(generated_unavail:add_child(x));
616 dest_nick = nil; -- set dest_nick to nil; so general populance doesn't see it for whole orig_occupant 645 dest_nick = nil; -- set dest_nick to nil; so general populace doesn't see it for whole orig_occupant
617 end 646 end
618 end 647 end
619 648
620 self:save_occupant(orig_occupant); 649 self:save_occupant(orig_occupant);
621 self:publicise_occupant_status(orig_occupant, orig_x, dest_nick); 650 self:publicise_occupant_status(orig_occupant, orig_x, dest_nick);
694 return self:handle_kickable(origin, stanza) 723 return self:handle_kickable(origin, stanza)
695 elseif type == nil or type == "unavailable" then 724 elseif type == nil or type == "unavailable" then
696 return self:handle_normal_presence(origin, stanza); 725 return self:handle_normal_presence(origin, stanza);
697 elseif type ~= 'result' then -- bad type 726 elseif type ~= 'result' then -- bad type
698 if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences 727 if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences
699 origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? 728 origin.send(st.error_reply(stanza, "modify", "bad-request", nil, self.jid)); -- FIXME correct error?
700 end 729 end
701 end 730 end
702 return true; 731 return true;
703 end 732 end
704 733
729 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; 758 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
730 return true; 759 return true;
731 else -- Type is "get" or "set" 760 else -- Type is "get" or "set"
732 local current_nick = self:get_occupant_jid(from); 761 local current_nick = self:get_occupant_jid(from);
733 if not current_nick then 762 if not current_nick then
734 origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat")); 763 origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat", self.jid));
735 return true; 764 return true;
736 end 765 end
737 if not occupant then -- recipient not in room 766 if not occupant then -- recipient not in room
738 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); 767 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room", self.jid));
739 return true; 768 return true;
740 end 769 end
741 -- XEP-0410 MUC Self-Ping #1220 770 -- XEP-0410 MUC Self-Ping #1220
742 if to == current_nick and stanza.attr.type == "get" and stanza:get_child("ping", "urn:xmpp:ping") then 771 if to == current_nick and stanza.attr.type == "get" and stanza:get_child("ping", "urn:xmpp:ping") then
743 self:route_stanza(st.reply(stanza)); 772 self:route_stanza(st.reply(stanza));
762 local from, to = stanza.attr.from, stanza.attr.to; 791 local from, to = stanza.attr.from, stanza.attr.to;
763 local current_nick = self:get_occupant_jid(from); 792 local current_nick = self:get_occupant_jid(from);
764 local type = stanza.attr.type; 793 local type = stanza.attr.type;
765 if not current_nick then -- not in room 794 if not current_nick then -- not in room
766 if type ~= "error" then 795 if type ~= "error" then
767 origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat")); 796 origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat", self.jid));
768 end 797 end
769 return true; 798 return true;
770 end 799 end
771 if type == "groupchat" then -- groupchat messages not allowed in PM 800 if type == "groupchat" then -- groupchat messages not allowed in PM
772 origin.send(st.error_reply(stanza, "modify", "bad-request")); 801 origin.send(st.error_reply(stanza, "modify", "bad-request", nil, self.jid));
773 return true; 802 return true;
774 elseif type == "error" and is_kickable_error(stanza) then 803 elseif type == "error" and is_kickable_error(stanza) then
775 log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); 804 log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
776 return self:handle_kickable(origin, stanza); -- send unavailable 805 return self:handle_kickable(origin, stanza); -- send unavailable
777 end 806 end
778 807
779 local o_data = self:get_occupant_by_nick(to); 808 local o_data = self:get_occupant_by_nick(to);
780 if not o_data then 809 if not o_data then
781 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); 810 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room", self.jid));
782 return true; 811 return true;
783 end 812 end
784 log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid); 813 log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid);
785 stanza = muc_util.filter_muc_x(st.clone(stanza)); 814 stanza = muc_util.filter_muc_x(st.clone(stanza));
786 stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); 815 stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up();
813 function room_mt:process_form(origin, stanza) 842 function room_mt:process_form(origin, stanza)
814 local form = stanza.tags[1]:get_child("x", "jabber:x:data"); 843 local form = stanza.tags[1]:get_child("x", "jabber:x:data");
815 if form.attr.type == "cancel" then 844 if form.attr.type == "cancel" then
816 origin.send(st.reply(stanza)); 845 origin.send(st.reply(stanza));
817 elseif form.attr.type == "submit" then 846 elseif form.attr.type == "submit" then
847 -- luacheck: ignore 231/errors
818 local fields, errors, present; 848 local fields, errors, present;
819 if form.tags[1] == nil then -- Instant room 849 if form.tags[1] == nil then -- Instant room
820 fields, present = {}, {}; 850 fields, present = {}, {};
821 else 851 else
852 -- FIXME handle form errors
822 fields, errors, present = self:get_form_layout(stanza.attr.from):data(form); 853 fields, errors, present = self:get_form_layout(stanza.attr.from):data(form);
823 if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then 854 if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then
824 origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); 855 origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration"));
825 return true; 856 return true;
826 end 857 end
877 self:save_occupant(occupant); 908 self:save_occupant(occupant);
878 occupants_updated[occupant] = true; 909 occupants_updated[occupant] = true;
879 end 910 end
880 for occupant in pairs(occupants_updated) do 911 for occupant in pairs(occupants_updated) do
881 self:publicise_occupant_status(occupant, x); 912 self:publicise_occupant_status(occupant, x);
882 module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; occupant = occupant;}); 913 module:fire_event("muc-occupant-left", {
914 room = self;
915 nick = occupant.nick;
916 occupant = occupant;
917 });
883 end 918 end
884 end 919 end
885 920
886 function room_mt:destroy(newjid, reason, password) 921 function room_mt:destroy(newjid, reason, password)
887 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) 922 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
970 local item = stanza.tags[1].tags[1]; 1005 local item = stanza.tags[1].tags[1];
971 local _aff = item.attr.affiliation; 1006 local _aff = item.attr.affiliation;
972 local _aff_rank = valid_affiliations[_aff or "none"]; 1007 local _aff_rank = valid_affiliations[_aff or "none"];
973 local _rol = item.attr.role; 1008 local _rol = item.attr.role;
974 if _aff and _aff_rank and not _rol then 1009 if _aff and _aff_rank and not _rol then
975 -- You need to be at least an admin, and be requesting info about your affifiliation or lower 1010 -- You need to be at least an admin, and be requesting info about your affiliation or lower
976 -- e.g. an admin can't ask for a list of owners 1011 -- e.g. an admin can't ask for a list of owners
977 local affiliation_rank = valid_affiliations[affiliation or "none"]; 1012 local affiliation_rank = valid_affiliations[affiliation or "none"];
978 if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank) 1013 if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank)
979 or (self:get_whois() == "anyone") then 1014 or (self:get_whois() == "anyone") then
980 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); 1015 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
1047 end 1082 end
1048 1083
1049 function room_mt:handle_groupchat_to_room(origin, stanza) 1084 function room_mt:handle_groupchat_to_room(origin, stanza)
1050 local from = stanza.attr.from; 1085 local from = stanza.attr.from;
1051 local occupant = self:get_occupant_by_real_jid(from); 1086 local occupant = self:get_occupant_by_real_jid(from);
1087 if not stanza.attr.id then
1088 stanza.attr.id = new_id()
1089 end
1052 if module:fire_event("muc-occupant-groupchat", { 1090 if module:fire_event("muc-occupant-groupchat", {
1053 room = self; origin = origin; stanza = stanza; from = from; occupant = occupant; 1091 room = self; origin = origin; stanza = stanza; from = from; occupant = occupant;
1054 }) then return true; end 1092 }) then return true; end
1055 stanza.attr.from = occupant.nick; 1093 stanza.attr.from = occupant.nick;
1056 self:broadcast_message(stanza); 1094 self:broadcast_message(stanza);
1216 function room_mt:route_stanza(stanza) -- luacheck: ignore 212 1254 function room_mt:route_stanza(stanza) -- luacheck: ignore 212
1217 module:send(stanza); 1255 module:send(stanza);
1218 end 1256 end
1219 1257
1220 function room_mt:get_affiliation(jid) 1258 function room_mt:get_affiliation(jid)
1221 local node, host, resource = jid_split(jid); 1259 local node, host = jid_split(jid);
1222 -- Affiliations are granted, revoked, and maintained based on the user's bare JID. 1260 -- Affiliations are granted, revoked, and maintained based on the user's bare JID.
1223 local bare = node and node.."@"..host or host; 1261 local bare = node and node.."@"..host or host;
1224 local result = self._affiliations[bare]; 1262 local result = self._affiliations[bare];
1225 if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned 1263 if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned
1226 return result; 1264 return result;
1239 end 1277 end
1240 1278
1241 function room_mt:set_affiliation(actor, jid, affiliation, reason, data) 1279 function room_mt:set_affiliation(actor, jid, affiliation, reason, data)
1242 if not actor then return nil, "modify", "not-acceptable"; end; 1280 if not actor then return nil, "modify", "not-acceptable"; end;
1243 1281
1244 local node, host, resource = jid_split(jid); 1282 local node, host = jid_split(jid);
1245 if not host then return nil, "modify", "not-acceptable"; end 1283 if not host then return nil, "modify", "not-acceptable"; end
1246 jid = jid_join(node, host); -- Bare 1284 jid = jid_join(node, host); -- Bare
1247 local is_host_only = node == nil; 1285 local is_host_only = node == nil;
1248 1286
1249 if valid_affiliations[affiliation or "none"] == nil then 1287 if valid_affiliations[affiliation or "none"] == nil then
1295 for nick, occupant in self:each_occupant() do -- luacheck: ignore 213 1333 for nick, occupant in self:each_occupant() do -- luacheck: ignore 213
1296 if occupant.bare_jid == jid or ( 1334 if occupant.bare_jid == jid or (
1297 -- Outcast can be by host. 1335 -- Outcast can be by host.
1298 is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host 1336 is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host
1299 ) then 1337 ) then
1300 -- need to publcize in all cases; as affiliation in <item/> has changed. 1338 -- need to publicize in all cases; as affiliation in <item/> has changed.
1301 occupants_updated[occupant] = occupant.role; 1339 occupants_updated[occupant] = occupant.role;
1302 if occupant.role ~= role and ( 1340 if occupant.role ~= role and (
1303 is_downgrade or 1341 is_downgrade or
1304 valid_roles[occupant.role or "none"] < role_rank -- upgrade 1342 valid_roles[occupant.role or "none"] < role_rank -- upgrade
1305 ) then 1343 ) then
1322 1360
1323 if next(occupants_updated) ~= nil then 1361 if next(occupants_updated) ~= nil then
1324 for occupant, old_role in pairs(occupants_updated) do 1362 for occupant, old_role in pairs(occupants_updated) do
1325 self:publicise_occupant_status(occupant, x, nil, actor, reason); 1363 self:publicise_occupant_status(occupant, x, nil, actor, reason);
1326 if occupant.role == nil then 1364 if occupant.role == nil then
1327 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); 1365 module:fire_event("muc-occupant-left", {
1366 room = self;
1367 nick = occupant.nick;
1368 occupant = occupant;
1369 });
1328 elseif is_semi_anonymous and 1370 elseif is_semi_anonymous and
1329 (old_role == "moderator" and occupant.role ~= "moderator") or 1371 (old_role == "moderator" and occupant.role ~= "moderator") or
1330 (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status 1372 (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status
1331 -- Send everyone else's presences (as jid visibility has changed) 1373 -- Send everyone else's presences (as jid visibility has changed)
1332 for real_jid in occupant:each_session() do 1374 for real_jid in occupant:each_session() do
1374 function room_mt:get_role(nick) 1416 function room_mt:get_role(nick)
1375 local occupant = self:get_occupant_by_nick(nick); 1417 local occupant = self:get_occupant_by_nick(nick);
1376 return occupant and occupant.role or nil; 1418 return occupant and occupant.role or nil;
1377 end 1419 end
1378 1420
1421 function room_mt:may_set_role(actor, occupant, role)
1422 local event = {
1423 room = self,
1424 actor = actor,
1425 occupant = occupant,
1426 role = role,
1427 };
1428
1429 module:fire_event("muc-pre-set-role", event);
1430 if event.allowed ~= nil then
1431 return event.allowed, event.error, event.condition;
1432 end
1433
1434 -- Can't do anything to other owners or admins
1435 local occupant_affiliation = self:get_affiliation(occupant.bare_jid);
1436 if occupant_affiliation == "owner" or occupant_affiliation == "admin" then
1437 return nil, "cancel", "not-allowed";
1438 end
1439
1440 -- If you are trying to give or take moderator role you need to be an owner or admin
1441 if occupant.role == "moderator" or role == "moderator" then
1442 local actor_affiliation = self:get_affiliation(actor);
1443 if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then
1444 return nil, "cancel", "not-allowed";
1445 end
1446 end
1447
1448 -- Need to be in the room and a moderator
1449 local actor_occupant = self:get_occupant_by_real_jid(actor);
1450 if not actor_occupant or actor_occupant.role ~= "moderator" then
1451 return nil, "cancel", "not-allowed";
1452 end
1453
1454 return true;
1455 end
1456
1379 function room_mt:set_role(actor, occupant_jid, role, reason) 1457 function room_mt:set_role(actor, occupant_jid, role, reason)
1380 if not actor then return nil, "modify", "not-acceptable"; end 1458 if not actor then return nil, "modify", "not-acceptable"; end
1381 1459
1382 local occupant = self:get_occupant_by_nick(occupant_jid); 1460 local occupant = self:get_occupant_by_nick(occupant_jid);
1383 if not occupant then return nil, "modify", "item-not-found"; end 1461 if not occupant then return nil, "modify", "item-not-found"; end
1388 role = role ~= "none" and role or nil; -- coerces `role == false` to `nil` 1466 role = role ~= "none" and role or nil; -- coerces `role == false` to `nil`
1389 1467
1390 if actor == true then 1468 if actor == true then
1391 actor = nil -- So we can pass it safely to 'publicise_occupant_status' below 1469 actor = nil -- So we can pass it safely to 'publicise_occupant_status' below
1392 else 1470 else
1393 -- Can't do anything to other owners or admins 1471 local allowed, err, condition = self:may_set_role(actor, occupant, role)
1394 local occupant_affiliation = self:get_affiliation(occupant.bare_jid); 1472 if not allowed then
1395 if occupant_affiliation == "owner" or occupant_affiliation == "admin" then 1473 return allowed, err, condition;
1396 return nil, "cancel", "not-allowed";
1397 end
1398
1399 -- If you are trying to give or take moderator role you need to be an owner or admin
1400 if occupant.role == "moderator" or role == "moderator" then
1401 local actor_affiliation = self:get_affiliation(actor);
1402 if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then
1403 return nil, "cancel", "not-allowed";
1404 end
1405 end
1406
1407 -- Need to be in the room and a moderator
1408 local actor_occupant = self:get_occupant_by_real_jid(actor);
1409 if not actor_occupant or actor_occupant.role ~= "moderator" then
1410 return nil, "cancel", "not-allowed";
1411 end 1474 end
1412 end 1475 end
1413 1476
1414 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); 1477 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"});
1415 if not role then 1478 if not role then
1416 x:tag("status", {code = "307"}):up(); 1479 x:tag("status", {code = "307"}):up();
1417 end 1480 end
1481
1482 local prev_role = occupant.role;
1418 occupant.role = role; 1483 occupant.role = role;
1419 self:save_occupant(occupant); 1484 self:save_occupant(occupant);
1420 self:publicise_occupant_status(occupant, x, nil, actor, reason); 1485 self:publicise_occupant_status(occupant, x, nil, actor, reason, prev_role);
1421 if role == nil then 1486 if role == nil then
1422 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); 1487 module:fire_event("muc-occupant-left", {
1488 room = self;
1489 nick = occupant.nick;
1490 occupant = occupant;
1491 });
1423 end 1492 end
1424 return true; 1493 return true;
1425 end 1494 end
1426 1495
1427 local whois = module:require "muc/whois"; 1496 local whois = module:require "muc/whois";
1502 -- Old storage format 1571 -- Old storage format
1503 room._affiliations = frozen._affiliations; 1572 room._affiliations = frozen._affiliations;
1504 else 1573 else
1505 -- New storage format 1574 -- New storage format
1506 for jid, data in pairs(frozen) do 1575 for jid, data in pairs(frozen) do
1507 local node, host, resource = jid_split(jid); 1576 local _, host, resource = jid_split(jid);
1508 if host:sub(1,1) ~= "_" and not resource and type(data) == "string" then 1577 if host:sub(1,1) ~= "_" and not resource and type(data) == "string" then
1509 -- bare jid: affiliation 1578 -- bare jid: affiliation
1510 room._affiliations[jid] = data; 1579 room._affiliations[jid] = data;
1511 end 1580 end
1512 end 1581 end