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"; |