comparison plugins/muc/muc.lib.lua @ 11200:bf8f2da84007

Merge 0.11->trunk
author Kim Alvefur <zash@zash.se>
date Thu, 05 Nov 2020 22:31:25 +0100
parents 818255f49297
children b1d7027be61e
comparison
equal deleted inserted replaced
11199:6c7c50a4de32 11200:bf8f2da84007
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
234 local event = { 235 local event = {
235 room = self; stanza = base_presence; x = base_x; 236 room = self; stanza = base_presence; x = base_x;
236 occupant = occupant; nick = nick; actor = actor; 237 occupant = occupant; nick = nick; actor = actor;
237 reason = reason; 238 reason = reason;
238 } 239 }
240 module:fire_event("muc-build-occupant-presence", event);
239 module:fire_event("muc-broadcast-presence", event); 241 module:fire_event("muc-broadcast-presence", event);
240 242
241 -- Allow muc-broadcast-presence listeners to change things 243 -- Allow muc-broadcast-presence listeners to change things
242 nick = event.nick; 244 nick = event.nick;
243 actor = event.actor; 245 actor = event.actor;
277 self_x = st.clone(x.self or base_x); 279 self_x = st.clone(x.self or base_x);
278 self:build_item_list(occupant, self_x, false, nick, actor_nick, nil, reason); 280 self:build_item_list(occupant, self_x, false, nick, actor_nick, nil, reason);
279 self_p = st.clone(base_presence):add_child(self_x); 281 self_p = st.clone(base_presence):add_child(self_x);
280 end 282 end
281 283
282 -- General populance 284 local broadcast_roles = self:get_presence_broadcast();
285
286 -- General populace
283 for occupant_nick, n_occupant in self:each_occupant() do 287 for occupant_nick, n_occupant in self:each_occupant() do
284 if occupant_nick ~= occupant.nick then 288 if occupant_nick ~= occupant.nick then
285 local pr; 289 local pr;
286 if can_see_real_jids(whois, n_occupant) then 290 if can_see_real_jids(whois, n_occupant) then
287 pr = get_full_p(); 291 pr = get_full_p();
288 elseif occupant.bare_jid == n_occupant.bare_jid then 292 elseif occupant.bare_jid == n_occupant.bare_jid then
289 pr = self_p; 293 pr = self_p;
290 else 294 else
291 pr = get_anon_p(); 295 pr = get_anon_p();
292 end 296 end
293 self:route_to_occupant(n_occupant, pr); 297 if broadcast_roles[occupant.role or "none"] or force_unavailable then
298 self:route_to_occupant(n_occupant, pr);
299 elseif prev_role and broadcast_roles[prev_role] then
300 pr.attr.type = 'unavailable';
301 self:route_to_occupant(n_occupant, pr);
302 end
303
294 end 304 end
295 end 305 end
296 306
297 -- Presences for occupant itself 307 -- Presences for occupant itself
298 self_x:tag("status", {code = "110";}):up(); 308 self_x:tag("status", {code = "110";}):up();
301 self:route_to_occupant(occupant, self_p); 311 self:route_to_occupant(occupant, self_p);
302 else 312 else
303 -- use their own presences as templates 313 -- use their own presences as templates
304 for full_jid, pr in occupant:each_session() do 314 for full_jid, pr in occupant:each_session() do
305 pr = st.clone(pr); 315 pr = st.clone(pr);
316 module:fire_event("muc-build-occupant-presence", { room = self, occupant = occupant, stanza = pr });
306 pr.attr.to = full_jid; 317 pr.attr.to = full_jid;
307 pr:add_child(self_x); 318 pr:add_child(self_x);
308 self:route_stanza(pr); 319 self:route_stanza(pr);
309 end 320 end
310 end 321 end
312 323
313 function room_mt:send_occupant_list(to, filter) 324 function room_mt:send_occupant_list(to, filter)
314 local to_bare = jid_bare(to); 325 local to_bare = jid_bare(to);
315 local is_anonymous = false; 326 local is_anonymous = false;
316 local whois = self:get_whois(); 327 local whois = self:get_whois();
328 local broadcast_roles = self:get_presence_broadcast();
317 if whois ~= "anyone" then 329 if whois ~= "anyone" then
318 local affiliation = self:get_affiliation(to); 330 local affiliation = self:get_affiliation(to);
319 if affiliation ~= "admin" and affiliation ~= "owner" then 331 if affiliation ~= "admin" and affiliation ~= "owner" then
320 local occupant = self:get_occupant_by_real_jid(to); 332 local occupant = self:get_occupant_by_real_jid(to);
321 if not (occupant and can_see_real_jids(whois, occupant)) then 333 if not (occupant and can_see_real_jids(whois, occupant)) then
322 is_anonymous = true; 334 is_anonymous = true;
323 end 335 end
324 end 336 end
325 end 337 end
338 local broadcast_bare_jids = {}; -- Track which bare JIDs we have sent presence for
326 for occupant_jid, occupant in self:each_occupant() do 339 for occupant_jid, occupant in self:each_occupant() do
340 broadcast_bare_jids[occupant.bare_jid] = true;
327 if filter == nil or filter(occupant_jid, occupant) then 341 if filter == nil or filter(occupant_jid, occupant) then
328 local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); 342 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 343 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()); 344 local pres = st.clone(occupant:get_presence());
331 pres.attr.to = to; 345 pres.attr.to = to;
332 pres:add_child(x); 346 pres:add_child(x);
333 self:route_stanza(pres); 347 module:fire_event("muc-build-occupant-presence", { room = self, occupant = occupant, stanza = pres });
348 if to_bare == occupant.bare_jid or broadcast_roles[occupant.role or "none"] then
349 self:route_stanza(pres);
350 end
351 end
352 end
353 if broadcast_roles.none then
354 -- Broadcast stanzas for affiliated users not currently in the MUC
355 for affiliated_jid, affiliation, affiliation_data in self:each_affiliation() do
356 local nick = affiliation_data and affiliation_data.reserved_nickname;
357 if (nick or not is_anonymous) and not broadcast_bare_jids[affiliated_jid]
358 and (filter == nil or filter(affiliated_jid, nil)) then
359 local from = nick and (self.jid.."/"..nick) or self.jid;
360 local pres = st.presence({ to = to, from = from, type = "unavailable" })
361 :tag("x", { xmlns = 'http://jabber.org/protocol/muc#user' })
362 :tag("item", {
363 affiliation = affiliation;
364 role = "none";
365 nick = nick;
366 jid = not is_anonymous and affiliated_jid or nil }):up()
367 :up();
368 self:route_stanza(pres);
369 end
334 end 370 end
335 end 371 end
336 end 372 end
337 373
338 function room_mt:get_disco_info(stanza) 374 function room_mt:get_disco_info(stanza)
371 407
372 function room_mt:handle_kickable(origin, stanza) -- luacheck: ignore 212 408 function room_mt:handle_kickable(origin, stanza) -- luacheck: ignore 212
373 local real_jid = stanza.attr.from; 409 local real_jid = stanza.attr.from;
374 local occupant = self:get_occupant_by_real_jid(real_jid); 410 local occupant = self:get_occupant_by_real_jid(real_jid);
375 if occupant == nil then return nil; end 411 if occupant == nil then return nil; end
376 local type, condition, text = stanza:get_error(); 412 local _, condition, text = stanza:get_error();
377 local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error"); 413 local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error");
378 if text and self:get_whois() == "anyone" then 414 if text and self:get_whois() == "anyone" then
379 error_message = error_message..": "..text; 415 error_message = error_message..": "..text;
380 end 416 end
381 occupant:set_session(real_jid, st.presence({type="unavailable"}) 417 occupant:set_session(real_jid, st.presence({type="unavailable"})
382 :tag('status'):text(error_message)); 418 :tag('status'):text(error_message));
419 local orig_role = occupant.role;
383 local is_last_session = occupant.jid == real_jid; 420 local is_last_session = occupant.jid == real_jid;
384 if is_last_session then 421 if is_last_session then
385 occupant.role = nil; 422 occupant.role = nil;
386 end 423 end
387 local new_occupant = self:save_occupant(occupant); 424 local new_occupant = self:save_occupant(occupant);
388 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); 425 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";});
389 if is_last_session then 426 if is_last_session then
390 x:tag("status", {code = "333"}); 427 x:tag("status", {code = "333"});
391 end 428 end
392 self:publicise_occupant_status(new_occupant or occupant, x); 429 self:publicise_occupant_status(new_occupant or occupant, x, nil, nil, nil, orig_role);
393 if is_last_session then 430 if is_last_session then
394 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); 431 module:fire_event("muc-occupant-left", {
432 room = self;
433 nick = occupant.nick;
434 occupant = occupant;
435 });
395 end 436 end
396 return true; 437 return true;
397 end 438 end
398 439
399 -- Give the room creator owner affiliation 440 -- Give the room creator owner affiliation
404 -- check if user is banned 445 -- check if user is banned
405 module:hook("muc-occupant-pre-join", function(event) 446 module:hook("muc-occupant-pre-join", function(event)
406 local room, stanza = event.room, event.stanza; 447 local room, stanza = event.room, event.stanza;
407 local affiliation = room:get_affiliation(stanza.attr.from); 448 local affiliation = room:get_affiliation(stanza.attr.from);
408 if affiliation == "outcast" then 449 if affiliation == "outcast" then
409 local reply = st.error_reply(stanza, "auth", "forbidden"):up(); 450 local reply = st.error_reply(stanza, "auth", "forbidden", nil, room.jid):up();
410 reply.tags[1].attr.code = "403"; 451 reply.tags[1].attr.code = "403";
411 event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); 452 event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
412 return true; 453 return true;
413 end 454 end
414 end, -10); 455 end, -10);
415 456
416 module:hook("muc-occupant-pre-join", function(event) 457 module:hook("muc-occupant-pre-join", function(event)
458 local room = event.room;
417 local nick = jid_resource(event.occupant.nick); 459 local nick = jid_resource(event.occupant.nick);
418 if not nick:find("%S") then 460 if not nick:find("%S") then
419 event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden")); 461 event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden", room.jid));
420 return true; 462 return true;
421 end 463 end
422 end, 1); 464 end, 1);
423 465
424 module:hook("muc-occupant-pre-change", function(event) 466 module:hook("muc-occupant-pre-change", function(event)
467 local room = event.room;
425 if not jid_resource(event.dest_occupant.nick):find("%S") then 468 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")); 469 event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden", room.jid));
427 return true; 470 return true;
428 end 471 end
429 end, 1); 472 end, 1);
430 473
474 module:hook("muc-occupant-pre-join", function(event)
475 local room = event.room;
476 local nick = jid_resource(event.occupant.nick);
477 if not resourceprep(nick, true) then -- strict
478 event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation", room.jid));
479 return true;
480 end
481 end, 2);
482
483 module:hook("muc-occupant-pre-change", function(event)
484 local room = event.room;
485 local nick = jid_resource(event.dest_occupant.nick);
486 if not resourceprep(nick, true) then -- strict
487 event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation", room.jid));
488 return true;
489 end
490 end, 2);
491
431 function room_mt:handle_first_presence(origin, stanza) 492 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; 493 local real_jid = stanza.attr.from;
440 local dest_jid = stanza.attr.to; 494 local dest_jid = stanza.attr.to;
441 local bare_jid = jid_bare(real_jid); 495 local bare_jid = jid_bare(real_jid);
442 if module:fire_event("muc-room-pre-create", { 496 if module:fire_event("muc-room-pre-create", {
443 room = self; 497 room = self;
503 local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc"); 557 local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc");
504 558
505 if orig_occupant == nil and not muc_x and stanza.attr.type == nil then 559 if orig_occupant == nil and not muc_x and stanza.attr.type == nil then
506 module:log("debug", "Attempted join without <x>, possibly desynced"); 560 module:log("debug", "Attempted join without <x>, possibly desynced");
507 origin.send(st.error_reply(stanza, "cancel", "item-not-found", 561 origin.send(st.error_reply(stanza, "cancel", "item-not-found",
508 "You must join the room before sending presence updates")); 562 "You are not currently connected to this chat", self.jid));
509 return true; 563 return true;
510 end 564 end
511 565
512 local is_first_dest_session; 566 local is_first_dest_session;
513 local dest_occupant; 567 local dest_occupant;
565 -- Check for nick conflicts 619 -- Check for nick conflicts
566 if dest_occupant ~= nil and not is_first_dest_session 620 if dest_occupant ~= nil and not is_first_dest_session
567 and bare_jid ~= jid_bare(dest_occupant.bare_jid) then 621 and bare_jid ~= jid_bare(dest_occupant.bare_jid) then
568 -- new nick or has different bare real jid 622 -- 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); 623 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(); 624 local reply = st.error_reply(stanza, "cancel", "conflict", nil, self.jid):up();
571 reply.tags[1].attr.code = "409"; 625 reply.tags[1].attr.code = "409";
572 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); 626 origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
573 return true; 627 return true;
574 end 628 end
575 629
576 -- Send presence stanza about original occupant 630 -- Send presence stanza about original occupant
577 if orig_occupant ~= nil and orig_occupant ~= dest_occupant then 631 if orig_occupant ~= nil and orig_occupant ~= dest_occupant then
578 local orig_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); 632 local orig_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";});
633 local orig_role = orig_occupant.role;
579 local dest_nick; 634 local dest_nick;
580 if dest_occupant == nil then -- Session is leaving 635 if dest_occupant == nil then -- Session is leaving
581 log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); 636 log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick);
582 if is_last_orig_session then 637 if is_last_orig_session then
583 orig_occupant.role = nil; 638 orig_occupant.role = nil;
611 -- self:build_item_list(orig_occupant, x, false, dest_nick); 666 -- 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); 667 add_item(x, self:get_affiliation(bare_jid), orig_occupant.role, real_jid, dest_nick);
613 x:tag("status", {code = "303";}):up(); 668 x:tag("status", {code = "303";}):up();
614 x:tag("status", {code = "110";}):up(); 669 x:tag("status", {code = "110";}):up();
615 self:route_stanza(generated_unavail:add_child(x)); 670 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 671 dest_nick = nil; -- set dest_nick to nil; so general populace doesn't see it for whole orig_occupant
617 end 672 end
618 end 673 end
619 674
620 self:save_occupant(orig_occupant); 675 self:save_occupant(orig_occupant);
621 self:publicise_occupant_status(orig_occupant, orig_x, dest_nick); 676 self:publicise_occupant_status(orig_occupant, orig_x, dest_nick, nil, nil, orig_role);
622 677
623 if is_last_orig_session then 678 if is_last_orig_session then
624 module:fire_event("muc-occupant-left", { 679 module:fire_event("muc-occupant-left", {
625 room = self; 680 room = self;
626 nick = orig_occupant.nick; 681 nick = orig_occupant.nick;
637 692
638 if orig_occupant == nil or muc_x then 693 if orig_occupant == nil or muc_x then
639 -- Send occupant list to newly joined or desynced user 694 -- Send occupant list to newly joined or desynced user
640 self:send_occupant_list(real_jid, function(nick, occupant) -- luacheck: ignore 212 695 self:send_occupant_list(real_jid, function(nick, occupant) -- luacheck: ignore 212
641 -- Don't include self 696 -- Don't include self
642 return occupant:get_presence(real_jid) == nil; 697 return (not occupant) or occupant:get_presence(real_jid) == nil;
643 end) 698 end)
644 end 699 end
645 local dest_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); 700 local dest_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";});
646 local self_x = st.clone(dest_x); 701 local self_x = st.clone(dest_x);
647 if orig_occupant == nil and self:get_whois() == "anyone" then 702 if orig_occupant == nil and self:get_whois() == "anyone" then
648 self_x:tag("status", {code = "100"}):up(); 703 self_x:tag("status", {code = "100"}):up();
649 end 704 end
650 if nick_changed then 705 if nick_changed then
651 self_x:tag("status", {code="210"}):up(); 706 self_x:tag("status", {code="210"}):up();
652 end 707 end
653 self:publicise_occupant_status(dest_occupant, {base=dest_x,self=self_x}); 708 self:publicise_occupant_status(dest_occupant, {base=dest_x,self=self_x}, nil, nil, nil, orig_occupant and orig_occupant.role or nil);
654 709
655 if orig_occupant ~= nil and orig_occupant ~= dest_occupant and not is_last_orig_session then 710 if orig_occupant ~= nil and orig_occupant ~= dest_occupant and not is_last_orig_session then
656 -- If user is swapping and wasn't last original session 711 -- If user is swapping and wasn't last original session
657 log("debug", "session %s split nicks; showing %s rejoining", real_jid, orig_occupant.nick); 712 log("debug", "session %s split nicks; showing %s rejoining", real_jid, orig_occupant.nick);
658 -- Show the original nick joining again 713 -- Show the original nick joining again
694 return self:handle_kickable(origin, stanza) 749 return self:handle_kickable(origin, stanza)
695 elseif type == nil or type == "unavailable" then 750 elseif type == nil or type == "unavailable" then
696 return self:handle_normal_presence(origin, stanza); 751 return self:handle_normal_presence(origin, stanza);
697 elseif type ~= 'result' then -- bad type 752 elseif type ~= 'result' then -- bad type
698 if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences 753 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? 754 origin.send(st.error_reply(stanza, "modify", "bad-request", nil, self.jid)); -- FIXME correct error?
700 end 755 end
701 end 756 end
702 return true; 757 return true;
703 end 758 end
704 759
729 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; 784 stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id;
730 return true; 785 return true;
731 else -- Type is "get" or "set" 786 else -- Type is "get" or "set"
732 local current_nick = self:get_occupant_jid(from); 787 local current_nick = self:get_occupant_jid(from);
733 if not current_nick then 788 if not current_nick then
734 origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat")); 789 origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat", self.jid));
735 return true; 790 return true;
736 end 791 end
737 if not occupant then -- recipient not in room 792 if not occupant then -- recipient not in room
738 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); 793 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room", self.jid));
739 return true; 794 return true;
740 end 795 end
741 -- XEP-0410 MUC Self-Ping #1220 796 -- 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 797 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)); 798 self:route_stanza(st.reply(stanza));
762 local from, to = stanza.attr.from, stanza.attr.to; 817 local from, to = stanza.attr.from, stanza.attr.to;
763 local current_nick = self:get_occupant_jid(from); 818 local current_nick = self:get_occupant_jid(from);
764 local type = stanza.attr.type; 819 local type = stanza.attr.type;
765 if not current_nick then -- not in room 820 if not current_nick then -- not in room
766 if type ~= "error" then 821 if type ~= "error" then
767 origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat")); 822 origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat", self.jid));
768 end 823 end
769 return true; 824 return true;
770 end 825 end
771 if type == "groupchat" then -- groupchat messages not allowed in PM 826 if type == "groupchat" then -- groupchat messages not allowed in PM
772 origin.send(st.error_reply(stanza, "modify", "bad-request")); 827 origin.send(st.error_reply(stanza, "modify", "bad-request", nil, self.jid));
773 return true; 828 return true;
774 elseif type == "error" and is_kickable_error(stanza) then 829 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); 830 log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
776 return self:handle_kickable(origin, stanza); -- send unavailable 831 return self:handle_kickable(origin, stanza); -- send unavailable
777 end 832 end
778 833
779 local o_data = self:get_occupant_by_nick(to); 834 local o_data = self:get_occupant_by_nick(to);
780 if not o_data then 835 if not o_data then
781 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); 836 origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room", self.jid));
782 return true; 837 return true;
783 end 838 end
784 log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid); 839 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)); 840 stanza = muc_util.filter_muc_x(st.clone(stanza));
786 stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); 841 stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up();
787 stanza.attr.from = current_nick; 842 stanza.attr.from = current_nick;
788 self:route_to_occupant(o_data, stanza) 843 if module:fire_event("muc-private-message", { room = self, origin = origin, stanza = stanza }) ~= false then
844 self:route_to_occupant(o_data, stanza)
845 end
789 -- TODO: Remove x tag? 846 -- TODO: Remove x tag?
790 stanza.attr.from = from; 847 stanza.attr.from = from;
791 return true; 848 return true;
792 end 849 end
793 850
813 function room_mt:process_form(origin, stanza) 870 function room_mt:process_form(origin, stanza)
814 local form = stanza.tags[1]:get_child("x", "jabber:x:data"); 871 local form = stanza.tags[1]:get_child("x", "jabber:x:data");
815 if form.attr.type == "cancel" then 872 if form.attr.type == "cancel" then
816 origin.send(st.reply(stanza)); 873 origin.send(st.reply(stanza));
817 elseif form.attr.type == "submit" then 874 elseif form.attr.type == "submit" then
875 -- luacheck: ignore 231/errors
818 local fields, errors, present; 876 local fields, errors, present;
819 if form.tags[1] == nil then -- Instant room 877 if form.tags[1] == nil then -- Instant room
820 fields, present = {}, {}; 878 fields, present = {}, {};
821 else 879 else
880 -- FIXME handle form errors
822 fields, errors, present = self:get_form_layout(stanza.attr.from):data(form); 881 fields, errors, present = self:get_form_layout(stanza.attr.from):data(form);
823 if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then 882 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")); 883 origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration"));
825 return true; 884 return true;
826 end 885 end
871 -- Removes everyone from the room 930 -- Removes everyone from the room
872 function room_mt:clear(x) 931 function room_mt:clear(x)
873 x = x or st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); 932 x = x or st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'});
874 local occupants_updated = {}; 933 local occupants_updated = {};
875 for nick, occupant in self:each_occupant() do -- luacheck: ignore 213 934 for nick, occupant in self:each_occupant() do -- luacheck: ignore 213
935 local prev_role = occupant.role;
876 occupant.role = nil; 936 occupant.role = nil;
877 self:save_occupant(occupant); 937 self:save_occupant(occupant);
878 occupants_updated[occupant] = true; 938 occupants_updated[occupant] = prev_role;
879 end 939 end
880 for occupant in pairs(occupants_updated) do 940 for occupant, prev_role in pairs(occupants_updated) do
881 self:publicise_occupant_status(occupant, x); 941 self:publicise_occupant_status(occupant, x, nil, nil, nil, prev_role);
882 module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; occupant = occupant;}); 942 module:fire_event("muc-occupant-left", {
943 room = self;
944 nick = occupant.nick;
945 occupant = occupant;
946 });
883 end 947 end
884 end 948 end
885 949
886 function room_mt:destroy(newjid, reason, password) 950 function room_mt:destroy(newjid, reason, password)
887 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) 951 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
970 local item = stanza.tags[1].tags[1]; 1034 local item = stanza.tags[1].tags[1];
971 local _aff = item.attr.affiliation; 1035 local _aff = item.attr.affiliation;
972 local _aff_rank = valid_affiliations[_aff or "none"]; 1036 local _aff_rank = valid_affiliations[_aff or "none"];
973 local _rol = item.attr.role; 1037 local _rol = item.attr.role;
974 if _aff and _aff_rank and not _rol then 1038 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 1039 -- 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 1040 -- e.g. an admin can't ask for a list of owners
977 local affiliation_rank = valid_affiliations[affiliation or "none"]; 1041 local affiliation_rank = valid_affiliations[affiliation or "none"];
978 if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank) 1042 if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank)
979 or (self:get_whois() == "anyone") then 1043 or (self:get_whois() == "anyone") then
980 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); 1044 local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
1047 end 1111 end
1048 1112
1049 function room_mt:handle_groupchat_to_room(origin, stanza) 1113 function room_mt:handle_groupchat_to_room(origin, stanza)
1050 local from = stanza.attr.from; 1114 local from = stanza.attr.from;
1051 local occupant = self:get_occupant_by_real_jid(from); 1115 local occupant = self:get_occupant_by_real_jid(from);
1116 if not stanza.attr.id then
1117 stanza.attr.id = new_id()
1118 end
1052 if module:fire_event("muc-occupant-groupchat", { 1119 if module:fire_event("muc-occupant-groupchat", {
1053 room = self; origin = origin; stanza = stanza; from = from; occupant = occupant; 1120 room = self; origin = origin; stanza = stanza; from = from; occupant = occupant;
1054 }) then return true; end 1121 }) then return true; end
1055 stanza.attr.from = occupant.nick; 1122 stanza.attr.from = occupant.nick;
1056 self:broadcast_message(stanza); 1123 self:broadcast_message(stanza);
1216 function room_mt:route_stanza(stanza) -- luacheck: ignore 212 1283 function room_mt:route_stanza(stanza) -- luacheck: ignore 212
1217 module:send(stanza); 1284 module:send(stanza);
1218 end 1285 end
1219 1286
1220 function room_mt:get_affiliation(jid) 1287 function room_mt:get_affiliation(jid)
1221 local node, host, resource = jid_split(jid); 1288 local node, host = jid_split(jid);
1222 -- Affiliations are granted, revoked, and maintained based on the user's bare JID. 1289 -- Affiliations are granted, revoked, and maintained based on the user's bare JID.
1223 local bare = node and node.."@"..host or host; 1290 local bare = node and node.."@"..host or host;
1224 local result = self._affiliations[bare]; 1291 local result = self._affiliations[bare];
1225 if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned 1292 if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned
1226 return result; 1293 return result;
1239 end 1306 end
1240 1307
1241 function room_mt:set_affiliation(actor, jid, affiliation, reason, data) 1308 function room_mt:set_affiliation(actor, jid, affiliation, reason, data)
1242 if not actor then return nil, "modify", "not-acceptable"; end; 1309 if not actor then return nil, "modify", "not-acceptable"; end;
1243 1310
1244 local node, host, resource = jid_split(jid); 1311 local node, host = jid_split(jid);
1245 if not host then return nil, "modify", "not-acceptable"; end 1312 if not host then return nil, "modify", "not-acceptable"; end
1246 jid = jid_join(node, host); -- Bare 1313 jid = jid_join(node, host); -- Bare
1247 local is_host_only = node == nil; 1314 local is_host_only = node == nil;
1248 1315
1249 if valid_affiliations[affiliation or "none"] == nil then 1316 if valid_affiliations[affiliation or "none"] == nil then
1295 for nick, occupant in self:each_occupant() do -- luacheck: ignore 213 1362 for nick, occupant in self:each_occupant() do -- luacheck: ignore 213
1296 if occupant.bare_jid == jid or ( 1363 if occupant.bare_jid == jid or (
1297 -- Outcast can be by host. 1364 -- Outcast can be by host.
1298 is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host 1365 is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host
1299 ) then 1366 ) then
1300 -- need to publcize in all cases; as affiliation in <item/> has changed. 1367 -- need to publicize in all cases; as affiliation in <item/> has changed.
1301 occupants_updated[occupant] = occupant.role; 1368 occupants_updated[occupant] = occupant.role;
1302 if occupant.role ~= role and ( 1369 if occupant.role ~= role and (
1303 is_downgrade or 1370 is_downgrade or
1304 valid_roles[occupant.role or "none"] < role_rank -- upgrade 1371 valid_roles[occupant.role or "none"] < role_rank -- upgrade
1305 ) then 1372 ) then
1320 end 1387 end
1321 local is_semi_anonymous = self:get_whois() == "moderators"; 1388 local is_semi_anonymous = self:get_whois() == "moderators";
1322 1389
1323 if next(occupants_updated) ~= nil then 1390 if next(occupants_updated) ~= nil then
1324 for occupant, old_role in pairs(occupants_updated) do 1391 for occupant, old_role in pairs(occupants_updated) do
1325 self:publicise_occupant_status(occupant, x, nil, actor, reason); 1392 self:publicise_occupant_status(occupant, x, nil, actor, reason, old_role);
1326 if occupant.role == nil then 1393 if occupant.role == nil then
1327 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); 1394 module:fire_event("muc-occupant-left", {
1395 room = self;
1396 nick = occupant.nick;
1397 occupant = occupant;
1398 });
1328 elseif is_semi_anonymous and 1399 elseif is_semi_anonymous and
1329 (old_role == "moderator" and occupant.role ~= "moderator") or 1400 (old_role == "moderator" and occupant.role ~= "moderator") or
1330 (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status 1401 (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) 1402 -- Send everyone else's presences (as jid visibility has changed)
1332 for real_jid in occupant:each_session() do 1403 for real_jid in occupant:each_session() do
1333 self:send_occupant_list(real_jid, function(occupant_jid, occupant) --luacheck: ignore 212 433 1404 self:send_occupant_list(real_jid, function(occupant_jid, occupant) --luacheck: ignore 212 433
1334 return occupant.bare_jid ~= jid; 1405 return (not occupant) or occupant.bare_jid ~= jid;
1335 end); 1406 end);
1336 end 1407 end
1337 end 1408 end
1338 end 1409 end
1339 else 1410 else
1374 function room_mt:get_role(nick) 1445 function room_mt:get_role(nick)
1375 local occupant = self:get_occupant_by_nick(nick); 1446 local occupant = self:get_occupant_by_nick(nick);
1376 return occupant and occupant.role or nil; 1447 return occupant and occupant.role or nil;
1377 end 1448 end
1378 1449
1450 function room_mt:may_set_role(actor, occupant, role)
1451 local event = {
1452 room = self,
1453 actor = actor,
1454 occupant = occupant,
1455 role = role,
1456 };
1457
1458 module:fire_event("muc-pre-set-role", event);
1459 if event.allowed ~= nil then
1460 return event.allowed, event.error, event.condition;
1461 end
1462
1463 -- Can't do anything to other owners or admins
1464 local occupant_affiliation = self:get_affiliation(occupant.bare_jid);
1465 if occupant_affiliation == "owner" or occupant_affiliation == "admin" then
1466 return nil, "cancel", "not-allowed";
1467 end
1468
1469 -- If you are trying to give or take moderator role you need to be an owner or admin
1470 if occupant.role == "moderator" or role == "moderator" then
1471 local actor_affiliation = self:get_affiliation(actor);
1472 if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then
1473 return nil, "cancel", "not-allowed";
1474 end
1475 end
1476
1477 -- Need to be in the room and a moderator
1478 local actor_occupant = self:get_occupant_by_real_jid(actor);
1479 if not actor_occupant or actor_occupant.role ~= "moderator" then
1480 return nil, "cancel", "not-allowed";
1481 end
1482
1483 return true;
1484 end
1485
1379 function room_mt:set_role(actor, occupant_jid, role, reason) 1486 function room_mt:set_role(actor, occupant_jid, role, reason)
1380 if not actor then return nil, "modify", "not-acceptable"; end 1487 if not actor then return nil, "modify", "not-acceptable"; end
1381 1488
1382 local occupant = self:get_occupant_by_nick(occupant_jid); 1489 local occupant = self:get_occupant_by_nick(occupant_jid);
1383 if not occupant then return nil, "modify", "item-not-found"; end 1490 if not occupant then return nil, "modify", "item-not-found"; end
1388 role = role ~= "none" and role or nil; -- coerces `role == false` to `nil` 1495 role = role ~= "none" and role or nil; -- coerces `role == false` to `nil`
1389 1496
1390 if actor == true then 1497 if actor == true then
1391 actor = nil -- So we can pass it safely to 'publicise_occupant_status' below 1498 actor = nil -- So we can pass it safely to 'publicise_occupant_status' below
1392 else 1499 else
1393 -- Can't do anything to other owners or admins 1500 local allowed, err, condition = self:may_set_role(actor, occupant, role)
1394 local occupant_affiliation = self:get_affiliation(occupant.bare_jid); 1501 if not allowed then
1395 if occupant_affiliation == "owner" or occupant_affiliation == "admin" then 1502 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 1503 end
1412 end 1504 end
1413 1505
1414 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); 1506 local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"});
1415 if not role then 1507 if not role then
1416 x:tag("status", {code = "307"}):up(); 1508 x:tag("status", {code = "307"}):up();
1417 end 1509 end
1510
1511 local prev_role = occupant.role;
1418 occupant.role = role; 1512 occupant.role = role;
1419 self:save_occupant(occupant); 1513 self:save_occupant(occupant);
1420 self:publicise_occupant_status(occupant, x, nil, actor, reason); 1514 self:publicise_occupant_status(occupant, x, nil, actor, reason, prev_role);
1421 if role == nil then 1515 if role == nil then
1422 module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;}); 1516 module:fire_event("muc-occupant-left", {
1517 room = self;
1518 nick = occupant.nick;
1519 occupant = occupant;
1520 });
1423 end 1521 end
1424 return true; 1522 return true;
1425 end 1523 end
1426 1524
1427 local whois = module:require "muc/whois"; 1525 local whois = module:require "muc/whois";
1439 _affiliations = {}; 1537 _affiliations = {};
1440 _affiliation_data = {}; 1538 _affiliation_data = {};
1441 }, room_mt); 1539 }, room_mt);
1442 end 1540 end
1443 1541
1444 local new_format = module:get_option_boolean("new_muc_storage_format", false); 1542 local new_format = module:get_option_boolean("new_muc_storage_format", true);
1445 1543
1446 function room_mt:freeze(live) 1544 function room_mt:freeze(live)
1447 local frozen, state; 1545 local frozen, state;
1448 if new_format then 1546 if new_format then
1449 frozen = { 1547 frozen = {
1503 -- Old storage format 1601 -- Old storage format
1504 room._affiliations = frozen._affiliations; 1602 room._affiliations = frozen._affiliations;
1505 else 1603 else
1506 -- New storage format 1604 -- New storage format
1507 for jid, data in pairs(frozen) do 1605 for jid, data in pairs(frozen) do
1508 local node, host, resource = jid_split(jid); 1606 local _, host, resource = jid_split(jid);
1509 if host:sub(1,1) ~= "_" and not resource and type(data) == "string" then 1607 if host:sub(1,1) ~= "_" and not resource and type(data) == "string" then
1510 -- bare jid: affiliation 1608 -- bare jid: affiliation
1511 room._affiliations[jid] = data; 1609 room._affiliations[jid] = data;
1512 end 1610 end
1513 end 1611 end