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(-) (limited to 'plugins/muc') 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