plugins/muc/muc.lib.lua
changeset 10438 8f709577fe8e
parent 10435 3db8372e203c
parent 10365 6e051bfca12d
child 10453 2e36a54906e4
equal deleted inserted replaced
10437:7777f25d5266 10438:8f709577fe8e
    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)
   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
   426 		event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden"));
   442 		event.origin.send(st.error_reply(event.stanza, "modify", "not-allowed", "Invisible Nicknames are forbidden"));
   427 		return true;
   443 		return true;
   428 	end
   444 	end
   429 end, 1);
   445 end, 1);
   430 
   446 
       
   447 module:hook("muc-occupant-pre-join", function(event)
       
   448 	local nick = jid_resource(event.occupant.nick);
       
   449 	if not resourceprep(nick, true) then -- strict
       
   450 		event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation"));
       
   451 		return true;
       
   452 	end
       
   453 end, 2);
       
   454 
       
   455 module:hook("muc-occupant-pre-change", function(event)
       
   456 	local nick = jid_resource(event.dest_occupant.nick);
       
   457 	if not resourceprep(nick, true) then -- strict
       
   458 		event.origin.send(st.error_reply(event.stanza, "modify", "jid-malformed", "Nickname must pass strict validation"));
       
   459 		return true;
       
   460 	end
       
   461 end, 2);
       
   462 
   431 function room_mt:handle_first_presence(origin, stanza)
   463 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;
   464 	local real_jid = stanza.attr.from;
   440 	local dest_jid = stanza.attr.to;
   465 	local dest_jid = stanza.attr.to;
   441 	local bare_jid = jid_bare(real_jid);
   466 	local bare_jid = jid_bare(real_jid);
   442 	if module:fire_event("muc-room-pre-create", {
   467 	if module:fire_event("muc-room-pre-create", {
   443 			room = self;
   468 			room = self;
   503 	local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc");
   528 	local muc_x = stanza:get_child("x", "http://jabber.org/protocol/muc");
   504 
   529 
   505 	if orig_occupant == nil and not muc_x and stanza.attr.type == nil then
   530 	if orig_occupant == nil and not muc_x and stanza.attr.type == nil then
   506 		module:log("debug", "Attempted join without <x>, possibly desynced");
   531 		module:log("debug", "Attempted join without <x>, possibly desynced");
   507 		origin.send(st.error_reply(stanza, "cancel", "item-not-found",
   532 		origin.send(st.error_reply(stanza, "cancel", "item-not-found",
   508 			"You must join the room before sending presence updates"));
   533 			"You are not currently connected to this chat"));
   509 		return true;
   534 		return true;
   510 	end
   535 	end
   511 
   536 
   512 	local is_first_dest_session;
   537 	local is_first_dest_session;
   513 	local dest_occupant;
   538 	local dest_occupant;
   611 				-- self:build_item_list(orig_occupant, x, false, dest_nick);
   636 				-- 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);
   637 				add_item(x, self:get_affiliation(bare_jid), orig_occupant.role, real_jid, dest_nick);
   613 				x:tag("status", {code = "303";}):up();
   638 				x:tag("status", {code = "303";}):up();
   614 				x:tag("status", {code = "110";}):up();
   639 				x:tag("status", {code = "110";}):up();
   615 				self:route_stanza(generated_unavail:add_child(x));
   640 				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
   641 				dest_nick = nil; -- set dest_nick to nil; so general populace doesn't see it for whole orig_occupant
   617 			end
   642 			end
   618 		end
   643 		end
   619 
   644 
   620 		self:save_occupant(orig_occupant);
   645 		self:save_occupant(orig_occupant);
   621 		self:publicise_occupant_status(orig_occupant, orig_x, dest_nick);
   646 		self:publicise_occupant_status(orig_occupant, orig_x, dest_nick);
   877 		self:save_occupant(occupant);
   902 		self:save_occupant(occupant);
   878 		occupants_updated[occupant] = true;
   903 		occupants_updated[occupant] = true;
   879 	end
   904 	end
   880 	for occupant in pairs(occupants_updated) do
   905 	for occupant in pairs(occupants_updated) do
   881 		self:publicise_occupant_status(occupant, x);
   906 		self:publicise_occupant_status(occupant, x);
   882 		module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; occupant = occupant;});
   907 		module:fire_event("muc-occupant-left", {
       
   908 				room = self;
       
   909 				nick = occupant.nick;
       
   910 				occupant = occupant;
       
   911 			});
   883 	end
   912 	end
   884 end
   913 end
   885 
   914 
   886 function room_mt:destroy(newjid, reason, password)
   915 function room_mt:destroy(newjid, reason, password)
   887 	local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
   916 	local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"})
   970 	local item = stanza.tags[1].tags[1];
   999 	local item = stanza.tags[1].tags[1];
   971 	local _aff = item.attr.affiliation;
  1000 	local _aff = item.attr.affiliation;
   972 	local _aff_rank = valid_affiliations[_aff or "none"];
  1001 	local _aff_rank = valid_affiliations[_aff or "none"];
   973 	local _rol = item.attr.role;
  1002 	local _rol = item.attr.role;
   974 	if _aff and _aff_rank and not _rol then
  1003 	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
  1004 		-- 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
  1005 		-- e.g. an admin can't ask for a list of owners
   977 		local affiliation_rank = valid_affiliations[affiliation or "none"];
  1006 		local affiliation_rank = valid_affiliations[affiliation or "none"];
   978 		if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank)
  1007 		if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank)
   979 		or (self:get_whois() == "anyone") then
  1008 		or (self:get_whois() == "anyone") then
   980 			local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
  1009 			local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
  1047 end
  1076 end
  1048 
  1077 
  1049 function room_mt:handle_groupchat_to_room(origin, stanza)
  1078 function room_mt:handle_groupchat_to_room(origin, stanza)
  1050 	local from = stanza.attr.from;
  1079 	local from = stanza.attr.from;
  1051 	local occupant = self:get_occupant_by_real_jid(from);
  1080 	local occupant = self:get_occupant_by_real_jid(from);
       
  1081 	if not stanza.attr.id then
       
  1082 		stanza.attr.id = new_id()
       
  1083 	end
  1052 	if module:fire_event("muc-occupant-groupchat", {
  1084 	if module:fire_event("muc-occupant-groupchat", {
  1053 		room = self; origin = origin; stanza = stanza; from = from; occupant = occupant;
  1085 		room = self; origin = origin; stanza = stanza; from = from; occupant = occupant;
  1054 	}) then return true; end
  1086 	}) then return true; end
  1055 	stanza.attr.from = occupant.nick;
  1087 	stanza.attr.from = occupant.nick;
  1056 	self:broadcast_message(stanza);
  1088 	self:broadcast_message(stanza);
  1295 	for nick, occupant in self:each_occupant() do -- luacheck: ignore 213
  1327 	for nick, occupant in self:each_occupant() do -- luacheck: ignore 213
  1296 		if occupant.bare_jid == jid or (
  1328 		if occupant.bare_jid == jid or (
  1297 			-- Outcast can be by host.
  1329 			-- Outcast can be by host.
  1298 			is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host
  1330 			is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host
  1299 		) then
  1331 		) then
  1300 			-- need to publcize in all cases; as affiliation in <item/> has changed.
  1332 			-- need to publicize in all cases; as affiliation in <item/> has changed.
  1301 			occupants_updated[occupant] = occupant.role;
  1333 			occupants_updated[occupant] = occupant.role;
  1302 			if occupant.role ~= role and (
  1334 			if occupant.role ~= role and (
  1303 				is_downgrade or
  1335 				is_downgrade or
  1304 				valid_roles[occupant.role or "none"] < role_rank -- upgrade
  1336 				valid_roles[occupant.role or "none"] < role_rank -- upgrade
  1305 			) then
  1337 			) then
  1322 
  1354 
  1323 	if next(occupants_updated) ~= nil then
  1355 	if next(occupants_updated) ~= nil then
  1324 		for occupant, old_role in pairs(occupants_updated) do
  1356 		for occupant, old_role in pairs(occupants_updated) do
  1325 			self:publicise_occupant_status(occupant, x, nil, actor, reason);
  1357 			self:publicise_occupant_status(occupant, x, nil, actor, reason);
  1326 			if occupant.role == nil then
  1358 			if occupant.role == nil then
  1327 				module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;});
  1359 				module:fire_event("muc-occupant-left", {
       
  1360 						room = self;
       
  1361 						nick = occupant.nick;
       
  1362 						occupant = occupant;
       
  1363 					});
  1328 			elseif is_semi_anonymous and
  1364 			elseif is_semi_anonymous and
  1329 				(old_role == "moderator" and occupant.role ~= "moderator") or
  1365 				(old_role == "moderator" and occupant.role ~= "moderator") or
  1330 				(old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status
  1366 				(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)
  1367 				-- Send everyone else's presences (as jid visibility has changed)
  1332 				for real_jid in occupant:each_session() do
  1368 				for real_jid in occupant:each_session() do
  1374 function room_mt:get_role(nick)
  1410 function room_mt:get_role(nick)
  1375 	local occupant = self:get_occupant_by_nick(nick);
  1411 	local occupant = self:get_occupant_by_nick(nick);
  1376 	return occupant and occupant.role or nil;
  1412 	return occupant and occupant.role or nil;
  1377 end
  1413 end
  1378 
  1414 
       
  1415 function room_mt:may_set_role(actor, occupant, role)
       
  1416 	local event = {
       
  1417 		room = self,
       
  1418 		actor = actor,
       
  1419 		occupant = occupant,
       
  1420 		role = role,
       
  1421 	};
       
  1422 
       
  1423 	module:fire_event("muc-pre-set-role", event);
       
  1424 	if event.allowed ~= nil then
       
  1425 		return event.allowed, event.error, event.condition;
       
  1426 	end
       
  1427 
       
  1428 	-- Can't do anything to other owners or admins
       
  1429 	local occupant_affiliation = self:get_affiliation(occupant.bare_jid);
       
  1430 	if occupant_affiliation == "owner" or occupant_affiliation == "admin" then
       
  1431 		return nil, "cancel", "not-allowed";
       
  1432 	end
       
  1433 
       
  1434 	-- If you are trying to give or take moderator role you need to be an owner or admin
       
  1435 	if occupant.role == "moderator" or role == "moderator" then
       
  1436 		local actor_affiliation = self:get_affiliation(actor);
       
  1437 		if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then
       
  1438 			return nil, "cancel", "not-allowed";
       
  1439 		end
       
  1440 	end
       
  1441 
       
  1442 	-- Need to be in the room and a moderator
       
  1443 	local actor_occupant = self:get_occupant_by_real_jid(actor);
       
  1444 	if not actor_occupant or actor_occupant.role ~= "moderator" then
       
  1445 		return nil, "cancel", "not-allowed";
       
  1446 	end
       
  1447 
       
  1448 	return true;
       
  1449 end
       
  1450 
  1379 function room_mt:set_role(actor, occupant_jid, role, reason)
  1451 function room_mt:set_role(actor, occupant_jid, role, reason)
  1380 	if not actor then return nil, "modify", "not-acceptable"; end
  1452 	if not actor then return nil, "modify", "not-acceptable"; end
  1381 
  1453 
  1382 	local occupant = self:get_occupant_by_nick(occupant_jid);
  1454 	local occupant = self:get_occupant_by_nick(occupant_jid);
  1383 	if not occupant then return nil, "modify", "item-not-found"; end
  1455 	if not occupant then return nil, "modify", "item-not-found"; end
  1388 	role = role ~= "none" and role or nil; -- coerces `role == false` to `nil`
  1460 	role = role ~= "none" and role or nil; -- coerces `role == false` to `nil`
  1389 
  1461 
  1390 	if actor == true then
  1462 	if actor == true then
  1391 		actor = nil -- So we can pass it safely to 'publicise_occupant_status' below
  1463 		actor = nil -- So we can pass it safely to 'publicise_occupant_status' below
  1392 	else
  1464 	else
  1393 		-- Can't do anything to other owners or admins
  1465 		local allowed, err, condition = self:may_set_role(actor, occupant, role)
  1394 		local occupant_affiliation = self:get_affiliation(occupant.bare_jid);
  1466 		if not allowed then
  1395 		if occupant_affiliation == "owner" or occupant_affiliation == "admin" then
  1467 			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
  1468 		end
  1412 	end
  1469 	end
  1413 
  1470 
  1414 	local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"});
  1471 	local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"});
  1415 	if not role then
  1472 	if not role then
  1416 		x:tag("status", {code = "307"}):up();
  1473 		x:tag("status", {code = "307"}):up();
  1417 	end
  1474 	end
       
  1475 
       
  1476 	local prev_role = occupant.role;
  1418 	occupant.role = role;
  1477 	occupant.role = role;
  1419 	self:save_occupant(occupant);
  1478 	self:save_occupant(occupant);
  1420 	self:publicise_occupant_status(occupant, x, nil, actor, reason);
  1479 	self:publicise_occupant_status(occupant, x, nil, actor, reason, prev_role);
  1421 	if role == nil then
  1480 	if role == nil then
  1422 		module:fire_event("muc-occupant-left", {room = self; nick = occupant.nick; occupant = occupant;});
  1481 		module:fire_event("muc-occupant-left", {
       
  1482 				room = self;
       
  1483 				nick = occupant.nick;
       
  1484 				occupant = occupant;
       
  1485 			});
  1423 	end
  1486 	end
  1424 	return true;
  1487 	return true;
  1425 end
  1488 end
  1426 
  1489 
  1427 local whois = module:require "muc/whois";
  1490 local whois = module:require "muc/whois";