From 2e9fab41473ed7b261f8d5076dfeac0752de1769 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Feb 2014 17:21:47 -0500 Subject: plugins/muc/mod_muc: Move Xep-0307 MUC unique to seperate file --- plugins/mod_muc_unique.lua | 11 +++++++++++ plugins/muc/mod_muc.lua | 31 ++++++++++--------------------- 2 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 plugins/mod_muc_unique.lua diff --git a/plugins/mod_muc_unique.lua b/plugins/mod_muc_unique.lua new file mode 100644 index 00000000..b27fcff6 --- /dev/null +++ b/plugins/mod_muc_unique.lua @@ -0,0 +1,11 @@ +-- XEP-0307: Unique Room Names for Multi-User Chat +local uuid_gen = require "util.uuid".generate; +module:add_feature "http://jabber.org/protocol/muc#unique" +module:hook("iq-get/host/http://jabber.org/protocol/muc#unique:unique", function() + local origin, stanza = event.origin, event.stanza; + origin.send(st.reply(stanza) + :tag("unique", {xmlns = "http://jabber.org/protocol/muc#unique"}) + :text(uuid_gen()) -- FIXME Random UUIDs can theoretically have collisions + ); + return true; +end,-1); diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index c514bafd..5a71ef75 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -31,7 +31,6 @@ local muc_new_room = muclib.new_room; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; -local uuid_gen = require "util.uuid".generate; local um_is_admin = require "core.usermanager".is_admin; local hosts = prosody.hosts; @@ -47,6 +46,7 @@ muclib.set_max_history_length(module:get_option_number("max_history_messages")); module:depends("disco"); module:add_identity("conference", "text", muc_name); module:add_feature("http://jabber.org/protocol/muc"); +module:depends "muc_unique" local function is_admin(jid) return um_is_admin(jid, module.host); @@ -136,25 +136,6 @@ module:hook("host-disco-items", function(event) end end); -local function handle_to_domain(event) - local origin, stanza = event.origin, event.stanza; - local type = stanza.attr.type; - if type == "error" or type == "result" then return; end - if stanza.name == "iq" and type == "get" then - local xmlns = stanza.tags[1].attr.xmlns; - local node = stanza.tags[1].attr.node; - if xmlns == "http://jabber.org/protocol/muc#unique" then - origin.send(st.reply(stanza):tag("unique", {xmlns = xmlns}):text(uuid_gen())); -- FIXME Random UUIDs can theoretically have collisions - else - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc - end - else - host_room:handle_stanza(origin, stanza); - --origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); - end - return true; -end - function stanza_handler(event) local origin, stanza = event.origin, event.stanza; local bare = jid_bare(stanza.attr.to); @@ -187,7 +168,15 @@ module:hook("presence/bare", stanza_handler, -1); module:hook("iq/full", stanza_handler, -1); module:hook("message/full", stanza_handler, -1); module:hook("presence/full", stanza_handler, -1); -module:hook("iq/host", handle_to_domain, -1); + +local function handle_to_domain(event) + local origin, stanza = event.origin, event.stanza; + local type = stanza.attr.type; + if type == "error" then return; end + host_room:handle_stanza(origin, stanza); + -- origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); + return true; +end module:hook("message/host", handle_to_domain, -1); module:hook("presence/host", handle_to_domain, -1); -- cgit v1.2.3 From e6b65c8d67e18a36af5f9a30cd2afc8c075814b8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Feb 2014 17:39:57 -0500 Subject: plugins/muc/muc.lib: Split out `send_history` into `parse_history` and `get_history` --- plugins/muc/muc.lib.lua | 95 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 33 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d09c768e..1e3a41e3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -9,6 +9,7 @@ local select = select; local pairs, ipairs = pairs, ipairs; +local gettime = os.time; local datetime = require "util.datetime"; local dataform = require "util.dataforms"; @@ -145,47 +146,75 @@ function room_mt:send_occupant_list(to) end end end -function room_mt:send_history(to, stanza) - local history = self._data['history']; -- send discussion history - if history then - local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc"); - local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); - local maxchars = history_tag and tonumber(history_tag.attr.maxchars); - if maxchars then maxchars = math.floor(maxchars); end +local function parse_history(stanza) + local x_tag = stanza:get_child("x", "http://jabber.org/protocol/muc"); + local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); + if not history_tag then + return nil, 20, nil + end - local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history); - if not history_tag then maxstanzas = 20; end + local maxchars = tonumber(history_tag.attr.maxchars); - local seconds = history_tag and tonumber(history_tag.attr.seconds); - if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end + local maxstanzas = tonumber(history_tag.attr.maxstanzas); - local since = history_tag and history_tag.attr.since; - if since then since = datetime.parse(since); since = since and datetime.datetime(since); end - if seconds and (not since or since < seconds) then since = seconds; end + -- messages received since the UTC datetime specified + local since = history_tag.attr.since; + if since then + since = datetime.parse(since); + end - local n = 0; - local charcount = 0; + -- messages received in the last "X" seconds. + local seconds = tonumber(history_tag.attr.seconds); + if seconds then + seconds = gettime() - seconds + if since then + since = math.max(since, seconds); + else + since = seconds; + end + end - for i=#history,1,-1 do - local entry = history[i]; - if maxchars then - if not entry.chars then - entry.stanza.attr.to = ""; - entry.chars = #tostring(entry.stanza); - end - charcount = charcount + entry.chars + #to; - if charcount > maxchars then break; end + return maxchars, maxstanzas, since +end +-- Get history for 'to' +function room_mt:get_history(to, maxchars, maxstanzas, since) + local history = self._data['history']; -- send discussion history + if not history then return end + local history_len = #history + + maxstanzas = maxstanzas or history_len + local n = 0; + local charcount = 0; + for i=history_len,1,-1 do + local entry = history[i]; + if maxchars then + if not entry.chars then + entry.stanza.attr.to = ""; + entry.chars = #tostring(entry.stanza); end - if since and since > entry.stamp then break; end - if n + 1 > maxstanzas then break; end - n = n + 1; - end - for i=#history-n+1,#history do - local msg = history[i].stanza; - msg.attr.to = to; - self:_route_stanza(msg); + charcount = charcount + entry.chars + #to; + if charcount > maxchars then break; end end + if since and since > entry.stamp then break; end + if n + 1 > maxstanzas then break; end + n = n + 1; + end + + local i = history_len-n+1 + return function() + if i > history_len then return nil end + local entry = history[i] + local msg = entry.stanza + msg.attr.to = to; + i = i + 1 + return msg + end +end +function room_mt:send_history(to, stanza) + local maxchars, maxstanzas, since = parse_history(stanza) + for msg in self:get_history(to, maxchars, maxstanzas, since) do + self:_route_stanza(msg); end end function room_mt:send_subject(to) -- cgit v1.2.3 From 2411decc0c9128750099d8ab1520c4ede4e3b751 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Feb 2014 14:36:49 -0500 Subject: plugins/muc/muc.lib: Split up `handle_to_room` into smaller handlers (thanks sysko) --- plugins/muc/muc.lib.lua | 342 ++++++++++++++++++++++++++---------------------- 1 file changed, 186 insertions(+), 156 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1e3a41e3..0b82f91c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -771,187 +771,207 @@ function room_mt:destroy(newjid, reason, password) module:fire_event("muc-room-destroyed", { room = self }); end -function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc +function room_mt:handle_iq_to_room(origin, stanza) local type = stanza.attr.type; local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; - if stanza.name == "iq" then - if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_info(stanza)); - elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_items(stanza)); - elseif xmlns == "http://jabber.org/protocol/muc#admin" then - local actor = stanza.attr.from; - local affiliation = self:get_affiliation(actor); - local current_nick = self._jid_nick[actor]; - local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); - local item = stanza.tags[1].tags[1]; - if item and item.name == "item" then - if type == "set" then - local callback = function() origin.send(st.reply(stanza)); end - if item.attr.jid then -- Validate provided JID - item.attr.jid = jid_prep(item.attr.jid); - if not item.attr.jid then - origin.send(st.error_reply(stanza, "modify", "jid-malformed")); - return; - end - end - if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation - local occupant = self._occupants[self.jid.."/"..item.attr.nick]; - if occupant then item.attr.jid = occupant.jid; end - elseif not item.attr.nick and item.attr.jid then - local nick = self._jid_nick[item.attr.jid]; - if nick then item.attr.nick = select(3, jid_split(nick)); end - end - local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; - if item.attr.affiliation and item.attr.jid and not item.attr.role then - local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - elseif item.attr.role and item.attr.nick and not item.attr.affiliation then - local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); + if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then + origin.send(self:get_disco_info(stanza)); + elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then + origin.send(self:get_disco_items(stanza)); + elseif xmlns == "http://jabber.org/protocol/muc#admin" then + local actor = stanza.attr.from; + local affiliation = self:get_affiliation(actor); + local current_nick = self._jid_nick[actor]; + local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); + local item = stanza.tags[1].tags[1]; + if item and item.name == "item" then + if type == "set" then + local callback = function() origin.send(st.reply(stanza)); end + if item.attr.jid then -- Validate provided JID + item.attr.jid = jid_prep(item.attr.jid); + if not item.attr.jid then + origin.send(st.error_reply(stanza, "modify", "jid-malformed")); + return; end - elseif type == "get" then - local _aff = item.attr.affiliation; - local _rol = item.attr.role; - if _aff and not _rol then - if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for jid, affiliation in pairs(self._affiliations) do - if affiliation == _aff then - reply:tag("item", {affiliation = _aff, jid = jid}):up(); - end + end + if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation + local occupant = self._occupants[self.jid.."/"..item.attr.nick]; + if occupant then item.attr.jid = occupant.jid; end + elseif not item.attr.nick and item.attr.jid then + local nick = self._jid_nick[item.attr.jid]; + if nick then item.attr.nick = select(3, jid_split(nick)); end + end + local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; + if item.attr.affiliation and item.attr.jid and not item.attr.role then + local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + elseif item.attr.role and item.attr.nick and not item.attr.affiliation then + local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + end + elseif type == "get" then + local _aff = item.attr.affiliation; + local _rol = item.attr.role; + if _aff and not _rol then + if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for jid, affiliation in pairs(self._affiliations) do + if affiliation == _aff then + reply:tag("item", {affiliation = _aff, jid = jid}):up(); end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); end - elseif _rol and not _aff then - if role == "moderator" then - -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? - if _rol == "none" then _rol = nil; end - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for occupant_jid, occupant in pairs(self._occupants) do - if occupant.role == _rol then - reply:tag("item", { - nick = select(3, jid_split(occupant_jid)), - role = _rol or "none", - affiliation = occupant.affiliation or "none", - jid = occupant.jid - }):up(); - end + origin.send(reply); + else + origin.send(st.error_reply(stanza, "auth", "forbidden")); + end + elseif _rol and not _aff then + if role == "moderator" then + -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? + if _rol == "none" then _rol = nil; end + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for occupant_jid, occupant in pairs(self._occupants) do + if occupant.role == _rol then + reply:tag("item", { + nick = select(3, jid_split(occupant_jid)), + role = _rol or "none", + affiliation = occupant.affiliation or "none", + jid = occupant.jid + }):up(); end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); end + origin.send(reply); else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); + origin.send(st.error_reply(stanza, "auth", "forbidden")); end + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "bad-request")); end - elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then - if self:get_affiliation(stanza.attr.from) ~= "owner" then - origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); - elseif stanza.attr.type == "get" then - self:send_form(origin, stanza); - elseif stanza.attr.type == "set" then - local child = stanza.tags[1].tags[1]; - if not child then - origin.send(st.error_reply(stanza, "modify", "bad-request")); - elseif child.name == "destroy" then - local newjid = child.attr.jid; - local reason, password; - for _,tag in ipairs(child.tags) do - if tag.name == "reason" then - reason = #tag.tags == 0 and tag[1]; - elseif tag.name == "password" then - password = #tag.tags == 0 and tag[1]; - end + elseif type == "set" or type == "get" then + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + end + elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then + if self:get_affiliation(stanza.attr.from) ~= "owner" then + origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); + elseif stanza.attr.type == "get" then + self:send_form(origin, stanza); + elseif stanza.attr.type == "set" then + local child = stanza.tags[1].tags[1]; + if not child then + origin.send(st.error_reply(stanza, "modify", "bad-request")); + elseif child.name == "destroy" then + local newjid = child.attr.jid; + local reason, password; + for _,tag in ipairs(child.tags) do + if tag.name == "reason" then + reason = #tag.tags == 0 and tag[1]; + elseif tag.name == "password" then + password = #tag.tags == 0 and tag[1]; end - self:destroy(newjid, reason, password); - origin.send(st.reply(stanza)); - else - self:process_form(origin, stanza); end + self:destroy(newjid, reason, password); + origin.send(st.reply(stanza)); + else + self:process_form(origin, stanza); end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end - elseif stanza.name == "message" and type == "groupchat" then + elseif type == "set" or type == "get" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end +end + +function room_mt:handle_groupchat_to_room(origin, stanza) + local from = stanza.attr.from; + local current_nick = self._jid_nick[from]; + local occupant = self._occupants[current_nick]; + if not occupant then -- not in room + origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + elseif occupant.role == "visitor" then + origin.send(st.error_reply(stanza, "auth", "forbidden")); + else local from = stanza.attr.from; - local current_nick = self._jid_nick[from]; - local occupant = self._occupants[current_nick]; - if not occupant then -- not in room - origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); - elseif occupant.role == "visitor" then - origin.send(st.error_reply(stanza, "auth", "forbidden")); - else - local from = stanza.attr.from; - stanza.attr.from = current_nick; - local subject = stanza:get_child_text("subject"); - if subject then - if occupant.role == "moderator" or - ( self._data.changesubject and occupant.role == "participant" ) then -- and participant - self:set_subject(current_nick, subject); - else - stanza.attr.from = from; - origin.send(st.error_reply(stanza, "auth", "forbidden")); - end + stanza.attr.from = current_nick; + local subject = stanza:get_child_text("subject"); + if subject then + if occupant.role == "moderator" or + ( self._data.changesubject and occupant.role == "participant" ) then -- and participant + self:set_subject(current_nick, subject); else - self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); + stanza.attr.from = from; + origin.send(st.error_reply(stanza, "auth", "forbidden")); end - stanza.attr.from = from; + else + self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); end - elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then - local current_nick = self._jid_nick[stanza.attr.from]; - log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick + stanza.attr.from = from; + end +end + + +function room_mt:handle_kickable_to_room(origin, stanza) + local current_nick = self._jid_nick[stanza.attr.from]; + log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); + self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable +end + +-- hack - some buggy clients send presence updates to the room rather than their nick +function room_mt:handle_presence_to_room(origin, stanza) + local type = stanza.attr.type; + local current_nick = self._jid_nick[stanza.attr.from]; + if current_nick then local to = stanza.attr.to; - local current_nick = self._jid_nick[stanza.attr.from]; - if current_nick then - stanza.attr.to = current_nick; - self:handle_to_occupant(origin, stanza); - stanza.attr.to = to; - elseif type ~= "error" and type ~= "result" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + stanza.attr.to = current_nick; + self:handle_to_occupant(origin, stanza); + stanza.attr.to = to; + elseif type ~= "error" and type ~= "result" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end +end + +function room_mt:handle_invite_to_room(origin, stanza, payload) + local _from, _to = stanza.attr.from, stanza.attr.to; + local _invitee = jid_prep(payload.attr.to); + if _invitee then + local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; + local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('invite', {from=_from}) + :tag('reason'):text(_reason or ""):up() + :up(); + if self:get_password() then + invite:tag("password"):text(self:get_password()):up(); + end + invite:up() + :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this + :text(_reason or "") + :up() + :tag('body') -- Add a plain message for clients which don't support invites + :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) + :up(); + if self:get_members_only() and not self:get_affiliation(_invitee) then + log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); + self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) end - elseif stanza.name == "message" and not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 + self:_route_stanza(invite); + else + origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); + end +end + +function room_mt:handle_message_to_room(origin, stanza) + local type = stanza.attr.type; + if type == "groupchat" then + return self:handle_groupchat_to_room(origin, stanza) + elseif type == "error" and is_kickable_error(stanza) then + return self:handle_kickable_to_room(origin, stanza) + elseif not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then local x = stanza.tags[1]; local payload = (#x.tags == 1 and x.tags[1]); if payload and payload.name == "invite" and payload.attr.to then - local _from, _to = stanza.attr.from, stanza.attr.to; - local _invitee = jid_prep(payload.attr.to); - if _invitee then - local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; - local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) - :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) - :tag('invite', {from=_from}) - :tag('reason'):text(_reason or ""):up() - :up(); - if self:get_password() then - invite:tag("password"):text(self:get_password()):up(); - end - invite:up() - :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this - :text(_reason or "") - :up() - :tag('body') -- Add a plain message for clients which don't support invites - :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) - :up(); - if self:get_members_only() and not self:get_affiliation(_invitee) then - log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); - self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) - end - self:_route_stanza(invite); - else - origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); - end + return self:handle_invite_to_room(origin, stanza, payload) else origin.send(st.error_reply(stanza, "cancel", "bad-request")); end @@ -961,6 +981,16 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha end end +function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc + if stanza.name == "iq" then + return self:handle_iq_to_room(origin, stanza) + elseif stanza.name == "message" then + return self:handle_message_to_room(origin, stanza) + elseif stanza.name == "presence" then + return self:handle_presence_to_room(origin, stanza) + end +end + function room_mt:handle_stanza(origin, stanza) local to_node, to_host, to_resource = jid_split(stanza.attr.to); if to_resource then -- cgit v1.2.3 From 81f26ff7290a30f1e16d3f6ca35e5ffe7ec9a0f2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Feb 2014 16:50:18 -0500 Subject: plugins/muc/muc.lib: Factor `handle_to_occupant` out into many functions --- plugins/muc/muc.lib.lua | 455 ++++++++++++++++++++++++++++-------------------- 1 file changed, 269 insertions(+), 186 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 0b82f91c..7cf857a4 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -404,212 +404,295 @@ local function deconstruct_stanza_id(room, stanza) end end +function room_mt:handle_presence_error_to_occupant(origin, stanza) + local current_nick = self._jid_nick[stanza.attr.from]; + if not current_nick then + return true -- discard + end + log("debug", "kicking %s from %s", current_nick, self.jid); + return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)) +end -function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc +function room_mt:handle_unavailable_to_occupant(origin, stanza) + local from = stanza.attr.from; + local current_nick = self._jid_nick[from]; + if not current_nick then + return true; -- discard + end + local pr = get_filtered_presence(stanza); + pr.attr.from = current_nick; + log("debug", "%s leaving %s", current_nick, self.jid); + self._jid_nick[from] = nil; + local occupant = self._occupants[current_nick]; + local new_jid = next(occupant.sessions); + if new_jid == from then new_jid = next(occupant.sessions, new_jid); end + if new_jid then + local jid = occupant.jid; + occupant.jid = new_jid; + occupant.sessions[from] = nil; + pr.attr.to = from; + pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up() + :tag("status", {code='110'}):up(); + self:_route_stanza(pr); + if jid ~= new_jid then + pr = st.clone(occupant.sessions[new_jid]) + :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"}); + pr.attr.from = current_nick; + self:broadcast_except_nick(pr, current_nick); + end + else + occupant.role = 'none'; + self:broadcast_presence(pr, from); + self._occupants[current_nick] = nil; + end + return true; +end + +function room_mt:handle_occupant_presence(origin, stanza) + local from = stanza.attr.from; + local pr = get_filtered_presence(stanza); + local current_nick = stanza.attr.to + pr.attr.from = current_nick; + log("debug", "%s broadcasted presence", current_nick); + self._occupants[current_nick].sessions[from] = pr; + self:broadcast_presence(pr, from); + return true; +end + +function room_mt:handle_change_nick(origin, stanza, current_nick, to) + local from = stanza.attr.from; + local occupant = self._occupants[current_nick]; + local is_multisession = next(occupant.sessions, next(occupant.sessions)); + if self._occupants[to] or is_multisession then + log("debug", "%s couldn't change nick", current_nick); + local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + reply.tags[1].attr.code = "409"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + else + local data = self._occupants[current_nick]; + local to_nick = select(3, jid_split(to)); + log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); + local p = st.presence({type='unavailable', from=current_nick}); + self:broadcast_presence(p, from, '303', to_nick); + self._occupants[current_nick] = nil; + self._occupants[to] = data; + self._jid_nick[from] = to; + local pr = get_filtered_presence(stanza); + pr.attr.from = to; + self._occupants[to].sessions[from] = pr; + self:broadcast_presence(pr, from); + return true; + end +end + +function room_mt:handle_join(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + log("debug", "%s joining as %s", from, to); + if not next(self._affiliations) then -- new room, no owners + self._affiliations[jid_bare(from)] = "owner"; + if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then + self.locked = nil; -- Older groupchat protocol doesn't lock + end + elseif self.locked then -- Deny entry + origin.send(st.error_reply(stanza, "cancel", "item-not-found")); + return true; + end + local affiliation = self:get_affiliation(from); + local role = self:get_default_role(affiliation) + if role then -- new occupant + local is_merge = not not self._occupants[to] + if not is_merge then + self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}}; + else + self._occupants[to].sessions[from] = get_filtered_presence(stanza); + end + self._jid_nick[from] = to; + self:send_occupant_list(from); + local pr = get_filtered_presence(stanza); + pr.attr.from = to; + pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up(); + if not is_merge then + self:broadcast_except_nick(pr, to); + end + pr:tag("status", {code='110'}):up(); + if self._data.whois == 'anyone' then + pr:tag("status", {code='100'}):up(); + end + if self.locked then + pr:tag("status", {code='201'}):up(); + end + pr.attr.to = from; + self:_route_stanza(pr); + self:send_history(from, stanza); + self:send_subject(from); + return true; + elseif not affiliation then -- registration required for entering members-only room + local reply = st.error_reply(stanza, "auth", "registration-required"):up(); + reply.tags[1].attr.code = "407"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + else -- banned + local reply = st.error_reply(stanza, "auth", "forbidden"):up(); + reply.tags[1].attr.code = "403"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end +end + +function room_mt:handle_available_to_occupant(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + local current_nick = self._jid_nick[from]; + if current_nick then + --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence + if current_nick == to then -- simple presence + return self:handle_occupant_presence(origin, stanza) + else -- change nick + return self:handle_change_nick(origin, stanza, current_nick, to) + end + --else -- possible rejoin + -- log("debug", "%s had connection replaced", current_nick); + -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}) + -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable + -- self:handle_to_occupant(origin, stanza); -- resend available + --end + else -- enter room + local new_nick = to; + if self._occupants[to] then + if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then + new_nick = nil; + end + end + local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); + password = password and password:get_child("password", "http://jabber.org/protocol/muc"); + password = password and password[1] ~= "" and password[1]; + if self:get_password() and self:get_password() ~= password then + log("debug", "%s couldn't join due to invalid password: %s", from, to); + local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); + reply.tags[1].attr.code = "401"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + elseif not new_nick then + log("debug", "%s couldn't join due to nick conflict: %s", from, to); + local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + reply.tags[1].attr.code = "409"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + else + return self:handle_join(origin, stanza) + end + end +end + +function room_mt:handle_presence_to_occupant(origin, stanza) + local type = stanza.attr.type; + if type == "error" then -- error, kick em out! + return self:handle_presence_error_to_occupant(origin, stanza) + elseif type == "unavailable" then -- unavailable + return self:handle_unavailable_to_occupant(origin, stanza) + elseif not type then -- available + return self:handle_available_to_occupant(origin, stanza) + elseif type ~= 'result' then -- bad type + if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences + origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? + end + end + return true; +end + +function room_mt:handle_private(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local room = jid_bare(to); local current_nick = self._jid_nick[from]; local type = stanza.attr.type; - log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); - if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end - if stanza.name == "presence" then - local pr = get_filtered_presence(stanza); - pr.attr.from = current_nick; - if type == "error" then -- error, kick em out! - if current_nick then - log("debug", "kicking %s from %s", current_nick, room); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); + local o_data = self._occupants[to]; + if o_data then + log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); + if stanza.name == "iq" then + local id = stanza.attr.id; + if stanza.attr.type == "get" or stanza.attr.type == "set" then + stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); + else + stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); end - elseif type == "unavailable" then -- unavailable - if current_nick then - log("debug", "%s leaving %s", current_nick, room); - self._jid_nick[from] = nil; - local occupant = self._occupants[current_nick]; - local new_jid = next(occupant.sessions); - if new_jid == from then new_jid = next(occupant.sessions, new_jid); end - if new_jid then - local jid = occupant.jid; - occupant.jid = new_jid; - occupant.sessions[from] = nil; - pr.attr.to = from; - pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up() - :tag("status", {code='110'}):up(); - self:_route_stanza(pr); - if jid ~= new_jid then - pr = st.clone(occupant.sessions[new_jid]) - :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"}); - pr.attr.from = current_nick; - self:broadcast_except_nick(pr, current_nick); - end - else - occupant.role = 'none'; - self:broadcast_presence(pr, from); - self._occupants[current_nick] = nil; - end + if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then + stanza.attr.to = jid_bare(stanza.attr.to); end - elseif not type then -- available - if current_nick then - --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence - if current_nick == to then -- simple presence - log("debug", "%s broadcasted presence", current_nick); - self._occupants[current_nick].sessions[from] = pr; - self:broadcast_presence(pr, from); - else -- change nick - local occupant = self._occupants[current_nick]; - local is_multisession = next(occupant.sessions, next(occupant.sessions)); - if self._occupants[to] or is_multisession then - log("debug", "%s couldn't change nick", current_nick); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); - reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - else - local data = self._occupants[current_nick]; - local to_nick = select(3, jid_split(to)); - if to_nick then - log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); - local p = st.presence({type='unavailable', from=current_nick}); - self:broadcast_presence(p, from, '303', to_nick); - self._occupants[current_nick] = nil; - self._occupants[to] = data; - self._jid_nick[from] = to; - pr.attr.from = to; - self._occupants[to].sessions[from] = pr; - self:broadcast_presence(pr, from); - else - --TODO malformed-jid - end - end - end - --else -- possible rejoin - -- log("debug", "%s had connection replaced", current_nick); - -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}) - -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable - -- self:handle_to_occupant(origin, stanza); -- resend available - --end - else -- enter room - local new_nick = to; - local is_merge; - if self._occupants[to] then - if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then - new_nick = nil; - end - is_merge = true; - end - local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); - password = password and password:get_child("password", "http://jabber.org/protocol/muc"); - password = password and password[1] ~= "" and password[1]; - if self:get_password() and self:get_password() ~= password then - log("debug", "%s couldn't join due to invalid password: %s", from, to); - local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); - reply.tags[1].attr.code = "401"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - elseif not new_nick then - log("debug", "%s couldn't join due to nick conflict: %s", from, to); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); - reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - else - log("debug", "%s joining as %s", from, to); - if not next(self._affiliations) then -- new room, no owners - self._affiliations[jid_bare(from)] = "owner"; - if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then - self.locked = nil; -- Older groupchat protocol doesn't lock - end - elseif self.locked then -- Deny entry - origin.send(st.error_reply(stanza, "cancel", "item-not-found")); - return; - end - local affiliation = self:get_affiliation(from); - local role = self:get_default_role(affiliation) - if role then -- new occupant - if not is_merge then - self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}}; - else - self._occupants[to].sessions[from] = get_filtered_presence(stanza); - end - self._jid_nick[from] = to; - self:send_occupant_list(from); - pr.attr.from = to; - pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up(); - if not is_merge then - self:broadcast_except_nick(pr, to); - end - pr:tag("status", {code='110'}):up(); - if self._data.whois == 'anyone' then - pr:tag("status", {code='100'}):up(); - end - if self.locked then - pr:tag("status", {code='201'}):up(); - end - pr.attr.to = from; - self:_route_stanza(pr); - self:send_history(from, stanza); - self:send_subject(from); - elseif not affiliation then -- registration required for entering members-only room - local reply = st.error_reply(stanza, "auth", "registration-required"):up(); - reply.tags[1].attr.code = "407"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - else -- banned - local reply = st.error_reply(stanza, "auth", "forbidden"):up(); - reply.tags[1].attr.code = "403"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - end - end + if stanza.attr.id then + self:_route_stanza(stanza); end - elseif type ~= 'result' then -- bad type - if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences - origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? + stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; + else -- message + stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); + stanza.attr.from = current_nick; + for jid in pairs(o_data.sessions) do + stanza.attr.to = jid; + self:_route_stanza(stanza); end + stanza.attr.from, stanza.attr.to = from, to; end - elseif not current_nick then -- not in room - if (type == "error" or type == "result") and stanza.name == "iq" then + elseif type ~= "error" and type ~= "result" then -- recipient not in room + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + end + return true; +end + +function room_mt:handle_iq_to_occupant(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + local current_nick = self._jid_nick[from]; + if not current_nick then + local type = stanza.attr.type; + if (type == "error" or type == "result") then local id = stanza.attr.id; stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); if stanza.attr.id then self:_route_stanza(stanza); end stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - elseif type ~= "error" then + else origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); end - elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM + return true; + else + return self:handle_private(origin, stanza) + end +end + +function room_mt:handle_message_to_occupant(origin, stanza) + local current_nick = self._jid_nick[stanza.attr.from]; + local type = stanza.attr.type; + if not current_nick then -- not in room + if type ~= "error" then + origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + end + return true; + end + if type == "groupchat" then -- groupchat messages not allowed in PM origin.send(st.error_reply(stanza, "modify", "bad-request")); - elseif current_nick and stanza.name == "message" and type == "error" and is_kickable_error(stanza) then + return true; + elseif type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable + return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable else -- private stanza - local o_data = self._occupants[to]; - if o_data then - log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); - if stanza.name == "iq" then - local id = stanza.attr.id; - if stanza.attr.type == "get" or stanza.attr.type == "set" then - stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); - else - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - end - if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then - stanza.attr.to = jid_bare(stanza.attr.to); - end - if stanza.attr.id then - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - else -- message - stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); - stanza.attr.from = current_nick; - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to = from, to; - end - elseif type ~= "error" and type ~= "result" then -- recipient not in room - origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); - end + return self:handle_private(origin, stanza) + end +end + +function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc + local from, to = stanza.attr.from, stanza.attr.to; + local room = jid_bare(to); + local current_nick = self._jid_nick[from]; + local type = stanza.attr.type; + log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); + if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end + if stanza.name == "presence" then + return self:handle_presence_to_occupant(origin, stanza) + elseif stanza.name == "iq" then + return self:handle_iq_to_occupant(origin, stanza) + elseif stanza.name == "message" then + return self:handle_message_to_occupant(origin, stanza) end end -- cgit v1.2.3 From 481464e64c46d8e81dde06fa4d05d44f82c9078b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 15:48:26 -0500 Subject: plugins/muc/muc.lib: Split out the room iq handler into functions --- plugins/muc/muc.lib.lua | 216 +++++++++++++++++++++++++++++------------------- 1 file changed, 129 insertions(+), 87 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7cf857a4..96caa2eb 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -854,114 +854,156 @@ function room_mt:destroy(newjid, reason, password) module:fire_event("muc-room-destroyed", { room = self }); end +function room_mt:handle_admin_item_set_command(origin, stanza) + local item = stanza.tags[1].tags[1]; + if item.attr.jid then -- Validate provided JID + item.attr.jid = jid_prep(item.attr.jid); + if not item.attr.jid then + origin.send(st.error_reply(stanza, "modify", "jid-malformed")); + return true; + end + end + if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation + local occupant = self._occupants[self.jid.."/"..item.attr.nick]; + if occupant then item.attr.jid = occupant.jid; end + elseif not item.attr.nick and item.attr.jid then + local nick = self._jid_nick[item.attr.jid]; + if nick then item.attr.nick = select(3, jid_split(nick)); end + end + local actor = stanza.attr.from; + local callback = function() origin.send(st.reply(stanza)); end + local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; + if item.attr.affiliation and item.attr.jid and not item.attr.role then + local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + return true; + elseif item.attr.role and item.attr.nick and not item.attr.affiliation then + local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + return true; + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; + end +end + +function room_mt:handle_admin_item_get_command(origin, stanza) + local actor = stanza.attr.from; + local affiliation = self:get_affiliation(actor); + local current_nick = self._jid_nick[actor]; + local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); + local item = stanza.tags[1].tags[1]; + local _aff = item.attr.affiliation; + local _rol = item.attr.role; + if _aff and not _rol then + if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for jid, affiliation in pairs(self._affiliations) do + if affiliation == _aff then + reply:tag("item", {affiliation = _aff, jid = jid}):up(); + end + end + origin.send(reply); + return true; + else + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end + elseif _rol and not _aff then + if role == "moderator" then + -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? + if _rol == "none" then _rol = nil; end + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for occupant_jid, occupant in pairs(self._occupants) do + if occupant.role == _rol then + reply:tag("item", { + nick = select(3, jid_split(occupant_jid)), + role = _rol or "none", + affiliation = occupant.affiliation or "none", + jid = occupant.jid + }):up(); + end + end + origin.send(reply); + return true; + else + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; + end +end + +function room_mt:handle_owner_query_get_to_room(origin, stanza) + if self:get_affiliation(stanza.attr.from) ~= "owner" then + origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); + return true; + end + + self:send_form(origin, stanza); + return true; +end +function room_mt:handle_owner_query_set_to_room(origin, stanza) + if self:get_affiliation(stanza.attr.from) ~= "owner" then + origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); + return true; + end + + local child = stanza.tags[1].tags[1]; + if not child then + origin.send(st.error_reply(stanza, "modify", "bad-request")); + return true; + elseif child.name == "destroy" then + local newjid = child.attr.jid; + local reason, password; + for _,tag in ipairs(child.tags) do + if tag.name == "reason" then + reason = #tag.tags == 0 and tag[1]; + elseif tag.name == "password" then + password = #tag.tags == 0 and tag[1]; + end + end + self:destroy(newjid, reason, password); + origin.send(st.reply(stanza)); + return true; + else + self:process_form(origin, stanza); + return true; + end +end + function room_mt:handle_iq_to_room(origin, stanza) local type = stanza.attr.type; local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then origin.send(self:get_disco_info(stanza)); + return true; elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then origin.send(self:get_disco_items(stanza)); + return true; elseif xmlns == "http://jabber.org/protocol/muc#admin" then - local actor = stanza.attr.from; - local affiliation = self:get_affiliation(actor); - local current_nick = self._jid_nick[actor]; - local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); local item = stanza.tags[1].tags[1]; if item and item.name == "item" then if type == "set" then - local callback = function() origin.send(st.reply(stanza)); end - if item.attr.jid then -- Validate provided JID - item.attr.jid = jid_prep(item.attr.jid); - if not item.attr.jid then - origin.send(st.error_reply(stanza, "modify", "jid-malformed")); - return; - end - end - if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation - local occupant = self._occupants[self.jid.."/"..item.attr.nick]; - if occupant then item.attr.jid = occupant.jid; end - elseif not item.attr.nick and item.attr.jid then - local nick = self._jid_nick[item.attr.jid]; - if nick then item.attr.nick = select(3, jid_split(nick)); end - end - local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; - if item.attr.affiliation and item.attr.jid and not item.attr.role then - local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - elseif item.attr.role and item.attr.nick and not item.attr.affiliation then - local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); - end + return self:handle_admin_item_set_command(origin, stanza) elseif type == "get" then - local _aff = item.attr.affiliation; - local _rol = item.attr.role; - if _aff and not _rol then - if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for jid, affiliation in pairs(self._affiliations) do - if affiliation == _aff then - reply:tag("item", {affiliation = _aff, jid = jid}):up(); - end - end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); - end - elseif _rol and not _aff then - if role == "moderator" then - -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? - if _rol == "none" then _rol = nil; end - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for occupant_jid, occupant in pairs(self._occupants) do - if occupant.role == _rol then - reply:tag("item", { - nick = select(3, jid_split(occupant_jid)), - role = _rol or "none", - affiliation = occupant.affiliation or "none", - jid = occupant.jid - }):up(); - end - end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); - end - else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); - end + return self:handle_admin_item_get_command(origin, stanza) end elseif type == "set" or type == "get" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; end elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then - if self:get_affiliation(stanza.attr.from) ~= "owner" then - origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); - elseif stanza.attr.type == "get" then - self:send_form(origin, stanza); + if stanza.attr.type == "get" then + return self:handle_owner_query_get_to_room(origin, stanza) elseif stanza.attr.type == "set" then - local child = stanza.tags[1].tags[1]; - if not child then - origin.send(st.error_reply(stanza, "modify", "bad-request")); - elseif child.name == "destroy" then - local newjid = child.attr.jid; - local reason, password; - for _,tag in ipairs(child.tags) do - if tag.name == "reason" then - reason = #tag.tags == 0 and tag[1]; - elseif tag.name == "password" then - password = #tag.tags == 0 and tag[1]; - end - end - self:destroy(newjid, reason, password); - origin.send(st.reply(stanza)); - else - self:process_form(origin, stanza); - end + return self:handle_owner_query_set_to_room(origin, stanza) end elseif type == "set" or type == "get" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + return true; end end -- cgit v1.2.3 From 126212dee38bcfdd5f516bbefceecf2621b0ec95 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 16:30:43 -0500 Subject: plugins/muc/muc.lib: Refactor _to_occupant handlers --- plugins/muc/muc.lib.lua | 99 +++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 96caa2eb..54511ede 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -603,65 +603,45 @@ function room_mt:handle_presence_to_occupant(origin, stanza) return true; end -function room_mt:handle_private(origin, stanza) +function room_mt:handle_iq_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; local type = stanza.attr.type; - local o_data = self._occupants[to]; - if o_data then - log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); - if stanza.name == "iq" then - local id = stanza.attr.id; - if stanza.attr.type == "get" or stanza.attr.type == "set" then - stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); - else - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - end - if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then - stanza.attr.to = jid_bare(stanza.attr.to); - end - if stanza.attr.id then - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - else -- message - stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); - stanza.attr.from = current_nick; - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to = from, to; + local id = stanza.attr.id; + if (type == "error" or type == "result") then + stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); + log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); + if stanza.attr.id then + self:_route_stanza(stanza); end - elseif type ~= "error" and type ~= "result" then -- recipient not in room - origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); - end - return true; -end - -function room_mt:handle_iq_to_occupant(origin, stanza) - local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; - if not current_nick then - local type = stanza.attr.type; - if (type == "error" or type == "result") then - local id = stanza.attr.id; - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - if stanza.attr.id then - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - else + stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; + return true; + else -- Type is "get" or "set" + local current_nick = self._jid_nick[from]; + if not current_nick then origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + return true; + end + local o_data = self._occupants[to]; + if not o_data then -- recipient not in room + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + return true; + end + stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); + log("debug", "%s sent private iq stanza to %s (%s)", from, to, o_data.jid); + if stanza.tags[1].attr.xmlns == 'vcard-temp' then + stanza.attr.to = jid_bare(stanza.attr.to); end + if stanza.attr.id then + self:_route_stanza(stanza); + end + stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; - else - return self:handle_private(origin, stanza) end end function room_mt:handle_message_to_occupant(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; + local from, to = stanza.attr.from, stanza.attr.to; + local current_nick = self._jid_nick[from]; local type = stanza.attr.type; if not current_nick then -- not in room if type ~= "error" then @@ -674,17 +654,30 @@ function room_mt:handle_message_to_occupant(origin, stanza) return true; elseif type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - else -- private stanza - return self:handle_private(origin, stanza) + self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable + return true; end + + local o_data = self._occupants[to]; + if not o_data then + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + return true; + end + log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid); + stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); + stanza.attr.from = current_nick; + for jid in pairs(o_data.sessions) do + stanza.attr.to = jid; + self:_route_stanza(stanza); + end + stanza.attr.from, stanza.attr.to = from, to; + return true; end function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc local from, to = stanza.attr.from, stanza.attr.to; local room = jid_bare(to); local current_nick = self._jid_nick[from]; - local type = stanza.attr.type; log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end if stanza.name == "presence" then -- cgit v1.2.3 From 87f32d9c9a8f07c453b7adec9253a3a2e6630716 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 17:17:01 -0500 Subject: plugins/muc/muc.lib: Move (de)construct_stanza_id into `handle_iq_to_occupant` It is the only place they were used; and I left the old function names in as comments. One reason for doing this was to reduce accesses to _occupants; which may be in a database in future revisions --- plugins/muc/muc.lib.lua | 57 +++++++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 54511ede..ce4fa923 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -380,30 +380,6 @@ function room_mt:get_whois() return self._data.whois; end -local function construct_stanza_id(room, stanza) - local from_jid, to_nick = stanza.attr.from, stanza.attr.to; - local from_nick = room._jid_nick[from_jid]; - local occupant = room._occupants[to_nick]; - local to_jid = occupant.jid; - - return from_nick, to_jid, base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from_jid)); -end -local function deconstruct_stanza_id(room, stanza) - local from_jid_possiblybare, to_nick = stanza.attr.from, stanza.attr.to; - local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); - local from_nick = room._jid_nick[from_jid]; - - if not(from_nick) then return; end - if not(from_jid_possiblybare == from_jid or from_jid_possiblybare == jid_bare(from_jid)) then return; end - - local occupant = room._occupants[to_nick]; - for to_jid in pairs(occupant and occupant.sessions or {}) do - if md5(to_jid) == to_jid_hash then - return from_nick, to_jid, id; - end - end -end - function room_mt:handle_presence_error_to_occupant(origin, stanza) local current_nick = self._jid_nick[stanza.attr.from]; if not current_nick then @@ -607,33 +583,45 @@ function room_mt:handle_iq_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; local type = stanza.attr.type; local id = stanza.attr.id; + local current_nick = self._jid_nick[from]; + local o_data = self._occupants[to]; if (type == "error" or type == "result") then - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); - if stanza.attr.id then - self:_route_stanza(stanza); + do -- deconstruct_stanza_id + if not current_nick or not o_data then return nil; end + local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); + if not(from == from_jid or from == jid_bare(from_jid)) then return nil; end + local session_jid + for to_jid in pairs(o_data.sessions) do + if md5(to_jid) == to_jid_hash then + session_jid = to_jid; + break; + end + end + if session_jid == nil then return nil; end + stanza.attr.from, stanza.attr.to, stanza.attr.id = current_nick, session_jid, id end + log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); + self:_route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; else -- Type is "get" or "set" - local current_nick = self._jid_nick[from]; if not current_nick then origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); return true; end - local o_data = self._occupants[to]; if not o_data then -- recipient not in room origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); return true; end - stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); + do -- construct_stanza_id + stanza.attr.id = base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from)); + end + stanza.attr.from, stanza.attr.to = current_nick, o_data.jid; log("debug", "%s sent private iq stanza to %s (%s)", from, to, o_data.jid); if stanza.tags[1].attr.xmlns == 'vcard-temp' then stanza.attr.to = jid_bare(stanza.attr.to); end - if stanza.attr.id then - self:_route_stanza(stanza); - end + self:_route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; end @@ -1027,7 +1015,6 @@ function room_mt:handle_groupchat_to_room(origin, stanza) end end - function room_mt:handle_kickable_to_room(origin, stanza) local current_nick = self._jid_nick[stanza.attr.from]; log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); -- cgit v1.2.3 From e89c74cde566c64200c058845622c0c1618b8242 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 17:40:16 -0500 Subject: plugins/muc/muc.lib: Add some missing return values --- plugins/muc/muc.lib.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ce4fa923..a5a87646 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -994,8 +994,10 @@ function room_mt:handle_groupchat_to_room(origin, stanza) local occupant = self._occupants[current_nick]; if not occupant then -- not in room origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + return true; elseif occupant.role == "visitor" then origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; else local from = stanza.attr.from; stanza.attr.from = current_nick; @@ -1012,6 +1014,7 @@ function room_mt:handle_groupchat_to_room(origin, stanza) self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); end stanza.attr.from = from; + return true; end end @@ -1019,6 +1022,7 @@ function room_mt:handle_kickable_to_room(origin, stanza) local current_nick = self._jid_nick[stanza.attr.from]; log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable + return true; end -- hack - some buggy clients send presence updates to the room rather than their nick @@ -1030,8 +1034,10 @@ function room_mt:handle_presence_to_room(origin, stanza) stanza.attr.to = current_nick; self:handle_to_occupant(origin, stanza); stanza.attr.to = to; + return true; elseif type ~= "error" and type ~= "result" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + return true; end end @@ -1060,8 +1066,10 @@ function room_mt:handle_invite_to_room(origin, stanza, payload) self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) end self:_route_stanza(invite); + return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); + return true; end end @@ -1079,10 +1087,12 @@ function room_mt:handle_message_to_room(origin, stanza) return self:handle_invite_to_room(origin, stanza, payload) else origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; end else if type == "error" or type == "result" then return; end origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + return true; end end -- cgit v1.2.3 From 6a1b8db129762c338e2f17bfd9d9384a6ad45854 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 17:52:40 -0500 Subject: plugins/muc/muc.lib: Make use of return values to send service-unavailable errors --- plugins/muc/muc.lib.lua | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index a5a87646..6131ca78 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -982,9 +982,8 @@ function room_mt:handle_iq_to_room(origin, stanza) elseif stanza.attr.type == "set" then return self:handle_owner_query_set_to_room(origin, stanza) end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - return true; + else + return nil; end end @@ -1035,9 +1034,8 @@ function room_mt:handle_presence_to_room(origin, stanza) self:handle_to_occupant(origin, stanza); stanza.attr.to = to; return true; - elseif type ~= "error" and type ~= "result" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - return true; + else + return nil; end end @@ -1090,9 +1088,7 @@ function room_mt:handle_message_to_room(origin, stanza) return true; end else - if type == "error" or type == "result" then return; end - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - return true; + return nil; end end @@ -1108,10 +1104,18 @@ end function room_mt:handle_stanza(origin, stanza) local to_node, to_host, to_resource = jid_split(stanza.attr.to); + local handled if to_resource then - self:handle_to_occupant(origin, stanza); + handled = self:handle_to_occupant(origin, stanza); else - self:handle_to_room(origin, stanza); + handled = self:handle_to_room(origin, stanza); + end + + if not handled then + local type = stanza.attr.type + if stanza.name ~= "iq" or type == "get" or type == "set" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end end end -- cgit v1.2.3 From 41423fb282ab448b824223234bdee0f4f85ace9b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 18:04:38 -0500 Subject: plugins/muc/muc.lib: Move all kick code into one place --- plugins/muc/muc.lib.lua | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6131ca78..39240b16 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -259,14 +259,16 @@ function room_mt:set_subject(current_nick, subject) return true; end -local function build_unavailable_presence_from_error(stanza) +function room_mt:handle_kickable(origin, stanza) local type, condition, text = stanza:get_error(); local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error"); if text then error_message = error_message..": "..text; end - return st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) + local kick_stanza = st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) :tag('status'):text(error_message); + self:handle_unavailable_to_occupant(origin, kick_stanza); -- send unavailable + return true; end function room_mt:set_name(name) @@ -380,15 +382,6 @@ function room_mt:get_whois() return self._data.whois; end -function room_mt:handle_presence_error_to_occupant(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; - if not current_nick then - return true -- discard - end - log("debug", "kicking %s from %s", current_nick, self.jid); - return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)) -end - function room_mt:handle_unavailable_to_occupant(origin, stanza) local from = stanza.attr.from; local current_nick = self._jid_nick[from]; @@ -566,7 +559,7 @@ end function room_mt:handle_presence_to_occupant(origin, stanza) local type = stanza.attr.type; if type == "error" then -- error, kick em out! - return self:handle_presence_error_to_occupant(origin, stanza) + return self:handle_kickable(origin, stanza) elseif type == "unavailable" then -- unavailable return self:handle_unavailable_to_occupant(origin, stanza) elseif not type then -- available @@ -642,8 +635,7 @@ function room_mt:handle_message_to_occupant(origin, stanza) return true; elseif type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - return true; + return self:handle_kickable(origin, stanza); -- send unavailable end local o_data = self._occupants[to]; @@ -1017,26 +1009,17 @@ function room_mt:handle_groupchat_to_room(origin, stanza) end end -function room_mt:handle_kickable_to_room(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; - log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - return true; -end - -- hack - some buggy clients send presence updates to the room rather than their nick function room_mt:handle_presence_to_room(origin, stanza) - local type = stanza.attr.type; local current_nick = self._jid_nick[stanza.attr.from]; + local handled if current_nick then local to = stanza.attr.to; stanza.attr.to = current_nick; - self:handle_to_occupant(origin, stanza); + handled = self:handle_presence_to_occupant(origin, stanza); stanza.attr.to = to; - return true; - else - return nil; end + return handled; end function room_mt:handle_invite_to_room(origin, stanza, payload) @@ -1076,7 +1059,7 @@ function room_mt:handle_message_to_room(origin, stanza) if type == "groupchat" then return self:handle_groupchat_to_room(origin, stanza) elseif type == "error" and is_kickable_error(stanza) then - return self:handle_kickable_to_room(origin, stanza) + return self:handle_kickable(origin, stanza) elseif not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then local x = stanza.tags[1]; -- cgit v1.2.3 From f93b8df931ad6a22cfff903dee24b6b91ecb5e88 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 19:06:33 -0500 Subject: plugins/muc/muc.lib: Add disco iq handlers with compatible argument signature --- plugins/muc/muc.lib.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 39240b16..081e4c6d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -827,6 +827,16 @@ function room_mt:destroy(newjid, reason, password) module:fire_event("muc-room-destroyed", { room = self }); end +function room_mt:handle_disco_info_get_query(origin, stanza) + origin.send(self:get_disco_info(stanza)); + return true; +end + +function room_mt:handle_disco_items_get_query(origin, stanza) + origin.send(self:get_disco_items(stanza)); + return true; +end + function room_mt:handle_admin_item_set_command(origin, stanza) local item = stanza.tags[1].tags[1]; if item.attr.jid then -- Validate provided JID @@ -951,11 +961,9 @@ function room_mt:handle_iq_to_room(origin, stanza) local type = stanza.attr.type; local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_info(stanza)); - return true; + return self:handle_disco_info_get_query(origin, stanza) elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_items(stanza)); - return true; + return self:handle_disco_items_get_query(origin, stanza) elseif xmlns == "http://jabber.org/protocol/muc#admin" then local item = stanza.tags[1].tags[1]; if item and item.name == "item" then -- cgit v1.2.3 From d78445f625dcab3f5098c7b29a30f6d9021816de Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 14:54:41 -0400 Subject: plugins/muc/muc: Add copyright for daurnimator --- plugins/muc/muc.lib.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 081e4c6d..8daa4b9f 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1,6 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- cgit v1.2.3 From f03d450c5a89c50e313f87041036e643e03b9dfa Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 14:56:20 -0400 Subject: plugins/muc/muc: Check for mediated invites in a smarter way --- plugins/muc/muc.lib.lua | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 8daa4b9f..8a8680c5 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1033,6 +1033,10 @@ end function room_mt:handle_invite_to_room(origin, stanza, payload) local _from, _to = stanza.attr.from, stanza.attr.to; + if not self._jid_nick[_from] then -- Should be in room to send invite + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end local _invitee = jid_prep(payload.attr.to); if _invitee then local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; @@ -1069,13 +1073,13 @@ function room_mt:handle_message_to_room(origin, stanza) return self:handle_groupchat_to_room(origin, stanza) elseif type == "error" and is_kickable_error(stanza) then return self:handle_kickable(origin, stanza) - elseif not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 - and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then - local x = stanza.tags[1]; - local payload = (#x.tags == 1 and x.tags[1]); - if payload and payload.name == "invite" and payload.attr.to then - return self:handle_invite_to_room(origin, stanza, payload) - else + elseif type == nil then + local x = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); + if x then + local payload = x.tags[1]; + if payload and payload.name == "invite" and payload.attr.to then + return self:handle_invite_to_room(origin, stanza, payload) + end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; end -- cgit v1.2.3 From 32ef3e0c33ede7676d9bf98919997502723a058e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 15:15:14 -0400 Subject: plugins/muc/muc: Rename `handle_invite_to_room` to `handle_mediated_invite`; clean up logic --- plugins/muc/muc.lib.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 8a8680c5..9b54fd2b 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1031,15 +1031,15 @@ function room_mt:handle_presence_to_room(origin, stanza) return handled; end -function room_mt:handle_invite_to_room(origin, stanza, payload) +function room_mt:handle_mediated_invite(origin, stanza, payload) local _from, _to = stanza.attr.from, stanza.attr.to; - if not self._jid_nick[_from] then -- Should be in room to send invite + if not self._jid_nick[_from] then -- Should be in room to send invite TODO: allow admins to send at any time origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end local _invitee = jid_prep(payload.attr.to); if _invitee then - local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; + local _reason = payload:get_child_text("reason") local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) :tag('invite', {from=_from}) @@ -1077,8 +1077,10 @@ function room_mt:handle_message_to_room(origin, stanza) local x = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); if x then local payload = x.tags[1]; - if payload and payload.name == "invite" and payload.attr.to then - return self:handle_invite_to_room(origin, stanza, payload) + if payload == nil then + -- fallthrough + elseif payload.name == "invite" and payload.attr.to then + return self:handle_mediated_invite(origin, stanza, payload) end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; -- cgit v1.2.3 From 5a0baa1d2f9dc13a5fce3013b2c8a68d7e0d1b83 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 15:15:28 -0400 Subject: plugins/muc/muc: Support mediated declines --- plugins/muc/muc.lib.lua | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9b54fd2b..0c87574a 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1067,6 +1067,29 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) end end +function room_mt:handle_mediated_decline(origin, stanza, payload) + local declinee = jid_prep(payload.attr.to); + if declinee then + local from, to = stanza.attr.from, stanza.attr.to; + -- TODO: Validate declinee + local reason = payload:get_child_text("reason") + local decline = st.message({from = to, to = declinee, id = stanza.attr.id}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('decline', {from=from}) + :tag('reason'):text(reason or ""):up() + :up() + :up() + :tag('body') -- Add a plain message for clients which don't support declines + :text(from..' declined your invite to the room '..to..(reason and (' ('..reason..')') or "")) + :up(); + self:_route_stanza(decline); + return true; + else + origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); + return true; + end +end + function room_mt:handle_message_to_room(origin, stanza) local type = stanza.attr.type; if type == "groupchat" then @@ -1081,6 +1104,8 @@ function room_mt:handle_message_to_room(origin, stanza) -- fallthrough elseif payload.name == "invite" and payload.attr.to then return self:handle_mediated_invite(origin, stanza, payload) + elseif payload.name == "decline" and payload.attr.to then + return self:handle_mediated_decline(origin, stanza, payload) end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; -- cgit v1.2.3 From 176fc76253fd26bca5efc0249b3666feb40ddf99 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 15:42:48 -0400 Subject: plugins/muc/muc: When there's no history; return an empty iterator --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 0c87574a..2afe6f58 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -181,7 +181,7 @@ end -- Get history for 'to' function room_mt:get_history(to, maxchars, maxstanzas, since) local history = self._data['history']; -- send discussion history - if not history then return end + if not history then return function() end end local history_len = #history maxstanzas = maxstanzas or history_len -- cgit v1.2.3 From 114bcccb5b95c3d122d2fe5bc3d61c809d76b2c6 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 16:01:53 -0400 Subject: plugins/muc/muc: Only call get_password once in invite creation --- plugins/muc/muc.lib.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2afe6f58..2c9b58d2 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1045,9 +1045,10 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) :tag('invite', {from=_from}) :tag('reason'):text(_reason or ""):up() :up(); - if self:get_password() then - invite:tag("password"):text(self:get_password()):up(); - end + local password = self:get_password() + if password then + invite:tag("password"):text(password):up(); + end invite:up() :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this :text(_reason or "") -- cgit v1.2.3 From f04399509b70df2b62c7207142593992f0af8bd7 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 18:52:28 -0400 Subject: plugins/muc/muc: Add 'muc-occupant-left' event --- plugins/muc/muc.lib.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2c9b58d2..9f7b5c70 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -416,6 +416,7 @@ function room_mt:handle_unavailable_to_occupant(origin, stanza) occupant.role = 'none'; self:broadcast_presence(pr, from); self._occupants[current_nick] = nil; + module:fire_event("muc-occupant-left", { room = self; nick = current_nick; }); end return true; end @@ -823,6 +824,7 @@ function room_mt:destroy(newjid, reason, password) self._jid_nick[jid] = nil; end self._occupants[nick] = nil; + module:fire_event("muc-occupant-left", { room = self; nick = nick; }); end self:set_persistent(false); module:fire_event("muc-room-destroyed", { room = self }); -- cgit v1.2.3 From fdee07a8095daec344aead18d6d8b6f9622573ab Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 18:55:52 -0400 Subject: plugins/muc/mod_muc: Refactor to use new methods available --- plugins/muc/mod_muc.lua | 110 +++++++++++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 38 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 5a71ef75..9adec74e 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -106,6 +106,14 @@ function create_room(jid) return room; end +function forget_room(jid) + rooms[jid] = nil; +end + +function get_room_from_jid(room_jid) + return rooms[room_jid] +end + local persistent_errors = false; for jid in pairs(persistent_rooms) do local node = jid_split(jid); @@ -125,6 +133,7 @@ if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); e local host_room = muc_new_room(muc_host); host_room.route_stanza = room_route_stanza; host_room.save = room_save; +rooms[muc_host] = host_room; module:hook("host-disco-items", function(event) local reply = event.reply; @@ -136,49 +145,74 @@ module:hook("host-disco-items", function(event) end end); -function stanza_handler(event) - local origin, stanza = event.origin, event.stanza; - local bare = jid_bare(stanza.attr.to); - local room = rooms[bare]; - if not room then - if stanza.name ~= "presence" then - origin.send(st.error_reply(stanza, "cancel", "item-not-found")); - return true; - end - if not(restrict_room_creation) or - is_admin(stanza.attr.from) or - (restrict_room_creation == "local" and select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "")) then - room = create_room(bare); - end +module:hook("muc-room-destroyed",function(event) + local room = event.room + forget_room(room.jid) +end) + +module:hook("muc-occupant-left",function(event) + local room = event.room + if not next(room._occupants) and not persistent_rooms[room.jid] then -- empty, non-persistent room + module:fire_event("muc-room-destroyed", { room = room }); end - if room then - room:handle_stanza(origin, stanza); - if not next(room._occupants) and not persistent_rooms[room.jid] then -- empty, non-persistent room - module:fire_event("muc-room-destroyed", { room = room }); - rooms[bare] = nil; -- discard room - end - else - origin.send(st.error_reply(stanza, "cancel", "not-allowed")); +end); + +-- Watch presence to create rooms +local function attempt_room_creation(event) + local origin, stanza = event.origin, event.stanza; + local room_jid = jid_bare(stanza.attr.to); + if stanza.attr.type == nil and + get_room_from_jid(room_jid) == nil and + ( + not(restrict_room_creation) or + is_admin(stanza.attr.from) or + ( + restrict_room_creation == "local" and + select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "") + ) + ) then + create_room(room_jid); end - return true; end -module:hook("iq/bare", stanza_handler, -1); -module:hook("message/bare", stanza_handler, -1); -module:hook("presence/bare", stanza_handler, -1); -module:hook("iq/full", stanza_handler, -1); -module:hook("message/full", stanza_handler, -1); -module:hook("presence/full", stanza_handler, -1); +module:hook("presence/full", attempt_room_creation, -1) +module:hook("presence/bare", attempt_room_creation, -1) +module:hook("presence/host", attempt_room_creation, -1) -local function handle_to_domain(event) - local origin, stanza = event.origin, event.stanza; - local type = stanza.attr.type; - if type == "error" then return; end - host_room:handle_stanza(origin, stanza); - -- origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); - return true; +for event_name, method in pairs { + -- Normal room interactions + ["iq-get/bare/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; + ["iq-get/bare/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; + ["iq-set/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; + ["iq-get/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; + ["iq-get/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; + ["message/bare"] = "handle_message_to_room" ; + ["presence/bare"] = "handle_presence_to_room" ; + -- Host room + ["iq-get/host/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; + ["iq-get/host/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; + ["iq-set/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; + ["iq-get/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; + ["iq-get/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; + ["message/host"] = "handle_message_to_room" ; + ["presence/host"] = "handle_presence_to_room" ; + -- Direct to occupant (normal rooms and host room) + ["presence/full"] = "handle_presence_to_occupant" ; + ["iq/full"] = "handle_iq_to_occupant" ; + ["message/full"] = "handle_message_to_occupant" ; +} do + module:hook(event_name, function (event) + local origin, stanza = event.origin, event.stanza; + local room = get_room_from_jid(jid_bare(stanza.attr.to)) + if room == nil then + origin.send(st.error_reply(stanza, "cancel", "not-allowed")); + return true; + end + return room[method](room, origin, stanza); + end, -2) end -module:hook("message/host", handle_to_domain, -1); -module:hook("presence/host", handle_to_domain, -1); + hosts[module.host].send = function(stanza) -- FIXME do a generic fix if stanza.attr.type == "result" or stanza.attr.type == "error" then -- cgit v1.2.3 From e3b729b9780b64e3efcd2a8163d3fcc8121daf14 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 18:56:26 -0400 Subject: plugins/muc/mod_muc: host sessions have a .send these days --- plugins/muc/mod_muc.lua | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 9adec74e..5f4b0c62 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -213,13 +213,6 @@ for event_name, method in pairs { end, -2) end - -hosts[module.host].send = function(stanza) -- FIXME do a generic fix - if stanza.attr.type == "result" or stanza.attr.type == "error" then - module:send(stanza); - else error("component.send only supports result and error stanzas at the moment"); end -end - hosts[module:get_host()].muc = { rooms = rooms }; local saved = false; -- cgit v1.2.3 From 5b4518c010d70120cf5afef3ad4208454407a49b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 12:03:15 -0400 Subject: plugins/muc: Provide a reasonable default `route_stanza` --- plugins/muc/mod_muc.lua | 3 --- plugins/muc/muc.lib.lua | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 5f4b0c62..a1ba5738 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -64,7 +64,6 @@ function muclib.room_mt:set_affiliation(actor, jid, affiliation, callback, reaso return _set_affiliation(self, actor, jid, affiliation, callback, reason); end -local function room_route_stanza(room, stanza) module:send(stanza); end local function room_save(room, forced) local node = jid_split(room.jid); persistent_rooms[room.jid] = room._data.persistent; @@ -89,7 +88,6 @@ end function create_room(jid) local room = muc_new_room(jid); - room.route_stanza = room_route_stanza; room.save = room_save; rooms[jid] = room; if lock_rooms then @@ -131,7 +129,6 @@ end if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end local host_room = muc_new_room(muc_host); -host_room.route_stanza = room_route_stanza; host_room.save = room_save; rooms[muc_host] = host_room; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9f7b5c70..198e25a3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1145,7 +1145,9 @@ function room_mt:handle_stanza(origin, stanza) end end -function room_mt:route_stanza(stanza) end -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end +function room_mt:route_stanza(stanza) + module:send(stanza) +end function room_mt:get_affiliation(jid) local node, host, resource = jid_split(jid); -- cgit v1.2.3 From ab848d94a3741d0162e06f9c454ecb84cb107aa9 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 12:19:44 -0400 Subject: plugins/muc/muc.lib: Use more modern stanza methods --- plugins/muc/muc.lib.lua | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 198e25a3..ead1f264 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -755,8 +755,7 @@ end function room_mt:process_form(origin, stanza) local query = stanza.tags[1]; - local form; - for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end + local form = query:get_child("x", "jabber:x:data") if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end @@ -858,7 +857,7 @@ function room_mt:handle_admin_item_set_command(origin, stanza) end local actor = stanza.attr.from; local callback = function() origin.send(st.reply(stanza)); end - local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; + local reason = item:get_child_text("reason"); if item.attr.affiliation and item.attr.jid and not item.attr.role then local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); if not success then origin.send(st.error_reply(stanza, errtype, err)); end @@ -943,14 +942,8 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) return true; elseif child.name == "destroy" then local newjid = child.attr.jid; - local reason, password; - for _,tag in ipairs(child.tags) do - if tag.name == "reason" then - reason = #tag.tags == 0 and tag[1]; - elseif tag.name == "password" then - password = #tag.tags == 0 and tag[1]; - end - end + local reason = child:get_child_text("reason"); + local password = child:get_child_text("password"); self:destroy(newjid, reason, password); origin.send(st.reply(stanza)); return true; @@ -1305,22 +1298,18 @@ function room_mt:_route_stanza(stanza) end end if muc_child then - for _, item in pairs(muc_child.tags) do - if item.name == "item" then - if from_occupant == to_occupant then - item.attr.jid = stanza.attr.to; - else - item.attr.jid = from_occupant.jid; - end + for item in muc_child:childtags("item") do + if from_occupant == to_occupant then + item.attr.jid = stanza.attr.to; + else + item.attr.jid = from_occupant.jid; end end end self:route_stanza(stanza); if muc_child then - for _, item in pairs(muc_child.tags) do - if item.name == "item" then - item.attr.jid = nil; - end + for item in muc_child:childtags("item") do + item.attr.jid = nil; end end end -- cgit v1.2.3 From fdfd511de7c8b3585433428c77ee306405c14b3b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 13:56:14 -0400 Subject: plugins/muc/muc.lib: Remove unused methods (breaks api) --- plugins/muc/muc.lib.lua | 72 ------------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ead1f264..972e30cb 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -656,21 +656,6 @@ function room_mt:handle_message_to_occupant(origin, stanza) return true; end -function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc - local from, to = stanza.attr.from, stanza.attr.to; - local room = jid_bare(to); - local current_nick = self._jid_nick[from]; - log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); - if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end - if stanza.name == "presence" then - return self:handle_presence_to_occupant(origin, stanza) - elseif stanza.name == "iq" then - return self:handle_iq_to_occupant(origin, stanza) - elseif stanza.name == "message" then - return self:handle_message_to_occupant(origin, stanza) - end -end - function room_mt:send_form(origin, stanza) origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") :add_child(self:get_form_layout(stanza.attr.from):form()) @@ -953,36 +938,6 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) end end -function room_mt:handle_iq_to_room(origin, stanza) - local type = stanza.attr.type; - local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; - if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then - return self:handle_disco_info_get_query(origin, stanza) - elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then - return self:handle_disco_items_get_query(origin, stanza) - elseif xmlns == "http://jabber.org/protocol/muc#admin" then - local item = stanza.tags[1].tags[1]; - if item and item.name == "item" then - if type == "set" then - return self:handle_admin_item_set_command(origin, stanza) - elseif type == "get" then - return self:handle_admin_item_get_command(origin, stanza) - end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "bad-request")); - return true; - end - elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then - if stanza.attr.type == "get" then - return self:handle_owner_query_get_to_room(origin, stanza) - elseif stanza.attr.type == "set" then - return self:handle_owner_query_set_to_room(origin, stanza) - end - else - return nil; - end -end - function room_mt:handle_groupchat_to_room(origin, stanza) local from = stanza.attr.from; local current_nick = self._jid_nick[from]; @@ -1111,33 +1066,6 @@ function room_mt:handle_message_to_room(origin, stanza) end end -function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc - if stanza.name == "iq" then - return self:handle_iq_to_room(origin, stanza) - elseif stanza.name == "message" then - return self:handle_message_to_room(origin, stanza) - elseif stanza.name == "presence" then - return self:handle_presence_to_room(origin, stanza) - end -end - -function room_mt:handle_stanza(origin, stanza) - local to_node, to_host, to_resource = jid_split(stanza.attr.to); - local handled - if to_resource then - handled = self:handle_to_occupant(origin, stanza); - else - handled = self:handle_to_room(origin, stanza); - end - - if not handled then - local type = stanza.attr.type - if stanza.name ~= "iq" or type == "get" or type == "set" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - end - end -end - function room_mt:route_stanza(stanza) module:send(stanza) end -- cgit v1.2.3 From cda8656a4e20360af37c389a3751625b528259ff Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 13:57:02 -0400 Subject: plugins/muc/muc.lib: Use module.host where `muc_domain` was previously --- plugins/muc/muc.lib.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 972e30cb..f3d69f8d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -25,7 +25,6 @@ local setmetatable = setmetatable; local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; -local muc_domain = nil; --module:get_host(); local default_history_length, max_history_length = 20, math.huge; ------------ @@ -118,8 +117,8 @@ function room_mt:save_to_history(stanza) stanza = st.clone(stanza); stanza.attr.to = ""; local stamp = datetime.datetime(); - stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203 - stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) + stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 + stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) local entry = { stanza = stanza, stamp = stamp }; t_insert(history, entry); while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end -- cgit v1.2.3 From 37e1117b7809b10db45ecbd2e9eb3cfa957ec652 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 13:59:59 -0400 Subject: plugins/muc/muc.lib: Tidy up is_kickable_error: it didn't need to return the condition. Also removes `get_error_condition`; it was a one liner used in one place --- plugins/muc/muc.lib.lua | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index f3d69f8d..2d45f17e 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -27,7 +27,6 @@ local md5 = require "util.hashes".md5; local default_history_length, max_history_length = 20, math.huge; ------------- local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; local function presence_filter(tag) if presence_filters[tag.attr.xmlns] then @@ -35,33 +34,28 @@ local function presence_filter(tag) end return tag; end - local function get_filtered_presence(stanza) return st.clone(stanza):maptags(presence_filter); end -local kickable_error_conditions = { - ["gone"] = true; - ["internal-server-error"] = true; - ["item-not-found"] = true; - ["jid-malformed"] = true; - ["recipient-unavailable"] = true; - ["redirect"] = true; - ["remote-server-not-found"] = true; - ["remote-server-timeout"] = true; - ["service-unavailable"] = true; - ["malformed error"] = true; -}; - -local function get_error_condition(stanza) - local _, condition = stanza:get_error(); - return condition or "malformed error"; -end - -local function is_kickable_error(stanza) - local cond = get_error_condition(stanza); - return kickable_error_conditions[cond] and cond; -end ------------ + +local is_kickable_error do + local kickable_error_conditions = { + ["gone"] = true; + ["internal-server-error"] = true; + ["item-not-found"] = true; + ["jid-malformed"] = true; + ["recipient-unavailable"] = true; + ["redirect"] = true; + ["remote-server-not-found"] = true; + ["remote-server-timeout"] = true; + ["service-unavailable"] = true; + ["malformed error"] = true; + }; + function is_kickable_error(stanza) + local cond = select(2, stanza:get_error()) or "malformed error"; + return kickable_error_conditions[cond]; + end +end local room_mt = {}; room_mt.__index = room_mt; -- cgit v1.2.3 From 38d1a05fee92a320301db02dfc189f5138fccfc7 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 14:06:04 -0400 Subject: plugins/muc/muc.lib: Tidy up `get_filtered_presence` --- plugins/muc/muc.lib.lua | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2d45f17e..2fb1a34e 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -27,15 +27,20 @@ local md5 = require "util.hashes".md5; local default_history_length, max_history_length = 20, math.huge; -local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; -local function presence_filter(tag) - if presence_filters[tag.attr.xmlns] then - return nil; +local get_filtered_presence do + local presence_filters = { + ["http://jabber.org/protocol/muc"] = true; + ["http://jabber.org/protocol/muc#user"] = true; + } + local function presence_filter(tag) + if presence_filters[tag.attr.xmlns] then + return nil; + end + return tag; + end + function get_filtered_presence(stanza) + return st.clone(stanza):maptags(presence_filter); end - return tag; -end -local function get_filtered_presence(stanza) - return st.clone(stanza):maptags(presence_filter); end local is_kickable_error do -- cgit v1.2.3 From 0fc08db387ae02f3b9c659401c4bf8fd25658ea8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 14:35:17 -0400 Subject: plugins/muc/muc.lib: In `_route_stanza` access occupant data less often --- plugins/muc/muc.lib.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2fb1a34e..1a5bc976 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1210,9 +1210,9 @@ end function room_mt:_route_stanza(stanza) local muc_child; - local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; - local from_occupant = self._occupants[stanza.attr.from]; if stanza.name == "presence" then + local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; + local from_occupant = self._occupants[stanza.attr.from]; if to_occupant and from_occupant then if self._data.whois == 'anyone' then muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); @@ -1222,13 +1222,13 @@ function room_mt:_route_stanza(stanza) end end end - end - if muc_child then - for item in muc_child:childtags("item") do - if from_occupant == to_occupant then - item.attr.jid = stanza.attr.to; - else - item.attr.jid = from_occupant.jid; + if muc_child then + for item in muc_child:childtags("item") do + if from_occupant == to_occupant then + item.attr.jid = stanza.attr.to; + else + item.attr.jid = from_occupant.jid; + end end end end -- cgit v1.2.3 From 21d9bec752b153efeeab4e779a8c1e2627a3f1dc Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 14:39:31 -0400 Subject: plugins/muc/muc.lib: Fetch config via accessors instead of using `_data` --- plugins/muc/muc.lib.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1a5bc976..81998d2c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -232,7 +232,7 @@ function room_mt:get_disco_info(stanza) :tag("feature", {var=self:get_members_only() and "muc_membersonly" or "muc_open"}):up() :tag("feature", {var=self:get_persistent() and "muc_persistent" or "muc_temporary"}):up() :tag("feature", {var=self:get_hidden() and "muc_hidden" or "muc_public"}):up() - :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() + :tag("feature", {var=self:get_whois() ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() :add_child(dataform.new({ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }, { name = "muc#roominfo_description", label = "Description", value = "" }, @@ -488,7 +488,7 @@ function room_mt:handle_join(origin, stanza) self:broadcast_except_nick(pr, to); end pr:tag("status", {code='110'}):up(); - if self._data.whois == 'anyone' then + if self:get_whois() == 'anyone' then pr:tag("status", {code='100'}):up(); end if self.locked then @@ -661,6 +661,7 @@ function room_mt:send_form(origin, stanza) end function room_mt:get_form_layout(actor) + local whois = self:get_whois() local form = dataform.new({ title = "Configuration for "..self.jid, instructions = "Complete and submit this form to configure the room.", @@ -704,8 +705,8 @@ function room_mt:get_form_layout(actor) type = 'list-single', label = 'Who May Discover Real JIDs?', value = { - { value = 'moderators', label = 'Moderators Only', default = self._data.whois == 'moderators' }, - { value = 'anyone', label = 'Anyone', default = self._data.whois == 'anyone' } + { value = 'moderators', label = 'Moderators Only', default = whois == 'moderators' }, + { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } } }, { @@ -952,7 +953,7 @@ function room_mt:handle_groupchat_to_room(origin, stanza) local subject = stanza:get_child_text("subject"); if subject then if occupant.role == "moderator" or - ( self._data.changesubject and occupant.role == "participant" ) then -- and participant + ( self:get_changesubject() and occupant.role == "participant" ) then -- and participant self:set_subject(current_nick, subject); else stanza.attr.from = from; @@ -1214,7 +1215,7 @@ function room_mt:_route_stanza(stanza) local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; local from_occupant = self._occupants[stanza.attr.from]; if to_occupant and from_occupant then - if self._data.whois == 'anyone' then + if self:get_whois() == 'anyone' then muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); else if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then -- cgit v1.2.3 From cca33567091e686a93fd560b9460abaf96caa057 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 15:00:53 -0400 Subject: plugins/muc/muc.lib: Use `get_occupant_jid` method instead of indexing _jid_nick --- plugins/muc/muc.lib.lua | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 81998d2c..173d9827 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -69,6 +69,10 @@ function room_mt:__tostring() return "MUC room ("..self.jid..")"; end +function room_mt:get_occupant_jid(real_jid) + return self._jid_nick[real_jid] +end + function room_mt:get_default_role(affiliation) if affiliation == "owner" or affiliation == "admin" then return "moderator"; @@ -134,7 +138,7 @@ function room_mt:broadcast_except_nick(stanza, nick) end function room_mt:send_occupant_list(to) - local current_nick = self._jid_nick[to]; + local current_nick = self:get_occupant_jid(to); for occupant, o_data in pairs(self._occupants) do if occupant ~= current_nick then local pres = get_filtered_presence(o_data.sessions[o_data.jid]); @@ -383,7 +387,7 @@ end function room_mt:handle_unavailable_to_occupant(origin, stanza) local from = stanza.attr.from; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); if not current_nick then return true; -- discard end @@ -514,7 +518,7 @@ end function room_mt:handle_available_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); if current_nick then --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence if current_nick == to then -- simple presence @@ -576,7 +580,7 @@ function room_mt:handle_iq_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; local type = stanza.attr.type; local id = stanza.attr.id; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); local o_data = self._occupants[to]; if (type == "error" or type == "result") then do -- deconstruct_stanza_id @@ -622,7 +626,7 @@ end function room_mt:handle_message_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); local type = stanza.attr.type; if not current_nick then -- not in room if type ~= "error" then @@ -836,7 +840,7 @@ function room_mt:handle_admin_item_set_command(origin, stanza) local occupant = self._occupants[self.jid.."/"..item.attr.nick]; if occupant then item.attr.jid = occupant.jid; end elseif not item.attr.nick and item.attr.jid then - local nick = self._jid_nick[item.attr.jid]; + local nick = self:get_occupant_jid(item.attr.jid); if nick then item.attr.nick = select(3, jid_split(nick)); end end local actor = stanza.attr.from; @@ -859,7 +863,7 @@ end function room_mt:handle_admin_item_get_command(origin, stanza) local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); - local current_nick = self._jid_nick[actor]; + local current_nick = self:get_occupant_jid(actor); local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); local item = stanza.tags[1].tags[1]; local _aff = item.attr.affiliation; @@ -939,7 +943,7 @@ end function room_mt:handle_groupchat_to_room(origin, stanza) local from = stanza.attr.from; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); local occupant = self._occupants[current_nick]; if not occupant then -- not in room origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); @@ -969,7 +973,7 @@ end -- hack - some buggy clients send presence updates to the room rather than their nick function room_mt:handle_presence_to_room(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; + local current_nick = self:get_occupant_jid(stanza.attr.from); local handled if current_nick then local to = stanza.attr.to; @@ -982,7 +986,8 @@ end function room_mt:handle_mediated_invite(origin, stanza, payload) local _from, _to = stanza.attr.from, stanza.attr.to; - if not self._jid_nick[_from] then -- Should be in room to send invite TODO: allow admins to send at any time + local current_nick = self:get_occupant_jid(_from) + if not current_nick then -- Should be in room to send invite TODO: allow admins to send at any time origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end @@ -1007,7 +1012,7 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) :up(); if self:get_members_only() and not self:get_affiliation(_invitee) then log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); - self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) + self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. current_nick) end self:_route_stanza(invite); return true; @@ -1157,7 +1162,7 @@ function room_mt:can_set_role(actor_jid, occupant_jid, role) if actor_jid == true then return true; end - local actor = self._occupants[self._jid_nick[actor_jid]]; + local actor = self._occupants[self:get_occupant_jid(actor_jid)]; if actor.role == "moderator" then if occupant.affiliation ~= "owner" and occupant.affiliation ~= "admin" then if actor.affiliation == "owner" or actor.affiliation == "admin" then @@ -1212,7 +1217,7 @@ end function room_mt:_route_stanza(stanza) local muc_child; if stanza.name == "presence" then - local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; + local to_occupant = self._occupants[self:get_occupant_jid(stanza.attr.to)]; local from_occupant = self._occupants[stanza.attr.from]; if to_occupant and from_occupant then if self:get_whois() == 'anyone' then -- cgit v1.2.3 From 4bbb266dab17014d2d8f25b1d8180e38c4a8a233 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 15:01:18 -0400 Subject: plugins/muc/muc.lib: Don't get same variable twice..... --- plugins/muc/muc.lib.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 173d9827..f3c72e26 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -445,13 +445,12 @@ function room_mt:handle_change_nick(origin, stanza, current_nick, to) origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; else - local data = self._occupants[current_nick]; local to_nick = select(3, jid_split(to)); - log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); + log("debug", "%s (%s) changing nick to %s", current_nick, occupant.jid, to); local p = st.presence({type='unavailable', from=current_nick}); self:broadcast_presence(p, from, '303', to_nick); self._occupants[current_nick] = nil; - self._occupants[to] = data; + self._occupants[to] = occupant; self._jid_nick[from] = to; local pr = get_filtered_presence(stanza); pr.attr.from = to; -- cgit v1.2.3 From 74fb113acd8f4c6c20c7ebb5df7ab9ac0de5932e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 16:19:31 -0400 Subject: plugins/muc/muc.lib: Add muc-invite-prepared event; Use it for granting affiliations in members only rooms --- plugins/muc/muc.lib.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index f3c72e26..7dfb0611 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1009,10 +1009,7 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) :tag('body') -- Add a plain message for clients which don't support invites :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) :up(); - if self:get_members_only() and not self:get_affiliation(_invitee) then - log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); - self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. current_nick) - end + module:fire_event("muc-invite-prepared", { room = self, stanza = invite }) self:_route_stanza(invite); return true; else @@ -1021,6 +1018,18 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) end end +-- When an invite is sent; add an affiliation for the invitee +module:hook("muc-invite-prepared", function(event) + local room, stanza = event.room, event.stanza + local invitee = stanza.attr.to + if room:get_members_only() and not room:get_affiliation(invitee) then + local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from + local current_nick = room:get_occupant_jid(from) + log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); + room:set_affiliation(from, invitee, "member", nil, "Invited by " .. current_nick) + end +end) + function room_mt:handle_mediated_decline(origin, stanza, payload) local declinee = jid_prep(payload.attr.to); if declinee then -- cgit v1.2.3 From 3b36ae6f437d90e504e3de4d7d2996f9197a805a Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 16:24:17 -0400 Subject: plugins/muc/muc.lib: Fix wrong variable in `construct_stanza_id` block --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7dfb0611..de251716 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -610,7 +610,7 @@ function room_mt:handle_iq_to_occupant(origin, stanza) return true; end do -- construct_stanza_id - stanza.attr.id = base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from)); + stanza.attr.id = base64.encode(o_data.jid.."\0"..stanza.attr.id.."\0"..md5(from)); end stanza.attr.from, stanza.attr.to = current_nick, o_data.jid; log("debug", "%s sent private iq stanza to %s (%s)", from, to, o_data.jid); -- cgit v1.2.3 From c6d4ea2e3b5db640310d5cb7b5d1b8b27fc7c831 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 16:28:11 -0400 Subject: plugins/muc/muc.lib: Remove `payload` argument from `handle_mediated_*`; extract it from inside. --- plugins/muc/muc.lib.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index de251716..be4d31c3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -983,7 +983,8 @@ function room_mt:handle_presence_to_room(origin, stanza) return handled; end -function room_mt:handle_mediated_invite(origin, stanza, payload) +function room_mt:handle_mediated_invite(origin, stanza) + local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite") local _from, _to = stanza.attr.from, stanza.attr.to; local current_nick = self:get_occupant_jid(_from) if not current_nick then -- Should be in room to send invite TODO: allow admins to send at any time @@ -1030,7 +1031,8 @@ module:hook("muc-invite-prepared", function(event) end end) -function room_mt:handle_mediated_decline(origin, stanza, payload) +function room_mt:handle_mediated_decline(origin, stanza) + local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline") local declinee = jid_prep(payload.attr.to); if declinee then local from, to = stanza.attr.from, stanza.attr.to; @@ -1066,9 +1068,9 @@ function room_mt:handle_message_to_room(origin, stanza) if payload == nil then -- fallthrough elseif payload.name == "invite" and payload.attr.to then - return self:handle_mediated_invite(origin, stanza, payload) + return self:handle_mediated_invite(origin, stanza) elseif payload.name == "decline" and payload.attr.to then - return self:handle_mediated_decline(origin, stanza, payload) + return self:handle_mediated_decline(origin, stanza) end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; -- cgit v1.2.3 From fd4a486166f340c736f15636ff5dc3940a806995 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:30:21 -0400 Subject: plugins/muc/muc.lib: Check role instead of current_nick --- plugins/muc/muc.lib.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index be4d31c3..7b010646 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -987,7 +987,8 @@ function room_mt:handle_mediated_invite(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite") local _from, _to = stanza.attr.from, stanza.attr.to; local current_nick = self:get_occupant_jid(_from) - if not current_nick then -- Should be in room to send invite TODO: allow admins to send at any time + -- Need visitor role or higher to invite + if not self._occupants[current_nick].role then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end -- cgit v1.2.3 From 0fc61fbaad4abf9d600c731e50bb9a414d6345d8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:33:32 -0400 Subject: plugins/muc/muc.lib: Send invite out from event: removes '-prepared' from event name --- plugins/muc/muc.lib.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7b010646..7cc1beb4 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1011,8 +1011,7 @@ function room_mt:handle_mediated_invite(origin, stanza) :tag('body') -- Add a plain message for clients which don't support invites :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) :up(); - module:fire_event("muc-invite-prepared", { room = self, stanza = invite }) - self:_route_stanza(invite); + module:fire_event("muc-invite", { room = self, stanza = invite, origin = origin, incoming = stanza }); return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); @@ -1020,8 +1019,13 @@ function room_mt:handle_mediated_invite(origin, stanza) end end +module:hook("muc-invite", function(event) + event.room:_route_stanza(event.stanza); + return true; +end, -1) + -- When an invite is sent; add an affiliation for the invitee -module:hook("muc-invite-prepared", function(event) +module:hook("muc-invite", function(event) local room, stanza = event.room, event.stanza local invitee = stanza.attr.to if room:get_members_only() and not room:get_affiliation(invitee) then -- cgit v1.2.3 From 35a820aba5a9017d350fe907bf4fe7b463e0f185 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:46:25 -0400 Subject: plugins/muc/muc.lib: Use `get_role` in `handle_admin_item_get_command`. Removed a TODO that's already done --- plugins/muc/muc.lib.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7cc1beb4..fe8aee72 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -862,8 +862,6 @@ end function room_mt:handle_admin_item_get_command(origin, stanza) local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); - local current_nick = self:get_occupant_jid(actor); - local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); local item = stanza.tags[1].tags[1]; local _aff = item.attr.affiliation; local _rol = item.attr.role; @@ -882,8 +880,8 @@ function room_mt:handle_admin_item_get_command(origin, stanza) return true; end elseif _rol and not _aff then + local role = self:get_role(self:get_occupant_jid(actor)) or self:get_default_role(affiliation); if role == "moderator" then - -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? if _rol == "none" then _rol = nil; end local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); for occupant_jid, occupant in pairs(self._occupants) do -- cgit v1.2.3 From 8f5af6d933d1f07db23b1c0f77ce0804c1643c84 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:50:00 -0400 Subject: plugins/muc/muc.lib: Allow users with affiliations to invite while not in room themselves --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index fe8aee72..af7bf356 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -986,7 +986,7 @@ function room_mt:handle_mediated_invite(origin, stanza) local _from, _to = stanza.attr.from, stanza.attr.to; local current_nick = self:get_occupant_jid(_from) -- Need visitor role or higher to invite - if not self._occupants[current_nick].role then + if not self:get_role(current_nick) or not self:get_default_role(self:get_affiliation(_from)) then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end -- cgit v1.2.3 From 8b8a024db40b127db492f4a0dd93a94f4e3cf801 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:50:49 -0400 Subject: plugins/muc/muc.lib: Use occupant jids when whois == "moderators" --- plugins/muc/muc.lib.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index af7bf356..30d9f96d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -992,6 +992,9 @@ function room_mt:handle_mediated_invite(origin, stanza) end local _invitee = jid_prep(payload.attr.to); if _invitee then + if self:get_whois() == "moderators" then + _from = current_nick; + end local _reason = payload:get_child_text("reason") local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) -- cgit v1.2.3 From 99fb14cb63c5c85ef0e087a393720d3f5d0760ad Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 11:06:10 -0400 Subject: plugins/muc: Have utility methods for locking the room --- plugins/muc/mod_muc.lua | 4 ++-- plugins/muc/muc.lib.lua | 25 ++++++++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index a1ba5738..e8782414 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -91,10 +91,10 @@ function create_room(jid) room.save = room_save; rooms[jid] = room; if lock_rooms then - room.locked = true; + room:lock(); if lock_room_timeout and lock_room_timeout > 0 then module:add_timer(lock_room_timeout, function () - if room.locked then + if room:is_locked() then room:destroy(); -- Not unlocked in time end end); diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 30d9f96d..746814ca 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -85,6 +85,17 @@ function room_mt:get_default_role(affiliation) end end +function room_mt:lock() + self.locked = true +end +function room_mt:unlock() + module:fire_event("muc-room-unlocked", { room = self }); + self.locked = nil +end +function room_mt:is_locked() + return not not self.locked +end + function room_mt:broadcast_presence(stanza, sid, code, nick) stanza = get_filtered_presence(stanza); local occupant = self._occupants[stanza.attr.from]; @@ -465,10 +476,10 @@ function room_mt:handle_join(origin, stanza) log("debug", "%s joining as %s", from, to); if not next(self._affiliations) then -- new room, no owners self._affiliations[jid_bare(from)] = "owner"; - if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then - self.locked = nil; -- Older groupchat protocol doesn't lock + if self:is_locked() and not stanza:get_child("x", "http://jabber.org/protocol/muc") then + self:unlock(); -- Older groupchat protocol doesn't lock end - elseif self.locked then -- Deny entry + elseif self:is_locked() then -- Deny entry origin.send(st.error_reply(stanza, "cancel", "item-not-found")); return true; end @@ -494,7 +505,7 @@ function room_mt:handle_join(origin, stanza) if self:get_whois() == 'anyone' then pr:tag("status", {code='100'}):up(); end - if self.locked then + if self:is_locked() then pr:tag("status", {code='201'}):up(); end pr.attr.to = from; @@ -777,9 +788,8 @@ function room_mt:process_form(origin, stanza) handle_option("password", "muc#roomconfig_roomsecret"); if self.save then self:save(true); end - if self.locked then - module:fire_event("muc-room-unlocked", { room = self }); - self.locked = nil; + if self:is_locked() then + self:unlock(); end origin.send(st.reply(stanza)); @@ -1267,6 +1277,7 @@ local _M = {}; -- module "muc" function _M.new_room(jid, config) return setmetatable({ jid = jid; + locked = nil; _jid_nick = {}; _occupants = {}; _data = { -- cgit v1.2.3 From 513bef46c407735b98e45cf49aec968f0ec1019e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 15:22:02 -0400 Subject: plugins/muc/muc.lib: Add route_to_occupant function to send a stanza to all occupant sessions --- plugins/muc/muc.lib.lua | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 746814ca..af9bd162 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -96,6 +96,15 @@ function room_mt:is_locked() return not not self.locked end +function room_mt:route_to_occupant(o_data, stanza) + local to = stanza.attr.to; + for jid in pairs(o_data.sessions) do + stanza.attr.to = jid; + self:_route_stanza(stanza); + end + stanza.attr.to = to; +end + function room_mt:broadcast_presence(stanza, sid, code, nick) stanza = get_filtered_presence(stanza); local occupant = self._occupants[stanza.attr.from]; @@ -113,14 +122,9 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) end end function room_mt:broadcast_message(stanza, historic) - local to = stanza.attr.to; - for occupant, o_data in pairs(self._occupants) do - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end + for occupant_jid, o_data in pairs(self._occupants) do + self:route_to_occupant(o_data, stanza) end - stanza.attr.to = to; if historic then -- add to history return self:save_to_history(stanza) end @@ -660,11 +664,8 @@ function room_mt:handle_message_to_occupant(origin, stanza) log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid); stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); stanza.attr.from = current_nick; - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to = from, to; + self:route_to_occupant(o_data, stanza) + stanza.attr.from = from; return true; end -- cgit v1.2.3 From c834ec947312d2fd929086991c50dcf3dfe2899b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 15:50:29 -0400 Subject: plugins/muc/muc.lib: Add decline event for parity with invite --- plugins/muc/muc.lib.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index af9bd162..7f8cc016 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1064,7 +1064,7 @@ function room_mt:handle_mediated_decline(origin, stanza) :tag('body') -- Add a plain message for clients which don't support declines :text(from..' declined your invite to the room '..to..(reason and (' ('..reason..')') or "")) :up(); - self:_route_stanza(decline); + module:fire_event("muc-decline", { room = self, stanza = decline, origin = origin, incoming = stanza }); return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); @@ -1072,6 +1072,11 @@ function room_mt:handle_mediated_decline(origin, stanza) end end +module:hook("muc-decline", function(event) + event.room:_route_stanza(event.stanza); + return true; +end, -1) + function room_mt:handle_message_to_room(origin, stanza) local type = stanza.attr.type; if type == "groupchat" then -- cgit v1.2.3 From ded172dcc5fed433bc4c7ce184ee1185b72d3f41 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 16:14:22 -0400 Subject: plugins/muc/muc.lib: Remove duplicate variable; it can never be nil. --- plugins/muc/muc.lib.lua | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7f8cc016..ac841248 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -114,12 +114,9 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) stanza:tag("status", {code=code}):up(); end self:broadcast_except_nick(stanza, stanza.attr.from); - local me = self._occupants[stanza.attr.from]; - if me then - stanza:tag("status", {code='110'}):up(); - stanza.attr.to = sid; - self:_route_stanza(stanza); - end + stanza:tag("status", {code='110'}):up(); + stanza.attr.to = sid; + self:_route_stanza(stanza); end function room_mt:broadcast_message(stanza, historic) for occupant_jid, o_data in pairs(self._occupants) do -- cgit v1.2.3 From 7951abf6fcb884bf455ffd24abbf80e0b40e7bab Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 16:19:13 -0400 Subject: plugins/muc/muc.lib: Additional `route_to_occupant` usage --- plugins/muc/muc.lib.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ac841248..6dce1a80 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -141,10 +141,7 @@ end function room_mt:broadcast_except_nick(stanza, nick) for rnick, occupant in pairs(self._occupants) do if rnick ~= nick then - for jid in pairs(occupant.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end + self:route_to_occupant(occupant, stanza) end end end -- cgit v1.2.3 From 0da972669cd49a90ba70cec5166dcc6ed2d5a1fd Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Mar 2014 14:01:02 -0400 Subject: plugins/muc/muc.lib: Move password check and nick conflict check into `handle_join` --- plugins/muc/muc.lib.lua | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6dce1a80..d859812f 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -472,6 +472,23 @@ end function room_mt:handle_join(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; log("debug", "%s joining as %s", from, to); + local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); + password = password and password:get_child("password", "http://jabber.org/protocol/muc"); + password = password and password[1] ~= "" and password[1]; + if self:get_password() and self:get_password() ~= password then + log("debug", "%s couldn't join due to invalid password: %s", from, to); + local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); + reply.tags[1].attr.code = "401"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + elseif self._occupants[to] -- occupant already exists + and jid_bare(from) ~= jid_bare(self._occupants[to].jid) then -- and has different bare real jid + log("debug", "%s couldn't join due to nick conflict: %s", from, to); + local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + reply.tags[1].attr.code = "409"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end if not next(self._affiliations) then -- new room, no owners self._affiliations[jid_bare(from)] = "owner"; if self:is_locked() and not stanza:get_child("x", "http://jabber.org/protocol/muc") then @@ -541,30 +558,7 @@ function room_mt:handle_available_to_occupant(origin, stanza) -- self:handle_to_occupant(origin, stanza); -- resend available --end else -- enter room - local new_nick = to; - if self._occupants[to] then - if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then - new_nick = nil; - end - end - local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); - password = password and password:get_child("password", "http://jabber.org/protocol/muc"); - password = password and password[1] ~= "" and password[1]; - if self:get_password() and self:get_password() ~= password then - log("debug", "%s couldn't join due to invalid password: %s", from, to); - local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); - reply.tags[1].attr.code = "401"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - elseif not new_nick then - log("debug", "%s couldn't join due to nick conflict: %s", from, to); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); - reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - else - return self:handle_join(origin, stanza) - end + return self:handle_join(origin, stanza) end end -- cgit v1.2.3 From a8855463cfb002193502d19541b9575960ad8072 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 10:25:43 -0400 Subject: plugins/muc/muc.lib: Add muc-occupant-prejoin events; Use it for banned, members-only, password, nick-conflict and lock checks This reorders some of the checks. Importantly; affiliations are checked first: this means banned users cannot try and guess passwords --- plugins/muc/muc.lib.lua | 82 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d859812f..1b3df029 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -469,36 +469,68 @@ function room_mt:handle_change_nick(origin, stanza, current_nick, to) end end -function room_mt:handle_join(origin, stanza) +module:hook("muc-occupant-pre-join", function(event) + return module:fire_event("muc-occupant-pre-join/affiliation", event) + or module:fire_event("muc-occupant-pre-join/password", event) + or module:fire_event("muc-occupant-pre-join/locked", event) + or module:fire_event("muc-occupant-pre-join/nick-conflict", event) +end, -1) + +module:hook("muc-occupant-pre-join/password", function(event) + local room, stanza = event.room, event.stanza; local from, to = stanza.attr.from, stanza.attr.to; - log("debug", "%s joining as %s", from, to); local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); password = password and password:get_child("password", "http://jabber.org/protocol/muc"); password = password and password[1] ~= "" and password[1]; - if self:get_password() and self:get_password() ~= password then + if room:get_password() and room:get_password() ~= password then + local from, to = stanza.attr.from, stanza.attr.to; log("debug", "%s couldn't join due to invalid password: %s", from, to); local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); reply.tags[1].attr.code = "401"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; - elseif self._occupants[to] -- occupant already exists - and jid_bare(from) ~= jid_bare(self._occupants[to].jid) then -- and has different bare real jid + end +end, -1) + +module:hook("muc-occupant-pre-join/nick-conflict", function(event) + local room, stanza = event.room, event.stanza; + local from, to = stanza.attr.from, stanza.attr.to; + local occupant = room._occupants[to] + if occupant -- occupant already exists + and jid_bare(from) ~= jid_bare(occupant.jid) then -- and has different bare real jid log("debug", "%s couldn't join due to nick conflict: %s", from, to); local reply = st.error_reply(stanza, "cancel", "conflict"):up(); reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end +end, -1) + +module:hook("muc-occupant-pre-join/locked", function(event) + if event.room:is_locked() then -- Deny entry + event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); return true; end - if not next(self._affiliations) then -- new room, no owners - self._affiliations[jid_bare(from)] = "owner"; +end, -1) + +function room_mt:handle_join(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + local affiliation = self:get_affiliation(from); + if affiliation == nil and next(self._affiliations) == nil then -- new room, no owners + affiliation = "owner"; + self._affiliations[jid_bare(from)] = affiliation; if self:is_locked() and not stanza:get_child("x", "http://jabber.org/protocol/muc") then self:unlock(); -- Older groupchat protocol doesn't lock end - elseif self:is_locked() then -- Deny entry - origin.send(st.error_reply(stanza, "cancel", "item-not-found")); - return true; end - local affiliation = self:get_affiliation(from); + if module:fire_event("muc-occupant-pre-join", { + room = self; + origin = origin; + stanza = stanza; + affiliation = affiliation; + }) then return true; end + log("debug", "%s joining as %s", from, to); + local role = self:get_default_role(affiliation) if role then -- new occupant local is_merge = not not self._occupants[to] @@ -528,18 +560,28 @@ function room_mt:handle_join(origin, stanza) self:send_history(from, stanza); self:send_subject(from); return true; - elseif not affiliation then -- registration required for entering members-only room - local reply = st.error_reply(stanza, "auth", "registration-required"):up(); + end +end + +-- registration required for entering members-only room +module:hook("muc-occupant-pre-join/affiliation", function(event) + if event.affiliation == nil and event.room:get_members_only() then + local reply = st.error_reply(event.stanza, "auth", "registration-required"):up(); reply.tags[1].attr.code = "407"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; - else -- banned - local reply = st.error_reply(stanza, "auth", "forbidden"):up(); + end +end, -1) + +-- banned +module:hook("muc-occupant-pre-join/affiliation", function(event) + if event.affiliation == "outcast" then + local reply = st.error_reply(event.stanza, "auth", "forbidden"):up(); reply.tags[1].attr.code = "403"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end +end, -1) function room_mt:handle_available_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; -- cgit v1.2.3 From a86714d770459194c8be1e6350220e2bd9c844e5 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 12:44:31 -0400 Subject: plugins/muc/muc.lib: Better password check --- plugins/muc/muc.lib.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1b3df029..821cb082 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -480,9 +480,9 @@ module:hook("muc-occupant-pre-join/password", function(event) local room, stanza = event.room, event.stanza; local from, to = stanza.attr.from, stanza.attr.to; local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); - password = password and password:get_child("password", "http://jabber.org/protocol/muc"); - password = password and password[1] ~= "" and password[1]; - if room:get_password() and room:get_password() ~= password then + password = password and password:get_child_text("password", "http://jabber.org/protocol/muc"); + if not password or password == "" then password = nil; end + if room:get_password() ~= password then local from, to = stanza.attr.from, stanza.attr.to; log("debug", "%s couldn't join due to invalid password: %s", from, to); local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); -- cgit v1.2.3 From 8de79917fccca8e42804ff91547ff215f4e94c86 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 13:10:54 -0400 Subject: plugins/muc/muc.lib: Add muc-broadcast-message event. Use it for saving to history --- plugins/muc/muc.lib.lua | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 821cb082..4366716a 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -119,25 +119,29 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) self:_route_stanza(stanza); end function room_mt:broadcast_message(stanza, historic) + module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic}); for occupant_jid, o_data in pairs(self._occupants) do self:route_to_occupant(o_data, stanza) end - if historic then -- add to history - return self:save_to_history(stanza) - end -end -function room_mt:save_to_history(stanza) - local history = self._data['history']; - if not history then history = {}; self._data['history'] = history; end - stanza = st.clone(stanza); - stanza.attr.to = ""; - local stamp = datetime.datetime(); - stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 - stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) - local entry = { stanza = stanza, stamp = stamp }; - t_insert(history, entry); - while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end end + +-- add to history +module:hook("muc-broadcast-message", function(event) + if event.historic then + local room = event.room + local history = room._data['history']; + if not history then history = {}; room._data['history'] = history; end + local stanza = st.clone(event.stanza); + stanza.attr.to = ""; + local stamp = datetime.datetime(); + stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 + stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) + local entry = { stanza = stanza, stamp = stamp }; + t_insert(history, entry); + while #history > room:get_historylength() do t_remove(history, 1) end + end +end) + function room_mt:broadcast_except_nick(stanza, nick) for rnick, occupant in pairs(self._occupants) do if rnick ~= nick then -- cgit v1.2.3 From 9dce8113050677fc6e1e227aa3c11644296b1285 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 13:34:06 -0400 Subject: plugins/muc/muc.lib: Add muc-get-history event; it uses an iterator in the event object so that messages don't need to be all in memory at once --- plugins/muc/muc.lib.lua | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 4366716a..791938db 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -193,13 +193,17 @@ local function parse_history(stanza) return maxchars, maxstanzas, since end --- Get history for 'to' -function room_mt:get_history(to, maxchars, maxstanzas, since) - local history = self._data['history']; -- send discussion history - if not history then return function() end end + +module:hook("muc-get-history", function(event) + local room = event.room + local history = room._data['history']; -- send discussion history + if not history then return nil end local history_len = #history - maxstanzas = maxstanzas or history_len + local to = event.to + local maxchars = event.maxchars + local maxstanzas = event.maxstanzas or history_len + local since = event.since local n = 0; local charcount = 0; for i=history_len,1,-1 do @@ -218,7 +222,7 @@ function room_mt:get_history(to, maxchars, maxstanzas, since) end local i = history_len-n+1 - return function() + function event:next_stanza() if i > history_len then return nil end local entry = history[i] local msg = entry.stanza @@ -226,10 +230,19 @@ function room_mt:get_history(to, maxchars, maxstanzas, since) i = i + 1 return msg end -end -function room_mt:send_history(to, stanza) + return true; +end) + +function room_mt:send_history(stanza) local maxchars, maxstanzas, since = parse_history(stanza) - for msg in self:get_history(to, maxchars, maxstanzas, since) do + local event = { + room = self; + to = stanza.attr.from; -- `to` is required to calculate the character count for `maxchars` + maxchars = maxchars, maxstanzas = maxstanzas, since = since; + next_stanza = function() end; -- events should define this iterator + } + module:fire_event("muc-get-history", event) + for msg in event.next_stanza , event do self:_route_stanza(msg); end end -- cgit v1.2.3 From 9b13e219b6b39ba568f62cf821b9a68bc4c56930 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 13:36:43 -0400 Subject: plugins/muc/muc.lib: Extra utility functions around subjects --- plugins/muc/muc.lib.lua | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 791938db..d0a5641c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -246,11 +246,6 @@ function room_mt:send_history(stanza) self:_route_stanza(msg); end end -function room_mt:send_subject(to) - if self._data['subject'] then - self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject'])); - end -end function room_mt:get_disco_info(stanza) local count = 0; for _ in pairs(self._occupants) do count = count + 1; end @@ -277,13 +272,30 @@ function room_mt:get_disco_items(stanza) end return reply; end + +function room_mt:get_subject() + return self._data['subject'], self._data['subject_from'] +end +local function create_subject_message(subject) + return st.message({type='groupchat'}) + :tag('subject'):text(subject):up(); +end +function room_mt:send_subject(to) + local from, subject = self:get_subject() + if subject then + local msg = create_subject_message(subject) + msg.attr.from = from + msg.attr.to = to + self:_route_stanza(msg); + end +end function room_mt:set_subject(current_nick, subject) if subject == "" then subject = nil; end self._data['subject'] = subject; self._data['subject_from'] = current_nick; if self.save then self:save(); end - local msg = st.message({type='groupchat', from=current_nick}) - :tag('subject'):text(subject):up(); + local msg = create_subject_message(subject) + msg.attr.from = current_nick self:broadcast_message(msg, false); return true; end -- cgit v1.2.3 From 2e431e6c6dbb1b184775d4b47780317688a09443 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 16:32:18 -0400 Subject: plugins/muc/muc.lib: Add :broadcast method; use it from :broadcast_except_nick and :broadcast_message --- plugins/muc/muc.lib.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d0a5641c..c955d47b 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -120,9 +120,7 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) end function room_mt:broadcast_message(stanza, historic) module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic}); - for occupant_jid, o_data in pairs(self._occupants) do - self:route_to_occupant(o_data, stanza) - end + self:broadcast(stanza); end -- add to history @@ -143,8 +141,14 @@ module:hook("muc-broadcast-message", function(event) end) function room_mt:broadcast_except_nick(stanza, nick) - for rnick, occupant in pairs(self._occupants) do - if rnick ~= nick then + return self:broadcast(stanza, function(rnick, occupant) return rnick ~= nick end) +end + +-- Broadcast a stanza to all occupants in the room. +-- optionally checks conditional called with nicl +function room_mt:broadcast(stanza, cond_func) + for nick, occupant in pairs(self._occupants) do + if cond_func == nil or cond_func(nick, occupant) then self:route_to_occupant(occupant, stanza) end end -- cgit v1.2.3 From 2d7176e0945645d177bab94195334a887f4fd001 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 18:09:42 -0400 Subject: plugins/muc: Rename admin query hook --- plugins/muc/mod_muc.lua | 8 ++++---- plugins/muc/muc.lib.lua | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index e8782414..8759cba4 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -179,8 +179,8 @@ for event_name, method in pairs { -- Normal room interactions ["iq-get/bare/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; ["iq-get/bare/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; - ["iq-set/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; - ["iq-get/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/bare/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ; + ["iq-get/bare/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_get_command" ; ["iq-set/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; ["iq-get/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; ["message/bare"] = "handle_message_to_room" ; @@ -188,8 +188,8 @@ for event_name, method in pairs { -- Host room ["iq-get/host/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; ["iq-get/host/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; - ["iq-set/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; - ["iq-get/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/host/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ; + ["iq-get/host/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_get_command" ; ["iq-set/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; ["iq-get/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; ["message/host"] = "handle_message_to_room" ; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index c955d47b..7e41b225 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -900,7 +900,7 @@ function room_mt:handle_disco_items_get_query(origin, stanza) return true; end -function room_mt:handle_admin_item_set_command(origin, stanza) +function room_mt:handle_admin_query_set_command(origin, stanza) local item = stanza.tags[1].tags[1]; if item.attr.jid then -- Validate provided JID item.attr.jid = jid_prep(item.attr.jid); @@ -933,7 +933,7 @@ function room_mt:handle_admin_item_set_command(origin, stanza) end end -function room_mt:handle_admin_item_get_command(origin, stanza) +function room_mt:handle_admin_query_get_command(origin, stanza) local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); local item = stanza.tags[1].tags[1]; -- cgit v1.2.3 From 2a107b50cd289c9d522232b3764c308959a7ca52 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 18:10:34 -0400 Subject: plugins/muc/muc.lib: Have timestamp as seconds since epoch inside of history --- plugins/muc/muc.lib.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7e41b225..20cba225 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -131,10 +131,11 @@ module:hook("muc-broadcast-message", function(event) if not history then history = {}; room._data['history'] = history; end local stanza = st.clone(event.stanza); stanza.attr.to = ""; - local stamp = datetime.datetime(); + local ts = gettime(); + local stamp = datetime.datetime(ts); stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) - local entry = { stanza = stanza, stamp = stamp }; + local entry = { stanza = stanza, timestamp = ts }; t_insert(history, entry); while #history > room:get_historylength() do t_remove(history, 1) end end @@ -220,7 +221,7 @@ module:hook("muc-get-history", function(event) charcount = charcount + entry.chars + #to; if charcount > maxchars then break; end end - if since and since > entry.stamp then break; end + if since and since > entry.timestamp then break; end if n + 1 > maxstanzas then break; end n = n + 1; end -- cgit v1.2.3 From f7642cdf7a2f8c4462b476fbb43eced7fcd52a28 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 19:16:46 -0400 Subject: plugins/muc/muc.lib: If decline is to person in room; route to all sessions --- plugins/muc/muc.lib.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 20cba225..27c50cd4 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1137,7 +1137,13 @@ function room_mt:handle_mediated_decline(origin, stanza) end module:hook("muc-decline", function(event) - event.room:_route_stanza(event.stanza); + local room, stanza = event.room, event.stanza + local occupant = room:get_occupant_by_real_jid(stanza.attr.to); + if occupant then + room:route_to_occupant(occupant, stanza) + else + room:route_stanza(stanza); + end return true; end, -1) -- cgit v1.2.3