From 76b14ec39b95d3f7b09b4cd3766e87c9fdd7fcd6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Oct 2013 16:40:27 +0200 Subject: mod_pubsub, util.pubsub: Keep track of the order of items --- plugins/mod_pubsub/mod_pubsub.lua | 2 +- plugins/mod_pubsub/pubsub.lib.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua index 81a66f8b..2868d409 100644 --- a/plugins/mod_pubsub/mod_pubsub.lua +++ b/plugins/mod_pubsub/mod_pubsub.lua @@ -103,7 +103,7 @@ module:hook("host-disco-items-node", function (event) return origin.send(pubsub_error_reply(stanza, ret)); end - for id, item in pairs(ret) do + for _, id in ipairs(ret) do reply:tag("item", { jid = module.host, name = id }):up(); end event.exists = true; diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua index 2b015e34..4e9acd68 100644 --- a/plugins/mod_pubsub/pubsub.lib.lua +++ b/plugins/mod_pubsub/pubsub.lib.lua @@ -42,8 +42,8 @@ function handlers.get_items(origin, stanza, items, service) end local data = st.stanza("items", { node = node }); - for _, entry in pairs(results) do - data:add_child(entry); + for _, id in ipairs(results) do + data:add_child(results[id]); end local reply; if data then -- cgit v1.2.3 From 841a59e0e13f566e3c2f324f8c54470dbec0e8e9 Mon Sep 17 00:00:00 2001 From: Florian Zeitz Date: Fri, 4 Oct 2013 18:42:44 +0200 Subject: mod_pep_plus: An util.pubsub based PEP module --- plugins/mod_pep_plus.lua | 368 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 plugins/mod_pep_plus.lua (limited to 'plugins') diff --git a/plugins/mod_pep_plus.lua b/plugins/mod_pep_plus.lua new file mode 100644 index 00000000..4a74e437 --- /dev/null +++ b/plugins/mod_pep_plus.lua @@ -0,0 +1,368 @@ +local pubsub = require "util.pubsub"; +local jid_bare = require "util.jid".bare; +local jid_split = require "util.jid".split; +local set_new = require "util.set".new; +local st = require "util.stanza"; +local calculate_hash = require "util.caps".calculate_hash; +local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed; + +local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; +local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event"; +local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; + +local lib_pubsub = module:require "pubsub"; +local handlers = lib_pubsub.handlers; +local pubsub_error_reply = lib_pubsub.pubsub_error_reply; + +local services = {}; +local recipients = {}; +local hash_map = {}; + +function module.save() + return { services = services }; +end + +function module.restore(data) + services = data.services; +end + +local function subscription_presence(user_bare, recipient) + local recipient_bare = jid_bare(recipient); + if (recipient_bare == user_bare) then return true; end + local username, host = jid_split(user_bare); + return is_contact_subscribed(username, host, recipient_bare); +end + +local function get_broadcaster(name) + local function simple_broadcast(kind, node, jids, item) + if item then + item = st.clone(item); + item.attr.xmlns = nil; -- Clear the pubsub namespace + end + local message = st.message({ from = name, type = "headline" }) + :tag("event", { xmlns = xmlns_pubsub_event }) + :tag(kind, { node = node }) + :add_child(item); + for jid in pairs(jids) do + module:log("debug", "Sending notification to %s from %s: %s", jid, name, tostring(item)); + message.attr.to = jid; + module:send(message); + end + end + return simple_broadcast; +end + +local function get_pep_service(name) + if services[name] then + return services[name]; + end + services[name] = pubsub.new({ + capabilities = { + none = { + create = false; + publish = false; + retract = false; + get_nodes = false; + + subscribe = false; + unsubscribe = false; + get_subscription = false; + get_subscriptions = false; + get_items = false; + + subscribe_other = false; + unsubscribe_other = false; + get_subscription_other = false; + get_subscriptions_other = false; + + be_subscribed = true; + be_unsubscribed = true; + + set_affiliation = false; + }; + subscriber = { + create = false; + publish = false; + retract = false; + get_nodes = true; + + subscribe = true; + unsubscribe = true; + get_subscription = true; + get_subscriptions = true; + get_items = true; + + subscribe_other = false; + unsubscribe_other = false; + get_subscription_other = false; + get_subscriptions_other = false; + + be_subscribed = true; + be_unsubscribed = true; + + set_affiliation = false; + }; + publisher = { + create = false; + publish = true; + retract = true; + get_nodes = true; + + subscribe = true; + unsubscribe = true; + get_subscription = true; + get_subscriptions = true; + get_items = true; + + subscribe_other = false; + unsubscribe_other = false; + get_subscription_other = false; + get_subscriptions_other = false; + + be_subscribed = true; + be_unsubscribed = true; + + set_affiliation = false; + }; + owner = { + create = true; + publish = true; + retract = true; + delete = true; + get_nodes = true; + + subscribe = true; + unsubscribe = true; + get_subscription = true; + get_subscriptions = true; + get_items = true; + + + subscribe_other = true; + unsubscribe_other = true; + get_subscription_other = true; + get_subscriptions_other = true; + + be_subscribed = true; + be_unsubscribed = true; + + set_affiliation = true; + }; + }; + + autocreate_on_publish = true; + autocreate_on_subscribe = true; + + broadcaster = get_broadcaster(name); + get_affiliation = function (jid) + if jid_bare(jid) == name then + return "owner"; + elseif subscription_presence(name, jid) then + return "subscriber"; + end + end; + + normalize_jid = jid_bare; + }); + return services[name]; +end + +function handle_pubsub_iq(event) + local origin, stanza = event.origin, event.stanza; + local pubsub = stanza.tags[1]; + local action = pubsub.tags[1]; + if not action then + return origin.send(st.error_reply(stanza, "cancel", "bad-request")); + end + local service_name = stanza.attr.to or origin.username.."@"..origin.host + local service = get_pep_service(service_name); + local handler = handlers[stanza.attr.type.."_"..action.name]; + if handler then + handler(origin, stanza, action, service); + return true; + end +end + +module:hook("iq/bare/"..xmlns_pubsub..":pubsub", handle_pubsub_iq); +module:hook("iq/bare/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq); + +module:add_identity("pubsub", "pep", module:get_option_string("name", "Prosody")); +module:add_feature("http://jabber.org/protocol/pubsub#publish"); + +local function get_caps_hash_from_presence(stanza, current) + local t = stanza.attr.type; + if not t then + local child = stanza:get_child("c", "http://jabber.org/protocol/caps"); + if child then + local attr = child.attr; + if attr.hash then -- new caps + if attr.hash == 'sha-1' and attr.node and attr.ver then + return attr.ver, attr.node.."#"..attr.ver; + end + else -- legacy caps + if attr.node and attr.ver then + return attr.node.."#"..attr.ver.."#"..(attr.ext or ""), attr.node.."#"..attr.ver; + end + end + end + return; -- no or bad caps + elseif t == "unavailable" or t == "error" then + return; + end + return current; -- no caps, could mean caps optimization, so return current +end + +local function resend_last_item(jid, node, service) + local ok, items = service:get_items(node, jid); + if not ok then return; end + for i, id in ipairs(items) do + service.config.broadcaster("items", node, { [jid] = true }, items[id]); + end +end + +local function update_subscriptions(recipient, service_name, nodes) + local service = get_pep_service(service_name); + + recipients[service_name] = recipients[service_name] or {}; + nodes = nodes or set_new(); + local old = recipients[service_name][recipient]; + + if old and type(old) == table then + for node in pairs((old - nodes):items()) do + service:remove_subscription(node, recipient, recipient); + end + end + + for node in nodes:items() do + service:add_subscription(node, recipient, recipient); + resend_last_item(recipient, node, service); + end + recipients[service_name][recipient] = nodes; +end + +module:hook("presence/bare", function(event) + -- inbound presence to bare JID recieved + local origin, stanza = event.origin, event.stanza; + local user = stanza.attr.to or (origin.username..'@'..origin.host); + local t = stanza.attr.type; + local self = not stanza.attr.to; + local service = get_pep_service(user); + + if not t then -- available presence + if self or subscription_presence(user, stanza.attr.from) then + local recipient = stanza.attr.from; + local current = recipients[user] and recipients[user][recipient]; + local hash, query_node = get_caps_hash_from_presence(stanza, current); + if current == hash or (current and current == hash_map[hash]) then return; end + if not hash then + update_subscriptions(recipient, user); + else + recipients[user] = recipients[user] or {}; + if hash_map[hash] then + update_subscriptions(recipient, user, hash_map[hash]); + else + recipients[user][recipient] = hash; + local from_bare = origin.type == "c2s" and origin.username.."@"..origin.host; + if self or origin.type ~= "c2s" or (recipients[from_bare] and recipients[from_bare][origin.full_jid]) ~= hash then + -- COMPAT from ~= stanza.attr.to because OneTeam can't deal with missing from attribute + origin.send( + st.stanza("iq", {from=user, to=stanza.attr.from, id="disco", type="get"}) + :tag("query", {xmlns = "http://jabber.org/protocol/disco#info", node = query_node}) + ); + end + end + end + end + elseif t == "unavailable" then + update_subscriptions(stanza.attr.from, user); + elseif not self and t == "unsubscribe" then + local from = jid_bare(stanza.attr.from); + local subscriptions = recipients[user]; + if subscriptions then + for subscriber in pairs(subscriptions) do + if jid_bare(subscriber) == from then + update_subscriptions(subscriber, user); + end + end + end + end +end, 10); + +module:hook("iq-result/bare/disco", function(event) + local origin, stanza = event.origin, event.stanza; + local disco = stanza:get_child("query", "http://jabber.org/protocol/disco#info"); + if not disco then + return; + end + + -- Process disco response + local self = not stanza.attr.to; + local user = stanza.attr.to or (origin.username..'@'..origin.host); + local contact = stanza.attr.from; + local current = recipients[user] and recipients[user][contact]; + if type(current) ~= "string" then return; end -- check if waiting for recipient's response + local ver = current; + if not string.find(current, "#") then + ver = calculate_hash(disco.tags); -- calculate hash + end + local notify = set_new(); + for _, feature in pairs(disco.tags) do + if feature.name == "feature" and feature.attr.var then + local nfeature = feature.attr.var:match("^(.*)%+notify$"); + if nfeature then notify:add(nfeature); end + end + end + hash_map[ver] = notify; -- update hash map + if self then + for jid, item in pairs(origin.roster) do -- for all interested contacts + if item.subscription == "both" or item.subscription == "from" then + if not recipients[jid] then recipients[jid] = {}; end + update_subscriptions(contact, jid, notify); + end + end + end + update_subscriptions(contact, user, notify); +end); + +module:hook("account-disco-info-node", function(event) + local reply, stanza, origin = event.reply, event.stanza, event.origin; + local service_name = stanza.attr.to or origin.username.."@"..origin.host + local service = get_pep_service(service_name); + local node = event.node; + local ok = service:get_items(node, jid_bare(stanza.attr.from) or true); + if not ok then return; end + event.exists = true; + reply:tag('identity', {category='pubsub', type='leaf'}):up(); +end); + +module:hook("account-disco-info", function(event) + local reply = event.reply; + reply:tag('identity', {category='pubsub', type='pep'}):up(); + reply:tag('feature', {var='http://jabber.org/protocol/pubsub#publish'}):up(); +end); + +module:hook("account-disco-items-node", function(event) + local reply, stanza, origin = event.reply, event.stanza, event.origin; + local node = event.node; + local service_name = stanza.attr.to or origin.username.."@"..origin.host + local service = get_pep_service(service_name); + local ok, ret = service:get_items(node, jid_bare(stanza.attr.from) or true); + if not ok then return; end + event.exists = true; + for _, id in ipairs(ret) do + reply:tag("item", { jid = service_name, name = id }):up(); + end +end); + +module:hook("account-disco-items", function(event) + local reply, stanza, origin = event.reply, event.stanza, event.origin; + + local service_name = reply.attr.from or origin.username.."@"..origin.host + local service = get_pep_service(service_name); + local ok, ret = service:get_nodes(jid_bare(stanza.attr.from)); + if not ok then return; end + + for node, node_obj in pairs(ret) do + reply:tag("item", { jid = service_name, node = node, name = node_obj.config.name }):up(); + end +end); -- cgit v1.2.3 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 (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(+) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(+) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(+) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(+) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(+) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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(-) (limited to 'plugins') 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 985fd9a14a12618d1e3759426d55c02df2957d51 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 19:16:13 -0400 Subject: plugins/muc: Massive refactor We now have occupant objects; you grab them, modify them, save them. New presence handling code. Modify all presence sending to go via new functions. --- plugins/muc/mod_muc.lua | 19 +- plugins/muc/muc.lib.lua | 779 +++++++++++++++++++++++-------------------- plugins/muc/occupant.lib.lua | 85 +++++ 3 files changed, 503 insertions(+), 380 deletions(-) create mode 100644 plugins/muc/occupant.lib.lua (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 8759cba4..a8a6388d 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -228,27 +228,14 @@ module.restore = function(data) hosts[module:get_host()].muc = { rooms = rooms }; end -function shutdown_room(room, stanza) - for nick, occupant in pairs(room._occupants) do - stanza.attr.from = nick; - for jid in pairs(occupant.sessions) do - stanza.attr.to = jid; - room:_route_stanza(stanza); - room._jid_nick[jid] = nil; - end - room._occupants[nick] = nil; - end -end function shutdown_component() if not saved then - local stanza = st.presence({type = "unavailable"}) - :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("item", { affiliation='none', role='none' }):up() + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("status", { code = "332"}):up(); for roomjid, room in pairs(rooms) do - shutdown_room(room, stanza); + room:clear(x); end - shutdown_room(host_room, stanza); + host_room:clear(x); end end module.unload = shutdown_component; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 27c50cd4..76fd7ee2 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -25,23 +25,9 @@ local setmetatable = setmetatable; local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; -local default_history_length, max_history_length = 20, math.huge; +local occupant_lib = module:require "muc/occupant" -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 -end +local default_history_length, max_history_length = 20, math.huge; local is_kickable_error do local kickable_error_conditions = { @@ -96,28 +82,103 @@ function room_mt:is_locked() return not not self.locked end -function room_mt:route_to_occupant(o_data, stanza) +--- Occupant functions +function room_mt:new_occupant(bare_real_jid, nick) + local occupant = occupant_lib.new(bare_real_jid, nick); + local affiliation = self:get_affiliation(bare_real_jid); + occupant.role = self:get_default_role(affiliation); + return occupant; +end + +function room_mt:get_occupant_by_nick(nick) + local occupant = self._occupants[nick]; + if occupant == nil then return nil end + return occupant_lib.copy(occupant); +end + +do + local function next_copied_occupant(occupants, occupant_jid) + local next_occupant_jid, raw_occupant = next(occupants, occupant_jid); + if next_occupant_jid == nil then return nil end + return next_occupant_jid, occupant_lib.copy(raw_occupant); + end + function room_mt:each_occupant(read_only) + return next_copied_occupant, self._occupants, nil; + end +end + +function room_mt:get_occupant_by_real_jid(real_jid) + local occupant_jid = self:get_occupant_jid(real_jid); + if occupant_jid == nil then return nil end + return self:get_occupant_by_nick(occupant_jid); +end + +function room_mt:save_occupant(occupant) + occupant = occupant_lib.copy(occupant); -- So that occupant can be modified more + local id = occupant.nick + + -- Need to maintain _jid_nick secondary index + local old_occupant = self._occupants[id]; + if old_occupant then + for real_jid in pairs(old_occupant.sessions) do + self._jid_nick[real_jid] = nil; + end + end + if occupant.role ~= nil and next(occupant.sessions) then + for real_jid, presence in occupant:each_session() do + self._jid_nick[real_jid] = occupant.nick; + end + else + occupant = nil + end + self._occupants[id] = occupant +end + +function room_mt:route_to_occupant(occupant, stanza) local to = stanza.attr.to; - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); + for jid, pr in occupant:each_session() do + if pr.attr.type ~= "unavailable" then + stanza.attr.to = jid; + self:route_stanza(stanza); + end 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]; - stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none", nick=nick}):up(); - if code then - stanza:tag("status", {code=code}):up(); +-- Adds an item to an "x" element. +-- actor is the attribute table +local function add_item(x, affiliation, role, jid, nick, actor, reason) + x:tag("item", {affiliation = affiliation; role = role; jid = jid; nick = nick;}) + if actor then + x:tag("actor", actor):up() + end + if reason then + x:tag("reason"):text(reason):up() + end + x:up(); + return x +end +-- actor is (real) jid +function room_mt:build_item_list(occupant, x, is_anonymous, nick, actor, reason) + local affiliation = self:get_affiliation(occupant.bare_jid); + local role = occupant.role; + local actor_jid = actor and self:get_occupant_jid(actor); + if actor then + actor = {nick = select(3,jid_split(actor_jid))}; + end + if is_anonymous then + add_item(x, affiliation, role, nil, nick, actor, reason); + else + if actor_jid then + actor.jid = actor_jid; + end + for real_jid, session in occupant:each_session() do + add_item(x, affiliation, role, real_jid, nick, actor, reason); + end end - self:broadcast_except_nick(stanza, stanza.attr.from); - stanza:tag("status", {code='110'}):up(); - stanza.attr.to = sid; - self:_route_stanza(stanza); + return x end + function room_mt:broadcast_message(stanza, historic) module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic}); self:broadcast(stanza); @@ -139,31 +200,89 @@ module:hook("muc-broadcast-message", function(event) 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) - return self:broadcast(stanza, function(rnick, occupant) return rnick ~= nick end) -end +end); -- Broadcast a stanza to all occupants in the room. --- optionally checks conditional called with nicl +-- optionally checks conditional called with (nick, occupant) function room_mt:broadcast(stanza, cond_func) - for nick, occupant in pairs(self._occupants) do + for nick, occupant in self:each_occupant() do if cond_func == nil or cond_func(nick, occupant) then self:route_to_occupant(occupant, stanza) end end end -function room_mt:send_occupant_list(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]); - pres.attr.to, pres.attr.from = to, occupant; - pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=o_data.affiliation or "none", role=o_data.role or "none"}):up(); - self:_route_stanza(pres); +-- Broadcasts an occupant's presence to the whole room +-- Takes (and modifies) the x element that goes into the stanzas +function room_mt:publicise_occupant_status(occupant, full_x, actor, reason) + local anon_x; + local has_anonymous = self:get_whois() ~= "anyone"; + if has_anonymous then + anon_x = st.clone(full_x); + self:build_item_list(occupant, anon_x, true, nil, actor, reason); + end + self:build_item_list(occupant,full_x, false, nil, actor, reason); + + -- General populance + local full_p + if occupant.role ~= nil then + -- Try to use main jid's presence + local pr = occupant:get_presence(); + if pr ~= nil then + full_p = st.clone(pr); + end + end + if full_p == nil then + full_p = st.presence{from=occupant.nick; type="unavailable"}; + end + local anon_p; + if has_anonymous then + anon_p = st.clone(full_p); + anon_p:add_child(anon_x); + end + full_p:add_child(full_x); + + for nick, n_occupant in self:each_occupant() do + if nick ~= occupant.nick or n_occupant.role == nil then + local pr = full_p; + if has_anonymous and n_occupant.role ~= "moderators" and occupant.bare_jid ~= n_occupant.bare_jid then + pr = anon_p; + end + self:route_to_occupant(n_occupant, pr); + end + end + + -- Presences for occupant itself + full_x:tag("status", {code = "110";}):up(); + if occupant.role == nil then + -- They get an unavailable + self:route_to_occupant(occupant, full_p); + else + -- use their own presences as templates + for full_jid, pr in occupant:each_session() do + if pr.attr.type ~= "unavailable" then + pr = st.clone(pr); + pr.attr.to = full_jid; + -- You can always see your own full jids + pr:add_child(full_x); + self:route_stanza(pr); + end + end + end +end + +function room_mt:send_occupant_list(to, filter) + local to_occupant = self:get_occupant_by_real_jid(to); + local has_anonymous = self:get_whois() ~= "anyone" + for occupant_jid, occupant in self:each_occupant() do + if filter and filter(occupant_jid, occupant) then + local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); + local is_anonymous = has_anonymous and occupant.role ~= "moderator" and to_occupant.bare_jid ~= occupant.bare_jid; + self:build_item_list(occupant, x, is_anonymous); + local pres = st.clone(occupant:get_presence()); + pres.attr.to = to; + pres:add_child(x); + self:route_stanza(pres); end end end @@ -248,12 +367,12 @@ function room_mt:send_history(stanza) } module:fire_event("muc-get-history", event) for msg in event.next_stanza , event do - self:_route_stanza(msg); + self:route_stanza(msg); end end function room_mt:get_disco_info(stanza) - local count = 0; for _ in pairs(self._occupants) do count = count + 1; end + local count = 0; for _ in self:each_occupant() 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() @@ -272,7 +391,7 @@ function room_mt:get_disco_info(stanza) end function room_mt:get_disco_items(stanza) local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items"); - for room_jid in pairs(self._occupants) do + for room_jid in self:each_occupant() do reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up(); end return reply; @@ -291,7 +410,7 @@ function room_mt:send_subject(to) local msg = create_subject_message(subject) msg.attr.from = from msg.attr.to = to - self:_route_stanza(msg); + self:route_stanza(msg); end end function room_mt:set_subject(current_nick, subject) @@ -306,14 +425,20 @@ function room_mt:set_subject(current_nick, subject) end function room_mt:handle_kickable(origin, stanza) + local real_jid = stanza.attr.from; + local occupant = self:get_occupant_by_real_jid(real_jid); + if occupant == nil then return nil; end 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 - 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 + occupant:set_session(real_jid, st.presence({type="unavailable"}) + :tag('status'):text(error_message)); + self:save_occupant(occupant); + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}) + :tag("status", {code = "307"}) + self:publicise_occupant_status(occupant, x); return true; end @@ -428,91 +553,26 @@ function room_mt:get_whois() return self._data.whois; end -function room_mt:handle_unavailable_to_occupant(origin, stanza) - local from = stanza.attr.from; - local current_nick = self:get_occupant_jid(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; - module:fire_event("muc-occupant-left", { room = self; nick = current_nick; }); +module:hook("muc-room-pre-create", function(event) + local room = event.room; + if room:is_locked() and not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then + room:unlock(); -- Older groupchat protocol doesn't lock end - return true; -end +end, 10); -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 to_nick = select(3, jid_split(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] = occupant; - 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 +-- Give the room creator owner affiliation +module:hook("muc-room-pre-create", function(event) + event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); +end, -1); 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) + or module:fire_event("muc-occupant-pre-join/locked", 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; local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); password = password and password:get_child_text("password", "http://jabber.org/protocol/muc"); if not password or password == "" then password = nil; end @@ -526,81 +586,19 @@ module:hook("muc-occupant-pre-join/password", function(event) 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"; - 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 -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 - end - 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] - 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:get_whois() == 'anyone' then - pr:tag("status", {code='100'}):up(); - end - if self:is_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; - end -end +end, -1); -- 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(); + local room, stanza = event.room, event.stanza; + local affiliation = room:get_affiliation(stanza.attr.from); + if affiliation == nil and event.room:get_members_only() then + local reply = st.error_reply(stanza, "auth", "registration-required"):up(); reply.tags[1].attr.code = "407"; event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; @@ -609,43 +607,173 @@ 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(); + local room, stanza = event.room, event.stanza; + local affiliation = room:get_affiliation(stanza.attr.from); + if affiliation == "outcast" then + local reply = st.error_reply(stanza, "auth", "forbidden"):up(); reply.tags[1].attr.code = "403"; event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end end, -1) -function room_mt:handle_available_to_occupant(origin, stanza) - local from, to = stanza.attr.from, stanza.attr.to; - 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 - 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 - return self:handle_join(origin, stanza) - end -end +module:hook("muc-occupant-joined", function(event) + local room, stanza = event.room, event.stanza; + local real_jid = stanza.attr.from; + room:send_occupant_list(real_jid, function(nick, occupant) + -- Don't include self + return occupant.sessions[real_jid] == nil + end); + room:send_history(stanza); + room:send_subject(real_jid); +end, -1); 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_kickable(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 == nil or type == "unavailable" then + local real_jid = stanza.attr.from; + local bare_jid = jid_bare(real_jid); + local orig_occupant, dest_occupant; + local is_new_room = next(self._affiliations) == nil; + if is_new_room then + if type == "unavailable" then return true; end -- Unavailable from someone not in the room + if module:fire_event("muc-room-pre-create", { + room = self; + origin = origin; + stanza = stanza; + }) then return true; end + else + orig_occupant = self:get_occupant_by_real_jid(real_jid); + if type == "unavailable" and orig_occupant == nil then return true; end -- Unavailable from someone not in the room + end + local is_first_dest_session; + if type == "unavailable" then + -- dest_occupant = nil + elseif orig_occupant and orig_occupant.nick == stanza.attr.to then -- Just a presence update + log("debug", "presence update for %s from session %s", orig_occupant.nick, real_jid); + dest_occupant = orig_occupant; + else + local dest_jid = stanza.attr.to; + dest_occupant = self:get_occupant_by_nick(dest_jid); + if dest_occupant == nil then + log("debug", "no occupant found for %s; creating new occupant object for %s", dest_jid, real_jid); + is_first_dest_session = true; + dest_occupant = self:new_occupant(bare_jid, dest_jid); + else + is_first_dest_session = false; + end + end + local is_last_orig_session; + if orig_occupant ~= nil then + -- Is there are least 2 sessions? + is_last_orig_session = next(orig_occupant.sessions, next(orig_occupant.sessions)) == nil; + end + + local event, event_name = { + room = self; + origin = origin; + stanza = stanza; + is_first_session = is_first_dest_session; + is_last_session = is_last_orig_session; + }; + if orig_occupant == nil then + event_name = "muc-occupant-pre-join"; + event.is_new_room = is_new_room; + elseif dest_occupant == nil then + event_name = "muc-occupant-pre-leave"; + else + event_name = "muc-occupant-pre-change"; + end + if module:fire_event(event_name, event) then return true; end + + -- Check for nick conflicts + if dest_occupant ~= nil and not is_first_dest_session and bare_jid ~= jid_bare(dest_occupant.bare_jid) then -- new nick or has different bare real jid + log("debug", "%s couldn't join due to nick conflict: %s", real_jid, dest_occupant.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; + end + + -- Send presence stanza about original occupant + if orig_occupant ~= nil and orig_occupant ~= dest_occupant then + local orig_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + + if dest_occupant == nil then -- Session is leaving + log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); + orig_occupant:set_session(real_jid, stanza); + else + log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick); + orig_occupant:remove_session(real_jid); -- If we are moving to a new nick; we don't want to get our own presence + + local dest_nick = select(3, jid_split(dest_occupant.nick)); + local affiliation = self:get_affiliation(bare_jid); + + -- This session + if not is_first_dest_session then -- User is swapping into another pre-existing session + log("debug", "session %s is swapping into multisession %s, showing it leave.", real_jid, dest_occupant.nick); + -- Show the other session leaving + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}) + :tag("status"):text("you are joining pre-existing session " .. dest_nick):up(); + add_item(x, affiliation, "none"); + local pr = st.presence{from = dest_occupant.nick, to = real_jid, type = "unavailable"} + :add_child(x); + self:route_stanza(pr); + else + if is_last_orig_session then -- User is moving to a new session + log("debug", "no sessions in %s left; marking as nick change", orig_occupant.nick); + -- Everyone gets to see this as a nick change + local jid = self:get_whois() ~= "anyone" and real_jid or nil; -- FIXME: mods should see real jids + add_item(orig_x, affiliation, orig_occupant.role, jid, dest_nick); + orig_x:tag("status", {code = "303";}):up(); + end + end + -- The session itself always sees a nick change + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + add_item(x, affiliation, orig_occupant.role, real_jid, dest_nick); + -- self:build_item_list(orig_occupant, x, false); -- COMPAT + x:tag("status", {code = "303";}):up(); + x:tag("status", {code = "110";}):up(); + self:route_stanza(st.presence{from = dest_occupant.nick, to = real_jid, type = "unavailable"}:add_child(x)); + end + self:save_occupant(orig_occupant); + self:publicise_occupant_status(orig_occupant, orig_x); + + if is_last_orig_session then + module:fire_event("muc-occupant-left", {room = self; nick = orig_occupant.nick;}); + end + end + + if dest_occupant ~= nil then + dest_occupant:set_session(real_jid, stanza); + local dest_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + if is_new_room then + dest_x:tag("status", {code = "201"}):up(); + end + if orig_occupant == nil and self:get_whois() == "anyone" then + dest_x:tag("status", {code = "100"}):up(); + end + self:save_occupant(dest_occupant); + self:publicise_occupant_status(dest_occupant, dest_x); + + if orig_occupant ~= nil and orig_occupant ~= dest_occupant and not is_last_orig_session then -- If user is swapping and wasn't last original session + log("debug", "session %s split nicks; showing %s rejoining", real_jid, orig_occupant.nick); + -- Show the original nick joining again + local pr = st.clone(orig_occupant:get_presence()); + pr.attr.to = real_jid; + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + self:build_item_list(orig_occupant, x, false); + -- TODO: new status code to inform client this was the multi-session it left? + pr:add_child(x); + self:route_stanza(pr); + end + + if orig_occupant == nil and is_first_dest_session then + module:fire_event("muc-occupant-joined", {room = self; nick = dest_occupant.nick; stanza = stanza;}); + end + 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? @@ -659,14 +787,14 @@ function room_mt:handle_iq_to_occupant(origin, stanza) local type = stanza.attr.type; local id = stanza.attr.id; local current_nick = self:get_occupant_jid(from); - local o_data = self._occupants[to]; + local occupant = self:get_occupant_by_nick(to); if (type == "error" or type == "result") then do -- deconstruct_stanza_id - if not current_nick or not o_data then return nil; end + if not current_nick or not occupant 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 + for to_jid in occupant:each_session() do if md5(to_jid) == to_jid_hash then session_jid = to_jid; break; @@ -676,7 +804,7 @@ function room_mt:handle_iq_to_occupant(origin, stanza) 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); + self:route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; else -- Type is "get" or "set" @@ -684,19 +812,19 @@ function room_mt:handle_iq_to_occupant(origin, stanza) origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); return true; end - if not o_data then -- recipient not in room + if not occupant then -- recipient not in room origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); return true; end do -- construct_stanza_id - stanza.attr.id = base64.encode(o_data.jid.."\0"..stanza.attr.id.."\0"..md5(from)); + stanza.attr.id = base64.encode(occupant.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); + stanza.attr.from, stanza.attr.to = current_nick, occupant.jid; + log("debug", "%s sent private iq stanza to %s (%s)", from, to, occupant.jid); if stanza.tags[1].attr.xmlns == 'vcard-temp' then stanza.attr.to = jid_bare(stanza.attr.to); end - self:_route_stanza(stanza); + self:route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; end @@ -720,7 +848,7 @@ function room_mt:handle_message_to_occupant(origin, stanza) return self:handle_kickable(origin, stanza); -- send unavailable end - local o_data = self._occupants[to]; + local o_data = self:get_occupant_by_nick(to); if not o_data then origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); return true; @@ -870,23 +998,29 @@ function room_mt:process_form(origin, stanza) end end -function room_mt:destroy(newjid, reason, password) - local pr = st.presence({type = "unavailable"}) - :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("item", { affiliation='none', role='none' }):up() - :tag("destroy", {jid=newjid}) - if reason then pr:tag("reason"):text(reason):up(); end - if password then pr:tag("password"):text(password):up(); end - for nick, occupant in pairs(self._occupants) do - pr.attr.from = nick; - for jid in pairs(occupant.sessions) do - pr.attr.to = jid; - self:_route_stanza(pr); - self._jid_nick[jid] = nil; - end - self._occupants[nick] = nil; - module:fire_event("muc-occupant-left", { room = self; nick = nick; }); +-- Removes everyone from the room +function room_mt:clear(x) + x = x or st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); + local occupants_updated = {}; + for nick, occupant in self:each_occupant() do + occupant.role = nil; + self:save_occupant(occupant); + occupants_updated[occupant] = true; end + for occupant in pairs(occupants_updated) do + self:publicise_occupant_status(occupant, x); + module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; }); + end +end + +function room_mt:destroy(newjid, reason, password) + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("item", { affiliation='none', role='none' }):up() + :tag("destroy", {jid=newjid}); + if reason then x:tag("reason"):text(reason):up(); end + if password then x:tag("password"):text(password):up(); end + x:up(); + self:clear(x); self:set_persistent(false); module:fire_event("muc-room-destroyed", { room = self }); end @@ -911,7 +1045,7 @@ function room_mt:handle_admin_query_set_command(origin, stanza) 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]; + local occupant = self:get_occupant_by_nick(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:get_occupant_jid(item.attr.jid); @@ -958,18 +1092,7 @@ function room_mt:handle_admin_query_get_command(origin, stanza) local role = self:get_role(self:get_occupant_jid(actor)) or self:get_default_role(affiliation); if role == "moderator" then 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); + self:send_occupant_list(actor, function(occupant_jid, occupant) return occupant.role == _rol end); return true; else origin.send(st.error_reply(stanza, "auth", "forbidden")); @@ -1015,8 +1138,7 @@ end function room_mt:handle_groupchat_to_room(origin, stanza) local from = stanza.attr.from; - local current_nick = self:get_occupant_jid(from); - local occupant = self._occupants[current_nick]; + local occupant = self:get_occupant_by_real_jid(from); if not occupant then -- not in room origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); return true; @@ -1025,12 +1147,12 @@ function room_mt:handle_groupchat_to_room(origin, stanza) return true; else local from = stanza.attr.from; - stanza.attr.from = current_nick; + stanza.attr.from = occupant.nick; local subject = stanza:get_child_text("subject"); if subject then if occupant.role == "moderator" or ( self:get_changesubject() and occupant.role == "participant" ) then -- and participant - self:set_subject(current_nick, subject); + self:set_subject(occupant.nick, subject); else stanza.attr.from = from; origin.send(st.error_reply(stanza, "auth", "forbidden")); @@ -1096,7 +1218,7 @@ function room_mt:handle_mediated_invite(origin, stanza) end module:hook("muc-invite", function(event) - event.room:_route_stanza(event.stanza); + event.room:route_stanza(event.stanza); return true; end, -1) @@ -1210,64 +1332,46 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) end self._affiliations[jid] = affiliation; local role = self:get_default_role(affiliation); - local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("item", {affiliation=affiliation or "none", role=role or "none"}) - :tag("reason"):text(reason or ""):up() - :up(); - local presence_type = nil; + local occupants_updated = {}; + for nick, occupant in self:each_occupant() do + if occupant.bare_jid == jid then + occupant.role = role; + self:save_occupant(occupant); + occupants_updated[occupant] = true; + end + end + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); if not role then -- getting kicked - presence_type = "unavailable"; if affiliation == "outcast" then x:tag("status", {code="301"}):up(); -- banned else x:tag("status", {code="321"}):up(); -- affiliation change end end - local modified_nicks = {}; - for nick, occupant in pairs(self._occupants) do - if jid_bare(occupant.jid) == jid then - if not role then -- getting kicked - self._occupants[nick] = nil; - else - occupant.affiliation, occupant.role = affiliation, role; - end - for jid,pres in pairs(occupant.sessions) do -- remove for all sessions of the nick - if not role then self._jid_nick[jid] = nil; end - local p = st.clone(pres); - p.attr.from = nick; - p.attr.type = presence_type; - p.attr.to = jid; - p:add_child(x); - self:_route_stanza(p); - if occupant.jid == jid then - modified_nicks[nick] = p; - end - end - end + for occupant in pairs(occupants_updated) do + self:publicise_occupant_status(occupant, x, actor, reason); end if self.save then self:save(); end if callback then callback(); end - for nick,p in pairs(modified_nicks) do - p.attr.from = nick; - self:broadcast_except_nick(p, nick); - end return true; end function room_mt:get_role(nick) - local session = self._occupants[nick]; - return session and session.role or nil; + local occupant = self:get_occupant_by_nick(nick); + return occupant and occupant.role or nil; end function room_mt:can_set_role(actor_jid, occupant_jid, role) - local occupant = self._occupants[occupant_jid]; + local occupant = self:get_occupant_by_nick(occupant_jid); 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:get_occupant_jid(actor_jid)]; + local actor = self:get_occupant_by_real_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 + local occupant_affiliation = self:get_affiliation(occupant.bare_jid) + local actor_affiliation = self:get_affiliation(actor.bare_jid) + if occupant_affiliation ~= "owner" and occupant_affiliation ~= "admin" then + if actor_affiliation == "owner" or actor_affiliation == "admin" then return true; elseif occupant.role ~= "moderator" and role ~= "moderator" then return true; @@ -1281,73 +1385,20 @@ function room_mt:set_role(actor, occupant_jid, role, callback, reason) if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role); if not allowed then return allowed, err_type, err_condition; end - local occupant = self._occupants[occupant_jid]; - local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"}) - :tag("reason"):text(reason or ""):up() - :up(); - local presence_type = nil; - if not role then -- kick - presence_type = "unavailable"; - self._occupants[occupant_jid] = nil; - for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick - self._jid_nick[jid] = nil; - end + + local occupant = self:get_occupant_by_nick(occupant_jid); + local occupant_affiliation = self:get_affiliation(occupant.bare_jid); + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); + if not role then x:tag("status", {code = "307"}):up(); - else - occupant.role = role; - end - local bp; - for jid,pres in pairs(occupant.sessions) do -- send to all sessions of the nick - local p = st.clone(pres); - p.attr.from = occupant_jid; - p.attr.type = presence_type; - p.attr.to = jid; - p:add_child(x); - self:_route_stanza(p); - if occupant.jid == jid then - bp = p; - end end + occupant.role = role; + self:save_occupant(occupant); + self:publicise_occupant_status(occupant, x, actor, reason); if callback then callback(); end - if bp then - self:broadcast_except_nick(bp, occupant_jid); - end return true; end -function room_mt:_route_stanza(stanza) - local muc_child; - if stanza.name == "presence" then - 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 - 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 - muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); - 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; - end - end - end - end - self:route_stanza(stanza); - if muc_child then - for item in muc_child:childtags("item") do - item.attr.jid = nil; - end - end -end - local _M = {}; -- module "muc" function _M.new_room(jid, config) diff --git a/plugins/muc/occupant.lib.lua b/plugins/muc/occupant.lib.lua new file mode 100644 index 00000000..b4b12390 --- /dev/null +++ b/plugins/muc/occupant.lib.lua @@ -0,0 +1,85 @@ +local next = next; +local pairs = pairs; +local setmetatable = setmetatable; +local st = require "util.stanza"; + +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 +end + +local occupant_mt = {}; +occupant_mt.__index = occupant_mt; + +local function new_occupant(bare_real_jid, nick) + return setmetatable({ + bare_jid = bare_real_jid; + nick = nick; -- in-room jid + sessions = {}; -- hash from real_jid to presence stanzas. stanzas should not be modified + role = nil; + jid = nil; -- Primary session + }, occupant_mt); +end + +-- Deep copy an occupant +local function copy_occupant(occupant) + local sessions = {}; + for full_jid, presence_stanza in pairs(occupant.sessions) do + if presence_stanza.attr.type ~= "unavailable" then + sessions[full_jid] = presence_stanza; + end + end + return setmetatable({ + bare_jid = occupant.bare_jid; + nick = occupant.nick; + sessions = sessions; + role = occupant.role; + jid = occupant.jid; + }, occupant_mt); +end + +function occupant_mt:set_session(real_jid, presence_stanza, replace_primary) + local pr = get_filtered_presence(presence_stanza); + pr.attr.from = self.nick; + pr.attr.to = real_jid; + + self.sessions[real_jid] = pr; + if replace_primary or self.jid == nil then + self.jid = real_jid; + end +end + +function occupant_mt:remove_session(real_jid) + -- Delete original session + local presence_stanza = self.sessions[real_jid]; + self.sessions[real_jid] = nil; + if self.jid == real_jid then + -- find another session to be the primary (might be nil) + self.jid = next(self.sessions); + end +end + +function occupant_mt:each_session() + return pairs(self.sessions) +end + +function occupant_mt:get_presence(real_jid) + return self.sessions[real_jid or self.jid] +end + +return { + new = new_occupant; + copy = copy_occupant; + mt = occupant_mt; +} -- 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(-) (limited to 'plugins') 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 From c7e768d9ac1447754a81b64633d14fd0190551e4 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 19:18:57 -0400 Subject: plugins/muc/muc.lib: non-function changes (reordering, semicolons and comments) --- plugins/muc/muc.lib.lua | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 76fd7ee2..d96658f3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -9,6 +9,9 @@ local select = select; local pairs, ipairs = pairs, ipairs; +local next = next; +local setmetatable = setmetatable; +local t_insert, t_remove = table.insert, table.remove; local gettime = os.time; local datetime = require "util.datetime"; @@ -20,8 +23,6 @@ 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 t_insert, t_remove = table.insert, table.remove; -local setmetatable = setmetatable; local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; @@ -355,7 +356,7 @@ module:hook("muc-get-history", function(event) return msg end return true; -end) +end); function room_mt:send_history(stanza) local maxchars, maxstanzas, since = parse_history(stanza) @@ -584,7 +585,7 @@ module:hook("muc-occupant-pre-join/password", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1) +end, -1); module:hook("muc-occupant-pre-join/locked", function(event) if event.room:is_locked() then -- Deny entry @@ -603,9 +604,9 @@ module:hook("muc-occupant-pre-join/affiliation", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1) +end, -1); --- banned +-- check if user is banned module:hook("muc-occupant-pre-join/affiliation", function(event) local room, stanza = event.room, event.stanza; local affiliation = room:get_affiliation(stanza.attr.from); @@ -615,7 +616,7 @@ module:hook("muc-occupant-pre-join/affiliation", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1) +end, -1); module:hook("muc-occupant-joined", function(event) local room, stanza = event.room, event.stanza; @@ -857,6 +858,7 @@ function room_mt:handle_message_to_occupant(origin, stanza) stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); stanza.attr.from = current_nick; self:route_to_occupant(o_data, stanza) + -- TODO: Remove x tag? stanza.attr.from = from; return true; end @@ -1189,16 +1191,16 @@ function room_mt:handle_mediated_invite(origin, stanza) end local _invitee = jid_prep(payload.attr.to); if _invitee then + local _reason = payload:get_child_text("reason"); 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'}) :tag('invite', {from=_from}) :tag('reason'):text(_reason or ""):up() :up(); - local password = self:get_password() + local password = self:get_password(); if password then invite:tag("password"):text(password):up(); end @@ -1209,7 +1211,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", { room = self, stanza = invite, origin = origin, incoming = stanza }); + 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")); @@ -1232,7 +1234,7 @@ module:hook("muc-invite", function(event) 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) +end); function room_mt:handle_mediated_decline(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline") @@ -1289,13 +1291,11 @@ function room_mt:handle_message_to_room(origin, stanza) origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; end - else - return nil; end end function room_mt:route_stanza(stanza) - module:send(stanza) + module:send(stanza); end function room_mt:get_affiliation(jid) -- cgit v1.2.3 From b0999be824b03b48299a20bef4a62450dd3f3ae0 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 11:05:52 -0400 Subject: plugins/muc/muc.lib: Remove callback parameter from set_role and set_affiliation --- plugins/muc/muc.lib.lua | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d96658f3..79252778 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1054,20 +1054,18 @@ function room_mt:handle_admin_query_set_command(origin, stanza) 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:get_child_text("reason"); + local success, errtype, err 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; + success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, reason); 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; + success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, reason); else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); - return true; + success, errtype, err = nil, "cancel", "bad-request"; end + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + origin.send(st.reply(stanza)); + return true; end function room_mt:handle_admin_query_get_command(origin, stanza) @@ -1232,7 +1230,7 @@ module:hook("muc-invite", function(event) 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) + room:set_affiliation(from, invitee, "member", "Invited by " .. current_nick) end end); @@ -1305,7 +1303,7 @@ function room_mt:get_affiliation(jid) if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned return result; end -function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) +function room_mt:set_affiliation(actor, jid, affiliation, reason) jid = jid_bare(jid); if affiliation == "none" then affiliation = nil; end if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then @@ -1315,7 +1313,6 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) 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 @@ -1352,7 +1349,6 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) self:publicise_occupant_status(occupant, x, actor, reason); end if self.save then self:save(); end - if callback then callback(); end return true; end @@ -1380,7 +1376,7 @@ function room_mt:can_set_role(actor_jid, occupant_jid, role) end return nil, "cancel", "not-allowed"; end -function room_mt:set_role(actor, occupant_jid, role, callback, reason) +function room_mt:set_role(actor, occupant_jid, role, reason) if role == "none" then role = nil; end if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role); @@ -1395,7 +1391,6 @@ function room_mt:set_role(actor, occupant_jid, role, callback, reason) occupant.role = role; self:save_occupant(occupant); self:publicise_occupant_status(occupant, x, actor, reason); - if callback then callback(); end return true; end -- cgit v1.2.3 From 6334074f11baac6bd8101e205f038e8494553bfe Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 13:05:36 -0400 Subject: plugins/muc/muc.lib: Clean up :set_role. Removes :can_set_role --- plugins/muc/muc.lib.lua | 58 +++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 23 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 79252778..b3c1c174 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1356,34 +1356,46 @@ function room_mt:get_role(nick) local occupant = self:get_occupant_by_nick(nick); return occupant and occupant.role or nil; end -function room_mt:can_set_role(actor_jid, occupant_jid, role) + +local valid_roles = { + none = true; + visitor = true; + participant = true; + moderator = true; +} +function room_mt:set_role(actor, occupant_jid, role, reason) + if not actor then return nil, "modify", "not-acceptable"; end + local occupant = self:get_occupant_by_nick(occupant_jid); - 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:get_occupant_by_real_jid(actor_jid); - if actor.role == "moderator" then - local occupant_affiliation = self:get_affiliation(occupant.bare_jid) - local actor_affiliation = self:get_affiliation(actor.bare_jid) - if occupant_affiliation ~= "owner" and occupant_affiliation ~= "admin" then - if actor_affiliation == "owner" or actor_affiliation == "admin" then - return true; - elseif occupant.role ~= "moderator" and role ~= "moderator" then - return true; + if not occupant then return nil, "modify", "not-acceptable"; end + + if valid_roles[role or "none"] == nil then + return nil, "modify", "not-acceptable"; + end + role = role ~= "none" and role or nil; -- coerces `role == false` to `nil` + + if actor ~= true then + -- Can't do anything to other owners or admins + local occupant_affiliation = self:get_affiliation(occupant.bare_jid); + if occupant_affiliation == "owner" and occupant_affiliation == "admin" then + return nil, "cancel", "not-allowed"; + end + + -- If you are trying to give or take moderator role you need to be an owner or admin + if occupant.role == "moderator" or role == "moderator" then + local actor_affiliation = self:get_affiliation(actor); + if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then + return nil, "cancel", "not-allowed"; end end + + -- Need to be in the room and a moderator + local actor_occupant = self:get_occupant_by_real_jid(actor); + if not actor_occupant or actor_occupant.role ~= "moderator" then + return nil, "cancel", "not-allowed"; + end end - return nil, "cancel", "not-allowed"; -end -function room_mt:set_role(actor, occupant_jid, role, reason) - if role == "none" then role = nil; end - if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end - local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role); - if not allowed then return allowed, err_type, err_condition; end - local occupant = self:get_occupant_by_nick(occupant_jid); - local occupant_affiliation = self:get_affiliation(occupant.bare_jid); local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); if not role then x:tag("status", {code = "307"}):up(); -- cgit v1.2.3 From 46f318fa4f74a2194d98e8b082c97f7a62296077 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 13:11:11 -0400 Subject: plugins/muc/muc.lib: Fix sending occupant jid instead of real jid in actor --- plugins/muc/muc.lib.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index b3c1c174..0d1e4170 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -163,18 +163,18 @@ end function room_mt:build_item_list(occupant, x, is_anonymous, nick, actor, reason) local affiliation = self:get_affiliation(occupant.bare_jid); local role = occupant.role; - local actor_jid = actor and self:get_occupant_jid(actor); + local actor_attr; if actor then - actor = {nick = select(3,jid_split(actor_jid))}; + actor_attr = {nick = select(3,jid_split(self:get_occupant_jid(actor)))}; end if is_anonymous then - add_item(x, affiliation, role, nil, nick, actor, reason); + add_item(x, affiliation, role, nil, nick, actor_attr, reason); else - if actor_jid then - actor.jid = actor_jid; + if actor_attr then + actor_attr.jid = actor; end for real_jid, session in occupant:each_session() do - add_item(x, affiliation, role, real_jid, nick, actor, reason); + add_item(x, affiliation, role, real_jid, nick, actor_attr, reason); end end return x -- cgit v1.2.3 From f9f2d557fa2db46dbdbc510c4a326be26b237e52 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 13:14:33 -0400 Subject: plugins/muc/muc.lib: Allow `:send_occupant_list` to have no filter --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 0d1e4170..b5b8bc97 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -276,7 +276,7 @@ function room_mt:send_occupant_list(to, filter) local to_occupant = self:get_occupant_by_real_jid(to); local has_anonymous = self:get_whois() ~= "anyone" for occupant_jid, occupant in self:each_occupant() do - if filter and filter(occupant_jid, occupant) then + if filter == nil or filter(occupant_jid, occupant) then local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); local is_anonymous = has_anonymous and occupant.role ~= "moderator" and to_occupant.bare_jid ~= occupant.bare_jid; self:build_item_list(occupant, x, is_anonymous); -- cgit v1.2.3 From 97759d2f65c1578f8a0cb030fb4ea83ff8d45682 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 13:34:46 -0400 Subject: plugins/muc/muc.lib: Fix anonymous check in `send_occupant_list` --- plugins/muc/muc.lib.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index b5b8bc97..02a2dc37 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -273,13 +273,21 @@ function room_mt:publicise_occupant_status(occupant, full_x, actor, reason) end function room_mt:send_occupant_list(to, filter) - local to_occupant = self:get_occupant_by_real_jid(to); - local has_anonymous = self:get_whois() ~= "anyone" + local to_bare = jid_bare(to); + local is_anonymous = true; + if self:get_whois() ~= "anyone" then + local affiliation = self:get_affiliation(to); + if affiliation ~= "admin" and affiliation ~= "owner" then + local occupant = self:get_occupant_by_real_jid(to); + if not occupant or occupant.role ~= "moderator" then + is_anonymous = false; + end + end + end for occupant_jid, occupant in self:each_occupant() do if filter == nil or filter(occupant_jid, occupant) then local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); - local is_anonymous = has_anonymous and occupant.role ~= "moderator" and to_occupant.bare_jid ~= occupant.bare_jid; - self:build_item_list(occupant, x, is_anonymous); + self:build_item_list(occupant, x, is_anonymous and to_bare ~= occupant.bare_jid); -- can always see your own jids local pres = st.clone(occupant:get_presence()); pres.attr.to = to; pres:add_child(x); -- cgit v1.2.3 From 09dc06b54e9f277d0fc9a0a92c68f20e7a839a75 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 14:15:18 -0400 Subject: plugins/muc/muc.lib: Smarter validation in set_affiliation --- plugins/muc/muc.lib.lua | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 02a2dc37..d001800d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1311,12 +1311,24 @@ function room_mt:get_affiliation(jid) if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned return result; end + +local valid_affiliations = { + outcast = true; + none = true; + member = true; + admin = true; + owner = true; +}; function room_mt:set_affiliation(actor, jid, affiliation, reason) + if not actor then return nil, "modify", "not-acceptable"; end; + jid = jid_bare(jid); - if affiliation == "none" then affiliation = nil; end - if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then + + if valid_affiliations[affiliation or "none"] == nil then return nil, "modify", "not-acceptable"; end + affiliation = affiliation ~= "none" and affiliation or nil; -- coerces `affiliation == false` to `nil` + if actor ~= true then local actor_affiliation = self:get_affiliation(actor); local target_affiliation = self:get_affiliation(jid); -- cgit v1.2.3 From f090e4eddaf71cbe01a3e6810961a8492a70549d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 17:58:25 -0400 Subject: plugins/muc/muc.lib: Improve set affiliation logic; - Each affiliation and role is now ranked - Changes up in affiliation will not downgrade your role - Now sends a new set of presences if you gained moderator in a semi-anonymous room. - Better input validation; matches closer with ':set_role' - Don't short circuit; as if user has non-default role they will not get updated --- plugins/muc/muc.lib.lua | 96 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 32 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d001800d..9ae3f56a 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -60,6 +60,21 @@ function room_mt:get_occupant_jid(real_jid) return self._jid_nick[real_jid] end +local valid_affiliations = { + outcast = 0; + none = 1; + member = 2; + admin = 3; + owner = 4; +}; + +local valid_roles = { + none = 0; + visitor = 1; + participant = 2; + moderator = 3; +}; + function room_mt:get_default_role(affiliation) if affiliation == "owner" or affiliation == "admin" then return "moderator"; @@ -1312,13 +1327,6 @@ function room_mt:get_affiliation(jid) return result; end -local valid_affiliations = { - outcast = true; - none = true; - member = true; - admin = true; - owner = true; -}; function room_mt:set_affiliation(actor, jid, affiliation, reason) if not actor then return nil, "modify", "not-acceptable"; end; @@ -1329,34 +1337,52 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason) end affiliation = affiliation ~= "none" and affiliation or nil; -- coerces `affiliation == false` to `nil` + local target_affiliation = self._affiliations[jid]; -- Raw; don't want to check against host + local is_downgrade = valid_affiliations[target_affiliation or "none"] > valid_affiliations[affiliation or "none"]; + 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 - 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"; + local actor_bare = jid_bare(actor); + local actor_affiliation = self._affiliations[actor_bare]; + if actor_affiliation == "owner" then + if actor_bare == jid then -- self change + -- need at least one owner + 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 + -- owners can do anything else + elseif affiliation == "owner" or affiliation == "admin" + or actor_affiliation ~= "admin" + or target_affiliation == "owner" or target_affiliation == "admin" then + -- Can't demote owners or other admins + return nil, "cancel", "not-allowed"; end end + + -- Set in 'database' self._affiliations[jid] = affiliation; + + -- Update roles local role = self:get_default_role(affiliation); - local occupants_updated = {}; + local role_rank = valid_roles[role or "none"]; + local occupants_updated = {}; -- Filled with old roles for nick, occupant in self:each_occupant() do if occupant.bare_jid == jid then - occupant.role = role; - self:save_occupant(occupant); - occupants_updated[occupant] = true; + -- need to publcize in all cases; as affiliation in has changed. + occupants_updated[occupant] = occupant.role; + if occupant.role ~= role and ( + is_downgrade or + valid_roles[occupant.role or "none"] < role_rank -- upgrade + ) then + occupant.role = role; + self:save_occupant(occupant); + end end end + + -- Tell the room of the new occupant affiliations+roles local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); if not role then -- getting kicked if affiliation == "outcast" then @@ -1365,9 +1391,21 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason) x:tag("status", {code="321"}):up(); -- affiliation change end end - for occupant in pairs(occupants_updated) do + local is_semi_anonymous = self:get_whois() == "moderators"; + for occupant, old_role in pairs(occupants_updated) do self:publicise_occupant_status(occupant, x, actor, reason); + if is_semi_anonymous and + (old_role == "moderator" and occupant.role ~= "moderator") or + (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status + -- Send everyone else's presences (as jid visibility has changed) + for real_jid in occupant:each_session() do + self:send_occupant_list(real_jid, function(occupant_jid, occupant) + return occupant.bare_jid ~= jid; + end); + end + end end + if self.save then self:save(); end return true; end @@ -1377,12 +1415,6 @@ function room_mt:get_role(nick) return occupant and occupant.role or nil; end -local valid_roles = { - none = true; - visitor = true; - participant = true; - moderator = true; -} function room_mt:set_role(actor, occupant_jid, role, reason) if not actor then return nil, "modify", "not-acceptable"; end -- cgit v1.2.3 From ac04e20d3bcd37432c086d4b91222914c7c4ed59 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 18:33:38 -0400 Subject: plugins/muc/muc.lib: Status codes should be inside of x element --- plugins/muc/muc.lib.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9ae3f56a..08ca0384 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1013,8 +1013,9 @@ function room_mt:process_form(origin, stanza) if next(changed) then local msg = st.message({type='groupchat', from=self.jid}) - :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up() - :tag('status', {code = '104'}):up(); + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('status', {code = '104'}):up() + :up(); if changed.whois then local code = (self:get_whois() == 'moderators') and "173" or "172"; msg.tags[1]:tag('status', {code = code}):up(); -- cgit v1.2.3 From 5d2b35e070ebe5d10aa07c5a5f8448aa17dfef44 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 18:33:57 -0400 Subject: plugins/muc/muc.lib: nick change unavailables should be from original occupant jid --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 08ca0384..6453cc3b 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -760,7 +760,7 @@ function room_mt:handle_presence_to_occupant(origin, stanza) -- self:build_item_list(orig_occupant, x, false); -- COMPAT x:tag("status", {code = "303";}):up(); x:tag("status", {code = "110";}):up(); - self:route_stanza(st.presence{from = dest_occupant.nick, to = real_jid, type = "unavailable"}:add_child(x)); + self:route_stanza(st.presence{from = orig_occupant.nick, to = real_jid, type = "unavailable"}:add_child(x)); end self:save_occupant(orig_occupant); self:publicise_occupant_status(orig_occupant, orig_x); -- cgit v1.2.3 From 7cbdc2b0f7570ca06ff5e8561b09cb5bc64c8a1f Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 18:47:35 -0400 Subject: plugins/muc/muc.lib: Fix typo (moderators vs moderator) --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6453cc3b..c1747440 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -261,7 +261,7 @@ function room_mt:publicise_occupant_status(occupant, full_x, actor, reason) for nick, n_occupant in self:each_occupant() do if nick ~= occupant.nick or n_occupant.role == nil then local pr = full_p; - if has_anonymous and n_occupant.role ~= "moderators" and occupant.bare_jid ~= n_occupant.bare_jid then + if has_anonymous and n_occupant.role ~= "moderator" and occupant.bare_jid ~= n_occupant.bare_jid then pr = anon_p; end self:route_to_occupant(n_occupant, pr); -- cgit v1.2.3 From 672d0fae0b9b785bc2a1def3143b4978fd379201 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 20:25:10 -0400 Subject: plugins/muc/muc.lib: Subjects get sent even if empty. --- plugins/muc/muc.lib.lua | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index c1747440..7704a099 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -424,26 +424,21 @@ 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'}) +local function create_subject_message(from, subject) + return st.message({from = from; 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 + local msg = create_subject_message(self:get_subject()); + msg.attr.to = to; + self:route_stanza(msg); 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 = create_subject_message(subject) - msg.attr.from = current_nick + local msg = create_subject_message(current_nick, subject); self:broadcast_message(msg, false); return true; end -- cgit v1.2.3 From d0e5de9af2239bd57d91a7c30e0a4d335b668a7b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 20:28:20 -0400 Subject: plugins/muc/muc.lib: Use occupant methods where possible --- plugins/muc/muc.lib.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7704a099..684539bd 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -641,7 +641,7 @@ module:hook("muc-occupant-joined", function(event) local real_jid = stanza.attr.from; room:send_occupant_list(real_jid, function(nick, occupant) -- Don't include self - return occupant.sessions[real_jid] == nil + return occupant:get_presence(real_jid) == nil; end); room:send_history(stanza); room:send_subject(real_jid); @@ -687,7 +687,8 @@ function room_mt:handle_presence_to_occupant(origin, stanza) local is_last_orig_session; if orig_occupant ~= nil then -- Is there are least 2 sessions? - is_last_orig_session = next(orig_occupant.sessions, next(orig_occupant.sessions)) == nil; + local iter, ob, last = orig_occupant:each_session(); + is_last_orig_session = iter(ob, iter(ob, last)) == nil; end local event, event_name = { -- cgit v1.2.3 From eb0739676fb5a91c11583764f1198999893f94ba Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 12:21:43 -0400 Subject: plugins/muc/muc.lib: Remove top level pre-join event. Assign event priorities for other handlers --- plugins/muc/muc.lib.lua | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 684539bd..9f7ba8d3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -585,12 +585,6 @@ module:hook("muc-room-pre-create", function(event) end, -1); 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); -end, -1) - -module:hook("muc-occupant-pre-join/password", function(event) local room, stanza = event.room, event.stanza; local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); password = password and password:get_child_text("password", "http://jabber.org/protocol/muc"); @@ -603,17 +597,17 @@ module:hook("muc-occupant-pre-join/password", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1); +end, -20); -module:hook("muc-occupant-pre-join/locked", function(event) +module:hook("muc-occupant-pre-join", 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 -end, -1); +end, -30); -- registration required for entering members-only room -module:hook("muc-occupant-pre-join/affiliation", function(event) +module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; local affiliation = room:get_affiliation(stanza.attr.from); if affiliation == nil and event.room:get_members_only() then @@ -622,10 +616,10 @@ module:hook("muc-occupant-pre-join/affiliation", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1); +end, -5); -- check if user is banned -module:hook("muc-occupant-pre-join/affiliation", function(event) +module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; local affiliation = room:get_affiliation(stanza.attr.from); if affiliation == "outcast" then @@ -634,7 +628,7 @@ module:hook("muc-occupant-pre-join/affiliation", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1); +end, -10); module:hook("muc-occupant-joined", function(event) local room, stanza = event.room, event.stanza; -- cgit v1.2.3 From 0cfac6e454973e4d7bb2f15e5f775adb43276ad4 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 12:31:15 -0400 Subject: plugins/muc/muc.lib: Tidy up muc-invite event. - Send inside of the actual handle_invite function - Move password, compat and body tagging into event handlers --- plugins/muc/muc.lib.lua | 64 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 20 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9f7ba8d3..93f8fc96 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1203,27 +1203,15 @@ function room_mt:handle_mediated_invite(origin, stanza) end local _invitee = jid_prep(payload.attr.to); if _invitee then - local _reason = payload:get_child_text("reason"); - if self:get_whois() == "moderators" then - _from = current_nick; - end 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(); - 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 "") - :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 "")) + :tag('reason'):text(payload:get_child_text("reason")):up() + :up() :up(); - module:fire_event("muc-invite", {room = self, stanza = invite, origin = origin, incoming = stanza}); + if not module:fire_event("muc-invite", {room = self, stanza = invite, origin = origin, incoming = stanza}) then + self:route_stanza(invite); + end return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); @@ -1231,10 +1219,46 @@ function room_mt:handle_mediated_invite(origin, stanza) end end +-- Add password to outgoing invite module:hook("muc-invite", function(event) - event.room:route_stanza(event.stanza); - return true; -end, -1) + local password = event.room:get_password(); + if password then + local x = event.stanza:get_child("x", "http://jabber.org/protocol/muc#user"); + x:tag("password"):text(password):up(); + end +end); + +-- COMPAT: Some older clients expect this +module:hook("muc-invite", function(event) + local room, stanza = event.room, event.stanza; + local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local reason = invite:get_child_text("reason"); + stanza:tag('x', {xmlns = "jabber:x:conference"; jid = room.jid;}) + :text(reason or "") + :up(); +end); + +-- Add a plain message for clients which don't support invites +module:hook("muc-invite", function(event) + local room, stanza = event.room, event.stanza; + local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local reason = invite:get_child_text("reason") or ""; + stanza:tag("body") + :text(invite.attr.from.." invited you to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) + :up(); +end); + +-- Mask 'from' jid as occupant jid if room is anonymous +module:hook("muc-invite", function(event) + local room, stanza = event.room, event.stanza; + if room:get_whois() == "moderators" and room:get_default_role(room:get_affiliation(stanza.attr.to)) ~= "moderator" then + local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local occupant_jid = room:get_occupant_jid(invite.attr.from); + if occupant_jid ~= nil then -- FIXME: This will expose real jid if inviter is not in room + invite.attr.from = occupant_jid; + end + end +end, 50); -- When an invite is sent; add an affiliation for the invitee module:hook("muc-invite", function(event) -- cgit v1.2.3 From b3f385662f6c306d09b4409d3eef61bc91f57f7b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 13:54:27 -0400 Subject: plugins/muc/muc.lib: Add pre-invite event. Move role check to it --- plugins/muc/muc.lib.lua | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 93f8fc96..a5118430 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1192,31 +1192,37 @@ function room_mt:handle_presence_to_room(origin, stanza) return handled; end -function room_mt:handle_mediated_invite(origin, stanza) - local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite") +-- Need visitor role or higher to invite +module:hook("muc-pre-invite", function(event) + local room, stanza = event.room, event.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:get_role(current_nick) or not self:get_default_role(self:get_affiliation(_from)) then - origin.send(st.error_reply(stanza, "auth", "forbidden")); + local inviter = room:get_occupant_by_real_jid(_from); + local role = inviter and inviter.role or room:get_default_role(room:get_affiliation(_from)); + if valid_roles[role or "none"] <= valid_roles.visitor then + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end - local _invitee = jid_prep(payload.attr.to); - if _invitee then - 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(payload:get_child_text("reason")):up() - :up() - :up(); - if not module:fire_event("muc-invite", {room = self, stanza = invite, origin = origin, incoming = stanza}) then - self:route_stanza(invite); - end - return true; - else +end); + +function room_mt:handle_mediated_invite(origin, stanza) + local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local invitee = jid_prep(payload.attr.to); + if not invitee then origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); return true; + elseif not module:fire_event("muc-pre-invite", {room = self, origin = origin, stanza = stanza}) then + return true; + end + local invite = st.message({from = self.jid, to = invitee, id = stanza.attr.id}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('invite', {from = stanza.attr.from;}) + :tag('reason'):text(payload:get_child_text("reason")):up() + :up() + :up(); + if not module:fire_event("muc-invite", {room = self, stanza = invite, origin = origin, incoming = stanza}) then + self:route_stanza(invite); end + return true; end -- Add password to outgoing invite -- cgit v1.2.3 From 8b8f89703e632711ee0a6371a2f7e2400176b692 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 14:06:35 -0400 Subject: plugins/muc/muc.lib: Update declines to be more like invites --- plugins/muc/muc.lib.lua | 53 +++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 26 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index a5118430..58fa7b83 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1279,39 +1279,40 @@ module:hook("muc-invite", function(event) end); function room_mt:handle_mediated_decline(origin, stanza) - local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline") + 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; - -- 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(); - module:fire_event("muc-decline", { room = self, stanza = decline, origin = origin, incoming = stanza }); - return true; - else + if not declinee then origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); return true; + elseif not module:fire_event("muc-pre-decline", {room = self, origin = origin, stanza = stanza}) then + return true; end + local decline = st.message({from = self.jid, to = declinee, id = stanza.attr.id}) + :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("decline", {from = stanza.attr.from}) + :tag("reason"):text(payload:get_child_text("reason")):up() + :up() + :up(); + if not module:fire_event("muc-decline", {room = self, stanza = decline, origin = origin, incoming = stanza}) then + local occupant = self:get_occupant_by_real_jid(decline.attr.to); + if occupant then + self:route_to_occupant(occupant, decline); + else + self:route_stanza(decline); + end + end + return true; end +-- Add a plain message for clients which don't support declines module:hook("muc-decline", function(event) - 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) + local room, stanza = event.room, event.stanza; + local decline = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline"); + local reason = decline:get_child_text("reason") or ""; + stanza:tag("body") + :text(decline.attr.from.." declined your invite to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) + :up(); +end); function room_mt:handle_message_to_room(origin, stanza) local type = stanza.attr.type; -- cgit v1.2.3 From e19853af0e7f6049fd43e3d061f4a1f54cbb8fa6 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 14:44:52 -0400 Subject: plugins/muc/muc.lib: Don't try and get occupant jids for annotating invite affiliation changes --- plugins/muc/muc.lib.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 58fa7b83..d6ba68f9 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1272,9 +1272,8 @@ module:hook("muc-invite", function(event) 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", "Invited by " .. current_nick) + room:set_affiliation(from, invitee, "member", "Invited by " .. from); -- This might fail; ignore for now end end); -- cgit v1.2.3 From 2c3dad041615c903fd05174183b7d2703681cc78 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 14:45:42 -0400 Subject: plugins/muc/muc.lib: restrict invitations in members only rooms to admins --- plugins/muc/muc.lib.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d6ba68f9..c8881178 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1204,6 +1204,17 @@ module:hook("muc-pre-invite", function(event) end end); +-- Invitation privileges in members-only rooms SHOULD be restricted to room admins; +-- if a member without privileges to edit the member list attempts to invite another user +-- the service SHOULD return a error to the occupant +module:hook("muc-pre-invite", function(event) + local room, stanza = event.room, event.stanza; + if room:get_members_only() and valid_affiliations[room:get_affiliation(stanza.attr.from) or "none"] < valid_affiliations.admin then + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end +end); + function room_mt:handle_mediated_invite(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); local invitee = jid_prep(payload.attr.to); -- cgit v1.2.3 From fbae5113588b6dd2743d72c29290613c3b7f9812 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 14:46:59 -0400 Subject: plugins/muc/muc.lib: Add muc-room-locked event --- plugins/muc/muc.lib.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index c8881178..d839786a 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -88,6 +88,7 @@ function room_mt:get_default_role(affiliation) end function room_mt:lock() + module:fire_event("muc-room-locked", { room = self }); self.locked = true end function room_mt:unlock() -- cgit v1.2.3 From b06bdb9b7d3da8b1c30ae09a8279396acbb8cad9 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 1 Apr 2014 15:41:44 -0400 Subject: plugins/muc/muc.lib: Split up get_disco_info into events This was done so we can split off functionality to other files later (e.g. plugins/muc/password) --- plugins/muc/muc.lib.lua | 55 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 16 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d839786a..5f504d40 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -397,23 +397,46 @@ function room_mt:send_history(stanza) end function room_mt:get_disco_info(stanza) - local count = 0; for _ in self:each_occupant() 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: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: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 = "" }, - { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) } - }):form({["muc#roominfo_description"] = self:get_description()}, 'result')) - ; + local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#info"); + local form = dataform.new { + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }; + }; + module:fire_event("muc-disco#info", {room = self; reply = reply; form = form;}); + reply:add_child(form:form(nil, "result")); + return reply; end +module:hook("muc-disco#info", function(event) + event.reply:tag("identity", {category="conference", type="text", name=event.room:get_name()}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = "http://jabber.org/protocol/muc"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_members_only() and "muc_membersonly" or "muc_open"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_persistent() and "muc_persistent" or "muc_temporary"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_hidden() and "muc_hidden" or "muc_public"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_whois() ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up(); +end); +module:hook("muc-disco#info", function(event) + table.insert(event.form, { name = "muc#roominfo_description", label = "Description", value = event.room:get_description() }); +end); +module:hook("muc-disco#info", function(event) + local count = 0; for _ in event.room:each_occupant() do count = count + 1; end + table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); +end); + function room_mt:get_disco_items(stanza) local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items"); for room_jid in self:each_occupant() do -- cgit v1.2.3 From 65b712f91dbc129a53575e974e3bc1463fedca92 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 1 Apr 2014 17:10:01 -0400 Subject: plugins/muc/muc.lib: Move default config layout into hooks --- plugins/muc/muc.lib.lua | 148 +++++++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 64 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5f504d40..df31a83c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -907,7 +907,6 @@ 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.", @@ -915,73 +914,94 @@ function room_mt:get_form_layout(actor) name = 'FORM_TYPE', type = 'hidden', value = 'http://jabber.org/protocol/muc#roomconfig' - }, - { - name = 'muc#roomconfig_roomname', - type = 'text-single', - label = 'Name', - value = self:get_name() or "", - }, - { - name = 'muc#roomconfig_roomdesc', - type = 'text-single', - label = 'Description', - value = self:get_description() or "", - }, - { - name = 'muc#roomconfig_persistentroom', - type = 'boolean', - label = 'Make Room Persistent?', - value = self:get_persistent() - }, - { - name = 'muc#roomconfig_publicroom', - type = 'boolean', - label = 'Make Room Publicly Searchable?', - 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', - type = 'list-single', - label = 'Who May Discover Real JIDs?', - value = { - { value = 'moderators', label = 'Moderators Only', default = whois == 'moderators' }, - { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } - } - }, - { - name = 'muc#roomconfig_roomsecret', - type = 'text-private', - label = 'Password', - value = self:get_password() or "", - }, - { - name = 'muc#roomconfig_moderatedroom', - type = 'boolean', - label = 'Make Room Moderated?', - value = self:get_moderated() - }, - { - name = 'muc#roomconfig_membersonly', - type = 'boolean', - label = 'Make Room 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 +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_roomname', + type = 'text-single', + label = 'Name', + value = event.room:get_name() or "", + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_roomdesc', + type = 'text-single', + label = 'Description', + value = event.room:get_description() or "", + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_persistentroom', + type = 'boolean', + label = 'Make Room Persistent?', + value = event.room:get_persistent() + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_publicroom', + type = 'boolean', + label = 'Make Room Publicly Searchable?', + value = not event.room:get_hidden() + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_changesubject', + type = 'boolean', + label = 'Allow Occupants to Change Subject?', + value = event.room:get_changesubject() + }); +end); +module:hook("muc-config-form", function(event) + local whois = event.room:get_whois(); + table.insert(event.form, { + name = 'muc#roomconfig_whois', + type = 'list-single', + label = 'Who May Discover Real JIDs?', + value = { + { value = 'moderators', label = 'Moderators Only', default = whois == 'moderators' }, + { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } + } + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_roomsecret', + type = 'text-private', + label = 'Password', + value = event.room:get_password() or "", + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_moderatedroom', + type = 'boolean', + label = 'Make Room Moderated?', + value = event.room:get_moderated() + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_membersonly', + type = 'boolean', + label = 'Make Room Members-Only?', + value = event.room:get_members_only() + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_historylength', + type = 'text-single', + label = 'Maximum Number of History Messages Returned by Room', + value = tostring(event.room:get_historylength()) + }); +end); function room_mt:process_form(origin, stanza) local query = stanza.tags[1]; -- cgit v1.2.3 From 5f51a5c88afc3989c2aa70ff00ea07c854dfcd61 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 1 Apr 2014 17:45:03 -0400 Subject: plugins/muc/muc.lib: Refactor out process_form into hooks --- plugins/muc/muc.lib.lua | 117 +++++++++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 46 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index df31a83c..7fd9746c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1004,59 +1004,82 @@ module:hook("muc-config-form", function(event) end); function room_mt:process_form(origin, stanza) - local query = stanza.tags[1]; - 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 - - 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 changed = {}; + local form = stanza.tags[1]:get_child("x", "jabber:x:data"); + if form.attr.type == "cancel" then + origin.send(st.reply(stanza)); + elseif form.attr.type == "submit" then + 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 true; + end - 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 changed = {}; - local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option }; - module:fire_event("muc-config-submitted", event); + 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 - 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"); + local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option }; + module:fire_event("muc-config-submitted", event); - if self.save then self:save(true); end - if self:is_locked() then - self:unlock(); - end - origin.send(st.reply(stanza)); + if self.save then self:save(true); end + if self:is_locked() then + self:unlock(); + end + origin.send(st.reply(stanza)); - if next(changed) then - local msg = st.message({type='groupchat', from=self.jid}) - :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) - :tag('status', {code = '104'}):up() - :up(); - if changed.whois then - local code = (self:get_whois() == 'moderators') and "173" or "172"; - msg.tags[1]:tag('status', {code = code}):up(); + if next(changed) then + local msg = st.message({type='groupchat', from=self.jid}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('status', {code = '104'}):up() + :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 - self:broadcast_message(msg, false) + else + origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); end + return true; end +module:hook("muc-config-submitted", function(event) + event.update_option("name", "muc#roomconfig_roomname"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("description", "muc#roomconfig_roomdesc"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("persistent", "muc#roomconfig_persistentroom"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("moderated", "muc#roomconfig_moderatedroom"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("members_only", "muc#roomconfig_membersonly"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("public", "muc#roomconfig_publicroom"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("changesubject", "muc#roomconfig_changesubject"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("historylength", "muc#roomconfig_historylength"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("whois", "muc#roomconfig_whois", valid_whois); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("password", "muc#roomconfig_roomsecret"); +end); -- Removes everyone from the room function room_mt:clear(x) @@ -1188,8 +1211,10 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) self:destroy(newjid, reason, password); origin.send(st.reply(stanza)); return true; + elseif child.name == "x" and child.attr.xmlns == "jabber:x:data" then + return self:process_form(origin, stanza); else - self:process_form(origin, stanza); + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return true; end end -- cgit v1.2.3 From 1782c1a4b939afc68f4ccee448726f61dfc58c44 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 11:35:00 -0400 Subject: plugins/muc/muc.lib: Modify muc-config-submitted to keep a list of status codes instead of fields changed --- plugins/muc/muc.lib.lua | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7fd9746c..07069130 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1014,18 +1014,16 @@ function room_mt:process_form(origin, stanza) return true; end - local changed = {}; - - local function handle_option(name, field, allowed) + local event = {room = self; origin = origin; stanza = stanza; fields = fields; status_codes = {};}; + function event.update_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; + event.status_codes["104"] = true; self["set_"..name](self, new); + return true; end - - local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option }; module:fire_event("muc-config-submitted", event); if self.save then self:save(true); end @@ -1034,15 +1032,13 @@ function room_mt:process_form(origin, stanza) end origin.send(st.reply(stanza)); - if next(changed) then + if next(event.status_codes) then local msg = st.message({type='groupchat', from=self.jid}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) - :tag('status', {code = '104'}):up() - :up(); - if changed.whois then - local code = (self:get_whois() == 'moderators') and "173" or "172"; - msg.tags[1]:tag('status', {code = code}):up(); + for code in pairs(event.status_codes) do + msg:tag("status", {code = code;}):up(); end + msg:up(); self:broadcast_message(msg, false) end else @@ -1075,7 +1071,10 @@ module:hook("muc-config-submitted", function(event) event.update_option("historylength", "muc#roomconfig_historylength"); end); module:hook("muc-config-submitted", function(event) - event.update_option("whois", "muc#roomconfig_whois", valid_whois); + if event.update_option("whois", "muc#roomconfig_whois", valid_whois) then + local code = (event.room:get_whois() == 'moderators') and "173" or "172"; + event.status_codes[code] = true; + end end); module:hook("muc-config-submitted", function(event) event.update_option("password", "muc#roomconfig_roomsecret"); -- cgit v1.2.3 From 906e49003903ef31a24eba95b20f9d4b1fc21431 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 15:14:52 -0400 Subject: plugins/muc/muc.lib: Move description functions out to own file --- plugins/muc/description.lib.lua | 43 +++++++++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 28 ++++----------------------- 2 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 plugins/muc/description.lib.lua (limited to 'plugins') diff --git a/plugins/muc/description.lib.lua b/plugins/muc/description.lib.lua new file mode 100644 index 00000000..30852922 --- /dev/null +++ b/plugins/muc/description.lib.lua @@ -0,0 +1,43 @@ +-- 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. +-- + +local function get_description(room) + return room._data.description; +end + +local function set_description(room, description) + if description == "" then description = nil; end + if get_description(room) == description then return false; end + room._data.description = description; + if room.save then room:save(true); end + return true; +end + +local function add_form_option(event) + table.insert(event.form, { + name = "muc#roomconfig_roomdesc"; + type = "text-single"; + label = "Description"; + value = get_description(event.room) or ""; + }); +end +module:hook("muc-disco#info", add_form_option); +module:hook("muc-config-form", add_form_option); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_roomdesc"]; + if new ~= nil and set_description(event.room, new) then + event.status_codes["104"] = true; + end +end); + +return { + get = get_description; + set = set_description; +}; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 07069130..ef572cfd 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -429,9 +429,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_whois() ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up(); end); -module:hook("muc-disco#info", function(event) - table.insert(event.form, { name = "muc#roominfo_description", label = "Description", value = event.room:get_description() }); -end); module:hook("muc-disco#info", function(event) local count = 0; for _ in event.room:each_occupant() do count = count + 1; end table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); @@ -495,16 +492,6 @@ end function room_mt:get_name() return self._data.name or jid_split(self.jid); end -function room_mt:set_description(description) - if description == "" or type(description) ~= "string" then description = nil; end - if self._data.description ~= description then - self._data.description = description; - if self.save then self:save(true); end - end -end -function room_mt:get_description() - return self._data.description; -end function room_mt:set_password(password) if password == "" or type(password) ~= "string" then password = nil; end if self._data.password ~= password then @@ -926,14 +913,6 @@ module:hook("muc-config-form", function(event) value = event.room:get_name() or "", }); end); -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_roomdesc', - type = 'text-single', - label = 'Description', - value = event.room:get_description() or "", - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_persistentroom', @@ -1049,9 +1028,6 @@ end module:hook("muc-config-submitted", function(event) event.update_option("name", "muc#roomconfig_roomname"); end); -module:hook("muc-config-submitted", function(event) - event.update_option("description", "muc#roomconfig_roomdesc"); -end); module:hook("muc-config-submitted", function(event) event.update_option("persistent", "muc#roomconfig_persistentroom"); end); @@ -1558,6 +1534,10 @@ function room_mt:set_role(actor, occupant_jid, role, reason) return true; end +local description = module:require "muc/description"; +room_mt.get_description = description.get; +room_mt.set_description = description.set; + local _M = {}; -- module "muc" function _M.new_room(jid, config) -- cgit v1.2.3 From 7da7cb7b4a7125eb1d77e67bb393ceab50355a07 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 15:36:08 -0400 Subject: plugins/muc/mod_muc: Move room locking into hook --- plugins/muc/mod_muc.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index a8a6388d..8b40d6ad 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -90,7 +90,13 @@ function create_room(jid) local room = muc_new_room(jid); room.save = room_save; rooms[jid] = room; - if lock_rooms then + module:fire_event("muc-room-created", { room = room }); + return room; +end + +if lock_rooms then + module:hook("muc-room-created", function(event) + local room = event.room; room:lock(); if lock_room_timeout and lock_room_timeout > 0 then module:add_timer(lock_room_timeout, function () @@ -99,9 +105,7 @@ function create_room(jid) end end); end - end - module:fire_event("muc-room-created", { room = room }); - return room; + end); end function forget_room(jid) -- cgit v1.2.3 From fd4362f97d1d75fa0ae251417801e9c82b4ef03c Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 15:48:25 -0400 Subject: plugins/muc: Move locking to seperate module --- plugins/muc/lock.lib.lua | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ plugins/muc/mod_muc.lua | 17 +------------ plugins/muc/muc.lib.lua | 30 ---------------------- 3 files changed, 66 insertions(+), 46 deletions(-) create mode 100644 plugins/muc/lock.lib.lua (limited to 'plugins') diff --git a/plugins/muc/lock.lib.lua b/plugins/muc/lock.lib.lua new file mode 100644 index 00000000..73dfa151 --- /dev/null +++ b/plugins/muc/lock.lib.lua @@ -0,0 +1,65 @@ +-- 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. +-- + +local lock_rooms = module:get_option_boolean("muc_room_locking", false); +local lock_room_timeout = module:get_option_number("muc_room_lock_timeout", 300); + +local function lock(room) + module:fire_event("muc-room-locked", {room = room;}); + room.locked = true; +end +local function unlock(room) + module:fire_event("muc-room-unlocked", {room = room;}); + room.locked = nil; +end +local function is_locked(room) + return not not room.locked; +end + +if lock_rooms then + module:hook("muc-room-created", function(event) + local room = event.room; + lock(room); + if lock_room_timeout and lock_room_timeout > 0 then + module:add_timer(lock_room_timeout, function () + if is_locked(room) then + room:destroy(); -- Not unlocked in time + end + end); + end + end); +end + +-- Older groupchat protocol doesn't lock +module:hook("muc-room-pre-create", function(event) + if is_locked(event.room) and not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then + unlock(event.room); + end +end, 10); + +-- Don't let users into room while it is locked +module:hook("muc-occupant-pre-join", function(event) + if is_locked(event.room) then -- Deny entry + event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); + return true; + end +end, -30); + +-- When config is submitted; unlock the room +module:hook("muc-config-submitted", function(event) + if is_locked(event.room) then + unlock(event.room); + end +end, -1); + +return { + lock = lock; + unlock = unlock; + is_locked = is_locked; +}; diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 8b40d6ad..6f6094b4 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -23,8 +23,6 @@ if restrict_room_creation then restrict_room_creation = nil; end end -local lock_rooms = module:get_option_boolean("muc_room_locking", false); -local lock_room_timeout = module:get_option_number("muc_room_lock_timeout", 300); local muclib = module:require "muc"; local muc_new_room = muclib.new_room; @@ -47,6 +45,7 @@ module:depends("disco"); module:add_identity("conference", "text", muc_name); module:add_feature("http://jabber.org/protocol/muc"); module:depends "muc_unique" +module:require "muc/lock"; local function is_admin(jid) return um_is_admin(jid, module.host); @@ -94,20 +93,6 @@ function create_room(jid) return room; end -if lock_rooms then - module:hook("muc-room-created", function(event) - local room = event.room; - room:lock(); - if lock_room_timeout and lock_room_timeout > 0 then - module:add_timer(lock_room_timeout, function () - if room:is_locked() then - room:destroy(); -- Not unlocked in time - end - end); - end - end); -end - function forget_room(jid) rooms[jid] = nil; end diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ef572cfd..6906b9ce 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -87,18 +87,6 @@ function room_mt:get_default_role(affiliation) end end -function room_mt:lock() - module:fire_event("muc-room-locked", { room = self }); - 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 - --- Occupant functions function room_mt:new_occupant(bare_real_jid, nick) local occupant = occupant_lib.new(bare_real_jid, nick); @@ -583,13 +571,6 @@ function room_mt:get_whois() return self._data.whois; end -module:hook("muc-room-pre-create", function(event) - local room = event.room; - if room:is_locked() and not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then - room:unlock(); -- Older groupchat protocol doesn't lock - end -end, 10); - -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); @@ -610,13 +591,6 @@ module:hook("muc-occupant-pre-join", function(event) end end, -20); -module:hook("muc-occupant-pre-join", 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 -end, -30); - -- registration required for entering members-only room module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; @@ -1006,9 +980,6 @@ function room_mt:process_form(origin, stanza) module:fire_event("muc-config-submitted", event); if self.save then self:save(true); end - if self:is_locked() then - self:unlock(); - end origin.send(st.reply(stanza)); if next(event.status_codes) then @@ -1543,7 +1514,6 @@ 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 4c0e0c3c239a4ae8d19c78f0f4d6d6975fa7fdc8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 15:56:37 -0400 Subject: plugins/muc/lock.lib: Need to let creator into the locked room :) --- plugins/muc/lock.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/lock.lib.lua b/plugins/muc/lock.lib.lua index 73dfa151..7cf19be3 100644 --- a/plugins/muc/lock.lib.lua +++ b/plugins/muc/lock.lib.lua @@ -45,7 +45,7 @@ end, 10); -- Don't let users into room while it is locked module:hook("muc-occupant-pre-join", function(event) - if is_locked(event.room) then -- Deny entry + if not event.is_new_room and is_locked(event.room) then -- Deny entry event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); return true; end -- cgit v1.2.3 From 7f0b9f176bd26c13df53b938fc19c01d2aca51ee Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 16:57:59 -0400 Subject: plugins/muc: Move password functions to seperate module --- plugins/muc/muc.lib.lua | 52 +++----------------------------- plugins/muc/password.lib.lua | 70 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 48 deletions(-) create mode 100644 plugins/muc/password.lib.lua (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6906b9ce..62553321 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -399,9 +399,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = "http://jabber.org/protocol/muc"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up(); -end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); end); @@ -480,16 +477,6 @@ end function room_mt:get_name() return self._data.name or jid_split(self.jid); end -function room_mt:set_password(password) - if password == "" or type(password) ~= "string" then password = nil; end - if self._data.password ~= password then - self._data.password = password; - if self.save then self:save(true); end - end -end -function room_mt:get_password() - return self._data.password; -end function room_mt:set_moderated(moderated) moderated = moderated and true or nil; if self._data.moderated ~= moderated then @@ -576,21 +563,6 @@ module:hook("muc-room-pre-create", function(event) event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); end, -1); -module:hook("muc-occupant-pre-join", function(event) - local room, stanza = event.room, event.stanza; - local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); - 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(); - reply.tags[1].attr.code = "401"; - event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - end -end, -20); - -- registration required for entering members-only room module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; @@ -923,14 +895,6 @@ module:hook("muc-config-form", function(event) } }); end); -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_roomsecret', - type = 'text-private', - label = 'Password', - value = event.room:get_password() or "", - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_moderatedroom', @@ -1023,9 +987,6 @@ module:hook("muc-config-submitted", function(event) event.status_codes[code] = true; end end); -module:hook("muc-config-submitted", function(event) - event.update_option("password", "muc#roomconfig_roomsecret"); -end); -- Removes everyone from the room function room_mt:clear(x) @@ -1251,15 +1212,6 @@ function room_mt:handle_mediated_invite(origin, stanza) return true; end --- Add password to outgoing invite -module:hook("muc-invite", function(event) - local password = event.room:get_password(); - if password then - local x = event.stanza:get_child("x", "http://jabber.org/protocol/muc#user"); - x:tag("password"):text(password):up(); - end -end); - -- COMPAT: Some older clients expect this module:hook("muc-invite", function(event) local room, stanza = event.room, event.stanza; @@ -1509,6 +1461,10 @@ local description = module:require "muc/description"; room_mt.get_description = description.get; room_mt.set_description = description.set; +local password = module:require "muc/password"; +room_mt.get_password = password.get; +room_mt.set_password = password.set; + local _M = {}; -- module "muc" function _M.new_room(jid, config) diff --git a/plugins/muc/password.lib.lua b/plugins/muc/password.lib.lua new file mode 100644 index 00000000..d169790a --- /dev/null +++ b/plugins/muc/password.lib.lua @@ -0,0 +1,70 @@ +-- 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. +-- + +local function get_password(room) + return room._data.password; +end + +local function set_password(room, password) + if password == "" then password = nil; end + if room._data.password == password then return false; end + room._data.password = password; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_password(event.room) and "muc_passwordprotected" or "muc_unsecured"}):up(); +end); + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_roomsecret"; + type = "text-private"; + label = "Password"; + value = get_password(event.room) or ""; + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_roomsecret"]; + if new ~= nil and set_password(event.room, new) then + event.status_codes["104"] = true; + end +end); + +-- Don't allow anyone to join room unless they provide the password +module:hook("muc-occupant-pre-join", function(event) + local room, stanza = event.room, event.stanza; + local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); + password = password and password:get_child_text("password", "http://jabber.org/protocol/muc"); + if not password or password == "" then password = nil; end + if get_password(room) ~= password then + local from, to = stanza.attr.from, stanza.attr.to; + module: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"; + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end +end, -20); + +-- Add password to outgoing invite +module:hook("muc-invite", function(event) + local password = get_password(event.room); + if password then + local x = event.stanza:get_child("x", "http://jabber.org/protocol/muc#user"); + x:tag("password"):text(password):up(); + end +end); + +return { + get = get_password; + set = set_password; +}; -- cgit v1.2.3 From 538ed6fc20ccd0b2622bc8b1b474c76db0cf8397 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 17:02:07 -0400 Subject: plugins/muc: Move name functions to seperate module --- plugins/muc/muc.lib.lua | 28 ++++------------------------ plugins/muc/name.lib.lua | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 24 deletions(-) create mode 100644 plugins/muc/name.lib.lua (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 62553321..15e3f9c9 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -393,9 +393,6 @@ function room_mt:get_disco_info(stanza) reply:add_child(form:form(nil, "result")); return reply; end -module:hook("muc-disco#info", function(event) - event.reply:tag("identity", {category="conference", type="text", name=event.room:get_name()}):up(); -end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = "http://jabber.org/protocol/muc"}):up(); end); @@ -467,16 +464,6 @@ function room_mt:handle_kickable(origin, stanza) return true; end -function room_mt:set_name(name) - if name == "" or type(name) ~= "string" or name == (jid_split(self.jid)) then name = nil; end - if self._data.name ~= name then - self._data.name = name; - if self.save then self:save(true); end - end -end -function room_mt:get_name() - return self._data.name or jid_split(self.jid); -end function room_mt:set_moderated(moderated) moderated = moderated and true or nil; if self._data.moderated ~= moderated then @@ -851,14 +838,6 @@ function room_mt:get_form_layout(actor) }); return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; end -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_roomname', - type = 'text-single', - label = 'Name', - value = event.room:get_name() or "", - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_persistentroom', @@ -960,9 +939,6 @@ function room_mt:process_form(origin, stanza) end return true; end -module:hook("muc-config-submitted", function(event) - event.update_option("name", "muc#roomconfig_roomname"); -end); module:hook("muc-config-submitted", function(event) event.update_option("persistent", "muc#roomconfig_persistentroom"); end); @@ -1457,6 +1433,10 @@ function room_mt:set_role(actor, occupant_jid, role, reason) return true; end +local name = module:require "muc/name"; +room_mt.get_name = name.get; +room_mt.set_name = name.set; + local description = module:require "muc/description"; room_mt.get_description = description.get; room_mt.set_description = description.set; diff --git a/plugins/muc/name.lib.lua b/plugins/muc/name.lib.lua new file mode 100644 index 00000000..49d12467 --- /dev/null +++ b/plugins/muc/name.lib.lua @@ -0,0 +1,47 @@ +-- 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. +-- + +local jid_split = require "util.jid".split; + +local function get_name(room) + return room._data.name or jid_split(room.jid); +end + +local function set_name(room, name) + if name == "" or name == (jid_split(room.jid)) then name = nil; end + if room._data.name == name then return false; end + room._data.name = name; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-disco#info", function(event) + event.reply:tag("identity", {category="conference", type="text", name=get_name(event.room)}):up(); +end); + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_roomname"; + type = "text-single"; + label = "Name"; + value = get_name(event.room) or ""; + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_roomname"]; + if new ~= nil and set_name(event.room, new) then + event.status_codes["104"] = true; + end +end); + +return { + get = get_name; + set = set_name; +}; -- cgit v1.2.3 From 9b57ba8391c07c5c5931641725b45760823570b7 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 18:37:52 -0400 Subject: plugins/muc/occupant.lib: Don't allow an unavailable session to be the primary jid --- plugins/muc/occupant.lib.lua | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/occupant.lib.lua b/plugins/muc/occupant.lib.lua index b4b12390..5cecb139 100644 --- a/plugins/muc/occupant.lib.lua +++ b/plugins/muc/occupant.lib.lua @@ -36,7 +36,8 @@ end local function copy_occupant(occupant) local sessions = {}; for full_jid, presence_stanza in pairs(occupant.sessions) do - if presence_stanza.attr.type ~= "unavailable" then + -- Don't keep unavailable presences, as they'll accumulate; unless they're the primary session + if presence_stanza.attr.type ~= "unavailable" or full_jid == occupant.jid then sessions[full_jid] = presence_stanza; end end @@ -49,24 +50,35 @@ local function copy_occupant(occupant) }, occupant_mt); end +-- finds another session to be the primary (there might not be one) +function occupant_mt:choose_new_primary() + for jid, pr in self:each_session() do + if pr.attr.type ~= "unavailable" then + return jid; + end + end + return nil; +end + function occupant_mt:set_session(real_jid, presence_stanza, replace_primary) local pr = get_filtered_presence(presence_stanza); pr.attr.from = self.nick; pr.attr.to = real_jid; self.sessions[real_jid] = pr; - if replace_primary or self.jid == nil then + if replace_primary then self.jid = real_jid; + elseif self.jid == nil or (pr.attr.type == "unavailable" and self.jid == real_jid) then + -- Only leave an unavailable presence as primary when there are no other options + self.jid = self:choose_new_primary() or real_jid; end end function occupant_mt:remove_session(real_jid) -- Delete original session - local presence_stanza = self.sessions[real_jid]; self.sessions[real_jid] = nil; if self.jid == real_jid then - -- find another session to be the primary (might be nil) - self.jid = next(self.sessions); + self.jid = self:choose_new_primary(); end end -- cgit v1.2.3 From 13c9f3da9076f3c1057d9ad6e6dc3a6e874d333c Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 12:25:03 -0400 Subject: plugins/muc/muc.lib: Clean up whois handling - adds functions `can_see_real_jids` and `get_base_presence` - In `publicise_occupant_status`, we don't generate each type of presence until it's used --- plugins/muc/muc.lib.lua | 65 +++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 26 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 15e3f9c9..5b160185 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -217,41 +217,53 @@ function room_mt:broadcast(stanza, cond_func) end end --- Broadcasts an occupant's presence to the whole room --- Takes (and modifies) the x element that goes into the stanzas -function room_mt:publicise_occupant_status(occupant, full_x, actor, reason) - local anon_x; - local has_anonymous = self:get_whois() ~= "anyone"; - if has_anonymous then - anon_x = st.clone(full_x); - self:build_item_list(occupant, anon_x, true, nil, actor, reason); +local function can_see_real_jids(whois, occupant) + if whois == "anyone" then + return true; + elseif whois == "moderators" then + return valid_roles[occupant.role or "none"] >= valid_roles.moderator; end - self:build_item_list(occupant,full_x, false, nil, actor, reason); +end - -- General populance - local full_p +local function get_base_presence(occupant) if occupant.role ~= nil then -- Try to use main jid's presence local pr = occupant:get_presence(); if pr ~= nil then - full_p = st.clone(pr); + return st.clone(pr); end end - if full_p == nil then - full_p = st.presence{from=occupant.nick; type="unavailable"}; - end - local anon_p; - if has_anonymous then - anon_p = st.clone(full_p); - anon_p:add_child(anon_x); + return st.presence {from = occupant.nick; type = "unavailable";}; +end + +-- Broadcasts an occupant's presence to the whole room +-- Takes the x element that goes into the stanzas +function room_mt:publicise_occupant_status(occupant, base_x, actor, reason) + -- Build real jid and (optionally) occupant jid template presences + local function get_presence(is_anonymous) + local x = st.clone(base_x); + self:build_item_list(occupant, x, is_anonymous, actor, reason); + return get_base_presence(occupant):add_child(x), x; + end + local full_p, full_x = get_presence(false); + local anon_p, anon_x; + local function get_anon_p() + if anon_p == nil then + anon_p, anon_x = get_presence(true); + end + return anon_p, anon_x; end - full_p:add_child(full_x); + local whois = self:get_whois(); + + -- General populance for nick, n_occupant in self:each_occupant() do - if nick ~= occupant.nick or n_occupant.role == nil then - local pr = full_p; - if has_anonymous and n_occupant.role ~= "moderator" and occupant.bare_jid ~= n_occupant.bare_jid then - pr = anon_p; + if nick ~= occupant.nick then + local pr; + if can_see_real_jids(whois, occupant) or occupant.bare_jid == n_occupant.bare_jid then + pr = full_p; + else + pr = get_anon_p(); end self:route_to_occupant(n_occupant, pr); end @@ -279,11 +291,12 @@ end function room_mt:send_occupant_list(to, filter) local to_bare = jid_bare(to); local is_anonymous = true; - if self:get_whois() ~= "anyone" then + local whois = self:get_whois(); + if whois ~= "anyone" then local affiliation = self:get_affiliation(to); if affiliation ~= "admin" and affiliation ~= "owner" then local occupant = self:get_occupant_by_real_jid(to); - if not occupant or occupant.role ~= "moderator" then + if not occupant or can_see_real_jids(whois, occupant) then is_anonymous = false; end end -- cgit v1.2.3 From 25e6558aafd036d7fddbf9ad95c39e2cda5b49b9 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 12:44:27 -0400 Subject: plugins/muc/muc.lib: Refactor of change-nick presence handling - Allow `nick` to be passed to `publicise_occupant_status`. - Moves multi-session handling to a more 'edge-case' area of code --- plugins/muc/muc.lib.lua | 66 +++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 35 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5b160185..353a7920 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -238,11 +238,11 @@ end -- Broadcasts an occupant's presence to the whole room -- Takes the x element that goes into the stanzas -function room_mt:publicise_occupant_status(occupant, base_x, actor, reason) +function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason) -- Build real jid and (optionally) occupant jid template presences local function get_presence(is_anonymous) local x = st.clone(base_x); - self:build_item_list(occupant, x, is_anonymous, actor, reason); + self:build_item_list(occupant, x, is_anonymous, nick, actor, reason); return get_base_presence(occupant):add_child(x), x; end local full_p, full_x = get_presence(false); @@ -277,13 +277,11 @@ function room_mt:publicise_occupant_status(occupant, base_x, actor, reason) else -- use their own presences as templates for full_jid, pr in occupant:each_session() do - if pr.attr.type ~= "unavailable" then - pr = st.clone(pr); - pr.attr.to = full_jid; - -- You can always see your own full jids - pr:add_child(full_x); - self:route_stanza(pr); - end + pr = st.clone(pr); + pr.attr.to = full_jid; + -- You can always see your own full jids + pr:add_child(full_x); + self:route_stanza(pr); end end end @@ -671,46 +669,44 @@ function room_mt:handle_presence_to_occupant(origin, stanza) -- Send presence stanza about original occupant if orig_occupant ~= nil and orig_occupant ~= dest_occupant then local orig_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); - + local dest_nick; if dest_occupant == nil then -- Session is leaving log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); orig_occupant:set_session(real_jid, stanza); else log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick); - orig_occupant:remove_session(real_jid); -- If we are moving to a new nick; we don't want to get our own presence - - local dest_nick = select(3, jid_split(dest_occupant.nick)); - local affiliation = self:get_affiliation(bare_jid); - - -- This session + local generated_unavail = st.presence {from = orig_occupant.nick, to = real_jid, type = "unavailable"}; + orig_occupant:set_session(real_jid, generated_unavail); + dest_nick = select(3, jid_split(dest_occupant.nick)); if not is_first_dest_session then -- User is swapping into another pre-existing session log("debug", "session %s is swapping into multisession %s, showing it leave.", real_jid, dest_occupant.nick); -- Show the other session leaving local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}) :tag("status"):text("you are joining pre-existing session " .. dest_nick):up(); - add_item(x, affiliation, "none"); + add_item(x, self:get_affiliation(bare_jid), "none"); local pr = st.presence{from = dest_occupant.nick, to = real_jid, type = "unavailable"} :add_child(x); self:route_stanza(pr); - else - if is_last_orig_session then -- User is moving to a new session - log("debug", "no sessions in %s left; marking as nick change", orig_occupant.nick); - -- Everyone gets to see this as a nick change - local jid = self:get_whois() ~= "anyone" and real_jid or nil; -- FIXME: mods should see real jids - add_item(orig_x, affiliation, orig_occupant.role, jid, dest_nick); - orig_x:tag("status", {code = "303";}):up(); - end end - -- The session itself always sees a nick change - local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); - add_item(x, affiliation, orig_occupant.role, real_jid, dest_nick); - -- self:build_item_list(orig_occupant, x, false); -- COMPAT - x:tag("status", {code = "303";}):up(); - x:tag("status", {code = "110";}):up(); - self:route_stanza(st.presence{from = orig_occupant.nick, to = real_jid, type = "unavailable"}:add_child(x)); + if is_first_dest_session and is_last_orig_session then -- Normal nick change + log("debug", "no sessions in %s left; publically marking as nick change", orig_occupant.nick); + orig_x:tag("status", {code = "303";}):up(); + else -- The session itself always needs to see a nick change + -- don't want to get our old nick's available presence, + -- so remove our session from there, and manually generate an unavailable + orig_occupant:remove_session(real_jid); + log("debug", "generating nick change for %s", real_jid); + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + -- self:build_item_list(orig_occupant, x, false, dest_nick); -- COMPAT: clients get confused if they see other items besides their own + add_item(x, self:get_affiliation(bare_jid), orig_occupant.role, real_jid, dest_nick); + x:tag("status", {code = "303";}):up(); + x:tag("status", {code = "110";}):up(); + self:route_stanza(generated_unavail:add_child(x)); + dest_nick = nil; -- set dest_nick to nil; so general populance doesn't see it for whole orig_occupant + end end self:save_occupant(orig_occupant); - self:publicise_occupant_status(orig_occupant, orig_x); + self:publicise_occupant_status(orig_occupant, orig_x, dest_nick); if is_last_orig_session then module:fire_event("muc-occupant-left", {room = self; nick = orig_occupant.nick;}); @@ -1381,7 +1377,7 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason) end local is_semi_anonymous = self:get_whois() == "moderators"; for occupant, old_role in pairs(occupants_updated) do - self:publicise_occupant_status(occupant, x, actor, reason); + self:publicise_occupant_status(occupant, x, nil, actor, reason); if is_semi_anonymous and (old_role == "moderator" and occupant.role ~= "moderator") or (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status @@ -1442,7 +1438,7 @@ function room_mt:set_role(actor, occupant_jid, role, reason) end occupant.role = role; self:save_occupant(occupant); - self:publicise_occupant_status(occupant, x, actor, reason); + self:publicise_occupant_status(occupant, x, nil, actor, reason); return true; end -- cgit v1.2.3 From b1cf3db9a1227bd6a2d21d27b81a65d597e5b7f8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 14:23:06 -0400 Subject: plugins/muc/muc.lib: Remember to coerce nil role to "none" --- plugins/muc/muc.lib.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 353a7920..20cbe04d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -163,10 +163,11 @@ local function add_item(x, affiliation, role, jid, nick, actor, reason) x:up(); return x end + -- actor is (real) jid function room_mt:build_item_list(occupant, x, is_anonymous, nick, actor, reason) - local affiliation = self:get_affiliation(occupant.bare_jid); - local role = occupant.role; + local affiliation = self:get_affiliation(occupant.bare_jid) or "none"; + local role = occupant.role or "none"; local actor_attr; if actor then actor_attr = {nick = select(3,jid_split(self:get_occupant_jid(actor)))}; -- cgit v1.2.3 From bae30c03d41b80b2bd10ca2d0c9b60d2a6913bc3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 14:24:27 -0400 Subject: plugins/muc: Move `whois` code to seperate file --- plugins/muc/muc.lib.lua | 54 ++++---------------------------------- plugins/muc/whois.lib.lua | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 49 deletions(-) create mode 100644 plugins/muc/whois.lib.lua (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 20cbe04d..7a8464e5 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -420,9 +420,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_hidden() and "muc_hidden" or "muc_public"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_whois() ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up(); -end); module:hook("muc-disco#info", function(event) local count = 0; for _ in event.room:each_occupant() do count = count + 1; end table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); @@ -543,20 +540,6 @@ function room_mt:set_historylength(length) 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 - -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); @@ -872,18 +855,6 @@ module:hook("muc-config-form", function(event) value = event.room:get_changesubject() }); end); -module:hook("muc-config-form", function(event) - local whois = event.room:get_whois(); - table.insert(event.form, { - name = 'muc#roomconfig_whois', - type = 'list-single', - label = 'Who May Discover Real JIDs?', - value = { - { value = 'moderators', label = 'Moderators Only', default = whois == 'moderators' }, - { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } - } - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_moderatedroom', @@ -967,12 +938,6 @@ end); module:hook("muc-config-submitted", function(event) event.update_option("historylength", "muc#roomconfig_historylength"); end); -module:hook("muc-config-submitted", function(event) - if event.update_option("whois", "muc#roomconfig_whois", valid_whois) then - local code = (event.room:get_whois() == 'moderators') and "173" or "172"; - event.status_codes[code] = true; - end -end); -- Removes everyone from the room function room_mt:clear(x) @@ -1218,21 +1183,9 @@ module:hook("muc-invite", function(event) :up(); end); --- Mask 'from' jid as occupant jid if room is anonymous -module:hook("muc-invite", function(event) - local room, stanza = event.room, event.stanza; - if room:get_whois() == "moderators" and room:get_default_role(room:get_affiliation(stanza.attr.to)) ~= "moderator" then - local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); - local occupant_jid = room:get_occupant_jid(invite.attr.from); - if occupant_jid ~= nil then -- FIXME: This will expose real jid if inviter is not in room - invite.attr.from = occupant_jid; - end - end -end, 50); - -- When an invite is sent; add an affiliation for the invitee module:hook("muc-invite", function(event) - local room, stanza = event.room, event.stanza + 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 @@ -1455,6 +1408,10 @@ local password = module:require "muc/password"; room_mt.get_password = password.get; room_mt.set_password = password.set; +local whois = module:require "muc/whois"; +room_mt.get_whois = whois.get; +room_mt.set_whois = whois.set; + local _M = {}; -- module "muc" function _M.new_room(jid, config) @@ -1463,7 +1420,6 @@ function _M.new_room(jid, config) _jid_nick = {}; _occupants = {}; _data = { - whois = 'moderators'; history_length = math.min((config and config.history_length) or default_history_length, max_history_length); }; diff --git a/plugins/muc/whois.lib.lua b/plugins/muc/whois.lib.lua new file mode 100644 index 00000000..f89e6087 --- /dev/null +++ b/plugins/muc/whois.lib.lua @@ -0,0 +1,67 @@ +-- 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. +-- + +local valid_whois = { + moderators = true; + anyone = true; +}; + +local function get_whois(room) + return room._data.whois or "moderators"; +end + +local function set_whois(room, whois) + assert(valid_whois[whois], "Invalid whois value") + if get_whois(room) == whois then return false; end + room._data.whois = whois; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_whois(event.room) ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up(); +end); + +module:hook("muc-config-form", function(event) + local whois = get_whois(event.room); + table.insert(event.form, { + name = 'muc#roomconfig_whois', + type = 'list-single', + label = 'Who May Discover Real JIDs?', + value = { + { value = 'moderators', label = 'Moderators Only', default = whois == 'moderators' }, + { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } + } + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_whois"]; + if new ~= nil and set_whois(event.room, new) then + local code = (new == 'moderators') and "173" or "172"; + event.status_codes[code] = true; + end +end); + +-- Mask 'from' jid as occupant jid if room is anonymous +module:hook("muc-invite", function(event) + local room, stanza = event.room, event.stanza; + if get_whois(room) == "moderators" and room:get_default_role(room:get_affiliation(stanza.attr.to)) ~= "moderator" then + local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local occupant_jid = room:get_occupant_jid(invite.attr.from); + if occupant_jid ~= nil then -- FIXME: This will expose real jid if inviter is not in room + invite.attr.from = occupant_jid; + end + end +end, 50); + +return { + get = get_whois; + set = set_whois; +}; -- cgit v1.2.3 From b661ecb24f5e68a3a7af2532f8e91189e305fb82 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 15:14:52 -0400 Subject: plugins/muc: Move history to an external module This resulted in the split up of the main muc-occupant-joined event handler into 3 seperate ones, handling occupant list, history and subject --- plugins/muc/history.lib.lua | 161 ++++++++++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 167 ++++++-------------------------------------- 2 files changed, 182 insertions(+), 146 deletions(-) create mode 100644 plugins/muc/history.lib.lua (limited to 'plugins') diff --git a/plugins/muc/history.lib.lua b/plugins/muc/history.lib.lua new file mode 100644 index 00000000..417d62a8 --- /dev/null +++ b/plugins/muc/history.lib.lua @@ -0,0 +1,161 @@ +-- 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. +-- + +local gettime = os.time; +local datetime = require "util.datetime"; +local st = require "util.stanza"; + +local default_history_length, max_history_length = 20, math.huge; + +local function set_max_history_length(_max_history_length) + max_history_length = _max_history_length or math.huge; +end + +local function get_historylength(room) + return math.min(room._data.history_length or default_history_length, max_history_length); +end + +local function set_historylength(room, length) + length = assert(tonumber(length), "Length not a valid number"); + if length == default_history_length then length = nil; end + room._data.history_length = length; + return true; +end + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_historylength"; + type = "text-single"; + label = "Maximum Number of History Messages Returned by Room"; + value = tostring(get_historylength(event.room)); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_historylength"]; + if new ~= nil and set_historylength(event.room, new) then + event.status_codes["104"] = true; + end +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, default_history_length, nil; + end + + local maxchars = tonumber(history_tag.attr.maxchars); + + local maxstanzas = tonumber(history_tag.attr.maxstanzas); + + -- messages received since the UTC datetime specified + local since = history_tag.attr.since; + if since then + since = datetime.parse(since); + end + + -- 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 + + return maxchars, maxstanzas, since; +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; + + 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 + 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 + end + if since and since > entry.timestamp then break; end + if n + 1 > maxstanzas then break; end + n = n + 1; + end + + local i = history_len-n+1 + function event:next_stanza() + 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 + return true; +end); + +local function send_history(room, stanza) + local maxchars, maxstanzas, since = parse_history(stanza); + local event = { + room = room; + 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 + room:route_stanza(msg); + end +end + +-- Send history on join +module:hook("muc-occupant-joined", function(event) + send_history(event.room, event.stanza); +end, 50); -- Between occupant list (80) and subject(20) + +-- add to history +module:hook("muc-broadcast-message", function(event) + local historic = event.stanza:get_child("body"); + if 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 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, timestamp = ts }; + table.insert(history, entry); + while #history > get_historylength(room) do table.remove(history, 1) end + end +end); + +return { + set_max_length = set_max_history_length; + parse_history = parse_history; + send = send_history; + get_length = get_historylength; + set_length = set_historylength; +}; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7a8464e5..89953615 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -11,13 +11,8 @@ local select = select; local pairs, ipairs = pairs, ipairs; local next = next; local setmetatable = setmetatable; -local t_insert, t_remove = table.insert, table.remove; - -local gettime = os.time; -local datetime = require "util.datetime"; local dataform = require "util.dataforms"; - local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; @@ -28,7 +23,6 @@ local md5 = require "util.hashes".md5; local occupant_lib = module:require "muc/occupant" -local default_history_length, max_history_length = 20, math.huge; local is_kickable_error do local kickable_error_conditions = { @@ -185,29 +179,11 @@ function room_mt:build_item_list(occupant, x, is_anonymous, nick, actor, reason) return x end -function room_mt:broadcast_message(stanza, historic) - module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic}); +function room_mt:broadcast_message(stanza) + module:fire_event("muc-broadcast-message", {room = self, stanza = stanza}); self:broadcast(stanza); 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 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, timestamp = ts }; - t_insert(history, entry); - while #history > room:get_historylength() do t_remove(history, 1) end - end -end); - -- Broadcast a stanza to all occupants in the room. -- optionally checks conditional called with (nick, occupant) function room_mt:broadcast(stanza, cond_func) @@ -312,90 +288,6 @@ function room_mt:send_occupant_list(to, filter) end 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 maxchars = tonumber(history_tag.attr.maxchars); - - local maxstanzas = tonumber(history_tag.attr.maxstanzas); - - -- messages received since the UTC datetime specified - local since = history_tag.attr.since; - if since then - since = datetime.parse(since); - end - - -- 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 - - return maxchars, maxstanzas, since -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 - - 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 - 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 - end - if since and since > entry.timestamp then break; end - if n + 1 > maxstanzas then break; end - n = n + 1; - end - - local i = history_len-n+1 - function event:next_stanza() - 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 - return true; -end); - -function room_mt:send_history(stanza) - local maxchars, maxstanzas, since = parse_history(stanza) - 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 - function room_mt:get_disco_info(stanza) local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#info"); local form = dataform.new { @@ -451,7 +343,7 @@ function room_mt:set_subject(current_nick, subject) self._data['subject_from'] = current_nick; if self.save then self:save(); end local msg = create_subject_message(current_nick, subject); - self:broadcast_message(msg, false); + self:broadcast_message(msg); return true; end @@ -529,16 +421,6 @@ 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 -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) @@ -569,16 +451,19 @@ module:hook("muc-occupant-pre-join", function(event) end end, -10); +-- Send occupant list to newly joined user module:hook("muc-occupant-joined", function(event) - local room, stanza = event.room, event.stanza; - local real_jid = stanza.attr.from; - room:send_occupant_list(real_jid, function(nick, occupant) + local real_jid = event.stanza.attr.from; + event.room:send_occupant_list(real_jid, function(nick, occupant) -- Don't include self return occupant:get_presence(real_jid) == nil; end); - room:send_history(stanza); - room:send_subject(real_jid); -end, -1); +end, 80); + +-- Send subject to joining user +module:hook("muc-occupant-joined", function(event) + event.room:send_subject(event.stanza.attr.from); +end, 20); function room_mt:handle_presence_to_occupant(origin, stanza) local type = stanza.attr.type; @@ -871,14 +756,6 @@ module:hook("muc-config-form", function(event) value = event.room:get_members_only() }); end); -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_historylength', - type = 'text-single', - label = 'Maximum Number of History Messages Returned by Room', - value = tostring(event.room:get_historylength()) - }); -end); function room_mt:process_form(origin, stanza) local form = stanza.tags[1]:get_child("x", "jabber:x:data"); @@ -913,7 +790,7 @@ function room_mt:process_form(origin, stanza) msg:tag("status", {code = code;}):up(); end msg:up(); - self:broadcast_message(msg, false) + self:broadcast_message(msg); end else origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); @@ -935,9 +812,6 @@ end); module:hook("muc-config-submitted", function(event) event.update_option("changesubject", "muc#roomconfig_changesubject"); end); -module:hook("muc-config-submitted", function(event) - event.update_option("historylength", "muc#roomconfig_historylength"); -end); -- Removes everyone from the room function room_mt:clear(x) @@ -1099,7 +973,7 @@ function room_mt:handle_groupchat_to_room(origin, stanza) origin.send(st.error_reply(stanza, "auth", "forbidden")); end else - self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); + self:broadcast_message(stanza); end stanza.attr.from = from; return true; @@ -1412,25 +1286,26 @@ local whois = module:require "muc/whois"; room_mt.get_whois = whois.get; room_mt.set_whois = whois.set; +local history = module:require "muc/history"; +room_mt.send_history = history.send; +room_mt.get_historylength = history.get_length; +room_mt.set_historylength = history.set_length; + local _M = {}; -- module "muc" +_M.set_max_history_length = history.set_max_length; + function _M.new_room(jid, config) return setmetatable({ jid = jid; _jid_nick = {}; _occupants = {}; _data = { - 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; -- cgit v1.2.3 From 185f0d33268c363466e638cbf14bd4ba492be13d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 16:04:04 -0400 Subject: plugins/muc/muc.lib: Move occupancy check to later in `deconstruct_stanza_id`: As vcards are from the bare jid, you need to use the `from_jid` out of the encoded `id` --- plugins/muc/muc.lib.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 89953615..694e13ff 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -622,13 +622,14 @@ 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:get_occupant_jid(from); local occupant = self:get_occupant_by_nick(to); if (type == "error" or type == "result") then do -- deconstruct_stanza_id - if not current_nick or not occupant then return nil; end + if not occupant 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 from_occupant_jid = self:get_occupant_jid(from_jid); + if from_occupant_jid == nil then return nil; end local session_jid for to_jid in occupant:each_session() do if md5(to_jid) == to_jid_hash then @@ -637,13 +638,14 @@ function room_mt:handle_iq_to_occupant(origin, stanza) end end if session_jid == nil then return nil; end - stanza.attr.from, stanza.attr.to, stanza.attr.id = current_nick, session_jid, id + stanza.attr.from, stanza.attr.to, stanza.attr.id = from_jid, 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:get_occupant_jid(from); if not current_nick then origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); return true; -- cgit v1.2.3 From e14f89d1d228c410f3a67cfd59792f660ad9562d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 16:10:43 -0400 Subject: plugins/muc/muc.lib: Use util.iterators to count occupants --- plugins/muc/muc.lib.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 694e13ff..59069cae 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -13,6 +13,7 @@ local next = next; local setmetatable = setmetatable; local dataform = require "util.dataforms"; +local iterators = require "util.iterators"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; @@ -313,7 +314,7 @@ module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_hidden() and "muc_hidden" or "muc_public"}):up(); end); module:hook("muc-disco#info", function(event) - local count = 0; for _ in event.room:each_occupant() do count = count + 1; end + local count = iterators.count(event.room:each_occupant()); table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); end); -- cgit v1.2.3 From adb653030ff70cff8ecbc10eb3650cc6ff79fba9 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 17:09:04 -0400 Subject: plugins/muc: Move valid_roles, valid_affiliations and is_kickable_error to new muc/util module --- plugins/muc/muc.lib.lua | 38 +++----------------------------------- plugins/muc/util.lib.lua | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 35 deletions(-) create mode 100644 plugins/muc/util.lib.lua (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 59069cae..6f19cd53 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -23,26 +23,9 @@ local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; local occupant_lib = module:require "muc/occupant" - - -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 muc_util = module:require "muc/util"; +local is_kickable_error = muc_util.is_kickable_error; +local valid_roles, valid_affiliations = muc_util.valid_roles, muc_util.valid_affiliations; local room_mt = {}; room_mt.__index = room_mt; @@ -55,21 +38,6 @@ function room_mt:get_occupant_jid(real_jid) return self._jid_nick[real_jid] end -local valid_affiliations = { - outcast = 0; - none = 1; - member = 2; - admin = 3; - owner = 4; -}; - -local valid_roles = { - none = 0; - visitor = 1; - participant = 2; - moderator = 3; -}; - function room_mt:get_default_role(affiliation) if affiliation == "owner" or affiliation == "admin" then return "moderator"; diff --git a/plugins/muc/util.lib.lua b/plugins/muc/util.lib.lua new file mode 100644 index 00000000..90a3a183 --- /dev/null +++ b/plugins/muc/util.lib.lua @@ -0,0 +1,44 @@ +-- 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. +-- + +local _M = {}; + +_M.valid_affiliations = { + outcast = -1; + none = 0; + member = 1; + admin = 2; + owner = 3; +}; + +_M.valid_roles = { + none = 0; + visitor = 1; + participant = 2; + moderator = 3; +}; + +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 _M.is_kickable_error(stanza) + local cond = select(2, stanza:get_error()) or "malformed error"; + return kickable_error_conditions[cond]; +end + +return _M; -- cgit v1.2.3 From d9474e875f5ea22a41f081e9586840ca70739db8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 18:10:16 -0400 Subject: plugins/muc/muc.lib: Fix getting a list of occupants by role (it was sending presences instead of items inside an iq) --- plugins/muc/muc.lib.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6f19cd53..fdddcfbe 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -113,7 +113,6 @@ function room_mt:route_to_occupant(occupant, stanza) stanza.attr.to = to; end --- Adds an item to an "x" element. -- actor is the attribute table local function add_item(x, affiliation, role, jid, nick, actor, reason) x:tag("item", {affiliation = affiliation; role = role; jid = jid; nick = nick;}) @@ -874,9 +873,17 @@ function room_mt:handle_admin_query_get_command(origin, stanza) 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 + if valid_roles[role or "none"] >= valid_roles.moderator then if _rol == "none" then _rol = nil; end - self:send_occupant_list(actor, function(occupant_jid, occupant) return occupant.role == _rol end); + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + -- TODO: whois check here? (though fully anonymous rooms are not supported) + for occupant_jid, occupant in self:each_occupant() do + if occupant.role == _rol then + local nick = select(3,jid_split(occupant_jid)); + self:build_item_list(occupant, reply, false, nick); + end + end + origin.send(reply:up()); return true; else origin.send(st.error_reply(stanza, "auth", "forbidden")); -- cgit v1.2.3 From 0b749352c28b79d5ebc582d479591adb8aa7ae2f Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 18:36:28 -0400 Subject: plugins/muc/muc.lib: Turn get_default_role into an event --- plugins/muc/muc.lib.lua | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index fdddcfbe..4f8aa15d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -39,16 +39,30 @@ function room_mt:get_occupant_jid(real_jid) end function room_mt:get_default_role(affiliation) - if affiliation == "owner" or affiliation == "admin" then + local role = module:fire_event("muc-get-default-role", { + room = self; + affiliation = affiliation; + affiliation_rank = valid_affiliations[affiliation or "none"]; + }); + return role, valid_roles[role or "none"]; +end +module:hook("muc-get-default-role", function(event) + if event.affiliation_rank >= valid_affiliations.admin then return "moderator"; - elseif affiliation == "member" then + elseif event.affiliation_rank >= valid_affiliations.member then return "participant"; - elseif not affiliation then - if not self:get_members_only() then - return self:get_moderated() and "visitor" or "participant"; - end end -end +end); +module:hook("muc-get-default-role", function(event) + if not event.affiliation and event.room:get_members_only() then + return false; + end +end); +module:hook("muc-get-default-role", function(event) + if not event.affiliation then + return event.room:get_moderated() and "visitor" or "participant"; + end +end, -1); --- Occupant functions function room_mt:new_occupant(bare_real_jid, nick) -- cgit v1.2.3 From cd72b2acc4eb00c11c30eab62e1cbf496996c36e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 4 Apr 2014 11:20:20 -0400 Subject: plugins/muc/muc.lib: Move members_only into seperate file --- plugins/muc/members_only.lib.lua | 89 ++++++++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 66 ++--------------------------- 2 files changed, 93 insertions(+), 62 deletions(-) create mode 100644 plugins/muc/members_only.lib.lua (limited to 'plugins') diff --git a/plugins/muc/members_only.lib.lua b/plugins/muc/members_only.lib.lua new file mode 100644 index 00000000..84a17699 --- /dev/null +++ b/plugins/muc/members_only.lib.lua @@ -0,0 +1,89 @@ +-- 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. +-- + +local muc_util = module:require "muc/util"; +local valid_roles, valid_affiliations = muc_util.valid_roles, muc_util.valid_affiliations; + +local function get_members_only(room) + return room._data.members_only; +end + +local function set_members_only(room, members_only) + members_only = members_only and true or nil; + if room._data.members_only == members_only then return false; end + room._data.members_only = members_only; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_members_only(event.room) and "muc_membersonly" or "muc_open"}):up(); +end); + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_membersonly"; + type = "boolean"; + label = "Make Room Members-Only?"; + value = get_members_only(event.room); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_membersonly"]; + if new ~= nil and set_members_only(event.room, new) then + event.status_codes["104"] = true; + end +end); + +-- No affiliation => role of "none" +module:hook("muc-get-default-role", function(event) + if not event.affiliation and get_members_only(event.room) then + return false; + end +end); + +-- registration required for entering members-only room +module:hook("muc-occupant-pre-join", function(event) + local room, stanza = event.room, event.stanza; + local affiliation = room:get_affiliation(stanza.attr.from); + if affiliation == nil and get_members_only(event.room) then + local reply = st.error_reply(stanza, "auth", "registration-required"):up(); + reply.tags[1].attr.code = "407"; + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end +end, -5); + +-- Invitation privileges in members-only rooms SHOULD be restricted to room admins; +-- if a member without privileges to edit the member list attempts to invite another user +-- the service SHOULD return a error to the occupant +module:hook("muc-pre-invite", function(event) + local room, stanza = event.room, event.stanza; + if get_members_only(room) and room:get_affiliation(stanza.attr.from) or "none" < valid_affiliations.admin then + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end +end); + +-- When an invite is sent; add an affiliation for the invitee +module:hook("muc-invite", function(event) + local room, stanza = event.room, event.stanza; + local invitee = stanza.attr.to; + if get_members_only(room) and not room:get_affiliation(invitee) then + local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from; + module:log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); + room:set_affiliation(from, invitee, "member", "Invited by " .. from); -- This might fail; ignore for now + end +end); + +return { + get = get_members_only; + set = set_members_only; +}; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 4f8aa15d..d03a32f3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -53,11 +53,6 @@ module:hook("muc-get-default-role", function(event) return "participant"; end end); -module:hook("muc-get-default-role", function(event) - if not event.affiliation and event.room:get_members_only() then - return false; - end -end); module:hook("muc-get-default-role", function(event) if not event.affiliation then return event.room:get_moderated() and "visitor" or "participant"; @@ -285,9 +280,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_members_only() and "muc_membersonly" or "muc_open"}):up(); -end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_persistent() and "muc_persistent" or "muc_temporary"}):up(); end); @@ -357,16 +349,6 @@ end function room_mt:get_moderated() return self._data.moderated; end -function room_mt:set_members_only(members_only) - members_only = members_only and true or nil; - if self._data.members_only ~= members_only then - self._data.members_only = members_only; - if self.save then self:save(true); end - end -end -function room_mt:get_members_only() - return self._data.members_only; -end function room_mt:set_persistent(persistent) persistent = persistent and true or nil; if self._data.persistent ~= persistent then @@ -409,17 +391,6 @@ module:hook("muc-room-pre-create", function(event) event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); end, -1); --- registration required for entering members-only room -module:hook("muc-occupant-pre-join", function(event) - local room, stanza = event.room, event.stanza; - local affiliation = room:get_affiliation(stanza.attr.from); - if affiliation == nil and event.room:get_members_only() then - local reply = st.error_reply(stanza, "auth", "registration-required"):up(); - reply.tags[1].attr.code = "407"; - event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - end -end, -5); -- check if user is banned module:hook("muc-occupant-pre-join", function(event) @@ -732,14 +703,6 @@ module:hook("muc-config-form", function(event) value = event.room:get_moderated() }); end); -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_membersonly', - type = 'boolean', - label = 'Make Room Members-Only?', - value = event.room:get_members_only() - }); -end); function room_mt:process_form(origin, stanza) local form = stanza.tags[1]:get_child("x", "jabber:x:data"); @@ -787,9 +750,6 @@ end); module:hook("muc-config-submitted", function(event) event.update_option("moderated", "muc#roomconfig_moderatedroom"); end); -module:hook("muc-config-submitted", function(event) - event.update_option("members_only", "muc#roomconfig_membersonly"); -end); module:hook("muc-config-submitted", function(event) event.update_option("public", "muc#roomconfig_publicroom"); end); @@ -997,17 +957,6 @@ module:hook("muc-pre-invite", function(event) end end); --- Invitation privileges in members-only rooms SHOULD be restricted to room admins; --- if a member without privileges to edit the member list attempts to invite another user --- the service SHOULD return a error to the occupant -module:hook("muc-pre-invite", function(event) - local room, stanza = event.room, event.stanza; - if room:get_members_only() and valid_affiliations[room:get_affiliation(stanza.attr.from) or "none"] < valid_affiliations.admin then - event.origin.send(st.error_reply(stanza, "auth", "forbidden")); - return true; - end -end); - function room_mt:handle_mediated_invite(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); local invitee = jid_prep(payload.attr.to); @@ -1049,17 +998,6 @@ module:hook("muc-invite", function(event) :up(); end); --- When an invite is sent; add an affiliation for the invitee -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 - local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from - log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); - room:set_affiliation(from, invitee, "member", "Invited by " .. from); -- This might fail; ignore for now - end -end); - 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); @@ -1278,6 +1216,10 @@ local whois = module:require "muc/whois"; room_mt.get_whois = whois.get; room_mt.set_whois = whois.set; +local members_only = module:require "muc/members_only"; +room_mt.get_members_only = members_only.get; +room_mt.set_members_only = members_only.set; + local history = module:require "muc/history"; room_mt.send_history = history.send; room_mt.get_historylength = history.get_length; -- cgit v1.2.3 From 0f4dd8a1b5de82bef12ea4b4c5696188fd740be6 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 15 Apr 2014 17:06:04 -0400 Subject: plugins/muc: Move persistent room configuration to own module --- plugins/muc/mod_muc.lua | 8 ++++--- plugins/muc/muc.lib.lua | 29 ++++--------------------- plugins/muc/persistent.lib.lua | 49 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 plugins/muc/persistent.lib.lua (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 6f6094b4..865e07b9 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -26,6 +26,7 @@ end local muclib = module:require "muc"; local muc_new_room = muclib.new_room; +local persistent = module:require "muc/persistent"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; @@ -65,8 +66,9 @@ end local function room_save(room, forced) local node = jid_split(room.jid); - persistent_rooms[room.jid] = room._data.persistent; - if room._data.persistent then + local is_persistent = persistent.get(room); + persistent_rooms[room.jid] = is_persistent; + if is_persistent then local history = room._data.history; room._data.history = nil; local data = { @@ -138,7 +140,7 @@ 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 + if not next(room._occupants) and not persistent.get(room) then -- empty, non-persistent room module:fire_event("muc-room-destroyed", { room = room }); end end); diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d03a32f3..87f3d69c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -280,9 +280,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_persistent() and "muc_persistent" or "muc_temporary"}):up(); -end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_hidden() and "muc_hidden" or "muc_public"}):up(); end); @@ -349,16 +346,6 @@ end function room_mt:get_moderated() return self._data.moderated; end -function room_mt:set_persistent(persistent) - persistent = persistent and true or nil; - if self._data.persistent ~= persistent then - self._data.persistent = persistent; - if self.save then self:save(true); end - end -end -function room_mt:get_persistent() - return self._data.persistent; -end function room_mt:set_hidden(hidden) hidden = hidden and true or nil; if self._data.hidden ~= hidden then @@ -671,14 +658,6 @@ function room_mt:get_form_layout(actor) }); return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; end -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_persistentroom', - type = 'boolean', - label = 'Make Room Persistent?', - value = event.room:get_persistent() - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_publicroom', @@ -744,9 +723,6 @@ function room_mt:process_form(origin, stanza) end return true; end -module:hook("muc-config-submitted", function(event) - event.update_option("persistent", "muc#roomconfig_persistentroom"); -end); module:hook("muc-config-submitted", function(event) event.update_option("moderated", "muc#roomconfig_moderatedroom"); end); @@ -780,7 +756,6 @@ function room_mt:destroy(newjid, reason, password) if password then x:tag("password"):text(password):up(); end x:up(); self:clear(x); - self:set_persistent(false); module:fire_event("muc-room-destroyed", { room = self }); end @@ -1220,6 +1195,10 @@ local members_only = module:require "muc/members_only"; room_mt.get_members_only = members_only.get; room_mt.set_members_only = members_only.set; +local persistent = module:require "muc/persistent"; +room_mt.get_persistent = persistent.get; +room_mt.set_persistent = persistent.set; + local history = module:require "muc/history"; room_mt.send_history = history.send; room_mt.get_historylength = history.get_length; diff --git a/plugins/muc/persistent.lib.lua b/plugins/muc/persistent.lib.lua new file mode 100644 index 00000000..ccf6fffe --- /dev/null +++ b/plugins/muc/persistent.lib.lua @@ -0,0 +1,49 @@ +-- 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. +-- + +local function get_persistent(room) + return room._data.persistent; +end + +local function set_persistent(room, persistent) + persistent = persistent and true or nil; + if get_persistent(room) == persistent then return false; end + room._data.persistent = persistent; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_persistentroom"; + type = "boolean"; + label = "Make Room Persistent?"; + value = get_persistent(event.room); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_persistentroom"]; + if new ~= nil and set_persistent(event.room, new) then + event.status_codes["104"] = true; + end +end); + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_persistent(event.room) and "muc_persistent" or "muc_temporary"}):up(); +end); + +module:hook("muc-room-destroyed", function(event) + set_persistent(event.room, false); +end); + +return { + get = get_persistent; + set = set_persistent; +}; -- cgit v1.2.3 From bc3b5d98d53223906060c78c2a5dd284a7f1fa03 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 15 Apr 2014 18:20:56 -0400 Subject: plugins/muc/muc.lib: Refactor subject logic; fix bug of mixed up subject/author --- plugins/muc/muc.lib.lua | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 87f3d69c..782d4fe2 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -297,7 +297,7 @@ function room_mt:get_disco_items(stanza) end function room_mt:get_subject() - return self._data['subject'], self._data['subject_from'] + return self._data.subject_from, self._data.subject; end local function create_subject_message(from, subject) return st.message({from = from; type = "groupchat"}) @@ -878,7 +878,25 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) end end +module:hook("muc-subject-change", function(event) + local room, stanza = event.room, event.stanza; + local occupant = room:get_occupant_by_real_jid(stanza.attr.from); + if occupant.role == "moderator" or + ( occupant.role == "participant" and room:get_changesubject() ) then -- and participant + local subject = stanza:get_child_text("subject"); + room:set_subject(occupant.nick, subject); + else + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); + end + return true; +end); + function room_mt:handle_groupchat_to_room(origin, stanza) + -- Prosody has made the decision that messages with are exclusively subject changes + -- e.g. body will be ignored; even if the subject change was not allowed + if stanza:get_child("subject") then + return module:fire_event("muc-subject-change", {room = self, origin = origin, stanza = stanza}); + end local from = stanza.attr.from; local occupant = self:get_occupant_by_real_jid(from); if not occupant then -- not in room @@ -887,24 +905,11 @@ function room_mt:handle_groupchat_to_room(origin, stanza) elseif occupant.role == "visitor" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; - else - local from = stanza.attr.from; - stanza.attr.from = occupant.nick; - local subject = stanza:get_child_text("subject"); - if subject then - if occupant.role == "moderator" or - ( self:get_changesubject() and occupant.role == "participant" ) then -- and participant - self:set_subject(occupant.nick, subject); - else - stanza.attr.from = from; - origin.send(st.error_reply(stanza, "auth", "forbidden")); - end - else - self:broadcast_message(stanza); - end - stanza.attr.from = from; - return true; end + stanza.attr.from = occupant.nick; + self:broadcast_message(stanza); + stanza.attr.from = from; + return true; end -- hack - some buggy clients send presence updates to the room rather than their nick -- cgit v1.2.3 From 68f64d281702713a8a342807b6b4b51616f3d40d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 16 Apr 2014 13:54:51 -0400 Subject: plugins/muc: Move subject code to seperate module --- plugins/muc/muc.lib.lua | 68 ++++----------------------------- plugins/muc/subject.lib.lua | 93 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 61 deletions(-) create mode 100644 plugins/muc/subject.lib.lua (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 782d4fe2..26b2d335 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -296,28 +296,6 @@ function room_mt:get_disco_items(stanza) return reply; end -function room_mt:get_subject() - return self._data.subject_from, self._data.subject; -end -local function create_subject_message(from, subject) - return st.message({from = from; type = "groupchat"}) - :tag('subject'):text(subject):up(); -end -function room_mt:send_subject(to) - local msg = create_subject_message(self:get_subject()); - msg.attr.to = to; - self:route_stanza(msg); -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 = create_subject_message(current_nick, subject); - self:broadcast_message(msg); - return true; -end - function room_mt:handle_kickable(origin, stanza) local real_jid = stanza.attr.from; local occupant = self:get_occupant_by_real_jid(real_jid); @@ -362,16 +340,6 @@ 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 -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) @@ -400,11 +368,6 @@ module:hook("muc-occupant-joined", function(event) end); end, 80); --- Send subject to joining user -module:hook("muc-occupant-joined", function(event) - event.room:send_subject(event.stanza.attr.from); -end, 20); - function room_mt:handle_presence_to_occupant(origin, stanza) local type = stanza.attr.type; if type == "error" then -- error, kick em out! @@ -666,14 +629,6 @@ module:hook("muc-config-form", function(event) value = not event.room:get_hidden() }); end); -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_changesubject', - type = 'boolean', - label = 'Allow Occupants to Change Subject?', - value = event.room:get_changesubject() - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_moderatedroom', @@ -729,9 +684,6 @@ end); module:hook("muc-config-submitted", function(event) event.update_option("public", "muc#roomconfig_publicroom"); end); -module:hook("muc-config-submitted", function(event) - event.update_option("changesubject", "muc#roomconfig_changesubject"); -end); -- Removes everyone from the room function room_mt:clear(x) @@ -878,19 +830,6 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) end end -module:hook("muc-subject-change", function(event) - local room, stanza = event.room, event.stanza; - local occupant = room:get_occupant_by_real_jid(stanza.attr.from); - if occupant.role == "moderator" or - ( occupant.role == "participant" and room:get_changesubject() ) then -- and participant - local subject = stanza:get_child_text("subject"); - room:set_subject(occupant.nick, subject); - else - event.origin.send(st.error_reply(stanza, "auth", "forbidden")); - end - return true; -end); - function room_mt:handle_groupchat_to_room(origin, stanza) -- Prosody has made the decision that messages with are exclusively subject changes -- e.g. body will be ignored; even if the subject change was not allowed @@ -1204,6 +1143,13 @@ local persistent = module:require "muc/persistent"; room_mt.get_persistent = persistent.get; room_mt.set_persistent = persistent.set; +local subject = module:require "muc/subject"; +room_mt.get_changesubject = subject.get_changesubject; +room_mt.set_changesubject = subject.set_changesubject; +room_mt.get_subject = subject.get; +room_mt.set_subject = subject.set; +room_mt.send_subject = subject.send; + local history = module:require "muc/history"; room_mt.send_history = history.send; room_mt.get_historylength = history.get_length; diff --git a/plugins/muc/subject.lib.lua b/plugins/muc/subject.lib.lua new file mode 100644 index 00000000..a42a18c5 --- /dev/null +++ b/plugins/muc/subject.lib.lua @@ -0,0 +1,93 @@ +-- 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. +-- + +local st = require "util.stanza"; + +local function create_subject_message(from, subject) + return st.message({from = from; type = "groupchat"}) + :tag("subject"):text(subject):up(); +end + +local function get_changesubject(room) + return room._data.changesubject; +end + +local function set_changesubject(room, changesubject) + changesubject = changesubject and true or nil; + if get_changesubject(room) == changesubject then return false; end + room._data.changesubject = changesubject; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_changesubject"; + type = "boolean"; + label = "Allow Occupants to Change Subject?"; + value = get_changesubject(event.room); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_changesubject"]; + if new ~= nil and set_changesubject(event.room, new) then + event.status_codes["104"] = true; + end +end); + +local function get_subject(room) + return room._data.subject_from, room._data.subject; +end + +local function send_subject(room, to) + local msg = create_subject_message(get_subject(room)); + msg.attr.to = to; + room:route_stanza(msg); +end + +local function set_subject(room, from, subject) + if subject == "" then subject = nil; end + local old_from, old_subject = get_subject(room); + if old_subject == subject and old_from == from then return false; end + room._data.subject_from = from; + room._data.subject = subject; + if room.save then room:save(); end + local msg = create_subject_message(from, subject); + room:broadcast_message(msg); + return true; +end + +-- Send subject to joining user +module:hook("muc-occupant-joined", function(event) + event.room:send_subject(event.stanza.attr.from); +end, 20); + +-- Role check for subject changes +module:hook("muc-subject-change", function(event) + local room, stanza = event.room, event.stanza; + local occupant = room:get_occupant_by_real_jid(stanza.attr.from); + if occupant.role == "moderator" or + ( occupant.role == "participant" and get_changesubject(room) ) then -- and participant + local subject = stanza:get_child_text("subject"); + set_subject(room, occupant.nick, subject); + return true; + else + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end +end); + +return { + get_changesubject = get_changesubject; + set_changesubject = set_changesubject; + get = get_subject; + set = set_subject; + send = send_subject; +}; -- cgit v1.2.3 From aa2da859d066615a8382a7179b7c1896e2211998 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 16 Apr 2014 14:16:14 -0400 Subject: plugins/muc: Move 'hidden' ('public') code to own file --- plugins/muc/hidden.lib.lua | 45 +++++++++++++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 40 ++++++++++------------------------------ 2 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 plugins/muc/hidden.lib.lua (limited to 'plugins') diff --git a/plugins/muc/hidden.lib.lua b/plugins/muc/hidden.lib.lua new file mode 100644 index 00000000..5ce43db8 --- /dev/null +++ b/plugins/muc/hidden.lib.lua @@ -0,0 +1,45 @@ +-- 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. +-- + +local function get_hidden(room) + return room._data.hidden; +end + +local function set_hidden(room, hidden) + hidden = hidden and true or nil; + if get_hidden(room) == hidden then return false; end + room._data.hidden = hidden; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_publicroom"; + type = "boolean"; + label = "Make Room Publicly Searchable?"; + value = not get_hidden(event.room); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_publicroom"]; + if new ~= nil and set_hidden(event.room, not new) then + event.status_codes["104"] = true; + end +end); + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_hidden(event.room) and "muc_hidden" or "muc_public"}):up(); +end); + +return { + get = get_hidden; + set = set_hidden; +}; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 26b2d335..fde6e17b 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -280,9 +280,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_hidden() and "muc_hidden" or "muc_public"}):up(); -end); module:hook("muc-disco#info", function(event) local count = iterators.count(event.room:each_occupant()); table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); @@ -324,22 +321,6 @@ end function room_mt:get_moderated() return self._data.moderated; end -function room_mt:set_hidden(hidden) - hidden = hidden and true or nil; - if self._data.hidden ~= hidden then - self._data.hidden = hidden; - if self.save then self:save(true); end - end -end -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 -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) @@ -621,14 +602,6 @@ function room_mt:get_form_layout(actor) }); return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; end -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_publicroom', - type = 'boolean', - label = 'Make Room Publicly Searchable?', - value = not event.room:get_hidden() - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_moderatedroom', @@ -681,9 +654,6 @@ end module:hook("muc-config-submitted", function(event) event.update_option("moderated", "muc#roomconfig_moderatedroom"); end); -module:hook("muc-config-submitted", function(event) - event.update_option("public", "muc#roomconfig_publicroom"); -end); -- Removes everyone from the room function room_mt:clear(x) @@ -1127,6 +1097,16 @@ local description = module:require "muc/description"; room_mt.get_description = description.get; room_mt.set_description = description.set; +local hidden = module:require "muc/hidden"; +room_mt.get_hidden = hidden.get; +room_mt.set_hidden = hidden.set; +function room_mt:get_public() + return not self:get_hidden(); +end +function room_mt:set_public(public) + return self:set_hidden(not public); +end + local password = module:require "muc/password"; room_mt.get_password = password.get; room_mt.set_password = password.set; -- cgit v1.2.3 From 7c55b4a2e2bf8564efa21b5bf6815107d44364bc Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 18 Apr 2014 12:19:04 -0400 Subject: plugins/muc: Move 'moderated' code to seperate file; changes default "muc-get-default-role" behaviour --- plugins/muc/moderated.lib.lua | 53 +++++++++++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 37 ++++-------------------------- 2 files changed, 58 insertions(+), 32 deletions(-) create mode 100644 plugins/muc/moderated.lib.lua (limited to 'plugins') diff --git a/plugins/muc/moderated.lib.lua b/plugins/muc/moderated.lib.lua new file mode 100644 index 00000000..c375b7ad --- /dev/null +++ b/plugins/muc/moderated.lib.lua @@ -0,0 +1,53 @@ +-- 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. +-- + +local function get_moderated(room) + return room._data.moderated; +end + +local function set_moderated(room, moderated) + moderated = moderated and true or nil; + if get_moderated(room) == moderated then return false; end + room._data.moderated = moderated; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_moderated(event.room) and "muc_moderated" or "muc_unmoderated"}):up(); +end); + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_moderatedroom"; + type = "boolean"; + label = "Make Room Moderated?"; + value = get_moderated(event.room); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_moderatedroom"]; + if new ~= nil and set_moderated(event.room, new) then + event.status_codes["104"] = true; + end +end); + +module:hook("muc-get-default-role", function(event) + if event.affiliation == nil then + if get_moderated(event.room) then + return "visitor" + end + end +end, 1); + +return { + get = get_moderated; + set = set_moderated; +}; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index fde6e17b..53f91b83 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -49,15 +49,10 @@ end module:hook("muc-get-default-role", function(event) if event.affiliation_rank >= valid_affiliations.admin then return "moderator"; - elseif event.affiliation_rank >= valid_affiliations.member then + elseif event.affiliation_rank >= valid_affiliations.none then return "participant"; end end); -module:hook("muc-get-default-role", function(event) - if not event.affiliation then - return event.room:get_moderated() and "visitor" or "participant"; - end -end, -1); --- Occupant functions function room_mt:new_occupant(bare_real_jid, nick) @@ -277,9 +272,6 @@ end module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = "http://jabber.org/protocol/muc"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); -end); module:hook("muc-disco#info", function(event) local count = iterators.count(event.room:each_occupant()); table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); @@ -311,23 +303,11 @@ function room_mt:handle_kickable(origin, stanza) return true; end -function room_mt:set_moderated(moderated) - moderated = moderated and true or nil; - if self._data.moderated ~= moderated then - self._data.moderated = moderated; - if self.save then self:save(true); end - end -end -function room_mt:get_moderated() - return self._data.moderated; -end - -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); end, -1); - -- check if user is banned module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; @@ -602,14 +582,6 @@ function room_mt:get_form_layout(actor) }); return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; end -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_moderatedroom', - type = 'boolean', - label = 'Make Room Moderated?', - value = event.room:get_moderated() - }); -end); function room_mt:process_form(origin, stanza) local form = stanza.tags[1]:get_child("x", "jabber:x:data"); @@ -651,9 +623,6 @@ function room_mt:process_form(origin, stanza) end return true; end -module:hook("muc-config-submitted", function(event) - event.update_option("moderated", "muc#roomconfig_moderatedroom"); -end); -- Removes everyone from the room function room_mt:clear(x) @@ -1119,6 +1088,10 @@ local members_only = module:require "muc/members_only"; room_mt.get_members_only = members_only.get; room_mt.set_members_only = members_only.set; +local moderated = module:require "muc/moderated"; +room_mt.get_moderated = moderated.get; +room_mt.set_moderated = moderated.set; + local persistent = module:require "muc/persistent"; room_mt.get_persistent = persistent.get; room_mt.set_persistent = persistent.set; -- cgit v1.2.3 From 544685b84c9618705ba70bb0c1a2ebb5258d67b2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 18 Apr 2014 12:19:33 -0400 Subject: plugins/muc/muc.lib: When user leaves; set their role to nil --- plugins/muc/muc.lib.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 53f91b83..61520354 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -405,6 +405,7 @@ function room_mt:handle_presence_to_occupant(origin, stanza) local dest_nick; if dest_occupant == nil then -- Session is leaving log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); + orig_occupant.role = nil; orig_occupant:set_session(real_jid, stanza); else log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick); -- cgit v1.2.3 From 70a87e5d5e5fe24252d03a5263243c19745ea72d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 18 Apr 2014 12:20:07 -0400 Subject: plugins/muc/subject.lib: If subject is not set by an occupant, it should come from room jid itself --- plugins/muc/subject.lib.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/subject.lib.lua b/plugins/muc/subject.lib.lua index a42a18c5..44fc915c 100644 --- a/plugins/muc/subject.lib.lua +++ b/plugins/muc/subject.lib.lua @@ -43,7 +43,8 @@ module:hook("muc-config-submitted", function(event) end); local function get_subject(room) - return room._data.subject_from, room._data.subject; + -- a stanza from the room JID (or from the occupant JID of the entity that set the subject) + return room._data.subject_from or room.jid, room._data.subject; end local function send_subject(room, to) @@ -66,7 +67,7 @@ end -- Send subject to joining user module:hook("muc-occupant-joined", function(event) - event.room:send_subject(event.stanza.attr.from); + send_subject(event.room, event.stanza.attr.from); end, 20); -- Role check for subject changes -- cgit v1.2.3 From 126eb3e5b9ea0372a264ddff6b4be37bd2029529 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 21 Apr 2014 17:39:18 -0400 Subject: plugins/muc/muc.lib: Remove reversed conditionals when firing pre- events --- plugins/muc/muc.lib.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 61520354..2e48ade1 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -822,7 +822,7 @@ function room_mt:handle_mediated_invite(origin, stanza) if not invitee then origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); return true; - elseif not module:fire_event("muc-pre-invite", {room = self, origin = origin, stanza = stanza}) then + elseif module:fire_event("muc-pre-invite", {room = self, origin = origin, stanza = stanza}) then return true; end local invite = st.message({from = self.jid, to = invitee, id = stanza.attr.id}) @@ -863,7 +863,7 @@ function room_mt:handle_mediated_decline(origin, stanza) if not declinee then origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); return true; - elseif not module:fire_event("muc-pre-decline", {room = self, origin = origin, stanza = stanza}) then + elseif module:fire_event("muc-pre-decline", {room = self, origin = origin, stanza = stanza}) then return true; end local decline = st.message({from = self.jid, to = declinee, id = stanza.attr.id}) -- cgit v1.2.3 From 9592b3a68c66c5d2ea10ea04ce3f47041cf2921d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 21 Apr 2014 17:49:57 -0400 Subject: plugins/muc/members_only.lib: Compare affiliations via rank; wrap some long lines --- plugins/muc/members_only.lib.lua | 48 ++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 17 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/members_only.lib.lua b/plugins/muc/members_only.lib.lua index 84a17699..b0999f0b 100644 --- a/plugins/muc/members_only.lib.lua +++ b/plugins/muc/members_only.lib.lua @@ -51,13 +51,16 @@ end); -- registration required for entering members-only room module:hook("muc-occupant-pre-join", function(event) - local room, stanza = event.room, event.stanza; - local affiliation = room:get_affiliation(stanza.attr.from); - if affiliation == nil and get_members_only(event.room) then - local reply = st.error_reply(stanza, "auth", "registration-required"):up(); - reply.tags[1].attr.code = "407"; - event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; + local room = event.room; + if get_members_only(room) then + local stanza = event.stanza; + local affiliation = room:get_affiliation(stanza.attr.from); + if valid_affiliations[affiliation or "none"] <= valid_affiliations.none then + local reply = st.error_reply(stanza, "auth", "registration-required"):up(); + reply.tags[1].attr.code = "407"; + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end end end, -5); @@ -65,21 +68,32 @@ end, -5); -- if a member without privileges to edit the member list attempts to invite another user -- the service SHOULD return a error to the occupant module:hook("muc-pre-invite", function(event) - local room, stanza = event.room, event.stanza; - if get_members_only(room) and room:get_affiliation(stanza.attr.from) or "none" < valid_affiliations.admin then - event.origin.send(st.error_reply(stanza, "auth", "forbidden")); - return true; + local room = event.room; + if get_members_only(room) then + local stanza = event.stanza; + local affiliation = room:get_affiliation(stanza.attr.from); + if valid_affiliations[affiliation or "none"] < valid_affiliations.admin then + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end end end); -- When an invite is sent; add an affiliation for the invitee module:hook("muc-invite", function(event) - local room, stanza = event.room, event.stanza; - local invitee = stanza.attr.to; - if get_members_only(room) and not room:get_affiliation(invitee) then - local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from; - module:log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); - room:set_affiliation(from, invitee, "member", "Invited by " .. from); -- This might fail; ignore for now + local room = event.room; + if get_members_only(room) then + local stanza = event.stanza; + local invitee = stanza.attr.to; + local affiliation = room:get_affiliation(invitee); + if valid_affiliations[affiliation or "none"] <= valid_affiliations.none then + local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user") + :get_child("invite").attr.from; + module:log("debug", "%s invited %s into members only room %s, granting membership", + from, invitee, room.jid); + -- This might fail; ignore for now + room:set_affiliation(from, invitee, "member", "Invited by " .. from); + end end end); -- cgit v1.2.3 From 09fa17a7efd4502c7fdf21ec2492946bb59239e3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 21 Apr 2014 17:51:32 -0400 Subject: plugins/muc/muc.lib: Move sending of occupant list to joining user out of hook, and into main flow: It has to occur before publication of their status --- plugins/muc/history.lib.lua | 2 +- plugins/muc/muc.lib.lua | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/history.lib.lua b/plugins/muc/history.lib.lua index 417d62a8..596f92da 100644 --- a/plugins/muc/history.lib.lua +++ b/plugins/muc/history.lib.lua @@ -131,7 +131,7 @@ end -- Send history on join module:hook("muc-occupant-joined", function(event) send_history(event.room, event.stanza); -end, 50); -- Between occupant list (80) and subject(20) +end, 50); -- Before subject(20) -- add to history module:hook("muc-broadcast-message", function(event) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2e48ade1..6aca5c62 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -320,15 +320,6 @@ module:hook("muc-occupant-pre-join", function(event) end end, -10); --- Send occupant list to newly joined user -module:hook("muc-occupant-joined", function(event) - local real_jid = event.stanza.attr.from; - event.room:send_occupant_list(real_jid, function(nick, occupant) - -- Don't include self - return occupant:get_presence(real_jid) == nil; - end); -end, 80); - function room_mt:handle_presence_to_occupant(origin, stanza) local type = stanza.attr.type; if type == "error" then -- error, kick em out! @@ -457,6 +448,14 @@ function room_mt:handle_presence_to_occupant(origin, stanza) dest_x:tag("status", {code = "100"}):up(); end self:save_occupant(dest_occupant); + + if orig_occupant == nil and is_first_dest_session then + -- Send occupant list to newly joined user + self:send_occupant_list(real_jid, function(nick, occupant) + -- Don't include self + return occupant:get_presence(real_jid) == nil; + end) + end self:publicise_occupant_status(dest_occupant, dest_x); if orig_occupant ~= nil and orig_occupant ~= dest_occupant and not is_last_orig_session then -- If user is swapping and wasn't last original session -- cgit v1.2.3 From 002467dc48dba878613d7aa9872217de0812a149 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 28 Apr 2014 16:30:53 -0400 Subject: plugins/muc/mod_muc: Move affiliation monkey patch into own scope --- plugins/muc/mod_muc.lua | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 865e07b9..2aa55a37 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -26,6 +26,7 @@ end local muclib = module:require "muc"; local muc_new_room = muclib.new_room; +room_mt = muclib.room_mt; -- Yes, global. local persistent = module:require "muc/persistent"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; @@ -52,16 +53,18 @@ local function is_admin(jid) return um_is_admin(jid, module.host); end -room_mt = muclib.room_mt; -- Yes, global. -local _set_affiliation = room_mt.set_affiliation; -local _get_affiliation = room_mt.get_affiliation; -function muclib.room_mt:get_affiliation(jid) - if is_admin(jid) then return "owner"; end - return _get_affiliation(self, jid); -end -function muclib.room_mt:set_affiliation(actor, jid, affiliation, callback, reason) - if is_admin(jid) then return nil, "modify", "not-acceptable"; end - return _set_affiliation(self, actor, jid, affiliation, callback, reason); +do -- Monkey patch to make server admins room owners + local _get_affiliation = room_mt.get_affiliation; + function room_mt:get_affiliation(jid) + if is_admin(jid) then return "owner"; end + return _get_affiliation(self, jid); + end + + local _set_affiliation = room_mt.set_affiliation; + function room_mt:set_affiliation(actor, jid, ...) + if is_admin(jid) then return nil, "modify", "not-acceptable"; end + return _set_affiliation(self, actor, jid, ...); + end end local function room_save(room, forced) -- cgit v1.2.3 From c2b74c312945d2afbaaf75a1ba6e24dc4bb0fe02 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 28 Apr 2014 16:33:54 -0400 Subject: plugins/muc/mod_muc: Use get_option_string instead of get_option and checking --- plugins/muc/mod_muc.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 2aa55a37..8a7ae9c7 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -13,8 +13,6 @@ if module:get_host_type() ~= "component" then end local muc_host = module:get_host(); -local muc_name = module:get_option("name"); -if type(muc_name) ~= "string" then muc_name = "Prosody Chatrooms"; end local restrict_room_creation = module:get_option("restrict_room_creation"); if restrict_room_creation then if restrict_room_creation == true then @@ -44,7 +42,7 @@ local room_configs = module:open_store("config"); muclib.set_max_history_length(module:get_option_number("max_history_messages")); module:depends("disco"); -module:add_identity("conference", "text", muc_name); +module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms")); module:add_feature("http://jabber.org/protocol/muc"); module:depends "muc_unique" module:require "muc/lock"; -- cgit v1.2.3 From c34f4b0ae3b32f17ad4ccffbf6356d593e4128d2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 28 Apr 2014 16:31:21 -0400 Subject: plugins/muc/mod_muc: No need to treat the host room specially --- plugins/muc/mod_muc.lua | 6 ------ 1 file changed, 6 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 8a7ae9c7..ecc136f1 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -12,7 +12,6 @@ if module:get_host_type() ~= "component" then error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0); end -local muc_host = module:get_host(); local restrict_room_creation = module:get_option("restrict_room_creation"); if restrict_room_creation then if restrict_room_creation == true then @@ -120,10 +119,6 @@ for jid in pairs(persistent_rooms) do end if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end -local host_room = muc_new_room(muc_host); -host_room.save = room_save; -rooms[muc_host] = host_room; - module:hook("host-disco-items", function(event) local reply = event.reply; module:log("debug", "host-disco-items called"); @@ -227,7 +222,6 @@ function shutdown_component() for roomjid, room in pairs(rooms) do room:clear(x); end - host_room:clear(x); end end module.unload = shutdown_component; -- cgit v1.2.3 From 51e7021e0ce200dc4dd2b8e2d5b91b4b9625c945 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 12:54:04 -0400 Subject: plugins/muc: Add room:has_occupant() method --- plugins/muc/mod_muc.lua | 2 +- plugins/muc/muc.lib.lua | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index ecc136f1..f0458e16 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -136,7 +136,7 @@ end) module:hook("muc-occupant-left",function(event) local room = event.room - if not next(room._occupants) and not persistent.get(room) then -- empty, non-persistent room + if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room module:fire_event("muc-room-destroyed", { room = room }); end end); diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6aca5c62..7c039ce3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -79,6 +79,10 @@ do end end +function room_mt:has_occupant() + return next(self._occupants, nil) ~= nil +end + function room_mt:get_occupant_by_real_jid(real_jid) local occupant_jid = self:get_occupant_jid(real_jid); if occupant_jid == nil then return nil end -- cgit v1.2.3 From 0c0b9b11b7fb62219cf1e6d56bcb97af14d99430 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 14:24:50 -0400 Subject: plugins/muc/mod_muc: Move room persistence to own block --- plugins/muc/mod_muc.lua | 104 ++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 48 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index f0458e16..3262905e 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -24,7 +24,6 @@ end local muclib = module:require "muc"; local muc_new_room = muclib.new_room; room_mt = muclib.room_mt; -- Yes, global. -local persistent = module:require "muc/persistent"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; @@ -33,9 +32,6 @@ local hosts = prosody.hosts; rooms = {}; local rooms = rooms; -local persistent_rooms_storage = module:open_store("persistent"); -local persistent_rooms = persistent_rooms_storage:get() or {}; -local room_configs = module:open_store("config"); -- Configurable options muclib.set_max_history_length(module:get_option_number("max_history_messages")); @@ -64,32 +60,8 @@ do -- Monkey patch to make server admins room owners end end -local function room_save(room, forced) - local node = jid_split(room.jid); - local is_persistent = persistent.get(room); - persistent_rooms[room.jid] = is_persistent; - if is_persistent then - local history = room._data.history; - room._data.history = nil; - local data = { - jid = room.jid; - _data = room._data; - _affiliations = room._affiliations; - }; - room_configs:set(node, data); - room._data.history = history; - elseif forced then - room_configs:set(node, nil); - if not next(room._occupants) then -- Room empty - rooms[room.jid] = nil; - end - end - if forced then persistent_rooms_storage:set(nil, persistent_rooms); end -end - function create_room(jid) local room = muc_new_room(jid); - room.save = room_save; rooms[jid] = room; module:fire_event("muc-room-created", { room = room }); return room; @@ -103,21 +75,64 @@ 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); - local data = room_configs:get(node); - if data then - local room = create_room(jid); - room._data = data._data; - room._affiliations = data._affiliations; - else -- missing room data - persistent_rooms[jid] = nil; - module:log("error", "Missing data for room '%s', removing from persistent room list", jid); - persistent_errors = true; +do -- Persistent rooms + local persistent = module:require "muc/persistent"; + local persistent_rooms_storage = module:open_store("persistent"); + local persistent_rooms = persistent_rooms_storage:get() or {}; + local room_configs = module:open_store("config"); + + local function room_save(room, forced) + local node = jid_split(room.jid); + local is_persistent = persistent.get(room); + persistent_rooms[room.jid] = is_persistent; + if is_persistent then + local history = room._data.history; + room._data.history = nil; + local data = { + jid = room.jid; + _data = room._data; + _affiliations = room._affiliations; + }; + room_configs:set(node, data); + room._data.history = history; + elseif forced then + room_configs:set(node, nil); + if not next(room._occupants) then -- Room empty + rooms[room.jid] = nil; + end + end + if forced then persistent_rooms_storage:set(nil, persistent_rooms); end end + + -- When room is created, over-ride 'save' method + module:hook("muc-room-created", function(event) + event.room.save = room_save; + end); + + -- Automatically destroy empty non-persistent rooms + module:hook("muc-occupant-left",function(event) + local room = event.room + if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room + module:fire_event("muc-room-destroyed", { room = room }); + end + end); + + local persistent_errors = false; + for jid in pairs(persistent_rooms) do + local node = jid_split(jid); + local data = room_configs:get(node); + if data then + local room = create_room(jid); + room._data = data._data; + room._affiliations = data._affiliations; + else -- missing room data + persistent_rooms[jid] = nil; + module:log("error", "Missing data for room '%s', removing from persistent room list", jid); + persistent_errors = true; + end + end + if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end end -if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end module:hook("host-disco-items", function(event) local reply = event.reply; @@ -134,13 +149,6 @@ module:hook("muc-room-destroyed",function(event) forget_room(room.jid) end) -module:hook("muc-occupant-left",function(event) - local room = event.room - if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room - module:fire_event("muc-room-destroyed", { room = room }); - end -end); - -- Watch presence to create rooms local function attempt_room_creation(event) local origin, stanza = event.origin, event.stanza; -- cgit v1.2.3 From 0de9e9485c7f6018acf7b4413126ffb0f86f5da2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 14:50:13 -0400 Subject: plugins/muc/mod_muc: Remove support for `host_session.muc` --- plugins/muc/mod_muc.lua | 3 --- 1 file changed, 3 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 3262905e..d7d3ac37 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -205,8 +205,6 @@ for event_name, method in pairs { end, -2) end -hosts[module:get_host()].muc = { rooms = rooms }; - local saved = false; module.save = function() saved = true; @@ -220,7 +218,6 @@ module.restore = function(data) room._data = oldroom._data; room._affiliations = oldroom._affiliations; end - hosts[module:get_host()].muc = { rooms = rooms }; end function shutdown_component() -- cgit v1.2.3 From 940094d4fa9737ad53cfaa3f667a05bae520f98c Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 15:13:06 -0400 Subject: plugins/muc: Move 'module:get_option_number("max_history_messages")' from mod_muc into history lib; remove from muclib exports --- plugins/muc/history.lib.lua | 3 ++- plugins/muc/mod_muc.lua | 2 -- plugins/muc/muc.lib.lua | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/history.lib.lua b/plugins/muc/history.lib.lua index 596f92da..1c14cf84 100644 --- a/plugins/muc/history.lib.lua +++ b/plugins/muc/history.lib.lua @@ -11,7 +11,8 @@ local gettime = os.time; local datetime = require "util.datetime"; local st = require "util.stanza"; -local default_history_length, max_history_length = 20, math.huge; +local default_history_length = 20; +local max_history_length = module:get_option_number("max_history_messages", math.huge); local function set_max_history_length(_max_history_length) max_history_length = _max_history_length or math.huge; diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index d7d3ac37..91b69829 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -33,8 +33,6 @@ local hosts = prosody.hosts; rooms = {}; local rooms = rooms; --- Configurable options -muclib.set_max_history_length(module:get_option_number("max_history_messages")); module:depends("disco"); module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms")); diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7c039ce3..27d738de 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1114,8 +1114,6 @@ room_mt.set_historylength = history.set_length; local _M = {}; -- module "muc" -_M.set_max_history_length = history.set_max_length; - function _M.new_room(jid, config) return setmetatable({ jid = jid; -- cgit v1.2.3 From bbd26576162f33990375f01db262a90d4121f091 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 17:18:14 -0400 Subject: plugins/muc/mod_muc: Use module:shared instead of save/restore --- plugins/muc/mod_muc.lua | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 91b69829..751491c2 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -30,9 +30,8 @@ local st = require "util.stanza"; local um_is_admin = require "core.usermanager".is_admin; local hosts = prosody.hosts; -rooms = {}; -local rooms = rooms; - +local rooms = module:shared "rooms"; +_G.rooms = rooms; module:depends("disco"); module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms")); @@ -203,31 +202,13 @@ for event_name, method in pairs { end, -2) end -local saved = false; -module.save = function() - saved = true; - return {rooms = rooms}; -end -module.restore = function(data) - for jid, oldroom in pairs(data.rooms or {}) do - local room = create_room(jid); - room._jid_nick = oldroom._jid_nick; - room._occupants = oldroom._occupants; - room._data = oldroom._data; - room._affiliations = oldroom._affiliations; - end -end - function shutdown_component() - if not saved then - local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("status", { code = "332"}):up(); - for roomjid, room in pairs(rooms) do - room:clear(x); - end + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("status", { code = "332"}):up(); + for roomjid, room in pairs(rooms) do + room:clear(x); end end -module.unload = shutdown_component; module:hook_global("server-stopping", shutdown_component); -- Ad-hoc commands -- cgit v1.2.3 From 202f300c96a891adbd5b1cceaf68bff73161faff Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 18:50:30 -0400 Subject: plugins/muc/lock.lib: lock inside of pre-create instead of 'created' --- plugins/muc/lock.lib.lua | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/lock.lib.lua b/plugins/muc/lock.lib.lua index 7cf19be3..319a6973 100644 --- a/plugins/muc/lock.lib.lua +++ b/plugins/muc/lock.lib.lua @@ -23,7 +23,10 @@ local function is_locked(room) end if lock_rooms then - module:hook("muc-room-created", function(event) + module:hook("muc-room-pre-create", function(event) + -- Older groupchat protocol doesn't lock + if not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then return end + -- Lock room at creation local room = event.room; lock(room); if lock_room_timeout and lock_room_timeout > 0 then @@ -33,16 +36,9 @@ if lock_rooms then end end); end - end); + end, 10); end --- Older groupchat protocol doesn't lock -module:hook("muc-room-pre-create", function(event) - if is_locked(event.room) and not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then - unlock(event.room); - end -end, 10); - -- Don't let users into room while it is locked module:hook("muc-occupant-pre-join", function(event) if not event.is_new_room and is_locked(event.room) then -- Deny entry -- cgit v1.2.3 From c73eccaaac9d389cf63c68d57c66aa8b51c97e63 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 19:00:45 -0400 Subject: plugins/muc/mod_muc: Move `restrict_room_creation` into own area. now uses pre-create hook --- plugins/muc/mod_muc.lua | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 751491c2..0940bc43 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -12,15 +12,6 @@ if module:get_host_type() ~= "component" then error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0); end -local restrict_room_creation = module:get_option("restrict_room_creation"); -if restrict_room_creation then - if restrict_room_creation == true then - restrict_room_creation = "admin"; - elseif restrict_room_creation ~= "admin" and restrict_room_creation ~= "local" then - restrict_room_creation = nil; - end -end - local muclib = module:require "muc"; local muc_new_room = muclib.new_room; room_mt = muclib.room_mt; -- Yes, global. @@ -146,20 +137,31 @@ module:hook("muc-room-destroyed",function(event) forget_room(room.jid) end) +do + local restrict_room_creation = module:get_option("restrict_room_creation"); + if restrict_room_creation == true then + restrict_room_creation = "admin"; + end + if restrict_room_creation then + local host_suffix = module.host:gsub("^[^%.]+%.", ""); + module:hook("muc-room-pre-create", function(event) + local user_jid = event.stanza.attr.from; + if not is_admin(user_jid) and not ( + restrict_room_creation == "local" and + select(2, jid_split(user_jid)) == host_suffix + ) then + origin.send(st.error_reply(stanza, "cancel", "not-allowed")); + return true; + end + end); + end +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 + if stanza.attr.type == nil and get_room_from_jid(room_jid) == nil then create_room(room_jid); end end -- cgit v1.2.3 From 63e9b9a510218fe91990a3fcaf1e62b74415c409 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 19:35:25 -0400 Subject: plugins/muc/mod_muc: Remove attempt_room_creation and create_room function. Instead have a 'track_room' function called from the end of the pre-create hook, and just create an un-tracked room object when we get a presence --- plugins/muc/mod_muc.lua | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 0940bc43..519b1e34 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -13,7 +13,6 @@ if module:get_host_type() ~= "component" then end local muclib = module:require "muc"; -local muc_new_room = muclib.new_room; room_mt = muclib.room_mt; -- Yes, global. local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; @@ -48,11 +47,8 @@ do -- Monkey patch to make server admins room owners end end -function create_room(jid) - local room = muc_new_room(jid); - rooms[jid] = room; - module:fire_event("muc-room-created", { room = room }); - return room; +function track_room(room) + rooms[room.jid] = room; end function forget_room(jid) @@ -93,9 +89,9 @@ do -- Persistent rooms end -- When room is created, over-ride 'save' method - module:hook("muc-room-created", function(event) + module:hook("muc-occupant-pre-create", function(event) event.room.save = room_save; - end); + end, 1000); -- Automatically destroy empty non-persistent rooms module:hook("muc-occupant-left",function(event) @@ -110,9 +106,10 @@ do -- Persistent rooms local node = jid_split(jid); local data = room_configs:get(node); if data then - local room = create_room(jid); + local room = muclib.new_room(jid); room._data = data._data; room._affiliations = data._affiliations; + track_room(room); else -- missing room data persistent_rooms[jid] = nil; module:log("error", "Missing data for room '%s', removing from persistent room list", jid); @@ -132,6 +129,10 @@ module:hook("host-disco-items", function(event) end end); +module:hook("muc-room-pre-create", function(event) + track_room(event.room); +end, -1000); + module:hook("muc-room-destroyed",function(event) local room = event.room forget_room(room.jid) @@ -157,18 +158,6 @@ do end 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 then - create_room(room_jid); - end -end -module:hook("presence/full", attempt_room_creation, -1) -module:hook("presence/bare", attempt_room_creation, -1) -module:hook("presence/host", attempt_room_creation, -1) - for event_name, method in pairs { -- Normal room interactions ["iq-get/bare/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; @@ -195,10 +184,16 @@ for event_name, method in pairs { } 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)) + local room_jid = jid_bare(stanza.attr.to); + local room = get_room_from_jid(room_jid); if room == nil then - origin.send(st.error_reply(stanza, "cancel", "not-allowed")); - return true; + -- Watch presence to create rooms + if stanza.attr.type == nil and stanza.name == "presence" then + room = muclib.new_room(room_jid); + else + origin.send(st.error_reply(stanza, "cancel", "not-allowed")); + return true; + end end return room[method](room, origin, stanza); end, -2) -- cgit v1.2.3 From 2c6ed6291abaae98e7afcf8f27ef16f013215c48 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 30 Apr 2014 13:12:32 -0400 Subject: plugins/muc/mod_muc.lua: Add "each_room" function to iterate over rooms (instead of accessing directly) --- plugins/muc/mod_muc.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 519b1e34..5ad86025 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -14,6 +14,7 @@ end local muclib = module:require "muc"; room_mt = muclib.room_mt; -- Yes, global. +local iterators = require "util.iterators"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; @@ -59,6 +60,10 @@ function get_room_from_jid(room_jid) return rooms[room_jid] end +function each_room() + return iterators.values(rooms); +end + do -- Persistent rooms local persistent = module:require "muc/persistent"; local persistent_rooms_storage = module:open_store("persistent"); @@ -122,9 +127,9 @@ end module:hook("host-disco-items", function(event) local reply = event.reply; module:log("debug", "host-disco-items called"); - for jid, room in pairs(rooms) do + for room in each_room() do if not room:get_hidden() then - reply:tag("item", {jid=jid, name=room:get_name()}):up(); + reply:tag("item", {jid=room.jid, name=room:get_name()}):up(); end end end); @@ -202,7 +207,7 @@ end function shutdown_component() local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("status", { code = "332"}):up(); - for roomjid, room in pairs(rooms) do + for room in each_room() do room:clear(x); end end @@ -211,7 +216,6 @@ module:hook_global("server-stopping", shutdown_component); -- Ad-hoc commands module:depends("adhoc") local t_concat = table.concat; -local keys = require "util.iterators".keys; local adhoc_new = module:require "adhoc".new; local adhoc_initial = require "util.adhoc".new_initial_data_form; local dataforms_new = require "util.dataforms".new; @@ -225,7 +229,7 @@ local destroy_rooms_layout = dataforms_new { }; local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function() - return { rooms = array.collect(keys(rooms)):sort() }; + return { rooms = array.collect(each_room):pluck("jid"):sort(); }; end, function(fields, errors) if errors then local errmsg = {}; -- cgit v1.2.3 From e6e40efd2fedc95dd8882fd7e4405a5fb01bdfc3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 30 Apr 2014 12:43:04 -0400 Subject: plugins/muc/mod_muc: Don't use rooms object directory in adhoc section --- plugins/muc/mod_muc.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 5ad86025..9f022b3a 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -239,8 +239,7 @@ end, function(fields, errors) return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; end for _, room in ipairs(fields.rooms) do - rooms[room]:destroy(); - rooms[room] = nil; + get_room_from_jid(room):destroy(); end return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") }; end); -- cgit v1.2.3 From ab23352301da2beecdd00a8ab1d3d5dc189cfd24 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 30 Apr 2014 12:43:47 -0400 Subject: plugins/muc/mod_muc: Place adhoc section into own scope --- plugins/muc/mod_muc.lua | 66 ++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 9f022b3a..79123ab5 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -6,8 +6,6 @@ -- COPYING file in the source package for more information. -- -local array = require "util.array"; - if module:get_host_type() ~= "component" then error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0); end @@ -213,36 +211,38 @@ function shutdown_component() end module:hook_global("server-stopping", shutdown_component); --- Ad-hoc commands -module:depends("adhoc") -local t_concat = table.concat; -local adhoc_new = module:require "adhoc".new; -local adhoc_initial = require "util.adhoc".new_initial_data_form; -local dataforms_new = require "util.dataforms".new; - -local destroy_rooms_layout = dataforms_new { - title = "Destroy rooms"; - instructions = "Select the rooms to destroy"; - - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#destroy" }; - { name = "rooms", type = "list-multi", required = true, label = "Rooms to destroy:"}; -}; - -local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function() - return { rooms = array.collect(each_room):pluck("jid"):sort(); }; -end, function(fields, errors) - if errors then - local errmsg = {}; - for name, err in pairs(errors) do - errmsg[#errmsg + 1] = name .. ": " .. err; +do -- Ad-hoc commands + module:depends "adhoc"; + local t_concat = table.concat; + local adhoc_new = module:require "adhoc".new; + local adhoc_initial = require "util.adhoc".new_initial_data_form; + local array = require "util.array"; + local dataforms_new = require "util.dataforms".new; + + local destroy_rooms_layout = dataforms_new { + title = "Destroy rooms"; + instructions = "Select the rooms to destroy"; + + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#destroy" }; + { name = "rooms", type = "list-multi", required = true, label = "Rooms to destroy:"}; + }; + + local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function() + return { rooms = array.collect(each_room):pluck("jid"):sort(); }; + end, function(fields, errors) + if errors then + local errmsg = {}; + for name, err in pairs(errors) do + errmsg[#errmsg + 1] = name .. ": " .. err; + end + return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; end - return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; - end - for _, room in ipairs(fields.rooms) do - get_room_from_jid(room):destroy(); - end - return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") }; -end); -local destroy_rooms_desc = adhoc_new("Destroy Rooms", "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin"); + for _, room in ipairs(fields.rooms) do + get_room_from_jid(room):destroy(); + end + return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") }; + end); + local destroy_rooms_desc = adhoc_new("Destroy Rooms", "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin"); -module:provides("adhoc", destroy_rooms_desc); + module:provides("adhoc", destroy_rooms_desc); +end -- cgit v1.2.3 From c83e3a24ae55f509d17571a90f7397b1274808ff Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 10 May 2014 21:30:00 +0100 Subject: mod_muc/muc.lib: Fix parameters to send_history() (thanks Peter Villeneuve) --- plugins/muc/muc.lib.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5debb4a3..f1e42d36 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -238,11 +238,11 @@ module:hook("muc-get-history", function(event) return true; end) -function room_mt:send_history(stanza) +function room_mt:send_history(to, stanza) local maxchars, maxstanzas, since = parse_history(stanza) local event = { room = self; - to = stanza.attr.from; -- `to` is required to calculate the character count for `maxchars` + to = to; -- `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 } -- cgit v1.2.3 From 2ca609d4fd0bd1a4414de184b657425df86bafc3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 19 May 2014 13:39:45 -0400 Subject: plugins/muc/mod_muc: Get rid of room global; use module:shared to get it --- plugins/muc/mod_muc.lua | 1 - 1 file changed, 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 79123ab5..628c72b3 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -20,7 +20,6 @@ local um_is_admin = require "core.usermanager".is_admin; local hosts = prosody.hosts; local rooms = module:shared "rooms"; -_G.rooms = rooms; module:depends("disco"); module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms")); -- cgit v1.2.3 From 4cc91481bf8eaa34ba1d5350dcd1119e7b45f8a7 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 19 May 2014 13:40:24 -0400 Subject: plugins/muc/muc: Always send occupant list to joining sessions --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 27d738de..df823180 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -453,7 +453,7 @@ function room_mt:handle_presence_to_occupant(origin, stanza) end self:save_occupant(dest_occupant); - if orig_occupant == nil and is_first_dest_session then + if orig_occupant == nil then -- Send occupant list to newly joined user self:send_occupant_list(real_jid, function(nick, occupant) -- Don't include self -- cgit v1.2.3 From 0d12eda7d73df356c24e9db0e7d3899962a1ce87 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 19 May 2014 13:40:54 -0400 Subject: plugins/muc/muc: Better check for live sessions --- plugins/muc/muc.lib.lua | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index df823180..5838cbe9 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -100,11 +100,21 @@ function room_mt:save_occupant(occupant) self._jid_nick[real_jid] = nil; end end - if occupant.role ~= nil and next(occupant.sessions) then + + local has_live_session = false + if occupant.role ~= nil then for real_jid, presence in occupant:each_session() do - self._jid_nick[real_jid] = occupant.nick; + if presence.attr.type == nil then + has_live_session = true + self._jid_nick[real_jid] = occupant.nick; + end end - else + if not has_live_session then + -- Has no live sessions left; they have left the room. + occupant.role = nil + end + end + if not has_live_session then occupant = nil end self._occupants[id] = occupant -- cgit v1.2.3 From ae461b42728b11d7e241ca26c6cded24b6bb0d6f Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 19 May 2014 13:47:28 -0400 Subject: plugins/muc/occupant: Check for type == nil rather than type ~= unavailable --- plugins/muc/occupant.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/occupant.lib.lua b/plugins/muc/occupant.lib.lua index 5cecb139..6a3f7df4 100644 --- a/plugins/muc/occupant.lib.lua +++ b/plugins/muc/occupant.lib.lua @@ -53,7 +53,7 @@ end -- finds another session to be the primary (there might not be one) function occupant_mt:choose_new_primary() for jid, pr in self:each_session() do - if pr.attr.type ~= "unavailable" then + if pr.attr.type == nil then return jid; end end -- cgit v1.2.3 From 975283ba9cb3fe63488a31e30a23f4d65bc0910b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 21 May 2014 13:11:00 -0400 Subject: plugins/muc/muc: Only set role to nil if it's the last session to leave --- plugins/muc/muc.lib.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5838cbe9..34934213 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -410,7 +410,9 @@ function room_mt:handle_presence_to_occupant(origin, stanza) local dest_nick; if dest_occupant == nil then -- Session is leaving log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); - orig_occupant.role = nil; + if is_last_orig_session then + orig_occupant.role = nil; + end orig_occupant:set_session(real_jid, stanza); else log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick); -- cgit v1.2.3 From 9e8f0a984c05647db2346160d1cf62139560fefc Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 21 May 2014 13:19:57 -0400 Subject: plugins/muc/muc.lib: Even unavailable session need to be routed to sometimes (e.g. their own leave) --- plugins/muc/muc.lib.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 34934213..0b361763 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -123,10 +123,8 @@ end function room_mt:route_to_occupant(occupant, stanza) local to = stanza.attr.to; for jid, pr in occupant:each_session() do - if pr.attr.type ~= "unavailable" then - stanza.attr.to = jid; - self:route_stanza(stanza); - end + stanza.attr.to = jid; + self:route_stanza(stanza); end stanza.attr.to = to; end -- cgit v1.2.3 From 12b7a1141a7a752f71f9bfbae4767fd25f6dd092 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 28 May 2014 21:52:57 +0200 Subject: mod_pep_plus: Expose get_pep_service() --- plugins/mod_pep_plus.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/mod_pep_plus.lua b/plugins/mod_pep_plus.lua index 4a74e437..ee57e647 100644 --- a/plugins/mod_pep_plus.lua +++ b/plugins/mod_pep_plus.lua @@ -52,7 +52,7 @@ local function get_broadcaster(name) return simple_broadcast; end -local function get_pep_service(name) +function get_pep_service(name) if services[name] then return services[name]; end -- cgit v1.2.3 From 0c26e52848bb6c5509f8507b4e6933c4f3051125 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 29 May 2014 13:27:41 -0400 Subject: plugins/muc/muc.lib: Fire event for presence broadcast --- plugins/muc/muc.lib.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 0b361763..1fc922d4 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -207,6 +207,9 @@ function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason return get_base_presence(occupant):add_child(x), x; end local full_p, full_x = get_presence(false); + + module:fire_event("muc-broadcast-presence", {room = self; stanza = full_p; x = full_x;}); + local anon_p, anon_x; local function get_anon_p() if anon_p == nil then -- cgit v1.2.3 From 13bb4e436c4327c969f49984eac471a4c82de9df Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Sun, 1 Jun 2014 04:42:55 -0400 Subject: plugins/muc/muc.lib: Fix whois check when broadcasting occupant presence --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1fc922d4..db7c3592 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -224,7 +224,7 @@ function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason for nick, n_occupant in self:each_occupant() do if nick ~= occupant.nick then local pr; - if can_see_real_jids(whois, occupant) or occupant.bare_jid == n_occupant.bare_jid then + if can_see_real_jids(whois, n_occupant) or occupant.bare_jid == n_occupant.bare_jid then pr = full_p; else pr = get_anon_p(); -- cgit v1.2.3 From bb07b5a54fb3bd2979ab84d494b702b35c8a445a Mon Sep 17 00:00:00 2001 From: daurnimator Date: Sun, 1 Jun 2014 15:40:04 -0400 Subject: plugins/muc/muc.lib: Fix incorrect whois logic (thanks mva) --- plugins/muc/muc.lib.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index db7c3592..5c639779 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -252,14 +252,14 @@ end function room_mt:send_occupant_list(to, filter) local to_bare = jid_bare(to); - local is_anonymous = true; + local is_anonymous = false; local whois = self:get_whois(); if whois ~= "anyone" then local affiliation = self:get_affiliation(to); if affiliation ~= "admin" and affiliation ~= "owner" then local occupant = self:get_occupant_by_real_jid(to); - if not occupant or can_see_real_jids(whois, occupant) then - is_anonymous = false; + if not (occupant and can_see_real_jids(whois, occupant)) then + is_anonymous = true; end end end -- cgit v1.2.3 From cc0208bd0602f2472db06cd8d54e988c782f2891 Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Thu, 5 Jun 2014 17:15:04 -0400 Subject: mod_admin_telnet: Update muc:room(jid) and muc:list(host) to use the new MUC API --- plugins/mod_admin_telnet.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 66560d44..0b96b68d 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -957,7 +957,7 @@ function def_env.muc:room(room_jid) if not room_name then return room_name, host; end - local room_obj = hosts[host].modules.muc.rooms[room_jid]; + local room_obj = hosts[host].modules.muc.get_room_from_jid(room_jid); if not room_obj then return nil, "No such room: "..room_jid; end @@ -970,8 +970,8 @@ function def_env.muc:list(host) return nil, "Please supply the address of a local MUC component"; end local c = 0; - for name in keys(host_session.modules.muc.rooms) do - print(name); + for room in host_session.modules.muc.each_room() do + print(room.jid); c = c + 1; end return true, c.." rooms"; -- cgit v1.2.3 From 38f33840a2670fffdb019284b49c5fd23c75a7a4 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 5 Jun 2014 17:07:14 -0400 Subject: plugins/muc: Move 'x' filtering from occupant to util --- plugins/muc/occupant.lib.lua | 17 +++-------------- plugins/muc/util.lib.lua | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 14 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/occupant.lib.lua b/plugins/muc/occupant.lib.lua index 6a3f7df4..d59252e2 100644 --- a/plugins/muc/occupant.lib.lua +++ b/plugins/muc/occupant.lib.lua @@ -2,21 +2,10 @@ local next = next; local pairs = pairs; local setmetatable = setmetatable; local st = require "util.stanza"; +local util = module:require "muc/util"; -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 +local function get_filtered_presence(stanza) + return util.filter_muc_x(st.clone(stanza)); end local occupant_mt = {}; diff --git a/plugins/muc/util.lib.lua b/plugins/muc/util.lib.lua index 90a3a183..16deb543 100644 --- a/plugins/muc/util.lib.lua +++ b/plugins/muc/util.lib.lua @@ -41,4 +41,18 @@ function _M.is_kickable_error(stanza) return kickable_error_conditions[cond]; end +local muc_x_filters = { + ["http://jabber.org/protocol/muc"] = true; + ["http://jabber.org/protocol/muc#user"] = true; +} +local function muc_x_filter(tag) + if muc_x_filters[tag.attr.xmlns] then + return nil; + end + return tag; +end +function _M.filter_muc_x(stanza) + return stanza:maptags(muc_x_filter); +end + return _M; -- cgit v1.2.3 From 557a75123d1298996951212d5dd3307d84e3334e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 5 Jun 2014 17:08:05 -0400 Subject: plugins/muc/muc: When forwarding mediated invites; use filtered version of original invite instead of new object --- plugins/muc/muc.lib.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5c639779..af7202da 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -841,8 +841,10 @@ function room_mt:handle_mediated_invite(origin, stanza) elseif module:fire_event("muc-pre-invite", {room = self, origin = origin, stanza = stanza}) then return true; end - local invite = st.message({from = self.jid, to = invitee, id = stanza.attr.id}) - :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + local invite = muc_util.filter_muc_x(st.clone(stanza)); + invite.attr.from = self.jid; + invite.attr.to = invitee; + invite:tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) :tag('invite', {from = stanza.attr.from;}) :tag('reason'):text(payload:get_child_text("reason")):up() :up() -- cgit v1.2.3 From 9318230b51773a234e4e6018275f984938e3ead3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 6 Jun 2014 14:33:43 -0400 Subject: plugins/muc/muc.lib: Use original decline as template for medated decline --- plugins/muc/muc.lib.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index af7202da..a7b6ef9c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -884,8 +884,10 @@ function room_mt:handle_mediated_decline(origin, stanza) elseif module:fire_event("muc-pre-decline", {room = self, origin = origin, stanza = stanza}) then return true; end - local decline = st.message({from = self.jid, to = declinee, id = stanza.attr.id}) - :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + local decline = muc_util.filter_muc_x(st.clone(stanza)); + decline.attr.from = self.jid; + decline.attr.to = declinee; + decline:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("decline", {from = stanza.attr.from}) :tag("reason"):text(payload:get_child_text("reason")):up() :up() -- cgit v1.2.3 From 473c08dc107419c8f0df2bb8a8c23a28369a5312 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 6 Jun 2014 14:34:01 -0400 Subject: plugins/muc/muc.lib: Don't add invite/decline bodies if they already have one --- plugins/muc/muc.lib.lua | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index a7b6ef9c..57cc3721 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -868,11 +868,13 @@ end); -- Add a plain message for clients which don't support invites module:hook("muc-invite", function(event) local room, stanza = event.room, event.stanza; - local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); - local reason = invite:get_child_text("reason") or ""; - stanza:tag("body") - :text(invite.attr.from.." invited you to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) - :up(); + if not stanza:get_child("body") then + local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local reason = invite:get_child_text("reason") or ""; + stanza:tag("body") + :text(invite.attr.from.." invited you to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) + :up(); + end end); function room_mt:handle_mediated_decline(origin, stanza) @@ -906,11 +908,13 @@ end -- Add a plain message for clients which don't support declines module:hook("muc-decline", function(event) local room, stanza = event.room, event.stanza; - local decline = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline"); - local reason = decline:get_child_text("reason") or ""; - stanza:tag("body") - :text(decline.attr.from.." declined your invite to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) - :up(); + if not stanza:get_child("body") then + local decline = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline"); + local reason = decline:get_child_text("reason") or ""; + stanza:tag("body") + :text(decline.attr.from.." declined your invite to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) + :up(); + end end); function room_mt:handle_message_to_room(origin, stanza) -- cgit v1.2.3 From be47daf8e124b7f96a7081d9873a277bf1403858 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 11 Jun 2014 14:59:59 -0400 Subject: plugins/muc/muc.lib: Deliver declines to in-room jids correctly --- plugins/muc/muc.lib.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 57cc3721..85c256a2 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -895,7 +895,11 @@ function room_mt:handle_mediated_decline(origin, stanza) :up() :up(); if not module:fire_event("muc-decline", {room = self, stanza = decline, origin = origin, incoming = stanza}) then - local occupant = self:get_occupant_by_real_jid(decline.attr.to); + local declinee = decline.attr.to; -- re-fetch, in case event modified it + local occupant + if jid_bare(declinee) == self.jid then -- declinee jid is already an in-room jid + occupant = self:get_occupant_by_nick(declinee); + end if occupant then self:route_to_occupant(occupant, decline); else -- cgit v1.2.3 From 75d78ae9e40c0d0b972842d30a35387f161823d5 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 11 Jun 2014 17:54:23 -0400 Subject: plugins/muc/mod_muc: fix wrong event for persistence --- plugins/muc/mod_muc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 628c72b3..4cd20245 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -91,7 +91,7 @@ do -- Persistent rooms end -- When room is created, over-ride 'save' method - module:hook("muc-occupant-pre-create", function(event) + module:hook("muc-room-pre-create", function(event) event.room.save = room_save; end, 1000); -- cgit v1.2.3 From 1694d8908a486fdc1fcee99b98cf5a8df65be13b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 17 Jun 2014 15:18:43 -0400 Subject: plugins/muc: Fix use of incorrect event on occupant join --- plugins/muc/history.lib.lua | 2 +- plugins/muc/muc.lib.lua | 7 +++++-- plugins/muc/subject.lib.lua | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/history.lib.lua b/plugins/muc/history.lib.lua index 1c14cf84..4129e7fa 100644 --- a/plugins/muc/history.lib.lua +++ b/plugins/muc/history.lib.lua @@ -130,7 +130,7 @@ local function send_history(room, stanza) end -- Send history on join -module:hook("muc-occupant-joined", function(event) +module:hook("muc-occupant-session-new", function(event) send_history(event.room, event.stanza); end, 50); -- Before subject(20) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 85c256a2..c645869e 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -487,8 +487,11 @@ function room_mt:handle_presence_to_occupant(origin, stanza) self:route_stanza(pr); end - if orig_occupant == nil and is_first_dest_session then - module:fire_event("muc-occupant-joined", {room = self; nick = dest_occupant.nick; stanza = stanza;}); + if orig_occupant == nil then + if is_first_dest_session then + module:fire_event("muc-occupant-joined", {room = self; nick = dest_occupant.nick; stanza = stanza;}); + end + module:fire_event("muc-occupant-session-new", {room = self; nick = dest_occupant.nick; stanza = stanza; jid = real_jid;}); end end elseif type ~= 'result' then -- bad type diff --git a/plugins/muc/subject.lib.lua b/plugins/muc/subject.lib.lua index 44fc915c..34f9a5d4 100644 --- a/plugins/muc/subject.lib.lua +++ b/plugins/muc/subject.lib.lua @@ -66,7 +66,7 @@ local function set_subject(room, from, subject) end -- Send subject to joining user -module:hook("muc-occupant-joined", function(event) +module:hook("muc-occupant-session-new", function(event) send_subject(event.room, event.stanza.attr.from); end, 20); -- cgit v1.2.3 From 3a09673e61b5d394fcade54e8e09dadcc840fdb0 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 17 Jun 2014 15:27:00 -0400 Subject: plugins/muc/muc: Fire broadcast presences event before creating full/anon presences --- plugins/muc/muc.lib.lua | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index c645869e..3ee17b03 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -186,30 +186,32 @@ local function can_see_real_jids(whois, occupant) end end -local function get_base_presence(occupant) +-- Broadcasts an occupant's presence to the whole room +-- Takes the x element that goes into the stanzas +function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason) + -- Build real jid and (optionally) occupant jid template presences + local base_presence; if occupant.role ~= nil then -- Try to use main jid's presence local pr = occupant:get_presence(); if pr ~= nil then - return st.clone(pr); + base_presence = st.clone(pr); end end - return st.presence {from = occupant.nick; type = "unavailable";}; -end + base_presence = base_presence or st.presence {from = occupant.nick; type = "unavailable";}; + + -- Fire event (before full_p and anon_p are created) + module:fire_event("muc-broadcast-presence", {room = self; stanza = base_presence; x = base_x;}); --- Broadcasts an occupant's presence to the whole room --- Takes the x element that goes into the stanzas -function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason) - -- Build real jid and (optionally) occupant jid template presences local function get_presence(is_anonymous) local x = st.clone(base_x); self:build_item_list(occupant, x, is_anonymous, nick, actor, reason); - return get_base_presence(occupant):add_child(x), x; + return st.clone(base_presence):add_child(x), x; end - local full_p, full_x = get_presence(false); - module:fire_event("muc-broadcast-presence", {room = self; stanza = full_p; x = full_x;}); + local full_p, full_x = get_presence(false); + -- Create anon_p lazily local anon_p, anon_x; local function get_anon_p() if anon_p == nil then -- cgit v1.2.3 From 2cc4b6a6aef216d80e788eac02e0e41633a22f07 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 8 Jul 2014 07:32:45 +0200 Subject: mod_pep_plus: Only broadcast newly added subscriptions --- plugins/mod_pep_plus.lua | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) (limited to 'plugins') diff --git a/plugins/mod_pep_plus.lua b/plugins/mod_pep_plus.lua index ee57e647..81dd3085 100644 --- a/plugins/mod_pep_plus.lua +++ b/plugins/mod_pep_plus.lua @@ -14,6 +14,8 @@ local lib_pubsub = module:require "pubsub"; local handlers = lib_pubsub.handlers; local pubsub_error_reply = lib_pubsub.pubsub_error_reply; +local empty_set = set_new(); + local services = {}; local recipients = {}; local hash_map = {}; @@ -222,22 +224,37 @@ end local function update_subscriptions(recipient, service_name, nodes) local service = get_pep_service(service_name); + nodes = nodes or empty_set; - recipients[service_name] = recipients[service_name] or {}; - nodes = nodes or set_new(); - local old = recipients[service_name][recipient]; + local service_recipients = recipients[service_name]; + if not service_recipients then + service_recipients = {}; + recipients[service_name] = service_recipients; + end - if old and type(old) == table then - for node in pairs((old - nodes):items()) do - service:remove_subscription(node, recipient, recipient); - end + local current = service_recipients[recipient]; + if not current or type(current) ~= "table" then + current = empty_set; + end + + if (current == empty_set or current:empty()) and (nodes == empty_set or nodes:empty()) then + return; end - for node in nodes:items() do + for node in current - nodes do + service:remove_subscription(node, recipient, recipient); + end + + for node in nodes - current do service:add_subscription(node, recipient, recipient); resend_last_item(recipient, node, service); end - recipients[service_name][recipient] = nodes; + + if nodes == empty_set or nodes:empty() then + nodes = nil; + end + + service_recipients[recipient] = nodes; end module:hook("presence/bare", function(event) -- cgit v1.2.3 From 4aba920b7e87b05c8ed6750149bf0ff6a9a65ab8 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 25 Jul 2014 18:32:15 +0100 Subject: mod_muc/muc.lib: Pass all info to muc-broadcast-presence handlers that would be required to use room:build_item_list() - useful for plugins --- plugins/muc/muc.lib.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 3ee17b03..1d95b6f8 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -201,7 +201,11 @@ function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason base_presence = base_presence or st.presence {from = occupant.nick; type = "unavailable";}; -- Fire event (before full_p and anon_p are created) - module:fire_event("muc-broadcast-presence", {room = self; stanza = base_presence; x = base_x;}); + module:fire_event("muc-broadcast-presence", { + room = self; stanza = base_presence; x = base_x; + occupant = occupant; is_anonymous = is_anonymous; + nick = nick; actor = actor; reason = reason; + }); local function get_presence(is_anonymous) local x = st.clone(base_x); -- cgit v1.2.3 From 6f4e24a0fc86a839792d0dda5a99746179231fd0 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 25 Jul 2014 18:34:43 +0100 Subject: mod_muc/muc.lib: Remove is_anonymous from event (fix for ec57067c1e0d) --- plugins/muc/muc.lib.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1d95b6f8..041187e5 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -203,8 +203,8 @@ function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason -- Fire event (before full_p and anon_p are created) module:fire_event("muc-broadcast-presence", { room = self; stanza = base_presence; x = base_x; - occupant = occupant; is_anonymous = is_anonymous; - nick = nick; actor = actor; reason = reason; + occupant = occupant; nick = nick; actor = actor; + reason = reason; }); local function get_presence(is_anonymous) -- cgit v1.2.3 From 069a36e4758fac28ad5c89cb8a5f8a4bb3fed71f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 25 Jul 2014 21:12:21 +0200 Subject: mod_s2s: Remove unused locals --- plugins/mod_s2s/mod_s2s.lua | 2 -- 1 file changed, 2 deletions(-) (limited to 'plugins') diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index f0d465c0..f177bf82 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -15,7 +15,6 @@ local core_process_stanza = prosody.core_process_stanza; local tostring, type = tostring, type; local t_insert = table.insert; local xpcall, traceback = xpcall, debug.traceback; -local NULL = {}; local add_task = require "util.timer".add_task; local st = require "util.stanza"; @@ -26,7 +25,6 @@ local s2s_new_incoming = require "core.s2smanager".new_incoming; local s2s_new_outgoing = require "core.s2smanager".new_outgoing; local s2s_destroy_session = require "core.s2smanager".destroy_session; local uuid_gen = require "util.uuid".generate; -local cert_verify_identity = require "util.x509".verify_identity; local fire_global_event = prosody.events.fire_event; local s2sout = module:require("s2sout"); -- cgit v1.2.3 From dfc87dc7ebe5d424022eae8e10fa8d7a1f404e0f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 5 Aug 2014 09:16:29 +0100 Subject: mod_muc: Fix use of undefined global. Fixes #431. --- plugins/muc/mod_muc.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 4cd20245..af46714c 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -148,7 +148,8 @@ do if restrict_room_creation then local host_suffix = module.host:gsub("^[^%.]+%.", ""); module:hook("muc-room-pre-create", function(event) - local user_jid = event.stanza.attr.from; + local origin, stanza = event.origin, event.stanza; + local user_jid = stanza.attr.from; if not is_admin(user_jid) and not ( restrict_room_creation == "local" and select(2, jid_split(user_jid)) == host_suffix -- cgit v1.2.3 From e77035761a9de8dc2efafcd771fd29a197bb8b0b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 5 Aug 2014 09:55:08 +0100 Subject: mod_muc: Import util.stanza into the config handler modules that need it. Fixes #432. --- plugins/muc/lock.lib.lua | 2 ++ plugins/muc/members_only.lib.lua | 2 ++ plugins/muc/password.lib.lua | 2 ++ 3 files changed, 6 insertions(+) (limited to 'plugins') diff --git a/plugins/muc/lock.lib.lua b/plugins/muc/lock.lib.lua index 319a6973..82f0dc3f 100644 --- a/plugins/muc/lock.lib.lua +++ b/plugins/muc/lock.lib.lua @@ -7,6 +7,8 @@ -- COPYING file in the source package for more information. -- +local st = require "util.stanza"; + local lock_rooms = module:get_option_boolean("muc_room_locking", false); local lock_room_timeout = module:get_option_number("muc_room_lock_timeout", 300); diff --git a/plugins/muc/members_only.lib.lua b/plugins/muc/members_only.lib.lua index b0999f0b..65807e80 100644 --- a/plugins/muc/members_only.lib.lua +++ b/plugins/muc/members_only.lib.lua @@ -7,6 +7,8 @@ -- COPYING file in the source package for more information. -- +local st = require "util.stanza"; + local muc_util = module:require "muc/util"; local valid_roles, valid_affiliations = muc_util.valid_roles, muc_util.valid_affiliations; diff --git a/plugins/muc/password.lib.lua b/plugins/muc/password.lib.lua index d169790a..48486d73 100644 --- a/plugins/muc/password.lib.lua +++ b/plugins/muc/password.lib.lua @@ -7,6 +7,8 @@ -- COPYING file in the source package for more information. -- +local st = require "util.stanza"; + local function get_password(room) return room._data.password; end -- cgit v1.2.3 From 1b133aa8a0cb4f862ffda75ce24f9fd45e2ddef2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 7 Aug 2014 12:16:16 -0400 Subject: plugins/mod_storage_sql2: Add map store support --- plugins/mod_storage_sql2.lua | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'plugins') diff --git a/plugins/mod_storage_sql2.lua b/plugins/mod_storage_sql2.lua index 0531c905..ce21d368 100644 --- a/plugins/mod_storage_sql2.lua +++ b/plugins/mod_storage_sql2.lua @@ -216,6 +216,40 @@ function keyval_store:users() return iterator(result); end +local map_store = {}; +map_store.__index = map_store; +function map_store:get(username, key) + return engine:transaction(function() + if type(key) == "string" and key ~= "" then + local iter, state, first = engine:select("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", + host, username, self.store, key or ""); + local row = iter(state, first); + if row then + return deserialize(row.type, row.value); + else + return nil; + end + else + error("TODO: non-string keys"); + end + end); +end +function map_store:set(username, key, data) + return engine:transaction(function() + if data == nil then + engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", + host, username, self.store, key or ""); + elseif type(key) == "string" and key ~= "" then + local t, value = assert(serialize(data)); + engine:update("UPDATE `prosody` SET `type`=?, `value`=? WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", + t, value, host, username, self.store, key); + else + error("TODO: non-string keys"); + end + return true; + end); +end + local archive_store = {} archive_store.__index = archive_store function archive_store:append(username, key, when, with, value) @@ -341,6 +375,7 @@ end local stores = { keyval = keyval_store; + map = map_store; archive = archive_store; }; -- cgit v1.2.3 From 306d70fb8afe003bb6e613caa99ba27f9e53ab31 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 7 Aug 2014 12:34:51 -0400 Subject: plugins/muc/mod_muc: Remove unused import --- plugins/muc/mod_muc.lua | 1 - 1 file changed, 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index af46714c..15949c1b 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -17,7 +17,6 @@ local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; local um_is_admin = require "core.usermanager".is_admin; -local hosts = prosody.hosts; local rooms = module:shared "rooms"; -- cgit v1.2.3 From 0f02217cb01316c6fcbfded5df0ca39346d63752 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 7 Aug 2014 12:35:12 -0400 Subject: plugins/muc/mod_muc: Use map store for room persistence --- plugins/muc/mod_muc.lua | 131 ++++++++++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 59 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 15949c1b..bffb6579 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -44,80 +44,93 @@ do -- Monkey patch to make server admins room owners end end +local persistent = module:require "muc/persistent"; +local persistent_rooms_storage = module:open_store("persistent"); +local persistent_rooms = module:open_store("persistent", "map"); +local room_configs = module:open_store("config"); + +local function room_save(room, forced) + local node = jid_split(room.jid); + local is_persistent = persistent.get(room); + persistent_rooms:set(room.jid, is_persistent); + if is_persistent then + local history = room._data.history; + room._data.history = nil; + local data = { + jid = room.jid; + _data = room._data; + _affiliations = room._affiliations; + }; + room_configs:set(node, data); + room._data.history = history; + elseif forced then + room_configs:set(node, nil); + if not next(room._occupants) then -- Room empty + rooms[room.jid] = nil; + end + end +end + +-- Automatically destroy empty non-persistent rooms +module:hook("muc-occupant-left",function(event) + local room = event.room + if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room + module:fire_event("muc-room-destroyed", { room = room }); + end +end); + function track_room(room) rooms[room.jid] = room; + -- When room is created, over-ride 'save' method + room.save = room_save; +end + +local function restore_room(jid) + local node = jid_split(jid); + local data = room_configs:get(node); + if data then + local room = muclib.new_room(jid); + room._data = data._data; + room._affiliations = data._affiliations; + track_room(room); + return room; + end end function forget_room(jid) rooms[jid] = nil; + local node = jid_split(room.jid); + room_configs:set(node, nil); + if persistent.get(room_jid) then + persistent_rooms:set(room_jid, nil); + end end function get_room_from_jid(room_jid) - return rooms[room_jid] -end - -function each_room() - return iterators.values(rooms); -end - -do -- Persistent rooms - local persistent = module:require "muc/persistent"; - local persistent_rooms_storage = module:open_store("persistent"); - local persistent_rooms = persistent_rooms_storage:get() or {}; - local room_configs = module:open_store("config"); - - local function room_save(room, forced) - local node = jid_split(room.jid); - local is_persistent = persistent.get(room); - persistent_rooms[room.jid] = is_persistent; - if is_persistent then - local history = room._data.history; - room._data.history = nil; - local data = { - jid = room.jid; - _data = room._data; - _affiliations = room._affiliations; - }; - room_configs:set(node, data); - room._data.history = history; - elseif forced then - room_configs:set(node, nil); - if not next(room._occupants) then -- Room empty - rooms[room.jid] = nil; + local room = rooms[room_jid]; + if room == nil then + -- Check if in persistent storage + if persistent_rooms:get(room_jid) then + room = restore_room(room_jid); + if room == nil then + module:log("error", "Missing data for room '%s', removing from persistent room list", room_jid); + persistent_rooms:set(room_jid, nil); end end - if forced then persistent_rooms_storage:set(nil, persistent_rooms); end end + return room +end - -- When room is created, over-ride 'save' method - module:hook("muc-room-pre-create", function(event) - event.room.save = room_save; - end, 1000); - - -- Automatically destroy empty non-persistent rooms - module:hook("muc-occupant-left",function(event) - local room = event.room - if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room - module:fire_event("muc-room-destroyed", { room = room }); - end - end); - - local persistent_errors = false; - for jid in pairs(persistent_rooms) do - local node = jid_split(jid); - local data = room_configs:get(node); - if data then - local room = muclib.new_room(jid); - room._data = data._data; - room._affiliations = data._affiliations; - track_room(room); - else -- missing room data - persistent_rooms[jid] = nil; - module:log("error", "Missing data for room '%s', removing from persistent room list", jid); - persistent_errors = true; +function each_room() + for room_jid in pairs(persistent_rooms_storage:get(nil) or {}) do + if rooms[room_jid] == nil then -- Don't restore rooms that already exist + local room = restore_room(room_jid); + if room == nil then + module:log("error", "Missing data for room '%s', omitting from iteration", room_jid); + end end end - if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end + return iterators.values(rooms); end module:hook("host-disco-items", function(event) -- cgit v1.2.3 From adf4b5a52e544766157ddfef5cef9db76768af4a Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 7 Aug 2014 18:03:31 -0400 Subject: plugins/muc/mod_muc: persistent rooms keyval store needs username as nil --- plugins/muc/mod_muc.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index bffb6579..3f01ee11 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -52,7 +52,7 @@ local room_configs = module:open_store("config"); local function room_save(room, forced) local node = jid_split(room.jid); local is_persistent = persistent.get(room); - persistent_rooms:set(room.jid, is_persistent); + persistent_rooms:set(nil, room.jid, is_persistent); if is_persistent then local history = room._data.history; room._data.history = nil; @@ -102,7 +102,7 @@ function forget_room(jid) local node = jid_split(room.jid); room_configs:set(node, nil); if persistent.get(room_jid) then - persistent_rooms:set(room_jid, nil); + persistent_rooms:set(nil, room_jid, nil); end end @@ -110,11 +110,11 @@ function get_room_from_jid(room_jid) local room = rooms[room_jid]; if room == nil then -- Check if in persistent storage - if persistent_rooms:get(room_jid) then + if persistent_rooms:get(nil, room_jid) then room = restore_room(room_jid); if room == nil then module:log("error", "Missing data for room '%s', removing from persistent room list", room_jid); - persistent_rooms:set(room_jid, nil); + persistent_rooms:set(nil, room_jid, nil); end end end -- cgit v1.2.3 From 06903bc73909fd1557c326f2cc90404aed4f7dcc Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 7 Aug 2014 18:34:51 -0400 Subject: plugins/mod_storage_sql2: Return correct arguments from map_store operations --- plugins/mod_storage_sql2.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'plugins') diff --git a/plugins/mod_storage_sql2.lua b/plugins/mod_storage_sql2.lua index ce21d368..c0e2e6cd 100644 --- a/plugins/mod_storage_sql2.lua +++ b/plugins/mod_storage_sql2.lua @@ -219,7 +219,7 @@ end local map_store = {}; map_store.__index = map_store; function map_store:get(username, key) - return engine:transaction(function() + local ok, result = engine:transaction(function() if type(key) == "string" and key ~= "" then local iter, state, first = engine:select("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", host, username, self.store, key or ""); @@ -233,9 +233,11 @@ function map_store:get(username, key) error("TODO: non-string keys"); end end); + if not ok then return nil, result; end + return result; end function map_store:set(username, key, data) - return engine:transaction(function() + local ok, result = engine:transaction(function() if data == nil then engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", host, username, self.store, key or ""); @@ -248,6 +250,8 @@ function map_store:set(username, key, data) end return true; end); + if not ok then return nil, result; end + return result; end local archive_store = {} -- cgit v1.2.3 From 0437582ce056715d99a91bf0afe958b045d0cc6a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 Aug 2014 21:52:32 +0200 Subject: mod_private: Use map store --- plugins/mod_private.lua | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) (limited to 'plugins') diff --git a/plugins/mod_private.lua b/plugins/mod_private.lua index 872d3760..05f05708 100644 --- a/plugins/mod_private.lua +++ b/plugins/mod_private.lua @@ -9,7 +9,7 @@ local st = require "util.stanza" -local private_storage = module:open_store(); +local private_storage = module:open_store("private", "map"); module:add_feature("jabber:iq:private"); @@ -21,25 +21,22 @@ module:hook("iq/self/jabber:iq:private:query", function(event) end local tag = query.tags[1]; local key = tag.name..":"..tag.attr.xmlns; - local data, err = private_storage:get(origin.username); - if err then - return origin.send(st.error_reply(stanza, "wait", "internal-server-error", err)); - end if stanza.attr.type == "get" then - if data and data[key] then + local data, err = private_storage:get(origin.username, key); + if data then return origin.send(st.reply(stanza):query("jabber:iq:private"):add_child(st.deserialize(data))); + elseif err then + return origin.send(st.error_reply(stanza, "wait", "internal-server-error", err)); else return origin.send(st.reply(stanza):add_child(query)); end else -- type == set - if not data then data = {}; end; - if #tag == 0 then - data[key] = nil; - else - data[key] = st.preserialize(tag); + local data; + if #tag ~= 0 then + data = st.preserialize(tag); end -- TODO delete datastore if empty - local ok, err = private_storage:set(origin.username, data); + local ok, err = private_storage:set(origin.username, key, data); if not ok then return origin.send(st.error_reply(stanza, "wait", "internal-server-error", err)); end -- cgit v1.2.3 From b699ac03fbf586261ecc605007993c563e15050b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 11 Aug 2014 11:36:30 +0200 Subject: mod_muc: Fix tracebacks (thanks nick1) --- plugins/muc/mod_muc.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 3f01ee11..5abde57c 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -97,11 +97,12 @@ local function restore_room(jid) end end -function forget_room(jid) - rooms[jid] = nil; +function forget_room(room) + local room_jid = room.jid; local node = jid_split(room.jid); + rooms[room_jid] = nil; room_configs:set(node, nil); - if persistent.get(room_jid) then + if persistent.get(room) then persistent_rooms:set(nil, room_jid, nil); end end @@ -148,8 +149,7 @@ module:hook("muc-room-pre-create", function(event) end, -1000); module:hook("muc-room-destroyed",function(event) - local room = event.room - forget_room(room.jid) + return forget_room(event.room); end) do -- cgit v1.2.3 From 7eb2ec7a56e3ffca8ddbe06ece12ba20c4125fac Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 12 Aug 2014 11:38:12 +0200 Subject: mod_storage_sql2: DELETE then INSERT in map stores --- plugins/mod_storage_sql2.lua | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) (limited to 'plugins') diff --git a/plugins/mod_storage_sql2.lua b/plugins/mod_storage_sql2.lua index c0e2e6cd..e5357416 100644 --- a/plugins/mod_storage_sql2.lua +++ b/plugins/mod_storage_sql2.lua @@ -221,13 +221,8 @@ map_store.__index = map_store; function map_store:get(username, key) local ok, result = engine:transaction(function() if type(key) == "string" and key ~= "" then - local iter, state, first = engine:select("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", - host, username, self.store, key or ""); - local row = iter(state, first); - if row then - return deserialize(row.type, row.value); - else - return nil; + for row in engine:select("SELECT `type`, `value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", host, username or "", self.store, key) do + return deserialize(row[1], row[2]); end else error("TODO: non-string keys"); @@ -238,13 +233,13 @@ function map_store:get(username, key) end function map_store:set(username, key, data) local ok, result = engine:transaction(function() - if data == nil then + if type(key) == "string" and key ~= "" then engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", - host, username, self.store, key or ""); - elseif type(key) == "string" and key ~= "" then - local t, value = assert(serialize(data)); - engine:update("UPDATE `prosody` SET `type`=?, `value`=? WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", - t, value, host, username, self.store, key); + host, username or "", self.store, key); + if data ~= nil then + local t, value = assert(serialize(data)); + engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, username or "", self.store, key, t, value); + end else error("TODO: non-string keys"); end -- cgit v1.2.3 From 46105d64bf9627c8618624281c966ec784f4fc12 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 31 Aug 2014 20:33:47 +0200 Subject: mod_muc: Fix 'destroy rooms' adhoc command (Thanks Florob) --- plugins/muc/mod_muc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins') diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 5abde57c..5428d270 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -240,7 +240,7 @@ do -- Ad-hoc commands }; local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function() - return { rooms = array.collect(each_room):pluck("jid"):sort(); }; + return { rooms = array.collect(each_room()):pluck("jid"):sort(); }; end, function(fields, errors) if errors then local errmsg = {}; -- cgit v1.2.3 From 136d5aee859f4b064efb6618fa0de99e491bd271 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 5 Sep 2014 11:19:16 -0400 Subject: plugins/muc/muc.lib: Add instant room support --- plugins/muc/muc.lib.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 041187e5..d430f984 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -614,10 +614,15 @@ function room_mt:process_form(origin, stanza) if form.attr.type == "cancel" then origin.send(st.reply(stanza)); elseif form.attr.type == "submit" then - 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 true; + local fields; + if form.tags[1] == nil then -- Instant room + fields = {}; + else + 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 true; + end end local event = {room = self; origin = origin; stanza = stanza; fields = fields; status_codes = {};}; -- cgit v1.2.3 From f4e4cfbd02d830efd60bc07af0b7d953c77bab88 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 5 Sep 2014 11:20:54 -0400 Subject: plugins/muc/muc.lib: Use get_affilation() inside of set_affiliation(), so that the override in mod_muc works --- plugins/muc/muc.lib.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d430f984..31b5c559 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -984,10 +984,9 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason) local is_downgrade = valid_affiliations[target_affiliation or "none"] > valid_affiliations[affiliation or "none"]; if actor ~= true then - local actor_bare = jid_bare(actor); - local actor_affiliation = self._affiliations[actor_bare]; + local actor_affiliation = self:get_affiliation(actor); if actor_affiliation == "owner" then - if actor_bare == jid then -- self change + if jid_bare(actor) == jid then -- self change -- need at least one owner 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 -- cgit v1.2.3 From 8728ee5e5a3925588e5502a87a3466ec8eebc44a Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 5 Sep 2014 11:28:28 -0400 Subject: plugins/muc/muc.lib: Add muc-set-affiliation event --- plugins/muc/muc.lib.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'plugins') diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 31b5c559..4e461df6 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1049,6 +1049,17 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason) end if self.save then self:save(); end + + module:fire_event("muc-set-affiliation", { + room = self; + actor = actor; + jid = jid; + affiliation = affiliation or "none"; + reason = reason; + previous_affiliation = target_affiliation; + in_room = next(occupants_updated) ~= nil; + }); + return true; end -- cgit v1.2.3 From 1e987242449ef303547f9df10b99d4313e21e3be Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 5 Sep 2014 12:16:53 -0400 Subject: plugins/muc: Add affiliation_notify config option to send out status code 101 --- plugins/muc/affiliation_notify.lib.lua | 66 ++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 2 ++ 2 files changed, 68 insertions(+) create mode 100644 plugins/muc/affiliation_notify.lib.lua (limited to 'plugins') diff --git a/plugins/muc/affiliation_notify.lib.lua b/plugins/muc/affiliation_notify.lib.lua new file mode 100644 index 00000000..e2ecbc3b --- /dev/null +++ b/plugins/muc/affiliation_notify.lib.lua @@ -0,0 +1,66 @@ +-- Prosody IM +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +--[[ +Out of courtesy, a MUC service MAY send an out-of-room +if a user's affiliation changes while the user is not in the room; +the message SHOULD be sent from the room to the user's bare JID, +MAY contain a element describing the affiliation change, +and MUST contain a status code of 101. +]] + + +local st = require "util.stanza"; + +local function get_affiliation_notify(room) + return room._data.affiliation_notify; +end + +local function set_affiliation_notify(room, affiliation_notify) + affiliation_notify = affiliation_notify and true or nil; + if room._data.affiliation_notify == affiliation_notify then return false; end + room._data.affiliation_notify = affiliation_notify; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_affiliationnotify"; + type = "boolean"; + label = "Notify users when their affiliation changes when they are not in the room?"; + value = get_affiliation_notify(event.room); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_affiliationnotify"]; + if new ~= nil and set_affiliation_notify(event.room, new) then + event.status_codes["104"] = true; + end +end); + +module:hook("muc-set-affiliation", function(event) + local room = event.room; + if not event.in_room and get_affiliation_notify(room) then + local body = string.format("Your affiliation in room %s is now %s.", room.jid, event.affiliation); + local stanza = st.message({ + type = "headline"; + from = room.jid; + to = event.jid; + }, body) + :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("status", {code="101"}):up() + :up(); + room:route_stanza(stanza); + end +end); + +return { + get = get_affiliation_notify; + set = set_affiliation_notify; +}; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 4e461df6..3dd21303 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1111,6 +1111,8 @@ function room_mt:set_role(actor, occupant_jid, role, reason) return true; end +local affiliation_notify = module:require "muc/affiliation_notify"; + local name = module:require "muc/name"; room_mt.get_name = name.get; room_mt.set_name = name.set; -- cgit v1.2.3