From 3fd77923dafc04d26a6b101c46e853ca65e9afae 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 ++-- util/pubsub.lua | 9 ++++++++- 3 files changed, 11 insertions(+), 4 deletions(-) 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 diff --git a/util/pubsub.lua b/util/pubsub.lua index 0dfd196b..e0d428c0 100644 --- a/util/pubsub.lua +++ b/util/pubsub.lua @@ -258,6 +258,7 @@ function service:publish(node, actor, id, item) end node_obj = self.nodes[node]; end + node_obj.data[#node_obj.data + 1] = id; node_obj.data[id] = item; self.events.fire_event("item-published", { node = node, actor = actor, id = id, item = item }); self.config.broadcaster("items", node, node_obj.subscribers, item); @@ -275,6 +276,12 @@ function service:retract(node, actor, id, retract) return false, "item-not-found"; end node_obj.data[id] = nil; + for i, _id in ipairs(node_obj.data) do + if id == _id then + table.remove(node_obj, i); + break; + end + end if retract then self.config.broadcaster("items", node, node_obj.subscribers, retract); end @@ -309,7 +316,7 @@ function service:get_items(node, actor, id) return false, "item-not-found"; end if id then -- Restrict results to a single specific item - return true, { [id] = node_obj.data[id] }; + return true, { id, [id] = node_obj.data[id] }; else return true, node_obj.data; end -- cgit v1.2.3 From 45490a4215a96c8745da04255738c67438705203 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 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 86e7a96d76ab04d32f92beace38ab0593e8d1b02 Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Wed, 30 Oct 2013 17:30:35 -0400 Subject: util.indexedbheap: A priority queue implementation with a reverse index with no per-entry memory allocation. --- util/indexedbheap.lua | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 util/indexedbheap.lua diff --git a/util/indexedbheap.lua b/util/indexedbheap.lua new file mode 100644 index 00000000..3cb03037 --- /dev/null +++ b/util/indexedbheap.lua @@ -0,0 +1,153 @@ + +local setmetatable = setmetatable; +local math_floor = math.floor; +local t_remove = table.remove; + +local function _heap_insert(self, item, sync, item2, index) + local pos = #self + 1; + while true do + local half_pos = math_floor(pos / 2); + if half_pos == 0 or item > self[half_pos] then break; end + self[pos] = self[half_pos]; + sync[pos] = sync[half_pos]; + index[sync[pos]] = pos; + pos = half_pos; + end + self[pos] = item; + sync[pos] = item2; + index[item2] = pos; +end + +local function _percolate_up(self, k, sync, index) + local tmp = self[k]; + local tmp_sync = sync[k]; + while k ~= 1 do + local parent = math_floor(k/2); + if tmp < self[parent] then break; end + self[k] = self[parent]; + sync[k] = sync[parent]; + index[sync[k]] = k; + k = parent; + end + self[k] = tmp; + sync[k] = tmp_sync; + index[tmp_sync] = k; + return k; +end + +local function _percolate_down(self, k, sync, index) + local tmp = self[k]; + local tmp_sync = sync[k]; + local size = #self; + local child = 2*k; + while 2*k <= size do + if child ~= size and self[child] > self[child + 1] then + child = child + 1; + end + if tmp > self[child] then + self[k] = self[child]; + sync[k] = sync[child]; + index[sync[k]] = k; + else + break; + end + + k = child; + child = 2*k; + end + self[k] = tmp; + sync[k] = tmp_sync; + index[tmp_sync] = k; + return k; +end + +local function _heap_pop(self, sync, index) + local size = #self; + if size == 0 then return nil; end + + local result = self[1]; + local result_sync = sync[1]; + index[result_sync] = nil; + if size == 1 then + self[1] = nil; + sync[1] = nil; + return result, result_sync; + end + self[1] = t_remove(self); + sync[1] = t_remove(sync); + index[sync[1]] = 1; + + _percolate_down(self, 1, sync, index); + + return result, result_sync; +end + +local indexed_heap = {}; + +function indexed_heap:insert(item, priority, id) + if id == nil then + id = self.current_id; + self.current_id = id + 1; + end + self.items[id] = item; + _heap_insert(self.priorities, priority, self.ids, id, self.index); + return id; +end +function indexed_heap:pop() + local priority, id = _heap_pop(self.priorities, self.ids, self.index); + if id then + local item = self.items[id]; + self.items[id] = nil; + return priority, item, id; + end +end +function indexed_heap:peek() + return self.priorities[1]; +end +function indexed_heap:reprioritize(id, priority) + local k = self.index[id]; + if k == nil then return; end + self.priorities[k] = priority; + + k = _percolate_up(self.priorities, k, self.ids, self.index); + k = _percolate_down(self.priorities, k, self.ids, self.index); +end +function indexed_heap:remove_index(k) + local size = #self.priorities; + + local result = self.priorities[k]; + local result_sync = self.ids[k]; + local item = self.items[result_sync]; + if result == nil then return; end + self.index[result_sync] = nil; + self.items[result_sync] = nil; + + self.priorities[k] = self.priorities[size]; + self.ids[k] = self.ids[size]; + self.index[self.ids[k]] = k; + t_remove(self.priorities); + t_remove(self.ids); + + k = _percolate_up(self.priorities, k, self.ids, self.index); + k = _percolate_down(self.priorities, k, self.ids, self.index); + + return result, item, result_sync; +end +function indexed_heap:remove(id) + return self:remove_index(self.index[id]); +end + +local mt = { __index = indexed_heap }; + +local _M = { + create = function() + return setmetatable({ + ids = {}; -- heap of ids, sync'd with priorities + items = {}; -- map id->items + priorities = {}; -- heap of priorities + index = {}; -- map of id->index of id in ids + current_id = 1.5 + }, mt); + end +}; +return _M; -- cgit v1.2.3 From 792398b07ef11702ddc6b842744840969e48b0e6 Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Wed, 30 Oct 2013 17:44:42 -0400 Subject: util.timer: Updated to use util.indexedbheap to provide a more complete API. Timers can now be stopped or rescheduled. Callbacks are now pcall'd. Adding/removing timers from within timer callbacks works better. Optional parameter can be passed when creating timer which gets passed to callback, eliminating the need for closures in various timer uses. Timers are now much more lightweight. --- util/timer.lua | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/util/timer.lua b/util/timer.lua index 0e10e144..d7a9b0bf 100644 --- a/util/timer.lua +++ b/util/timer.lua @@ -6,6 +6,8 @@ -- COPYING file in the source package for more information. -- +local indexedbheap = require "util.indexedbheap"; +local log = require "util.logger".init("timer"); local server = require "net.server"; local math_min = math.min local math_huge = math.huge @@ -78,6 +80,60 @@ else end end -add_task = _add_task; +--add_task = _add_task; + +local h = indexedbheap.create(); +local params = {}; +local next_time = nil; +local _id, _callback, _now, _param; +local function _call() return _callback(_now, _id, _param); end +local function _traceback_handler(err) log("error", "Traceback[timer]: %s", traceback(tostring(err), 2)); end +local function _on_timer(now) + local peek; + while true do + peek = h:peek(); + if peek == nil or peek > now then break; end + local _; + _, _callback, _id = h:pop(); + _now = now; + _param = params[id]; + params[id] = nil; + --item(now, id, _param); -- FIXME pcall + local success, err = xpcall(_call, _traceback_handler); + if success and type(err) == "number" then + h:insert(_callback, err + now, _id); -- re-add + end + end + next_time = peek; + if peek ~= nil then + return peek - now; + end +end +function add_task(delay, callback, param) + local current_time = get_time(); + local event_time = current_time + delay; + + local id = h:insert(callback, event_time); + params[id] = param; + if next_time == nil or event_time < next_time then + next_time = event_time; + _add_task(next_time - current_time, on_timer); + end + return id; +end +function stop(id) + params[id] = nil; + return h:remove(id); +end +function reschedule(id, delay) + local current_time = get_time(); + local event_time = current_time + delay; + h:reprioritize(id, delay); + if next_time == nil or event_time < next_time then + next_time = event_time; + _add_task(next_time - current_time, on_timer); + end + return id; +end return _M; -- cgit v1.2.3 From 0f260f9b4e23572e8063c0e4302ca9bccac117d0 Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Wed, 30 Oct 2013 17:51:37 -0400 Subject: util.timer: Fix variable name typo. --- util/timer.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/timer.lua b/util/timer.lua index d7a9b0bf..99aade15 100644 --- a/util/timer.lua +++ b/util/timer.lua @@ -117,7 +117,7 @@ function add_task(delay, callback, param) params[id] = param; if next_time == nil or event_time < next_time then next_time = event_time; - _add_task(next_time - current_time, on_timer); + _add_task(next_time - current_time, _on_timer); end return id; end @@ -131,7 +131,7 @@ function reschedule(id, delay) h:reprioritize(id, delay); if next_time == nil or event_time < next_time then next_time = event_time; - _add_task(next_time - current_time, on_timer); + _add_task(next_time - current_time, _on_timer); end return id; end -- cgit v1.2.3 From ad2211ad5722c3f1e46587682eaa85210321a68d Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Wed, 30 Oct 2013 17:56:00 -0400 Subject: util.timer: Fix another variable name typo (thanks again zash). --- util/timer.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/timer.lua b/util/timer.lua index 99aade15..76deaff1 100644 --- a/util/timer.lua +++ b/util/timer.lua @@ -96,8 +96,8 @@ local function _on_timer(now) local _; _, _callback, _id = h:pop(); _now = now; - _param = params[id]; - params[id] = nil; + _param = params[_id]; + params[_id] = nil; --item(now, id, _param); -- FIXME pcall local success, err = xpcall(_call, _traceback_handler); if success and type(err) == "number" then -- cgit v1.2.3 From 9fde274e2174f63d3f50e38f2992b6019a106ec7 Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Wed, 30 Oct 2013 17:58:17 -0400 Subject: util.timer: Import all require upvalues. --- util/timer.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/util/timer.lua b/util/timer.lua index 76deaff1..451e27d3 100644 --- a/util/timer.lua +++ b/util/timer.lua @@ -15,6 +15,9 @@ local get_time = require "socket".gettime; local t_insert = table.insert; local pairs = pairs; local type = type; +local debug_traceback = debug.traceback; +local tostring = tostring; +local xpcall = xpcall; local data = {}; local new_data = {}; @@ -87,7 +90,7 @@ local params = {}; local next_time = nil; local _id, _callback, _now, _param; local function _call() return _callback(_now, _id, _param); end -local function _traceback_handler(err) log("error", "Traceback[timer]: %s", traceback(tostring(err), 2)); end +local function _traceback_handler(err) log("error", "Traceback[timer]: %s", debug_traceback(tostring(err), 2)); end local function _on_timer(now) local peek; while true do -- cgit v1.2.3 From b3325a4073f83da672df19711e78be2d58c7ff92 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 6 Nov 2013 12:56:18 -0500 Subject: util/timer: Re-set params when timer is rescheduled --- util/timer.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/util/timer.lua b/util/timer.lua index 451e27d3..23bd6a37 100644 --- a/util/timer.lua +++ b/util/timer.lua @@ -105,6 +105,7 @@ local function _on_timer(now) local success, err = xpcall(_call, _traceback_handler); if success and type(err) == "number" then h:insert(_callback, err + now, _id); -- re-add + params[_id] = _param; end end next_time = peek; -- cgit v1.2.3 From b2c50851b295612a149727ee89302fd2efef2fe3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 6 Nov 2013 12:56:35 -0500 Subject: core/moduleapi: Return timer object from module:add_timer --- core/moduleapi.lua | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index 65e00d41..a32ad245 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -347,11 +347,30 @@ function api:send(stanza) return core_post_stanza(hosts[self.host], stanza); end -function api:add_timer(delay, callback) - return timer.add_task(delay, function (t) - if self.loaded == false then return; end - return callback(t); - end); +local timer_methods = { } +local timer_mt = { + __index = timer_methods; +} +function timer_methods:stop( ) + timer.stop(self.id); +end +timer_methods.disarm = timer_methods.stop +function timer_methods:reschedule(delay) + timer.reschedule(self.id, delay) +end + +local function timer_callback(now, id, t) + if t.module_env.loaded == false then return; end + return t.callback(now, unpack(t, 1, t.n)); +end + +local pack = table.pack or function(...) return {n=select("#",...), ...}; end +function api:add_timer(delay, callback, ...) + local t = pack(...) + t.module_env = self; + t.callback = callback; + t.id = timer.add_task(delay, timer_callback, t); + return setmetatable(t, timer_mt); end local path_sep = package.config:sub(1,1); -- cgit v1.2.3 From c6d12b1a102ee760b7d5a567e3346124589e29e3 Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Wed, 6 Nov 2013 14:38:51 -0500 Subject: core.moduleapi: Fix some global accesses. --- core/moduleapi.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/moduleapi.lua b/core/moduleapi.lua index a32ad245..5a24f69c 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -16,8 +16,10 @@ local timer = require "util.timer"; local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; local error, setmetatable, type = error, setmetatable, type; -local ipairs, pairs, select, unpack = ipairs, pairs, select, unpack; +local ipairs, pairs, select = ipairs, pairs, select; local tonumber, tostring = tonumber, tostring; +local pack = table.pack or function(...) return {n=select("#",...), ...}; end -- table.pack is only in 5.2 +local unpack = table.unpack or unpack; -- renamed in 5.2 local prosody = prosody; local hosts = prosody.hosts; @@ -364,7 +366,6 @@ local function timer_callback(now, id, t) return t.callback(now, unpack(t, 1, t.n)); end -local pack = table.pack or function(...) return {n=select("#",...), ...}; end function api:add_timer(delay, callback, ...) local t = pack(...) t.module_env = self; -- cgit v1.2.3 From 34e978cc074e534fab4319a5d11f94b5673eec81 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 18 Dec 2013 17:50:38 -0500 Subject: net/server_select: pcall require ssl (easy to forget to require ssl) --- net/server_select.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index c5e0772f..61078202 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -48,13 +48,13 @@ local coroutine_yield = coroutine.yield --// extern libs //-- -local luasec = use "ssl" +local has_luasec, luasec = pcall ( require , "ssl" ) local luasocket = use "socket" or require "socket" local luasocket_gettime = luasocket.gettime --// extern lib methods //-- -local ssl_wrap = ( luasec and luasec.wrap ) +local ssl_wrap = ( has_luasec and luasec.wrap ) local socket_bind = luasocket.bind local socket_sleep = luasocket.sleep local socket_select = luasocket.select @@ -585,7 +585,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport end ) end - if luasec then + if has_luasec then handler.starttls = function( self, _sslctx) if _sslctx then handler:set_sslctx(_sslctx); @@ -638,7 +638,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport _socketlist[ socket ] = handler _readlistlen = addsocket(_readlist, socket, _readlistlen) - if sslctx and luasec then + if sslctx and has_luasec then out_put "server.lua: auto-starting ssl negotiation..." handler.autostart_ssl = true; local ok, err = handler:starttls(sslctx); @@ -721,7 +721,7 @@ addserver = function( addr, port, listeners, pattern, sslctx ) -- this function err = "invalid port" elseif _server[ addr..":"..port ] then err = "listeners on '[" .. addr .. "]:" .. port .. "' already exist" - elseif sslctx and not luasec then + elseif sslctx and not has_luasec then err = "luasec not found" end if err then -- cgit v1.2.3 From 927d7917375186ae365d85c6d59f7f9f6a012b57 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 18 Dec 2013 17:51:27 -0500 Subject: net/server_select: Check arguments to add_server correctly --- net/server_select.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index 61078202..e319e016 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -713,11 +713,13 @@ end ----------------------------------// PUBLIC //-- addserver = function( addr, port, listeners, pattern, sslctx ) -- this function provides a way for other scripts to reg a server + addr = addr or "*" local err if type( listeners ) ~= "table" then err = "invalid listener table" - end - if type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then + elseif type ( addr ) ~= "string" then + err = "invalid address" + elseif type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then err = "invalid port" elseif _server[ addr..":"..port ] then err = "listeners on '[" .. addr .. "]:" .. port .. "' already exist" @@ -728,7 +730,6 @@ addserver = function( addr, port, listeners, pattern, sslctx ) -- this function out_error( "server.lua, [", addr, "]:", port, ": ", err ) return nil, err end - addr = addr or "*" local server, err = socket_bind( addr, port, _tcpbacklog ) if err then out_error( "server.lua, [", addr, "]:", port, ": ", err ) -- cgit v1.2.3 From 0639f3d4a3fbd107d93ca064a3248c7f18862aad Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 18 Dec 2013 17:52:28 -0500 Subject: net/server_event: add_client should have same arguments no-matter the server backend --- net/server_event.lua | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/net/server_event.lua b/net/server_event.lua index 59217a0c..82accc99 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -744,36 +744,21 @@ do --function handleclient( client, ip, port, server, pattern, listener, _, sslctx ) -- creates an client interface end - function addclient( addr, serverport, listener, pattern, localaddr, localport, sslcfg, startssl ) + function addclient( addr, serverport, listener, pattern, sslctx ) + if sslctx and not ssl then + debug "need luasec, but not available" + return nil, "luasec not found" + end local client, err = socket.tcp() -- creating new socket if not client then debug( "cannot create socket:", err ) return nil, err end client:settimeout( 0 ) -- set nonblocking - if localaddr then - local res, err = client:bind( localaddr, localport, -1 ) - if not res then - debug( "cannot bind client:", err ) - return nil, err - end - end - local sslctx - if sslcfg then -- handle ssl/new context - if not ssl then - debug "need luasec, but not available" - return nil, "luasec not found" - end - sslctx, err = sslcfg - if err then - debug( "cannot create new ssl context:", err ) - return nil, err - end - end local res, err = client:connect( addr, serverport ) -- connect if res or ( err == "timeout" ) then local ip, port = client:getsockname( ) - local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx, startssl ) + local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx ) interface:_start_connection( startssl ) debug( "new connection id:", interface.id ) return interface, err -- cgit v1.2.3 From 98bd53004dab9e535058cc46cf019610df7ced0f Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 18 Dec 2013 17:54:31 -0500 Subject: net/server_select: addclient: Check for failure correctly; remove wrapconnection call on failure --- net/server_select.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/server_select.lua b/net/server_select.lua index e319e016..bd4e59df 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -936,11 +936,11 @@ local addclient = function( address, port, listeners, pattern, sslctx ) return nil, err end client:settimeout( 0 ) - _, err = client:connect( address, port ) - if err then -- try again + local ok, err = client:connect( address, port ) + if ok or err == "timeout" then return wrapclient( client, address, port, listeners, pattern, sslctx ) else - return wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx ) + return nil, err end end -- cgit v1.2.3 From e3c0bdb6f3179edbb38371a301af4da309760246 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 18 Dec 2013 17:55:03 -0500 Subject: net/server_select: addclient: Check arguments --- net/server_select.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/net/server_select.lua b/net/server_select.lua index bd4e59df..c707e48f 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -931,6 +931,21 @@ local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx end local addclient = function( address, port, listeners, pattern, sslctx ) + local err + if type( listeners ) ~= "table" then + err = "invalid listener table" + elseif type ( addr ) ~= "string" then + err = "invalid address" + elseif type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then + err = "invalid port" + elseif sslctx and not has_luasec then + err = "luasec not found" + end + if err then + out_error( "server.lua, addclient: ", err ) + return nil, err + end + local client, err = luasocket.tcp( ) if err then return nil, err -- cgit v1.2.3 From 31e5378e58926c552870a92f33d79da12af75507 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 18 Dec 2013 18:06:33 -0500 Subject: net/server_select: Fix typo --- net/server_select.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/server_select.lua b/net/server_select.lua index c707e48f..91b8b01f 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -934,7 +934,7 @@ local addclient = function( address, port, listeners, pattern, sslctx ) local err if type( listeners ) ~= "table" then err = "invalid listener table" - elseif type ( addr ) ~= "string" then + elseif type ( address ) ~= "string" then err = "invalid address" elseif type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then err = "invalid port" -- cgit v1.2.3 From 48f909666b964c8170ea8785a5e6b6043a25648b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 18 Dec 2013 18:11:17 -0500 Subject: net/server_event: pcall require ssl rather than relying on globals --- net/server_event.lua | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/net/server_event.lua b/net/server_event.lua index 82accc99..502cc80a 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -44,7 +44,7 @@ local setmetatable = use "setmetatable" local t_insert = table.insert local t_concat = table.concat -local ssl = use "ssl" +local has_luasec, ssl = pcall ( require , "ssl" ) local socket = use "socket" or require "socket" local log = require ("util.logger").init("socket") @@ -136,7 +136,7 @@ do self:_close() debug( "new connection failed. id:", self.id, "error:", self.fatalerror ) else - if plainssl and ssl then -- start ssl session + if plainssl and has_luasec then -- start ssl session self:starttls(self._sslctx, true) else -- normal connection self:_start_session(true) @@ -506,7 +506,7 @@ do _sslctx = sslctx; -- parameters _usingssl = false; -- client is using ssl; } - if not ssl then interface.starttls = false; end + if not has_luasec then interface.starttls = false; end interface.id = tostring(interface):match("%x+$"); interface.writecallback = function( event ) -- called on write events --vdebug( "new client write event, id/ip/port:", interface, ip, port ) @@ -689,7 +689,7 @@ do interface._connections = interface._connections + 1 -- increase connection count local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, sslctx ) --vdebug( "client id:", clientinterface, "startssl:", startssl ) - if ssl and sslctx then + if has_luasec and sslctx then clientinterface:starttls(sslctx, true) else clientinterface:_start_session( true ) @@ -710,25 +710,17 @@ do end local addserver = ( function( ) - return function( addr, port, listener, pattern, sslcfg, startssl ) -- TODO: check arguments - --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslcfg or "nil", startssl or "nil") + return function( addr, port, listener, pattern, sslctx, startssl ) -- TODO: check arguments + --vdebug( "creating new tcp server with following parameters:", addr or "nil", port or "nil", sslctx or "nil", startssl or "nil") + if sslctx and not has_luasec then + debug "fatal error: luasec not found" + return nil, "luasec not found" + end local server, err = socket.bind( addr, port, cfg.ACCEPT_QUEUE ) -- create server socket if not server then debug( "creating server socket on "..addr.." port "..port.." failed:", err ) return nil, err end - local sslctx - if sslcfg then - if not ssl then - debug "fatal error: luasec not found" - return nil, "luasec not found" - end - sslctx, err = sslcfg - if err then - debug( "error while creating new ssl context for server socket:", err ) - return nil, err - end - end local interface = handleserver( server, addr, port, pattern, listener, sslctx, startssl ) -- new server handler debug( "new server created with id:", tostring(interface)) return interface @@ -745,7 +737,7 @@ do end function addclient( addr, serverport, listener, pattern, sslctx ) - if sslctx and not ssl then + if sslctx and not has_luasec then debug "need luasec, but not available" return nil, "luasec not found" end -- cgit v1.2.3 From 248c6e05ed1c5927c6c18c0481f152993f1a03a3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 18 Dec 2013 18:11:47 -0500 Subject: net/server: addclient: wrapclient already calls startconnection for us --- net/server_event.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/server_event.lua b/net/server_event.lua index 502cc80a..81dc4512 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -128,7 +128,7 @@ do return self:_destroy(); end - function interface_mt:_start_connection(plainssl) -- should be called from addclient + function interface_mt:_start_connection(plainssl) -- called from wrapclient local callback = function( event ) if EV_TIMEOUT == event then -- timeout during connection self.fatalerror = "connection timeout" @@ -751,7 +751,6 @@ do if res or ( err == "timeout" ) then local ip, port = client:getsockname( ) local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx ) - interface:_start_connection( startssl ) debug( "new connection id:", interface.id ) return interface, err else -- cgit v1.2.3 From 8184587aa4a1ace6268e9ceb2059f1c72bf14633 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 18 Dec 2013 19:00:24 -0500 Subject: net/http: Use server.addclient --- net/http.lua | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/net/http.lua b/net/http.lua index ab9ec7b6..b87c9396 100644 --- a/net/http.lua +++ b/net/http.lua @@ -6,7 +6,6 @@ -- COPYING file in the source package for more information. -- -local socket = require "socket" local b64 = require "util.encodings".base64.encode; local url = require "socket.url" local httpstream_new = require "net.http.parser".new; @@ -160,21 +159,17 @@ function request(u, ex, callback) end local port_number = port and tonumber(port) or (using_https and 443 or 80); - -- Connect the socket, and wrap it with net.server - local conn = socket.tcp(); - conn:settimeout(10); - local ok, err = conn:connect(host, port_number); - if not ok and err ~= "timeout" then - callback(nil, 0, req); - return nil, err; - end - local sslctx = false; if using_https then sslctx = ex and ex.sslctx or { mode = "client", protocol = "sslv23", options = { "no_sslv2" } }; end - req.handler, req.conn = assert(server.wrapclient(conn, host, port_number, listener, "*a", sslctx)); + local handler, conn = server.addclient(host, port_number, listener, "*a", sslctx) + if not handler then + callback(nil, 0, req); + return nil, conn; + end + req.handler, req.conn = handler, conn req.write = function (...) return req.handler:write(...); end req.callback = function (content, code, request, response) log("debug", "Calling callback, status %s", code or "---"); return select(2, xpcall(function () return callback(content, code, request, response) end, handleerr)); end -- cgit v1.2.3 From 24118d8f74b097bf71f61d240c855ff5655e510d Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2013 17:55:41 +0100 Subject: net.server_{select,event}: addclient: Add argument for overriding socket type --- net/server_event.lua | 17 +++++++++++++---- net/server_select.lua | 11 +++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/net/server_event.lua b/net/server_event.lua index 81dc4512..ae64d50e 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -736,12 +736,19 @@ do --function handleclient( client, ip, port, server, pattern, listener, _, sslctx ) -- creates an client interface end - function addclient( addr, serverport, listener, pattern, sslctx ) + function addclient( addr, serverport, listener, pattern, sslctx, typ ) if sslctx and not has_luasec then debug "need luasec, but not available" return nil, "luasec not found" end - local client, err = socket.tcp() -- creating new socket + if not typ then + typ = "tcp" + end + local create = socket[typ] + if type( create ) ~= "function" then + return nil, "invalid socket type" + end + local client, err = create() -- creating new socket if not client then debug( "cannot create socket:", err ) return nil, err @@ -749,8 +756,10 @@ do client:settimeout( 0 ) -- set nonblocking local res, err = client:connect( addr, serverport ) -- connect if res or ( err == "timeout" ) then - local ip, port = client:getsockname( ) - local interface = wrapclient( client, ip, serverport, listener, pattern, sslctx ) + if client.getsockname then + addr = client:getsockname( ) + end + local interface = wrapclient( client, addr, serverport, listener, pattern, sslctx ) debug( "new connection id:", interface.id ) return interface, err else diff --git a/net/server_select.lua b/net/server_select.lua index 91b8b01f..1ce3c8c7 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -930,7 +930,7 @@ local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx return handler, socket end -local addclient = function( address, port, listeners, pattern, sslctx ) +local addclient = function( address, port, listeners, pattern, sslctx, typ ) local err if type( listeners ) ~= "table" then err = "invalid listener table" @@ -941,12 +941,19 @@ local addclient = function( address, port, listeners, pattern, sslctx ) elseif sslctx and not has_luasec then err = "luasec not found" end + if not typ then + typ = "tcp" + end + local create = luasocket[typ] + if type( create ) ~= "function" then + err = "invalid socket type" + end if err then out_error( "server.lua, addclient: ", err ) return nil, err end - local client, err = luasocket.tcp( ) + local client, err = create( ) if err then return nil, err end -- cgit v1.2.3 From bcb9af93c22dba3008a79ecd44eb947c2c7cd910 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2013 17:57:53 +0100 Subject: net.server_{select,event}: addclient: Use getaddrinfo to detect IP address type if no socket type argument given. (Argument must be given for non-TCP) --- net/server_event.lua | 9 ++++++++- net/server_select.lua | 10 +++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/net/server_event.lua b/net/server_event.lua index ae64d50e..1a3b8ca6 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -46,6 +46,7 @@ local t_concat = table.concat local has_luasec, ssl = pcall ( require , "ssl" ) local socket = use "socket" or require "socket" +local getaddrinfo = socket.dns.getaddrinfo local log = require ("util.logger").init("socket") @@ -742,7 +743,13 @@ do return nil, "luasec not found" end if not typ then - typ = "tcp" + local addrinfo, err = getaddrinfo(addr) + if not addrinfo then return nil, err end + if addrinfo[1] and addrinfo[1].family == "inet6" then + typ = "tcp6" + else + typ = "tcp" + end end local create = socket[typ] if type( create ) ~= "function" then diff --git a/net/server_select.lua b/net/server_select.lua index 1ce3c8c7..ee9cac7e 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -51,6 +51,7 @@ local coroutine_yield = coroutine.yield local has_luasec, luasec = pcall ( require , "ssl" ) local luasocket = use "socket" or require "socket" local luasocket_gettime = luasocket.gettime +local getaddrinfo = luasocket.dns.getaddrinfo --// extern lib methods //-- @@ -942,12 +943,19 @@ local addclient = function( address, port, listeners, pattern, sslctx, typ ) err = "luasec not found" end if not typ then - typ = "tcp" + local addrinfo, err = getaddrinfo(address) + if not addrinfo then return nil, err end + if addrinfo[1] and addrinfo[1].family == "inet6" then + typ = "tcp6" + else + typ = "tcp" + end end local create = luasocket[typ] if type( create ) ~= "function" then err = "invalid socket type" end + if err then out_error( "server.lua, addclient: ", err ) return nil, err -- cgit v1.2.3 From 29d3c27219e71e8f2ed0c835bb83333ca5ce9fbc Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 23 Dec 2013 23:23:59 +0100 Subject: net.server_{select,event}: addclient: Handle missing getaddrinfo --- net/server_event.lua | 6 ++---- net/server_select.lua | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/net/server_event.lua b/net/server_event.lua index 1a3b8ca6..ef0a27d8 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -742,16 +742,14 @@ do debug "need luasec, but not available" return nil, "luasec not found" end - if not typ then + if getaddrinfo and not typ then local addrinfo, err = getaddrinfo(addr) if not addrinfo then return nil, err end if addrinfo[1] and addrinfo[1].family == "inet6" then typ = "tcp6" - else - typ = "tcp" end end - local create = socket[typ] + local create = socket[typ or "tcp"] if type( create ) ~= "function" then return nil, "invalid socket type" end diff --git a/net/server_select.lua b/net/server_select.lua index ee9cac7e..b69b5fc7 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -942,16 +942,14 @@ local addclient = function( address, port, listeners, pattern, sslctx, typ ) elseif sslctx and not has_luasec then err = "luasec not found" end - if not typ then + if getaddrinfo and not typ then local addrinfo, err = getaddrinfo(address) if not addrinfo then return nil, err end if addrinfo[1] and addrinfo[1].family == "inet6" then typ = "tcp6" - else - typ = "tcp" end end - local create = luasocket[typ] + local create = luasocket[typ or "tcp"] if type( create ) ~= "function" then err = "invalid socket type" end -- cgit v1.2.3 From f7f9aaee30c452d00f1f0ad721e8669c203e2171 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Feb 2014 17:21:47 -0500 Subject: plugins/muc/mod_muc: Move Xep-0307 MUC unique to seperate file --- plugins/mod_muc_unique.lua | 11 +++++++++++ plugins/muc/mod_muc.lua | 31 ++++++++++--------------------- 2 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 plugins/mod_muc_unique.lua diff --git a/plugins/mod_muc_unique.lua b/plugins/mod_muc_unique.lua new file mode 100644 index 00000000..b27fcff6 --- /dev/null +++ b/plugins/mod_muc_unique.lua @@ -0,0 +1,11 @@ +-- XEP-0307: Unique Room Names for Multi-User Chat +local uuid_gen = require "util.uuid".generate; +module:add_feature "http://jabber.org/protocol/muc#unique" +module:hook("iq-get/host/http://jabber.org/protocol/muc#unique:unique", function() + local origin, stanza = event.origin, event.stanza; + origin.send(st.reply(stanza) + :tag("unique", {xmlns = "http://jabber.org/protocol/muc#unique"}) + :text(uuid_gen()) -- FIXME Random UUIDs can theoretically have collisions + ); + return true; +end,-1); diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index c514bafd..5a71ef75 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -31,7 +31,6 @@ local muc_new_room = muclib.new_room; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; -local uuid_gen = require "util.uuid".generate; local um_is_admin = require "core.usermanager".is_admin; local hosts = prosody.hosts; @@ -47,6 +46,7 @@ muclib.set_max_history_length(module:get_option_number("max_history_messages")); module:depends("disco"); module:add_identity("conference", "text", muc_name); module:add_feature("http://jabber.org/protocol/muc"); +module:depends "muc_unique" local function is_admin(jid) return um_is_admin(jid, module.host); @@ -136,25 +136,6 @@ module:hook("host-disco-items", function(event) end end); -local function handle_to_domain(event) - local origin, stanza = event.origin, event.stanza; - local type = stanza.attr.type; - if type == "error" or type == "result" then return; end - if stanza.name == "iq" and type == "get" then - local xmlns = stanza.tags[1].attr.xmlns; - local node = stanza.tags[1].attr.node; - if xmlns == "http://jabber.org/protocol/muc#unique" then - origin.send(st.reply(stanza):tag("unique", {xmlns = xmlns}):text(uuid_gen())); -- FIXME Random UUIDs can theoretically have collisions - else - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc - end - else - host_room:handle_stanza(origin, stanza); - --origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); - end - return true; -end - function stanza_handler(event) local origin, stanza = event.origin, event.stanza; local bare = jid_bare(stanza.attr.to); @@ -187,7 +168,15 @@ module:hook("presence/bare", stanza_handler, -1); module:hook("iq/full", stanza_handler, -1); module:hook("message/full", stanza_handler, -1); module:hook("presence/full", stanza_handler, -1); -module:hook("iq/host", handle_to_domain, -1); + +local function handle_to_domain(event) + local origin, stanza = event.origin, event.stanza; + local type = stanza.attr.type; + if type == "error" then return; end + host_room:handle_stanza(origin, stanza); + -- origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); + return true; +end module:hook("message/host", handle_to_domain, -1); module:hook("presence/host", handle_to_domain, -1); -- cgit v1.2.3 From 924356d7b75f81989d912b885436cb78c6dd3438 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Feb 2014 17:39:57 -0500 Subject: plugins/muc/muc.lib: Split out `send_history` into `parse_history` and `get_history` --- plugins/muc/muc.lib.lua | 95 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 33 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d09c768e..1e3a41e3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -9,6 +9,7 @@ local select = select; local pairs, ipairs = pairs, ipairs; +local gettime = os.time; local datetime = require "util.datetime"; local dataform = require "util.dataforms"; @@ -145,47 +146,75 @@ function room_mt:send_occupant_list(to) end end end -function room_mt:send_history(to, stanza) - local history = self._data['history']; -- send discussion history - if history then - local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc"); - local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); - local maxchars = history_tag and tonumber(history_tag.attr.maxchars); - if maxchars then maxchars = math.floor(maxchars); end +local function parse_history(stanza) + local x_tag = stanza:get_child("x", "http://jabber.org/protocol/muc"); + local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); + if not history_tag then + return nil, 20, nil + end - local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history); - if not history_tag then maxstanzas = 20; end + local maxchars = tonumber(history_tag.attr.maxchars); - local seconds = history_tag and tonumber(history_tag.attr.seconds); - if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end + local maxstanzas = tonumber(history_tag.attr.maxstanzas); - local since = history_tag and history_tag.attr.since; - if since then since = datetime.parse(since); since = since and datetime.datetime(since); end - if seconds and (not since or since < seconds) then since = seconds; end + -- messages received since the UTC datetime specified + local since = history_tag.attr.since; + if since then + since = datetime.parse(since); + end - local n = 0; - local charcount = 0; + -- messages received in the last "X" seconds. + local seconds = tonumber(history_tag.attr.seconds); + if seconds then + seconds = gettime() - seconds + if since then + since = math.max(since, seconds); + else + since = seconds; + end + end - for i=#history,1,-1 do - local entry = history[i]; - if maxchars then - if not entry.chars then - entry.stanza.attr.to = ""; - entry.chars = #tostring(entry.stanza); - end - charcount = charcount + entry.chars + #to; - if charcount > maxchars then break; end + return maxchars, maxstanzas, since +end +-- Get history for 'to' +function room_mt:get_history(to, maxchars, maxstanzas, since) + local history = self._data['history']; -- send discussion history + if not history then return end + local history_len = #history + + maxstanzas = maxstanzas or history_len + local n = 0; + local charcount = 0; + for i=history_len,1,-1 do + local entry = history[i]; + if maxchars then + if not entry.chars then + entry.stanza.attr.to = ""; + entry.chars = #tostring(entry.stanza); end - if since and since > entry.stamp then break; end - if n + 1 > maxstanzas then break; end - n = n + 1; - end - for i=#history-n+1,#history do - local msg = history[i].stanza; - msg.attr.to = to; - self:_route_stanza(msg); + charcount = charcount + entry.chars + #to; + if charcount > maxchars then break; end end + if since and since > entry.stamp then break; end + if n + 1 > maxstanzas then break; end + n = n + 1; + end + + local i = history_len-n+1 + return function() + if i > history_len then return nil end + local entry = history[i] + local msg = entry.stanza + msg.attr.to = to; + i = i + 1 + return msg + end +end +function room_mt:send_history(to, stanza) + local maxchars, maxstanzas, since = parse_history(stanza) + for msg in self:get_history(to, maxchars, maxstanzas, since) do + self:_route_stanza(msg); end end function room_mt:send_subject(to) -- cgit v1.2.3 From b99a408d8c82456bb5b83a5f9fa55a9f04f6c734 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Feb 2014 14:36:49 -0500 Subject: plugins/muc/muc.lib: Split up `handle_to_room` into smaller handlers (thanks sysko) --- plugins/muc/muc.lib.lua | 342 ++++++++++++++++++++++++++---------------------- 1 file changed, 186 insertions(+), 156 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1e3a41e3..0b82f91c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -771,187 +771,207 @@ function room_mt:destroy(newjid, reason, password) module:fire_event("muc-room-destroyed", { room = self }); end -function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc +function room_mt:handle_iq_to_room(origin, stanza) local type = stanza.attr.type; local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; - if stanza.name == "iq" then - if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_info(stanza)); - elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_items(stanza)); - elseif xmlns == "http://jabber.org/protocol/muc#admin" then - local actor = stanza.attr.from; - local affiliation = self:get_affiliation(actor); - local current_nick = self._jid_nick[actor]; - local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); - local item = stanza.tags[1].tags[1]; - if item and item.name == "item" then - if type == "set" then - local callback = function() origin.send(st.reply(stanza)); end - if item.attr.jid then -- Validate provided JID - item.attr.jid = jid_prep(item.attr.jid); - if not item.attr.jid then - origin.send(st.error_reply(stanza, "modify", "jid-malformed")); - return; - end - end - if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation - local occupant = self._occupants[self.jid.."/"..item.attr.nick]; - if occupant then item.attr.jid = occupant.jid; end - elseif not item.attr.nick and item.attr.jid then - local nick = self._jid_nick[item.attr.jid]; - if nick then item.attr.nick = select(3, jid_split(nick)); end - end - local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; - if item.attr.affiliation and item.attr.jid and not item.attr.role then - local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - elseif item.attr.role and item.attr.nick and not item.attr.affiliation then - local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); + if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then + origin.send(self:get_disco_info(stanza)); + elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then + origin.send(self:get_disco_items(stanza)); + elseif xmlns == "http://jabber.org/protocol/muc#admin" then + local actor = stanza.attr.from; + local affiliation = self:get_affiliation(actor); + local current_nick = self._jid_nick[actor]; + local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); + local item = stanza.tags[1].tags[1]; + if item and item.name == "item" then + if type == "set" then + local callback = function() origin.send(st.reply(stanza)); end + if item.attr.jid then -- Validate provided JID + item.attr.jid = jid_prep(item.attr.jid); + if not item.attr.jid then + origin.send(st.error_reply(stanza, "modify", "jid-malformed")); + return; end - elseif type == "get" then - local _aff = item.attr.affiliation; - local _rol = item.attr.role; - if _aff and not _rol then - if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for jid, affiliation in pairs(self._affiliations) do - if affiliation == _aff then - reply:tag("item", {affiliation = _aff, jid = jid}):up(); - end + end + if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation + local occupant = self._occupants[self.jid.."/"..item.attr.nick]; + if occupant then item.attr.jid = occupant.jid; end + elseif not item.attr.nick and item.attr.jid then + local nick = self._jid_nick[item.attr.jid]; + if nick then item.attr.nick = select(3, jid_split(nick)); end + end + local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; + if item.attr.affiliation and item.attr.jid and not item.attr.role then + local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + elseif item.attr.role and item.attr.nick and not item.attr.affiliation then + local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + end + elseif type == "get" then + local _aff = item.attr.affiliation; + local _rol = item.attr.role; + if _aff and not _rol then + if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for jid, affiliation in pairs(self._affiliations) do + if affiliation == _aff then + reply:tag("item", {affiliation = _aff, jid = jid}):up(); end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); end - elseif _rol and not _aff then - if role == "moderator" then - -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? - if _rol == "none" then _rol = nil; end - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for occupant_jid, occupant in pairs(self._occupants) do - if occupant.role == _rol then - reply:tag("item", { - nick = select(3, jid_split(occupant_jid)), - role = _rol or "none", - affiliation = occupant.affiliation or "none", - jid = occupant.jid - }):up(); - end + origin.send(reply); + else + origin.send(st.error_reply(stanza, "auth", "forbidden")); + end + elseif _rol and not _aff then + if role == "moderator" then + -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? + if _rol == "none" then _rol = nil; end + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for occupant_jid, occupant in pairs(self._occupants) do + if occupant.role == _rol then + reply:tag("item", { + nick = select(3, jid_split(occupant_jid)), + role = _rol or "none", + affiliation = occupant.affiliation or "none", + jid = occupant.jid + }):up(); end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); end + origin.send(reply); else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); + origin.send(st.error_reply(stanza, "auth", "forbidden")); end + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "bad-request")); end - elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then - if self:get_affiliation(stanza.attr.from) ~= "owner" then - origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); - elseif stanza.attr.type == "get" then - self:send_form(origin, stanza); - elseif stanza.attr.type == "set" then - local child = stanza.tags[1].tags[1]; - if not child then - origin.send(st.error_reply(stanza, "modify", "bad-request")); - elseif child.name == "destroy" then - local newjid = child.attr.jid; - local reason, password; - for _,tag in ipairs(child.tags) do - if tag.name == "reason" then - reason = #tag.tags == 0 and tag[1]; - elseif tag.name == "password" then - password = #tag.tags == 0 and tag[1]; - end + elseif type == "set" or type == "get" then + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + end + elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then + if self:get_affiliation(stanza.attr.from) ~= "owner" then + origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); + elseif stanza.attr.type == "get" then + self:send_form(origin, stanza); + elseif stanza.attr.type == "set" then + local child = stanza.tags[1].tags[1]; + if not child then + origin.send(st.error_reply(stanza, "modify", "bad-request")); + elseif child.name == "destroy" then + local newjid = child.attr.jid; + local reason, password; + for _,tag in ipairs(child.tags) do + if tag.name == "reason" then + reason = #tag.tags == 0 and tag[1]; + elseif tag.name == "password" then + password = #tag.tags == 0 and tag[1]; end - self:destroy(newjid, reason, password); - origin.send(st.reply(stanza)); - else - self:process_form(origin, stanza); end + self:destroy(newjid, reason, password); + origin.send(st.reply(stanza)); + else + self:process_form(origin, stanza); end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end - elseif stanza.name == "message" and type == "groupchat" then + elseif type == "set" or type == "get" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end +end + +function room_mt:handle_groupchat_to_room(origin, stanza) + local from = stanza.attr.from; + local current_nick = self._jid_nick[from]; + local occupant = self._occupants[current_nick]; + if not occupant then -- not in room + origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + elseif occupant.role == "visitor" then + origin.send(st.error_reply(stanza, "auth", "forbidden")); + else local from = stanza.attr.from; - local current_nick = self._jid_nick[from]; - local occupant = self._occupants[current_nick]; - if not occupant then -- not in room - origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); - elseif occupant.role == "visitor" then - origin.send(st.error_reply(stanza, "auth", "forbidden")); - else - local from = stanza.attr.from; - stanza.attr.from = current_nick; - local subject = stanza:get_child_text("subject"); - if subject then - if occupant.role == "moderator" or - ( self._data.changesubject and occupant.role == "participant" ) then -- and participant - self:set_subject(current_nick, subject); - else - stanza.attr.from = from; - origin.send(st.error_reply(stanza, "auth", "forbidden")); - end + stanza.attr.from = current_nick; + local subject = stanza:get_child_text("subject"); + if subject then + if occupant.role == "moderator" or + ( self._data.changesubject and occupant.role == "participant" ) then -- and participant + self:set_subject(current_nick, subject); else - self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); + stanza.attr.from = from; + origin.send(st.error_reply(stanza, "auth", "forbidden")); end - stanza.attr.from = from; + else + self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); end - elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then - local current_nick = self._jid_nick[stanza.attr.from]; - log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick + stanza.attr.from = from; + end +end + + +function room_mt:handle_kickable_to_room(origin, stanza) + local current_nick = self._jid_nick[stanza.attr.from]; + log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); + self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable +end + +-- hack - some buggy clients send presence updates to the room rather than their nick +function room_mt:handle_presence_to_room(origin, stanza) + local type = stanza.attr.type; + local current_nick = self._jid_nick[stanza.attr.from]; + if current_nick then local to = stanza.attr.to; - local current_nick = self._jid_nick[stanza.attr.from]; - if current_nick then - stanza.attr.to = current_nick; - self:handle_to_occupant(origin, stanza); - stanza.attr.to = to; - elseif type ~= "error" and type ~= "result" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + stanza.attr.to = current_nick; + self:handle_to_occupant(origin, stanza); + stanza.attr.to = to; + elseif type ~= "error" and type ~= "result" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end +end + +function room_mt:handle_invite_to_room(origin, stanza, payload) + local _from, _to = stanza.attr.from, stanza.attr.to; + local _invitee = jid_prep(payload.attr.to); + if _invitee then + local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; + local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('invite', {from=_from}) + :tag('reason'):text(_reason or ""):up() + :up(); + if self:get_password() then + invite:tag("password"):text(self:get_password()):up(); + end + invite:up() + :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this + :text(_reason or "") + :up() + :tag('body') -- Add a plain message for clients which don't support invites + :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) + :up(); + if self:get_members_only() and not self:get_affiliation(_invitee) then + log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); + self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) end - elseif stanza.name == "message" and not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 + self:_route_stanza(invite); + else + origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); + end +end + +function room_mt:handle_message_to_room(origin, stanza) + local type = stanza.attr.type; + if type == "groupchat" then + return self:handle_groupchat_to_room(origin, stanza) + elseif type == "error" and is_kickable_error(stanza) then + return self:handle_kickable_to_room(origin, stanza) + elseif not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then local x = stanza.tags[1]; local payload = (#x.tags == 1 and x.tags[1]); if payload and payload.name == "invite" and payload.attr.to then - local _from, _to = stanza.attr.from, stanza.attr.to; - local _invitee = jid_prep(payload.attr.to); - if _invitee then - local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; - local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) - :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) - :tag('invite', {from=_from}) - :tag('reason'):text(_reason or ""):up() - :up(); - if self:get_password() then - invite:tag("password"):text(self:get_password()):up(); - end - invite:up() - :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this - :text(_reason or "") - :up() - :tag('body') -- Add a plain message for clients which don't support invites - :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) - :up(); - if self:get_members_only() and not self:get_affiliation(_invitee) then - log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); - self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) - end - self:_route_stanza(invite); - else - origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); - end + return self:handle_invite_to_room(origin, stanza, payload) else origin.send(st.error_reply(stanza, "cancel", "bad-request")); end @@ -961,6 +981,16 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha end end +function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc + if stanza.name == "iq" then + return self:handle_iq_to_room(origin, stanza) + elseif stanza.name == "message" then + return self:handle_message_to_room(origin, stanza) + elseif stanza.name == "presence" then + return self:handle_presence_to_room(origin, stanza) + end +end + function room_mt:handle_stanza(origin, stanza) local to_node, to_host, to_resource = jid_split(stanza.attr.to); if to_resource then -- cgit v1.2.3 From 5e9efd46c2253d72bdd3abf8bc031c7a6422c8c9 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Feb 2014 16:50:18 -0500 Subject: plugins/muc/muc.lib: Factor `handle_to_occupant` out into many functions --- plugins/muc/muc.lib.lua | 455 ++++++++++++++++++++++++++++-------------------- 1 file changed, 269 insertions(+), 186 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 0b82f91c..7cf857a4 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -404,212 +404,295 @@ local function deconstruct_stanza_id(room, stanza) end end +function room_mt:handle_presence_error_to_occupant(origin, stanza) + local current_nick = self._jid_nick[stanza.attr.from]; + if not current_nick then + return true -- discard + end + log("debug", "kicking %s from %s", current_nick, self.jid); + return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)) +end -function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc +function room_mt:handle_unavailable_to_occupant(origin, stanza) + local from = stanza.attr.from; + local current_nick = self._jid_nick[from]; + if not current_nick then + return true; -- discard + end + local pr = get_filtered_presence(stanza); + pr.attr.from = current_nick; + log("debug", "%s leaving %s", current_nick, self.jid); + self._jid_nick[from] = nil; + local occupant = self._occupants[current_nick]; + local new_jid = next(occupant.sessions); + if new_jid == from then new_jid = next(occupant.sessions, new_jid); end + if new_jid then + local jid = occupant.jid; + occupant.jid = new_jid; + occupant.sessions[from] = nil; + pr.attr.to = from; + pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up() + :tag("status", {code='110'}):up(); + self:_route_stanza(pr); + if jid ~= new_jid then + pr = st.clone(occupant.sessions[new_jid]) + :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"}); + pr.attr.from = current_nick; + self:broadcast_except_nick(pr, current_nick); + end + else + occupant.role = 'none'; + self:broadcast_presence(pr, from); + self._occupants[current_nick] = nil; + end + return true; +end + +function room_mt:handle_occupant_presence(origin, stanza) + local from = stanza.attr.from; + local pr = get_filtered_presence(stanza); + local current_nick = stanza.attr.to + pr.attr.from = current_nick; + log("debug", "%s broadcasted presence", current_nick); + self._occupants[current_nick].sessions[from] = pr; + self:broadcast_presence(pr, from); + return true; +end + +function room_mt:handle_change_nick(origin, stanza, current_nick, to) + local from = stanza.attr.from; + local occupant = self._occupants[current_nick]; + local is_multisession = next(occupant.sessions, next(occupant.sessions)); + if self._occupants[to] or is_multisession then + log("debug", "%s couldn't change nick", current_nick); + local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + reply.tags[1].attr.code = "409"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + else + local data = self._occupants[current_nick]; + local to_nick = select(3, jid_split(to)); + log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); + local p = st.presence({type='unavailable', from=current_nick}); + self:broadcast_presence(p, from, '303', to_nick); + self._occupants[current_nick] = nil; + self._occupants[to] = data; + self._jid_nick[from] = to; + local pr = get_filtered_presence(stanza); + pr.attr.from = to; + self._occupants[to].sessions[from] = pr; + self:broadcast_presence(pr, from); + return true; + end +end + +function room_mt:handle_join(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + log("debug", "%s joining as %s", from, to); + if not next(self._affiliations) then -- new room, no owners + self._affiliations[jid_bare(from)] = "owner"; + if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then + self.locked = nil; -- Older groupchat protocol doesn't lock + end + elseif self.locked then -- Deny entry + origin.send(st.error_reply(stanza, "cancel", "item-not-found")); + return true; + end + local affiliation = self:get_affiliation(from); + local role = self:get_default_role(affiliation) + if role then -- new occupant + local is_merge = not not self._occupants[to] + if not is_merge then + self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}}; + else + self._occupants[to].sessions[from] = get_filtered_presence(stanza); + end + self._jid_nick[from] = to; + self:send_occupant_list(from); + local pr = get_filtered_presence(stanza); + pr.attr.from = to; + pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up(); + if not is_merge then + self:broadcast_except_nick(pr, to); + end + pr:tag("status", {code='110'}):up(); + if self._data.whois == 'anyone' then + pr:tag("status", {code='100'}):up(); + end + if self.locked then + pr:tag("status", {code='201'}):up(); + end + pr.attr.to = from; + self:_route_stanza(pr); + self:send_history(from, stanza); + self:send_subject(from); + return true; + elseif not affiliation then -- registration required for entering members-only room + local reply = st.error_reply(stanza, "auth", "registration-required"):up(); + reply.tags[1].attr.code = "407"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + else -- banned + local reply = st.error_reply(stanza, "auth", "forbidden"):up(); + reply.tags[1].attr.code = "403"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end +end + +function room_mt:handle_available_to_occupant(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + local current_nick = self._jid_nick[from]; + if current_nick then + --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence + if current_nick == to then -- simple presence + return self:handle_occupant_presence(origin, stanza) + else -- change nick + return self:handle_change_nick(origin, stanza, current_nick, to) + end + --else -- possible rejoin + -- log("debug", "%s had connection replaced", current_nick); + -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}) + -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable + -- self:handle_to_occupant(origin, stanza); -- resend available + --end + else -- enter room + local new_nick = to; + if self._occupants[to] then + if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then + new_nick = nil; + end + end + local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); + password = password and password:get_child("password", "http://jabber.org/protocol/muc"); + password = password and password[1] ~= "" and password[1]; + if self:get_password() and self:get_password() ~= password then + log("debug", "%s couldn't join due to invalid password: %s", from, to); + local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); + reply.tags[1].attr.code = "401"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + elseif not new_nick then + log("debug", "%s couldn't join due to nick conflict: %s", from, to); + local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + reply.tags[1].attr.code = "409"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + else + return self:handle_join(origin, stanza) + end + end +end + +function room_mt:handle_presence_to_occupant(origin, stanza) + local type = stanza.attr.type; + if type == "error" then -- error, kick em out! + return self:handle_presence_error_to_occupant(origin, stanza) + elseif type == "unavailable" then -- unavailable + return self:handle_unavailable_to_occupant(origin, stanza) + elseif not type then -- available + return self:handle_available_to_occupant(origin, stanza) + elseif type ~= 'result' then -- bad type + if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences + origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? + end + end + return true; +end + +function room_mt:handle_private(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local room = jid_bare(to); local current_nick = self._jid_nick[from]; local type = stanza.attr.type; - log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); - if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end - if stanza.name == "presence" then - local pr = get_filtered_presence(stanza); - pr.attr.from = current_nick; - if type == "error" then -- error, kick em out! - if current_nick then - log("debug", "kicking %s from %s", current_nick, room); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); + local o_data = self._occupants[to]; + if o_data then + log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); + if stanza.name == "iq" then + local id = stanza.attr.id; + if stanza.attr.type == "get" or stanza.attr.type == "set" then + stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); + else + stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); end - elseif type == "unavailable" then -- unavailable - if current_nick then - log("debug", "%s leaving %s", current_nick, room); - self._jid_nick[from] = nil; - local occupant = self._occupants[current_nick]; - local new_jid = next(occupant.sessions); - if new_jid == from then new_jid = next(occupant.sessions, new_jid); end - if new_jid then - local jid = occupant.jid; - occupant.jid = new_jid; - occupant.sessions[from] = nil; - pr.attr.to = from; - pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up() - :tag("status", {code='110'}):up(); - self:_route_stanza(pr); - if jid ~= new_jid then - pr = st.clone(occupant.sessions[new_jid]) - :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"}); - pr.attr.from = current_nick; - self:broadcast_except_nick(pr, current_nick); - end - else - occupant.role = 'none'; - self:broadcast_presence(pr, from); - self._occupants[current_nick] = nil; - end + if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then + stanza.attr.to = jid_bare(stanza.attr.to); end - elseif not type then -- available - if current_nick then - --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence - if current_nick == to then -- simple presence - log("debug", "%s broadcasted presence", current_nick); - self._occupants[current_nick].sessions[from] = pr; - self:broadcast_presence(pr, from); - else -- change nick - local occupant = self._occupants[current_nick]; - local is_multisession = next(occupant.sessions, next(occupant.sessions)); - if self._occupants[to] or is_multisession then - log("debug", "%s couldn't change nick", current_nick); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); - reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - else - local data = self._occupants[current_nick]; - local to_nick = select(3, jid_split(to)); - if to_nick then - log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); - local p = st.presence({type='unavailable', from=current_nick}); - self:broadcast_presence(p, from, '303', to_nick); - self._occupants[current_nick] = nil; - self._occupants[to] = data; - self._jid_nick[from] = to; - pr.attr.from = to; - self._occupants[to].sessions[from] = pr; - self:broadcast_presence(pr, from); - else - --TODO malformed-jid - end - end - end - --else -- possible rejoin - -- log("debug", "%s had connection replaced", current_nick); - -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}) - -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable - -- self:handle_to_occupant(origin, stanza); -- resend available - --end - else -- enter room - local new_nick = to; - local is_merge; - if self._occupants[to] then - if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then - new_nick = nil; - end - is_merge = true; - end - local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); - password = password and password:get_child("password", "http://jabber.org/protocol/muc"); - password = password and password[1] ~= "" and password[1]; - if self:get_password() and self:get_password() ~= password then - log("debug", "%s couldn't join due to invalid password: %s", from, to); - local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); - reply.tags[1].attr.code = "401"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - elseif not new_nick then - log("debug", "%s couldn't join due to nick conflict: %s", from, to); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); - reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - else - log("debug", "%s joining as %s", from, to); - if not next(self._affiliations) then -- new room, no owners - self._affiliations[jid_bare(from)] = "owner"; - if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then - self.locked = nil; -- Older groupchat protocol doesn't lock - end - elseif self.locked then -- Deny entry - origin.send(st.error_reply(stanza, "cancel", "item-not-found")); - return; - end - local affiliation = self:get_affiliation(from); - local role = self:get_default_role(affiliation) - if role then -- new occupant - if not is_merge then - self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}}; - else - self._occupants[to].sessions[from] = get_filtered_presence(stanza); - end - self._jid_nick[from] = to; - self:send_occupant_list(from); - pr.attr.from = to; - pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up(); - if not is_merge then - self:broadcast_except_nick(pr, to); - end - pr:tag("status", {code='110'}):up(); - if self._data.whois == 'anyone' then - pr:tag("status", {code='100'}):up(); - end - if self.locked then - pr:tag("status", {code='201'}):up(); - end - pr.attr.to = from; - self:_route_stanza(pr); - self:send_history(from, stanza); - self:send_subject(from); - elseif not affiliation then -- registration required for entering members-only room - local reply = st.error_reply(stanza, "auth", "registration-required"):up(); - reply.tags[1].attr.code = "407"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - else -- banned - local reply = st.error_reply(stanza, "auth", "forbidden"):up(); - reply.tags[1].attr.code = "403"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - end - end + if stanza.attr.id then + self:_route_stanza(stanza); end - elseif type ~= 'result' then -- bad type - if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences - origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? + stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; + else -- message + stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); + stanza.attr.from = current_nick; + for jid in pairs(o_data.sessions) do + stanza.attr.to = jid; + self:_route_stanza(stanza); end + stanza.attr.from, stanza.attr.to = from, to; end - elseif not current_nick then -- not in room - if (type == "error" or type == "result") and stanza.name == "iq" then + elseif type ~= "error" and type ~= "result" then -- recipient not in room + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + end + return true; +end + +function room_mt:handle_iq_to_occupant(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + local current_nick = self._jid_nick[from]; + if not current_nick then + local type = stanza.attr.type; + if (type == "error" or type == "result") then local id = stanza.attr.id; stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); if stanza.attr.id then self:_route_stanza(stanza); end stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - elseif type ~= "error" then + else origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); end - elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM + return true; + else + return self:handle_private(origin, stanza) + end +end + +function room_mt:handle_message_to_occupant(origin, stanza) + local current_nick = self._jid_nick[stanza.attr.from]; + local type = stanza.attr.type; + if not current_nick then -- not in room + if type ~= "error" then + origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + end + return true; + end + if type == "groupchat" then -- groupchat messages not allowed in PM origin.send(st.error_reply(stanza, "modify", "bad-request")); - elseif current_nick and stanza.name == "message" and type == "error" and is_kickable_error(stanza) then + return true; + elseif type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable + return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable else -- private stanza - local o_data = self._occupants[to]; - if o_data then - log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); - if stanza.name == "iq" then - local id = stanza.attr.id; - if stanza.attr.type == "get" or stanza.attr.type == "set" then - stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); - else - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - end - if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then - stanza.attr.to = jid_bare(stanza.attr.to); - end - if stanza.attr.id then - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - else -- message - stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); - stanza.attr.from = current_nick; - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to = from, to; - end - elseif type ~= "error" and type ~= "result" then -- recipient not in room - origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); - end + return self:handle_private(origin, stanza) + end +end + +function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc + local from, to = stanza.attr.from, stanza.attr.to; + local room = jid_bare(to); + local current_nick = self._jid_nick[from]; + local type = stanza.attr.type; + log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); + if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end + if stanza.name == "presence" then + return self:handle_presence_to_occupant(origin, stanza) + elseif stanza.name == "iq" then + return self:handle_iq_to_occupant(origin, stanza) + elseif stanza.name == "message" then + return self:handle_message_to_occupant(origin, stanza) end end -- cgit v1.2.3 From 3c862fc4dd06cb143f8248026e4e1a035ee6b40b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 15:48:26 -0500 Subject: plugins/muc/muc.lib: Split out the room iq handler into functions --- plugins/muc/muc.lib.lua | 216 +++++++++++++++++++++++++++++------------------- 1 file changed, 129 insertions(+), 87 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7cf857a4..96caa2eb 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -854,114 +854,156 @@ function room_mt:destroy(newjid, reason, password) module:fire_event("muc-room-destroyed", { room = self }); end +function room_mt:handle_admin_item_set_command(origin, stanza) + local item = stanza.tags[1].tags[1]; + if item.attr.jid then -- Validate provided JID + item.attr.jid = jid_prep(item.attr.jid); + if not item.attr.jid then + origin.send(st.error_reply(stanza, "modify", "jid-malformed")); + return true; + end + end + if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation + local occupant = self._occupants[self.jid.."/"..item.attr.nick]; + if occupant then item.attr.jid = occupant.jid; end + elseif not item.attr.nick and item.attr.jid then + local nick = self._jid_nick[item.attr.jid]; + if nick then item.attr.nick = select(3, jid_split(nick)); end + end + local actor = stanza.attr.from; + local callback = function() origin.send(st.reply(stanza)); end + local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; + if item.attr.affiliation and item.attr.jid and not item.attr.role then + local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + return true; + elseif item.attr.role and item.attr.nick and not item.attr.affiliation then + local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + return true; + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; + end +end + +function room_mt:handle_admin_item_get_command(origin, stanza) + local actor = stanza.attr.from; + local affiliation = self:get_affiliation(actor); + local current_nick = self._jid_nick[actor]; + local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); + local item = stanza.tags[1].tags[1]; + local _aff = item.attr.affiliation; + local _rol = item.attr.role; + if _aff and not _rol then + if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for jid, affiliation in pairs(self._affiliations) do + if affiliation == _aff then + reply:tag("item", {affiliation = _aff, jid = jid}):up(); + end + end + origin.send(reply); + return true; + else + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end + elseif _rol and not _aff then + if role == "moderator" then + -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? + if _rol == "none" then _rol = nil; end + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for occupant_jid, occupant in pairs(self._occupants) do + if occupant.role == _rol then + reply:tag("item", { + nick = select(3, jid_split(occupant_jid)), + role = _rol or "none", + affiliation = occupant.affiliation or "none", + jid = occupant.jid + }):up(); + end + end + origin.send(reply); + return true; + else + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; + end +end + +function room_mt:handle_owner_query_get_to_room(origin, stanza) + if self:get_affiliation(stanza.attr.from) ~= "owner" then + origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); + return true; + end + + self:send_form(origin, stanza); + return true; +end +function room_mt:handle_owner_query_set_to_room(origin, stanza) + if self:get_affiliation(stanza.attr.from) ~= "owner" then + origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); + return true; + end + + local child = stanza.tags[1].tags[1]; + if not child then + origin.send(st.error_reply(stanza, "modify", "bad-request")); + return true; + elseif child.name == "destroy" then + local newjid = child.attr.jid; + local reason, password; + for _,tag in ipairs(child.tags) do + if tag.name == "reason" then + reason = #tag.tags == 0 and tag[1]; + elseif tag.name == "password" then + password = #tag.tags == 0 and tag[1]; + end + end + self:destroy(newjid, reason, password); + origin.send(st.reply(stanza)); + return true; + else + self:process_form(origin, stanza); + return true; + end +end + function room_mt:handle_iq_to_room(origin, stanza) local type = stanza.attr.type; local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then origin.send(self:get_disco_info(stanza)); + return true; elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then origin.send(self:get_disco_items(stanza)); + return true; elseif xmlns == "http://jabber.org/protocol/muc#admin" then - local actor = stanza.attr.from; - local affiliation = self:get_affiliation(actor); - local current_nick = self._jid_nick[actor]; - local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); local item = stanza.tags[1].tags[1]; if item and item.name == "item" then if type == "set" then - local callback = function() origin.send(st.reply(stanza)); end - if item.attr.jid then -- Validate provided JID - item.attr.jid = jid_prep(item.attr.jid); - if not item.attr.jid then - origin.send(st.error_reply(stanza, "modify", "jid-malformed")); - return; - end - end - if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation - local occupant = self._occupants[self.jid.."/"..item.attr.nick]; - if occupant then item.attr.jid = occupant.jid; end - elseif not item.attr.nick and item.attr.jid then - local nick = self._jid_nick[item.attr.jid]; - if nick then item.attr.nick = select(3, jid_split(nick)); end - end - local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; - if item.attr.affiliation and item.attr.jid and not item.attr.role then - local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - elseif item.attr.role and item.attr.nick and not item.attr.affiliation then - local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); - end + return self:handle_admin_item_set_command(origin, stanza) elseif type == "get" then - local _aff = item.attr.affiliation; - local _rol = item.attr.role; - if _aff and not _rol then - if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for jid, affiliation in pairs(self._affiliations) do - if affiliation == _aff then - reply:tag("item", {affiliation = _aff, jid = jid}):up(); - end - end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); - end - elseif _rol and not _aff then - if role == "moderator" then - -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? - if _rol == "none" then _rol = nil; end - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for occupant_jid, occupant in pairs(self._occupants) do - if occupant.role == _rol then - reply:tag("item", { - nick = select(3, jid_split(occupant_jid)), - role = _rol or "none", - affiliation = occupant.affiliation or "none", - jid = occupant.jid - }):up(); - end - end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); - end - else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); - end + return self:handle_admin_item_get_command(origin, stanza) end elseif type == "set" or type == "get" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; end elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then - if self:get_affiliation(stanza.attr.from) ~= "owner" then - origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); - elseif stanza.attr.type == "get" then - self:send_form(origin, stanza); + if stanza.attr.type == "get" then + return self:handle_owner_query_get_to_room(origin, stanza) elseif stanza.attr.type == "set" then - local child = stanza.tags[1].tags[1]; - if not child then - origin.send(st.error_reply(stanza, "modify", "bad-request")); - elseif child.name == "destroy" then - local newjid = child.attr.jid; - local reason, password; - for _,tag in ipairs(child.tags) do - if tag.name == "reason" then - reason = #tag.tags == 0 and tag[1]; - elseif tag.name == "password" then - password = #tag.tags == 0 and tag[1]; - end - end - self:destroy(newjid, reason, password); - origin.send(st.reply(stanza)); - else - self:process_form(origin, stanza); - end + return self:handle_owner_query_set_to_room(origin, stanza) end elseif type == "set" or type == "get" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + return true; end end -- cgit v1.2.3 From 0a47966cb74d8048921a5dbb8a15a8597e9eb4b4 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 16:30:43 -0500 Subject: plugins/muc/muc.lib: Refactor _to_occupant handlers --- plugins/muc/muc.lib.lua | 99 +++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 96caa2eb..54511ede 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -603,65 +603,45 @@ function room_mt:handle_presence_to_occupant(origin, stanza) return true; end -function room_mt:handle_private(origin, stanza) +function room_mt:handle_iq_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; local type = stanza.attr.type; - local o_data = self._occupants[to]; - if o_data then - log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); - if stanza.name == "iq" then - local id = stanza.attr.id; - if stanza.attr.type == "get" or stanza.attr.type == "set" then - stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); - else - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - end - if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then - stanza.attr.to = jid_bare(stanza.attr.to); - end - if stanza.attr.id then - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - else -- message - stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); - stanza.attr.from = current_nick; - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to = from, to; + local id = stanza.attr.id; + if (type == "error" or type == "result") then + stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); + log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); + if stanza.attr.id then + self:_route_stanza(stanza); end - elseif type ~= "error" and type ~= "result" then -- recipient not in room - origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); - end - return true; -end - -function room_mt:handle_iq_to_occupant(origin, stanza) - local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; - if not current_nick then - local type = stanza.attr.type; - if (type == "error" or type == "result") then - local id = stanza.attr.id; - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - if stanza.attr.id then - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - else + stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; + return true; + else -- Type is "get" or "set" + local current_nick = self._jid_nick[from]; + if not current_nick then origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + return true; + end + local o_data = self._occupants[to]; + if not o_data then -- recipient not in room + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + return true; + end + stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); + log("debug", "%s sent private iq stanza to %s (%s)", from, to, o_data.jid); + if stanza.tags[1].attr.xmlns == 'vcard-temp' then + stanza.attr.to = jid_bare(stanza.attr.to); end + if stanza.attr.id then + self:_route_stanza(stanza); + end + stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; - else - return self:handle_private(origin, stanza) end end function room_mt:handle_message_to_occupant(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; + local from, to = stanza.attr.from, stanza.attr.to; + local current_nick = self._jid_nick[from]; local type = stanza.attr.type; if not current_nick then -- not in room if type ~= "error" then @@ -674,17 +654,30 @@ function room_mt:handle_message_to_occupant(origin, stanza) return true; elseif type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - else -- private stanza - return self:handle_private(origin, stanza) + self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable + return true; end + + local o_data = self._occupants[to]; + if not o_data then + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + return true; + end + log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid); + stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); + stanza.attr.from = current_nick; + for jid in pairs(o_data.sessions) do + stanza.attr.to = jid; + self:_route_stanza(stanza); + end + stanza.attr.from, stanza.attr.to = from, to; + return true; end function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc local from, to = stanza.attr.from, stanza.attr.to; local room = jid_bare(to); local current_nick = self._jid_nick[from]; - local type = stanza.attr.type; log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end if stanza.name == "presence" then -- cgit v1.2.3 From 3b0319ff5f5542815943f265ab30079b1f32f8b4 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 17:17:01 -0500 Subject: plugins/muc/muc.lib: Move (de)construct_stanza_id into `handle_iq_to_occupant` It is the only place they were used; and I left the old function names in as comments. One reason for doing this was to reduce accesses to _occupants; which may be in a database in future revisions --- plugins/muc/muc.lib.lua | 57 +++++++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 54511ede..ce4fa923 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -380,30 +380,6 @@ function room_mt:get_whois() return self._data.whois; end -local function construct_stanza_id(room, stanza) - local from_jid, to_nick = stanza.attr.from, stanza.attr.to; - local from_nick = room._jid_nick[from_jid]; - local occupant = room._occupants[to_nick]; - local to_jid = occupant.jid; - - return from_nick, to_jid, base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from_jid)); -end -local function deconstruct_stanza_id(room, stanza) - local from_jid_possiblybare, to_nick = stanza.attr.from, stanza.attr.to; - local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); - local from_nick = room._jid_nick[from_jid]; - - if not(from_nick) then return; end - if not(from_jid_possiblybare == from_jid or from_jid_possiblybare == jid_bare(from_jid)) then return; end - - local occupant = room._occupants[to_nick]; - for to_jid in pairs(occupant and occupant.sessions or {}) do - if md5(to_jid) == to_jid_hash then - return from_nick, to_jid, id; - end - end -end - function room_mt:handle_presence_error_to_occupant(origin, stanza) local current_nick = self._jid_nick[stanza.attr.from]; if not current_nick then @@ -607,33 +583,45 @@ function room_mt:handle_iq_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; local type = stanza.attr.type; local id = stanza.attr.id; + local current_nick = self._jid_nick[from]; + local o_data = self._occupants[to]; if (type == "error" or type == "result") then - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); - if stanza.attr.id then - self:_route_stanza(stanza); + do -- deconstruct_stanza_id + if not current_nick or not o_data then return nil; end + local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); + if not(from == from_jid or from == jid_bare(from_jid)) then return nil; end + local session_jid + for to_jid in pairs(o_data.sessions) do + if md5(to_jid) == to_jid_hash then + session_jid = to_jid; + break; + end + end + if session_jid == nil then return nil; end + stanza.attr.from, stanza.attr.to, stanza.attr.id = current_nick, session_jid, id end + log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); + self:_route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; else -- Type is "get" or "set" - local current_nick = self._jid_nick[from]; if not current_nick then origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); return true; end - local o_data = self._occupants[to]; if not o_data then -- recipient not in room origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); return true; end - stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); + do -- construct_stanza_id + stanza.attr.id = base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from)); + end + stanza.attr.from, stanza.attr.to = current_nick, o_data.jid; log("debug", "%s sent private iq stanza to %s (%s)", from, to, o_data.jid); if stanza.tags[1].attr.xmlns == 'vcard-temp' then stanza.attr.to = jid_bare(stanza.attr.to); end - if stanza.attr.id then - self:_route_stanza(stanza); - end + self:_route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; end @@ -1027,7 +1015,6 @@ function room_mt:handle_groupchat_to_room(origin, stanza) end end - function room_mt:handle_kickable_to_room(origin, stanza) local current_nick = self._jid_nick[stanza.attr.from]; log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); -- cgit v1.2.3 From 79c28ceeabf5e5b471796f59193007f8ec6e691c Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 17:40:16 -0500 Subject: plugins/muc/muc.lib: Add some missing return values --- plugins/muc/muc.lib.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ce4fa923..a5a87646 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -994,8 +994,10 @@ function room_mt:handle_groupchat_to_room(origin, stanza) local occupant = self._occupants[current_nick]; if not occupant then -- not in room origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + return true; elseif occupant.role == "visitor" then origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; else local from = stanza.attr.from; stanza.attr.from = current_nick; @@ -1012,6 +1014,7 @@ function room_mt:handle_groupchat_to_room(origin, stanza) self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); end stanza.attr.from = from; + return true; end end @@ -1019,6 +1022,7 @@ function room_mt:handle_kickable_to_room(origin, stanza) local current_nick = self._jid_nick[stanza.attr.from]; log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable + return true; end -- hack - some buggy clients send presence updates to the room rather than their nick @@ -1030,8 +1034,10 @@ function room_mt:handle_presence_to_room(origin, stanza) stanza.attr.to = current_nick; self:handle_to_occupant(origin, stanza); stanza.attr.to = to; + return true; elseif type ~= "error" and type ~= "result" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + return true; end end @@ -1060,8 +1066,10 @@ function room_mt:handle_invite_to_room(origin, stanza, payload) self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) end self:_route_stanza(invite); + return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); + return true; end end @@ -1079,10 +1087,12 @@ function room_mt:handle_message_to_room(origin, stanza) return self:handle_invite_to_room(origin, stanza, payload) else origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; end else if type == "error" or type == "result" then return; end origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + return true; end end -- cgit v1.2.3 From 59baf666116be90aa25f7560292be076a519b121 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 17:52:40 -0500 Subject: plugins/muc/muc.lib: Make use of return values to send service-unavailable errors --- plugins/muc/muc.lib.lua | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index a5a87646..6131ca78 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -982,9 +982,8 @@ function room_mt:handle_iq_to_room(origin, stanza) elseif stanza.attr.type == "set" then return self:handle_owner_query_set_to_room(origin, stanza) end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - return true; + else + return nil; end end @@ -1035,9 +1034,8 @@ function room_mt:handle_presence_to_room(origin, stanza) self:handle_to_occupant(origin, stanza); stanza.attr.to = to; return true; - elseif type ~= "error" and type ~= "result" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - return true; + else + return nil; end end @@ -1090,9 +1088,7 @@ function room_mt:handle_message_to_room(origin, stanza) return true; end else - if type == "error" or type == "result" then return; end - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - return true; + return nil; end end @@ -1108,10 +1104,18 @@ end function room_mt:handle_stanza(origin, stanza) local to_node, to_host, to_resource = jid_split(stanza.attr.to); + local handled if to_resource then - self:handle_to_occupant(origin, stanza); + handled = self:handle_to_occupant(origin, stanza); else - self:handle_to_room(origin, stanza); + handled = self:handle_to_room(origin, stanza); + end + + if not handled then + local type = stanza.attr.type + if stanza.name ~= "iq" or type == "get" or type == "set" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end end end -- cgit v1.2.3 From 25fc86d5f19e1f51cb55196a594033ea3d11d18b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 18:04:38 -0500 Subject: plugins/muc/muc.lib: Move all kick code into one place --- plugins/muc/muc.lib.lua | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6131ca78..39240b16 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -259,14 +259,16 @@ function room_mt:set_subject(current_nick, subject) return true; end -local function build_unavailable_presence_from_error(stanza) +function room_mt:handle_kickable(origin, stanza) local type, condition, text = stanza:get_error(); local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error"); if text then error_message = error_message..": "..text; end - return st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) + local kick_stanza = st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) :tag('status'):text(error_message); + self:handle_unavailable_to_occupant(origin, kick_stanza); -- send unavailable + return true; end function room_mt:set_name(name) @@ -380,15 +382,6 @@ function room_mt:get_whois() return self._data.whois; end -function room_mt:handle_presence_error_to_occupant(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; - if not current_nick then - return true -- discard - end - log("debug", "kicking %s from %s", current_nick, self.jid); - return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)) -end - function room_mt:handle_unavailable_to_occupant(origin, stanza) local from = stanza.attr.from; local current_nick = self._jid_nick[from]; @@ -566,7 +559,7 @@ end function room_mt:handle_presence_to_occupant(origin, stanza) local type = stanza.attr.type; if type == "error" then -- error, kick em out! - return self:handle_presence_error_to_occupant(origin, stanza) + return self:handle_kickable(origin, stanza) elseif type == "unavailable" then -- unavailable return self:handle_unavailable_to_occupant(origin, stanza) elseif not type then -- available @@ -642,8 +635,7 @@ function room_mt:handle_message_to_occupant(origin, stanza) return true; elseif type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - return true; + return self:handle_kickable(origin, stanza); -- send unavailable end local o_data = self._occupants[to]; @@ -1017,26 +1009,17 @@ function room_mt:handle_groupchat_to_room(origin, stanza) end end -function room_mt:handle_kickable_to_room(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; - log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - return true; -end - -- hack - some buggy clients send presence updates to the room rather than their nick function room_mt:handle_presence_to_room(origin, stanza) - local type = stanza.attr.type; local current_nick = self._jid_nick[stanza.attr.from]; + local handled if current_nick then local to = stanza.attr.to; stanza.attr.to = current_nick; - self:handle_to_occupant(origin, stanza); + handled = self:handle_presence_to_occupant(origin, stanza); stanza.attr.to = to; - return true; - else - return nil; end + return handled; end function room_mt:handle_invite_to_room(origin, stanza, payload) @@ -1076,7 +1059,7 @@ function room_mt:handle_message_to_room(origin, stanza) if type == "groupchat" then return self:handle_groupchat_to_room(origin, stanza) elseif type == "error" and is_kickable_error(stanza) then - return self:handle_kickable_to_room(origin, stanza) + return self:handle_kickable(origin, stanza) elseif not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then local x = stanza.tags[1]; -- cgit v1.2.3 From 957d62d3e4127f4e9ac3e3cd4491e9fdc78e6d75 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 19:06:33 -0500 Subject: plugins/muc/muc.lib: Add disco iq handlers with compatible argument signature --- plugins/muc/muc.lib.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 39240b16..081e4c6d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -827,6 +827,16 @@ function room_mt:destroy(newjid, reason, password) module:fire_event("muc-room-destroyed", { room = self }); end +function room_mt:handle_disco_info_get_query(origin, stanza) + origin.send(self:get_disco_info(stanza)); + return true; +end + +function room_mt:handle_disco_items_get_query(origin, stanza) + origin.send(self:get_disco_items(stanza)); + return true; +end + function room_mt:handle_admin_item_set_command(origin, stanza) local item = stanza.tags[1].tags[1]; if item.attr.jid then -- Validate provided JID @@ -951,11 +961,9 @@ function room_mt:handle_iq_to_room(origin, stanza) local type = stanza.attr.type; local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_info(stanza)); - return true; + return self:handle_disco_info_get_query(origin, stanza) elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_items(stanza)); - return true; + return self:handle_disco_items_get_query(origin, stanza) elseif xmlns == "http://jabber.org/protocol/muc#admin" then local item = stanza.tags[1].tags[1]; if item and item.name == "item" then -- cgit v1.2.3 From 0eace14ce404db740379a76011e110e2be8c0662 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 14:54:41 -0400 Subject: plugins/muc/muc: Add copyright for daurnimator --- plugins/muc/muc.lib.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 081e4c6d..8daa4b9f 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1,6 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- cgit v1.2.3 From 6f7f21479ad928d2b94d497834592c15260cfcf5 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 14:56:20 -0400 Subject: plugins/muc/muc: Check for mediated invites in a smarter way --- plugins/muc/muc.lib.lua | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 8daa4b9f..8a8680c5 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1033,6 +1033,10 @@ end function room_mt:handle_invite_to_room(origin, stanza, payload) local _from, _to = stanza.attr.from, stanza.attr.to; + if not self._jid_nick[_from] then -- Should be in room to send invite + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end local _invitee = jid_prep(payload.attr.to); if _invitee then local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; @@ -1069,13 +1073,13 @@ function room_mt:handle_message_to_room(origin, stanza) return self:handle_groupchat_to_room(origin, stanza) elseif type == "error" and is_kickable_error(stanza) then return self:handle_kickable(origin, stanza) - elseif not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 - and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then - local x = stanza.tags[1]; - local payload = (#x.tags == 1 and x.tags[1]); - if payload and payload.name == "invite" and payload.attr.to then - return self:handle_invite_to_room(origin, stanza, payload) - else + elseif type == nil then + local x = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); + if x then + local payload = x.tags[1]; + if payload and payload.name == "invite" and payload.attr.to then + return self:handle_invite_to_room(origin, stanza, payload) + end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; end -- cgit v1.2.3 From a37f0e8d2a4f0e37be89ad46eaf5b4a3223776d3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 15:15:14 -0400 Subject: plugins/muc/muc: Rename `handle_invite_to_room` to `handle_mediated_invite`; clean up logic --- plugins/muc/muc.lib.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 8a8680c5..9b54fd2b 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1031,15 +1031,15 @@ function room_mt:handle_presence_to_room(origin, stanza) return handled; end -function room_mt:handle_invite_to_room(origin, stanza, payload) +function room_mt:handle_mediated_invite(origin, stanza, payload) local _from, _to = stanza.attr.from, stanza.attr.to; - if not self._jid_nick[_from] then -- Should be in room to send invite + if not self._jid_nick[_from] then -- Should be in room to send invite TODO: allow admins to send at any time origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end local _invitee = jid_prep(payload.attr.to); if _invitee then - local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; + local _reason = payload:get_child_text("reason") local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) :tag('invite', {from=_from}) @@ -1077,8 +1077,10 @@ function room_mt:handle_message_to_room(origin, stanza) local x = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); if x then local payload = x.tags[1]; - if payload and payload.name == "invite" and payload.attr.to then - return self:handle_invite_to_room(origin, stanza, payload) + if payload == nil then + -- fallthrough + elseif payload.name == "invite" and payload.attr.to then + return self:handle_mediated_invite(origin, stanza, payload) end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; -- cgit v1.2.3 From f360fb88d220a8d745a53c102df5addb27e980cf Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 15:15:28 -0400 Subject: plugins/muc/muc: Support mediated declines --- plugins/muc/muc.lib.lua | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9b54fd2b..0c87574a 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1067,6 +1067,29 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) end end +function room_mt:handle_mediated_decline(origin, stanza, payload) + local declinee = jid_prep(payload.attr.to); + if declinee then + local from, to = stanza.attr.from, stanza.attr.to; + -- TODO: Validate declinee + local reason = payload:get_child_text("reason") + local decline = st.message({from = to, to = declinee, id = stanza.attr.id}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('decline', {from=from}) + :tag('reason'):text(reason or ""):up() + :up() + :up() + :tag('body') -- Add a plain message for clients which don't support declines + :text(from..' declined your invite to the room '..to..(reason and (' ('..reason..')') or "")) + :up(); + self:_route_stanza(decline); + return true; + else + origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); + return true; + end +end + function room_mt:handle_message_to_room(origin, stanza) local type = stanza.attr.type; if type == "groupchat" then @@ -1081,6 +1104,8 @@ function room_mt:handle_message_to_room(origin, stanza) -- fallthrough elseif payload.name == "invite" and payload.attr.to then return self:handle_mediated_invite(origin, stanza, payload) + elseif payload.name == "decline" and payload.attr.to then + return self:handle_mediated_decline(origin, stanza, payload) end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; -- cgit v1.2.3 From 4aebdcf7dd4f710d3c35324e2f6d7141518f102f Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 15:42:48 -0400 Subject: plugins/muc/muc: When there's no history; return an empty iterator --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 0c87574a..2afe6f58 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -181,7 +181,7 @@ end -- Get history for 'to' function room_mt:get_history(to, maxchars, maxstanzas, since) local history = self._data['history']; -- send discussion history - if not history then return end + if not history then return function() end end local history_len = #history maxstanzas = maxstanzas or history_len -- cgit v1.2.3 From 557523053f6a5e2d9eb161ca14ab16ec8133407c Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 16:01:53 -0400 Subject: plugins/muc/muc: Only call get_password once in invite creation --- plugins/muc/muc.lib.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2afe6f58..2c9b58d2 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1045,9 +1045,10 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) :tag('invite', {from=_from}) :tag('reason'):text(_reason or ""):up() :up(); - if self:get_password() then - invite:tag("password"):text(self:get_password()):up(); - end + local password = self:get_password() + if password then + invite:tag("password"):text(password):up(); + end invite:up() :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this :text(_reason or "") -- cgit v1.2.3 From 2e88255cb1faa8bf1974b0ace24c08b89b9eadc7 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 18:52:28 -0400 Subject: plugins/muc/muc: Add 'muc-occupant-left' event --- plugins/muc/muc.lib.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2c9b58d2..9f7b5c70 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -416,6 +416,7 @@ function room_mt:handle_unavailable_to_occupant(origin, stanza) occupant.role = 'none'; self:broadcast_presence(pr, from); self._occupants[current_nick] = nil; + module:fire_event("muc-occupant-left", { room = self; nick = current_nick; }); end return true; end @@ -823,6 +824,7 @@ function room_mt:destroy(newjid, reason, password) self._jid_nick[jid] = nil; end self._occupants[nick] = nil; + module:fire_event("muc-occupant-left", { room = self; nick = nick; }); end self:set_persistent(false); module:fire_event("muc-room-destroyed", { room = self }); -- cgit v1.2.3 From 090de4a62b9b57800c0c8700ea8f427147928cce Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 18:55:52 -0400 Subject: plugins/muc/mod_muc: Refactor to use new methods available --- plugins/muc/mod_muc.lua | 110 +++++++++++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 38 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 5a71ef75..9adec74e 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -106,6 +106,14 @@ function create_room(jid) return room; end +function forget_room(jid) + rooms[jid] = nil; +end + +function get_room_from_jid(room_jid) + return rooms[room_jid] +end + local persistent_errors = false; for jid in pairs(persistent_rooms) do local node = jid_split(jid); @@ -125,6 +133,7 @@ if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); e local host_room = muc_new_room(muc_host); host_room.route_stanza = room_route_stanza; host_room.save = room_save; +rooms[muc_host] = host_room; module:hook("host-disco-items", function(event) local reply = event.reply; @@ -136,49 +145,74 @@ module:hook("host-disco-items", function(event) end end); -function stanza_handler(event) - local origin, stanza = event.origin, event.stanza; - local bare = jid_bare(stanza.attr.to); - local room = rooms[bare]; - if not room then - if stanza.name ~= "presence" then - origin.send(st.error_reply(stanza, "cancel", "item-not-found")); - return true; - end - if not(restrict_room_creation) or - is_admin(stanza.attr.from) or - (restrict_room_creation == "local" and select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "")) then - room = create_room(bare); - end +module:hook("muc-room-destroyed",function(event) + local room = event.room + forget_room(room.jid) +end) + +module:hook("muc-occupant-left",function(event) + local room = event.room + if not next(room._occupants) and not persistent_rooms[room.jid] then -- empty, non-persistent room + module:fire_event("muc-room-destroyed", { room = room }); end - if room then - room:handle_stanza(origin, stanza); - if not next(room._occupants) and not persistent_rooms[room.jid] then -- empty, non-persistent room - module:fire_event("muc-room-destroyed", { room = room }); - rooms[bare] = nil; -- discard room - end - else - origin.send(st.error_reply(stanza, "cancel", "not-allowed")); +end); + +-- Watch presence to create rooms +local function attempt_room_creation(event) + local origin, stanza = event.origin, event.stanza; + local room_jid = jid_bare(stanza.attr.to); + if stanza.attr.type == nil and + get_room_from_jid(room_jid) == nil and + ( + not(restrict_room_creation) or + is_admin(stanza.attr.from) or + ( + restrict_room_creation == "local" and + select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "") + ) + ) then + create_room(room_jid); end - return true; end -module:hook("iq/bare", stanza_handler, -1); -module:hook("message/bare", stanza_handler, -1); -module:hook("presence/bare", stanza_handler, -1); -module:hook("iq/full", stanza_handler, -1); -module:hook("message/full", stanza_handler, -1); -module:hook("presence/full", stanza_handler, -1); +module:hook("presence/full", attempt_room_creation, -1) +module:hook("presence/bare", attempt_room_creation, -1) +module:hook("presence/host", attempt_room_creation, -1) -local function handle_to_domain(event) - local origin, stanza = event.origin, event.stanza; - local type = stanza.attr.type; - if type == "error" then return; end - host_room:handle_stanza(origin, stanza); - -- origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); - return true; +for event_name, method in pairs { + -- Normal room interactions + ["iq-get/bare/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; + ["iq-get/bare/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; + ["iq-set/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; + ["iq-get/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; + ["iq-get/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; + ["message/bare"] = "handle_message_to_room" ; + ["presence/bare"] = "handle_presence_to_room" ; + -- Host room + ["iq-get/host/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; + ["iq-get/host/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; + ["iq-set/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; + ["iq-get/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; + ["iq-get/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; + ["message/host"] = "handle_message_to_room" ; + ["presence/host"] = "handle_presence_to_room" ; + -- Direct to occupant (normal rooms and host room) + ["presence/full"] = "handle_presence_to_occupant" ; + ["iq/full"] = "handle_iq_to_occupant" ; + ["message/full"] = "handle_message_to_occupant" ; +} do + module:hook(event_name, function (event) + local origin, stanza = event.origin, event.stanza; + local room = get_room_from_jid(jid_bare(stanza.attr.to)) + if room == nil then + origin.send(st.error_reply(stanza, "cancel", "not-allowed")); + return true; + end + return room[method](room, origin, stanza); + end, -2) end -module:hook("message/host", handle_to_domain, -1); -module:hook("presence/host", handle_to_domain, -1); + hosts[module.host].send = function(stanza) -- FIXME do a generic fix if stanza.attr.type == "result" or stanza.attr.type == "error" then -- cgit v1.2.3 From dc2b5beff10a767092fe92f1aeb706a694bce062 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 18:56:26 -0400 Subject: plugins/muc/mod_muc: host sessions have a .send these days --- plugins/muc/mod_muc.lua | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 9adec74e..5f4b0c62 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -213,13 +213,6 @@ for event_name, method in pairs { end, -2) end - -hosts[module.host].send = function(stanza) -- FIXME do a generic fix - if stanza.attr.type == "result" or stanza.attr.type == "error" then - module:send(stanza); - else error("component.send only supports result and error stanzas at the moment"); end -end - hosts[module:get_host()].muc = { rooms = rooms }; local saved = false; -- cgit v1.2.3 From f11ccaa2fe22a7f5852309847e72f42ace473265 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 12:03:15 -0400 Subject: plugins/muc: Provide a reasonable default `route_stanza` --- plugins/muc/mod_muc.lua | 3 --- plugins/muc/muc.lib.lua | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 5f4b0c62..a1ba5738 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -64,7 +64,6 @@ function muclib.room_mt:set_affiliation(actor, jid, affiliation, callback, reaso return _set_affiliation(self, actor, jid, affiliation, callback, reason); end -local function room_route_stanza(room, stanza) module:send(stanza); end local function room_save(room, forced) local node = jid_split(room.jid); persistent_rooms[room.jid] = room._data.persistent; @@ -89,7 +88,6 @@ end function create_room(jid) local room = muc_new_room(jid); - room.route_stanza = room_route_stanza; room.save = room_save; rooms[jid] = room; if lock_rooms then @@ -131,7 +129,6 @@ end if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end local host_room = muc_new_room(muc_host); -host_room.route_stanza = room_route_stanza; host_room.save = room_save; rooms[muc_host] = host_room; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9f7b5c70..198e25a3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1145,7 +1145,9 @@ function room_mt:handle_stanza(origin, stanza) end end -function room_mt:route_stanza(stanza) end -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end +function room_mt:route_stanza(stanza) + module:send(stanza) +end function room_mt:get_affiliation(jid) local node, host, resource = jid_split(jid); -- cgit v1.2.3 From 09a6fc6b86b62dacf48e7bdb3b3d260447b765bc Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 12:19:44 -0400 Subject: plugins/muc/muc.lib: Use more modern stanza methods --- plugins/muc/muc.lib.lua | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 198e25a3..ead1f264 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -755,8 +755,7 @@ end function room_mt:process_form(origin, stanza) local query = stanza.tags[1]; - local form; - for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end + local form = query:get_child("x", "jabber:x:data") if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end @@ -858,7 +857,7 @@ function room_mt:handle_admin_item_set_command(origin, stanza) end local actor = stanza.attr.from; local callback = function() origin.send(st.reply(stanza)); end - local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; + local reason = item:get_child_text("reason"); if item.attr.affiliation and item.attr.jid and not item.attr.role then local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); if not success then origin.send(st.error_reply(stanza, errtype, err)); end @@ -943,14 +942,8 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) return true; elseif child.name == "destroy" then local newjid = child.attr.jid; - local reason, password; - for _,tag in ipairs(child.tags) do - if tag.name == "reason" then - reason = #tag.tags == 0 and tag[1]; - elseif tag.name == "password" then - password = #tag.tags == 0 and tag[1]; - end - end + local reason = child:get_child_text("reason"); + local password = child:get_child_text("password"); self:destroy(newjid, reason, password); origin.send(st.reply(stanza)); return true; @@ -1305,22 +1298,18 @@ function room_mt:_route_stanza(stanza) end end if muc_child then - for _, item in pairs(muc_child.tags) do - if item.name == "item" then - if from_occupant == to_occupant then - item.attr.jid = stanza.attr.to; - else - item.attr.jid = from_occupant.jid; - end + for item in muc_child:childtags("item") do + if from_occupant == to_occupant then + item.attr.jid = stanza.attr.to; + else + item.attr.jid = from_occupant.jid; end end end self:route_stanza(stanza); if muc_child then - for _, item in pairs(muc_child.tags) do - if item.name == "item" then - item.attr.jid = nil; - end + for item in muc_child:childtags("item") do + item.attr.jid = nil; end end end -- cgit v1.2.3 From d986769600d4a588e7af9e910810475c0bef1031 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 13:56:14 -0400 Subject: plugins/muc/muc.lib: Remove unused methods (breaks api) --- plugins/muc/muc.lib.lua | 72 ------------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ead1f264..972e30cb 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -656,21 +656,6 @@ function room_mt:handle_message_to_occupant(origin, stanza) return true; end -function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc - local from, to = stanza.attr.from, stanza.attr.to; - local room = jid_bare(to); - local current_nick = self._jid_nick[from]; - log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); - if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end - if stanza.name == "presence" then - return self:handle_presence_to_occupant(origin, stanza) - elseif stanza.name == "iq" then - return self:handle_iq_to_occupant(origin, stanza) - elseif stanza.name == "message" then - return self:handle_message_to_occupant(origin, stanza) - end -end - function room_mt:send_form(origin, stanza) origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") :add_child(self:get_form_layout(stanza.attr.from):form()) @@ -953,36 +938,6 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) end end -function room_mt:handle_iq_to_room(origin, stanza) - local type = stanza.attr.type; - local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; - if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then - return self:handle_disco_info_get_query(origin, stanza) - elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then - return self:handle_disco_items_get_query(origin, stanza) - elseif xmlns == "http://jabber.org/protocol/muc#admin" then - local item = stanza.tags[1].tags[1]; - if item and item.name == "item" then - if type == "set" then - return self:handle_admin_item_set_command(origin, stanza) - elseif type == "get" then - return self:handle_admin_item_get_command(origin, stanza) - end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "bad-request")); - return true; - end - elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then - if stanza.attr.type == "get" then - return self:handle_owner_query_get_to_room(origin, stanza) - elseif stanza.attr.type == "set" then - return self:handle_owner_query_set_to_room(origin, stanza) - end - else - return nil; - end -end - function room_mt:handle_groupchat_to_room(origin, stanza) local from = stanza.attr.from; local current_nick = self._jid_nick[from]; @@ -1111,33 +1066,6 @@ function room_mt:handle_message_to_room(origin, stanza) end end -function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc - if stanza.name == "iq" then - return self:handle_iq_to_room(origin, stanza) - elseif stanza.name == "message" then - return self:handle_message_to_room(origin, stanza) - elseif stanza.name == "presence" then - return self:handle_presence_to_room(origin, stanza) - end -end - -function room_mt:handle_stanza(origin, stanza) - local to_node, to_host, to_resource = jid_split(stanza.attr.to); - local handled - if to_resource then - handled = self:handle_to_occupant(origin, stanza); - else - handled = self:handle_to_room(origin, stanza); - end - - if not handled then - local type = stanza.attr.type - if stanza.name ~= "iq" or type == "get" or type == "set" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - end - end -end - function room_mt:route_stanza(stanza) module:send(stanza) end -- cgit v1.2.3 From 0019bfd631209ffd73380b8f52f6d8003da2f518 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 13:57:02 -0400 Subject: plugins/muc/muc.lib: Use module.host where `muc_domain` was previously --- plugins/muc/muc.lib.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 972e30cb..f3d69f8d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -25,7 +25,6 @@ local setmetatable = setmetatable; local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; -local muc_domain = nil; --module:get_host(); local default_history_length, max_history_length = 20, math.huge; ------------ @@ -118,8 +117,8 @@ function room_mt:save_to_history(stanza) stanza = st.clone(stanza); stanza.attr.to = ""; local stamp = datetime.datetime(); - stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203 - stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) + stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 + stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) local entry = { stanza = stanza, stamp = stamp }; t_insert(history, entry); while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end -- cgit v1.2.3 From de9f31f2e7be51c92fbf096465212e40ce4548d3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 13:59:59 -0400 Subject: plugins/muc/muc.lib: Tidy up is_kickable_error: it didn't need to return the condition. Also removes `get_error_condition`; it was a one liner used in one place --- plugins/muc/muc.lib.lua | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index f3d69f8d..2d45f17e 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -27,7 +27,6 @@ local md5 = require "util.hashes".md5; local default_history_length, max_history_length = 20, math.huge; ------------- local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; local function presence_filter(tag) if presence_filters[tag.attr.xmlns] then @@ -35,33 +34,28 @@ local function presence_filter(tag) end return tag; end - local function get_filtered_presence(stanza) return st.clone(stanza):maptags(presence_filter); end -local kickable_error_conditions = { - ["gone"] = true; - ["internal-server-error"] = true; - ["item-not-found"] = true; - ["jid-malformed"] = true; - ["recipient-unavailable"] = true; - ["redirect"] = true; - ["remote-server-not-found"] = true; - ["remote-server-timeout"] = true; - ["service-unavailable"] = true; - ["malformed error"] = true; -}; - -local function get_error_condition(stanza) - local _, condition = stanza:get_error(); - return condition or "malformed error"; -end - -local function is_kickable_error(stanza) - local cond = get_error_condition(stanza); - return kickable_error_conditions[cond] and cond; -end ------------ + +local is_kickable_error do + local kickable_error_conditions = { + ["gone"] = true; + ["internal-server-error"] = true; + ["item-not-found"] = true; + ["jid-malformed"] = true; + ["recipient-unavailable"] = true; + ["redirect"] = true; + ["remote-server-not-found"] = true; + ["remote-server-timeout"] = true; + ["service-unavailable"] = true; + ["malformed error"] = true; + }; + function is_kickable_error(stanza) + local cond = select(2, stanza:get_error()) or "malformed error"; + return kickable_error_conditions[cond]; + end +end local room_mt = {}; room_mt.__index = room_mt; -- cgit v1.2.3 From bd65d4b2a15b57bd65f1167261825325d0917708 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 14:06:04 -0400 Subject: plugins/muc/muc.lib: Tidy up `get_filtered_presence` --- plugins/muc/muc.lib.lua | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2d45f17e..2fb1a34e 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -27,15 +27,20 @@ local md5 = require "util.hashes".md5; local default_history_length, max_history_length = 20, math.huge; -local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; -local function presence_filter(tag) - if presence_filters[tag.attr.xmlns] then - return nil; +local get_filtered_presence do + local presence_filters = { + ["http://jabber.org/protocol/muc"] = true; + ["http://jabber.org/protocol/muc#user"] = true; + } + local function presence_filter(tag) + if presence_filters[tag.attr.xmlns] then + return nil; + end + return tag; + end + function get_filtered_presence(stanza) + return st.clone(stanza):maptags(presence_filter); end - return tag; -end -local function get_filtered_presence(stanza) - return st.clone(stanza):maptags(presence_filter); end local is_kickable_error do -- cgit v1.2.3 From eb0de6b20278678f98b948f971a3c4a1a6958ff7 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 14:35:17 -0400 Subject: plugins/muc/muc.lib: In `_route_stanza` access occupant data less often --- plugins/muc/muc.lib.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2fb1a34e..1a5bc976 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1210,9 +1210,9 @@ end function room_mt:_route_stanza(stanza) local muc_child; - local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; - local from_occupant = self._occupants[stanza.attr.from]; if stanza.name == "presence" then + local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; + local from_occupant = self._occupants[stanza.attr.from]; if to_occupant and from_occupant then if self._data.whois == 'anyone' then muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); @@ -1222,13 +1222,13 @@ function room_mt:_route_stanza(stanza) end end end - end - if muc_child then - for item in muc_child:childtags("item") do - if from_occupant == to_occupant then - item.attr.jid = stanza.attr.to; - else - item.attr.jid = from_occupant.jid; + if muc_child then + for item in muc_child:childtags("item") do + if from_occupant == to_occupant then + item.attr.jid = stanza.attr.to; + else + item.attr.jid = from_occupant.jid; + end end end end -- cgit v1.2.3 From 8fdc0a7cbcbd3a8a38f763287fb98323fb83b1c3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 14:39:31 -0400 Subject: plugins/muc/muc.lib: Fetch config via accessors instead of using `_data` --- plugins/muc/muc.lib.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1a5bc976..81998d2c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -232,7 +232,7 @@ function room_mt:get_disco_info(stanza) :tag("feature", {var=self:get_members_only() and "muc_membersonly" or "muc_open"}):up() :tag("feature", {var=self:get_persistent() and "muc_persistent" or "muc_temporary"}):up() :tag("feature", {var=self:get_hidden() and "muc_hidden" or "muc_public"}):up() - :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() + :tag("feature", {var=self:get_whois() ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() :add_child(dataform.new({ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }, { name = "muc#roominfo_description", label = "Description", value = "" }, @@ -488,7 +488,7 @@ function room_mt:handle_join(origin, stanza) self:broadcast_except_nick(pr, to); end pr:tag("status", {code='110'}):up(); - if self._data.whois == 'anyone' then + if self:get_whois() == 'anyone' then pr:tag("status", {code='100'}):up(); end if self.locked then @@ -661,6 +661,7 @@ function room_mt:send_form(origin, stanza) end function room_mt:get_form_layout(actor) + local whois = self:get_whois() local form = dataform.new({ title = "Configuration for "..self.jid, instructions = "Complete and submit this form to configure the room.", @@ -704,8 +705,8 @@ function room_mt:get_form_layout(actor) type = 'list-single', label = 'Who May Discover Real JIDs?', value = { - { value = 'moderators', label = 'Moderators Only', default = self._data.whois == 'moderators' }, - { value = 'anyone', label = 'Anyone', default = self._data.whois == 'anyone' } + { value = 'moderators', label = 'Moderators Only', default = whois == 'moderators' }, + { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } } }, { @@ -952,7 +953,7 @@ function room_mt:handle_groupchat_to_room(origin, stanza) local subject = stanza:get_child_text("subject"); if subject then if occupant.role == "moderator" or - ( self._data.changesubject and occupant.role == "participant" ) then -- and participant + ( self:get_changesubject() and occupant.role == "participant" ) then -- and participant self:set_subject(current_nick, subject); else stanza.attr.from = from; @@ -1214,7 +1215,7 @@ function room_mt:_route_stanza(stanza) local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; local from_occupant = self._occupants[stanza.attr.from]; if to_occupant and from_occupant then - if self._data.whois == 'anyone' then + if self:get_whois() == 'anyone' then muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); else if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then -- cgit v1.2.3 From a3c091edf5f5a93c8036d38f73ef33305b5e0979 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 15:00:53 -0400 Subject: plugins/muc/muc.lib: Use `get_occupant_jid` method instead of indexing _jid_nick --- plugins/muc/muc.lib.lua | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 81998d2c..173d9827 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -69,6 +69,10 @@ function room_mt:__tostring() return "MUC room ("..self.jid..")"; end +function room_mt:get_occupant_jid(real_jid) + return self._jid_nick[real_jid] +end + function room_mt:get_default_role(affiliation) if affiliation == "owner" or affiliation == "admin" then return "moderator"; @@ -134,7 +138,7 @@ function room_mt:broadcast_except_nick(stanza, nick) end function room_mt:send_occupant_list(to) - local current_nick = self._jid_nick[to]; + local current_nick = self:get_occupant_jid(to); for occupant, o_data in pairs(self._occupants) do if occupant ~= current_nick then local pres = get_filtered_presence(o_data.sessions[o_data.jid]); @@ -383,7 +387,7 @@ end function room_mt:handle_unavailable_to_occupant(origin, stanza) local from = stanza.attr.from; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); if not current_nick then return true; -- discard end @@ -514,7 +518,7 @@ end function room_mt:handle_available_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); if current_nick then --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence if current_nick == to then -- simple presence @@ -576,7 +580,7 @@ function room_mt:handle_iq_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; local type = stanza.attr.type; local id = stanza.attr.id; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); local o_data = self._occupants[to]; if (type == "error" or type == "result") then do -- deconstruct_stanza_id @@ -622,7 +626,7 @@ end function room_mt:handle_message_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); local type = stanza.attr.type; if not current_nick then -- not in room if type ~= "error" then @@ -836,7 +840,7 @@ function room_mt:handle_admin_item_set_command(origin, stanza) local occupant = self._occupants[self.jid.."/"..item.attr.nick]; if occupant then item.attr.jid = occupant.jid; end elseif not item.attr.nick and item.attr.jid then - local nick = self._jid_nick[item.attr.jid]; + local nick = self:get_occupant_jid(item.attr.jid); if nick then item.attr.nick = select(3, jid_split(nick)); end end local actor = stanza.attr.from; @@ -859,7 +863,7 @@ end function room_mt:handle_admin_item_get_command(origin, stanza) local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); - local current_nick = self._jid_nick[actor]; + local current_nick = self:get_occupant_jid(actor); local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); local item = stanza.tags[1].tags[1]; local _aff = item.attr.affiliation; @@ -939,7 +943,7 @@ end function room_mt:handle_groupchat_to_room(origin, stanza) local from = stanza.attr.from; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); local occupant = self._occupants[current_nick]; if not occupant then -- not in room origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); @@ -969,7 +973,7 @@ end -- hack - some buggy clients send presence updates to the room rather than their nick function room_mt:handle_presence_to_room(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; + local current_nick = self:get_occupant_jid(stanza.attr.from); local handled if current_nick then local to = stanza.attr.to; @@ -982,7 +986,8 @@ end function room_mt:handle_mediated_invite(origin, stanza, payload) local _from, _to = stanza.attr.from, stanza.attr.to; - if not self._jid_nick[_from] then -- Should be in room to send invite TODO: allow admins to send at any time + local current_nick = self:get_occupant_jid(_from) + if not current_nick then -- Should be in room to send invite TODO: allow admins to send at any time origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end @@ -1007,7 +1012,7 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) :up(); if self:get_members_only() and not self:get_affiliation(_invitee) then log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); - self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) + self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. current_nick) end self:_route_stanza(invite); return true; @@ -1157,7 +1162,7 @@ function room_mt:can_set_role(actor_jid, occupant_jid, role) if actor_jid == true then return true; end - local actor = self._occupants[self._jid_nick[actor_jid]]; + local actor = self._occupants[self:get_occupant_jid(actor_jid)]; if actor.role == "moderator" then if occupant.affiliation ~= "owner" and occupant.affiliation ~= "admin" then if actor.affiliation == "owner" or actor.affiliation == "admin" then @@ -1212,7 +1217,7 @@ end function room_mt:_route_stanza(stanza) local muc_child; if stanza.name == "presence" then - local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; + local to_occupant = self._occupants[self:get_occupant_jid(stanza.attr.to)]; local from_occupant = self._occupants[stanza.attr.from]; if to_occupant and from_occupant then if self:get_whois() == 'anyone' then -- cgit v1.2.3 From dde1ed8455c6affd2b967c8fcf3d79aaa874f32b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 15:01:18 -0400 Subject: plugins/muc/muc.lib: Don't get same variable twice..... --- plugins/muc/muc.lib.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 173d9827..f3c72e26 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -445,13 +445,12 @@ function room_mt:handle_change_nick(origin, stanza, current_nick, to) origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; else - local data = self._occupants[current_nick]; local to_nick = select(3, jid_split(to)); - log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); + log("debug", "%s (%s) changing nick to %s", current_nick, occupant.jid, to); local p = st.presence({type='unavailable', from=current_nick}); self:broadcast_presence(p, from, '303', to_nick); self._occupants[current_nick] = nil; - self._occupants[to] = data; + self._occupants[to] = occupant; self._jid_nick[from] = to; local pr = get_filtered_presence(stanza); pr.attr.from = to; -- cgit v1.2.3 From 19dd45441f0a534547d81a51bfdf0ce7da973fed Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 16:19:31 -0400 Subject: plugins/muc/muc.lib: Add muc-invite-prepared event; Use it for granting affiliations in members only rooms --- plugins/muc/muc.lib.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index f3c72e26..7dfb0611 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1009,10 +1009,7 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) :tag('body') -- Add a plain message for clients which don't support invites :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) :up(); - if self:get_members_only() and not self:get_affiliation(_invitee) then - log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); - self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. current_nick) - end + module:fire_event("muc-invite-prepared", { room = self, stanza = invite }) self:_route_stanza(invite); return true; else @@ -1021,6 +1018,18 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) end end +-- When an invite is sent; add an affiliation for the invitee +module:hook("muc-invite-prepared", function(event) + local room, stanza = event.room, event.stanza + local invitee = stanza.attr.to + if room:get_members_only() and not room:get_affiliation(invitee) then + local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from + local current_nick = room:get_occupant_jid(from) + log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); + room:set_affiliation(from, invitee, "member", nil, "Invited by " .. current_nick) + end +end) + function room_mt:handle_mediated_decline(origin, stanza, payload) local declinee = jid_prep(payload.attr.to); if declinee then -- cgit v1.2.3 From fcf5bbb2f2970f7719ecc53a7add8a6c828bb5f9 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 16:24:17 -0400 Subject: plugins/muc/muc.lib: Fix wrong variable in `construct_stanza_id` block --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7dfb0611..de251716 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -610,7 +610,7 @@ function room_mt:handle_iq_to_occupant(origin, stanza) return true; end do -- construct_stanza_id - stanza.attr.id = base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from)); + stanza.attr.id = base64.encode(o_data.jid.."\0"..stanza.attr.id.."\0"..md5(from)); end stanza.attr.from, stanza.attr.to = current_nick, o_data.jid; log("debug", "%s sent private iq stanza to %s (%s)", from, to, o_data.jid); -- cgit v1.2.3 From 0ae91158e6c464932b6e0f540bc43b0b41ca8191 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 16:28:11 -0400 Subject: plugins/muc/muc.lib: Remove `payload` argument from `handle_mediated_*`; extract it from inside. --- plugins/muc/muc.lib.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index de251716..be4d31c3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -983,7 +983,8 @@ function room_mt:handle_presence_to_room(origin, stanza) return handled; end -function room_mt:handle_mediated_invite(origin, stanza, payload) +function room_mt:handle_mediated_invite(origin, stanza) + local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite") local _from, _to = stanza.attr.from, stanza.attr.to; local current_nick = self:get_occupant_jid(_from) if not current_nick then -- Should be in room to send invite TODO: allow admins to send at any time @@ -1030,7 +1031,8 @@ module:hook("muc-invite-prepared", function(event) end end) -function room_mt:handle_mediated_decline(origin, stanza, payload) +function room_mt:handle_mediated_decline(origin, stanza) + local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline") local declinee = jid_prep(payload.attr.to); if declinee then local from, to = stanza.attr.from, stanza.attr.to; @@ -1066,9 +1068,9 @@ function room_mt:handle_message_to_room(origin, stanza) if payload == nil then -- fallthrough elseif payload.name == "invite" and payload.attr.to then - return self:handle_mediated_invite(origin, stanza, payload) + return self:handle_mediated_invite(origin, stanza) elseif payload.name == "decline" and payload.attr.to then - return self:handle_mediated_decline(origin, stanza, payload) + return self:handle_mediated_decline(origin, stanza) end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; -- cgit v1.2.3 From 2f6301bca2108abb064aaf2c2ff7f991e97ce67a Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:30:21 -0400 Subject: plugins/muc/muc.lib: Check role instead of current_nick --- plugins/muc/muc.lib.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index be4d31c3..7b010646 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -987,7 +987,8 @@ function room_mt:handle_mediated_invite(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite") local _from, _to = stanza.attr.from, stanza.attr.to; local current_nick = self:get_occupant_jid(_from) - if not current_nick then -- Should be in room to send invite TODO: allow admins to send at any time + -- Need visitor role or higher to invite + if not self._occupants[current_nick].role then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end -- cgit v1.2.3 From 583d913c8f3939753a6ca2252877a50131d976fb Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:33:32 -0400 Subject: plugins/muc/muc.lib: Send invite out from event: removes '-prepared' from event name --- plugins/muc/muc.lib.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7b010646..7cc1beb4 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1011,8 +1011,7 @@ function room_mt:handle_mediated_invite(origin, stanza) :tag('body') -- Add a plain message for clients which don't support invites :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) :up(); - module:fire_event("muc-invite-prepared", { room = self, stanza = invite }) - self:_route_stanza(invite); + module:fire_event("muc-invite", { room = self, stanza = invite, origin = origin, incoming = stanza }); return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); @@ -1020,8 +1019,13 @@ function room_mt:handle_mediated_invite(origin, stanza) end end +module:hook("muc-invite", function(event) + event.room:_route_stanza(event.stanza); + return true; +end, -1) + -- When an invite is sent; add an affiliation for the invitee -module:hook("muc-invite-prepared", function(event) +module:hook("muc-invite", function(event) local room, stanza = event.room, event.stanza local invitee = stanza.attr.to if room:get_members_only() and not room:get_affiliation(invitee) then -- cgit v1.2.3 From 1dcf3e89383ef54bfedc412a7184b01e04c3a57b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:46:25 -0400 Subject: plugins/muc/muc.lib: Use `get_role` in `handle_admin_item_get_command`. Removed a TODO that's already done --- plugins/muc/muc.lib.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7cc1beb4..fe8aee72 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -862,8 +862,6 @@ end function room_mt:handle_admin_item_get_command(origin, stanza) local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); - local current_nick = self:get_occupant_jid(actor); - local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); local item = stanza.tags[1].tags[1]; local _aff = item.attr.affiliation; local _rol = item.attr.role; @@ -882,8 +880,8 @@ function room_mt:handle_admin_item_get_command(origin, stanza) return true; end elseif _rol and not _aff then + local role = self:get_role(self:get_occupant_jid(actor)) or self:get_default_role(affiliation); if role == "moderator" then - -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? if _rol == "none" then _rol = nil; end local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); for occupant_jid, occupant in pairs(self._occupants) do -- cgit v1.2.3 From 061cc1c21602540ef72cdd765c171d2898ae3b83 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:50:00 -0400 Subject: plugins/muc/muc.lib: Allow users with affiliations to invite while not in room themselves --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index fe8aee72..af7bf356 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -986,7 +986,7 @@ function room_mt:handle_mediated_invite(origin, stanza) local _from, _to = stanza.attr.from, stanza.attr.to; local current_nick = self:get_occupant_jid(_from) -- Need visitor role or higher to invite - if not self._occupants[current_nick].role then + if not self:get_role(current_nick) or not self:get_default_role(self:get_affiliation(_from)) then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end -- cgit v1.2.3 From 2cacd0b95ae3df65b630a19c4ee866c02830def4 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:50:49 -0400 Subject: plugins/muc/muc.lib: Use occupant jids when whois == "moderators" --- plugins/muc/muc.lib.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index af7bf356..30d9f96d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -992,6 +992,9 @@ function room_mt:handle_mediated_invite(origin, stanza) end local _invitee = jid_prep(payload.attr.to); if _invitee then + if self:get_whois() == "moderators" then + _from = current_nick; + end local _reason = payload:get_child_text("reason") local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) -- cgit v1.2.3 From 46dedfa06a5a0046b34cd43a130a19a29be5c16d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 11:06:10 -0400 Subject: plugins/muc: Have utility methods for locking the room --- plugins/muc/mod_muc.lua | 4 ++-- plugins/muc/muc.lib.lua | 25 ++++++++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index a1ba5738..e8782414 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -91,10 +91,10 @@ function create_room(jid) room.save = room_save; rooms[jid] = room; if lock_rooms then - room.locked = true; + room:lock(); if lock_room_timeout and lock_room_timeout > 0 then module:add_timer(lock_room_timeout, function () - if room.locked then + if room:is_locked() then room:destroy(); -- Not unlocked in time end end); diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 30d9f96d..746814ca 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -85,6 +85,17 @@ function room_mt:get_default_role(affiliation) end end +function room_mt:lock() + self.locked = true +end +function room_mt:unlock() + module:fire_event("muc-room-unlocked", { room = self }); + self.locked = nil +end +function room_mt:is_locked() + return not not self.locked +end + function room_mt:broadcast_presence(stanza, sid, code, nick) stanza = get_filtered_presence(stanza); local occupant = self._occupants[stanza.attr.from]; @@ -465,10 +476,10 @@ function room_mt:handle_join(origin, stanza) log("debug", "%s joining as %s", from, to); if not next(self._affiliations) then -- new room, no owners self._affiliations[jid_bare(from)] = "owner"; - if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then - self.locked = nil; -- Older groupchat protocol doesn't lock + if self:is_locked() and not stanza:get_child("x", "http://jabber.org/protocol/muc") then + self:unlock(); -- Older groupchat protocol doesn't lock end - elseif self.locked then -- Deny entry + elseif self:is_locked() then -- Deny entry origin.send(st.error_reply(stanza, "cancel", "item-not-found")); return true; end @@ -494,7 +505,7 @@ function room_mt:handle_join(origin, stanza) if self:get_whois() == 'anyone' then pr:tag("status", {code='100'}):up(); end - if self.locked then + if self:is_locked() then pr:tag("status", {code='201'}):up(); end pr.attr.to = from; @@ -777,9 +788,8 @@ function room_mt:process_form(origin, stanza) handle_option("password", "muc#roomconfig_roomsecret"); if self.save then self:save(true); end - if self.locked then - module:fire_event("muc-room-unlocked", { room = self }); - self.locked = nil; + if self:is_locked() then + self:unlock(); end origin.send(st.reply(stanza)); @@ -1267,6 +1277,7 @@ local _M = {}; -- module "muc" function _M.new_room(jid, config) return setmetatable({ jid = jid; + locked = nil; _jid_nick = {}; _occupants = {}; _data = { -- cgit v1.2.3 From 4b1e0493b74fa0674f550f5148aeba5cf116e269 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 15:22:02 -0400 Subject: plugins/muc/muc.lib: Add route_to_occupant function to send a stanza to all occupant sessions --- plugins/muc/muc.lib.lua | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 746814ca..af9bd162 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -96,6 +96,15 @@ function room_mt:is_locked() return not not self.locked end +function room_mt:route_to_occupant(o_data, stanza) + local to = stanza.attr.to; + for jid in pairs(o_data.sessions) do + stanza.attr.to = jid; + self:_route_stanza(stanza); + end + stanza.attr.to = to; +end + function room_mt:broadcast_presence(stanza, sid, code, nick) stanza = get_filtered_presence(stanza); local occupant = self._occupants[stanza.attr.from]; @@ -113,14 +122,9 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) end end function room_mt:broadcast_message(stanza, historic) - local to = stanza.attr.to; - for occupant, o_data in pairs(self._occupants) do - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end + for occupant_jid, o_data in pairs(self._occupants) do + self:route_to_occupant(o_data, stanza) end - stanza.attr.to = to; if historic then -- add to history return self:save_to_history(stanza) end @@ -660,11 +664,8 @@ function room_mt:handle_message_to_occupant(origin, stanza) log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid); stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); stanza.attr.from = current_nick; - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to = from, to; + self:route_to_occupant(o_data, stanza) + stanza.attr.from = from; return true; end -- cgit v1.2.3 From 1798efadbd1ff13485794cd8e8be31d83fea169a Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 15:50:29 -0400 Subject: plugins/muc/muc.lib: Add decline event for parity with invite --- plugins/muc/muc.lib.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index af9bd162..7f8cc016 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1064,7 +1064,7 @@ function room_mt:handle_mediated_decline(origin, stanza) :tag('body') -- Add a plain message for clients which don't support declines :text(from..' declined your invite to the room '..to..(reason and (' ('..reason..')') or "")) :up(); - self:_route_stanza(decline); + module:fire_event("muc-decline", { room = self, stanza = decline, origin = origin, incoming = stanza }); return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); @@ -1072,6 +1072,11 @@ function room_mt:handle_mediated_decline(origin, stanza) end end +module:hook("muc-decline", function(event) + event.room:_route_stanza(event.stanza); + return true; +end, -1) + function room_mt:handle_message_to_room(origin, stanza) local type = stanza.attr.type; if type == "groupchat" then -- cgit v1.2.3 From ae5119a1916f4300ff969e578bad23fc2242cca0 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 16:14:22 -0400 Subject: plugins/muc/muc.lib: Remove duplicate variable; it can never be nil. --- plugins/muc/muc.lib.lua | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7f8cc016..ac841248 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -114,12 +114,9 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) stanza:tag("status", {code=code}):up(); end self:broadcast_except_nick(stanza, stanza.attr.from); - local me = self._occupants[stanza.attr.from]; - if me then - stanza:tag("status", {code='110'}):up(); - stanza.attr.to = sid; - self:_route_stanza(stanza); - end + stanza:tag("status", {code='110'}):up(); + stanza.attr.to = sid; + self:_route_stanza(stanza); end function room_mt:broadcast_message(stanza, historic) for occupant_jid, o_data in pairs(self._occupants) do -- cgit v1.2.3 From 4e4cb53a5b3ebe24ec7be7a07b62d08d7a3356d1 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 16:19:13 -0400 Subject: plugins/muc/muc.lib: Additional `route_to_occupant` usage --- plugins/muc/muc.lib.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ac841248..6dce1a80 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -141,10 +141,7 @@ end function room_mt:broadcast_except_nick(stanza, nick) for rnick, occupant in pairs(self._occupants) do if rnick ~= nick then - for jid in pairs(occupant.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end + self:route_to_occupant(occupant, stanza) end end end -- cgit v1.2.3 From e438f99e05eb1b950c29292f151ec3fe399a425d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Mar 2014 14:01:02 -0400 Subject: plugins/muc/muc.lib: Move password check and nick conflict check into `handle_join` --- plugins/muc/muc.lib.lua | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6dce1a80..d859812f 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -472,6 +472,23 @@ end function room_mt:handle_join(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; log("debug", "%s joining as %s", from, to); + local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); + password = password and password:get_child("password", "http://jabber.org/protocol/muc"); + password = password and password[1] ~= "" and password[1]; + if self:get_password() and self:get_password() ~= password then + log("debug", "%s couldn't join due to invalid password: %s", from, to); + local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); + reply.tags[1].attr.code = "401"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + elseif self._occupants[to] -- occupant already exists + and jid_bare(from) ~= jid_bare(self._occupants[to].jid) then -- and has different bare real jid + log("debug", "%s couldn't join due to nick conflict: %s", from, to); + local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + reply.tags[1].attr.code = "409"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end if not next(self._affiliations) then -- new room, no owners self._affiliations[jid_bare(from)] = "owner"; if self:is_locked() and not stanza:get_child("x", "http://jabber.org/protocol/muc") then @@ -541,30 +558,7 @@ function room_mt:handle_available_to_occupant(origin, stanza) -- self:handle_to_occupant(origin, stanza); -- resend available --end else -- enter room - local new_nick = to; - if self._occupants[to] then - if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then - new_nick = nil; - end - end - local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); - password = password and password:get_child("password", "http://jabber.org/protocol/muc"); - password = password and password[1] ~= "" and password[1]; - if self:get_password() and self:get_password() ~= password then - log("debug", "%s couldn't join due to invalid password: %s", from, to); - local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); - reply.tags[1].attr.code = "401"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - elseif not new_nick then - log("debug", "%s couldn't join due to nick conflict: %s", from, to); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); - reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - else - return self:handle_join(origin, stanza) - end + return self:handle_join(origin, stanza) end end -- cgit v1.2.3 From fa09856c291e00f330d3cee4fd58ef534877397d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 10:25:43 -0400 Subject: plugins/muc/muc.lib: Add muc-occupant-prejoin events; Use it for banned, members-only, password, nick-conflict and lock checks This reorders some of the checks. Importantly; affiliations are checked first: this means banned users cannot try and guess passwords --- plugins/muc/muc.lib.lua | 82 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d859812f..1b3df029 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -469,36 +469,68 @@ function room_mt:handle_change_nick(origin, stanza, current_nick, to) end end -function room_mt:handle_join(origin, stanza) +module:hook("muc-occupant-pre-join", function(event) + return module:fire_event("muc-occupant-pre-join/affiliation", event) + or module:fire_event("muc-occupant-pre-join/password", event) + or module:fire_event("muc-occupant-pre-join/locked", event) + or module:fire_event("muc-occupant-pre-join/nick-conflict", event) +end, -1) + +module:hook("muc-occupant-pre-join/password", function(event) + local room, stanza = event.room, event.stanza; local from, to = stanza.attr.from, stanza.attr.to; - log("debug", "%s joining as %s", from, to); local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); password = password and password:get_child("password", "http://jabber.org/protocol/muc"); password = password and password[1] ~= "" and password[1]; - if self:get_password() and self:get_password() ~= password then + if room:get_password() and room:get_password() ~= password then + local from, to = stanza.attr.from, stanza.attr.to; log("debug", "%s couldn't join due to invalid password: %s", from, to); local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); reply.tags[1].attr.code = "401"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; - elseif self._occupants[to] -- occupant already exists - and jid_bare(from) ~= jid_bare(self._occupants[to].jid) then -- and has different bare real jid + end +end, -1) + +module:hook("muc-occupant-pre-join/nick-conflict", function(event) + local room, stanza = event.room, event.stanza; + local from, to = stanza.attr.from, stanza.attr.to; + local occupant = room._occupants[to] + if occupant -- occupant already exists + and jid_bare(from) ~= jid_bare(occupant.jid) then -- and has different bare real jid log("debug", "%s couldn't join due to nick conflict: %s", from, to); local reply = st.error_reply(stanza, "cancel", "conflict"):up(); reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end +end, -1) + +module:hook("muc-occupant-pre-join/locked", function(event) + if event.room:is_locked() then -- Deny entry + event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); return true; end - if not next(self._affiliations) then -- new room, no owners - self._affiliations[jid_bare(from)] = "owner"; +end, -1) + +function room_mt:handle_join(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + local affiliation = self:get_affiliation(from); + if affiliation == nil and next(self._affiliations) == nil then -- new room, no owners + affiliation = "owner"; + self._affiliations[jid_bare(from)] = affiliation; if self:is_locked() and not stanza:get_child("x", "http://jabber.org/protocol/muc") then self:unlock(); -- Older groupchat protocol doesn't lock end - elseif self:is_locked() then -- Deny entry - origin.send(st.error_reply(stanza, "cancel", "item-not-found")); - return true; end - local affiliation = self:get_affiliation(from); + if module:fire_event("muc-occupant-pre-join", { + room = self; + origin = origin; + stanza = stanza; + affiliation = affiliation; + }) then return true; end + log("debug", "%s joining as %s", from, to); + local role = self:get_default_role(affiliation) if role then -- new occupant local is_merge = not not self._occupants[to] @@ -528,18 +560,28 @@ function room_mt:handle_join(origin, stanza) self:send_history(from, stanza); self:send_subject(from); return true; - elseif not affiliation then -- registration required for entering members-only room - local reply = st.error_reply(stanza, "auth", "registration-required"):up(); + end +end + +-- registration required for entering members-only room +module:hook("muc-occupant-pre-join/affiliation", function(event) + if event.affiliation == nil and event.room:get_members_only() then + local reply = st.error_reply(event.stanza, "auth", "registration-required"):up(); reply.tags[1].attr.code = "407"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; - else -- banned - local reply = st.error_reply(stanza, "auth", "forbidden"):up(); + end +end, -1) + +-- banned +module:hook("muc-occupant-pre-join/affiliation", function(event) + if event.affiliation == "outcast" then + local reply = st.error_reply(event.stanza, "auth", "forbidden"):up(); reply.tags[1].attr.code = "403"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end +end, -1) function room_mt:handle_available_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; -- cgit v1.2.3 From 4ff61ce8c55007091a4ca2a695711d9f4db9c8a8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 12:44:31 -0400 Subject: plugins/muc/muc.lib: Better password check --- plugins/muc/muc.lib.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1b3df029..821cb082 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -480,9 +480,9 @@ module:hook("muc-occupant-pre-join/password", function(event) local room, stanza = event.room, event.stanza; local from, to = stanza.attr.from, stanza.attr.to; local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); - password = password and password:get_child("password", "http://jabber.org/protocol/muc"); - password = password and password[1] ~= "" and password[1]; - if room:get_password() and room:get_password() ~= password then + password = password and password:get_child_text("password", "http://jabber.org/protocol/muc"); + if not password or password == "" then password = nil; end + if room:get_password() ~= password then local from, to = stanza.attr.from, stanza.attr.to; log("debug", "%s couldn't join due to invalid password: %s", from, to); local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); -- cgit v1.2.3 From 648ed9029771e9d9ba3e45ee84a616a3a28b4583 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 13:10:54 -0400 Subject: plugins/muc/muc.lib: Add muc-broadcast-message event. Use it for saving to history --- plugins/muc/muc.lib.lua | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 821cb082..4366716a 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -119,25 +119,29 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) self:_route_stanza(stanza); end function room_mt:broadcast_message(stanza, historic) + module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic}); for occupant_jid, o_data in pairs(self._occupants) do self:route_to_occupant(o_data, stanza) end - if historic then -- add to history - return self:save_to_history(stanza) - end -end -function room_mt:save_to_history(stanza) - local history = self._data['history']; - if not history then history = {}; self._data['history'] = history; end - stanza = st.clone(stanza); - stanza.attr.to = ""; - local stamp = datetime.datetime(); - stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 - stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) - local entry = { stanza = stanza, stamp = stamp }; - t_insert(history, entry); - while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end end + +-- add to history +module:hook("muc-broadcast-message", function(event) + if event.historic then + local room = event.room + local history = room._data['history']; + if not history then history = {}; room._data['history'] = history; end + local stanza = st.clone(event.stanza); + stanza.attr.to = ""; + local stamp = datetime.datetime(); + stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 + stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) + local entry = { stanza = stanza, stamp = stamp }; + t_insert(history, entry); + while #history > room:get_historylength() do t_remove(history, 1) end + end +end) + function room_mt:broadcast_except_nick(stanza, nick) for rnick, occupant in pairs(self._occupants) do if rnick ~= nick then -- cgit v1.2.3 From 277c93f8cb3e03bc46377225d7792fc79d2f9491 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 13:34:06 -0400 Subject: plugins/muc/muc.lib: Add muc-get-history event; it uses an iterator in the event object so that messages don't need to be all in memory at once --- plugins/muc/muc.lib.lua | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 4366716a..791938db 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -193,13 +193,17 @@ local function parse_history(stanza) return maxchars, maxstanzas, since end --- Get history for 'to' -function room_mt:get_history(to, maxchars, maxstanzas, since) - local history = self._data['history']; -- send discussion history - if not history then return function() end end + +module:hook("muc-get-history", function(event) + local room = event.room + local history = room._data['history']; -- send discussion history + if not history then return nil end local history_len = #history - maxstanzas = maxstanzas or history_len + local to = event.to + local maxchars = event.maxchars + local maxstanzas = event.maxstanzas or history_len + local since = event.since local n = 0; local charcount = 0; for i=history_len,1,-1 do @@ -218,7 +222,7 @@ function room_mt:get_history(to, maxchars, maxstanzas, since) end local i = history_len-n+1 - return function() + function event:next_stanza() if i > history_len then return nil end local entry = history[i] local msg = entry.stanza @@ -226,10 +230,19 @@ function room_mt:get_history(to, maxchars, maxstanzas, since) i = i + 1 return msg end -end -function room_mt:send_history(to, stanza) + return true; +end) + +function room_mt:send_history(stanza) local maxchars, maxstanzas, since = parse_history(stanza) - for msg in self:get_history(to, maxchars, maxstanzas, since) do + local event = { + room = self; + to = stanza.attr.from; -- `to` is required to calculate the character count for `maxchars` + maxchars = maxchars, maxstanzas = maxstanzas, since = since; + next_stanza = function() end; -- events should define this iterator + } + module:fire_event("muc-get-history", event) + for msg in event.next_stanza , event do self:_route_stanza(msg); end end -- cgit v1.2.3 From 5c48aabc62456d8ce139035feef8433381057206 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 13:36:43 -0400 Subject: plugins/muc/muc.lib: Extra utility functions around subjects --- plugins/muc/muc.lib.lua | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 791938db..d0a5641c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -246,11 +246,6 @@ function room_mt:send_history(stanza) self:_route_stanza(msg); end end -function room_mt:send_subject(to) - if self._data['subject'] then - self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject'])); - end -end function room_mt:get_disco_info(stanza) local count = 0; for _ in pairs(self._occupants) do count = count + 1; end @@ -277,13 +272,30 @@ function room_mt:get_disco_items(stanza) end return reply; end + +function room_mt:get_subject() + return self._data['subject'], self._data['subject_from'] +end +local function create_subject_message(subject) + return st.message({type='groupchat'}) + :tag('subject'):text(subject):up(); +end +function room_mt:send_subject(to) + local from, subject = self:get_subject() + if subject then + local msg = create_subject_message(subject) + msg.attr.from = from + msg.attr.to = to + self:_route_stanza(msg); + end +end function room_mt:set_subject(current_nick, subject) if subject == "" then subject = nil; end self._data['subject'] = subject; self._data['subject_from'] = current_nick; if self.save then self:save(); end - local msg = st.message({type='groupchat', from=current_nick}) - :tag('subject'):text(subject):up(); + local msg = create_subject_message(subject) + msg.attr.from = current_nick self:broadcast_message(msg, false); return true; end -- cgit v1.2.3 From 11354adf944504dfb6d04461ed345cf5cf05e9d6 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 16:32:18 -0400 Subject: plugins/muc/muc.lib: Add :broadcast method; use it from :broadcast_except_nick and :broadcast_message --- plugins/muc/muc.lib.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d0a5641c..c955d47b 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -120,9 +120,7 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) end function room_mt:broadcast_message(stanza, historic) module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic}); - for occupant_jid, o_data in pairs(self._occupants) do - self:route_to_occupant(o_data, stanza) - end + self:broadcast(stanza); end -- add to history @@ -143,8 +141,14 @@ module:hook("muc-broadcast-message", function(event) end) function room_mt:broadcast_except_nick(stanza, nick) - for rnick, occupant in pairs(self._occupants) do - if rnick ~= nick then + return self:broadcast(stanza, function(rnick, occupant) return rnick ~= nick end) +end + +-- Broadcast a stanza to all occupants in the room. +-- optionally checks conditional called with nicl +function room_mt:broadcast(stanza, cond_func) + for nick, occupant in pairs(self._occupants) do + if cond_func == nil or cond_func(nick, occupant) then self:route_to_occupant(occupant, stanza) end end -- cgit v1.2.3 From c8387ffd1b04c06ce767fd8d087d05b23eddb455 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 18:09:42 -0400 Subject: plugins/muc: Rename admin query hook --- plugins/muc/mod_muc.lua | 8 ++++---- plugins/muc/muc.lib.lua | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index e8782414..8759cba4 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -179,8 +179,8 @@ for event_name, method in pairs { -- Normal room interactions ["iq-get/bare/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; ["iq-get/bare/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; - ["iq-set/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; - ["iq-get/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/bare/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ; + ["iq-get/bare/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_get_command" ; ["iq-set/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; ["iq-get/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; ["message/bare"] = "handle_message_to_room" ; @@ -188,8 +188,8 @@ for event_name, method in pairs { -- Host room ["iq-get/host/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; ["iq-get/host/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; - ["iq-set/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; - ["iq-get/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/host/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ; + ["iq-get/host/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_get_command" ; ["iq-set/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; ["iq-get/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; ["message/host"] = "handle_message_to_room" ; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index c955d47b..7e41b225 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -900,7 +900,7 @@ function room_mt:handle_disco_items_get_query(origin, stanza) return true; end -function room_mt:handle_admin_item_set_command(origin, stanza) +function room_mt:handle_admin_query_set_command(origin, stanza) local item = stanza.tags[1].tags[1]; if item.attr.jid then -- Validate provided JID item.attr.jid = jid_prep(item.attr.jid); @@ -933,7 +933,7 @@ function room_mt:handle_admin_item_set_command(origin, stanza) end end -function room_mt:handle_admin_item_get_command(origin, stanza) +function room_mt:handle_admin_query_get_command(origin, stanza) local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); local item = stanza.tags[1].tags[1]; -- cgit v1.2.3 From 9644f38a30dc50e75b0f38d455d93fb5b9eed3f6 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 18:10:34 -0400 Subject: plugins/muc/muc.lib: Have timestamp as seconds since epoch inside of history --- plugins/muc/muc.lib.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7e41b225..20cba225 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -131,10 +131,11 @@ module:hook("muc-broadcast-message", function(event) if not history then history = {}; room._data['history'] = history; end local stanza = st.clone(event.stanza); stanza.attr.to = ""; - local stamp = datetime.datetime(); + local ts = gettime(); + local stamp = datetime.datetime(ts); stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) - local entry = { stanza = stanza, stamp = stamp }; + local entry = { stanza = stanza, timestamp = ts }; t_insert(history, entry); while #history > room:get_historylength() do t_remove(history, 1) end end @@ -220,7 +221,7 @@ module:hook("muc-get-history", function(event) charcount = charcount + entry.chars + #to; if charcount > maxchars then break; end end - if since and since > entry.stamp then break; end + if since and since > entry.timestamp then break; end if n + 1 > maxstanzas then break; end n = n + 1; end -- cgit v1.2.3 From 31edf036e67ab716cb51657ef8dff9abbe8b28ed 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 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 8cf7b85bbb8bdc3b7bd673267fc1e5e775f45d88 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 19:16:46 -0400 Subject: plugins/muc/muc.lib: If decline is to person in room; route to all sessions --- plugins/muc/muc.lib.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 20cba225..27c50cd4 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1137,7 +1137,13 @@ function room_mt:handle_mediated_decline(origin, stanza) end module:hook("muc-decline", function(event) - event.room:_route_stanza(event.stanza); + local room, stanza = event.room, event.stanza + local occupant = room:get_occupant_by_real_jid(stanza.attr.to); + if occupant then + room:route_to_occupant(occupant, stanza) + else + room:route_stanza(stanza); + end return true; end, -1) -- cgit v1.2.3 From ed6e3ff65c0dd2e127645292978d8fc01f779d3b 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(-) 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 b8a6c67b1e851410f0434409420be7de70e6b5a6 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(-) 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 5fc225f002fd854af9c0cbd3afee5c4f8d8c8ebf 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(-) 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 5ae44714df607f256b33d46eb6f15771eef815ff 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(-) 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 8d5f9ece4d3712fa342fd13775f67b37664d4009 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(-) 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 a30f58b1d31331c490bf65b1ad1e0af59b0af28a 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(-) 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 ad1ec83264f36e03d2ae9a2292bc1aac6963350c 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(-) 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 0bb399dca12ff1c6f0b15393ce957497acddce41 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(-) 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 6119040a43a5d6b2fa10800e5be86fd0349a8414 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(-) 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 1f002c75637643f656885a816637d5294ab218c4 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(-) 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 ad434dae47724f59a03b6ec1eefa04290d9b9ea2 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(-) 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 84060786506a2a3180dce7ae57beebe7e651d7c7 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(-) 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 a3d3e9267405dbb9e81078366bf949e604748c78 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(-) 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 889de2c14de2837e7633fb854143b01c38f01824 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(-) 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 e34eab62fb6d71f347d6a9b3e4f168d1d0b0bdbb 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(-) 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 4bf752d569c7317cc100a9a782014c790d778da7 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(-) 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 edc904993af832a512414b93e658c67d6bf70e7a 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(-) 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 65805c61067e1e7bd907e1026c7e129368c2c645 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(-) 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 b4fe6a7f30c62cec91442d4f2abcb904d30ad6b1 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(+) 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 4942d8729f5ab8ae88cbbf1f737055359e6c6ceb 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(+) 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 04c29720d4d50e8883ecea0fd3dbfd2c6b5d03b2 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(-) 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 6bf8dd1d03e44a553d69d0e1b4a52749259da657 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(-) 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 e1aea88025ad7cb7d37b43e7c10e0f2b7a4039f5 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(-) 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 69eaebe2db313d342078bb4e1d83f693ac514f25 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(-) 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 5c2a128e96093068d94191fae472508108d43197 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 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 905c1a9c37d76e0e5322fbfc9629bbf07aa81638 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(-) 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 cada6120bc227c8ae60d765c842ce699c311300e 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 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 efeed80315ee278524a5a85b565e138ea822a50d 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(-) 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 854fa905aa3e8252201a4868fb362da6a70138a0 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 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 cfa0f1c37a911a4adb4142922eeaf7fddb8d7a92 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 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 f62cd0821ca3b54fe2b047fa56a6183285c8c9ce 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(-) 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 a04936d9270ac02a197d8bdd8918746583ef6d57 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(-) 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 fb062fee3bea8a9e1030ea651fd943f8301258da 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(-) 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 a52c27274d2cbf9fe382a4969f08d3dc4e69d4ad 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(-) 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 b4988bb024264115283521e3fdb9c98f3dad0870 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 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 fbb58db35976fbee12a7202f50a554bcfe257c7e 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 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 d8832205ef2728e337ff016a71b96e3aaffea912 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(-) 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 0187db99aed68ccd4cb38ef5988303b5782d94eb 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(-) 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 6a0d6594950e45d82e345da6a5cfb46d3664ec9d 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 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 c447a52dedee550cbbf18d44f69deb633076f6ed 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(-) 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 376a54dd5ede06e2107ff8f55584899e052fffd8 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(-) 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 b53a3f65e084e6d148f7440e49f6a5a2b971bb93 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 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 9328daeaca197d5aa90f4c340be7aad6b3b805b9 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 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 0e2182c8e257bbb62004ece1780546ee1d34e21a 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(-) 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 f49516c1d47a2efe7fd9fe778034a003967d0d6d 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 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 6c42b2659125a44df52fc5b40b83ec24f9711556 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 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 d48aad1049c3842479c5224548ebc2beaf90fdb0 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 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 59c6047e77980b89431b1d59e1458976bfb6eb18 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(+) 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 943088da0cc04e5b110d5d7655d12d88613c2c18 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(-) 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 1a0787a68d52dd7b91760177059d6d9e809b7c1a 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(-) 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 f7b24a0c2752bc002a749ed981919981b9c22dbf 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(-) 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 36353e3edb63ffa2612b0101d6a6133c9994c263 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(-) 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 0f7452e6a709099cc722d9e7b6484b2cb12a417d Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Wed, 23 Apr 2014 11:38:34 -0400 Subject: util.indexedbheap: Fix a possible traceback when removing the last item. --- util/indexedbheap.lua | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/util/indexedbheap.lua b/util/indexedbheap.lua index 3cb03037..c60861e8 100644 --- a/util/indexedbheap.lua +++ b/util/indexedbheap.lua @@ -113,23 +113,27 @@ function indexed_heap:reprioritize(id, priority) k = _percolate_down(self.priorities, k, self.ids, self.index); end function indexed_heap:remove_index(k) - local size = #self.priorities; - local result = self.priorities[k]; + if result == nil then return; end + local result_sync = self.ids[k]; local item = self.items[result_sync]; - if result == nil then return; end - self.index[result_sync] = nil; - self.items[result_sync] = nil; + local size = #self.priorities; self.priorities[k] = self.priorities[size]; self.ids[k] = self.ids[size]; self.index[self.ids[k]] = k; + t_remove(self.priorities); t_remove(self.ids); - k = _percolate_up(self.priorities, k, self.ids, self.index); - k = _percolate_down(self.priorities, k, self.ids, self.index); + self.index[result_sync] = nil; + self.items[result_sync] = nil; + + if size > k then + k = _percolate_up(self.priorities, k, self.ids, self.index); + k = _percolate_down(self.priorities, k, self.ids, self.index); + end return result, item, result_sync; end -- cgit v1.2.3 From a1421105a2eabc7b9c19a2ac7a4d4f372c6696cf 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(-) 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 de1d66aad0e12f9985623f051dc88f4ee509121c 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(-) 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 d14f2b82f19d187dcc22eb3a58cec7647900036e 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(-) 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 8a13710f6f44199de9eb227e5a6401cb0bc45857 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(-) 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 19f4fafa22434ec32c4707184da910bbce75bb85 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(-) 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 c010a538d09eaa0a958590329f7fc7ae62e695a5 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(-) 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 184b7839475c004c55eb6bda315a69ae3ccaee95 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(-) 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 f0e4ed8339ed677c644bd2bf8d17aa31a18472da 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(-) 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 dec7312aa52711ea86d95d697916f8a4e2d07888 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(-) 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 0b8ddb3f56709f829959041ff470837b1e861f4c 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(-) 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 6e37374435557c2c1b8c8fd67c7c2ec8e209abcd 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(-) 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 aeb3c041440a149cc47ab960c16cba021b16ac14 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(-) 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 7df6f92a24114e8087f4585a093a799fab21da7f 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(-) 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 169df15f6cec44c23deda5c8274c30fbb357fef2 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(-) 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 e8fdae76775a0c767f3cd2efabc2ce52cf511b0a 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(-) 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 c7f704026295901fadf0be6407110bd67075e0e3 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(-) 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 61778d912d7b8eaf29609c0c928eb0f031e4583a 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(-) 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 75357b0f410a3e344b5e4c7cbbcbb24ba7d9d140 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(-) 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 d6b18f4e20e7ac945426acf5d869b446c61d57e0 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(-) 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 347202819d3fd42945160856f0c409a245b4d1f1 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(-) 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 4d7106bee63fe544a4bec49c201d56b93d3794d3 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(-) 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 e8abeeee4dfbdd0234fa3c5b507a3e9aa64e7a4e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 28 May 2014 20:12:13 +0200 Subject: util.vcard: Library for parsing vCards --- util/vcard.lua | 454 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 454 insertions(+) create mode 100644 util/vcard.lua diff --git a/util/vcard.lua b/util/vcard.lua new file mode 100644 index 00000000..e3005801 --- /dev/null +++ b/util/vcard.lua @@ -0,0 +1,454 @@ +-- Copyright (C) 2011-2014 Kim Alvefur +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +-- TODO +-- Fix folding. + +local st = require "util.stanza"; +local t_insert, t_concat = table.insert, table.concat; +local type = type; +local next, pairs, ipairs = next, pairs, ipairs; + +local from_text, to_text, from_xep54, to_xep54; + +local line_sep = "\n"; + +local vCard_dtd; -- See end of file + +local function fold_line() + error "Not implemented" --TODO +end +local function unfold_line() + error "Not implemented" + -- gsub("\r?\n[ \t]([^\r\n])", "%1"); +end + +local function vCard_esc(s) + return s:gsub("[,:;\\]", "\\%1"):gsub("\n","\\n"); +end + +local function vCard_unesc(s) + return s:gsub("\\?[\\nt:;,]", { + ["\\\\"] = "\\", + ["\\n"] = "\n", + ["\\r"] = "\r", + ["\\t"] = "\t", + ["\\:"] = ":", -- FIXME Shouldn't need to espace : in values, just params + ["\\;"] = ";", + ["\\,"] = ",", + [":"] = "\29", + [";"] = "\30", + [","] = "\31", + }); +end + +local function item_to_xep54(item) + local t = st.stanza(item.name, { xmlns = "vcard-temp" }); + + local prop_def = vCard_dtd[item.name]; + if prop_def == "text" then + t:text(item[1]); + elseif type(prop_def) == "table" then + if prop_def.types and item.TYPE then + if type(item.TYPE) == "table" then + for _,v in pairs(prop_def.types) do + for _,typ in pairs(item.TYPE) do + if typ:upper() == v then + t:tag(v):up(); + break; + end + end + end + else + t:tag(item.TYPE:upper()):up(); + end + end + + if prop_def.props then + for _,v in pairs(prop_def.props) do + if item[v] then + t:tag(v):up(); + end + end + end + + if prop_def.value then + t:tag(prop_def.value):text(item[1]):up(); + elseif prop_def.values then + local prop_def_values = prop_def.values; + local repeat_last = prop_def_values.behaviour == "repeat-last" and prop_def_values[#prop_def_values]; + for i=1,#item do + t:tag(prop_def.values[i] or repeat_last):text(item[i]):up(); + end + end + end + + return t; +end + +local function vcard_to_xep54(vCard) + local t = st.stanza("vCard", { xmlns = "vcard-temp" }); + for i=1,#vCard do + t:add_child(item_to_xep54(vCard[i])); + end + return t; +end + +function to_xep54(vCards) + if not vCards[1] or vCards[1].name then + return vcard_to_xep54(vCards) + else + local t = st.stanza("xCard", { xmlns = "vcard-temp" }); + for i=1,#vCards do + t:add_child(vcard_to_xep54(vCards[i])); + end + return t; + end +end + +function from_text(data) + data = data -- unfold and remove empty lines + :gsub("\r\n","\n") + :gsub("\n ", "") + :gsub("\n\n+","\n"); + local vCards = {}; + local c; -- current item + for line in data:gmatch("[^\n]+") do + local line = vCard_unesc(line); + local name, params, value = line:match("^([-%a]+)(\30?[^\29]*)\29(.*)$"); + value = value:gsub("\29",":"); + if #params > 0 then + local _params = {}; + for k,isval,v in params:gmatch("\30([^=]+)(=?)([^\30]*)") do + k = k:upper(); + local _vt = {}; + for _p in v:gmatch("[^\31]+") do + _vt[#_vt+1]=_p + _vt[_p]=true; + end + if isval == "=" then + _params[k]=_vt; + else + _params[k]=true; + end + end + params = _params; + end + if name == "BEGIN" and value == "VCARD" then + c = {}; + vCards[#vCards+1] = c; + elseif name == "END" and value == "VCARD" then + c = nil; + elseif c and vCard_dtd[name] then + local dtd = vCard_dtd[name]; + local p = { name = name }; + c[#c+1]=p; + --c[name]=p; + local up = c; + c = p; + if dtd.types then + for _, t in ipairs(dtd.types) do + local t = t:lower(); + if ( params.TYPE and params.TYPE[t] == true) + or params[t] == true then + c.TYPE=t; + end + end + end + if dtd.props then + for _, p in ipairs(dtd.props) do + if params[p] then + if params[p] == true then + c[p]=true; + else + for _, prop in ipairs(params[p]) do + c[p]=prop; + end + end + end + end + end + if dtd == "text" or dtd.value then + t_insert(c, value); + elseif dtd.values then + local value = "\30"..value; + for p in value:gmatch("\30([^\30]*)") do + t_insert(c, p); + end + end + c = up; + end + end + return vCards; +end + +local function item_to_text(item) + local value = {}; + for i=1,#item do + value[i] = vCard_esc(item[i]); + end + value = t_concat(value, ";"); + + local params = ""; + for k,v in pairs(item) do + if type(k) == "string" and k ~= "name" then + params = params .. (";%s=%s"):format(k, type(v) == "table" and t_concat(v,",") or v); + end + end + + return ("%s%s:%s"):format(item.name, params, value) +end + +local function vcard_to_text(vcard) + local t={}; + t_insert(t, "BEGIN:VCARD") + for i=1,#vcard do + t_insert(t, item_to_text(vcard[i])); + end + t_insert(t, "END:VCARD") + return t_concat(t, line_sep); +end + +function to_text(vCards) + if vCards[1] and vCards[1].name then + return vcard_to_text(vCards) + else + local t = {}; + for i=1,#vCards do + t[i]=vcard_to_text(vCards[i]); + end + return t_concat(t, line_sep); + end +end + +local function from_xep54_item(item) + local prop_name = item.name; + local prop_def = vCard_dtd[prop_name]; + + local prop = { name = prop_name }; + + if prop_def == "text" then + prop[1] = item:get_text(); + elseif type(prop_def) == "table" then + if prop_def.value then --single item + prop[1] = item:get_child_text(prop_def.value) or ""; + elseif prop_def.values then --array + local value_names = prop_def.values; + if value_names.behaviour == "repeat-last" then + for i=1,#item.tags do + t_insert(prop, item.tags[i]:get_text() or ""); + end + else + for i=1,#value_names do + t_insert(prop, item:get_child_text(value_names[i]) or ""); + end + end + elseif prop_def.names then + local names = prop_def.names; + for i=1,#names do + if item:get_child(names[i]) then + prop[1] = names[i]; + break; + end + end + end + + if prop_def.props_verbatim then + for k,v in pairs(prop_def.props_verbatim) do + prop[k] = v; + end + end + + if prop_def.types then + local types = prop_def.types; + prop.TYPE = {}; + for i=1,#types do + if item:get_child(types[i]) then + t_insert(prop.TYPE, types[i]:lower()); + end + end + if #prop.TYPE == 0 then + prop.TYPE = nil; + end + end + + -- A key-value pair, within a key-value pair? + if prop_def.props then + local params = prop_def.props; + for i=1,#params do + local name = params[i] + local data = item:get_child_text(name); + if data then + prop[name] = prop[name] or {}; + t_insert(prop[name], data); + end + end + end + else + return nil + end + + return prop; +end + +local function from_xep54_vCard(vCard) + local tags = vCard.tags; + local t = {}; + for i=1,#tags do + t_insert(t, from_xep54_item(tags[i])); + end + return t +end + +function from_xep54(vCard) + if vCard.attr.xmlns ~= "vcard-temp" then + return nil, "wrong-xmlns"; + end + if vCard.name == "xCard" then -- A collection of vCards + local t = {}; + local vCards = vCard.tags; + for i=1,#vCards do + t[i] = from_xep54_vCard(vCards[i]); + end + return t + elseif vCard.name == "vCard" then -- A single vCard + return from_xep54_vCard(vCard) + end +end + +-- This was adapted from http://xmpp.org/extensions/xep-0054.html#dtd +vCard_dtd = { + VERSION = "text", --MUST be 3.0, so parsing is redundant + FN = "text", + N = { + values = { + "FAMILY", + "GIVEN", + "MIDDLE", + "PREFIX", + "SUFFIX", + }, + }, + NICKNAME = "text", + PHOTO = { + props_verbatim = { ENCODING = { "b" } }, + props = { "TYPE" }, + value = "BINVAL", --{ "EXTVAL", }, + }, + BDAY = "text", + ADR = { + types = { + "HOME", + "WORK", + "POSTAL", + "PARCEL", + "DOM", + "INTL", + "PREF", + }, + values = { + "POBOX", + "EXTADD", + "STREET", + "LOCALITY", + "REGION", + "PCODE", + "CTRY", + } + }, + LABEL = { + types = { + "HOME", + "WORK", + "POSTAL", + "PARCEL", + "DOM", + "INTL", + "PREF", + }, + value = "LINE", + }, + TEL = { + types = { + "HOME", + "WORK", + "VOICE", + "FAX", + "PAGER", + "MSG", + "CELL", + "VIDEO", + "BBS", + "MODEM", + "ISDN", + "PCS", + "PREF", + }, + value = "NUMBER", + }, + EMAIL = { + types = { + "HOME", + "WORK", + "INTERNET", + "PREF", + "X400", + }, + value = "USERID", + }, + JABBERID = "text", + MAILER = "text", + TZ = "text", + GEO = { + values = { + "LAT", + "LON", + }, + }, + TITLE = "text", + ROLE = "text", + LOGO = "copy of PHOTO", + AGENT = "text", + ORG = { + values = { + behaviour = "repeat-last", + "ORGNAME", + "ORGUNIT", + } + }, + CATEGORIES = { + values = "KEYWORD", + }, + NOTE = "text", + PRODID = "text", + REV = "text", + SORTSTRING = "text", + SOUND = "copy of PHOTO", + UID = "text", + URL = "text", + CLASS = { + names = { -- The item.name is the value if it's one of these. + "PUBLIC", + "PRIVATE", + "CONFIDENTIAL", + }, + }, + KEY = { + props = { "TYPE" }, + value = "CRED", + }, + DESC = "text", +}; +vCard_dtd.LOGO = vCard_dtd.PHOTO; +vCard_dtd.SOUND = vCard_dtd.PHOTO; + +return { + from_text = from_text; + to_text = to_text; + + from_xep54 = from_xep54; + to_xep54 = to_xep54; +}; -- cgit v1.2.3 From 72a1bf0bb532f8f8b6b66d4f67c41bb309cc9bbb Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 28 May 2014 21:11:02 +0200 Subject: util.vcard: Add support for converting to vcard4 --- util/vcard.lua | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/util/vcard.lua b/util/vcard.lua index e3005801..70c923b8 100644 --- a/util/vcard.lua +++ b/util/vcard.lua @@ -319,6 +319,68 @@ function from_xep54(vCard) end end +local vcard4 = { } + +function vcard4:text(node, params, value) + self:tag(node:lower()) + -- FIXME params + if type(value) == "string" then + self:tag("text"):text(value):up() + elseif vcard4[node] then + vcard4[node](value); + end + self:up(); +end + +function vcard4.N(value) + for i, k in ipairs(vCard_dtd.N.values) do + value:tag(k):text(value[i]):up(); + end +end + +local xmlns_vcard4 = "urn:ietf:params:xml:ns:vcard-4.0" + +local function item_to_vcard4(item) + local typ = item.name:lower(); + local t = st.stanza(typ, { xmlns = xmlns_vcard4 }); + + local prop_def = vCard4_dtd[typ]; + if prop_def == "text" then + t:tag("text"):text(item[1]):up(); + elseif type(prop_def) == "table" then + if prop_def.values then + for i, v in ipairs(prop_def.values) do + t:tag(v:lower()):text(item[i] or ""):up(); + end + else + t:tag("unsupported",{xmlns="http://zash.se/protocol/vcardlib"}) + end + else + t:tag("unsupported",{xmlns="http://zash.se/protocol/vcardlib"}) + end + return t; +end + +local function vcard_to_vcard4xml(vCard) + local t = st.stanza("vcard", { xmlns = xmlns_vcard4 }); + for i=1,#vCard do + t:add_child(item_to_vcard4(vCard[i])); + end + return t; +end + +local function vcards_to_vcard4xml(vCards) + if not vCards[1] or vCards[1].name then + return vcard_to_vcard4xml(vCards) + else + local t = st.stanza("vcards", { xmlns = xmlns_vcard4 }); + for i=1,#vCards do + t:add_child(vcard_to_vcard4xml(vCards[i])); + end + return t; + end +end + -- This was adapted from http://xmpp.org/extensions/xep-0054.html#dtd vCard_dtd = { VERSION = "text", --MUST be 3.0, so parsing is redundant @@ -445,10 +507,69 @@ vCard_dtd = { vCard_dtd.LOGO = vCard_dtd.PHOTO; vCard_dtd.SOUND = vCard_dtd.PHOTO; +vCard4_dtd = { + source = "uri", + kind = "text", + xml = "text", + fn = "text", + n = { + values = { + "family", + "given", + "middle", + "prefix", + "suffix", + }, + }, + nickname = "text", + photo = "uri", + bday = "date-and-or-time", + anniversary = "date-and-or-time", + gender = "text", + adr = { + values = { + "pobox", + "ext", + "street", + "locality", + "region", + "code", + "country", + } + }, + tel = "text", + email = "text", + impp = "uri", + lang = "language-tag", + tz = "text", + geo = "uri", + title = "text", + role = "text", + logo = "uri", + org = "text", + member = "uri", + related = "uri", + categories = "text", + note = "text", + prodid = "text", + rev = "timestamp", + sound = "uri", + uid = "uri", + clientpidmap = "number, uuid", + url = "uri", + version = "text", + key = "uri", + fburl = "uri", + caladruri = "uri", + caluri = "uri", +}; + return { from_text = from_text; to_text = to_text; from_xep54 = from_xep54; to_xep54 = to_xep54; + + to_vcard4 = vcards_to_vcard4xml; }; -- cgit v1.2.3 From ed4cff4baa6a091923b8af119dc6dee47e404453 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(-) 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 f8321df6533ed9b1cc0e5945b3a3974df4963a4b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 28 May 2014 22:09:32 +0200 Subject: util.vcard: Add missing local declaration --- util/vcard.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/util/vcard.lua b/util/vcard.lua index 70c923b8..29a40844 100644 --- a/util/vcard.lua +++ b/util/vcard.lua @@ -17,6 +17,7 @@ local from_text, to_text, from_xep54, to_xep54; local line_sep = "\n"; local vCard_dtd; -- See end of file +local vCard4_dtd; local function fold_line() error "Not implemented" --TODO -- cgit v1.2.3 From d90fa5cc55629417e7fd8eaa90538b1ebfbe377d 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(+) 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 61bb7fbe1bc2c7f560f9b7b276d5a3a3541aa906 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(-) 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 7c22fd5ce2de30d91f5d96cb48f78f9085806a89 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(-) 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 a1dbf229a35ea904e520b9a4508f024735a2b4ee 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(-) 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 7fa36918b0198b806059172a6221f3e776cfde53 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(-) 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 97f159a9f2e4914a184899d98080fd1c1438113b 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(-) 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 010963c10efbbe5c9f0cdbfc9f96fe722d211f8f 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(-) 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 2399b7c676168a3dad3555f8c3be2e0e1c9d58cc 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(-) 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 79b2d37386a35b6f3184cb5fdaada94efa9a5a4d 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(-) 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 dea262f569c57679977957ba575d99343cb4cd06 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(-) 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 888029fa95c563e507a63b8291b426b4965130a9 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(-) 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 3f4c83225b50777da921a2a11aac6d07296b2b8c 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(-) 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 55a097981ec6c98f4dd3115e143c066fbe90d8e3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 25 Jun 2014 12:15:00 -0400 Subject: net/server_*: Fix addclient: LuaSocket 3.0-rc1 sometimes returns EALREADY instead of EINPROGRESS when the dns lookup has multiple results --- net/server_event.lua | 2 +- net/server_select.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/server_event.lua b/net/server_event.lua index a3087847..b79fc463 100644 --- a/net/server_event.lua +++ b/net/server_event.lua @@ -761,7 +761,7 @@ do end client:settimeout( 0 ) -- set nonblocking local res, err = client:connect( addr, serverport ) -- connect - if res or ( err == "timeout" ) then + if res or ( err == "timeout" or err == "Operation already in progress" ) then if client.getsockname then addr = client:getsockname( ) end diff --git a/net/server_select.lua b/net/server_select.lua index 4a36617c..0aaea4be 100644 --- a/net/server_select.lua +++ b/net/server_select.lua @@ -966,7 +966,7 @@ local addclient = function( address, port, listeners, pattern, sslctx, typ ) end client:settimeout( 0 ) local ok, err = client:connect( address, port ) - if ok or err == "timeout" then + if ok or err == "timeout" or err == "Operation already in progress" then return wrapclient( client, address, port, listeners, pattern, sslctx ) else return nil, err -- cgit v1.2.3 From bfd2c5fee05474008a1b5df153ed42f00fe16222 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(-) 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 ed90b8cac29a9890dc93d0f43b8cee84c960a1f1 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 9 Jul 2014 08:23:16 +0200 Subject: util.vcard: Add support for uri types in vcard4 --- util/vcard.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/vcard.lua b/util/vcard.lua index 29a40844..152b0b2d 100644 --- a/util/vcard.lua +++ b/util/vcard.lua @@ -348,6 +348,8 @@ local function item_to_vcard4(item) local prop_def = vCard4_dtd[typ]; if prop_def == "text" then t:tag("text"):text(item[1]):up(); + elseif prop_def == "uri" then + t:tag("uri"):text(item[1]):up(); elseif type(prop_def) == "table" then if prop_def.values then for i, v in ipairs(prop_def.values) do -- cgit v1.2.3 From 3e2beb6f79855994744222adf371b3c5be7c9aa4 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(-) 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 ab16d298a1ee0c27f2a7bb9b74f9071983244774 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(-) 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 a2718c16c6009c31883b73ba5d5121e42f647f78 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(-) 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 e4e4477db09bf26c7d9e476035fe747a8878c002 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(-) 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 d6932207f3577d82e93f4bbbec27449ac38f896f 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(+) 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 f137f896830c31e11278a50fe1c2b0853f72f903 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 7 Aug 2014 12:15:15 -0400 Subject: core/storagemanager: When map store isn't available, fallback to keyval store --- core/storagemanager.lua | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/core/storagemanager.lua b/core/storagemanager.lua index 5674ff32..d8a7b2e2 100644 --- a/core/storagemanager.lua +++ b/core/storagemanager.lua @@ -80,11 +80,44 @@ function get_driver(host, store) return driver, driver_name; end +local map_shim_mt = { + __index = { + get = function(self, username, key) + local ret, err = self.keyval_store:get(username); + if ret == nil and err then return nil, err end + return ret[key]; + end; + set = function(self, username, key, data) + local current, err = self.keyval_store:get(username); + if current == nil then + if err then + return nil, err; + else + current = {}; + end + end + current[key] = data; + return self.keyval_store:set(username, current); + end; + }; +} +local function create_map_shim(host, store) + local keyval_store, err = open(host, store, "keyval"); + if keyval_store == nil then return nil, err end + return setmetatable({ + keyval_store = keyval_store; + }, map_shim_mt); +end + function open(host, store, typ) local driver, driver_name = get_driver(host, store); local ret, err = driver:open(store, typ); if not ret then if err == "unsupported-store" then + if typ == "map" then -- Use shim on top of keyval store + log("debug", "map storage driver unavailable, using shim on top of keyval store."); + return create_map_shim(host, store); + end log("debug", "Storage driver %s does not support store %s (%s), falling back to null driver", driver_name, store, typ or ""); ret = null_storage_driver; -- cgit v1.2.3 From fdfdd3105375b0445333fcb76978980acd47e2ea 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(+) 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 272cf5898d21b3f0a595bcef96a9de0ec9c414fa 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(-) 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 b3f68a84c0e4bb904d7de78c338c671872e1ca17 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(-) 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 08034e198e2eb8587a60d731857242b954b80114 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(-) 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 3fbc5f1e6e7bd733be056cceae83cbdceaf9dc1c 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(-) 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 227ec5a23363231da05d349560e5477421454e29 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 8 Aug 2014 12:38:35 +0200 Subject: storagemanager: Fix map store shim if store is empty --- core/storagemanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/storagemanager.lua b/core/storagemanager.lua index d8a7b2e2..b2ad29d0 100644 --- a/core/storagemanager.lua +++ b/core/storagemanager.lua @@ -84,7 +84,7 @@ local map_shim_mt = { __index = { get = function(self, username, key) local ret, err = self.keyval_store:get(username); - if ret == nil and err then return nil, err end + if ret == nil then return nil, err end return ret[key]; end; set = function(self, username, key, data) -- cgit v1.2.3 From 04feababf4d9b63876a06fdd52ed41f775c496b0 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(-) 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 0378ffa9ba9ebb02f5a7b1afe7b39fc9a5ae871f 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(-) 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 ac2ceb011e50fe85fddd319e9367e1fd02e83c01 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(-) 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 6594e3042b1afd3ecf39c841c6e631fa57f1ad18 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(-) 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 aa51ed52ec4359dd1c47f9025b78ab00c985cba9 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(-) 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 24eb372e33f37c917b008d90bf92a6ea8d020e34 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(-) 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 aa0dea0c38d2911bf9a531b78f9b7b3b54bf0865 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(+) 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 c35ba29a9af6e98aa3972e486a37ff6e0090113d 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 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 From 534a115ec7aff123fa3358c500554d02b247e4a8 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 10 Sep 2014 16:47:55 +0200 Subject: util.vcard: Turn PHOTO fields into data-uris --- util/vcard.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/util/vcard.lua b/util/vcard.lua index 152b0b2d..8aafa24d 100644 --- a/util/vcard.lua +++ b/util/vcard.lua @@ -349,7 +349,11 @@ local function item_to_vcard4(item) if prop_def == "text" then t:tag("text"):text(item[1]):up(); elseif prop_def == "uri" then - t:tag("uri"):text(item[1]):up(); + if item.ENCODING and item.ENCODING[1] == 'b' then + t:tag("uri"):text("data:;base64,"):text(item[1]):up(); + else + t:tag("uri"):text(item[1]):up(); + end elseif type(prop_def) == "table" then if prop_def.values then for i, v in ipairs(prop_def.values) do -- cgit v1.2.3