diff options
Diffstat (limited to 'plugins/muc/muc.lib.lua')
-rw-r--r-- | plugins/muc/muc.lib.lua | 397 |
1 files changed, 232 insertions, 165 deletions
diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index f3e2dd52..0565d692 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1,7 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -9,7 +9,6 @@ local select = select; local pairs, ipairs = pairs, ipairs; -local datamanager = require "util.datamanager"; local datetime = require "util.datetime"; local dataform = require "util.dataforms"; @@ -19,38 +18,25 @@ local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; local st = require "util.stanza"; local log = require "util.logger".init("mod_muc"); -local multitable_new = require "util.multitable".new; local t_insert, t_remove = table.insert, table.remove; 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 = 20; +local default_history_length, max_history_length = 20, math.huge; ------------ -local function filter_xmlns_from_array(array, filters) - local count = 0; - for i=#array,1,-1 do - local attr = array[i].attr; - if filters[attr and attr.xmlns] then - t_remove(array, i); - count = count + 1; - end - end - return count; -end -local function filter_xmlns_from_stanza(stanza, filters) - if filters then - if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then - return stanza, filter_xmlns_from_array(stanza, filters); - end +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 stanza, 0; + return tag; end -local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; + local function get_filtered_presence(stanza) - return filter_xmlns_from_stanza(st.clone(stanza):reset(), presence_filters); + return st.clone(stanza):maptags(presence_filter); end local kickable_error_conditions = { ["gone"] = true; @@ -74,30 +60,23 @@ local function is_kickable_error(stanza) local cond = get_error_condition(stanza); return kickable_error_conditions[cond] and cond; end -local function getUsingPath(stanza, path, getText) - local tag = stanza; - for _, name in ipairs(path) do - if type(tag) ~= 'table' then return; end - tag = tag:child_with_name(name); - end - if tag and getText then tag = table.concat(tag); end - return tag; -end -local function getTag(stanza, path) return getUsingPath(stanza, path); end -local function getText(stanza, path) return getUsingPath(stanza, path, true); end ----------- local room_mt = {}; room_mt.__index = room_mt; +function room_mt:__tostring() + return "MUC room ("..self.jid..")"; +end + function room_mt:get_default_role(affiliation) if affiliation == "owner" or affiliation == "admin" then return "moderator"; elseif affiliation == "member" then return "participant"; elseif not affiliation then - if not self:is_members_only() then - return self:is_moderated() and "visitor" or "participant"; + if not self:get_members_only() then + return self:get_moderated() and "visitor" or "participant"; end end end @@ -133,7 +112,6 @@ function room_mt:broadcast_message(stanza, historic) stanza = st.clone(stanza); stanza.attr.to = ""; local stamp = datetime.datetime(); - local chars = #tostring(stanza); 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) local entry = { stanza = stanza, stamp = stamp }; @@ -169,10 +147,10 @@ function room_mt:send_history(to, stanza) 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 maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history); if not history_tag then maxstanzas = 20; end @@ -185,8 +163,7 @@ function room_mt:send_history(to, stanza) local n = 0; local charcount = 0; - local stanzacount = 0; - + for i=#history,1,-1 do local entry = history[i]; if maxchars then @@ -213,18 +190,20 @@ function room_mt:send_history(to, stanza) end function room_mt:get_disco_info(stanza) + local count = 0; for _ in pairs(self._occupants) do count = count + 1; end return st.reply(stanza):query("http://jabber.org/protocol/disco#info") :tag("identity", {category="conference", type="text", name=self:get_name()}):up() :tag("feature", {var="http://jabber.org/protocol/muc"}):up() :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up() - :tag("feature", {var=self:is_moderated() and "muc_moderated" or "muc_unmoderated"}):up() - :tag("feature", {var=self:is_members_only() and "muc_membersonly" or "muc_open"}):up() - :tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up() - :tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up() + :tag("feature", {var=self:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up() + :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() :add_child(dataform.new({ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }, - { name = "muc#roominfo_description", label = "Description"} + { name = "muc#roominfo_description", label = "Description"}, + { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) } }):form({["muc#roominfo_description"] = self:get_description()}, 'result')) ; end @@ -236,7 +215,6 @@ function room_mt:get_disco_items(stanza) return reply; end function room_mt:set_subject(current_nick, subject) - -- TODO check nick's authority if subject == "" then subject = nil; end self._data['subject'] = subject; self._data['subject_from'] = current_nick; @@ -294,7 +272,7 @@ function room_mt:set_moderated(moderated) if self.save then self:save(true); end end end -function room_mt:is_moderated() +function room_mt:get_moderated() return self._data.moderated; end function room_mt:set_members_only(members_only) @@ -304,7 +282,7 @@ function room_mt:set_members_only(members_only) if self.save then self:save(true); end end end -function room_mt:is_members_only() +function room_mt:get_members_only() return self._data.members_only; end function room_mt:set_persistent(persistent) @@ -314,7 +292,7 @@ function room_mt:set_persistent(persistent) if self.save then self:save(true); end end end -function room_mt:is_persistent() +function room_mt:get_persistent() return self._data.persistent; end function room_mt:set_hidden(hidden) @@ -324,9 +302,74 @@ function room_mt:set_hidden(hidden) if self.save then self:save(true); end end end -function room_mt:is_hidden() +function room_mt:get_hidden() return self._data.hidden; end +function room_mt:get_public() + return not self:get_hidden(); +end +function room_mt:set_public(public) + return self:set_hidden(not public); +end +function room_mt:set_changesubject(changesubject) + changesubject = changesubject and true or nil; + if self._data.changesubject ~= changesubject then + self._data.changesubject = changesubject; + if self.save then self:save(true); end + end +end +function room_mt:get_changesubject() + return self._data.changesubject; +end +function room_mt:get_historylength() + return self._data.history_length or default_history_length; +end +function room_mt:set_historylength(length) + length = math.min(tonumber(length) or default_history_length, max_history_length or math.huge); + if length == default_history_length then + length = nil; + end + self._data.history_length = length; +end + + +local valid_whois = { moderators = true, anyone = true }; + +function room_mt:set_whois(whois) + if valid_whois[whois] and self._data.whois ~= whois then + self._data.whois = whois; + if self.save then self:save(true); end + end +end + +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_to_occupant(origin, stanza) -- PM, vCards, etc local from, to = stanza.attr.from, stanza.attr.to; @@ -346,6 +389,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc 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 @@ -356,7 +400,7 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc 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'}); + :tag("status", {code='110'}):up(); self:_route_stanza(pr); if jid ~= new_jid then pr = st.clone(occupant.sessions[new_jid]) @@ -370,7 +414,6 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc self:broadcast_presence(pr, from); self._occupants[current_nick] = nil; end - self._jid_nick[from] = nil; end elseif not type then -- available if current_nick then @@ -437,6 +480,12 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc 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) @@ -454,10 +503,13 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc if not is_merge then self:broadcast_except_nick(pr, to); end - pr:tag("status", {code='110'}); + 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); @@ -478,25 +530,14 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc end end elseif not current_nick then -- not in room - if type == "error" or type == "result" then - local id = stanza.name == "iq" and stanza.attr.id and base64.decode(stanza.attr.id); - local _nick, _id, _hash = (id or ""):match("^(.+)%z(.*)%z(.+)$"); - local occupant = self._occupants[stanza.attr.to]; - if occupant and _nick and self._jid_nick[_nick] and _id and _hash then - local id, _to = stanza.attr.id; - for jid in pairs(occupant.sessions) do - if md5(jid) == _hash then - _to = jid; - break; - end - end - if _to then - stanza.attr.to, stanza.attr.from, stanza.attr.id = _to, self._jid_nick[_nick], _id; - self:_route_stanza(stanza); - stanza.attr.to, stanza.attr.from, stanza.attr.id = to, from, id; - end + if (type == "error" or type == "result") and stanza.name == "iq" 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 - else + stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; + elseif type ~= "error" then origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); end elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM @@ -508,16 +549,28 @@ function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc local o_data = self._occupants[to]; if o_data then log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); - local jid = o_data.jid; - local bare = jid_bare(jid); - stanza.attr.to, stanza.attr.from = jid, current_nick; - local id = stanza.attr.id; - if stanza.name=='iq' and type=='get' and stanza.tags[1].attr.xmlns == 'vcard-temp' and bare ~= jid then - stanza.attr.to = bare; - stanza.attr.id = base64.encode(jid.."\0"..id.."\0"..md5(from)); + 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.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 - self:_route_stanza(stanza); - stanza.attr.to, stanza.attr.from, stanza.attr.id = to, from, id; 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 @@ -526,15 +579,14 @@ 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():form()) + :add_child(self:get_form_layout(stanza.attr.from):form()) ); end -function room_mt:get_form_layout() - local title = "Configuration for "..self.jid; - return dataform.new({ - title = title, - instructions = title, +function room_mt:get_form_layout(actor) + local form = dataform.new({ + title = "Configuration for "..self.jid, + instructions = "Complete and submit this form to configure the room.", { name = 'FORM_TYPE', type = 'hidden', @@ -556,13 +608,19 @@ function room_mt:get_form_layout() name = 'muc#roomconfig_persistentroom', type = 'boolean', label = 'Make Room Persistent?', - value = self:is_persistent() + value = self:get_persistent() }, { name = 'muc#roomconfig_publicroom', type = 'boolean', label = 'Make Room Publicly Searchable?', - value = not self:is_hidden() + value = not self:get_hidden() + }, + { + name = 'muc#roomconfig_changesubject', + type = 'boolean', + label = 'Allow Occupants to Change Subject?', + value = self:get_changesubject() }, { name = 'muc#roomconfig_whois', @@ -583,22 +641,24 @@ function room_mt:get_form_layout() name = 'muc#roomconfig_moderatedroom', type = 'boolean', label = 'Make Room Moderated?', - value = self:is_moderated() + value = self:get_moderated() }, { name = 'muc#roomconfig_membersonly', type = 'boolean', label = 'Make Room Members-Only?', - value = self:is_members_only() + value = self:get_members_only() + }, + { + name = 'muc#roomconfig_historylength', + type = 'text-single', + label = 'Maximum Number of History Messages Returned by Room', + value = tostring(self:get_historylength()) } }); + return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; end -local valid_whois = { - moderators = true, - anyone = true, -} - function room_mt:process_form(origin, stanza) local query = stanza.tags[1]; local form; @@ -607,69 +667,50 @@ function room_mt:process_form(origin, stanza) 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 - local fields = self:get_form_layout():data(form); + local fields = self:get_form_layout(stanza.attr.from):data(form); if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); return; end - local dirty = false - local name = fields['muc#roomconfig_roomname']; - if name ~= self:get_name() then - self:set_name(name); - end + local changed = {}; - local description = fields['muc#roomconfig_roomdesc']; - if description ~= self:get_description() then - self:set_description(description); + local function handle_option(name, field, allowed) + local new = fields[field]; + if new == nil then return; end + if allowed and not allowed[new] then return; end + if new == self["get_"..name](self) then return; end + changed[name] = true; + self["set_"..name](self, new); end - local persistent = fields['muc#roomconfig_persistentroom']; - dirty = dirty or (self:is_persistent() ~= persistent) - module:log("debug", "persistent=%s", tostring(persistent)); - - local moderated = fields['muc#roomconfig_moderatedroom']; - dirty = dirty or (self:is_moderated() ~= moderated) - module:log("debug", "moderated=%s", tostring(moderated)); - - local membersonly = fields['muc#roomconfig_membersonly']; - dirty = dirty or (self:is_members_only() ~= membersonly) - module:log("debug", "membersonly=%s", tostring(membersonly)); - - local public = fields['muc#roomconfig_publicroom']; - dirty = dirty or (self:is_hidden() ~= (not public and true or nil)) - - local whois = fields['muc#roomconfig_whois']; - if not valid_whois[whois] then - origin.send(st.error_reply(stanza, 'cancel', 'bad-request', "Invalid value for 'whois'")); - return; - end - local whois_changed = self._data.whois ~= whois - self._data.whois = whois - module:log('debug', 'whois=%s', whois) + local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option }; + module:fire_event("muc-config-submitted", event); - local password = fields['muc#roomconfig_roomsecret']; - if self:get_password() ~= password then - self:set_password(password); - end - self:set_moderated(moderated); - self:set_members_only(membersonly); - self:set_persistent(persistent); - self:set_hidden(not public); + handle_option("name", "muc#roomconfig_roomname"); + handle_option("description", "muc#roomconfig_roomdesc"); + handle_option("persistent", "muc#roomconfig_persistentroom"); + handle_option("moderated", "muc#roomconfig_moderatedroom"); + handle_option("members_only", "muc#roomconfig_membersonly"); + handle_option("public", "muc#roomconfig_publicroom"); + handle_option("changesubject", "muc#roomconfig_changesubject"); + handle_option("historylength", "muc#roomconfig_historylength"); + handle_option("whois", "muc#roomconfig_whois", valid_whois); + 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; + end origin.send(st.reply(stanza)); - if dirty or whois_changed then + if next(changed) then local msg = st.message({type='groupchat', from=self.jid}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up() - - if dirty then - msg.tags[1]:tag('status', {code = '104'}):up(); - end - if whois_changed then - local code = (whois == 'moderators') and "173" or "172"; + :tag('status', {code = '104'}):up(); + if changed.whois then + local code = (self:get_whois() == 'moderators') and "173" or "172"; msg.tags[1]:tag('status', {code = code}):up(); end - self:broadcast_message(msg, false) end end @@ -691,15 +732,16 @@ function room_mt:destroy(newjid, reason, password) self._occupants[nick] = nil; end self:set_persistent(false); + 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 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" 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" then + 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; @@ -777,13 +819,13 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha 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")); + 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, "auth", "bad-request")); + origin.send(st.error_reply(stanza, "modify", "bad-request")); elseif child.name == "destroy" then local newjid = child.attr.jid; local reason, password; @@ -804,27 +846,27 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end elseif stanza.name == "message" and type == "groupchat" then - local from, to = stanza.attr.from, stanza.attr.to; - local room = jid_bare(to); + 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, "cancel", "forbidden")); + origin.send(st.error_reply(stanza, "auth", "forbidden")); else local from = stanza.attr.from; stanza.attr.from = current_nick; - local subject = getText(stanza, {"subject"}); + local subject = stanza:get_child_text("subject"); if subject then - if occupant.role == "moderator" then - self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza + 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, "cancel", "forbidden")); + origin.send(st.error_reply(stanza, "auth", "forbidden")); end else - self:broadcast_message(stanza, true); + self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); end stanza.attr.from = from; end @@ -842,8 +884,8 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha elseif type ~= "error" and type ~= "result" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end - elseif stanza.name == "message" and not stanza.attr.type 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 + elseif stanza.name == "message" and 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 @@ -866,7 +908,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha :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:is_members_only() and not self:get_affiliation(_invitee) then + 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 @@ -907,8 +949,25 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then return nil, "modify", "not-acceptable"; end - if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end - if jid_bare(actor) == jid then return nil, "cancel", "not-allowed"; end + if actor ~= true then + local actor_affiliation = self:get_affiliation(actor); + local target_affiliation = self:get_affiliation(jid); + if target_affiliation == affiliation then -- no change, shortcut + if callback then callback(); end + return true; + end + if actor_affiliation ~= "owner" then + if affiliation == "owner" or affiliation == "admin" or actor_affiliation ~= "admin" or target_affiliation == "owner" or target_affiliation == "admin" then + return nil, "cancel", "not-allowed"; + end + elseif target_affiliation == "owner" and jid_bare(actor) == jid then -- self change + local is_last = true; + for j, aff in pairs(self._affiliations) do if j ~= jid and aff == "owner" then is_last = false; break; end end + if is_last then + return nil, "cancel", "conflict"; + end + end + end self._affiliations[jid] = affiliation; local role = self:get_default_role(affiliation); local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) @@ -960,11 +1019,12 @@ function room_mt:get_role(nick) return session and session.role or nil; end function room_mt:can_set_role(actor_jid, occupant_jid, role) - local actor = self._occupants[self._jid_nick[actor_jid]]; local occupant = self._occupants[occupant_jid]; - - if not occupant or not actor then return nil, "modify", "not-acceptable"; end + if not occupant or not actor_jid then return nil, "modify", "not-acceptable"; end + if actor_jid == true then return true; end + + local actor = self._occupants[self._jid_nick[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 @@ -1061,10 +1121,17 @@ function _M.new_room(jid, config) _occupants = {}; _data = { whois = 'moderators'; - history_length = (config and config.history_length); + history_length = math.min((config and config.history_length) + or default_history_length, max_history_length); }; _affiliations = {}; }, room_mt); end +function _M.set_max_history_length(_max_history_length) + max_history_length = _max_history_length or math.huge; +end + +_M.room_mt = room_mt; + return _M; |