From 76b14ec39b95d3f7b09b4cd3766e87c9fdd7fcd6 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 4 Oct 2013 16:40:27 +0200 Subject: mod_pubsub, util.pubsub: Keep track of the order of items --- plugins/mod_pubsub/mod_pubsub.lua | 2 +- plugins/mod_pubsub/pubsub.lib.lua | 4 ++-- 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 841a59e0e13f566e3c2f324f8c54470dbec0e8e9 Mon Sep 17 00:00:00 2001 From: Florian Zeitz Date: Fri, 4 Oct 2013 18:42:44 +0200 Subject: mod_pep_plus: An util.pubsub based PEP module --- plugins/mod_pep_plus.lua | 368 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 plugins/mod_pep_plus.lua 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 511f7a76a11199b7cd4d13c85f0703f3e7930e3c 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 495b904d7210537b53736f6fd2330a26527ce89c 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 053117d38dcf80f594cc75d9b4f9fc35d400d567 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 c782ef5a75dea9e56a5b890df5306089260a1dcb 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 33de95fb8a9a209c1645035fb4488c0e7cbc3f1e 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 c84cd87f944f1bf63db2c29d299c66f915e47957 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 f725efa472048c323c16dbb544954a94db8b66d2 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 37895d0d715ca071bc1c79554935074bbec3dd1c 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 a3c709b756f0789a33a1bc3703504b29981d2623 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 1bdf48e7d2bb8cc29d9b2d11c004a4b7e6f82515 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 a0bb667fee2d4407b2da4380bc89e949e5f632a2 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 32b5b56170e51bf48bbd1467a84f2134f86e4cdd 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 ca6af8e2cfeb2373224dc5949f1a3e02cd42b81b 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 62ce3bb70888010588030c679fb778c22ab023fb 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 ae044f21b05363570a8e8ee96c716e9b4f42f2b9 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 b771de25145cf3910acde0e698e32201460d67e3 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 5c25cdaa77b2868e99e6ff97d7fd4fe34e115773 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 3e156487cff0779f075a765451cacb2a7c47b52c 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 cf48e76e783abc6925a78a4a3750d3a1cbaaf864 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 195743afe083f3b66a4fa2cd594e75f150e81968 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 2e9fab41473ed7b261f8d5076dfeac0752de1769 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Feb 2014 17:21:47 -0500 Subject: plugins/muc/mod_muc: Move Xep-0307 MUC unique to seperate file --- plugins/mod_muc_unique.lua | 11 +++++++++++ plugins/muc/mod_muc.lua | 31 ++++++++++--------------------- 2 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 plugins/mod_muc_unique.lua diff --git a/plugins/mod_muc_unique.lua b/plugins/mod_muc_unique.lua new file mode 100644 index 00000000..b27fcff6 --- /dev/null +++ b/plugins/mod_muc_unique.lua @@ -0,0 +1,11 @@ +-- XEP-0307: Unique Room Names for Multi-User Chat +local uuid_gen = require "util.uuid".generate; +module:add_feature "http://jabber.org/protocol/muc#unique" +module:hook("iq-get/host/http://jabber.org/protocol/muc#unique:unique", function() + local origin, stanza = event.origin, event.stanza; + origin.send(st.reply(stanza) + :tag("unique", {xmlns = "http://jabber.org/protocol/muc#unique"}) + :text(uuid_gen()) -- FIXME Random UUIDs can theoretically have collisions + ); + return true; +end,-1); diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index c514bafd..5a71ef75 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -31,7 +31,6 @@ local muc_new_room = muclib.new_room; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; -local uuid_gen = require "util.uuid".generate; local um_is_admin = require "core.usermanager".is_admin; local hosts = prosody.hosts; @@ -47,6 +46,7 @@ muclib.set_max_history_length(module:get_option_number("max_history_messages")); module:depends("disco"); module:add_identity("conference", "text", muc_name); module:add_feature("http://jabber.org/protocol/muc"); +module:depends "muc_unique" local function is_admin(jid) return um_is_admin(jid, module.host); @@ -136,25 +136,6 @@ module:hook("host-disco-items", function(event) end end); -local function handle_to_domain(event) - local origin, stanza = event.origin, event.stanza; - local type = stanza.attr.type; - if type == "error" or type == "result" then return; end - if stanza.name == "iq" and type == "get" then - local xmlns = stanza.tags[1].attr.xmlns; - local node = stanza.tags[1].attr.node; - if xmlns == "http://jabber.org/protocol/muc#unique" then - origin.send(st.reply(stanza):tag("unique", {xmlns = xmlns}):text(uuid_gen())); -- FIXME Random UUIDs can theoretically have collisions - else - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc - end - else - host_room:handle_stanza(origin, stanza); - --origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); - end - return true; -end - function stanza_handler(event) local origin, stanza = event.origin, event.stanza; local bare = jid_bare(stanza.attr.to); @@ -187,7 +168,15 @@ module:hook("presence/bare", stanza_handler, -1); module:hook("iq/full", stanza_handler, -1); module:hook("message/full", stanza_handler, -1); module:hook("presence/full", stanza_handler, -1); -module:hook("iq/host", handle_to_domain, -1); + +local function handle_to_domain(event) + local origin, stanza = event.origin, event.stanza; + local type = stanza.attr.type; + if type == "error" then return; end + host_room:handle_stanza(origin, stanza); + -- origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); + return true; +end module:hook("message/host", handle_to_domain, -1); module:hook("presence/host", handle_to_domain, -1); -- cgit v1.2.3 From e6b65c8d67e18a36af5f9a30cd2afc8c075814b8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Feb 2014 17:39:57 -0500 Subject: plugins/muc/muc.lib: Split out `send_history` into `parse_history` and `get_history` --- plugins/muc/muc.lib.lua | 95 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 33 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d09c768e..1e3a41e3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -9,6 +9,7 @@ local select = select; local pairs, ipairs = pairs, ipairs; +local gettime = os.time; local datetime = require "util.datetime"; local dataform = require "util.dataforms"; @@ -145,47 +146,75 @@ function room_mt:send_occupant_list(to) end end end -function room_mt:send_history(to, stanza) - local history = self._data['history']; -- send discussion history - if history then - local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc"); - local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); - local maxchars = history_tag and tonumber(history_tag.attr.maxchars); - if maxchars then maxchars = math.floor(maxchars); end +local function parse_history(stanza) + local x_tag = stanza:get_child("x", "http://jabber.org/protocol/muc"); + local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); + if not history_tag then + return nil, 20, nil + end - local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history); - if not history_tag then maxstanzas = 20; end + local maxchars = tonumber(history_tag.attr.maxchars); - local seconds = history_tag and tonumber(history_tag.attr.seconds); - if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end + local maxstanzas = tonumber(history_tag.attr.maxstanzas); - local since = history_tag and history_tag.attr.since; - if since then since = datetime.parse(since); since = since and datetime.datetime(since); end - if seconds and (not since or since < seconds) then since = seconds; end + -- messages received since the UTC datetime specified + local since = history_tag.attr.since; + if since then + since = datetime.parse(since); + end - local n = 0; - local charcount = 0; + -- messages received in the last "X" seconds. + local seconds = tonumber(history_tag.attr.seconds); + if seconds then + seconds = gettime() - seconds + if since then + since = math.max(since, seconds); + else + since = seconds; + end + end - for i=#history,1,-1 do - local entry = history[i]; - if maxchars then - if not entry.chars then - entry.stanza.attr.to = ""; - entry.chars = #tostring(entry.stanza); - end - charcount = charcount + entry.chars + #to; - if charcount > maxchars then break; end + return maxchars, maxstanzas, since +end +-- Get history for 'to' +function room_mt:get_history(to, maxchars, maxstanzas, since) + local history = self._data['history']; -- send discussion history + if not history then return end + local history_len = #history + + maxstanzas = maxstanzas or history_len + local n = 0; + local charcount = 0; + for i=history_len,1,-1 do + local entry = history[i]; + if maxchars then + if not entry.chars then + entry.stanza.attr.to = ""; + entry.chars = #tostring(entry.stanza); end - if since and since > entry.stamp then break; end - if n + 1 > maxstanzas then break; end - n = n + 1; - end - for i=#history-n+1,#history do - local msg = history[i].stanza; - msg.attr.to = to; - self:_route_stanza(msg); + charcount = charcount + entry.chars + #to; + if charcount > maxchars then break; end end + if since and since > entry.stamp then break; end + if n + 1 > maxstanzas then break; end + n = n + 1; + end + + local i = history_len-n+1 + return function() + if i > history_len then return nil end + local entry = history[i] + local msg = entry.stanza + msg.attr.to = to; + i = i + 1 + return msg + end +end +function room_mt:send_history(to, stanza) + local maxchars, maxstanzas, since = parse_history(stanza) + for msg in self:get_history(to, maxchars, maxstanzas, since) do + self:_route_stanza(msg); end end function room_mt:send_subject(to) -- cgit v1.2.3 From 2411decc0c9128750099d8ab1520c4ede4e3b751 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Feb 2014 14:36:49 -0500 Subject: plugins/muc/muc.lib: Split up `handle_to_room` into smaller handlers (thanks sysko) --- plugins/muc/muc.lib.lua | 342 ++++++++++++++++++++++++++---------------------- 1 file changed, 186 insertions(+), 156 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1e3a41e3..0b82f91c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -771,187 +771,207 @@ function room_mt:destroy(newjid, reason, password) module:fire_event("muc-room-destroyed", { room = self }); end -function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc +function room_mt:handle_iq_to_room(origin, stanza) local type = stanza.attr.type; local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; - if stanza.name == "iq" then - if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_info(stanza)); - elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_items(stanza)); - elseif xmlns == "http://jabber.org/protocol/muc#admin" then - local actor = stanza.attr.from; - local affiliation = self:get_affiliation(actor); - local current_nick = self._jid_nick[actor]; - local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); - local item = stanza.tags[1].tags[1]; - if item and item.name == "item" then - if type == "set" then - local callback = function() origin.send(st.reply(stanza)); end - if item.attr.jid then -- Validate provided JID - item.attr.jid = jid_prep(item.attr.jid); - if not item.attr.jid then - origin.send(st.error_reply(stanza, "modify", "jid-malformed")); - return; - end - end - if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation - local occupant = self._occupants[self.jid.."/"..item.attr.nick]; - if occupant then item.attr.jid = occupant.jid; end - elseif not item.attr.nick and item.attr.jid then - local nick = self._jid_nick[item.attr.jid]; - if nick then item.attr.nick = select(3, jid_split(nick)); end - end - local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; - if item.attr.affiliation and item.attr.jid and not item.attr.role then - local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - elseif item.attr.role and item.attr.nick and not item.attr.affiliation then - local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); + if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then + origin.send(self:get_disco_info(stanza)); + elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then + origin.send(self:get_disco_items(stanza)); + elseif xmlns == "http://jabber.org/protocol/muc#admin" then + local actor = stanza.attr.from; + local affiliation = self:get_affiliation(actor); + local current_nick = self._jid_nick[actor]; + local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); + local item = stanza.tags[1].tags[1]; + if item and item.name == "item" then + if type == "set" then + local callback = function() origin.send(st.reply(stanza)); end + if item.attr.jid then -- Validate provided JID + item.attr.jid = jid_prep(item.attr.jid); + if not item.attr.jid then + origin.send(st.error_reply(stanza, "modify", "jid-malformed")); + return; end - elseif type == "get" then - local _aff = item.attr.affiliation; - local _rol = item.attr.role; - if _aff and not _rol then - if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for jid, affiliation in pairs(self._affiliations) do - if affiliation == _aff then - reply:tag("item", {affiliation = _aff, jid = jid}):up(); - end + end + if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation + local occupant = self._occupants[self.jid.."/"..item.attr.nick]; + if occupant then item.attr.jid = occupant.jid; end + elseif not item.attr.nick and item.attr.jid then + local nick = self._jid_nick[item.attr.jid]; + if nick then item.attr.nick = select(3, jid_split(nick)); end + end + local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; + if item.attr.affiliation and item.attr.jid and not item.attr.role then + local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + elseif item.attr.role and item.attr.nick and not item.attr.affiliation then + local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + end + elseif type == "get" then + local _aff = item.attr.affiliation; + local _rol = item.attr.role; + if _aff and not _rol then + if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for jid, affiliation in pairs(self._affiliations) do + if affiliation == _aff then + reply:tag("item", {affiliation = _aff, jid = jid}):up(); end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); end - elseif _rol and not _aff then - if role == "moderator" then - -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? - if _rol == "none" then _rol = nil; end - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for occupant_jid, occupant in pairs(self._occupants) do - if occupant.role == _rol then - reply:tag("item", { - nick = select(3, jid_split(occupant_jid)), - role = _rol or "none", - affiliation = occupant.affiliation or "none", - jid = occupant.jid - }):up(); - end + origin.send(reply); + else + origin.send(st.error_reply(stanza, "auth", "forbidden")); + end + elseif _rol and not _aff then + if role == "moderator" then + -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? + if _rol == "none" then _rol = nil; end + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for occupant_jid, occupant in pairs(self._occupants) do + if occupant.role == _rol then + reply:tag("item", { + nick = select(3, jid_split(occupant_jid)), + role = _rol or "none", + affiliation = occupant.affiliation or "none", + jid = occupant.jid + }):up(); end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); end + origin.send(reply); else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); + origin.send(st.error_reply(stanza, "auth", "forbidden")); end + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "bad-request")); end - elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then - if self:get_affiliation(stanza.attr.from) ~= "owner" then - origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); - elseif stanza.attr.type == "get" then - self:send_form(origin, stanza); - elseif stanza.attr.type == "set" then - local child = stanza.tags[1].tags[1]; - if not child then - origin.send(st.error_reply(stanza, "modify", "bad-request")); - elseif child.name == "destroy" then - local newjid = child.attr.jid; - local reason, password; - for _,tag in ipairs(child.tags) do - if tag.name == "reason" then - reason = #tag.tags == 0 and tag[1]; - elseif tag.name == "password" then - password = #tag.tags == 0 and tag[1]; - end + elseif type == "set" or type == "get" then + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + end + elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then + if self:get_affiliation(stanza.attr.from) ~= "owner" then + origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); + elseif stanza.attr.type == "get" then + self:send_form(origin, stanza); + elseif stanza.attr.type == "set" then + local child = stanza.tags[1].tags[1]; + if not child then + origin.send(st.error_reply(stanza, "modify", "bad-request")); + elseif child.name == "destroy" then + local newjid = child.attr.jid; + local reason, password; + for _,tag in ipairs(child.tags) do + if tag.name == "reason" then + reason = #tag.tags == 0 and tag[1]; + elseif tag.name == "password" then + password = #tag.tags == 0 and tag[1]; end - self:destroy(newjid, reason, password); - origin.send(st.reply(stanza)); - else - self:process_form(origin, stanza); end + self:destroy(newjid, reason, password); + origin.send(st.reply(stanza)); + else + self:process_form(origin, stanza); end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end - elseif stanza.name == "message" and type == "groupchat" then + elseif type == "set" or type == "get" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end +end + +function room_mt:handle_groupchat_to_room(origin, stanza) + local from = stanza.attr.from; + local current_nick = self._jid_nick[from]; + local occupant = self._occupants[current_nick]; + if not occupant then -- not in room + origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + elseif occupant.role == "visitor" then + origin.send(st.error_reply(stanza, "auth", "forbidden")); + else local from = stanza.attr.from; - local current_nick = self._jid_nick[from]; - local occupant = self._occupants[current_nick]; - if not occupant then -- not in room - origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); - elseif occupant.role == "visitor" then - origin.send(st.error_reply(stanza, "auth", "forbidden")); - else - local from = stanza.attr.from; - stanza.attr.from = current_nick; - local subject = stanza:get_child_text("subject"); - if subject then - if occupant.role == "moderator" or - ( self._data.changesubject and occupant.role == "participant" ) then -- and participant - self:set_subject(current_nick, subject); - else - stanza.attr.from = from; - origin.send(st.error_reply(stanza, "auth", "forbidden")); - end + stanza.attr.from = current_nick; + local subject = stanza:get_child_text("subject"); + if subject then + if occupant.role == "moderator" or + ( self._data.changesubject and occupant.role == "participant" ) then -- and participant + self:set_subject(current_nick, subject); else - self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); + stanza.attr.from = from; + origin.send(st.error_reply(stanza, "auth", "forbidden")); end - stanza.attr.from = from; + else + self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); end - elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then - local current_nick = self._jid_nick[stanza.attr.from]; - log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick + stanza.attr.from = from; + end +end + + +function room_mt:handle_kickable_to_room(origin, stanza) + local current_nick = self._jid_nick[stanza.attr.from]; + log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); + self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable +end + +-- hack - some buggy clients send presence updates to the room rather than their nick +function room_mt:handle_presence_to_room(origin, stanza) + local type = stanza.attr.type; + local current_nick = self._jid_nick[stanza.attr.from]; + if current_nick then local to = stanza.attr.to; - local current_nick = self._jid_nick[stanza.attr.from]; - if current_nick then - stanza.attr.to = current_nick; - self:handle_to_occupant(origin, stanza); - stanza.attr.to = to; - elseif type ~= "error" and type ~= "result" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + stanza.attr.to = current_nick; + self:handle_to_occupant(origin, stanza); + stanza.attr.to = to; + elseif type ~= "error" and type ~= "result" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end +end + +function room_mt:handle_invite_to_room(origin, stanza, payload) + local _from, _to = stanza.attr.from, stanza.attr.to; + local _invitee = jid_prep(payload.attr.to); + if _invitee then + local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; + local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('invite', {from=_from}) + :tag('reason'):text(_reason or ""):up() + :up(); + if self:get_password() then + invite:tag("password"):text(self:get_password()):up(); + end + invite:up() + :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this + :text(_reason or "") + :up() + :tag('body') -- Add a plain message for clients which don't support invites + :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) + :up(); + if self:get_members_only() and not self:get_affiliation(_invitee) then + log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); + self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) end - elseif stanza.name == "message" and not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 + self:_route_stanza(invite); + else + origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); + end +end + +function room_mt:handle_message_to_room(origin, stanza) + local type = stanza.attr.type; + if type == "groupchat" then + return self:handle_groupchat_to_room(origin, stanza) + elseif type == "error" and is_kickable_error(stanza) then + return self:handle_kickable_to_room(origin, stanza) + elseif not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then local x = stanza.tags[1]; local payload = (#x.tags == 1 and x.tags[1]); if payload and payload.name == "invite" and payload.attr.to then - local _from, _to = stanza.attr.from, stanza.attr.to; - local _invitee = jid_prep(payload.attr.to); - if _invitee then - local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; - local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) - :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) - :tag('invite', {from=_from}) - :tag('reason'):text(_reason or ""):up() - :up(); - if self:get_password() then - invite:tag("password"):text(self:get_password()):up(); - end - invite:up() - :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this - :text(_reason or "") - :up() - :tag('body') -- Add a plain message for clients which don't support invites - :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) - :up(); - if self:get_members_only() and not self:get_affiliation(_invitee) then - log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); - self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) - end - self:_route_stanza(invite); - else - origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); - end + return self:handle_invite_to_room(origin, stanza, payload) else origin.send(st.error_reply(stanza, "cancel", "bad-request")); end @@ -961,6 +981,16 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha end end +function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc + if stanza.name == "iq" then + return self:handle_iq_to_room(origin, stanza) + elseif stanza.name == "message" then + return self:handle_message_to_room(origin, stanza) + elseif stanza.name == "presence" then + return self:handle_presence_to_room(origin, stanza) + end +end + function room_mt:handle_stanza(origin, stanza) local to_node, to_host, to_resource = jid_split(stanza.attr.to); if to_resource then -- cgit v1.2.3 From 81f26ff7290a30f1e16d3f6ca35e5ffe7ec9a0f2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Feb 2014 16:50:18 -0500 Subject: plugins/muc/muc.lib: Factor `handle_to_occupant` out into many functions --- plugins/muc/muc.lib.lua | 455 ++++++++++++++++++++++++++++-------------------- 1 file changed, 269 insertions(+), 186 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 0b82f91c..7cf857a4 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -404,212 +404,295 @@ local function deconstruct_stanza_id(room, stanza) end end +function room_mt:handle_presence_error_to_occupant(origin, stanza) + local current_nick = self._jid_nick[stanza.attr.from]; + if not current_nick then + return true -- discard + end + log("debug", "kicking %s from %s", current_nick, self.jid); + return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)) +end -function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc +function room_mt:handle_unavailable_to_occupant(origin, stanza) + local from = stanza.attr.from; + local current_nick = self._jid_nick[from]; + if not current_nick then + return true; -- discard + end + local pr = get_filtered_presence(stanza); + pr.attr.from = current_nick; + log("debug", "%s leaving %s", current_nick, self.jid); + self._jid_nick[from] = nil; + local occupant = self._occupants[current_nick]; + local new_jid = next(occupant.sessions); + if new_jid == from then new_jid = next(occupant.sessions, new_jid); end + if new_jid then + local jid = occupant.jid; + occupant.jid = new_jid; + occupant.sessions[from] = nil; + pr.attr.to = from; + pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up() + :tag("status", {code='110'}):up(); + self:_route_stanza(pr); + if jid ~= new_jid then + pr = st.clone(occupant.sessions[new_jid]) + :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"}); + pr.attr.from = current_nick; + self:broadcast_except_nick(pr, current_nick); + end + else + occupant.role = 'none'; + self:broadcast_presence(pr, from); + self._occupants[current_nick] = nil; + end + return true; +end + +function room_mt:handle_occupant_presence(origin, stanza) + local from = stanza.attr.from; + local pr = get_filtered_presence(stanza); + local current_nick = stanza.attr.to + pr.attr.from = current_nick; + log("debug", "%s broadcasted presence", current_nick); + self._occupants[current_nick].sessions[from] = pr; + self:broadcast_presence(pr, from); + return true; +end + +function room_mt:handle_change_nick(origin, stanza, current_nick, to) + local from = stanza.attr.from; + local occupant = self._occupants[current_nick]; + local is_multisession = next(occupant.sessions, next(occupant.sessions)); + if self._occupants[to] or is_multisession then + log("debug", "%s couldn't change nick", current_nick); + local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + reply.tags[1].attr.code = "409"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + else + local data = self._occupants[current_nick]; + local to_nick = select(3, jid_split(to)); + log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); + local p = st.presence({type='unavailable', from=current_nick}); + self:broadcast_presence(p, from, '303', to_nick); + self._occupants[current_nick] = nil; + self._occupants[to] = data; + self._jid_nick[from] = to; + local pr = get_filtered_presence(stanza); + pr.attr.from = to; + self._occupants[to].sessions[from] = pr; + self:broadcast_presence(pr, from); + return true; + end +end + +function room_mt:handle_join(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + log("debug", "%s joining as %s", from, to); + if not next(self._affiliations) then -- new room, no owners + self._affiliations[jid_bare(from)] = "owner"; + if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then + self.locked = nil; -- Older groupchat protocol doesn't lock + end + elseif self.locked then -- Deny entry + origin.send(st.error_reply(stanza, "cancel", "item-not-found")); + return true; + end + local affiliation = self:get_affiliation(from); + local role = self:get_default_role(affiliation) + if role then -- new occupant + local is_merge = not not self._occupants[to] + if not is_merge then + self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}}; + else + self._occupants[to].sessions[from] = get_filtered_presence(stanza); + end + self._jid_nick[from] = to; + self:send_occupant_list(from); + local pr = get_filtered_presence(stanza); + pr.attr.from = to; + pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) + :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up(); + if not is_merge then + self:broadcast_except_nick(pr, to); + end + pr:tag("status", {code='110'}):up(); + if self._data.whois == 'anyone' then + pr:tag("status", {code='100'}):up(); + end + if self.locked then + pr:tag("status", {code='201'}):up(); + end + pr.attr.to = from; + self:_route_stanza(pr); + self:send_history(from, stanza); + self:send_subject(from); + return true; + elseif not affiliation then -- registration required for entering members-only room + local reply = st.error_reply(stanza, "auth", "registration-required"):up(); + reply.tags[1].attr.code = "407"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + else -- banned + local reply = st.error_reply(stanza, "auth", "forbidden"):up(); + reply.tags[1].attr.code = "403"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end +end + +function room_mt:handle_available_to_occupant(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + local current_nick = self._jid_nick[from]; + if current_nick then + --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence + if current_nick == to then -- simple presence + return self:handle_occupant_presence(origin, stanza) + else -- change nick + return self:handle_change_nick(origin, stanza, current_nick, to) + end + --else -- possible rejoin + -- log("debug", "%s had connection replaced", current_nick); + -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}) + -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable + -- self:handle_to_occupant(origin, stanza); -- resend available + --end + else -- enter room + local new_nick = to; + if self._occupants[to] then + if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then + new_nick = nil; + end + end + local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); + password = password and password:get_child("password", "http://jabber.org/protocol/muc"); + password = password and password[1] ~= "" and password[1]; + if self:get_password() and self:get_password() ~= password then + log("debug", "%s couldn't join due to invalid password: %s", from, to); + local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); + reply.tags[1].attr.code = "401"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + elseif not new_nick then + log("debug", "%s couldn't join due to nick conflict: %s", from, to); + local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + reply.tags[1].attr.code = "409"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + else + return self:handle_join(origin, stanza) + end + end +end + +function room_mt:handle_presence_to_occupant(origin, stanza) + local type = stanza.attr.type; + if type == "error" then -- error, kick em out! + return self:handle_presence_error_to_occupant(origin, stanza) + elseif type == "unavailable" then -- unavailable + return self:handle_unavailable_to_occupant(origin, stanza) + elseif not type then -- available + return self:handle_available_to_occupant(origin, stanza) + elseif type ~= 'result' then -- bad type + if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences + origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? + end + end + return true; +end + +function room_mt:handle_private(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local room = jid_bare(to); local current_nick = self._jid_nick[from]; local type = stanza.attr.type; - log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); - if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end - if stanza.name == "presence" then - local pr = get_filtered_presence(stanza); - pr.attr.from = current_nick; - if type == "error" then -- error, kick em out! - if current_nick then - log("debug", "kicking %s from %s", current_nick, room); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); + local o_data = self._occupants[to]; + if o_data then + log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); + if stanza.name == "iq" then + local id = stanza.attr.id; + if stanza.attr.type == "get" or stanza.attr.type == "set" then + stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); + else + stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); end - elseif type == "unavailable" then -- unavailable - if current_nick then - log("debug", "%s leaving %s", current_nick, room); - self._jid_nick[from] = nil; - local occupant = self._occupants[current_nick]; - local new_jid = next(occupant.sessions); - if new_jid == from then new_jid = next(occupant.sessions, new_jid); end - if new_jid then - local jid = occupant.jid; - occupant.jid = new_jid; - occupant.sessions[from] = nil; - pr.attr.to = from; - pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up() - :tag("status", {code='110'}):up(); - self:_route_stanza(pr); - if jid ~= new_jid then - pr = st.clone(occupant.sessions[new_jid]) - :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"}); - pr.attr.from = current_nick; - self:broadcast_except_nick(pr, current_nick); - end - else - occupant.role = 'none'; - self:broadcast_presence(pr, from); - self._occupants[current_nick] = nil; - end + if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then + stanza.attr.to = jid_bare(stanza.attr.to); end - elseif not type then -- available - if current_nick then - --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence - if current_nick == to then -- simple presence - log("debug", "%s broadcasted presence", current_nick); - self._occupants[current_nick].sessions[from] = pr; - self:broadcast_presence(pr, from); - else -- change nick - local occupant = self._occupants[current_nick]; - local is_multisession = next(occupant.sessions, next(occupant.sessions)); - if self._occupants[to] or is_multisession then - log("debug", "%s couldn't change nick", current_nick); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); - reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - else - local data = self._occupants[current_nick]; - local to_nick = select(3, jid_split(to)); - if to_nick then - log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); - local p = st.presence({type='unavailable', from=current_nick}); - self:broadcast_presence(p, from, '303', to_nick); - self._occupants[current_nick] = nil; - self._occupants[to] = data; - self._jid_nick[from] = to; - pr.attr.from = to; - self._occupants[to].sessions[from] = pr; - self:broadcast_presence(pr, from); - else - --TODO malformed-jid - end - end - end - --else -- possible rejoin - -- log("debug", "%s had connection replaced", current_nick); - -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}) - -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable - -- self:handle_to_occupant(origin, stanza); -- resend available - --end - else -- enter room - local new_nick = to; - local is_merge; - if self._occupants[to] then - if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then - new_nick = nil; - end - is_merge = true; - end - local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); - password = password and password:get_child("password", "http://jabber.org/protocol/muc"); - password = password and password[1] ~= "" and password[1]; - if self:get_password() and self:get_password() ~= password then - log("debug", "%s couldn't join due to invalid password: %s", from, to); - local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); - reply.tags[1].attr.code = "401"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - elseif not new_nick then - log("debug", "%s couldn't join due to nick conflict: %s", from, to); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); - reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - else - log("debug", "%s joining as %s", from, to); - if not next(self._affiliations) then -- new room, no owners - self._affiliations[jid_bare(from)] = "owner"; - if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then - self.locked = nil; -- Older groupchat protocol doesn't lock - end - elseif self.locked then -- Deny entry - origin.send(st.error_reply(stanza, "cancel", "item-not-found")); - return; - end - local affiliation = self:get_affiliation(from); - local role = self:get_default_role(affiliation) - if role then -- new occupant - if not is_merge then - self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}}; - else - self._occupants[to].sessions[from] = get_filtered_presence(stanza); - end - self._jid_nick[from] = to; - self:send_occupant_list(from); - pr.attr.from = to; - pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up(); - if not is_merge then - self:broadcast_except_nick(pr, to); - end - pr:tag("status", {code='110'}):up(); - if self._data.whois == 'anyone' then - pr:tag("status", {code='100'}):up(); - end - if self.locked then - pr:tag("status", {code='201'}):up(); - end - pr.attr.to = from; - self:_route_stanza(pr); - self:send_history(from, stanza); - self:send_subject(from); - elseif not affiliation then -- registration required for entering members-only room - local reply = st.error_reply(stanza, "auth", "registration-required"):up(); - reply.tags[1].attr.code = "407"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - else -- banned - local reply = st.error_reply(stanza, "auth", "forbidden"):up(); - reply.tags[1].attr.code = "403"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - end - end + if stanza.attr.id then + self:_route_stanza(stanza); end - elseif type ~= 'result' then -- bad type - if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences - origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? + stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; + else -- message + stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); + stanza.attr.from = current_nick; + for jid in pairs(o_data.sessions) do + stanza.attr.to = jid; + self:_route_stanza(stanza); end + stanza.attr.from, stanza.attr.to = from, to; end - elseif not current_nick then -- not in room - if (type == "error" or type == "result") and stanza.name == "iq" then + elseif type ~= "error" and type ~= "result" then -- recipient not in room + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + end + return true; +end + +function room_mt:handle_iq_to_occupant(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + local current_nick = self._jid_nick[from]; + if not current_nick then + local type = stanza.attr.type; + if (type == "error" or type == "result") then local id = stanza.attr.id; stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); if stanza.attr.id then self:_route_stanza(stanza); end stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - elseif type ~= "error" then + else origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); end - elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM + return true; + else + return self:handle_private(origin, stanza) + end +end + +function room_mt:handle_message_to_occupant(origin, stanza) + local current_nick = self._jid_nick[stanza.attr.from]; + local type = stanza.attr.type; + if not current_nick then -- not in room + if type ~= "error" then + origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + end + return true; + end + if type == "groupchat" then -- groupchat messages not allowed in PM origin.send(st.error_reply(stanza, "modify", "bad-request")); - elseif current_nick and stanza.name == "message" and type == "error" and is_kickable_error(stanza) then + return true; + elseif type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable + return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable else -- private stanza - local o_data = self._occupants[to]; - if o_data then - log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); - if stanza.name == "iq" then - local id = stanza.attr.id; - if stanza.attr.type == "get" or stanza.attr.type == "set" then - stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); - else - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - end - if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then - stanza.attr.to = jid_bare(stanza.attr.to); - end - if stanza.attr.id then - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - else -- message - stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); - stanza.attr.from = current_nick; - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to = from, to; - end - elseif type ~= "error" and type ~= "result" then -- recipient not in room - origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); - end + return self:handle_private(origin, stanza) + end +end + +function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc + local from, to = stanza.attr.from, stanza.attr.to; + local room = jid_bare(to); + local current_nick = self._jid_nick[from]; + local type = stanza.attr.type; + log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); + if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end + if stanza.name == "presence" then + return self:handle_presence_to_occupant(origin, stanza) + elseif stanza.name == "iq" then + return self:handle_iq_to_occupant(origin, stanza) + elseif stanza.name == "message" then + return self:handle_message_to_occupant(origin, stanza) end end -- cgit v1.2.3 From 481464e64c46d8e81dde06fa4d05d44f82c9078b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 15:48:26 -0500 Subject: plugins/muc/muc.lib: Split out the room iq handler into functions --- plugins/muc/muc.lib.lua | 216 +++++++++++++++++++++++++++++------------------- 1 file changed, 129 insertions(+), 87 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7cf857a4..96caa2eb 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -854,114 +854,156 @@ function room_mt:destroy(newjid, reason, password) module:fire_event("muc-room-destroyed", { room = self }); end +function room_mt:handle_admin_item_set_command(origin, stanza) + local item = stanza.tags[1].tags[1]; + if item.attr.jid then -- Validate provided JID + item.attr.jid = jid_prep(item.attr.jid); + if not item.attr.jid then + origin.send(st.error_reply(stanza, "modify", "jid-malformed")); + return true; + end + end + if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation + local occupant = self._occupants[self.jid.."/"..item.attr.nick]; + if occupant then item.attr.jid = occupant.jid; end + elseif not item.attr.nick and item.attr.jid then + local nick = self._jid_nick[item.attr.jid]; + if nick then item.attr.nick = select(3, jid_split(nick)); end + end + local actor = stanza.attr.from; + local callback = function() origin.send(st.reply(stanza)); end + local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; + if item.attr.affiliation and item.attr.jid and not item.attr.role then + local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + return true; + elseif item.attr.role and item.attr.nick and not item.attr.affiliation then + local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + return true; + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; + end +end + +function room_mt:handle_admin_item_get_command(origin, stanza) + local actor = stanza.attr.from; + local affiliation = self:get_affiliation(actor); + local current_nick = self._jid_nick[actor]; + local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); + local item = stanza.tags[1].tags[1]; + local _aff = item.attr.affiliation; + local _rol = item.attr.role; + if _aff and not _rol then + if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for jid, affiliation in pairs(self._affiliations) do + if affiliation == _aff then + reply:tag("item", {affiliation = _aff, jid = jid}):up(); + end + end + origin.send(reply); + return true; + else + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end + elseif _rol and not _aff then + if role == "moderator" then + -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? + if _rol == "none" then _rol = nil; end + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + for occupant_jid, occupant in pairs(self._occupants) do + if occupant.role == _rol then + reply:tag("item", { + nick = select(3, jid_split(occupant_jid)), + role = _rol or "none", + affiliation = occupant.affiliation or "none", + jid = occupant.jid + }):up(); + end + end + origin.send(reply); + return true; + else + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end + else + origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; + end +end + +function room_mt:handle_owner_query_get_to_room(origin, stanza) + if self:get_affiliation(stanza.attr.from) ~= "owner" then + origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); + return true; + end + + self:send_form(origin, stanza); + return true; +end +function room_mt:handle_owner_query_set_to_room(origin, stanza) + if self:get_affiliation(stanza.attr.from) ~= "owner" then + origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); + return true; + end + + local child = stanza.tags[1].tags[1]; + if not child then + origin.send(st.error_reply(stanza, "modify", "bad-request")); + return true; + elseif child.name == "destroy" then + local newjid = child.attr.jid; + local reason, password; + for _,tag in ipairs(child.tags) do + if tag.name == "reason" then + reason = #tag.tags == 0 and tag[1]; + elseif tag.name == "password" then + password = #tag.tags == 0 and tag[1]; + end + end + self:destroy(newjid, reason, password); + origin.send(st.reply(stanza)); + return true; + else + self:process_form(origin, stanza); + return true; + end +end + function room_mt:handle_iq_to_room(origin, stanza) local type = stanza.attr.type; local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then origin.send(self:get_disco_info(stanza)); + return true; elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then origin.send(self:get_disco_items(stanza)); + return true; elseif xmlns == "http://jabber.org/protocol/muc#admin" then - local actor = stanza.attr.from; - local affiliation = self:get_affiliation(actor); - local current_nick = self._jid_nick[actor]; - local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); local item = stanza.tags[1].tags[1]; if item and item.name == "item" then if type == "set" then - local callback = function() origin.send(st.reply(stanza)); end - if item.attr.jid then -- Validate provided JID - item.attr.jid = jid_prep(item.attr.jid); - if not item.attr.jid then - origin.send(st.error_reply(stanza, "modify", "jid-malformed")); - return; - end - end - if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation - local occupant = self._occupants[self.jid.."/"..item.attr.nick]; - if occupant then item.attr.jid = occupant.jid; end - elseif not item.attr.nick and item.attr.jid then - local nick = self._jid_nick[item.attr.jid]; - if nick then item.attr.nick = select(3, jid_split(nick)); end - end - local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; - if item.attr.affiliation and item.attr.jid and not item.attr.role then - local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - elseif item.attr.role and item.attr.nick and not item.attr.affiliation then - local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); - end + return self:handle_admin_item_set_command(origin, stanza) elseif type == "get" then - local _aff = item.attr.affiliation; - local _rol = item.attr.role; - if _aff and not _rol then - if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for jid, affiliation in pairs(self._affiliations) do - if affiliation == _aff then - reply:tag("item", {affiliation = _aff, jid = jid}):up(); - end - end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); - end - elseif _rol and not _aff then - if role == "moderator" then - -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? - if _rol == "none" then _rol = nil; end - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for occupant_jid, occupant in pairs(self._occupants) do - if occupant.role == _rol then - reply:tag("item", { - nick = select(3, jid_split(occupant_jid)), - role = _rol or "none", - affiliation = occupant.affiliation or "none", - jid = occupant.jid - }):up(); - end - end - origin.send(reply); - else - origin.send(st.error_reply(stanza, "auth", "forbidden")); - end - else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); - end + return self:handle_admin_item_get_command(origin, stanza) end elseif type == "set" or type == "get" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; end elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then - if self:get_affiliation(stanza.attr.from) ~= "owner" then - origin.send(st.error_reply(stanza, "auth", "forbidden", "Only owners can configure rooms")); - elseif stanza.attr.type == "get" then - self:send_form(origin, stanza); + if stanza.attr.type == "get" then + return self:handle_owner_query_get_to_room(origin, stanza) elseif stanza.attr.type == "set" then - local child = stanza.tags[1].tags[1]; - if not child then - origin.send(st.error_reply(stanza, "modify", "bad-request")); - elseif child.name == "destroy" then - local newjid = child.attr.jid; - local reason, password; - for _,tag in ipairs(child.tags) do - if tag.name == "reason" then - reason = #tag.tags == 0 and tag[1]; - elseif tag.name == "password" then - password = #tag.tags == 0 and tag[1]; - end - end - self:destroy(newjid, reason, password); - origin.send(st.reply(stanza)); - else - self:process_form(origin, stanza); - end + return self:handle_owner_query_set_to_room(origin, stanza) end elseif type == "set" or type == "get" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + return true; end end -- cgit v1.2.3 From 126212dee38bcfdd5f516bbefceecf2621b0ec95 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 16:30:43 -0500 Subject: plugins/muc/muc.lib: Refactor _to_occupant handlers --- plugins/muc/muc.lib.lua | 99 +++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 96caa2eb..54511ede 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -603,65 +603,45 @@ function room_mt:handle_presence_to_occupant(origin, stanza) return true; end -function room_mt:handle_private(origin, stanza) +function room_mt:handle_iq_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; local type = stanza.attr.type; - local o_data = self._occupants[to]; - if o_data then - log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid); - if stanza.name == "iq" then - local id = stanza.attr.id; - if stanza.attr.type == "get" or stanza.attr.type == "set" then - stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); - else - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - end - if type == 'get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then - stanza.attr.to = jid_bare(stanza.attr.to); - end - if stanza.attr.id then - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - else -- message - stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); - stanza.attr.from = current_nick; - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to = from, to; + local id = stanza.attr.id; + if (type == "error" or type == "result") then + stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); + log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); + if stanza.attr.id then + self:_route_stanza(stanza); end - elseif type ~= "error" and type ~= "result" then -- recipient not in room - origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); - end - return true; -end - -function room_mt:handle_iq_to_occupant(origin, stanza) - local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; - if not current_nick then - local type = stanza.attr.type; - if (type == "error" or type == "result") then - local id = stanza.attr.id; - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - if stanza.attr.id then - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; - else + stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; + return true; + else -- Type is "get" or "set" + local current_nick = self._jid_nick[from]; + if not current_nick then origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + return true; + end + local o_data = self._occupants[to]; + if not o_data then -- recipient not in room + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + return true; + end + stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); + log("debug", "%s sent private iq stanza to %s (%s)", from, to, o_data.jid); + if stanza.tags[1].attr.xmlns == 'vcard-temp' then + stanza.attr.to = jid_bare(stanza.attr.to); end + if stanza.attr.id then + self:_route_stanza(stanza); + end + stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; - else - return self:handle_private(origin, stanza) end end function room_mt:handle_message_to_occupant(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; + local from, to = stanza.attr.from, stanza.attr.to; + local current_nick = self._jid_nick[from]; local type = stanza.attr.type; if not current_nick then -- not in room if type ~= "error" then @@ -674,17 +654,30 @@ function room_mt:handle_message_to_occupant(origin, stanza) return true; elseif type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - else -- private stanza - return self:handle_private(origin, stanza) + self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable + return true; end + + local o_data = self._occupants[to]; + if not o_data then + origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); + return true; + end + log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid); + stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); + stanza.attr.from = current_nick; + for jid in pairs(o_data.sessions) do + stanza.attr.to = jid; + self:_route_stanza(stanza); + end + stanza.attr.from, stanza.attr.to = from, to; + return true; end function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc local from, to = stanza.attr.from, stanza.attr.to; local room = jid_bare(to); local current_nick = self._jid_nick[from]; - local type = stanza.attr.type; log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end if stanza.name == "presence" then -- cgit v1.2.3 From 87f32d9c9a8f07c453b7adec9253a3a2e6630716 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 17:17:01 -0500 Subject: plugins/muc/muc.lib: Move (de)construct_stanza_id into `handle_iq_to_occupant` It is the only place they were used; and I left the old function names in as comments. One reason for doing this was to reduce accesses to _occupants; which may be in a database in future revisions --- plugins/muc/muc.lib.lua | 57 +++++++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 54511ede..ce4fa923 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -380,30 +380,6 @@ function room_mt:get_whois() return self._data.whois; end -local function construct_stanza_id(room, stanza) - local from_jid, to_nick = stanza.attr.from, stanza.attr.to; - local from_nick = room._jid_nick[from_jid]; - local occupant = room._occupants[to_nick]; - local to_jid = occupant.jid; - - return from_nick, to_jid, base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from_jid)); -end -local function deconstruct_stanza_id(room, stanza) - local from_jid_possiblybare, to_nick = stanza.attr.from, stanza.attr.to; - local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); - local from_nick = room._jid_nick[from_jid]; - - if not(from_nick) then return; end - if not(from_jid_possiblybare == from_jid or from_jid_possiblybare == jid_bare(from_jid)) then return; end - - local occupant = room._occupants[to_nick]; - for to_jid in pairs(occupant and occupant.sessions or {}) do - if md5(to_jid) == to_jid_hash then - return from_nick, to_jid, id; - end - end -end - function room_mt:handle_presence_error_to_occupant(origin, stanza) local current_nick = self._jid_nick[stanza.attr.from]; if not current_nick then @@ -607,33 +583,45 @@ function room_mt:handle_iq_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; local type = stanza.attr.type; local id = stanza.attr.id; + local current_nick = self._jid_nick[from]; + local o_data = self._occupants[to]; if (type == "error" or type == "result") then - stanza.attr.from, stanza.attr.to, stanza.attr.id = deconstruct_stanza_id(self, stanza); - log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); - if stanza.attr.id then - self:_route_stanza(stanza); + do -- deconstruct_stanza_id + if not current_nick or not o_data then return nil; end + local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); + if not(from == from_jid or from == jid_bare(from_jid)) then return nil; end + local session_jid + for to_jid in pairs(o_data.sessions) do + if md5(to_jid) == to_jid_hash then + session_jid = to_jid; + break; + end + end + if session_jid == nil then return nil; end + stanza.attr.from, stanza.attr.to, stanza.attr.id = current_nick, session_jid, id end + log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); + self:_route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; else -- Type is "get" or "set" - local current_nick = self._jid_nick[from]; if not current_nick then origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); return true; end - local o_data = self._occupants[to]; if not o_data then -- recipient not in room origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); return true; end - stanza.attr.from, stanza.attr.to, stanza.attr.id = construct_stanza_id(self, stanza); + do -- construct_stanza_id + stanza.attr.id = base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from)); + end + stanza.attr.from, stanza.attr.to = current_nick, o_data.jid; log("debug", "%s sent private iq stanza to %s (%s)", from, to, o_data.jid); if stanza.tags[1].attr.xmlns == 'vcard-temp' then stanza.attr.to = jid_bare(stanza.attr.to); end - if stanza.attr.id then - self:_route_stanza(stanza); - end + self:_route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; end @@ -1027,7 +1015,6 @@ function room_mt:handle_groupchat_to_room(origin, stanza) end end - function room_mt:handle_kickable_to_room(origin, stanza) local current_nick = self._jid_nick[stanza.attr.from]; log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); -- cgit v1.2.3 From e89c74cde566c64200c058845622c0c1618b8242 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 17:40:16 -0500 Subject: plugins/muc/muc.lib: Add some missing return values --- plugins/muc/muc.lib.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ce4fa923..a5a87646 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -994,8 +994,10 @@ function room_mt:handle_groupchat_to_room(origin, stanza) local occupant = self._occupants[current_nick]; if not occupant then -- not in room origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); + return true; elseif occupant.role == "visitor" then origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; else local from = stanza.attr.from; stanza.attr.from = current_nick; @@ -1012,6 +1014,7 @@ function room_mt:handle_groupchat_to_room(origin, stanza) self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); end stanza.attr.from = from; + return true; end end @@ -1019,6 +1022,7 @@ function room_mt:handle_kickable_to_room(origin, stanza) local current_nick = self._jid_nick[stanza.attr.from]; log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable + return true; end -- hack - some buggy clients send presence updates to the room rather than their nick @@ -1030,8 +1034,10 @@ function room_mt:handle_presence_to_room(origin, stanza) stanza.attr.to = current_nick; self:handle_to_occupant(origin, stanza); stanza.attr.to = to; + return true; elseif type ~= "error" and type ~= "result" then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + return true; end end @@ -1060,8 +1066,10 @@ function room_mt:handle_invite_to_room(origin, stanza, payload) self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) end self:_route_stanza(invite); + return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); + return true; end end @@ -1079,10 +1087,12 @@ function room_mt:handle_message_to_room(origin, stanza) return self:handle_invite_to_room(origin, stanza, payload) else origin.send(st.error_reply(stanza, "cancel", "bad-request")); + return true; end else if type == "error" or type == "result" then return; end origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + return true; end end -- cgit v1.2.3 From 6a1b8db129762c338e2f17bfd9d9384a6ad45854 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 17:52:40 -0500 Subject: plugins/muc/muc.lib: Make use of return values to send service-unavailable errors --- plugins/muc/muc.lib.lua | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index a5a87646..6131ca78 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -982,9 +982,8 @@ function room_mt:handle_iq_to_room(origin, stanza) elseif stanza.attr.type == "set" then return self:handle_owner_query_set_to_room(origin, stanza) end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - return true; + else + return nil; end end @@ -1035,9 +1034,8 @@ function room_mt:handle_presence_to_room(origin, stanza) self:handle_to_occupant(origin, stanza); stanza.attr.to = to; return true; - elseif type ~= "error" and type ~= "result" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - return true; + else + return nil; end end @@ -1090,9 +1088,7 @@ function room_mt:handle_message_to_room(origin, stanza) return true; end else - if type == "error" or type == "result" then return; end - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - return true; + return nil; end end @@ -1108,10 +1104,18 @@ end function room_mt:handle_stanza(origin, stanza) local to_node, to_host, to_resource = jid_split(stanza.attr.to); + local handled if to_resource then - self:handle_to_occupant(origin, stanza); + handled = self:handle_to_occupant(origin, stanza); else - self:handle_to_room(origin, stanza); + handled = self:handle_to_room(origin, stanza); + end + + if not handled then + local type = stanza.attr.type + if stanza.name ~= "iq" or type == "get" or type == "set" then + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); + end end end -- cgit v1.2.3 From 41423fb282ab448b824223234bdee0f4f85ace9b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 18:04:38 -0500 Subject: plugins/muc/muc.lib: Move all kick code into one place --- plugins/muc/muc.lib.lua | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6131ca78..39240b16 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -259,14 +259,16 @@ function room_mt:set_subject(current_nick, subject) return true; end -local function build_unavailable_presence_from_error(stanza) +function room_mt:handle_kickable(origin, stanza) local type, condition, text = stanza:get_error(); local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error"); if text then error_message = error_message..": "..text; end - return st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) + local kick_stanza = st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) :tag('status'):text(error_message); + self:handle_unavailable_to_occupant(origin, kick_stanza); -- send unavailable + return true; end function room_mt:set_name(name) @@ -380,15 +382,6 @@ function room_mt:get_whois() return self._data.whois; end -function room_mt:handle_presence_error_to_occupant(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; - if not current_nick then - return true -- discard - end - log("debug", "kicking %s from %s", current_nick, self.jid); - return self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)) -end - function room_mt:handle_unavailable_to_occupant(origin, stanza) local from = stanza.attr.from; local current_nick = self._jid_nick[from]; @@ -566,7 +559,7 @@ end function room_mt:handle_presence_to_occupant(origin, stanza) local type = stanza.attr.type; if type == "error" then -- error, kick em out! - return self:handle_presence_error_to_occupant(origin, stanza) + return self:handle_kickable(origin, stanza) elseif type == "unavailable" then -- unavailable return self:handle_unavailable_to_occupant(origin, stanza) elseif not type then -- available @@ -642,8 +635,7 @@ function room_mt:handle_message_to_occupant(origin, stanza) return true; elseif type == "error" and is_kickable_error(stanza) then log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - return true; + return self:handle_kickable(origin, stanza); -- send unavailable end local o_data = self._occupants[to]; @@ -1017,26 +1009,17 @@ function room_mt:handle_groupchat_to_room(origin, stanza) end end -function room_mt:handle_kickable_to_room(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; - log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid); - self:handle_to_occupant(origin, build_unavailable_presence_from_error(stanza)); -- send unavailable - return true; -end - -- hack - some buggy clients send presence updates to the room rather than their nick function room_mt:handle_presence_to_room(origin, stanza) - local type = stanza.attr.type; local current_nick = self._jid_nick[stanza.attr.from]; + local handled if current_nick then local to = stanza.attr.to; stanza.attr.to = current_nick; - self:handle_to_occupant(origin, stanza); + handled = self:handle_presence_to_occupant(origin, stanza); stanza.attr.to = to; - return true; - else - return nil; end + return handled; end function room_mt:handle_invite_to_room(origin, stanza, payload) @@ -1076,7 +1059,7 @@ function room_mt:handle_message_to_room(origin, stanza) if type == "groupchat" then return self:handle_groupchat_to_room(origin, stanza) elseif type == "error" and is_kickable_error(stanza) then - return self:handle_kickable_to_room(origin, stanza) + return self:handle_kickable(origin, stanza) elseif not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then local x = stanza.tags[1]; -- cgit v1.2.3 From f93b8df931ad6a22cfff903dee24b6b91ecb5e88 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Feb 2014 19:06:33 -0500 Subject: plugins/muc/muc.lib: Add disco iq handlers with compatible argument signature --- plugins/muc/muc.lib.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 39240b16..081e4c6d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -827,6 +827,16 @@ function room_mt:destroy(newjid, reason, password) module:fire_event("muc-room-destroyed", { room = self }); end +function room_mt:handle_disco_info_get_query(origin, stanza) + origin.send(self:get_disco_info(stanza)); + return true; +end + +function room_mt:handle_disco_items_get_query(origin, stanza) + origin.send(self:get_disco_items(stanza)); + return true; +end + function room_mt:handle_admin_item_set_command(origin, stanza) local item = stanza.tags[1].tags[1]; if item.attr.jid then -- Validate provided JID @@ -951,11 +961,9 @@ function room_mt:handle_iq_to_room(origin, stanza) local type = stanza.attr.type; local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_info(stanza)); - return true; + return self:handle_disco_info_get_query(origin, stanza) elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then - origin.send(self:get_disco_items(stanza)); - return true; + return self:handle_disco_items_get_query(origin, stanza) elseif xmlns == "http://jabber.org/protocol/muc#admin" then local item = stanza.tags[1].tags[1]; if item and item.name == "item" then -- cgit v1.2.3 From d78445f625dcab3f5098c7b29a30f6d9021816de Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 14:54:41 -0400 Subject: plugins/muc/muc: Add copyright for daurnimator --- plugins/muc/muc.lib.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 081e4c6d..8daa4b9f 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1,6 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator -- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- cgit v1.2.3 From f03d450c5a89c50e313f87041036e643e03b9dfa Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 14:56:20 -0400 Subject: plugins/muc/muc: Check for mediated invites in a smarter way --- plugins/muc/muc.lib.lua | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 8daa4b9f..8a8680c5 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1033,6 +1033,10 @@ end function room_mt:handle_invite_to_room(origin, stanza, payload) local _from, _to = stanza.attr.from, stanza.attr.to; + if not self._jid_nick[_from] then -- Should be in room to send invite + origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end local _invitee = jid_prep(payload.attr.to); if _invitee then local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; @@ -1069,13 +1073,13 @@ function room_mt:handle_message_to_room(origin, stanza) return self:handle_groupchat_to_room(origin, stanza) elseif type == "error" and is_kickable_error(stanza) then return self:handle_kickable(origin, stanza) - elseif not(type == "chat" or type == "error" or type == "groupchat" or type == "headline") and #stanza.tags == 1 - and self._jid_nick[stanza.attr.from] and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then - local x = stanza.tags[1]; - local payload = (#x.tags == 1 and x.tags[1]); - if payload and payload.name == "invite" and payload.attr.to then - return self:handle_invite_to_room(origin, stanza, payload) - else + elseif type == nil then + local x = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); + if x then + local payload = x.tags[1]; + if payload and payload.name == "invite" and payload.attr.to then + return self:handle_invite_to_room(origin, stanza, payload) + end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; end -- cgit v1.2.3 From 32ef3e0c33ede7676d9bf98919997502723a058e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 15:15:14 -0400 Subject: plugins/muc/muc: Rename `handle_invite_to_room` to `handle_mediated_invite`; clean up logic --- plugins/muc/muc.lib.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 8a8680c5..9b54fd2b 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1031,15 +1031,15 @@ function room_mt:handle_presence_to_room(origin, stanza) return handled; end -function room_mt:handle_invite_to_room(origin, stanza, payload) +function room_mt:handle_mediated_invite(origin, stanza, payload) local _from, _to = stanza.attr.from, stanza.attr.to; - if not self._jid_nick[_from] then -- Should be in room to send invite + if not self._jid_nick[_from] then -- Should be in room to send invite TODO: allow admins to send at any time origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end local _invitee = jid_prep(payload.attr.to); if _invitee then - local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1]; + local _reason = payload:get_child_text("reason") local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) :tag('invite', {from=_from}) @@ -1077,8 +1077,10 @@ function room_mt:handle_message_to_room(origin, stanza) local x = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); if x then local payload = x.tags[1]; - if payload and payload.name == "invite" and payload.attr.to then - return self:handle_invite_to_room(origin, stanza, payload) + if payload == nil then + -- fallthrough + elseif payload.name == "invite" and payload.attr.to then + return self:handle_mediated_invite(origin, stanza, payload) end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; -- cgit v1.2.3 From 5a0baa1d2f9dc13a5fce3013b2c8a68d7e0d1b83 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 15:15:28 -0400 Subject: plugins/muc/muc: Support mediated declines --- plugins/muc/muc.lib.lua | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9b54fd2b..0c87574a 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1067,6 +1067,29 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) end end +function room_mt:handle_mediated_decline(origin, stanza, payload) + local declinee = jid_prep(payload.attr.to); + if declinee then + local from, to = stanza.attr.from, stanza.attr.to; + -- TODO: Validate declinee + local reason = payload:get_child_text("reason") + local decline = st.message({from = to, to = declinee, id = stanza.attr.id}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('decline', {from=from}) + :tag('reason'):text(reason or ""):up() + :up() + :up() + :tag('body') -- Add a plain message for clients which don't support declines + :text(from..' declined your invite to the room '..to..(reason and (' ('..reason..')') or "")) + :up(); + self:_route_stanza(decline); + return true; + else + origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); + return true; + end +end + function room_mt:handle_message_to_room(origin, stanza) local type = stanza.attr.type; if type == "groupchat" then @@ -1081,6 +1104,8 @@ function room_mt:handle_message_to_room(origin, stanza) -- fallthrough elseif payload.name == "invite" and payload.attr.to then return self:handle_mediated_invite(origin, stanza, payload) + elseif payload.name == "decline" and payload.attr.to then + return self:handle_mediated_decline(origin, stanza, payload) end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; -- cgit v1.2.3 From 176fc76253fd26bca5efc0249b3666feb40ddf99 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 15:42:48 -0400 Subject: plugins/muc/muc: When there's no history; return an empty iterator --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 0c87574a..2afe6f58 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -181,7 +181,7 @@ end -- Get history for 'to' function room_mt:get_history(to, maxchars, maxstanzas, since) local history = self._data['history']; -- send discussion history - if not history then return end + if not history then return function() end end local history_len = #history maxstanzas = maxstanzas or history_len -- cgit v1.2.3 From 114bcccb5b95c3d122d2fe5bc3d61c809d76b2c6 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 16:01:53 -0400 Subject: plugins/muc/muc: Only call get_password once in invite creation --- plugins/muc/muc.lib.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2afe6f58..2c9b58d2 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1045,9 +1045,10 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) :tag('invite', {from=_from}) :tag('reason'):text(_reason or ""):up() :up(); - if self:get_password() then - invite:tag("password"):text(self:get_password()):up(); - end + local password = self:get_password() + if password then + invite:tag("password"):text(password):up(); + end invite:up() :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this :text(_reason or "") -- cgit v1.2.3 From f04399509b70df2b62c7207142593992f0af8bd7 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 18:52:28 -0400 Subject: plugins/muc/muc: Add 'muc-occupant-left' event --- plugins/muc/muc.lib.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2c9b58d2..9f7b5c70 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -416,6 +416,7 @@ function room_mt:handle_unavailable_to_occupant(origin, stanza) occupant.role = 'none'; self:broadcast_presence(pr, from); self._occupants[current_nick] = nil; + module:fire_event("muc-occupant-left", { room = self; nick = current_nick; }); end return true; end @@ -823,6 +824,7 @@ function room_mt:destroy(newjid, reason, password) self._jid_nick[jid] = nil; end self._occupants[nick] = nil; + module:fire_event("muc-occupant-left", { room = self; nick = nick; }); end self:set_persistent(false); module:fire_event("muc-room-destroyed", { room = self }); -- cgit v1.2.3 From fdee07a8095daec344aead18d6d8b6f9622573ab Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 18:55:52 -0400 Subject: plugins/muc/mod_muc: Refactor to use new methods available --- plugins/muc/mod_muc.lua | 110 +++++++++++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 38 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 5a71ef75..9adec74e 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -106,6 +106,14 @@ function create_room(jid) return room; end +function forget_room(jid) + rooms[jid] = nil; +end + +function get_room_from_jid(room_jid) + return rooms[room_jid] +end + local persistent_errors = false; for jid in pairs(persistent_rooms) do local node = jid_split(jid); @@ -125,6 +133,7 @@ if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); e local host_room = muc_new_room(muc_host); host_room.route_stanza = room_route_stanza; host_room.save = room_save; +rooms[muc_host] = host_room; module:hook("host-disco-items", function(event) local reply = event.reply; @@ -136,49 +145,74 @@ module:hook("host-disco-items", function(event) end end); -function stanza_handler(event) - local origin, stanza = event.origin, event.stanza; - local bare = jid_bare(stanza.attr.to); - local room = rooms[bare]; - if not room then - if stanza.name ~= "presence" then - origin.send(st.error_reply(stanza, "cancel", "item-not-found")); - return true; - end - if not(restrict_room_creation) or - is_admin(stanza.attr.from) or - (restrict_room_creation == "local" and select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "")) then - room = create_room(bare); - end +module:hook("muc-room-destroyed",function(event) + local room = event.room + forget_room(room.jid) +end) + +module:hook("muc-occupant-left",function(event) + local room = event.room + if not next(room._occupants) and not persistent_rooms[room.jid] then -- empty, non-persistent room + module:fire_event("muc-room-destroyed", { room = room }); end - if room then - room:handle_stanza(origin, stanza); - if not next(room._occupants) and not persistent_rooms[room.jid] then -- empty, non-persistent room - module:fire_event("muc-room-destroyed", { room = room }); - rooms[bare] = nil; -- discard room - end - else - origin.send(st.error_reply(stanza, "cancel", "not-allowed")); +end); + +-- Watch presence to create rooms +local function attempt_room_creation(event) + local origin, stanza = event.origin, event.stanza; + local room_jid = jid_bare(stanza.attr.to); + if stanza.attr.type == nil and + get_room_from_jid(room_jid) == nil and + ( + not(restrict_room_creation) or + is_admin(stanza.attr.from) or + ( + restrict_room_creation == "local" and + select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "") + ) + ) then + create_room(room_jid); end - return true; end -module:hook("iq/bare", stanza_handler, -1); -module:hook("message/bare", stanza_handler, -1); -module:hook("presence/bare", stanza_handler, -1); -module:hook("iq/full", stanza_handler, -1); -module:hook("message/full", stanza_handler, -1); -module:hook("presence/full", stanza_handler, -1); +module:hook("presence/full", attempt_room_creation, -1) +module:hook("presence/bare", attempt_room_creation, -1) +module:hook("presence/host", attempt_room_creation, -1) -local function handle_to_domain(event) - local origin, stanza = event.origin, event.stanza; - local type = stanza.attr.type; - if type == "error" then return; end - host_room:handle_stanza(origin, stanza); - -- origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it")); - return true; +for event_name, method in pairs { + -- Normal room interactions + ["iq-get/bare/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; + ["iq-get/bare/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; + ["iq-set/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; + ["iq-get/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; + ["iq-get/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; + ["message/bare"] = "handle_message_to_room" ; + ["presence/bare"] = "handle_presence_to_room" ; + -- Host room + ["iq-get/host/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; + ["iq-get/host/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; + ["iq-set/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; + ["iq-get/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; + ["iq-get/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; + ["message/host"] = "handle_message_to_room" ; + ["presence/host"] = "handle_presence_to_room" ; + -- Direct to occupant (normal rooms and host room) + ["presence/full"] = "handle_presence_to_occupant" ; + ["iq/full"] = "handle_iq_to_occupant" ; + ["message/full"] = "handle_message_to_occupant" ; +} do + module:hook(event_name, function (event) + local origin, stanza = event.origin, event.stanza; + local room = get_room_from_jid(jid_bare(stanza.attr.to)) + if room == nil then + origin.send(st.error_reply(stanza, "cancel", "not-allowed")); + return true; + end + return room[method](room, origin, stanza); + end, -2) end -module:hook("message/host", handle_to_domain, -1); -module:hook("presence/host", handle_to_domain, -1); + hosts[module.host].send = function(stanza) -- FIXME do a generic fix if stanza.attr.type == "result" or stanza.attr.type == "error" then -- cgit v1.2.3 From e3b729b9780b64e3efcd2a8163d3fcc8121daf14 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 18 Mar 2014 18:56:26 -0400 Subject: plugins/muc/mod_muc: host sessions have a .send these days --- plugins/muc/mod_muc.lua | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 9adec74e..5f4b0c62 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -213,13 +213,6 @@ for event_name, method in pairs { end, -2) end - -hosts[module.host].send = function(stanza) -- FIXME do a generic fix - if stanza.attr.type == "result" or stanza.attr.type == "error" then - module:send(stanza); - else error("component.send only supports result and error stanzas at the moment"); end -end - hosts[module:get_host()].muc = { rooms = rooms }; local saved = false; -- cgit v1.2.3 From 5b4518c010d70120cf5afef3ad4208454407a49b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 12:03:15 -0400 Subject: plugins/muc: Provide a reasonable default `route_stanza` --- plugins/muc/mod_muc.lua | 3 --- plugins/muc/muc.lib.lua | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 5f4b0c62..a1ba5738 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -64,7 +64,6 @@ function muclib.room_mt:set_affiliation(actor, jid, affiliation, callback, reaso return _set_affiliation(self, actor, jid, affiliation, callback, reason); end -local function room_route_stanza(room, stanza) module:send(stanza); end local function room_save(room, forced) local node = jid_split(room.jid); persistent_rooms[room.jid] = room._data.persistent; @@ -89,7 +88,6 @@ end function create_room(jid) local room = muc_new_room(jid); - room.route_stanza = room_route_stanza; room.save = room_save; rooms[jid] = room; if lock_rooms then @@ -131,7 +129,6 @@ end if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end local host_room = muc_new_room(muc_host); -host_room.route_stanza = room_route_stanza; host_room.save = room_save; rooms[muc_host] = host_room; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9f7b5c70..198e25a3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1145,7 +1145,9 @@ function room_mt:handle_stanza(origin, stanza) end end -function room_mt:route_stanza(stanza) end -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end +function room_mt:route_stanza(stanza) + module:send(stanza) +end function room_mt:get_affiliation(jid) local node, host, resource = jid_split(jid); -- cgit v1.2.3 From ab848d94a3741d0162e06f9c454ecb84cb107aa9 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 12:19:44 -0400 Subject: plugins/muc/muc.lib: Use more modern stanza methods --- plugins/muc/muc.lib.lua | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 198e25a3..ead1f264 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -755,8 +755,7 @@ end function room_mt:process_form(origin, stanza) local query = stanza.tags[1]; - local form; - for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end + local form = query:get_child("x", "jabber:x:data") if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end @@ -858,7 +857,7 @@ function room_mt:handle_admin_item_set_command(origin, stanza) end local actor = stanza.attr.from; local callback = function() origin.send(st.reply(stanza)); end - local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1]; + local reason = item:get_child_text("reason"); if item.attr.affiliation and item.attr.jid and not item.attr.role then local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); if not success then origin.send(st.error_reply(stanza, errtype, err)); end @@ -943,14 +942,8 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) return true; elseif child.name == "destroy" then local newjid = child.attr.jid; - local reason, password; - for _,tag in ipairs(child.tags) do - if tag.name == "reason" then - reason = #tag.tags == 0 and tag[1]; - elseif tag.name == "password" then - password = #tag.tags == 0 and tag[1]; - end - end + local reason = child:get_child_text("reason"); + local password = child:get_child_text("password"); self:destroy(newjid, reason, password); origin.send(st.reply(stanza)); return true; @@ -1305,22 +1298,18 @@ function room_mt:_route_stanza(stanza) end end if muc_child then - for _, item in pairs(muc_child.tags) do - if item.name == "item" then - if from_occupant == to_occupant then - item.attr.jid = stanza.attr.to; - else - item.attr.jid = from_occupant.jid; - end + for item in muc_child:childtags("item") do + if from_occupant == to_occupant then + item.attr.jid = stanza.attr.to; + else + item.attr.jid = from_occupant.jid; end end end self:route_stanza(stanza); if muc_child then - for _, item in pairs(muc_child.tags) do - if item.name == "item" then - item.attr.jid = nil; - end + for item in muc_child:childtags("item") do + item.attr.jid = nil; end end end -- cgit v1.2.3 From fdfd511de7c8b3585433428c77ee306405c14b3b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 13:56:14 -0400 Subject: plugins/muc/muc.lib: Remove unused methods (breaks api) --- plugins/muc/muc.lib.lua | 72 ------------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ead1f264..972e30cb 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -656,21 +656,6 @@ function room_mt:handle_message_to_occupant(origin, stanza) return true; end -function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc - local from, to = stanza.attr.from, stanza.attr.to; - local room = jid_bare(to); - local current_nick = self._jid_nick[from]; - log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag()); - if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end - if stanza.name == "presence" then - return self:handle_presence_to_occupant(origin, stanza) - elseif stanza.name == "iq" then - return self:handle_iq_to_occupant(origin, stanza) - elseif stanza.name == "message" then - return self:handle_message_to_occupant(origin, stanza) - end -end - function room_mt:send_form(origin, stanza) origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner") :add_child(self:get_form_layout(stanza.attr.from):form()) @@ -953,36 +938,6 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) end end -function room_mt:handle_iq_to_room(origin, stanza) - local type = stanza.attr.type; - local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; - if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" and not stanza.tags[1].attr.node then - return self:handle_disco_info_get_query(origin, stanza) - elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" and not stanza.tags[1].attr.node then - return self:handle_disco_items_get_query(origin, stanza) - elseif xmlns == "http://jabber.org/protocol/muc#admin" then - local item = stanza.tags[1].tags[1]; - if item and item.name == "item" then - if type == "set" then - return self:handle_admin_item_set_command(origin, stanza) - elseif type == "get" then - return self:handle_admin_item_get_command(origin, stanza) - end - elseif type == "set" or type == "get" then - origin.send(st.error_reply(stanza, "cancel", "bad-request")); - return true; - end - elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then - if stanza.attr.type == "get" then - return self:handle_owner_query_get_to_room(origin, stanza) - elseif stanza.attr.type == "set" then - return self:handle_owner_query_set_to_room(origin, stanza) - end - else - return nil; - end -end - function room_mt:handle_groupchat_to_room(origin, stanza) local from = stanza.attr.from; local current_nick = self._jid_nick[from]; @@ -1111,33 +1066,6 @@ function room_mt:handle_message_to_room(origin, stanza) end end -function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc - if stanza.name == "iq" then - return self:handle_iq_to_room(origin, stanza) - elseif stanza.name == "message" then - return self:handle_message_to_room(origin, stanza) - elseif stanza.name == "presence" then - return self:handle_presence_to_room(origin, stanza) - end -end - -function room_mt:handle_stanza(origin, stanza) - local to_node, to_host, to_resource = jid_split(stanza.attr.to); - local handled - if to_resource then - handled = self:handle_to_occupant(origin, stanza); - else - handled = self:handle_to_room(origin, stanza); - end - - if not handled then - local type = stanza.attr.type - if stanza.name ~= "iq" or type == "get" or type == "set" then - origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); - end - end -end - function room_mt:route_stanza(stanza) module:send(stanza) end -- cgit v1.2.3 From cda8656a4e20360af37c389a3751625b528259ff Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 13:57:02 -0400 Subject: plugins/muc/muc.lib: Use module.host where `muc_domain` was previously --- plugins/muc/muc.lib.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 972e30cb..f3d69f8d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -25,7 +25,6 @@ local setmetatable = setmetatable; local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; -local muc_domain = nil; --module:get_host(); local default_history_length, max_history_length = 20, math.huge; ------------ @@ -118,8 +117,8 @@ function room_mt:save_to_history(stanza) stanza = st.clone(stanza); stanza.attr.to = ""; local stamp = datetime.datetime(); - stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203 - stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) + stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 + stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) local entry = { stanza = stanza, stamp = stamp }; t_insert(history, entry); while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end -- cgit v1.2.3 From 37e1117b7809b10db45ecbd2e9eb3cfa957ec652 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 13:59:59 -0400 Subject: plugins/muc/muc.lib: Tidy up is_kickable_error: it didn't need to return the condition. Also removes `get_error_condition`; it was a one liner used in one place --- plugins/muc/muc.lib.lua | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index f3d69f8d..2d45f17e 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -27,7 +27,6 @@ local md5 = require "util.hashes".md5; local default_history_length, max_history_length = 20, math.huge; ------------- local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; local function presence_filter(tag) if presence_filters[tag.attr.xmlns] then @@ -35,33 +34,28 @@ local function presence_filter(tag) end return tag; end - local function get_filtered_presence(stanza) return st.clone(stanza):maptags(presence_filter); end -local kickable_error_conditions = { - ["gone"] = true; - ["internal-server-error"] = true; - ["item-not-found"] = true; - ["jid-malformed"] = true; - ["recipient-unavailable"] = true; - ["redirect"] = true; - ["remote-server-not-found"] = true; - ["remote-server-timeout"] = true; - ["service-unavailable"] = true; - ["malformed error"] = true; -}; - -local function get_error_condition(stanza) - local _, condition = stanza:get_error(); - return condition or "malformed error"; -end - -local function is_kickable_error(stanza) - local cond = get_error_condition(stanza); - return kickable_error_conditions[cond] and cond; -end ------------ + +local is_kickable_error do + local kickable_error_conditions = { + ["gone"] = true; + ["internal-server-error"] = true; + ["item-not-found"] = true; + ["jid-malformed"] = true; + ["recipient-unavailable"] = true; + ["redirect"] = true; + ["remote-server-not-found"] = true; + ["remote-server-timeout"] = true; + ["service-unavailable"] = true; + ["malformed error"] = true; + }; + function is_kickable_error(stanza) + local cond = select(2, stanza:get_error()) or "malformed error"; + return kickable_error_conditions[cond]; + end +end local room_mt = {}; room_mt.__index = room_mt; -- cgit v1.2.3 From 38d1a05fee92a320301db02dfc189f5138fccfc7 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 14:06:04 -0400 Subject: plugins/muc/muc.lib: Tidy up `get_filtered_presence` --- plugins/muc/muc.lib.lua | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2d45f17e..2fb1a34e 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -27,15 +27,20 @@ local md5 = require "util.hashes".md5; local default_history_length, max_history_length = 20, math.huge; -local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true}; -local function presence_filter(tag) - if presence_filters[tag.attr.xmlns] then - return nil; +local get_filtered_presence do + local presence_filters = { + ["http://jabber.org/protocol/muc"] = true; + ["http://jabber.org/protocol/muc#user"] = true; + } + local function presence_filter(tag) + if presence_filters[tag.attr.xmlns] then + return nil; + end + return tag; + end + function get_filtered_presence(stanza) + return st.clone(stanza):maptags(presence_filter); end - return tag; -end -local function get_filtered_presence(stanza) - return st.clone(stanza):maptags(presence_filter); end local is_kickable_error do -- cgit v1.2.3 From 0fc08db387ae02f3b9c659401c4bf8fd25658ea8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 14:35:17 -0400 Subject: plugins/muc/muc.lib: In `_route_stanza` access occupant data less often --- plugins/muc/muc.lib.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 2fb1a34e..1a5bc976 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1210,9 +1210,9 @@ end function room_mt:_route_stanza(stanza) local muc_child; - local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; - local from_occupant = self._occupants[stanza.attr.from]; if stanza.name == "presence" then + local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; + local from_occupant = self._occupants[stanza.attr.from]; if to_occupant and from_occupant then if self._data.whois == 'anyone' then muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); @@ -1222,13 +1222,13 @@ function room_mt:_route_stanza(stanza) end end end - end - if muc_child then - for item in muc_child:childtags("item") do - if from_occupant == to_occupant then - item.attr.jid = stanza.attr.to; - else - item.attr.jid = from_occupant.jid; + if muc_child then + for item in muc_child:childtags("item") do + if from_occupant == to_occupant then + item.attr.jid = stanza.attr.to; + else + item.attr.jid = from_occupant.jid; + end end end end -- cgit v1.2.3 From 21d9bec752b153efeeab4e779a8c1e2627a3f1dc Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 14:39:31 -0400 Subject: plugins/muc/muc.lib: Fetch config via accessors instead of using `_data` --- plugins/muc/muc.lib.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1a5bc976..81998d2c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -232,7 +232,7 @@ function room_mt:get_disco_info(stanza) :tag("feature", {var=self:get_members_only() and "muc_membersonly" or "muc_open"}):up() :tag("feature", {var=self:get_persistent() and "muc_persistent" or "muc_temporary"}):up() :tag("feature", {var=self:get_hidden() and "muc_hidden" or "muc_public"}):up() - :tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() + :tag("feature", {var=self:get_whois() ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() :add_child(dataform.new({ { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }, { name = "muc#roominfo_description", label = "Description", value = "" }, @@ -488,7 +488,7 @@ function room_mt:handle_join(origin, stanza) self:broadcast_except_nick(pr, to); end pr:tag("status", {code='110'}):up(); - if self._data.whois == 'anyone' then + if self:get_whois() == 'anyone' then pr:tag("status", {code='100'}):up(); end if self.locked then @@ -661,6 +661,7 @@ function room_mt:send_form(origin, stanza) end function room_mt:get_form_layout(actor) + local whois = self:get_whois() local form = dataform.new({ title = "Configuration for "..self.jid, instructions = "Complete and submit this form to configure the room.", @@ -704,8 +705,8 @@ function room_mt:get_form_layout(actor) type = 'list-single', label = 'Who May Discover Real JIDs?', value = { - { value = 'moderators', label = 'Moderators Only', default = self._data.whois == 'moderators' }, - { value = 'anyone', label = 'Anyone', default = self._data.whois == 'anyone' } + { value = 'moderators', label = 'Moderators Only', default = whois == 'moderators' }, + { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } } }, { @@ -952,7 +953,7 @@ function room_mt:handle_groupchat_to_room(origin, stanza) local subject = stanza:get_child_text("subject"); if subject then if occupant.role == "moderator" or - ( self._data.changesubject and occupant.role == "participant" ) then -- and participant + ( self:get_changesubject() and occupant.role == "participant" ) then -- and participant self:set_subject(current_nick, subject); else stanza.attr.from = from; @@ -1214,7 +1215,7 @@ function room_mt:_route_stanza(stanza) local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; local from_occupant = self._occupants[stanza.attr.from]; if to_occupant and from_occupant then - if self._data.whois == 'anyone' then + if self:get_whois() == 'anyone' then muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); else if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then -- cgit v1.2.3 From cca33567091e686a93fd560b9460abaf96caa057 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 15:00:53 -0400 Subject: plugins/muc/muc.lib: Use `get_occupant_jid` method instead of indexing _jid_nick --- plugins/muc/muc.lib.lua | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 81998d2c..173d9827 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -69,6 +69,10 @@ function room_mt:__tostring() return "MUC room ("..self.jid..")"; end +function room_mt:get_occupant_jid(real_jid) + return self._jid_nick[real_jid] +end + function room_mt:get_default_role(affiliation) if affiliation == "owner" or affiliation == "admin" then return "moderator"; @@ -134,7 +138,7 @@ function room_mt:broadcast_except_nick(stanza, nick) end function room_mt:send_occupant_list(to) - local current_nick = self._jid_nick[to]; + local current_nick = self:get_occupant_jid(to); for occupant, o_data in pairs(self._occupants) do if occupant ~= current_nick then local pres = get_filtered_presence(o_data.sessions[o_data.jid]); @@ -383,7 +387,7 @@ end function room_mt:handle_unavailable_to_occupant(origin, stanza) local from = stanza.attr.from; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); if not current_nick then return true; -- discard end @@ -514,7 +518,7 @@ end function room_mt:handle_available_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); if current_nick then --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence if current_nick == to then -- simple presence @@ -576,7 +580,7 @@ function room_mt:handle_iq_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; local type = stanza.attr.type; local id = stanza.attr.id; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); local o_data = self._occupants[to]; if (type == "error" or type == "result") then do -- deconstruct_stanza_id @@ -622,7 +626,7 @@ end function room_mt:handle_message_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); local type = stanza.attr.type; if not current_nick then -- not in room if type ~= "error" then @@ -836,7 +840,7 @@ function room_mt:handle_admin_item_set_command(origin, stanza) local occupant = self._occupants[self.jid.."/"..item.attr.nick]; if occupant then item.attr.jid = occupant.jid; end elseif not item.attr.nick and item.attr.jid then - local nick = self._jid_nick[item.attr.jid]; + local nick = self:get_occupant_jid(item.attr.jid); if nick then item.attr.nick = select(3, jid_split(nick)); end end local actor = stanza.attr.from; @@ -859,7 +863,7 @@ end function room_mt:handle_admin_item_get_command(origin, stanza) local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); - local current_nick = self._jid_nick[actor]; + local current_nick = self:get_occupant_jid(actor); local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); local item = stanza.tags[1].tags[1]; local _aff = item.attr.affiliation; @@ -939,7 +943,7 @@ end function room_mt:handle_groupchat_to_room(origin, stanza) local from = stanza.attr.from; - local current_nick = self._jid_nick[from]; + local current_nick = self:get_occupant_jid(from); local occupant = self._occupants[current_nick]; if not occupant then -- not in room origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); @@ -969,7 +973,7 @@ end -- hack - some buggy clients send presence updates to the room rather than their nick function room_mt:handle_presence_to_room(origin, stanza) - local current_nick = self._jid_nick[stanza.attr.from]; + local current_nick = self:get_occupant_jid(stanza.attr.from); local handled if current_nick then local to = stanza.attr.to; @@ -982,7 +986,8 @@ end function room_mt:handle_mediated_invite(origin, stanza, payload) local _from, _to = stanza.attr.from, stanza.attr.to; - if not self._jid_nick[_from] then -- Should be in room to send invite TODO: allow admins to send at any time + local current_nick = self:get_occupant_jid(_from) + if not current_nick then -- Should be in room to send invite TODO: allow admins to send at any time origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end @@ -1007,7 +1012,7 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) :up(); if self:get_members_only() and not self:get_affiliation(_invitee) then log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); - self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. self._jid_nick[_from]) + self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. current_nick) end self:_route_stanza(invite); return true; @@ -1157,7 +1162,7 @@ function room_mt:can_set_role(actor_jid, occupant_jid, role) if actor_jid == true then return true; end - local actor = self._occupants[self._jid_nick[actor_jid]]; + local actor = self._occupants[self:get_occupant_jid(actor_jid)]; if actor.role == "moderator" then if occupant.affiliation ~= "owner" and occupant.affiliation ~= "admin" then if actor.affiliation == "owner" or actor.affiliation == "admin" then @@ -1212,7 +1217,7 @@ end function room_mt:_route_stanza(stanza) local muc_child; if stanza.name == "presence" then - local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]]; + local to_occupant = self._occupants[self:get_occupant_jid(stanza.attr.to)]; local from_occupant = self._occupants[stanza.attr.from]; if to_occupant and from_occupant then if self:get_whois() == 'anyone' then -- cgit v1.2.3 From 4bbb266dab17014d2d8f25b1d8180e38c4a8a233 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 15:01:18 -0400 Subject: plugins/muc/muc.lib: Don't get same variable twice..... --- plugins/muc/muc.lib.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 173d9827..f3c72e26 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -445,13 +445,12 @@ function room_mt:handle_change_nick(origin, stanza, current_nick, to) origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; else - local data = self._occupants[current_nick]; local to_nick = select(3, jid_split(to)); - log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to); + log("debug", "%s (%s) changing nick to %s", current_nick, occupant.jid, to); local p = st.presence({type='unavailable', from=current_nick}); self:broadcast_presence(p, from, '303', to_nick); self._occupants[current_nick] = nil; - self._occupants[to] = data; + self._occupants[to] = occupant; self._jid_nick[from] = to; local pr = get_filtered_presence(stanza); pr.attr.from = to; -- cgit v1.2.3 From 74fb113acd8f4c6c20c7ebb5df7ab9ac0de5932e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 16:19:31 -0400 Subject: plugins/muc/muc.lib: Add muc-invite-prepared event; Use it for granting affiliations in members only rooms --- plugins/muc/muc.lib.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index f3c72e26..7dfb0611 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1009,10 +1009,7 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) :tag('body') -- Add a plain message for clients which don't support invites :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) :up(); - if self:get_members_only() and not self:get_affiliation(_invitee) then - log("debug", "%s invited %s into members only room %s, granting membership", _from, _invitee, _to); - self:set_affiliation(_from, _invitee, "member", nil, "Invited by " .. current_nick) - end + module:fire_event("muc-invite-prepared", { room = self, stanza = invite }) self:_route_stanza(invite); return true; else @@ -1021,6 +1018,18 @@ function room_mt:handle_mediated_invite(origin, stanza, payload) end end +-- When an invite is sent; add an affiliation for the invitee +module:hook("muc-invite-prepared", function(event) + local room, stanza = event.room, event.stanza + local invitee = stanza.attr.to + if room:get_members_only() and not room:get_affiliation(invitee) then + local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from + local current_nick = room:get_occupant_jid(from) + log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); + room:set_affiliation(from, invitee, "member", nil, "Invited by " .. current_nick) + end +end) + function room_mt:handle_mediated_decline(origin, stanza, payload) local declinee = jid_prep(payload.attr.to); if declinee then -- cgit v1.2.3 From 3b36ae6f437d90e504e3de4d7d2996f9197a805a Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 16:24:17 -0400 Subject: plugins/muc/muc.lib: Fix wrong variable in `construct_stanza_id` block --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7dfb0611..de251716 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -610,7 +610,7 @@ function room_mt:handle_iq_to_occupant(origin, stanza) return true; end do -- construct_stanza_id - stanza.attr.id = base64.encode(to_jid.."\0"..stanza.attr.id.."\0"..md5(from)); + stanza.attr.id = base64.encode(o_data.jid.."\0"..stanza.attr.id.."\0"..md5(from)); end stanza.attr.from, stanza.attr.to = current_nick, o_data.jid; log("debug", "%s sent private iq stanza to %s (%s)", from, to, o_data.jid); -- cgit v1.2.3 From c6d4ea2e3b5db640310d5cb7b5d1b8b27fc7c831 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 16:28:11 -0400 Subject: plugins/muc/muc.lib: Remove `payload` argument from `handle_mediated_*`; extract it from inside. --- plugins/muc/muc.lib.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index de251716..be4d31c3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -983,7 +983,8 @@ function room_mt:handle_presence_to_room(origin, stanza) return handled; end -function room_mt:handle_mediated_invite(origin, stanza, payload) +function room_mt:handle_mediated_invite(origin, stanza) + local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite") local _from, _to = stanza.attr.from, stanza.attr.to; local current_nick = self:get_occupant_jid(_from) if not current_nick then -- Should be in room to send invite TODO: allow admins to send at any time @@ -1030,7 +1031,8 @@ module:hook("muc-invite-prepared", function(event) end end) -function room_mt:handle_mediated_decline(origin, stanza, payload) +function room_mt:handle_mediated_decline(origin, stanza) + local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline") local declinee = jid_prep(payload.attr.to); if declinee then local from, to = stanza.attr.from, stanza.attr.to; @@ -1066,9 +1068,9 @@ function room_mt:handle_message_to_room(origin, stanza) if payload == nil then -- fallthrough elseif payload.name == "invite" and payload.attr.to then - return self:handle_mediated_invite(origin, stanza, payload) + return self:handle_mediated_invite(origin, stanza) elseif payload.name == "decline" and payload.attr.to then - return self:handle_mediated_decline(origin, stanza, payload) + return self:handle_mediated_decline(origin, stanza) end origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; -- cgit v1.2.3 From fd4a486166f340c736f15636ff5dc3940a806995 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:30:21 -0400 Subject: plugins/muc/muc.lib: Check role instead of current_nick --- plugins/muc/muc.lib.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index be4d31c3..7b010646 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -987,7 +987,8 @@ function room_mt:handle_mediated_invite(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite") local _from, _to = stanza.attr.from, stanza.attr.to; local current_nick = self:get_occupant_jid(_from) - if not current_nick then -- Should be in room to send invite TODO: allow admins to send at any time + -- Need visitor role or higher to invite + if not self._occupants[current_nick].role then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end -- cgit v1.2.3 From 0fc61fbaad4abf9d600c731e50bb9a414d6345d8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:33:32 -0400 Subject: plugins/muc/muc.lib: Send invite out from event: removes '-prepared' from event name --- plugins/muc/muc.lib.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7b010646..7cc1beb4 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1011,8 +1011,7 @@ function room_mt:handle_mediated_invite(origin, stanza) :tag('body') -- Add a plain message for clients which don't support invites :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) :up(); - module:fire_event("muc-invite-prepared", { room = self, stanza = invite }) - self:_route_stanza(invite); + module:fire_event("muc-invite", { room = self, stanza = invite, origin = origin, incoming = stanza }); return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); @@ -1020,8 +1019,13 @@ function room_mt:handle_mediated_invite(origin, stanza) end end +module:hook("muc-invite", function(event) + event.room:_route_stanza(event.stanza); + return true; +end, -1) + -- When an invite is sent; add an affiliation for the invitee -module:hook("muc-invite-prepared", function(event) +module:hook("muc-invite", function(event) local room, stanza = event.room, event.stanza local invitee = stanza.attr.to if room:get_members_only() and not room:get_affiliation(invitee) then -- cgit v1.2.3 From 35a820aba5a9017d350fe907bf4fe7b463e0f185 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:46:25 -0400 Subject: plugins/muc/muc.lib: Use `get_role` in `handle_admin_item_get_command`. Removed a TODO that's already done --- plugins/muc/muc.lib.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7cc1beb4..fe8aee72 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -862,8 +862,6 @@ end function room_mt:handle_admin_item_get_command(origin, stanza) local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); - local current_nick = self:get_occupant_jid(actor); - local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation); local item = stanza.tags[1].tags[1]; local _aff = item.attr.affiliation; local _rol = item.attr.role; @@ -882,8 +880,8 @@ function room_mt:handle_admin_item_get_command(origin, stanza) return true; end elseif _rol and not _aff then + local role = self:get_role(self:get_occupant_jid(actor)) or self:get_default_role(affiliation); if role == "moderator" then - -- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway? if _rol == "none" then _rol = nil; end local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); for occupant_jid, occupant in pairs(self._occupants) do -- cgit v1.2.3 From 8f5af6d933d1f07db23b1c0f77ce0804c1643c84 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:50:00 -0400 Subject: plugins/muc/muc.lib: Allow users with affiliations to invite while not in room themselves --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index fe8aee72..af7bf356 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -986,7 +986,7 @@ function room_mt:handle_mediated_invite(origin, stanza) local _from, _to = stanza.attr.from, stanza.attr.to; local current_nick = self:get_occupant_jid(_from) -- Need visitor role or higher to invite - if not self._occupants[current_nick].role then + if not self:get_role(current_nick) or not self:get_default_role(self:get_affiliation(_from)) then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end -- cgit v1.2.3 From 8b8a024db40b127db492f4a0dd93a94f4e3cf801 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 19 Mar 2014 17:50:49 -0400 Subject: plugins/muc/muc.lib: Use occupant jids when whois == "moderators" --- plugins/muc/muc.lib.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index af7bf356..30d9f96d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -992,6 +992,9 @@ function room_mt:handle_mediated_invite(origin, stanza) end local _invitee = jid_prep(payload.attr.to); if _invitee then + if self:get_whois() == "moderators" then + _from = current_nick; + end local _reason = payload:get_child_text("reason") local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) -- cgit v1.2.3 From 99fb14cb63c5c85ef0e087a393720d3f5d0760ad Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 11:06:10 -0400 Subject: plugins/muc: Have utility methods for locking the room --- plugins/muc/mod_muc.lua | 4 ++-- plugins/muc/muc.lib.lua | 25 ++++++++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index a1ba5738..e8782414 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -91,10 +91,10 @@ function create_room(jid) room.save = room_save; rooms[jid] = room; if lock_rooms then - room.locked = true; + room:lock(); if lock_room_timeout and lock_room_timeout > 0 then module:add_timer(lock_room_timeout, function () - if room.locked then + if room:is_locked() then room:destroy(); -- Not unlocked in time end end); diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 30d9f96d..746814ca 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -85,6 +85,17 @@ function room_mt:get_default_role(affiliation) end end +function room_mt:lock() + self.locked = true +end +function room_mt:unlock() + module:fire_event("muc-room-unlocked", { room = self }); + self.locked = nil +end +function room_mt:is_locked() + return not not self.locked +end + function room_mt:broadcast_presence(stanza, sid, code, nick) stanza = get_filtered_presence(stanza); local occupant = self._occupants[stanza.attr.from]; @@ -465,10 +476,10 @@ function room_mt:handle_join(origin, stanza) log("debug", "%s joining as %s", from, to); if not next(self._affiliations) then -- new room, no owners self._affiliations[jid_bare(from)] = "owner"; - if self.locked and not stanza:get_child("x", "http://jabber.org/protocol/muc") then - self.locked = nil; -- Older groupchat protocol doesn't lock + if self:is_locked() and not stanza:get_child("x", "http://jabber.org/protocol/muc") then + self:unlock(); -- Older groupchat protocol doesn't lock end - elseif self.locked then -- Deny entry + elseif self:is_locked() then -- Deny entry origin.send(st.error_reply(stanza, "cancel", "item-not-found")); return true; end @@ -494,7 +505,7 @@ function room_mt:handle_join(origin, stanza) if self:get_whois() == 'anyone' then pr:tag("status", {code='100'}):up(); end - if self.locked then + if self:is_locked() then pr:tag("status", {code='201'}):up(); end pr.attr.to = from; @@ -777,9 +788,8 @@ function room_mt:process_form(origin, stanza) handle_option("password", "muc#roomconfig_roomsecret"); if self.save then self:save(true); end - if self.locked then - module:fire_event("muc-room-unlocked", { room = self }); - self.locked = nil; + if self:is_locked() then + self:unlock(); end origin.send(st.reply(stanza)); @@ -1267,6 +1277,7 @@ local _M = {}; -- module "muc" function _M.new_room(jid, config) return setmetatable({ jid = jid; + locked = nil; _jid_nick = {}; _occupants = {}; _data = { -- cgit v1.2.3 From 513bef46c407735b98e45cf49aec968f0ec1019e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 15:22:02 -0400 Subject: plugins/muc/muc.lib: Add route_to_occupant function to send a stanza to all occupant sessions --- plugins/muc/muc.lib.lua | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 746814ca..af9bd162 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -96,6 +96,15 @@ function room_mt:is_locked() return not not self.locked end +function room_mt:route_to_occupant(o_data, stanza) + local to = stanza.attr.to; + for jid in pairs(o_data.sessions) do + stanza.attr.to = jid; + self:_route_stanza(stanza); + end + stanza.attr.to = to; +end + function room_mt:broadcast_presence(stanza, sid, code, nick) stanza = get_filtered_presence(stanza); local occupant = self._occupants[stanza.attr.from]; @@ -113,14 +122,9 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) end end function room_mt:broadcast_message(stanza, historic) - local to = stanza.attr.to; - for occupant, o_data in pairs(self._occupants) do - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end + for occupant_jid, o_data in pairs(self._occupants) do + self:route_to_occupant(o_data, stanza) end - stanza.attr.to = to; if historic then -- add to history return self:save_to_history(stanza) end @@ -660,11 +664,8 @@ function room_mt:handle_message_to_occupant(origin, stanza) log("debug", "%s sent private message stanza to %s (%s)", from, to, o_data.jid); stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); stanza.attr.from = current_nick; - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end - stanza.attr.from, stanza.attr.to = from, to; + self:route_to_occupant(o_data, stanza) + stanza.attr.from = from; return true; end -- cgit v1.2.3 From c834ec947312d2fd929086991c50dcf3dfe2899b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 15:50:29 -0400 Subject: plugins/muc/muc.lib: Add decline event for parity with invite --- plugins/muc/muc.lib.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index af9bd162..7f8cc016 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1064,7 +1064,7 @@ function room_mt:handle_mediated_decline(origin, stanza) :tag('body') -- Add a plain message for clients which don't support declines :text(from..' declined your invite to the room '..to..(reason and (' ('..reason..')') or "")) :up(); - self:_route_stanza(decline); + module:fire_event("muc-decline", { room = self, stanza = decline, origin = origin, incoming = stanza }); return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); @@ -1072,6 +1072,11 @@ function room_mt:handle_mediated_decline(origin, stanza) end end +module:hook("muc-decline", function(event) + event.room:_route_stanza(event.stanza); + return true; +end, -1) + function room_mt:handle_message_to_room(origin, stanza) local type = stanza.attr.type; if type == "groupchat" then -- cgit v1.2.3 From ded172dcc5fed433bc4c7ce184ee1185b72d3f41 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 16:14:22 -0400 Subject: plugins/muc/muc.lib: Remove duplicate variable; it can never be nil. --- plugins/muc/muc.lib.lua | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7f8cc016..ac841248 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -114,12 +114,9 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) stanza:tag("status", {code=code}):up(); end self:broadcast_except_nick(stanza, stanza.attr.from); - local me = self._occupants[stanza.attr.from]; - if me then - stanza:tag("status", {code='110'}):up(); - stanza.attr.to = sid; - self:_route_stanza(stanza); - end + stanza:tag("status", {code='110'}):up(); + stanza.attr.to = sid; + self:_route_stanza(stanza); end function room_mt:broadcast_message(stanza, historic) for occupant_jid, o_data in pairs(self._occupants) do -- cgit v1.2.3 From 7951abf6fcb884bf455ffd24abbf80e0b40e7bab Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 20 Mar 2014 16:19:13 -0400 Subject: plugins/muc/muc.lib: Additional `route_to_occupant` usage --- plugins/muc/muc.lib.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ac841248..6dce1a80 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -141,10 +141,7 @@ end function room_mt:broadcast_except_nick(stanza, nick) for rnick, occupant in pairs(self._occupants) do if rnick ~= nick then - for jid in pairs(occupant.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); - end + self:route_to_occupant(occupant, stanza) end end end -- cgit v1.2.3 From 0da972669cd49a90ba70cec5166dcc6ed2d5a1fd Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 21 Mar 2014 14:01:02 -0400 Subject: plugins/muc/muc.lib: Move password check and nick conflict check into `handle_join` --- plugins/muc/muc.lib.lua | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6dce1a80..d859812f 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -472,6 +472,23 @@ end function room_mt:handle_join(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; log("debug", "%s joining as %s", from, to); + local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); + password = password and password:get_child("password", "http://jabber.org/protocol/muc"); + password = password and password[1] ~= "" and password[1]; + if self:get_password() and self:get_password() ~= password then + log("debug", "%s couldn't join due to invalid password: %s", from, to); + local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); + reply.tags[1].attr.code = "401"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + elseif self._occupants[to] -- occupant already exists + and jid_bare(from) ~= jid_bare(self._occupants[to].jid) then -- and has different bare real jid + log("debug", "%s couldn't join due to nick conflict: %s", from, to); + local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + reply.tags[1].attr.code = "409"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end if not next(self._affiliations) then -- new room, no owners self._affiliations[jid_bare(from)] = "owner"; if self:is_locked() and not stanza:get_child("x", "http://jabber.org/protocol/muc") then @@ -541,30 +558,7 @@ function room_mt:handle_available_to_occupant(origin, stanza) -- self:handle_to_occupant(origin, stanza); -- resend available --end else -- enter room - local new_nick = to; - if self._occupants[to] then - if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then - new_nick = nil; - end - end - local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); - password = password and password:get_child("password", "http://jabber.org/protocol/muc"); - password = password and password[1] ~= "" and password[1]; - if self:get_password() and self:get_password() ~= password then - log("debug", "%s couldn't join due to invalid password: %s", from, to); - local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); - reply.tags[1].attr.code = "401"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - elseif not new_nick then - log("debug", "%s couldn't join due to nick conflict: %s", from, to); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); - reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - else - return self:handle_join(origin, stanza) - end + return self:handle_join(origin, stanza) end end -- cgit v1.2.3 From a8855463cfb002193502d19541b9575960ad8072 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 10:25:43 -0400 Subject: plugins/muc/muc.lib: Add muc-occupant-prejoin events; Use it for banned, members-only, password, nick-conflict and lock checks This reorders some of the checks. Importantly; affiliations are checked first: this means banned users cannot try and guess passwords --- plugins/muc/muc.lib.lua | 82 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d859812f..1b3df029 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -469,36 +469,68 @@ function room_mt:handle_change_nick(origin, stanza, current_nick, to) end end -function room_mt:handle_join(origin, stanza) +module:hook("muc-occupant-pre-join", function(event) + return module:fire_event("muc-occupant-pre-join/affiliation", event) + or module:fire_event("muc-occupant-pre-join/password", event) + or module:fire_event("muc-occupant-pre-join/locked", event) + or module:fire_event("muc-occupant-pre-join/nick-conflict", event) +end, -1) + +module:hook("muc-occupant-pre-join/password", function(event) + local room, stanza = event.room, event.stanza; local from, to = stanza.attr.from, stanza.attr.to; - log("debug", "%s joining as %s", from, to); local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); password = password and password:get_child("password", "http://jabber.org/protocol/muc"); password = password and password[1] ~= "" and password[1]; - if self:get_password() and self:get_password() ~= password then + if room:get_password() and room:get_password() ~= password then + local from, to = stanza.attr.from, stanza.attr.to; log("debug", "%s couldn't join due to invalid password: %s", from, to); local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); reply.tags[1].attr.code = "401"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; - elseif self._occupants[to] -- occupant already exists - and jid_bare(from) ~= jid_bare(self._occupants[to].jid) then -- and has different bare real jid + end +end, -1) + +module:hook("muc-occupant-pre-join/nick-conflict", function(event) + local room, stanza = event.room, event.stanza; + local from, to = stanza.attr.from, stanza.attr.to; + local occupant = room._occupants[to] + if occupant -- occupant already exists + and jid_bare(from) ~= jid_bare(occupant.jid) then -- and has different bare real jid log("debug", "%s couldn't join due to nick conflict: %s", from, to); local reply = st.error_reply(stanza, "cancel", "conflict"):up(); reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end +end, -1) + +module:hook("muc-occupant-pre-join/locked", function(event) + if event.room:is_locked() then -- Deny entry + event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); return true; end - if not next(self._affiliations) then -- new room, no owners - self._affiliations[jid_bare(from)] = "owner"; +end, -1) + +function room_mt:handle_join(origin, stanza) + local from, to = stanza.attr.from, stanza.attr.to; + local affiliation = self:get_affiliation(from); + if affiliation == nil and next(self._affiliations) == nil then -- new room, no owners + affiliation = "owner"; + self._affiliations[jid_bare(from)] = affiliation; if self:is_locked() and not stanza:get_child("x", "http://jabber.org/protocol/muc") then self:unlock(); -- Older groupchat protocol doesn't lock end - elseif self:is_locked() then -- Deny entry - origin.send(st.error_reply(stanza, "cancel", "item-not-found")); - return true; end - local affiliation = self:get_affiliation(from); + if module:fire_event("muc-occupant-pre-join", { + room = self; + origin = origin; + stanza = stanza; + affiliation = affiliation; + }) then return true; end + log("debug", "%s joining as %s", from, to); + local role = self:get_default_role(affiliation) if role then -- new occupant local is_merge = not not self._occupants[to] @@ -528,18 +560,28 @@ function room_mt:handle_join(origin, stanza) self:send_history(from, stanza); self:send_subject(from); return true; - elseif not affiliation then -- registration required for entering members-only room - local reply = st.error_reply(stanza, "auth", "registration-required"):up(); + end +end + +-- registration required for entering members-only room +module:hook("muc-occupant-pre-join/affiliation", function(event) + if event.affiliation == nil and event.room:get_members_only() then + local reply = st.error_reply(event.stanza, "auth", "registration-required"):up(); reply.tags[1].attr.code = "407"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; - else -- banned - local reply = st.error_reply(stanza, "auth", "forbidden"):up(); + end +end, -1) + +-- banned +module:hook("muc-occupant-pre-join/affiliation", function(event) + if event.affiliation == "outcast" then + local reply = st.error_reply(event.stanza, "auth", "forbidden"):up(); reply.tags[1].attr.code = "403"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end +end, -1) function room_mt:handle_available_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; -- cgit v1.2.3 From a86714d770459194c8be1e6350220e2bd9c844e5 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 12:44:31 -0400 Subject: plugins/muc/muc.lib: Better password check --- plugins/muc/muc.lib.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1b3df029..821cb082 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -480,9 +480,9 @@ module:hook("muc-occupant-pre-join/password", function(event) local room, stanza = event.room, event.stanza; local from, to = stanza.attr.from, stanza.attr.to; local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); - password = password and password:get_child("password", "http://jabber.org/protocol/muc"); - password = password and password[1] ~= "" and password[1]; - if room:get_password() and room:get_password() ~= password then + password = password and password:get_child_text("password", "http://jabber.org/protocol/muc"); + if not password or password == "" then password = nil; end + if room:get_password() ~= password then local from, to = stanza.attr.from, stanza.attr.to; log("debug", "%s couldn't join due to invalid password: %s", from, to); local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); -- cgit v1.2.3 From 8de79917fccca8e42804ff91547ff215f4e94c86 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 13:10:54 -0400 Subject: plugins/muc/muc.lib: Add muc-broadcast-message event. Use it for saving to history --- plugins/muc/muc.lib.lua | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 821cb082..4366716a 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -119,25 +119,29 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) self:_route_stanza(stanza); end function room_mt:broadcast_message(stanza, historic) + module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic}); for occupant_jid, o_data in pairs(self._occupants) do self:route_to_occupant(o_data, stanza) end - if historic then -- add to history - return self:save_to_history(stanza) - end -end -function room_mt:save_to_history(stanza) - local history = self._data['history']; - if not history then history = {}; self._data['history'] = history; end - stanza = st.clone(stanza); - stanza.attr.to = ""; - local stamp = datetime.datetime(); - stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 - stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) - local entry = { stanza = stanza, stamp = stamp }; - t_insert(history, entry); - while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end end + +-- add to history +module:hook("muc-broadcast-message", function(event) + if event.historic then + local room = event.room + local history = room._data['history']; + if not history then history = {}; room._data['history'] = history; end + local stanza = st.clone(event.stanza); + stanza.attr.to = ""; + local stamp = datetime.datetime(); + stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 + stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) + local entry = { stanza = stanza, stamp = stamp }; + t_insert(history, entry); + while #history > room:get_historylength() do t_remove(history, 1) end + end +end) + function room_mt:broadcast_except_nick(stanza, nick) for rnick, occupant in pairs(self._occupants) do if rnick ~= nick then -- cgit v1.2.3 From 9dce8113050677fc6e1e227aa3c11644296b1285 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 13:34:06 -0400 Subject: plugins/muc/muc.lib: Add muc-get-history event; it uses an iterator in the event object so that messages don't need to be all in memory at once --- plugins/muc/muc.lib.lua | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 4366716a..791938db 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -193,13 +193,17 @@ local function parse_history(stanza) return maxchars, maxstanzas, since end --- Get history for 'to' -function room_mt:get_history(to, maxchars, maxstanzas, since) - local history = self._data['history']; -- send discussion history - if not history then return function() end end + +module:hook("muc-get-history", function(event) + local room = event.room + local history = room._data['history']; -- send discussion history + if not history then return nil end local history_len = #history - maxstanzas = maxstanzas or history_len + local to = event.to + local maxchars = event.maxchars + local maxstanzas = event.maxstanzas or history_len + local since = event.since local n = 0; local charcount = 0; for i=history_len,1,-1 do @@ -218,7 +222,7 @@ function room_mt:get_history(to, maxchars, maxstanzas, since) end local i = history_len-n+1 - return function() + function event:next_stanza() if i > history_len then return nil end local entry = history[i] local msg = entry.stanza @@ -226,10 +230,19 @@ function room_mt:get_history(to, maxchars, maxstanzas, since) i = i + 1 return msg end -end -function room_mt:send_history(to, stanza) + return true; +end) + +function room_mt:send_history(stanza) local maxchars, maxstanzas, since = parse_history(stanza) - for msg in self:get_history(to, maxchars, maxstanzas, since) do + local event = { + room = self; + to = stanza.attr.from; -- `to` is required to calculate the character count for `maxchars` + maxchars = maxchars, maxstanzas = maxstanzas, since = since; + next_stanza = function() end; -- events should define this iterator + } + module:fire_event("muc-get-history", event) + for msg in event.next_stanza , event do self:_route_stanza(msg); end end -- cgit v1.2.3 From 9b13e219b6b39ba568f62cf821b9a68bc4c56930 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 13:36:43 -0400 Subject: plugins/muc/muc.lib: Extra utility functions around subjects --- plugins/muc/muc.lib.lua | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 791938db..d0a5641c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -246,11 +246,6 @@ function room_mt:send_history(stanza) self:_route_stanza(msg); end end -function room_mt:send_subject(to) - if self._data['subject'] then - self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject'])); - end -end function room_mt:get_disco_info(stanza) local count = 0; for _ in pairs(self._occupants) do count = count + 1; end @@ -277,13 +272,30 @@ function room_mt:get_disco_items(stanza) end return reply; end + +function room_mt:get_subject() + return self._data['subject'], self._data['subject_from'] +end +local function create_subject_message(subject) + return st.message({type='groupchat'}) + :tag('subject'):text(subject):up(); +end +function room_mt:send_subject(to) + local from, subject = self:get_subject() + if subject then + local msg = create_subject_message(subject) + msg.attr.from = from + msg.attr.to = to + self:_route_stanza(msg); + end +end function room_mt:set_subject(current_nick, subject) if subject == "" then subject = nil; end self._data['subject'] = subject; self._data['subject_from'] = current_nick; if self.save then self:save(); end - local msg = st.message({type='groupchat', from=current_nick}) - :tag('subject'):text(subject):up(); + local msg = create_subject_message(subject) + msg.attr.from = current_nick self:broadcast_message(msg, false); return true; end -- cgit v1.2.3 From 2e431e6c6dbb1b184775d4b47780317688a09443 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 24 Mar 2014 16:32:18 -0400 Subject: plugins/muc/muc.lib: Add :broadcast method; use it from :broadcast_except_nick and :broadcast_message --- plugins/muc/muc.lib.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d0a5641c..c955d47b 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -120,9 +120,7 @@ function room_mt:broadcast_presence(stanza, sid, code, nick) end function room_mt:broadcast_message(stanza, historic) module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic}); - for occupant_jid, o_data in pairs(self._occupants) do - self:route_to_occupant(o_data, stanza) - end + self:broadcast(stanza); end -- add to history @@ -143,8 +141,14 @@ module:hook("muc-broadcast-message", function(event) end) function room_mt:broadcast_except_nick(stanza, nick) - for rnick, occupant in pairs(self._occupants) do - if rnick ~= nick then + return self:broadcast(stanza, function(rnick, occupant) return rnick ~= nick end) +end + +-- Broadcast a stanza to all occupants in the room. +-- optionally checks conditional called with nicl +function room_mt:broadcast(stanza, cond_func) + for nick, occupant in pairs(self._occupants) do + if cond_func == nil or cond_func(nick, occupant) then self:route_to_occupant(occupant, stanza) end end -- cgit v1.2.3 From 2d7176e0945645d177bab94195334a887f4fd001 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 18:09:42 -0400 Subject: plugins/muc: Rename admin query hook --- plugins/muc/mod_muc.lua | 8 ++++---- plugins/muc/muc.lib.lua | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index e8782414..8759cba4 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -179,8 +179,8 @@ for event_name, method in pairs { -- Normal room interactions ["iq-get/bare/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; ["iq-get/bare/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; - ["iq-set/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; - ["iq-get/bare/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/bare/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ; + ["iq-get/bare/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_get_command" ; ["iq-set/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; ["iq-get/bare/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; ["message/bare"] = "handle_message_to_room" ; @@ -188,8 +188,8 @@ for event_name, method in pairs { -- Host room ["iq-get/host/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; ["iq-get/host/http://jabber.org/protocol/disco#items:query"] = "handle_disco_items_get_query" ; - ["iq-set/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_set_command" ; - ["iq-get/host/http://jabber.org/protocol/muc#admin:item"] = "handle_admin_item_get_command" ; + ["iq-set/host/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ; + ["iq-get/host/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_get_command" ; ["iq-set/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_set_to_room" ; ["iq-get/host/http://jabber.org/protocol/muc#owner:query"] = "handle_owner_query_get_to_room" ; ["message/host"] = "handle_message_to_room" ; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index c955d47b..7e41b225 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -900,7 +900,7 @@ function room_mt:handle_disco_items_get_query(origin, stanza) return true; end -function room_mt:handle_admin_item_set_command(origin, stanza) +function room_mt:handle_admin_query_set_command(origin, stanza) local item = stanza.tags[1].tags[1]; if item.attr.jid then -- Validate provided JID item.attr.jid = jid_prep(item.attr.jid); @@ -933,7 +933,7 @@ function room_mt:handle_admin_item_set_command(origin, stanza) end end -function room_mt:handle_admin_item_get_command(origin, stanza) +function room_mt:handle_admin_query_get_command(origin, stanza) local actor = stanza.attr.from; local affiliation = self:get_affiliation(actor); local item = stanza.tags[1].tags[1]; -- cgit v1.2.3 From 2a107b50cd289c9d522232b3764c308959a7ca52 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 18:10:34 -0400 Subject: plugins/muc/muc.lib: Have timestamp as seconds since epoch inside of history --- plugins/muc/muc.lib.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7e41b225..20cba225 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -131,10 +131,11 @@ module:hook("muc-broadcast-message", function(event) if not history then history = {}; room._data['history'] = history; end local stanza = st.clone(event.stanza); stanza.attr.to = ""; - local stamp = datetime.datetime(); + local ts = gettime(); + local stamp = datetime.datetime(ts); stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) - local entry = { stanza = stanza, stamp = stamp }; + local entry = { stanza = stanza, timestamp = ts }; t_insert(history, entry); while #history > room:get_historylength() do t_remove(history, 1) end end @@ -220,7 +221,7 @@ module:hook("muc-get-history", function(event) charcount = charcount + entry.chars + #to; if charcount > maxchars then break; end end - if since and since > entry.stamp then break; end + if since and since > entry.timestamp then break; end if n + 1 > maxstanzas then break; end n = n + 1; end -- cgit v1.2.3 From 985fd9a14a12618d1e3759426d55c02df2957d51 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 19:16:13 -0400 Subject: plugins/muc: Massive refactor We now have occupant objects; you grab them, modify them, save them. New presence handling code. Modify all presence sending to go via new functions. --- plugins/muc/mod_muc.lua | 19 +- plugins/muc/muc.lib.lua | 779 +++++++++++++++++++++++-------------------- plugins/muc/occupant.lib.lua | 85 +++++ 3 files changed, 503 insertions(+), 380 deletions(-) create mode 100644 plugins/muc/occupant.lib.lua diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 8759cba4..a8a6388d 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -228,27 +228,14 @@ module.restore = function(data) hosts[module:get_host()].muc = { rooms = rooms }; end -function shutdown_room(room, stanza) - for nick, occupant in pairs(room._occupants) do - stanza.attr.from = nick; - for jid in pairs(occupant.sessions) do - stanza.attr.to = jid; - room:_route_stanza(stanza); - room._jid_nick[jid] = nil; - end - room._occupants[nick] = nil; - end -end function shutdown_component() if not saved then - local stanza = st.presence({type = "unavailable"}) - :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("item", { affiliation='none', role='none' }):up() + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("status", { code = "332"}):up(); for roomjid, room in pairs(rooms) do - shutdown_room(room, stanza); + room:clear(x); end - shutdown_room(host_room, stanza); + host_room:clear(x); end end module.unload = shutdown_component; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 27c50cd4..76fd7ee2 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -25,23 +25,9 @@ local setmetatable = setmetatable; local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; -local default_history_length, max_history_length = 20, math.huge; +local occupant_lib = module:require "muc/occupant" -local get_filtered_presence do - local presence_filters = { - ["http://jabber.org/protocol/muc"] = true; - ["http://jabber.org/protocol/muc#user"] = true; - } - local function presence_filter(tag) - if presence_filters[tag.attr.xmlns] then - return nil; - end - return tag; - end - function get_filtered_presence(stanza) - return st.clone(stanza):maptags(presence_filter); - end -end +local default_history_length, max_history_length = 20, math.huge; local is_kickable_error do local kickable_error_conditions = { @@ -96,28 +82,103 @@ function room_mt:is_locked() return not not self.locked end -function room_mt:route_to_occupant(o_data, stanza) +--- Occupant functions +function room_mt:new_occupant(bare_real_jid, nick) + local occupant = occupant_lib.new(bare_real_jid, nick); + local affiliation = self:get_affiliation(bare_real_jid); + occupant.role = self:get_default_role(affiliation); + return occupant; +end + +function room_mt:get_occupant_by_nick(nick) + local occupant = self._occupants[nick]; + if occupant == nil then return nil end + return occupant_lib.copy(occupant); +end + +do + local function next_copied_occupant(occupants, occupant_jid) + local next_occupant_jid, raw_occupant = next(occupants, occupant_jid); + if next_occupant_jid == nil then return nil end + return next_occupant_jid, occupant_lib.copy(raw_occupant); + end + function room_mt:each_occupant(read_only) + return next_copied_occupant, self._occupants, nil; + end +end + +function room_mt:get_occupant_by_real_jid(real_jid) + local occupant_jid = self:get_occupant_jid(real_jid); + if occupant_jid == nil then return nil end + return self:get_occupant_by_nick(occupant_jid); +end + +function room_mt:save_occupant(occupant) + occupant = occupant_lib.copy(occupant); -- So that occupant can be modified more + local id = occupant.nick + + -- Need to maintain _jid_nick secondary index + local old_occupant = self._occupants[id]; + if old_occupant then + for real_jid in pairs(old_occupant.sessions) do + self._jid_nick[real_jid] = nil; + end + end + if occupant.role ~= nil and next(occupant.sessions) then + for real_jid, presence in occupant:each_session() do + self._jid_nick[real_jid] = occupant.nick; + end + else + occupant = nil + end + self._occupants[id] = occupant +end + +function room_mt:route_to_occupant(occupant, stanza) local to = stanza.attr.to; - for jid in pairs(o_data.sessions) do - stanza.attr.to = jid; - self:_route_stanza(stanza); + for jid, pr in occupant:each_session() do + if pr.attr.type ~= "unavailable" then + stanza.attr.to = jid; + self:route_stanza(stanza); + end end stanza.attr.to = to; end -function room_mt:broadcast_presence(stanza, sid, code, nick) - stanza = get_filtered_presence(stanza); - local occupant = self._occupants[stanza.attr.from]; - stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none", nick=nick}):up(); - if code then - stanza:tag("status", {code=code}):up(); +-- Adds an item to an "x" element. +-- actor is the attribute table +local function add_item(x, affiliation, role, jid, nick, actor, reason) + x:tag("item", {affiliation = affiliation; role = role; jid = jid; nick = nick;}) + if actor then + x:tag("actor", actor):up() + end + if reason then + x:tag("reason"):text(reason):up() + end + x:up(); + return x +end +-- actor is (real) jid +function room_mt:build_item_list(occupant, x, is_anonymous, nick, actor, reason) + local affiliation = self:get_affiliation(occupant.bare_jid); + local role = occupant.role; + local actor_jid = actor and self:get_occupant_jid(actor); + if actor then + actor = {nick = select(3,jid_split(actor_jid))}; + end + if is_anonymous then + add_item(x, affiliation, role, nil, nick, actor, reason); + else + if actor_jid then + actor.jid = actor_jid; + end + for real_jid, session in occupant:each_session() do + add_item(x, affiliation, role, real_jid, nick, actor, reason); + end end - self:broadcast_except_nick(stanza, stanza.attr.from); - stanza:tag("status", {code='110'}):up(); - stanza.attr.to = sid; - self:_route_stanza(stanza); + return x end + function room_mt:broadcast_message(stanza, historic) module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic}); self:broadcast(stanza); @@ -139,31 +200,89 @@ module:hook("muc-broadcast-message", function(event) t_insert(history, entry); while #history > room:get_historylength() do t_remove(history, 1) end end -end) - -function room_mt:broadcast_except_nick(stanza, nick) - return self:broadcast(stanza, function(rnick, occupant) return rnick ~= nick end) -end +end); -- Broadcast a stanza to all occupants in the room. --- optionally checks conditional called with nicl +-- optionally checks conditional called with (nick, occupant) function room_mt:broadcast(stanza, cond_func) - for nick, occupant in pairs(self._occupants) do + for nick, occupant in self:each_occupant() do if cond_func == nil or cond_func(nick, occupant) then self:route_to_occupant(occupant, stanza) end end end -function room_mt:send_occupant_list(to) - local current_nick = self:get_occupant_jid(to); - for occupant, o_data in pairs(self._occupants) do - if occupant ~= current_nick then - local pres = get_filtered_presence(o_data.sessions[o_data.jid]); - pres.attr.to, pres.attr.from = to, occupant; - pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=o_data.affiliation or "none", role=o_data.role or "none"}):up(); - self:_route_stanza(pres); +-- Broadcasts an occupant's presence to the whole room +-- Takes (and modifies) the x element that goes into the stanzas +function room_mt:publicise_occupant_status(occupant, full_x, actor, reason) + local anon_x; + local has_anonymous = self:get_whois() ~= "anyone"; + if has_anonymous then + anon_x = st.clone(full_x); + self:build_item_list(occupant, anon_x, true, nil, actor, reason); + end + self:build_item_list(occupant,full_x, false, nil, actor, reason); + + -- General populance + local full_p + if occupant.role ~= nil then + -- Try to use main jid's presence + local pr = occupant:get_presence(); + if pr ~= nil then + full_p = st.clone(pr); + end + end + if full_p == nil then + full_p = st.presence{from=occupant.nick; type="unavailable"}; + end + local anon_p; + if has_anonymous then + anon_p = st.clone(full_p); + anon_p:add_child(anon_x); + end + full_p:add_child(full_x); + + for nick, n_occupant in self:each_occupant() do + if nick ~= occupant.nick or n_occupant.role == nil then + local pr = full_p; + if has_anonymous and n_occupant.role ~= "moderators" and occupant.bare_jid ~= n_occupant.bare_jid then + pr = anon_p; + end + self:route_to_occupant(n_occupant, pr); + end + end + + -- Presences for occupant itself + full_x:tag("status", {code = "110";}):up(); + if occupant.role == nil then + -- They get an unavailable + self:route_to_occupant(occupant, full_p); + else + -- use their own presences as templates + for full_jid, pr in occupant:each_session() do + if pr.attr.type ~= "unavailable" then + pr = st.clone(pr); + pr.attr.to = full_jid; + -- You can always see your own full jids + pr:add_child(full_x); + self:route_stanza(pr); + end + end + end +end + +function room_mt:send_occupant_list(to, filter) + local to_occupant = self:get_occupant_by_real_jid(to); + local has_anonymous = self:get_whois() ~= "anyone" + for occupant_jid, occupant in self:each_occupant() do + if filter and filter(occupant_jid, occupant) then + local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); + local is_anonymous = has_anonymous and occupant.role ~= "moderator" and to_occupant.bare_jid ~= occupant.bare_jid; + self:build_item_list(occupant, x, is_anonymous); + local pres = st.clone(occupant:get_presence()); + pres.attr.to = to; + pres:add_child(x); + self:route_stanza(pres); end end end @@ -248,12 +367,12 @@ function room_mt:send_history(stanza) } module:fire_event("muc-get-history", event) for msg in event.next_stanza , event do - self:_route_stanza(msg); + self:route_stanza(msg); end end function room_mt:get_disco_info(stanza) - local count = 0; for _ in pairs(self._occupants) do count = count + 1; end + local count = 0; for _ in self:each_occupant() do count = count + 1; end return st.reply(stanza):query("http://jabber.org/protocol/disco#info") :tag("identity", {category="conference", type="text", name=self:get_name()}):up() :tag("feature", {var="http://jabber.org/protocol/muc"}):up() @@ -272,7 +391,7 @@ function room_mt:get_disco_info(stanza) end function room_mt:get_disco_items(stanza) local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items"); - for room_jid in pairs(self._occupants) do + for room_jid in self:each_occupant() do reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up(); end return reply; @@ -291,7 +410,7 @@ function room_mt:send_subject(to) local msg = create_subject_message(subject) msg.attr.from = from msg.attr.to = to - self:_route_stanza(msg); + self:route_stanza(msg); end end function room_mt:set_subject(current_nick, subject) @@ -306,14 +425,20 @@ function room_mt:set_subject(current_nick, subject) end function room_mt:handle_kickable(origin, stanza) + local real_jid = stanza.attr.from; + local occupant = self:get_occupant_by_real_jid(real_jid); + if occupant == nil then return nil; end local type, condition, text = stanza:get_error(); local error_message = "Kicked: "..(condition and condition:gsub("%-", " ") or "presence error"); if text then error_message = error_message..": "..text; end - local kick_stanza = st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to}) - :tag('status'):text(error_message); - self:handle_unavailable_to_occupant(origin, kick_stanza); -- send unavailable + occupant:set_session(real_jid, st.presence({type="unavailable"}) + :tag('status'):text(error_message)); + self:save_occupant(occupant); + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}) + :tag("status", {code = "307"}) + self:publicise_occupant_status(occupant, x); return true; end @@ -428,91 +553,26 @@ function room_mt:get_whois() return self._data.whois; end -function room_mt:handle_unavailable_to_occupant(origin, stanza) - local from = stanza.attr.from; - local current_nick = self:get_occupant_jid(from); - if not current_nick then - return true; -- discard - end - local pr = get_filtered_presence(stanza); - pr.attr.from = current_nick; - log("debug", "%s leaving %s", current_nick, self.jid); - self._jid_nick[from] = nil; - local occupant = self._occupants[current_nick]; - local new_jid = next(occupant.sessions); - if new_jid == from then new_jid = next(occupant.sessions, new_jid); end - if new_jid then - local jid = occupant.jid; - occupant.jid = new_jid; - occupant.sessions[from] = nil; - pr.attr.to = from; - pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up() - :tag("status", {code='110'}):up(); - self:_route_stanza(pr); - if jid ~= new_jid then - pr = st.clone(occupant.sessions[new_jid]) - :tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"}); - pr.attr.from = current_nick; - self:broadcast_except_nick(pr, current_nick); - end - else - occupant.role = 'none'; - self:broadcast_presence(pr, from); - self._occupants[current_nick] = nil; - module:fire_event("muc-occupant-left", { room = self; nick = current_nick; }); +module:hook("muc-room-pre-create", function(event) + local room = event.room; + if room:is_locked() and not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then + room:unlock(); -- Older groupchat protocol doesn't lock end - return true; -end +end, 10); -function room_mt:handle_occupant_presence(origin, stanza) - local from = stanza.attr.from; - local pr = get_filtered_presence(stanza); - local current_nick = stanza.attr.to - pr.attr.from = current_nick; - log("debug", "%s broadcasted presence", current_nick); - self._occupants[current_nick].sessions[from] = pr; - self:broadcast_presence(pr, from); - return true; -end - -function room_mt:handle_change_nick(origin, stanza, current_nick, to) - local from = stanza.attr.from; - local occupant = self._occupants[current_nick]; - local is_multisession = next(occupant.sessions, next(occupant.sessions)); - if self._occupants[to] or is_multisession then - log("debug", "%s couldn't change nick", current_nick); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); - reply.tags[1].attr.code = "409"; - origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - else - local to_nick = select(3, jid_split(to)); - log("debug", "%s (%s) changing nick to %s", current_nick, occupant.jid, to); - local p = st.presence({type='unavailable', from=current_nick}); - self:broadcast_presence(p, from, '303', to_nick); - self._occupants[current_nick] = nil; - self._occupants[to] = occupant; - self._jid_nick[from] = to; - local pr = get_filtered_presence(stanza); - pr.attr.from = to; - self._occupants[to].sessions[from] = pr; - self:broadcast_presence(pr, from); - return true; - end -end +-- Give the room creator owner affiliation +module:hook("muc-room-pre-create", function(event) + event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); +end, -1); module:hook("muc-occupant-pre-join", function(event) return module:fire_event("muc-occupant-pre-join/affiliation", event) or module:fire_event("muc-occupant-pre-join/password", event) - or module:fire_event("muc-occupant-pre-join/locked", event) - or module:fire_event("muc-occupant-pre-join/nick-conflict", event) + or module:fire_event("muc-occupant-pre-join/locked", event); end, -1) module:hook("muc-occupant-pre-join/password", function(event) local room, stanza = event.room, event.stanza; - local from, to = stanza.attr.from, stanza.attr.to; local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); password = password and password:get_child_text("password", "http://jabber.org/protocol/muc"); if not password or password == "" then password = nil; end @@ -526,81 +586,19 @@ module:hook("muc-occupant-pre-join/password", function(event) end end, -1) -module:hook("muc-occupant-pre-join/nick-conflict", function(event) - local room, stanza = event.room, event.stanza; - local from, to = stanza.attr.from, stanza.attr.to; - local occupant = room._occupants[to] - if occupant -- occupant already exists - and jid_bare(from) ~= jid_bare(occupant.jid) then -- and has different bare real jid - log("debug", "%s couldn't join due to nick conflict: %s", from, to); - local reply = st.error_reply(stanza, "cancel", "conflict"):up(); - reply.tags[1].attr.code = "409"; - event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - end -end, -1) - module:hook("muc-occupant-pre-join/locked", function(event) if event.room:is_locked() then -- Deny entry event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); return true; end -end, -1) - -function room_mt:handle_join(origin, stanza) - local from, to = stanza.attr.from, stanza.attr.to; - local affiliation = self:get_affiliation(from); - if affiliation == nil and next(self._affiliations) == nil then -- new room, no owners - affiliation = "owner"; - self._affiliations[jid_bare(from)] = affiliation; - if self:is_locked() and not stanza:get_child("x", "http://jabber.org/protocol/muc") then - self:unlock(); -- Older groupchat protocol doesn't lock - end - end - if module:fire_event("muc-occupant-pre-join", { - room = self; - origin = origin; - stanza = stanza; - affiliation = affiliation; - }) then return true; end - log("debug", "%s joining as %s", from, to); - - local role = self:get_default_role(affiliation) - if role then -- new occupant - local is_merge = not not self._occupants[to] - if not is_merge then - self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}}; - else - self._occupants[to].sessions[from] = get_filtered_presence(stanza); - end - self._jid_nick[from] = to; - self:send_occupant_list(from); - local pr = get_filtered_presence(stanza); - pr.attr.from = to; - pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'}) - :tag("item", {affiliation=affiliation or "none", role=role or "none"}):up(); - if not is_merge then - self:broadcast_except_nick(pr, to); - end - pr:tag("status", {code='110'}):up(); - if self:get_whois() == 'anyone' then - pr:tag("status", {code='100'}):up(); - end - if self:is_locked() then - pr:tag("status", {code='201'}):up(); - end - pr.attr.to = from; - self:_route_stanza(pr); - self:send_history(from, stanza); - self:send_subject(from); - return true; - end -end +end, -1); -- registration required for entering members-only room module:hook("muc-occupant-pre-join/affiliation", function(event) - if event.affiliation == nil and event.room:get_members_only() then - local reply = st.error_reply(event.stanza, "auth", "registration-required"):up(); + local room, stanza = event.room, event.stanza; + local affiliation = room:get_affiliation(stanza.attr.from); + if affiliation == nil and event.room:get_members_only() then + local reply = st.error_reply(stanza, "auth", "registration-required"):up(); reply.tags[1].attr.code = "407"; event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; @@ -609,43 +607,173 @@ end, -1) -- banned module:hook("muc-occupant-pre-join/affiliation", function(event) - if event.affiliation == "outcast" then - local reply = st.error_reply(event.stanza, "auth", "forbidden"):up(); + local room, stanza = event.room, event.stanza; + local affiliation = room:get_affiliation(stanza.attr.from); + if affiliation == "outcast" then + local reply = st.error_reply(stanza, "auth", "forbidden"):up(); reply.tags[1].attr.code = "403"; event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end end, -1) -function room_mt:handle_available_to_occupant(origin, stanza) - local from, to = stanza.attr.from, stanza.attr.to; - local current_nick = self:get_occupant_jid(from); - if current_nick then - --if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence - if current_nick == to then -- simple presence - return self:handle_occupant_presence(origin, stanza) - else -- change nick - return self:handle_change_nick(origin, stanza, current_nick, to) - end - --else -- possible rejoin - -- log("debug", "%s had connection replaced", current_nick); - -- self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}) - -- :tag('status'):text('Replaced by new connection'):up()); -- send unavailable - -- self:handle_to_occupant(origin, stanza); -- resend available - --end - else -- enter room - return self:handle_join(origin, stanza) - end -end +module:hook("muc-occupant-joined", function(event) + local room, stanza = event.room, event.stanza; + local real_jid = stanza.attr.from; + room:send_occupant_list(real_jid, function(nick, occupant) + -- Don't include self + return occupant.sessions[real_jid] == nil + end); + room:send_history(stanza); + room:send_subject(real_jid); +end, -1); function room_mt:handle_presence_to_occupant(origin, stanza) local type = stanza.attr.type; if type == "error" then -- error, kick em out! return self:handle_kickable(origin, stanza) - elseif type == "unavailable" then -- unavailable - return self:handle_unavailable_to_occupant(origin, stanza) - elseif not type then -- available - return self:handle_available_to_occupant(origin, stanza) + elseif type == nil or type == "unavailable" then + local real_jid = stanza.attr.from; + local bare_jid = jid_bare(real_jid); + local orig_occupant, dest_occupant; + local is_new_room = next(self._affiliations) == nil; + if is_new_room then + if type == "unavailable" then return true; end -- Unavailable from someone not in the room + if module:fire_event("muc-room-pre-create", { + room = self; + origin = origin; + stanza = stanza; + }) then return true; end + else + orig_occupant = self:get_occupant_by_real_jid(real_jid); + if type == "unavailable" and orig_occupant == nil then return true; end -- Unavailable from someone not in the room + end + local is_first_dest_session; + if type == "unavailable" then + -- dest_occupant = nil + elseif orig_occupant and orig_occupant.nick == stanza.attr.to then -- Just a presence update + log("debug", "presence update for %s from session %s", orig_occupant.nick, real_jid); + dest_occupant = orig_occupant; + else + local dest_jid = stanza.attr.to; + dest_occupant = self:get_occupant_by_nick(dest_jid); + if dest_occupant == nil then + log("debug", "no occupant found for %s; creating new occupant object for %s", dest_jid, real_jid); + is_first_dest_session = true; + dest_occupant = self:new_occupant(bare_jid, dest_jid); + else + is_first_dest_session = false; + end + end + local is_last_orig_session; + if orig_occupant ~= nil then + -- Is there are least 2 sessions? + is_last_orig_session = next(orig_occupant.sessions, next(orig_occupant.sessions)) == nil; + end + + local event, event_name = { + room = self; + origin = origin; + stanza = stanza; + is_first_session = is_first_dest_session; + is_last_session = is_last_orig_session; + }; + if orig_occupant == nil then + event_name = "muc-occupant-pre-join"; + event.is_new_room = is_new_room; + elseif dest_occupant == nil then + event_name = "muc-occupant-pre-leave"; + else + event_name = "muc-occupant-pre-change"; + end + if module:fire_event(event_name, event) then return true; end + + -- Check for nick conflicts + if dest_occupant ~= nil and not is_first_dest_session and bare_jid ~= jid_bare(dest_occupant.bare_jid) then -- new nick or has different bare real jid + log("debug", "%s couldn't join due to nick conflict: %s", real_jid, dest_occupant.nick); + local reply = st.error_reply(stanza, "cancel", "conflict"):up(); + reply.tags[1].attr.code = "409"; + origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end + + -- Send presence stanza about original occupant + if orig_occupant ~= nil and orig_occupant ~= dest_occupant then + local orig_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + + if dest_occupant == nil then -- Session is leaving + log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); + orig_occupant:set_session(real_jid, stanza); + else + log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick); + orig_occupant:remove_session(real_jid); -- If we are moving to a new nick; we don't want to get our own presence + + local dest_nick = select(3, jid_split(dest_occupant.nick)); + local affiliation = self:get_affiliation(bare_jid); + + -- This session + if not is_first_dest_session then -- User is swapping into another pre-existing session + log("debug", "session %s is swapping into multisession %s, showing it leave.", real_jid, dest_occupant.nick); + -- Show the other session leaving + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}) + :tag("status"):text("you are joining pre-existing session " .. dest_nick):up(); + add_item(x, affiliation, "none"); + local pr = st.presence{from = dest_occupant.nick, to = real_jid, type = "unavailable"} + :add_child(x); + self:route_stanza(pr); + else + if is_last_orig_session then -- User is moving to a new session + log("debug", "no sessions in %s left; marking as nick change", orig_occupant.nick); + -- Everyone gets to see this as a nick change + local jid = self:get_whois() ~= "anyone" and real_jid or nil; -- FIXME: mods should see real jids + add_item(orig_x, affiliation, orig_occupant.role, jid, dest_nick); + orig_x:tag("status", {code = "303";}):up(); + end + end + -- The session itself always sees a nick change + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + add_item(x, affiliation, orig_occupant.role, real_jid, dest_nick); + -- self:build_item_list(orig_occupant, x, false); -- COMPAT + x:tag("status", {code = "303";}):up(); + x:tag("status", {code = "110";}):up(); + self:route_stanza(st.presence{from = dest_occupant.nick, to = real_jid, type = "unavailable"}:add_child(x)); + end + self:save_occupant(orig_occupant); + self:publicise_occupant_status(orig_occupant, orig_x); + + if is_last_orig_session then + module:fire_event("muc-occupant-left", {room = self; nick = orig_occupant.nick;}); + end + end + + if dest_occupant ~= nil then + dest_occupant:set_session(real_jid, stanza); + local dest_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + if is_new_room then + dest_x:tag("status", {code = "201"}):up(); + end + if orig_occupant == nil and self:get_whois() == "anyone" then + dest_x:tag("status", {code = "100"}):up(); + end + self:save_occupant(dest_occupant); + self:publicise_occupant_status(dest_occupant, dest_x); + + if orig_occupant ~= nil and orig_occupant ~= dest_occupant and not is_last_orig_session then -- If user is swapping and wasn't last original session + log("debug", "session %s split nicks; showing %s rejoining", real_jid, orig_occupant.nick); + -- Show the original nick joining again + local pr = st.clone(orig_occupant:get_presence()); + pr.attr.to = real_jid; + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + self:build_item_list(orig_occupant, x, false); + -- TODO: new status code to inform client this was the multi-session it left? + pr:add_child(x); + self:route_stanza(pr); + end + + if orig_occupant == nil and is_first_dest_session then + module:fire_event("muc-occupant-joined", {room = self; nick = dest_occupant.nick; stanza = stanza;}); + end + end elseif type ~= 'result' then -- bad type if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error? @@ -659,14 +787,14 @@ function room_mt:handle_iq_to_occupant(origin, stanza) local type = stanza.attr.type; local id = stanza.attr.id; local current_nick = self:get_occupant_jid(from); - local o_data = self._occupants[to]; + local occupant = self:get_occupant_by_nick(to); if (type == "error" or type == "result") then do -- deconstruct_stanza_id - if not current_nick or not o_data then return nil; end + if not current_nick or not occupant then return nil; end local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); if not(from == from_jid or from == jid_bare(from_jid)) then return nil; end local session_jid - for to_jid in pairs(o_data.sessions) do + for to_jid in occupant:each_session() do if md5(to_jid) == to_jid_hash then session_jid = to_jid; break; @@ -676,7 +804,7 @@ function room_mt:handle_iq_to_occupant(origin, stanza) stanza.attr.from, stanza.attr.to, stanza.attr.id = current_nick, session_jid, id end log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); - self:_route_stanza(stanza); + self:route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; else -- Type is "get" or "set" @@ -684,19 +812,19 @@ function room_mt:handle_iq_to_occupant(origin, stanza) origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); return true; end - if not o_data then -- recipient not in room + if not occupant then -- recipient not in room origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); return true; end do -- construct_stanza_id - stanza.attr.id = base64.encode(o_data.jid.."\0"..stanza.attr.id.."\0"..md5(from)); + stanza.attr.id = base64.encode(occupant.jid.."\0"..stanza.attr.id.."\0"..md5(from)); end - stanza.attr.from, stanza.attr.to = current_nick, o_data.jid; - log("debug", "%s sent private iq stanza to %s (%s)", from, to, o_data.jid); + stanza.attr.from, stanza.attr.to = current_nick, occupant.jid; + log("debug", "%s sent private iq stanza to %s (%s)", from, to, occupant.jid); if stanza.tags[1].attr.xmlns == 'vcard-temp' then stanza.attr.to = jid_bare(stanza.attr.to); end - self:_route_stanza(stanza); + self:route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; end @@ -720,7 +848,7 @@ function room_mt:handle_message_to_occupant(origin, stanza) return self:handle_kickable(origin, stanza); -- send unavailable end - local o_data = self._occupants[to]; + local o_data = self:get_occupant_by_nick(to); if not o_data then origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room")); return true; @@ -870,23 +998,29 @@ function room_mt:process_form(origin, stanza) end end -function room_mt:destroy(newjid, reason, password) - local pr = st.presence({type = "unavailable"}) - :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("item", { affiliation='none', role='none' }):up() - :tag("destroy", {jid=newjid}) - if reason then pr:tag("reason"):text(reason):up(); end - if password then pr:tag("password"):text(password):up(); end - for nick, occupant in pairs(self._occupants) do - pr.attr.from = nick; - for jid in pairs(occupant.sessions) do - pr.attr.to = jid; - self:_route_stanza(pr); - self._jid_nick[jid] = nil; - end - self._occupants[nick] = nil; - module:fire_event("muc-occupant-left", { room = self; nick = nick; }); +-- Removes everyone from the room +function room_mt:clear(x) + x = x or st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); + local occupants_updated = {}; + for nick, occupant in self:each_occupant() do + occupant.role = nil; + self:save_occupant(occupant); + occupants_updated[occupant] = true; end + for occupant in pairs(occupants_updated) do + self:publicise_occupant_status(occupant, x); + module:fire_event("muc-occupant-left", { room = self; nick = occupant.nick; }); + end +end + +function room_mt:destroy(newjid, reason, password) + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("item", { affiliation='none', role='none' }):up() + :tag("destroy", {jid=newjid}); + if reason then x:tag("reason"):text(reason):up(); end + if password then x:tag("password"):text(password):up(); end + x:up(); + self:clear(x); self:set_persistent(false); module:fire_event("muc-room-destroyed", { room = self }); end @@ -911,7 +1045,7 @@ function room_mt:handle_admin_query_set_command(origin, stanza) end end if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation - local occupant = self._occupants[self.jid.."/"..item.attr.nick]; + local occupant = self:get_occupant_by_nick(self.jid.."/"..item.attr.nick); if occupant then item.attr.jid = occupant.jid; end elseif not item.attr.nick and item.attr.jid then local nick = self:get_occupant_jid(item.attr.jid); @@ -958,18 +1092,7 @@ function room_mt:handle_admin_query_get_command(origin, stanza) local role = self:get_role(self:get_occupant_jid(actor)) or self:get_default_role(affiliation); if role == "moderator" then if _rol == "none" then _rol = nil; end - local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); - for occupant_jid, occupant in pairs(self._occupants) do - if occupant.role == _rol then - reply:tag("item", { - nick = select(3, jid_split(occupant_jid)), - role = _rol or "none", - affiliation = occupant.affiliation or "none", - jid = occupant.jid - }):up(); - end - end - origin.send(reply); + self:send_occupant_list(actor, function(occupant_jid, occupant) return occupant.role == _rol end); return true; else origin.send(st.error_reply(stanza, "auth", "forbidden")); @@ -1015,8 +1138,7 @@ end function room_mt:handle_groupchat_to_room(origin, stanza) local from = stanza.attr.from; - local current_nick = self:get_occupant_jid(from); - local occupant = self._occupants[current_nick]; + local occupant = self:get_occupant_by_real_jid(from); if not occupant then -- not in room origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); return true; @@ -1025,12 +1147,12 @@ function room_mt:handle_groupchat_to_room(origin, stanza) return true; else local from = stanza.attr.from; - stanza.attr.from = current_nick; + stanza.attr.from = occupant.nick; local subject = stanza:get_child_text("subject"); if subject then if occupant.role == "moderator" or ( self:get_changesubject() and occupant.role == "participant" ) then -- and participant - self:set_subject(current_nick, subject); + self:set_subject(occupant.nick, subject); else stanza.attr.from = from; origin.send(st.error_reply(stanza, "auth", "forbidden")); @@ -1096,7 +1218,7 @@ function room_mt:handle_mediated_invite(origin, stanza) end module:hook("muc-invite", function(event) - event.room:_route_stanza(event.stanza); + event.room:route_stanza(event.stanza); return true; end, -1) @@ -1210,64 +1332,46 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) end self._affiliations[jid] = affiliation; local role = self:get_default_role(affiliation); - local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("item", {affiliation=affiliation or "none", role=role or "none"}) - :tag("reason"):text(reason or ""):up() - :up(); - local presence_type = nil; + local occupants_updated = {}; + for nick, occupant in self:each_occupant() do + if occupant.bare_jid == jid then + occupant.role = role; + self:save_occupant(occupant); + occupants_updated[occupant] = true; + end + end + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); if not role then -- getting kicked - presence_type = "unavailable"; if affiliation == "outcast" then x:tag("status", {code="301"}):up(); -- banned else x:tag("status", {code="321"}):up(); -- affiliation change end end - local modified_nicks = {}; - for nick, occupant in pairs(self._occupants) do - if jid_bare(occupant.jid) == jid then - if not role then -- getting kicked - self._occupants[nick] = nil; - else - occupant.affiliation, occupant.role = affiliation, role; - end - for jid,pres in pairs(occupant.sessions) do -- remove for all sessions of the nick - if not role then self._jid_nick[jid] = nil; end - local p = st.clone(pres); - p.attr.from = nick; - p.attr.type = presence_type; - p.attr.to = jid; - p:add_child(x); - self:_route_stanza(p); - if occupant.jid == jid then - modified_nicks[nick] = p; - end - end - end + for occupant in pairs(occupants_updated) do + self:publicise_occupant_status(occupant, x, actor, reason); end if self.save then self:save(); end if callback then callback(); end - for nick,p in pairs(modified_nicks) do - p.attr.from = nick; - self:broadcast_except_nick(p, nick); - end return true; end function room_mt:get_role(nick) - local session = self._occupants[nick]; - return session and session.role or nil; + local occupant = self:get_occupant_by_nick(nick); + return occupant and occupant.role or nil; end function room_mt:can_set_role(actor_jid, occupant_jid, role) - local occupant = self._occupants[occupant_jid]; + local occupant = self:get_occupant_by_nick(occupant_jid); if not occupant or not actor_jid then return nil, "modify", "not-acceptable"; end if actor_jid == true then return true; end - local actor = self._occupants[self:get_occupant_jid(actor_jid)]; + local actor = self:get_occupant_by_real_jid(actor_jid); if actor.role == "moderator" then - if occupant.affiliation ~= "owner" and occupant.affiliation ~= "admin" then - if actor.affiliation == "owner" or actor.affiliation == "admin" then + local occupant_affiliation = self:get_affiliation(occupant.bare_jid) + local actor_affiliation = self:get_affiliation(actor.bare_jid) + if occupant_affiliation ~= "owner" and occupant_affiliation ~= "admin" then + if actor_affiliation == "owner" or actor_affiliation == "admin" then return true; elseif occupant.role ~= "moderator" and role ~= "moderator" then return true; @@ -1281,73 +1385,20 @@ function room_mt:set_role(actor, occupant_jid, role, callback, reason) if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role); if not allowed then return allowed, err_type, err_condition; end - local occupant = self._occupants[occupant_jid]; - local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"}) - :tag("reason"):text(reason or ""):up() - :up(); - local presence_type = nil; - if not role then -- kick - presence_type = "unavailable"; - self._occupants[occupant_jid] = nil; - for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick - self._jid_nick[jid] = nil; - end + + local occupant = self:get_occupant_by_nick(occupant_jid); + local occupant_affiliation = self:get_affiliation(occupant.bare_jid); + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); + if not role then x:tag("status", {code = "307"}):up(); - else - occupant.role = role; - end - local bp; - for jid,pres in pairs(occupant.sessions) do -- send to all sessions of the nick - local p = st.clone(pres); - p.attr.from = occupant_jid; - p.attr.type = presence_type; - p.attr.to = jid; - p:add_child(x); - self:_route_stanza(p); - if occupant.jid == jid then - bp = p; - end end + occupant.role = role; + self:save_occupant(occupant); + self:publicise_occupant_status(occupant, x, actor, reason); if callback then callback(); end - if bp then - self:broadcast_except_nick(bp, occupant_jid); - end return true; end -function room_mt:_route_stanza(stanza) - local muc_child; - if stanza.name == "presence" then - local to_occupant = self._occupants[self:get_occupant_jid(stanza.attr.to)]; - local from_occupant = self._occupants[stanza.attr.from]; - if to_occupant and from_occupant then - if self:get_whois() == 'anyone' then - muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); - else - if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then - muc_child = stanza:get_child("x", "http://jabber.org/protocol/muc#user"); - end - end - end - if muc_child then - for item in muc_child:childtags("item") do - if from_occupant == to_occupant then - item.attr.jid = stanza.attr.to; - else - item.attr.jid = from_occupant.jid; - end - end - end - end - self:route_stanza(stanza); - if muc_child then - for item in muc_child:childtags("item") do - item.attr.jid = nil; - end - end -end - local _M = {}; -- module "muc" function _M.new_room(jid, config) diff --git a/plugins/muc/occupant.lib.lua b/plugins/muc/occupant.lib.lua new file mode 100644 index 00000000..b4b12390 --- /dev/null +++ b/plugins/muc/occupant.lib.lua @@ -0,0 +1,85 @@ +local next = next; +local pairs = pairs; +local setmetatable = setmetatable; +local st = require "util.stanza"; + +local get_filtered_presence do + local presence_filters = { + ["http://jabber.org/protocol/muc"] = true; + ["http://jabber.org/protocol/muc#user"] = true; + } + local function presence_filter(tag) + if presence_filters[tag.attr.xmlns] then + return nil; + end + return tag; + end + function get_filtered_presence(stanza) + return st.clone(stanza):maptags(presence_filter); + end +end + +local occupant_mt = {}; +occupant_mt.__index = occupant_mt; + +local function new_occupant(bare_real_jid, nick) + return setmetatable({ + bare_jid = bare_real_jid; + nick = nick; -- in-room jid + sessions = {}; -- hash from real_jid to presence stanzas. stanzas should not be modified + role = nil; + jid = nil; -- Primary session + }, occupant_mt); +end + +-- Deep copy an occupant +local function copy_occupant(occupant) + local sessions = {}; + for full_jid, presence_stanza in pairs(occupant.sessions) do + if presence_stanza.attr.type ~= "unavailable" then + sessions[full_jid] = presence_stanza; + end + end + return setmetatable({ + bare_jid = occupant.bare_jid; + nick = occupant.nick; + sessions = sessions; + role = occupant.role; + jid = occupant.jid; + }, occupant_mt); +end + +function occupant_mt:set_session(real_jid, presence_stanza, replace_primary) + local pr = get_filtered_presence(presence_stanza); + pr.attr.from = self.nick; + pr.attr.to = real_jid; + + self.sessions[real_jid] = pr; + if replace_primary or self.jid == nil then + self.jid = real_jid; + end +end + +function occupant_mt:remove_session(real_jid) + -- Delete original session + local presence_stanza = self.sessions[real_jid]; + self.sessions[real_jid] = nil; + if self.jid == real_jid then + -- find another session to be the primary (might be nil) + self.jid = next(self.sessions); + end +end + +function occupant_mt:each_session() + return pairs(self.sessions) +end + +function occupant_mt:get_presence(real_jid) + return self.sessions[real_jid or self.jid] +end + +return { + new = new_occupant; + copy = copy_occupant; + mt = occupant_mt; +} -- cgit v1.2.3 From f7642cdf7a2f8c4462b476fbb43eced7fcd52a28 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 19:16:46 -0400 Subject: plugins/muc/muc.lib: If decline is to person in room; route to all sessions --- plugins/muc/muc.lib.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 20cba225..27c50cd4 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1137,7 +1137,13 @@ function room_mt:handle_mediated_decline(origin, stanza) end module:hook("muc-decline", function(event) - event.room:_route_stanza(event.stanza); + local room, stanza = event.room, event.stanza + local occupant = room:get_occupant_by_real_jid(stanza.attr.to); + if occupant then + room:route_to_occupant(occupant, stanza) + else + room:route_stanza(stanza); + end return true; end, -1) -- cgit v1.2.3 From c7e768d9ac1447754a81b64633d14fd0190551e4 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 27 Mar 2014 19:18:57 -0400 Subject: plugins/muc/muc.lib: non-function changes (reordering, semicolons and comments) --- plugins/muc/muc.lib.lua | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 76fd7ee2..d96658f3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -9,6 +9,9 @@ local select = select; local pairs, ipairs = pairs, ipairs; +local next = next; +local setmetatable = setmetatable; +local t_insert, t_remove = table.insert, table.remove; local gettime = os.time; local datetime = require "util.datetime"; @@ -20,8 +23,6 @@ local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; local st = require "util.stanza"; local log = require "util.logger".init("mod_muc"); -local t_insert, t_remove = table.insert, table.remove; -local setmetatable = setmetatable; local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; @@ -355,7 +356,7 @@ module:hook("muc-get-history", function(event) return msg end return true; -end) +end); function room_mt:send_history(stanza) local maxchars, maxstanzas, since = parse_history(stanza) @@ -584,7 +585,7 @@ module:hook("muc-occupant-pre-join/password", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1) +end, -1); module:hook("muc-occupant-pre-join/locked", function(event) if event.room:is_locked() then -- Deny entry @@ -603,9 +604,9 @@ module:hook("muc-occupant-pre-join/affiliation", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1) +end, -1); --- banned +-- check if user is banned module:hook("muc-occupant-pre-join/affiliation", function(event) local room, stanza = event.room, event.stanza; local affiliation = room:get_affiliation(stanza.attr.from); @@ -615,7 +616,7 @@ module:hook("muc-occupant-pre-join/affiliation", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1) +end, -1); module:hook("muc-occupant-joined", function(event) local room, stanza = event.room, event.stanza; @@ -857,6 +858,7 @@ function room_mt:handle_message_to_occupant(origin, stanza) stanza:tag("x", { xmlns = "http://jabber.org/protocol/muc#user" }):up(); stanza.attr.from = current_nick; self:route_to_occupant(o_data, stanza) + -- TODO: Remove x tag? stanza.attr.from = from; return true; end @@ -1189,16 +1191,16 @@ function room_mt:handle_mediated_invite(origin, stanza) end local _invitee = jid_prep(payload.attr.to); if _invitee then + local _reason = payload:get_child_text("reason"); if self:get_whois() == "moderators" then _from = current_nick; end - local _reason = payload:get_child_text("reason") local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) :tag('invite', {from=_from}) :tag('reason'):text(_reason or ""):up() :up(); - local password = self:get_password() + local password = self:get_password(); if password then invite:tag("password"):text(password):up(); end @@ -1209,7 +1211,7 @@ function room_mt:handle_mediated_invite(origin, stanza) :tag('body') -- Add a plain message for clients which don't support invites :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) :up(); - module:fire_event("muc-invite", { room = self, stanza = invite, origin = origin, incoming = stanza }); + module:fire_event("muc-invite", {room = self, stanza = invite, origin = origin, incoming = stanza}); return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); @@ -1232,7 +1234,7 @@ module:hook("muc-invite", function(event) log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); room:set_affiliation(from, invitee, "member", nil, "Invited by " .. current_nick) end -end) +end); function room_mt:handle_mediated_decline(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline") @@ -1289,13 +1291,11 @@ function room_mt:handle_message_to_room(origin, stanza) origin.send(st.error_reply(stanza, "cancel", "bad-request")); return true; end - else - return nil; end end function room_mt:route_stanza(stanza) - module:send(stanza) + module:send(stanza); end function room_mt:get_affiliation(jid) -- cgit v1.2.3 From b0999be824b03b48299a20bef4a62450dd3f3ae0 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 11:05:52 -0400 Subject: plugins/muc/muc.lib: Remove callback parameter from set_role and set_affiliation --- plugins/muc/muc.lib.lua | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d96658f3..79252778 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1054,20 +1054,18 @@ function room_mt:handle_admin_query_set_command(origin, stanza) if nick then item.attr.nick = select(3, jid_split(nick)); end end local actor = stanza.attr.from; - local callback = function() origin.send(st.reply(stanza)); end local reason = item:get_child_text("reason"); + local success, errtype, err if item.attr.affiliation and item.attr.jid and not item.attr.role then - local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - return true; + success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, reason); elseif item.attr.role and item.attr.nick and not item.attr.affiliation then - local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason); - if not success then origin.send(st.error_reply(stanza, errtype, err)); end - return true; + success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, reason); else - origin.send(st.error_reply(stanza, "cancel", "bad-request")); - return true; + success, errtype, err = nil, "cancel", "bad-request"; end + if not success then origin.send(st.error_reply(stanza, errtype, err)); end + origin.send(st.reply(stanza)); + return true; end function room_mt:handle_admin_query_get_command(origin, stanza) @@ -1232,7 +1230,7 @@ module:hook("muc-invite", function(event) local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from local current_nick = room:get_occupant_jid(from) log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); - room:set_affiliation(from, invitee, "member", nil, "Invited by " .. current_nick) + room:set_affiliation(from, invitee, "member", "Invited by " .. current_nick) end end); @@ -1305,7 +1303,7 @@ function room_mt:get_affiliation(jid) if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned return result; end -function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) +function room_mt:set_affiliation(actor, jid, affiliation, reason) jid = jid_bare(jid); if affiliation == "none" then affiliation = nil; end if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then @@ -1315,7 +1313,6 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) local actor_affiliation = self:get_affiliation(actor); local target_affiliation = self:get_affiliation(jid); if target_affiliation == affiliation then -- no change, shortcut - if callback then callback(); end return true; end if actor_affiliation ~= "owner" then @@ -1352,7 +1349,6 @@ function room_mt:set_affiliation(actor, jid, affiliation, callback, reason) self:publicise_occupant_status(occupant, x, actor, reason); end if self.save then self:save(); end - if callback then callback(); end return true; end @@ -1380,7 +1376,7 @@ function room_mt:can_set_role(actor_jid, occupant_jid, role) end return nil, "cancel", "not-allowed"; end -function room_mt:set_role(actor, occupant_jid, role, callback, reason) +function room_mt:set_role(actor, occupant_jid, role, reason) if role == "none" then role = nil; end if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role); @@ -1395,7 +1391,6 @@ function room_mt:set_role(actor, occupant_jid, role, callback, reason) occupant.role = role; self:save_occupant(occupant); self:publicise_occupant_status(occupant, x, actor, reason); - if callback then callback(); end return true; end -- cgit v1.2.3 From 6334074f11baac6bd8101e205f038e8494553bfe Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 13:05:36 -0400 Subject: plugins/muc/muc.lib: Clean up :set_role. Removes :can_set_role --- plugins/muc/muc.lib.lua | 58 +++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 79252778..b3c1c174 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1356,34 +1356,46 @@ function room_mt:get_role(nick) local occupant = self:get_occupant_by_nick(nick); return occupant and occupant.role or nil; end -function room_mt:can_set_role(actor_jid, occupant_jid, role) + +local valid_roles = { + none = true; + visitor = true; + participant = true; + moderator = true; +} +function room_mt:set_role(actor, occupant_jid, role, reason) + if not actor then return nil, "modify", "not-acceptable"; end + local occupant = self:get_occupant_by_nick(occupant_jid); - if not occupant or not actor_jid then return nil, "modify", "not-acceptable"; end - - if actor_jid == true then return true; end - - local actor = self:get_occupant_by_real_jid(actor_jid); - if actor.role == "moderator" then - local occupant_affiliation = self:get_affiliation(occupant.bare_jid) - local actor_affiliation = self:get_affiliation(actor.bare_jid) - if occupant_affiliation ~= "owner" and occupant_affiliation ~= "admin" then - if actor_affiliation == "owner" or actor_affiliation == "admin" then - return true; - elseif occupant.role ~= "moderator" and role ~= "moderator" then - return true; + if not occupant then return nil, "modify", "not-acceptable"; end + + if valid_roles[role or "none"] == nil then + return nil, "modify", "not-acceptable"; + end + role = role ~= "none" and role or nil; -- coerces `role == false` to `nil` + + if actor ~= true then + -- Can't do anything to other owners or admins + local occupant_affiliation = self:get_affiliation(occupant.bare_jid); + if occupant_affiliation == "owner" and occupant_affiliation == "admin" then + return nil, "cancel", "not-allowed"; + end + + -- If you are trying to give or take moderator role you need to be an owner or admin + if occupant.role == "moderator" or role == "moderator" then + local actor_affiliation = self:get_affiliation(actor); + if actor_affiliation ~= "owner" and actor_affiliation ~= "admin" then + return nil, "cancel", "not-allowed"; end end + + -- Need to be in the room and a moderator + local actor_occupant = self:get_occupant_by_real_jid(actor); + if not actor_occupant or actor_occupant.role ~= "moderator" then + return nil, "cancel", "not-allowed"; + end end - return nil, "cancel", "not-allowed"; -end -function room_mt:set_role(actor, occupant_jid, role, reason) - if role == "none" then role = nil; end - if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end - local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role); - if not allowed then return allowed, err_type, err_condition; end - local occupant = self:get_occupant_by_nick(occupant_jid); - local occupant_affiliation = self:get_affiliation(occupant.bare_jid); local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); if not role then x:tag("status", {code = "307"}):up(); -- cgit v1.2.3 From 46f318fa4f74a2194d98e8b082c97f7a62296077 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 13:11:11 -0400 Subject: plugins/muc/muc.lib: Fix sending occupant jid instead of real jid in actor --- plugins/muc/muc.lib.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index b3c1c174..0d1e4170 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -163,18 +163,18 @@ end function room_mt:build_item_list(occupant, x, is_anonymous, nick, actor, reason) local affiliation = self:get_affiliation(occupant.bare_jid); local role = occupant.role; - local actor_jid = actor and self:get_occupant_jid(actor); + local actor_attr; if actor then - actor = {nick = select(3,jid_split(actor_jid))}; + actor_attr = {nick = select(3,jid_split(self:get_occupant_jid(actor)))}; end if is_anonymous then - add_item(x, affiliation, role, nil, nick, actor, reason); + add_item(x, affiliation, role, nil, nick, actor_attr, reason); else - if actor_jid then - actor.jid = actor_jid; + if actor_attr then + actor_attr.jid = actor; end for real_jid, session in occupant:each_session() do - add_item(x, affiliation, role, real_jid, nick, actor, reason); + add_item(x, affiliation, role, real_jid, nick, actor_attr, reason); end end return x -- cgit v1.2.3 From f9f2d557fa2db46dbdbc510c4a326be26b237e52 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 13:14:33 -0400 Subject: plugins/muc/muc.lib: Allow `:send_occupant_list` to have no filter --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 0d1e4170..b5b8bc97 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -276,7 +276,7 @@ function room_mt:send_occupant_list(to, filter) local to_occupant = self:get_occupant_by_real_jid(to); local has_anonymous = self:get_whois() ~= "anyone" for occupant_jid, occupant in self:each_occupant() do - if filter and filter(occupant_jid, occupant) then + if filter == nil or filter(occupant_jid, occupant) then local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); local is_anonymous = has_anonymous and occupant.role ~= "moderator" and to_occupant.bare_jid ~= occupant.bare_jid; self:build_item_list(occupant, x, is_anonymous); -- cgit v1.2.3 From 97759d2f65c1578f8a0cb030fb4ea83ff8d45682 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 13:34:46 -0400 Subject: plugins/muc/muc.lib: Fix anonymous check in `send_occupant_list` --- plugins/muc/muc.lib.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index b5b8bc97..02a2dc37 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -273,13 +273,21 @@ function room_mt:publicise_occupant_status(occupant, full_x, actor, reason) end function room_mt:send_occupant_list(to, filter) - local to_occupant = self:get_occupant_by_real_jid(to); - local has_anonymous = self:get_whois() ~= "anyone" + local to_bare = jid_bare(to); + local is_anonymous = true; + if self:get_whois() ~= "anyone" then + local affiliation = self:get_affiliation(to); + if affiliation ~= "admin" and affiliation ~= "owner" then + local occupant = self:get_occupant_by_real_jid(to); + if not occupant or occupant.role ~= "moderator" then + is_anonymous = false; + end + end + end for occupant_jid, occupant in self:each_occupant() do if filter == nil or filter(occupant_jid, occupant) then local x = st.stanza("x", {xmlns='http://jabber.org/protocol/muc#user'}); - local is_anonymous = has_anonymous and occupant.role ~= "moderator" and to_occupant.bare_jid ~= occupant.bare_jid; - self:build_item_list(occupant, x, is_anonymous); + self:build_item_list(occupant, x, is_anonymous and to_bare ~= occupant.bare_jid); -- can always see your own jids local pres = st.clone(occupant:get_presence()); pres.attr.to = to; pres:add_child(x); -- cgit v1.2.3 From 09dc06b54e9f277d0fc9a0a92c68f20e7a839a75 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 14:15:18 -0400 Subject: plugins/muc/muc.lib: Smarter validation in set_affiliation --- plugins/muc/muc.lib.lua | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 02a2dc37..d001800d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1311,12 +1311,24 @@ function room_mt:get_affiliation(jid) if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned return result; end + +local valid_affiliations = { + outcast = true; + none = true; + member = true; + admin = true; + owner = true; +}; function room_mt:set_affiliation(actor, jid, affiliation, reason) + if not actor then return nil, "modify", "not-acceptable"; end; + jid = jid_bare(jid); - if affiliation == "none" then affiliation = nil; end - if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then + + if valid_affiliations[affiliation or "none"] == nil then return nil, "modify", "not-acceptable"; end + affiliation = affiliation ~= "none" and affiliation or nil; -- coerces `affiliation == false` to `nil` + if actor ~= true then local actor_affiliation = self:get_affiliation(actor); local target_affiliation = self:get_affiliation(jid); -- cgit v1.2.3 From f090e4eddaf71cbe01a3e6810961a8492a70549d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 17:58:25 -0400 Subject: plugins/muc/muc.lib: Improve set affiliation logic; - Each affiliation and role is now ranked - Changes up in affiliation will not downgrade your role - Now sends a new set of presences if you gained moderator in a semi-anonymous room. - Better input validation; matches closer with ':set_role' - Don't short circuit; as if user has non-default role they will not get updated --- plugins/muc/muc.lib.lua | 96 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d001800d..9ae3f56a 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -60,6 +60,21 @@ function room_mt:get_occupant_jid(real_jid) return self._jid_nick[real_jid] end +local valid_affiliations = { + outcast = 0; + none = 1; + member = 2; + admin = 3; + owner = 4; +}; + +local valid_roles = { + none = 0; + visitor = 1; + participant = 2; + moderator = 3; +}; + function room_mt:get_default_role(affiliation) if affiliation == "owner" or affiliation == "admin" then return "moderator"; @@ -1312,13 +1327,6 @@ function room_mt:get_affiliation(jid) return result; end -local valid_affiliations = { - outcast = true; - none = true; - member = true; - admin = true; - owner = true; -}; function room_mt:set_affiliation(actor, jid, affiliation, reason) if not actor then return nil, "modify", "not-acceptable"; end; @@ -1329,34 +1337,52 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason) end affiliation = affiliation ~= "none" and affiliation or nil; -- coerces `affiliation == false` to `nil` + local target_affiliation = self._affiliations[jid]; -- Raw; don't want to check against host + local is_downgrade = valid_affiliations[target_affiliation or "none"] > valid_affiliations[affiliation or "none"]; + if actor ~= true then - local actor_affiliation = self:get_affiliation(actor); - local target_affiliation = self:get_affiliation(jid); - if target_affiliation == affiliation then -- no change, shortcut - return true; - end - if actor_affiliation ~= "owner" then - if affiliation == "owner" or affiliation == "admin" or actor_affiliation ~= "admin" or target_affiliation == "owner" or target_affiliation == "admin" then - return nil, "cancel", "not-allowed"; - end - elseif target_affiliation == "owner" and jid_bare(actor) == jid then -- self change - local is_last = true; - for j, aff in pairs(self._affiliations) do if j ~= jid and aff == "owner" then is_last = false; break; end end - if is_last then - return nil, "cancel", "conflict"; + local actor_bare = jid_bare(actor); + local actor_affiliation = self._affiliations[actor_bare]; + if actor_affiliation == "owner" then + if actor_bare == jid then -- self change + -- need at least one owner + local is_last = true; + for j, aff in pairs(self._affiliations) do if j ~= jid and aff == "owner" then is_last = false; break; end end + if is_last then + return nil, "cancel", "conflict"; + end end + -- owners can do anything else + elseif affiliation == "owner" or affiliation == "admin" + or actor_affiliation ~= "admin" + or target_affiliation == "owner" or target_affiliation == "admin" then + -- Can't demote owners or other admins + return nil, "cancel", "not-allowed"; end end + + -- Set in 'database' self._affiliations[jid] = affiliation; + + -- Update roles local role = self:get_default_role(affiliation); - local occupants_updated = {}; + local role_rank = valid_roles[role or "none"]; + local occupants_updated = {}; -- Filled with old roles for nick, occupant in self:each_occupant() do if occupant.bare_jid == jid then - occupant.role = role; - self:save_occupant(occupant); - occupants_updated[occupant] = true; + -- need to publcize in all cases; as affiliation in has changed. + occupants_updated[occupant] = occupant.role; + if occupant.role ~= role and ( + is_downgrade or + valid_roles[occupant.role or "none"] < role_rank -- upgrade + ) then + occupant.role = role; + self:save_occupant(occupant); + end end end + + -- Tell the room of the new occupant affiliations+roles local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}); if not role then -- getting kicked if affiliation == "outcast" then @@ -1365,9 +1391,21 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason) x:tag("status", {code="321"}):up(); -- affiliation change end end - for occupant in pairs(occupants_updated) do + local is_semi_anonymous = self:get_whois() == "moderators"; + for occupant, old_role in pairs(occupants_updated) do self:publicise_occupant_status(occupant, x, actor, reason); + if is_semi_anonymous and + (old_role == "moderator" and occupant.role ~= "moderator") or + (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status + -- Send everyone else's presences (as jid visibility has changed) + for real_jid in occupant:each_session() do + self:send_occupant_list(real_jid, function(occupant_jid, occupant) + return occupant.bare_jid ~= jid; + end); + end + end end + if self.save then self:save(); end return true; end @@ -1377,12 +1415,6 @@ function room_mt:get_role(nick) return occupant and occupant.role or nil; end -local valid_roles = { - none = true; - visitor = true; - participant = true; - moderator = true; -} function room_mt:set_role(actor, occupant_jid, role, reason) if not actor then return nil, "modify", "not-acceptable"; end -- cgit v1.2.3 From ac04e20d3bcd37432c086d4b91222914c7c4ed59 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 18:33:38 -0400 Subject: plugins/muc/muc.lib: Status codes should be inside of x element --- plugins/muc/muc.lib.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9ae3f56a..08ca0384 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1013,8 +1013,9 @@ function room_mt:process_form(origin, stanza) if next(changed) then local msg = st.message({type='groupchat', from=self.jid}) - :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}):up() - :tag('status', {code = '104'}):up(); + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('status', {code = '104'}):up() + :up(); if changed.whois then local code = (self:get_whois() == 'moderators') and "173" or "172"; msg.tags[1]:tag('status', {code = code}):up(); -- cgit v1.2.3 From 5d2b35e070ebe5d10aa07c5a5f8448aa17dfef44 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 18:33:57 -0400 Subject: plugins/muc/muc.lib: nick change unavailables should be from original occupant jid --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 08ca0384..6453cc3b 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -760,7 +760,7 @@ function room_mt:handle_presence_to_occupant(origin, stanza) -- self:build_item_list(orig_occupant, x, false); -- COMPAT x:tag("status", {code = "303";}):up(); x:tag("status", {code = "110";}):up(); - self:route_stanza(st.presence{from = dest_occupant.nick, to = real_jid, type = "unavailable"}:add_child(x)); + self:route_stanza(st.presence{from = orig_occupant.nick, to = real_jid, type = "unavailable"}:add_child(x)); end self:save_occupant(orig_occupant); self:publicise_occupant_status(orig_occupant, orig_x); -- cgit v1.2.3 From 7cbdc2b0f7570ca06ff5e8561b09cb5bc64c8a1f Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 18:47:35 -0400 Subject: plugins/muc/muc.lib: Fix typo (moderators vs moderator) --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6453cc3b..c1747440 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -261,7 +261,7 @@ function room_mt:publicise_occupant_status(occupant, full_x, actor, reason) for nick, n_occupant in self:each_occupant() do if nick ~= occupant.nick or n_occupant.role == nil then local pr = full_p; - if has_anonymous and n_occupant.role ~= "moderators" and occupant.bare_jid ~= n_occupant.bare_jid then + if has_anonymous and n_occupant.role ~= "moderator" and occupant.bare_jid ~= n_occupant.bare_jid then pr = anon_p; end self:route_to_occupant(n_occupant, pr); -- cgit v1.2.3 From 672d0fae0b9b785bc2a1def3143b4978fd379201 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 20:25:10 -0400 Subject: plugins/muc/muc.lib: Subjects get sent even if empty. --- plugins/muc/muc.lib.lua | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index c1747440..7704a099 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -424,26 +424,21 @@ end function room_mt:get_subject() return self._data['subject'], self._data['subject_from'] end -local function create_subject_message(subject) - return st.message({type='groupchat'}) +local function create_subject_message(from, subject) + return st.message({from = from; type = "groupchat"}) :tag('subject'):text(subject):up(); end function room_mt:send_subject(to) - local from, subject = self:get_subject() - if subject then - local msg = create_subject_message(subject) - msg.attr.from = from - msg.attr.to = to - self:route_stanza(msg); - end + local msg = create_subject_message(self:get_subject()); + msg.attr.to = to; + self:route_stanza(msg); end function room_mt:set_subject(current_nick, subject) if subject == "" then subject = nil; end self._data['subject'] = subject; self._data['subject_from'] = current_nick; if self.save then self:save(); end - local msg = create_subject_message(subject) - msg.attr.from = current_nick + local msg = create_subject_message(current_nick, subject); self:broadcast_message(msg, false); return true; end -- cgit v1.2.3 From d0e5de9af2239bd57d91a7c30e0a4d335b668a7b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 28 Mar 2014 20:28:20 -0400 Subject: plugins/muc/muc.lib: Use occupant methods where possible --- plugins/muc/muc.lib.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7704a099..684539bd 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -641,7 +641,7 @@ module:hook("muc-occupant-joined", function(event) local real_jid = stanza.attr.from; room:send_occupant_list(real_jid, function(nick, occupant) -- Don't include self - return occupant.sessions[real_jid] == nil + return occupant:get_presence(real_jid) == nil; end); room:send_history(stanza); room:send_subject(real_jid); @@ -687,7 +687,8 @@ function room_mt:handle_presence_to_occupant(origin, stanza) local is_last_orig_session; if orig_occupant ~= nil then -- Is there are least 2 sessions? - is_last_orig_session = next(orig_occupant.sessions, next(orig_occupant.sessions)) == nil; + local iter, ob, last = orig_occupant:each_session(); + is_last_orig_session = iter(ob, iter(ob, last)) == nil; end local event, event_name = { -- cgit v1.2.3 From eb0739676fb5a91c11583764f1198999893f94ba Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 12:21:43 -0400 Subject: plugins/muc/muc.lib: Remove top level pre-join event. Assign event priorities for other handlers --- plugins/muc/muc.lib.lua | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 684539bd..9f7ba8d3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -585,12 +585,6 @@ module:hook("muc-room-pre-create", function(event) end, -1); module:hook("muc-occupant-pre-join", function(event) - return module:fire_event("muc-occupant-pre-join/affiliation", event) - or module:fire_event("muc-occupant-pre-join/password", event) - or module:fire_event("muc-occupant-pre-join/locked", event); -end, -1) - -module:hook("muc-occupant-pre-join/password", function(event) local room, stanza = event.room, event.stanza; local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); password = password and password:get_child_text("password", "http://jabber.org/protocol/muc"); @@ -603,17 +597,17 @@ module:hook("muc-occupant-pre-join/password", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1); +end, -20); -module:hook("muc-occupant-pre-join/locked", function(event) +module:hook("muc-occupant-pre-join", function(event) if event.room:is_locked() then -- Deny entry event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); return true; end -end, -1); +end, -30); -- registration required for entering members-only room -module:hook("muc-occupant-pre-join/affiliation", function(event) +module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; local affiliation = room:get_affiliation(stanza.attr.from); if affiliation == nil and event.room:get_members_only() then @@ -622,10 +616,10 @@ module:hook("muc-occupant-pre-join/affiliation", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1); +end, -5); -- check if user is banned -module:hook("muc-occupant-pre-join/affiliation", function(event) +module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; local affiliation = room:get_affiliation(stanza.attr.from); if affiliation == "outcast" then @@ -634,7 +628,7 @@ module:hook("muc-occupant-pre-join/affiliation", function(event) event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); return true; end -end, -1); +end, -10); module:hook("muc-occupant-joined", function(event) local room, stanza = event.room, event.stanza; -- cgit v1.2.3 From 0cfac6e454973e4d7bb2f15e5f775adb43276ad4 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 12:31:15 -0400 Subject: plugins/muc/muc.lib: Tidy up muc-invite event. - Send inside of the actual handle_invite function - Move password, compat and body tagging into event handlers --- plugins/muc/muc.lib.lua | 64 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9f7ba8d3..93f8fc96 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1203,27 +1203,15 @@ function room_mt:handle_mediated_invite(origin, stanza) end local _invitee = jid_prep(payload.attr.to); if _invitee then - local _reason = payload:get_child_text("reason"); - if self:get_whois() == "moderators" then - _from = current_nick; - end local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) :tag('invite', {from=_from}) - :tag('reason'):text(_reason or ""):up() - :up(); - local password = self:get_password(); - if password then - invite:tag("password"):text(password):up(); - end - invite:up() - :tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this - :text(_reason or "") - :up() - :tag('body') -- Add a plain message for clients which don't support invites - :text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or "")) + :tag('reason'):text(payload:get_child_text("reason")):up() + :up() :up(); - module:fire_event("muc-invite", {room = self, stanza = invite, origin = origin, incoming = stanza}); + if not module:fire_event("muc-invite", {room = self, stanza = invite, origin = origin, incoming = stanza}) then + self:route_stanza(invite); + end return true; else origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); @@ -1231,10 +1219,46 @@ function room_mt:handle_mediated_invite(origin, stanza) end end +-- Add password to outgoing invite module:hook("muc-invite", function(event) - event.room:route_stanza(event.stanza); - return true; -end, -1) + local password = event.room:get_password(); + if password then + local x = event.stanza:get_child("x", "http://jabber.org/protocol/muc#user"); + x:tag("password"):text(password):up(); + end +end); + +-- COMPAT: Some older clients expect this +module:hook("muc-invite", function(event) + local room, stanza = event.room, event.stanza; + local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local reason = invite:get_child_text("reason"); + stanza:tag('x', {xmlns = "jabber:x:conference"; jid = room.jid;}) + :text(reason or "") + :up(); +end); + +-- Add a plain message for clients which don't support invites +module:hook("muc-invite", function(event) + local room, stanza = event.room, event.stanza; + local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local reason = invite:get_child_text("reason") or ""; + stanza:tag("body") + :text(invite.attr.from.." invited you to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) + :up(); +end); + +-- Mask 'from' jid as occupant jid if room is anonymous +module:hook("muc-invite", function(event) + local room, stanza = event.room, event.stanza; + if room:get_whois() == "moderators" and room:get_default_role(room:get_affiliation(stanza.attr.to)) ~= "moderator" then + local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local occupant_jid = room:get_occupant_jid(invite.attr.from); + if occupant_jid ~= nil then -- FIXME: This will expose real jid if inviter is not in room + invite.attr.from = occupant_jid; + end + end +end, 50); -- When an invite is sent; add an affiliation for the invitee module:hook("muc-invite", function(event) -- cgit v1.2.3 From b3f385662f6c306d09b4409d3eef61bc91f57f7b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 13:54:27 -0400 Subject: plugins/muc/muc.lib: Add pre-invite event. Move role check to it --- plugins/muc/muc.lib.lua | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 93f8fc96..a5118430 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1192,31 +1192,37 @@ function room_mt:handle_presence_to_room(origin, stanza) return handled; end -function room_mt:handle_mediated_invite(origin, stanza) - local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite") +-- Need visitor role or higher to invite +module:hook("muc-pre-invite", function(event) + local room, stanza = event.room, event.stanza; local _from, _to = stanza.attr.from, stanza.attr.to; - local current_nick = self:get_occupant_jid(_from) - -- Need visitor role or higher to invite - if not self:get_role(current_nick) or not self:get_default_role(self:get_affiliation(_from)) then - origin.send(st.error_reply(stanza, "auth", "forbidden")); + local inviter = room:get_occupant_by_real_jid(_from); + local role = inviter and inviter.role or room:get_default_role(room:get_affiliation(_from)); + if valid_roles[role or "none"] <= valid_roles.visitor then + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end - local _invitee = jid_prep(payload.attr.to); - if _invitee then - local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id}) - :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) - :tag('invite', {from=_from}) - :tag('reason'):text(payload:get_child_text("reason")):up() - :up() - :up(); - if not module:fire_event("muc-invite", {room = self, stanza = invite, origin = origin, incoming = stanza}) then - self:route_stanza(invite); - end - return true; - else +end); + +function room_mt:handle_mediated_invite(origin, stanza) + local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local invitee = jid_prep(payload.attr.to); + if not invitee then origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); return true; + elseif not module:fire_event("muc-pre-invite", {room = self, origin = origin, stanza = stanza}) then + return true; + end + local invite = st.message({from = self.jid, to = invitee, id = stanza.attr.id}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('invite', {from = stanza.attr.from;}) + :tag('reason'):text(payload:get_child_text("reason")):up() + :up() + :up(); + if not module:fire_event("muc-invite", {room = self, stanza = invite, origin = origin, incoming = stanza}) then + self:route_stanza(invite); end + return true; end -- Add password to outgoing invite -- cgit v1.2.3 From 8b8f89703e632711ee0a6371a2f7e2400176b692 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 14:06:35 -0400 Subject: plugins/muc/muc.lib: Update declines to be more like invites --- plugins/muc/muc.lib.lua | 53 +++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index a5118430..58fa7b83 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1279,39 +1279,40 @@ module:hook("muc-invite", function(event) end); function room_mt:handle_mediated_decline(origin, stanza) - local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline") + local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline"); local declinee = jid_prep(payload.attr.to); - if declinee then - local from, to = stanza.attr.from, stanza.attr.to; - -- TODO: Validate declinee - local reason = payload:get_child_text("reason") - local decline = st.message({from = to, to = declinee, id = stanza.attr.id}) - :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) - :tag('decline', {from=from}) - :tag('reason'):text(reason or ""):up() - :up() - :up() - :tag('body') -- Add a plain message for clients which don't support declines - :text(from..' declined your invite to the room '..to..(reason and (' ('..reason..')') or "")) - :up(); - module:fire_event("muc-decline", { room = self, stanza = decline, origin = origin, incoming = stanza }); - return true; - else + if not declinee then origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); return true; + elseif not module:fire_event("muc-pre-decline", {room = self, origin = origin, stanza = stanza}) then + return true; end + local decline = st.message({from = self.jid, to = declinee, id = stanza.attr.id}) + :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("decline", {from = stanza.attr.from}) + :tag("reason"):text(payload:get_child_text("reason")):up() + :up() + :up(); + if not module:fire_event("muc-decline", {room = self, stanza = decline, origin = origin, incoming = stanza}) then + local occupant = self:get_occupant_by_real_jid(decline.attr.to); + if occupant then + self:route_to_occupant(occupant, decline); + else + self:route_stanza(decline); + end + end + return true; end +-- Add a plain message for clients which don't support declines module:hook("muc-decline", function(event) - local room, stanza = event.room, event.stanza - local occupant = room:get_occupant_by_real_jid(stanza.attr.to); - if occupant then - room:route_to_occupant(occupant, stanza) - else - room:route_stanza(stanza); - end - return true; -end, -1) + local room, stanza = event.room, event.stanza; + local decline = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline"); + local reason = decline:get_child_text("reason") or ""; + stanza:tag("body") + :text(decline.attr.from.." declined your invite to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) + :up(); +end); function room_mt:handle_message_to_room(origin, stanza) local type = stanza.attr.type; -- cgit v1.2.3 From e19853af0e7f6049fd43e3d061f4a1f54cbb8fa6 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 14:44:52 -0400 Subject: plugins/muc/muc.lib: Don't try and get occupant jids for annotating invite affiliation changes --- plugins/muc/muc.lib.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 58fa7b83..d6ba68f9 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1272,9 +1272,8 @@ module:hook("muc-invite", function(event) local invitee = stanza.attr.to if room:get_members_only() and not room:get_affiliation(invitee) then local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from - local current_nick = room:get_occupant_jid(from) log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); - room:set_affiliation(from, invitee, "member", "Invited by " .. current_nick) + room:set_affiliation(from, invitee, "member", "Invited by " .. from); -- This might fail; ignore for now end end); -- cgit v1.2.3 From 2c3dad041615c903fd05174183b7d2703681cc78 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 14:45:42 -0400 Subject: plugins/muc/muc.lib: restrict invitations in members only rooms to admins --- plugins/muc/muc.lib.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d6ba68f9..c8881178 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1204,6 +1204,17 @@ module:hook("muc-pre-invite", function(event) end end); +-- Invitation privileges in members-only rooms SHOULD be restricted to room admins; +-- if a member without privileges to edit the member list attempts to invite another user +-- the service SHOULD return a error to the occupant +module:hook("muc-pre-invite", function(event) + local room, stanza = event.room, event.stanza; + if room:get_members_only() and valid_affiliations[room:get_affiliation(stanza.attr.from) or "none"] < valid_affiliations.admin then + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end +end); + function room_mt:handle_mediated_invite(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); local invitee = jid_prep(payload.attr.to); -- cgit v1.2.3 From fbae5113588b6dd2743d72c29290613c3b7f9812 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 31 Mar 2014 14:46:59 -0400 Subject: plugins/muc/muc.lib: Add muc-room-locked event --- plugins/muc/muc.lib.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index c8881178..d839786a 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -88,6 +88,7 @@ function room_mt:get_default_role(affiliation) end function room_mt:lock() + module:fire_event("muc-room-locked", { room = self }); self.locked = true end function room_mt:unlock() -- cgit v1.2.3 From b06bdb9b7d3da8b1c30ae09a8279396acbb8cad9 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 1 Apr 2014 15:41:44 -0400 Subject: plugins/muc/muc.lib: Split up get_disco_info into events This was done so we can split off functionality to other files later (e.g. plugins/muc/password) --- plugins/muc/muc.lib.lua | 55 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d839786a..5f504d40 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -397,23 +397,46 @@ function room_mt:send_history(stanza) end function room_mt:get_disco_info(stanza) - local count = 0; for _ in self:each_occupant() do count = count + 1; end - return st.reply(stanza):query("http://jabber.org/protocol/disco#info") - :tag("identity", {category="conference", type="text", name=self:get_name()}):up() - :tag("feature", {var="http://jabber.org/protocol/muc"}):up() - :tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up() - :tag("feature", {var=self:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up() - :tag("feature", {var=self:get_members_only() and "muc_membersonly" or "muc_open"}):up() - :tag("feature", {var=self:get_persistent() and "muc_persistent" or "muc_temporary"}):up() - :tag("feature", {var=self:get_hidden() and "muc_hidden" or "muc_public"}):up() - :tag("feature", {var=self:get_whois() ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up() - :add_child(dataform.new({ - { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }, - { name = "muc#roominfo_description", label = "Description", value = "" }, - { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) } - }):form({["muc#roominfo_description"] = self:get_description()}, 'result')) - ; + local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#info"); + local form = dataform.new { + { name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/muc#roominfo" }; + }; + module:fire_event("muc-disco#info", {room = self; reply = reply; form = form;}); + reply:add_child(form:form(nil, "result")); + return reply; end +module:hook("muc-disco#info", function(event) + event.reply:tag("identity", {category="conference", type="text", name=event.room:get_name()}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = "http://jabber.org/protocol/muc"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_members_only() and "muc_membersonly" or "muc_open"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_persistent() and "muc_persistent" or "muc_temporary"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_hidden() and "muc_hidden" or "muc_public"}):up(); +end); +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = event.room:get_whois() ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up(); +end); +module:hook("muc-disco#info", function(event) + table.insert(event.form, { name = "muc#roominfo_description", label = "Description", value = event.room:get_description() }); +end); +module:hook("muc-disco#info", function(event) + local count = 0; for _ in event.room:each_occupant() do count = count + 1; end + table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); +end); + function room_mt:get_disco_items(stanza) local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items"); for room_jid in self:each_occupant() do -- cgit v1.2.3 From 65b712f91dbc129a53575e974e3bc1463fedca92 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 1 Apr 2014 17:10:01 -0400 Subject: plugins/muc/muc.lib: Move default config layout into hooks --- plugins/muc/muc.lib.lua | 148 +++++++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 64 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5f504d40..df31a83c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -907,7 +907,6 @@ function room_mt:send_form(origin, stanza) end function room_mt:get_form_layout(actor) - local whois = self:get_whois() local form = dataform.new({ title = "Configuration for "..self.jid, instructions = "Complete and submit this form to configure the room.", @@ -915,73 +914,94 @@ function room_mt:get_form_layout(actor) name = 'FORM_TYPE', type = 'hidden', value = 'http://jabber.org/protocol/muc#roomconfig' - }, - { - name = 'muc#roomconfig_roomname', - type = 'text-single', - label = 'Name', - value = self:get_name() or "", - }, - { - name = 'muc#roomconfig_roomdesc', - type = 'text-single', - label = 'Description', - value = self:get_description() or "", - }, - { - name = 'muc#roomconfig_persistentroom', - type = 'boolean', - label = 'Make Room Persistent?', - value = self:get_persistent() - }, - { - name = 'muc#roomconfig_publicroom', - type = 'boolean', - label = 'Make Room Publicly Searchable?', - value = not self:get_hidden() - }, - { - name = 'muc#roomconfig_changesubject', - type = 'boolean', - label = 'Allow Occupants to Change Subject?', - value = self:get_changesubject() - }, - { - name = 'muc#roomconfig_whois', - type = 'list-single', - label = 'Who May Discover Real JIDs?', - value = { - { value = 'moderators', label = 'Moderators Only', default = whois == 'moderators' }, - { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } - } - }, - { - name = 'muc#roomconfig_roomsecret', - type = 'text-private', - label = 'Password', - value = self:get_password() or "", - }, - { - name = 'muc#roomconfig_moderatedroom', - type = 'boolean', - label = 'Make Room Moderated?', - value = self:get_moderated() - }, - { - name = 'muc#roomconfig_membersonly', - type = 'boolean', - label = 'Make Room Members-Only?', - value = self:get_members_only() - }, - { - name = 'muc#roomconfig_historylength', - type = 'text-single', - label = 'Maximum Number of History Messages Returned by Room', - value = tostring(self:get_historylength()) } }); return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; end +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_roomname', + type = 'text-single', + label = 'Name', + value = event.room:get_name() or "", + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_roomdesc', + type = 'text-single', + label = 'Description', + value = event.room:get_description() or "", + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_persistentroom', + type = 'boolean', + label = 'Make Room Persistent?', + value = event.room:get_persistent() + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_publicroom', + type = 'boolean', + label = 'Make Room Publicly Searchable?', + value = not event.room:get_hidden() + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_changesubject', + type = 'boolean', + label = 'Allow Occupants to Change Subject?', + value = event.room:get_changesubject() + }); +end); +module:hook("muc-config-form", function(event) + local whois = event.room:get_whois(); + table.insert(event.form, { + name = 'muc#roomconfig_whois', + type = 'list-single', + label = 'Who May Discover Real JIDs?', + value = { + { value = 'moderators', label = 'Moderators Only', default = whois == 'moderators' }, + { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } + } + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_roomsecret', + type = 'text-private', + label = 'Password', + value = event.room:get_password() or "", + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_moderatedroom', + type = 'boolean', + label = 'Make Room Moderated?', + value = event.room:get_moderated() + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_membersonly', + type = 'boolean', + label = 'Make Room Members-Only?', + value = event.room:get_members_only() + }); +end); +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = 'muc#roomconfig_historylength', + type = 'text-single', + label = 'Maximum Number of History Messages Returned by Room', + value = tostring(event.room:get_historylength()) + }); +end); function room_mt:process_form(origin, stanza) local query = stanza.tags[1]; -- cgit v1.2.3 From 5f51a5c88afc3989c2aa70ff00ea07c854dfcd61 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 1 Apr 2014 17:45:03 -0400 Subject: plugins/muc/muc.lib: Refactor out process_form into hooks --- plugins/muc/muc.lib.lua | 117 +++++++++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 46 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index df31a83c..7fd9746c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1004,59 +1004,82 @@ module:hook("muc-config-form", function(event) end); function room_mt:process_form(origin, stanza) - local query = stanza.tags[1]; - local form = query:get_child("x", "jabber:x:data") - if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end - if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end - if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); return; end - - local fields = self:get_form_layout(stanza.attr.from):data(form); - if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); return; end - - - local changed = {}; + local form = stanza.tags[1]:get_child("x", "jabber:x:data"); + if form.attr.type == "cancel" then + origin.send(st.reply(stanza)); + elseif form.attr.type == "submit" then + local fields = self:get_form_layout(stanza.attr.from):data(form); + if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then + origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); + return true; + end - local function handle_option(name, field, allowed) - local new = fields[field]; - if new == nil then return; end - if allowed and not allowed[new] then return; end - if new == self["get_"..name](self) then return; end - changed[name] = true; - self["set_"..name](self, new); - end + local changed = {}; - local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option }; - module:fire_event("muc-config-submitted", event); + local function handle_option(name, field, allowed) + local new = fields[field]; + if new == nil then return; end + if allowed and not allowed[new] then return; end + if new == self["get_"..name](self) then return; end + changed[name] = true; + self["set_"..name](self, new); + end - handle_option("name", "muc#roomconfig_roomname"); - handle_option("description", "muc#roomconfig_roomdesc"); - handle_option("persistent", "muc#roomconfig_persistentroom"); - handle_option("moderated", "muc#roomconfig_moderatedroom"); - handle_option("members_only", "muc#roomconfig_membersonly"); - handle_option("public", "muc#roomconfig_publicroom"); - handle_option("changesubject", "muc#roomconfig_changesubject"); - handle_option("historylength", "muc#roomconfig_historylength"); - handle_option("whois", "muc#roomconfig_whois", valid_whois); - handle_option("password", "muc#roomconfig_roomsecret"); + local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option }; + module:fire_event("muc-config-submitted", event); - if self.save then self:save(true); end - if self:is_locked() then - self:unlock(); - end - origin.send(st.reply(stanza)); + if self.save then self:save(true); end + if self:is_locked() then + self:unlock(); + end + origin.send(st.reply(stanza)); - if next(changed) then - local msg = st.message({type='groupchat', from=self.jid}) - :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) - :tag('status', {code = '104'}):up() - :up(); - if changed.whois then - local code = (self:get_whois() == 'moderators') and "173" or "172"; - msg.tags[1]:tag('status', {code = code}):up(); + if next(changed) then + local msg = st.message({type='groupchat', from=self.jid}) + :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + :tag('status', {code = '104'}):up() + :up(); + if changed.whois then + local code = (self:get_whois() == 'moderators') and "173" or "172"; + msg.tags[1]:tag('status', {code = code}):up(); + end + self:broadcast_message(msg, false) end - self:broadcast_message(msg, false) + else + origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); end + return true; end +module:hook("muc-config-submitted", function(event) + event.update_option("name", "muc#roomconfig_roomname"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("description", "muc#roomconfig_roomdesc"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("persistent", "muc#roomconfig_persistentroom"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("moderated", "muc#roomconfig_moderatedroom"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("members_only", "muc#roomconfig_membersonly"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("public", "muc#roomconfig_publicroom"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("changesubject", "muc#roomconfig_changesubject"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("historylength", "muc#roomconfig_historylength"); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("whois", "muc#roomconfig_whois", valid_whois); +end); +module:hook("muc-config-submitted", function(event) + event.update_option("password", "muc#roomconfig_roomsecret"); +end); -- Removes everyone from the room function room_mt:clear(x) @@ -1188,8 +1211,10 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) self:destroy(newjid, reason, password); origin.send(st.reply(stanza)); return true; + elseif child.name == "x" and child.attr.xmlns == "jabber:x:data" then + return self:process_form(origin, stanza); else - self:process_form(origin, stanza); + origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return true; end end -- cgit v1.2.3 From 1782c1a4b939afc68f4ccee448726f61dfc58c44 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 11:35:00 -0400 Subject: plugins/muc/muc.lib: Modify muc-config-submitted to keep a list of status codes instead of fields changed --- plugins/muc/muc.lib.lua | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7fd9746c..07069130 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1014,18 +1014,16 @@ function room_mt:process_form(origin, stanza) return true; end - local changed = {}; - - local function handle_option(name, field, allowed) + local event = {room = self; origin = origin; stanza = stanza; fields = fields; status_codes = {};}; + function event.update_option(name, field, allowed) local new = fields[field]; if new == nil then return; end if allowed and not allowed[new] then return; end if new == self["get_"..name](self) then return; end - changed[name] = true; + event.status_codes["104"] = true; self["set_"..name](self, new); + return true; end - - local event = { room = self, fields = fields, changed = changed, stanza = stanza, origin = origin, update_option = handle_option }; module:fire_event("muc-config-submitted", event); if self.save then self:save(true); end @@ -1034,15 +1032,13 @@ function room_mt:process_form(origin, stanza) end origin.send(st.reply(stanza)); - if next(changed) then + if next(event.status_codes) then local msg = st.message({type='groupchat', from=self.jid}) :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) - :tag('status', {code = '104'}):up() - :up(); - if changed.whois then - local code = (self:get_whois() == 'moderators') and "173" or "172"; - msg.tags[1]:tag('status', {code = code}):up(); + for code in pairs(event.status_codes) do + msg:tag("status", {code = code;}):up(); end + msg:up(); self:broadcast_message(msg, false) end else @@ -1075,7 +1071,10 @@ module:hook("muc-config-submitted", function(event) event.update_option("historylength", "muc#roomconfig_historylength"); end); module:hook("muc-config-submitted", function(event) - event.update_option("whois", "muc#roomconfig_whois", valid_whois); + if event.update_option("whois", "muc#roomconfig_whois", valid_whois) then + local code = (event.room:get_whois() == 'moderators') and "173" or "172"; + event.status_codes[code] = true; + end end); module:hook("muc-config-submitted", function(event) event.update_option("password", "muc#roomconfig_roomsecret"); -- cgit v1.2.3 From 906e49003903ef31a24eba95b20f9d4b1fc21431 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 15:14:52 -0400 Subject: plugins/muc/muc.lib: Move description functions out to own file --- plugins/muc/description.lib.lua | 43 +++++++++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 28 ++++----------------------- 2 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 plugins/muc/description.lib.lua diff --git a/plugins/muc/description.lib.lua b/plugins/muc/description.lib.lua new file mode 100644 index 00000000..30852922 --- /dev/null +++ b/plugins/muc/description.lib.lua @@ -0,0 +1,43 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local function get_description(room) + return room._data.description; +end + +local function set_description(room, description) + if description == "" then description = nil; end + if get_description(room) == description then return false; end + room._data.description = description; + if room.save then room:save(true); end + return true; +end + +local function add_form_option(event) + table.insert(event.form, { + name = "muc#roomconfig_roomdesc"; + type = "text-single"; + label = "Description"; + value = get_description(event.room) or ""; + }); +end +module:hook("muc-disco#info", add_form_option); +module:hook("muc-config-form", add_form_option); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_roomdesc"]; + if new ~= nil and set_description(event.room, new) then + event.status_codes["104"] = true; + end +end); + +return { + get = get_description; + set = set_description; +}; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 07069130..ef572cfd 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -429,9 +429,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_whois() ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up(); end); -module:hook("muc-disco#info", function(event) - table.insert(event.form, { name = "muc#roominfo_description", label = "Description", value = event.room:get_description() }); -end); module:hook("muc-disco#info", function(event) local count = 0; for _ in event.room:each_occupant() do count = count + 1; end table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); @@ -495,16 +492,6 @@ end function room_mt:get_name() return self._data.name or jid_split(self.jid); end -function room_mt:set_description(description) - if description == "" or type(description) ~= "string" then description = nil; end - if self._data.description ~= description then - self._data.description = description; - if self.save then self:save(true); end - end -end -function room_mt:get_description() - return self._data.description; -end function room_mt:set_password(password) if password == "" or type(password) ~= "string" then password = nil; end if self._data.password ~= password then @@ -926,14 +913,6 @@ module:hook("muc-config-form", function(event) value = event.room:get_name() or "", }); end); -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_roomdesc', - type = 'text-single', - label = 'Description', - value = event.room:get_description() or "", - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_persistentroom', @@ -1049,9 +1028,6 @@ end module:hook("muc-config-submitted", function(event) event.update_option("name", "muc#roomconfig_roomname"); end); -module:hook("muc-config-submitted", function(event) - event.update_option("description", "muc#roomconfig_roomdesc"); -end); module:hook("muc-config-submitted", function(event) event.update_option("persistent", "muc#roomconfig_persistentroom"); end); @@ -1558,6 +1534,10 @@ function room_mt:set_role(actor, occupant_jid, role, reason) return true; end +local description = module:require "muc/description"; +room_mt.get_description = description.get; +room_mt.set_description = description.set; + local _M = {}; -- module "muc" function _M.new_room(jid, config) -- cgit v1.2.3 From 7da7cb7b4a7125eb1d77e67bb393ceab50355a07 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 15:36:08 -0400 Subject: plugins/muc/mod_muc: Move room locking into hook --- plugins/muc/mod_muc.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index a8a6388d..8b40d6ad 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -90,7 +90,13 @@ function create_room(jid) local room = muc_new_room(jid); room.save = room_save; rooms[jid] = room; - if lock_rooms then + module:fire_event("muc-room-created", { room = room }); + return room; +end + +if lock_rooms then + module:hook("muc-room-created", function(event) + local room = event.room; room:lock(); if lock_room_timeout and lock_room_timeout > 0 then module:add_timer(lock_room_timeout, function () @@ -99,9 +105,7 @@ function create_room(jid) end end); end - end - module:fire_event("muc-room-created", { room = room }); - return room; + end); end function forget_room(jid) -- cgit v1.2.3 From fd4362f97d1d75fa0ae251417801e9c82b4ef03c Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 15:48:25 -0400 Subject: plugins/muc: Move locking to seperate module --- plugins/muc/lock.lib.lua | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ plugins/muc/mod_muc.lua | 17 +------------ plugins/muc/muc.lib.lua | 30 ---------------------- 3 files changed, 66 insertions(+), 46 deletions(-) create mode 100644 plugins/muc/lock.lib.lua diff --git a/plugins/muc/lock.lib.lua b/plugins/muc/lock.lib.lua new file mode 100644 index 00000000..73dfa151 --- /dev/null +++ b/plugins/muc/lock.lib.lua @@ -0,0 +1,65 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local lock_rooms = module:get_option_boolean("muc_room_locking", false); +local lock_room_timeout = module:get_option_number("muc_room_lock_timeout", 300); + +local function lock(room) + module:fire_event("muc-room-locked", {room = room;}); + room.locked = true; +end +local function unlock(room) + module:fire_event("muc-room-unlocked", {room = room;}); + room.locked = nil; +end +local function is_locked(room) + return not not room.locked; +end + +if lock_rooms then + module:hook("muc-room-created", function(event) + local room = event.room; + lock(room); + if lock_room_timeout and lock_room_timeout > 0 then + module:add_timer(lock_room_timeout, function () + if is_locked(room) then + room:destroy(); -- Not unlocked in time + end + end); + end + end); +end + +-- Older groupchat protocol doesn't lock +module:hook("muc-room-pre-create", function(event) + if is_locked(event.room) and not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then + unlock(event.room); + end +end, 10); + +-- Don't let users into room while it is locked +module:hook("muc-occupant-pre-join", function(event) + if is_locked(event.room) then -- Deny entry + event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); + return true; + end +end, -30); + +-- When config is submitted; unlock the room +module:hook("muc-config-submitted", function(event) + if is_locked(event.room) then + unlock(event.room); + end +end, -1); + +return { + lock = lock; + unlock = unlock; + is_locked = is_locked; +}; diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 8b40d6ad..6f6094b4 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -23,8 +23,6 @@ if restrict_room_creation then restrict_room_creation = nil; end end -local lock_rooms = module:get_option_boolean("muc_room_locking", false); -local lock_room_timeout = module:get_option_number("muc_room_lock_timeout", 300); local muclib = module:require "muc"; local muc_new_room = muclib.new_room; @@ -47,6 +45,7 @@ module:depends("disco"); module:add_identity("conference", "text", muc_name); module:add_feature("http://jabber.org/protocol/muc"); module:depends "muc_unique" +module:require "muc/lock"; local function is_admin(jid) return um_is_admin(jid, module.host); @@ -94,20 +93,6 @@ function create_room(jid) return room; end -if lock_rooms then - module:hook("muc-room-created", function(event) - local room = event.room; - room:lock(); - if lock_room_timeout and lock_room_timeout > 0 then - module:add_timer(lock_room_timeout, function () - if room:is_locked() then - room:destroy(); -- Not unlocked in time - end - end); - end - end); -end - function forget_room(jid) rooms[jid] = nil; end diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index ef572cfd..6906b9ce 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -87,18 +87,6 @@ function room_mt:get_default_role(affiliation) end end -function room_mt:lock() - module:fire_event("muc-room-locked", { room = self }); - self.locked = true -end -function room_mt:unlock() - module:fire_event("muc-room-unlocked", { room = self }); - self.locked = nil -end -function room_mt:is_locked() - return not not self.locked -end - --- Occupant functions function room_mt:new_occupant(bare_real_jid, nick) local occupant = occupant_lib.new(bare_real_jid, nick); @@ -583,13 +571,6 @@ function room_mt:get_whois() return self._data.whois; end -module:hook("muc-room-pre-create", function(event) - local room = event.room; - if room:is_locked() and not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then - room:unlock(); -- Older groupchat protocol doesn't lock - end -end, 10); - -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); @@ -610,13 +591,6 @@ module:hook("muc-occupant-pre-join", function(event) end end, -20); -module:hook("muc-occupant-pre-join", function(event) - if event.room:is_locked() then -- Deny entry - event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); - return true; - end -end, -30); - -- registration required for entering members-only room module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; @@ -1006,9 +980,6 @@ function room_mt:process_form(origin, stanza) module:fire_event("muc-config-submitted", event); if self.save then self:save(true); end - if self:is_locked() then - self:unlock(); - end origin.send(st.reply(stanza)); if next(event.status_codes) then @@ -1543,7 +1514,6 @@ local _M = {}; -- module "muc" function _M.new_room(jid, config) return setmetatable({ jid = jid; - locked = nil; _jid_nick = {}; _occupants = {}; _data = { -- cgit v1.2.3 From 4c0e0c3c239a4ae8d19c78f0f4d6d6975fa7fdc8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 15:56:37 -0400 Subject: plugins/muc/lock.lib: Need to let creator into the locked room :) --- plugins/muc/lock.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/lock.lib.lua b/plugins/muc/lock.lib.lua index 73dfa151..7cf19be3 100644 --- a/plugins/muc/lock.lib.lua +++ b/plugins/muc/lock.lib.lua @@ -45,7 +45,7 @@ end, 10); -- Don't let users into room while it is locked module:hook("muc-occupant-pre-join", function(event) - if is_locked(event.room) then -- Deny entry + if not event.is_new_room and is_locked(event.room) then -- Deny entry event.origin.send(st.error_reply(event.stanza, "cancel", "item-not-found")); return true; end -- cgit v1.2.3 From 7f0b9f176bd26c13df53b938fc19c01d2aca51ee Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 16:57:59 -0400 Subject: plugins/muc: Move password functions to seperate module --- plugins/muc/muc.lib.lua | 52 +++----------------------------- plugins/muc/password.lib.lua | 70 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 48 deletions(-) create mode 100644 plugins/muc/password.lib.lua diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6906b9ce..62553321 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -399,9 +399,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = "http://jabber.org/protocol/muc"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up(); -end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); end); @@ -480,16 +477,6 @@ end function room_mt:get_name() return self._data.name or jid_split(self.jid); end -function room_mt:set_password(password) - if password == "" or type(password) ~= "string" then password = nil; end - if self._data.password ~= password then - self._data.password = password; - if self.save then self:save(true); end - end -end -function room_mt:get_password() - return self._data.password; -end function room_mt:set_moderated(moderated) moderated = moderated and true or nil; if self._data.moderated ~= moderated then @@ -576,21 +563,6 @@ module:hook("muc-room-pre-create", function(event) event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); end, -1); -module:hook("muc-occupant-pre-join", function(event) - local room, stanza = event.room, event.stanza; - local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); - password = password and password:get_child_text("password", "http://jabber.org/protocol/muc"); - if not password or password == "" then password = nil; end - if room:get_password() ~= password then - local from, to = stanza.attr.from, stanza.attr.to; - log("debug", "%s couldn't join due to invalid password: %s", from, to); - local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); - reply.tags[1].attr.code = "401"; - event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - end -end, -20); - -- registration required for entering members-only room module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; @@ -923,14 +895,6 @@ module:hook("muc-config-form", function(event) } }); end); -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_roomsecret', - type = 'text-private', - label = 'Password', - value = event.room:get_password() or "", - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_moderatedroom', @@ -1023,9 +987,6 @@ module:hook("muc-config-submitted", function(event) event.status_codes[code] = true; end end); -module:hook("muc-config-submitted", function(event) - event.update_option("password", "muc#roomconfig_roomsecret"); -end); -- Removes everyone from the room function room_mt:clear(x) @@ -1251,15 +1212,6 @@ function room_mt:handle_mediated_invite(origin, stanza) return true; end --- Add password to outgoing invite -module:hook("muc-invite", function(event) - local password = event.room:get_password(); - if password then - local x = event.stanza:get_child("x", "http://jabber.org/protocol/muc#user"); - x:tag("password"):text(password):up(); - end -end); - -- COMPAT: Some older clients expect this module:hook("muc-invite", function(event) local room, stanza = event.room, event.stanza; @@ -1509,6 +1461,10 @@ local description = module:require "muc/description"; room_mt.get_description = description.get; room_mt.set_description = description.set; +local password = module:require "muc/password"; +room_mt.get_password = password.get; +room_mt.set_password = password.set; + local _M = {}; -- module "muc" function _M.new_room(jid, config) diff --git a/plugins/muc/password.lib.lua b/plugins/muc/password.lib.lua new file mode 100644 index 00000000..d169790a --- /dev/null +++ b/plugins/muc/password.lib.lua @@ -0,0 +1,70 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local function get_password(room) + return room._data.password; +end + +local function set_password(room, password) + if password == "" then password = nil; end + if room._data.password == password then return false; end + room._data.password = password; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_password(event.room) and "muc_passwordprotected" or "muc_unsecured"}):up(); +end); + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_roomsecret"; + type = "text-private"; + label = "Password"; + value = get_password(event.room) or ""; + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_roomsecret"]; + if new ~= nil and set_password(event.room, new) then + event.status_codes["104"] = true; + end +end); + +-- Don't allow anyone to join room unless they provide the password +module:hook("muc-occupant-pre-join", function(event) + local room, stanza = event.room, event.stanza; + local password = stanza:get_child("x", "http://jabber.org/protocol/muc"); + password = password and password:get_child_text("password", "http://jabber.org/protocol/muc"); + if not password or password == "" then password = nil; end + if get_password(room) ~= password then + local from, to = stanza.attr.from, stanza.attr.to; + module:log("debug", "%s couldn't join due to invalid password: %s", from, to); + local reply = st.error_reply(stanza, "auth", "not-authorized"):up(); + reply.tags[1].attr.code = "401"; + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end +end, -20); + +-- Add password to outgoing invite +module:hook("muc-invite", function(event) + local password = get_password(event.room); + if password then + local x = event.stanza:get_child("x", "http://jabber.org/protocol/muc#user"); + x:tag("password"):text(password):up(); + end +end); + +return { + get = get_password; + set = set_password; +}; -- cgit v1.2.3 From 538ed6fc20ccd0b2622bc8b1b474c76db0cf8397 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 17:02:07 -0400 Subject: plugins/muc: Move name functions to seperate module --- plugins/muc/muc.lib.lua | 28 ++++------------------------ plugins/muc/name.lib.lua | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 24 deletions(-) create mode 100644 plugins/muc/name.lib.lua diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 62553321..15e3f9c9 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -393,9 +393,6 @@ function room_mt:get_disco_info(stanza) reply:add_child(form:form(nil, "result")); return reply; end -module:hook("muc-disco#info", function(event) - event.reply:tag("identity", {category="conference", type="text", name=event.room:get_name()}):up(); -end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = "http://jabber.org/protocol/muc"}):up(); end); @@ -467,16 +464,6 @@ function room_mt:handle_kickable(origin, stanza) return true; end -function room_mt:set_name(name) - if name == "" or type(name) ~= "string" or name == (jid_split(self.jid)) then name = nil; end - if self._data.name ~= name then - self._data.name = name; - if self.save then self:save(true); end - end -end -function room_mt:get_name() - return self._data.name or jid_split(self.jid); -end function room_mt:set_moderated(moderated) moderated = moderated and true or nil; if self._data.moderated ~= moderated then @@ -851,14 +838,6 @@ function room_mt:get_form_layout(actor) }); return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; end -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_roomname', - type = 'text-single', - label = 'Name', - value = event.room:get_name() or "", - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_persistentroom', @@ -960,9 +939,6 @@ function room_mt:process_form(origin, stanza) end return true; end -module:hook("muc-config-submitted", function(event) - event.update_option("name", "muc#roomconfig_roomname"); -end); module:hook("muc-config-submitted", function(event) event.update_option("persistent", "muc#roomconfig_persistentroom"); end); @@ -1457,6 +1433,10 @@ function room_mt:set_role(actor, occupant_jid, role, reason) return true; end +local name = module:require "muc/name"; +room_mt.get_name = name.get; +room_mt.set_name = name.set; + local description = module:require "muc/description"; room_mt.get_description = description.get; room_mt.set_description = description.set; diff --git a/plugins/muc/name.lib.lua b/plugins/muc/name.lib.lua new file mode 100644 index 00000000..49d12467 --- /dev/null +++ b/plugins/muc/name.lib.lua @@ -0,0 +1,47 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local jid_split = require "util.jid".split; + +local function get_name(room) + return room._data.name or jid_split(room.jid); +end + +local function set_name(room, name) + if name == "" or name == (jid_split(room.jid)) then name = nil; end + if room._data.name == name then return false; end + room._data.name = name; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-disco#info", function(event) + event.reply:tag("identity", {category="conference", type="text", name=get_name(event.room)}):up(); +end); + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_roomname"; + type = "text-single"; + label = "Name"; + value = get_name(event.room) or ""; + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_roomname"]; + if new ~= nil and set_name(event.room, new) then + event.status_codes["104"] = true; + end +end); + +return { + get = get_name; + set = set_name; +}; -- cgit v1.2.3 From 9b57ba8391c07c5c5931641725b45760823570b7 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 2 Apr 2014 18:37:52 -0400 Subject: plugins/muc/occupant.lib: Don't allow an unavailable session to be the primary jid --- plugins/muc/occupant.lib.lua | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/plugins/muc/occupant.lib.lua b/plugins/muc/occupant.lib.lua index b4b12390..5cecb139 100644 --- a/plugins/muc/occupant.lib.lua +++ b/plugins/muc/occupant.lib.lua @@ -36,7 +36,8 @@ end local function copy_occupant(occupant) local sessions = {}; for full_jid, presence_stanza in pairs(occupant.sessions) do - if presence_stanza.attr.type ~= "unavailable" then + -- Don't keep unavailable presences, as they'll accumulate; unless they're the primary session + if presence_stanza.attr.type ~= "unavailable" or full_jid == occupant.jid then sessions[full_jid] = presence_stanza; end end @@ -49,24 +50,35 @@ local function copy_occupant(occupant) }, occupant_mt); end +-- finds another session to be the primary (there might not be one) +function occupant_mt:choose_new_primary() + for jid, pr in self:each_session() do + if pr.attr.type ~= "unavailable" then + return jid; + end + end + return nil; +end + function occupant_mt:set_session(real_jid, presence_stanza, replace_primary) local pr = get_filtered_presence(presence_stanza); pr.attr.from = self.nick; pr.attr.to = real_jid; self.sessions[real_jid] = pr; - if replace_primary or self.jid == nil then + if replace_primary then self.jid = real_jid; + elseif self.jid == nil or (pr.attr.type == "unavailable" and self.jid == real_jid) then + -- Only leave an unavailable presence as primary when there are no other options + self.jid = self:choose_new_primary() or real_jid; end end function occupant_mt:remove_session(real_jid) -- Delete original session - local presence_stanza = self.sessions[real_jid]; self.sessions[real_jid] = nil; if self.jid == real_jid then - -- find another session to be the primary (might be nil) - self.jid = next(self.sessions); + self.jid = self:choose_new_primary(); end end -- cgit v1.2.3 From 13c9f3da9076f3c1057d9ad6e6dc3a6e874d333c Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 12:25:03 -0400 Subject: plugins/muc/muc.lib: Clean up whois handling - adds functions `can_see_real_jids` and `get_base_presence` - In `publicise_occupant_status`, we don't generate each type of presence until it's used --- plugins/muc/muc.lib.lua | 65 +++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 15e3f9c9..5b160185 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -217,41 +217,53 @@ function room_mt:broadcast(stanza, cond_func) end end --- Broadcasts an occupant's presence to the whole room --- Takes (and modifies) the x element that goes into the stanzas -function room_mt:publicise_occupant_status(occupant, full_x, actor, reason) - local anon_x; - local has_anonymous = self:get_whois() ~= "anyone"; - if has_anonymous then - anon_x = st.clone(full_x); - self:build_item_list(occupant, anon_x, true, nil, actor, reason); +local function can_see_real_jids(whois, occupant) + if whois == "anyone" then + return true; + elseif whois == "moderators" then + return valid_roles[occupant.role or "none"] >= valid_roles.moderator; end - self:build_item_list(occupant,full_x, false, nil, actor, reason); +end - -- General populance - local full_p +local function get_base_presence(occupant) if occupant.role ~= nil then -- Try to use main jid's presence local pr = occupant:get_presence(); if pr ~= nil then - full_p = st.clone(pr); + return st.clone(pr); end end - if full_p == nil then - full_p = st.presence{from=occupant.nick; type="unavailable"}; - end - local anon_p; - if has_anonymous then - anon_p = st.clone(full_p); - anon_p:add_child(anon_x); + return st.presence {from = occupant.nick; type = "unavailable";}; +end + +-- Broadcasts an occupant's presence to the whole room +-- Takes the x element that goes into the stanzas +function room_mt:publicise_occupant_status(occupant, base_x, actor, reason) + -- Build real jid and (optionally) occupant jid template presences + local function get_presence(is_anonymous) + local x = st.clone(base_x); + self:build_item_list(occupant, x, is_anonymous, actor, reason); + return get_base_presence(occupant):add_child(x), x; + end + local full_p, full_x = get_presence(false); + local anon_p, anon_x; + local function get_anon_p() + if anon_p == nil then + anon_p, anon_x = get_presence(true); + end + return anon_p, anon_x; end - full_p:add_child(full_x); + local whois = self:get_whois(); + + -- General populance for nick, n_occupant in self:each_occupant() do - if nick ~= occupant.nick or n_occupant.role == nil then - local pr = full_p; - if has_anonymous and n_occupant.role ~= "moderator" and occupant.bare_jid ~= n_occupant.bare_jid then - pr = anon_p; + if nick ~= occupant.nick then + local pr; + if can_see_real_jids(whois, occupant) or occupant.bare_jid == n_occupant.bare_jid then + pr = full_p; + else + pr = get_anon_p(); end self:route_to_occupant(n_occupant, pr); end @@ -279,11 +291,12 @@ end function room_mt:send_occupant_list(to, filter) local to_bare = jid_bare(to); local is_anonymous = true; - if self:get_whois() ~= "anyone" then + local whois = self:get_whois(); + if whois ~= "anyone" then local affiliation = self:get_affiliation(to); if affiliation ~= "admin" and affiliation ~= "owner" then local occupant = self:get_occupant_by_real_jid(to); - if not occupant or occupant.role ~= "moderator" then + if not occupant or can_see_real_jids(whois, occupant) then is_anonymous = false; end end -- cgit v1.2.3 From 25e6558aafd036d7fddbf9ad95c39e2cda5b49b9 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 12:44:27 -0400 Subject: plugins/muc/muc.lib: Refactor of change-nick presence handling - Allow `nick` to be passed to `publicise_occupant_status`. - Moves multi-session handling to a more 'edge-case' area of code --- plugins/muc/muc.lib.lua | 66 +++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5b160185..353a7920 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -238,11 +238,11 @@ end -- Broadcasts an occupant's presence to the whole room -- Takes the x element that goes into the stanzas -function room_mt:publicise_occupant_status(occupant, base_x, actor, reason) +function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason) -- Build real jid and (optionally) occupant jid template presences local function get_presence(is_anonymous) local x = st.clone(base_x); - self:build_item_list(occupant, x, is_anonymous, actor, reason); + self:build_item_list(occupant, x, is_anonymous, nick, actor, reason); return get_base_presence(occupant):add_child(x), x; end local full_p, full_x = get_presence(false); @@ -277,13 +277,11 @@ function room_mt:publicise_occupant_status(occupant, base_x, actor, reason) else -- use their own presences as templates for full_jid, pr in occupant:each_session() do - if pr.attr.type ~= "unavailable" then - pr = st.clone(pr); - pr.attr.to = full_jid; - -- You can always see your own full jids - pr:add_child(full_x); - self:route_stanza(pr); - end + pr = st.clone(pr); + pr.attr.to = full_jid; + -- You can always see your own full jids + pr:add_child(full_x); + self:route_stanza(pr); end end end @@ -671,46 +669,44 @@ function room_mt:handle_presence_to_occupant(origin, stanza) -- Send presence stanza about original occupant if orig_occupant ~= nil and orig_occupant ~= dest_occupant then local orig_x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); - + local dest_nick; if dest_occupant == nil then -- Session is leaving log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); orig_occupant:set_session(real_jid, stanza); else log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick); - orig_occupant:remove_session(real_jid); -- If we are moving to a new nick; we don't want to get our own presence - - local dest_nick = select(3, jid_split(dest_occupant.nick)); - local affiliation = self:get_affiliation(bare_jid); - - -- This session + local generated_unavail = st.presence {from = orig_occupant.nick, to = real_jid, type = "unavailable"}; + orig_occupant:set_session(real_jid, generated_unavail); + dest_nick = select(3, jid_split(dest_occupant.nick)); if not is_first_dest_session then -- User is swapping into another pre-existing session log("debug", "session %s is swapping into multisession %s, showing it leave.", real_jid, dest_occupant.nick); -- Show the other session leaving local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}) :tag("status"):text("you are joining pre-existing session " .. dest_nick):up(); - add_item(x, affiliation, "none"); + add_item(x, self:get_affiliation(bare_jid), "none"); local pr = st.presence{from = dest_occupant.nick, to = real_jid, type = "unavailable"} :add_child(x); self:route_stanza(pr); - else - if is_last_orig_session then -- User is moving to a new session - log("debug", "no sessions in %s left; marking as nick change", orig_occupant.nick); - -- Everyone gets to see this as a nick change - local jid = self:get_whois() ~= "anyone" and real_jid or nil; -- FIXME: mods should see real jids - add_item(orig_x, affiliation, orig_occupant.role, jid, dest_nick); - orig_x:tag("status", {code = "303";}):up(); - end end - -- The session itself always sees a nick change - local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); - add_item(x, affiliation, orig_occupant.role, real_jid, dest_nick); - -- self:build_item_list(orig_occupant, x, false); -- COMPAT - x:tag("status", {code = "303";}):up(); - x:tag("status", {code = "110";}):up(); - self:route_stanza(st.presence{from = orig_occupant.nick, to = real_jid, type = "unavailable"}:add_child(x)); + if is_first_dest_session and is_last_orig_session then -- Normal nick change + log("debug", "no sessions in %s left; publically marking as nick change", orig_occupant.nick); + orig_x:tag("status", {code = "303";}):up(); + else -- The session itself always needs to see a nick change + -- don't want to get our old nick's available presence, + -- so remove our session from there, and manually generate an unavailable + orig_occupant:remove_session(real_jid); + log("debug", "generating nick change for %s", real_jid); + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}); + -- self:build_item_list(orig_occupant, x, false, dest_nick); -- COMPAT: clients get confused if they see other items besides their own + add_item(x, self:get_affiliation(bare_jid), orig_occupant.role, real_jid, dest_nick); + x:tag("status", {code = "303";}):up(); + x:tag("status", {code = "110";}):up(); + self:route_stanza(generated_unavail:add_child(x)); + dest_nick = nil; -- set dest_nick to nil; so general populance doesn't see it for whole orig_occupant + end end self:save_occupant(orig_occupant); - self:publicise_occupant_status(orig_occupant, orig_x); + self:publicise_occupant_status(orig_occupant, orig_x, dest_nick); if is_last_orig_session then module:fire_event("muc-occupant-left", {room = self; nick = orig_occupant.nick;}); @@ -1381,7 +1377,7 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason) end local is_semi_anonymous = self:get_whois() == "moderators"; for occupant, old_role in pairs(occupants_updated) do - self:publicise_occupant_status(occupant, x, actor, reason); + self:publicise_occupant_status(occupant, x, nil, actor, reason); if is_semi_anonymous and (old_role == "moderator" and occupant.role ~= "moderator") or (old_role ~= "moderator" and occupant.role == "moderator") then -- Has gained or lost moderator status @@ -1442,7 +1438,7 @@ function room_mt:set_role(actor, occupant_jid, role, reason) end occupant.role = role; self:save_occupant(occupant); - self:publicise_occupant_status(occupant, x, actor, reason); + self:publicise_occupant_status(occupant, x, nil, actor, reason); return true; end -- cgit v1.2.3 From b1cf3db9a1227bd6a2d21d27b81a65d597e5b7f8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 14:23:06 -0400 Subject: plugins/muc/muc.lib: Remember to coerce nil role to "none" --- plugins/muc/muc.lib.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 353a7920..20cbe04d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -163,10 +163,11 @@ local function add_item(x, affiliation, role, jid, nick, actor, reason) x:up(); return x end + -- actor is (real) jid function room_mt:build_item_list(occupant, x, is_anonymous, nick, actor, reason) - local affiliation = self:get_affiliation(occupant.bare_jid); - local role = occupant.role; + local affiliation = self:get_affiliation(occupant.bare_jid) or "none"; + local role = occupant.role or "none"; local actor_attr; if actor then actor_attr = {nick = select(3,jid_split(self:get_occupant_jid(actor)))}; -- cgit v1.2.3 From bae30c03d41b80b2bd10ca2d0c9b60d2a6913bc3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 14:24:27 -0400 Subject: plugins/muc: Move `whois` code to seperate file --- plugins/muc/muc.lib.lua | 54 ++++---------------------------------- plugins/muc/whois.lib.lua | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 49 deletions(-) create mode 100644 plugins/muc/whois.lib.lua diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 20cbe04d..7a8464e5 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -420,9 +420,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_hidden() and "muc_hidden" or "muc_public"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_whois() ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up(); -end); module:hook("muc-disco#info", function(event) local count = 0; for _ in event.room:each_occupant() do count = count + 1; end table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); @@ -543,20 +540,6 @@ function room_mt:set_historylength(length) self._data.history_length = length; end - -local valid_whois = { moderators = true, anyone = true }; - -function room_mt:set_whois(whois) - if valid_whois[whois] and self._data.whois ~= whois then - self._data.whois = whois; - if self.save then self:save(true); end - end -end - -function room_mt:get_whois() - return self._data.whois; -end - -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); @@ -872,18 +855,6 @@ module:hook("muc-config-form", function(event) value = event.room:get_changesubject() }); end); -module:hook("muc-config-form", function(event) - local whois = event.room:get_whois(); - table.insert(event.form, { - name = 'muc#roomconfig_whois', - type = 'list-single', - label = 'Who May Discover Real JIDs?', - value = { - { value = 'moderators', label = 'Moderators Only', default = whois == 'moderators' }, - { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } - } - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_moderatedroom', @@ -967,12 +938,6 @@ end); module:hook("muc-config-submitted", function(event) event.update_option("historylength", "muc#roomconfig_historylength"); end); -module:hook("muc-config-submitted", function(event) - if event.update_option("whois", "muc#roomconfig_whois", valid_whois) then - local code = (event.room:get_whois() == 'moderators') and "173" or "172"; - event.status_codes[code] = true; - end -end); -- Removes everyone from the room function room_mt:clear(x) @@ -1218,21 +1183,9 @@ module:hook("muc-invite", function(event) :up(); end); --- Mask 'from' jid as occupant jid if room is anonymous -module:hook("muc-invite", function(event) - local room, stanza = event.room, event.stanza; - if room:get_whois() == "moderators" and room:get_default_role(room:get_affiliation(stanza.attr.to)) ~= "moderator" then - local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); - local occupant_jid = room:get_occupant_jid(invite.attr.from); - if occupant_jid ~= nil then -- FIXME: This will expose real jid if inviter is not in room - invite.attr.from = occupant_jid; - end - end -end, 50); - -- When an invite is sent; add an affiliation for the invitee module:hook("muc-invite", function(event) - local room, stanza = event.room, event.stanza + local room, stanza = event.room, event.stanza; local invitee = stanza.attr.to if room:get_members_only() and not room:get_affiliation(invitee) then local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from @@ -1455,6 +1408,10 @@ local password = module:require "muc/password"; room_mt.get_password = password.get; room_mt.set_password = password.set; +local whois = module:require "muc/whois"; +room_mt.get_whois = whois.get; +room_mt.set_whois = whois.set; + local _M = {}; -- module "muc" function _M.new_room(jid, config) @@ -1463,7 +1420,6 @@ function _M.new_room(jid, config) _jid_nick = {}; _occupants = {}; _data = { - whois = 'moderators'; history_length = math.min((config and config.history_length) or default_history_length, max_history_length); }; diff --git a/plugins/muc/whois.lib.lua b/plugins/muc/whois.lib.lua new file mode 100644 index 00000000..f89e6087 --- /dev/null +++ b/plugins/muc/whois.lib.lua @@ -0,0 +1,67 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local valid_whois = { + moderators = true; + anyone = true; +}; + +local function get_whois(room) + return room._data.whois or "moderators"; +end + +local function set_whois(room, whois) + assert(valid_whois[whois], "Invalid whois value") + if get_whois(room) == whois then return false; end + room._data.whois = whois; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_whois(event.room) ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up(); +end); + +module:hook("muc-config-form", function(event) + local whois = get_whois(event.room); + table.insert(event.form, { + name = 'muc#roomconfig_whois', + type = 'list-single', + label = 'Who May Discover Real JIDs?', + value = { + { value = 'moderators', label = 'Moderators Only', default = whois == 'moderators' }, + { value = 'anyone', label = 'Anyone', default = whois == 'anyone' } + } + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_whois"]; + if new ~= nil and set_whois(event.room, new) then + local code = (new == 'moderators') and "173" or "172"; + event.status_codes[code] = true; + end +end); + +-- Mask 'from' jid as occupant jid if room is anonymous +module:hook("muc-invite", function(event) + local room, stanza = event.room, event.stanza; + if get_whois(room) == "moderators" and room:get_default_role(room:get_affiliation(stanza.attr.to)) ~= "moderator" then + local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local occupant_jid = room:get_occupant_jid(invite.attr.from); + if occupant_jid ~= nil then -- FIXME: This will expose real jid if inviter is not in room + invite.attr.from = occupant_jid; + end + end +end, 50); + +return { + get = get_whois; + set = set_whois; +}; -- cgit v1.2.3 From b661ecb24f5e68a3a7af2532f8e91189e305fb82 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 15:14:52 -0400 Subject: plugins/muc: Move history to an external module This resulted in the split up of the main muc-occupant-joined event handler into 3 seperate ones, handling occupant list, history and subject --- plugins/muc/history.lib.lua | 161 ++++++++++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 167 ++++++-------------------------------------- 2 files changed, 182 insertions(+), 146 deletions(-) create mode 100644 plugins/muc/history.lib.lua diff --git a/plugins/muc/history.lib.lua b/plugins/muc/history.lib.lua new file mode 100644 index 00000000..417d62a8 --- /dev/null +++ b/plugins/muc/history.lib.lua @@ -0,0 +1,161 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local gettime = os.time; +local datetime = require "util.datetime"; +local st = require "util.stanza"; + +local default_history_length, max_history_length = 20, math.huge; + +local function set_max_history_length(_max_history_length) + max_history_length = _max_history_length or math.huge; +end + +local function get_historylength(room) + return math.min(room._data.history_length or default_history_length, max_history_length); +end + +local function set_historylength(room, length) + length = assert(tonumber(length), "Length not a valid number"); + if length == default_history_length then length = nil; end + room._data.history_length = length; + return true; +end + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_historylength"; + type = "text-single"; + label = "Maximum Number of History Messages Returned by Room"; + value = tostring(get_historylength(event.room)); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_historylength"]; + if new ~= nil and set_historylength(event.room, new) then + event.status_codes["104"] = true; + end +end); + +local function parse_history(stanza) + local x_tag = stanza:get_child("x", "http://jabber.org/protocol/muc"); + local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); + if not history_tag then + return nil, default_history_length, nil; + end + + local maxchars = tonumber(history_tag.attr.maxchars); + + local maxstanzas = tonumber(history_tag.attr.maxstanzas); + + -- messages received since the UTC datetime specified + local since = history_tag.attr.since; + if since then + since = datetime.parse(since); + end + + -- messages received in the last "X" seconds. + local seconds = tonumber(history_tag.attr.seconds); + if seconds then + seconds = gettime() - seconds; + if since then + since = math.max(since, seconds); + else + since = seconds; + end + end + + return maxchars, maxstanzas, since; +end + +module:hook("muc-get-history", function(event) + local room = event.room; + local history = room._data["history"]; -- send discussion history + if not history then return nil end + local history_len = #history; + + local to = event.to; + local maxchars = event.maxchars; + local maxstanzas = event.maxstanzas or history_len; + local since = event.since; + local n = 0; + local charcount = 0; + for i=history_len,1,-1 do + local entry = history[i]; + if maxchars then + if not entry.chars then + entry.stanza.attr.to = ""; + entry.chars = #tostring(entry.stanza); + end + charcount = charcount + entry.chars + #to; + if charcount > maxchars then break; end + end + if since and since > entry.timestamp then break; end + if n + 1 > maxstanzas then break; end + n = n + 1; + end + + local i = history_len-n+1 + function event:next_stanza() + if i > history_len then return nil end + local entry = history[i]; + local msg = entry.stanza; + msg.attr.to = to; + i = i + 1; + return msg; + end + return true; +end); + +local function send_history(room, stanza) + local maxchars, maxstanzas, since = parse_history(stanza); + local event = { + room = room; + to = stanza.attr.from; -- `to` is required to calculate the character count for `maxchars` + maxchars = maxchars, maxstanzas = maxstanzas, since = since; + next_stanza = function() end; -- events should define this iterator + }; + module:fire_event("muc-get-history", event); + for msg in event.next_stanza, event do + room:route_stanza(msg); + end +end + +-- Send history on join +module:hook("muc-occupant-joined", function(event) + send_history(event.room, event.stanza); +end, 50); -- Between occupant list (80) and subject(20) + +-- add to history +module:hook("muc-broadcast-message", function(event) + local historic = event.stanza:get_child("body"); + if historic then + local room = event.room + local history = room._data["history"]; + if not history then history = {}; room._data["history"] = history; end + local stanza = st.clone(event.stanza); + stanza.attr.to = ""; + local ts = gettime(); + local stamp = datetime.datetime(ts); + stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 + stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) + local entry = { stanza = stanza, timestamp = ts }; + table.insert(history, entry); + while #history > get_historylength(room) do table.remove(history, 1) end + end +end); + +return { + set_max_length = set_max_history_length; + parse_history = parse_history; + send = send_history; + get_length = get_historylength; + set_length = set_historylength; +}; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7a8464e5..89953615 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -11,13 +11,8 @@ local select = select; local pairs, ipairs = pairs, ipairs; local next = next; local setmetatable = setmetatable; -local t_insert, t_remove = table.insert, table.remove; - -local gettime = os.time; -local datetime = require "util.datetime"; local dataform = require "util.dataforms"; - local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; @@ -28,7 +23,6 @@ local md5 = require "util.hashes".md5; local occupant_lib = module:require "muc/occupant" -local default_history_length, max_history_length = 20, math.huge; local is_kickable_error do local kickable_error_conditions = { @@ -185,29 +179,11 @@ function room_mt:build_item_list(occupant, x, is_anonymous, nick, actor, reason) return x end -function room_mt:broadcast_message(stanza, historic) - module:fire_event("muc-broadcast-message", {room = self, stanza = stanza, historic = historic}); +function room_mt:broadcast_message(stanza) + module:fire_event("muc-broadcast-message", {room = self, stanza = stanza}); self:broadcast(stanza); end --- add to history -module:hook("muc-broadcast-message", function(event) - if event.historic then - local room = event.room - local history = room._data['history']; - if not history then history = {}; room._data['history'] = history; end - local stanza = st.clone(event.stanza); - stanza.attr.to = ""; - local ts = gettime(); - local stamp = datetime.datetime(ts); - stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp}):up(); -- XEP-0203 - stanza:tag("x", {xmlns = "jabber:x:delay", from = module.host, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated) - local entry = { stanza = stanza, timestamp = ts }; - t_insert(history, entry); - while #history > room:get_historylength() do t_remove(history, 1) end - end -end); - -- Broadcast a stanza to all occupants in the room. -- optionally checks conditional called with (nick, occupant) function room_mt:broadcast(stanza, cond_func) @@ -312,90 +288,6 @@ function room_mt:send_occupant_list(to, filter) end end -local function parse_history(stanza) - local x_tag = stanza:get_child("x", "http://jabber.org/protocol/muc"); - local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc"); - if not history_tag then - return nil, 20, nil - end - - local maxchars = tonumber(history_tag.attr.maxchars); - - local maxstanzas = tonumber(history_tag.attr.maxstanzas); - - -- messages received since the UTC datetime specified - local since = history_tag.attr.since; - if since then - since = datetime.parse(since); - end - - -- messages received in the last "X" seconds. - local seconds = tonumber(history_tag.attr.seconds); - if seconds then - seconds = gettime() - seconds - if since then - since = math.max(since, seconds); - else - since = seconds; - end - end - - return maxchars, maxstanzas, since -end - -module:hook("muc-get-history", function(event) - local room = event.room - local history = room._data['history']; -- send discussion history - if not history then return nil end - local history_len = #history - - local to = event.to - local maxchars = event.maxchars - local maxstanzas = event.maxstanzas or history_len - local since = event.since - local n = 0; - local charcount = 0; - for i=history_len,1,-1 do - local entry = history[i]; - if maxchars then - if not entry.chars then - entry.stanza.attr.to = ""; - entry.chars = #tostring(entry.stanza); - end - charcount = charcount + entry.chars + #to; - if charcount > maxchars then break; end - end - if since and since > entry.timestamp then break; end - if n + 1 > maxstanzas then break; end - n = n + 1; - end - - local i = history_len-n+1 - function event:next_stanza() - if i > history_len then return nil end - local entry = history[i] - local msg = entry.stanza - msg.attr.to = to; - i = i + 1 - return msg - end - return true; -end); - -function room_mt:send_history(stanza) - local maxchars, maxstanzas, since = parse_history(stanza) - local event = { - room = self; - to = stanza.attr.from; -- `to` is required to calculate the character count for `maxchars` - maxchars = maxchars, maxstanzas = maxstanzas, since = since; - next_stanza = function() end; -- events should define this iterator - } - module:fire_event("muc-get-history", event) - for msg in event.next_stanza , event do - self:route_stanza(msg); - end -end - function room_mt:get_disco_info(stanza) local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#info"); local form = dataform.new { @@ -451,7 +343,7 @@ function room_mt:set_subject(current_nick, subject) self._data['subject_from'] = current_nick; if self.save then self:save(); end local msg = create_subject_message(current_nick, subject); - self:broadcast_message(msg, false); + self:broadcast_message(msg); return true; end @@ -529,16 +421,6 @@ end function room_mt:get_changesubject() return self._data.changesubject; end -function room_mt:get_historylength() - return self._data.history_length or default_history_length; -end -function room_mt:set_historylength(length) - length = math.min(tonumber(length) or default_history_length, max_history_length or math.huge); - if length == default_history_length then - length = nil; - end - self._data.history_length = length; -end -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) @@ -569,16 +451,19 @@ module:hook("muc-occupant-pre-join", function(event) end end, -10); +-- Send occupant list to newly joined user module:hook("muc-occupant-joined", function(event) - local room, stanza = event.room, event.stanza; - local real_jid = stanza.attr.from; - room:send_occupant_list(real_jid, function(nick, occupant) + local real_jid = event.stanza.attr.from; + event.room:send_occupant_list(real_jid, function(nick, occupant) -- Don't include self return occupant:get_presence(real_jid) == nil; end); - room:send_history(stanza); - room:send_subject(real_jid); -end, -1); +end, 80); + +-- Send subject to joining user +module:hook("muc-occupant-joined", function(event) + event.room:send_subject(event.stanza.attr.from); +end, 20); function room_mt:handle_presence_to_occupant(origin, stanza) local type = stanza.attr.type; @@ -871,14 +756,6 @@ module:hook("muc-config-form", function(event) value = event.room:get_members_only() }); end); -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_historylength', - type = 'text-single', - label = 'Maximum Number of History Messages Returned by Room', - value = tostring(event.room:get_historylength()) - }); -end); function room_mt:process_form(origin, stanza) local form = stanza.tags[1]:get_child("x", "jabber:x:data"); @@ -913,7 +790,7 @@ function room_mt:process_form(origin, stanza) msg:tag("status", {code = code;}):up(); end msg:up(); - self:broadcast_message(msg, false) + self:broadcast_message(msg); end else origin.send(st.error_reply(stanza, "cancel", "bad-request", "Not a submitted form")); @@ -935,9 +812,6 @@ end); module:hook("muc-config-submitted", function(event) event.update_option("changesubject", "muc#roomconfig_changesubject"); end); -module:hook("muc-config-submitted", function(event) - event.update_option("historylength", "muc#roomconfig_historylength"); -end); -- Removes everyone from the room function room_mt:clear(x) @@ -1099,7 +973,7 @@ function room_mt:handle_groupchat_to_room(origin, stanza) origin.send(st.error_reply(stanza, "auth", "forbidden")); end else - self:broadcast_message(stanza, self:get_historylength() > 0 and stanza:get_child("body")); + self:broadcast_message(stanza); end stanza.attr.from = from; return true; @@ -1412,25 +1286,26 @@ local whois = module:require "muc/whois"; room_mt.get_whois = whois.get; room_mt.set_whois = whois.set; +local history = module:require "muc/history"; +room_mt.send_history = history.send; +room_mt.get_historylength = history.get_length; +room_mt.set_historylength = history.set_length; + local _M = {}; -- module "muc" +_M.set_max_history_length = history.set_max_length; + function _M.new_room(jid, config) return setmetatable({ jid = jid; _jid_nick = {}; _occupants = {}; _data = { - history_length = math.min((config and config.history_length) - or default_history_length, max_history_length); }; _affiliations = {}; }, room_mt); end -function _M.set_max_history_length(_max_history_length) - max_history_length = _max_history_length or math.huge; -end - _M.room_mt = room_mt; return _M; -- cgit v1.2.3 From 185f0d33268c363466e638cbf14bd4ba492be13d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 16:04:04 -0400 Subject: plugins/muc/muc.lib: Move occupancy check to later in `deconstruct_stanza_id`: As vcards are from the bare jid, you need to use the `from_jid` out of the encoded `id` --- plugins/muc/muc.lib.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 89953615..694e13ff 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -622,13 +622,14 @@ function room_mt:handle_iq_to_occupant(origin, stanza) local from, to = stanza.attr.from, stanza.attr.to; local type = stanza.attr.type; local id = stanza.attr.id; - local current_nick = self:get_occupant_jid(from); local occupant = self:get_occupant_by_nick(to); if (type == "error" or type == "result") then do -- deconstruct_stanza_id - if not current_nick or not occupant then return nil; end + if not occupant then return nil; end local from_jid, id, to_jid_hash = (base64.decode(stanza.attr.id) or ""):match("^(.+)%z(.*)%z(.+)$"); if not(from == from_jid or from == jid_bare(from_jid)) then return nil; end + local from_occupant_jid = self:get_occupant_jid(from_jid); + if from_occupant_jid == nil then return nil; end local session_jid for to_jid in occupant:each_session() do if md5(to_jid) == to_jid_hash then @@ -637,13 +638,14 @@ function room_mt:handle_iq_to_occupant(origin, stanza) end end if session_jid == nil then return nil; end - stanza.attr.from, stanza.attr.to, stanza.attr.id = current_nick, session_jid, id + stanza.attr.from, stanza.attr.to, stanza.attr.id = from_jid, session_jid, id; end log("debug", "%s sent private iq stanza to %s (%s)", from, to, stanza.attr.to); self:route_stanza(stanza); stanza.attr.from, stanza.attr.to, stanza.attr.id = from, to, id; return true; else -- Type is "get" or "set" + local current_nick = self:get_occupant_jid(from); if not current_nick then origin.send(st.error_reply(stanza, "cancel", "not-acceptable")); return true; -- cgit v1.2.3 From e14f89d1d228c410f3a67cfd59792f660ad9562d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 16:10:43 -0400 Subject: plugins/muc/muc.lib: Use util.iterators to count occupants --- plugins/muc/muc.lib.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 694e13ff..59069cae 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -13,6 +13,7 @@ local next = next; local setmetatable = setmetatable; local dataform = require "util.dataforms"; +local iterators = require "util.iterators"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; @@ -313,7 +314,7 @@ module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_hidden() and "muc_hidden" or "muc_public"}):up(); end); module:hook("muc-disco#info", function(event) - local count = 0; for _ in event.room:each_occupant() do count = count + 1; end + local count = iterators.count(event.room:each_occupant()); table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); end); -- cgit v1.2.3 From adb653030ff70cff8ecbc10eb3650cc6ff79fba9 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 17:09:04 -0400 Subject: plugins/muc: Move valid_roles, valid_affiliations and is_kickable_error to new muc/util module --- plugins/muc/muc.lib.lua | 38 +++----------------------------------- plugins/muc/util.lib.lua | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 35 deletions(-) create mode 100644 plugins/muc/util.lib.lua diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 59069cae..6f19cd53 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -23,26 +23,9 @@ local base64 = require "util.encodings".base64; local md5 = require "util.hashes".md5; local occupant_lib = module:require "muc/occupant" - - -local is_kickable_error do - local kickable_error_conditions = { - ["gone"] = true; - ["internal-server-error"] = true; - ["item-not-found"] = true; - ["jid-malformed"] = true; - ["recipient-unavailable"] = true; - ["redirect"] = true; - ["remote-server-not-found"] = true; - ["remote-server-timeout"] = true; - ["service-unavailable"] = true; - ["malformed error"] = true; - }; - function is_kickable_error(stanza) - local cond = select(2, stanza:get_error()) or "malformed error"; - return kickable_error_conditions[cond]; - end -end +local muc_util = module:require "muc/util"; +local is_kickable_error = muc_util.is_kickable_error; +local valid_roles, valid_affiliations = muc_util.valid_roles, muc_util.valid_affiliations; local room_mt = {}; room_mt.__index = room_mt; @@ -55,21 +38,6 @@ function room_mt:get_occupant_jid(real_jid) return self._jid_nick[real_jid] end -local valid_affiliations = { - outcast = 0; - none = 1; - member = 2; - admin = 3; - owner = 4; -}; - -local valid_roles = { - none = 0; - visitor = 1; - participant = 2; - moderator = 3; -}; - function room_mt:get_default_role(affiliation) if affiliation == "owner" or affiliation == "admin" then return "moderator"; diff --git a/plugins/muc/util.lib.lua b/plugins/muc/util.lib.lua new file mode 100644 index 00000000..90a3a183 --- /dev/null +++ b/plugins/muc/util.lib.lua @@ -0,0 +1,44 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local _M = {}; + +_M.valid_affiliations = { + outcast = -1; + none = 0; + member = 1; + admin = 2; + owner = 3; +}; + +_M.valid_roles = { + none = 0; + visitor = 1; + participant = 2; + moderator = 3; +}; + +local kickable_error_conditions = { + ["gone"] = true; + ["internal-server-error"] = true; + ["item-not-found"] = true; + ["jid-malformed"] = true; + ["recipient-unavailable"] = true; + ["redirect"] = true; + ["remote-server-not-found"] = true; + ["remote-server-timeout"] = true; + ["service-unavailable"] = true; + ["malformed error"] = true; +}; +function _M.is_kickable_error(stanza) + local cond = select(2, stanza:get_error()) or "malformed error"; + return kickable_error_conditions[cond]; +end + +return _M; -- cgit v1.2.3 From d9474e875f5ea22a41f081e9586840ca70739db8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 18:10:16 -0400 Subject: plugins/muc/muc.lib: Fix getting a list of occupants by role (it was sending presences instead of items inside an iq) --- plugins/muc/muc.lib.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6f19cd53..fdddcfbe 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -113,7 +113,6 @@ function room_mt:route_to_occupant(occupant, stanza) stanza.attr.to = to; end --- Adds an item to an "x" element. -- actor is the attribute table local function add_item(x, affiliation, role, jid, nick, actor, reason) x:tag("item", {affiliation = affiliation; role = role; jid = jid; nick = nick;}) @@ -874,9 +873,17 @@ function room_mt:handle_admin_query_get_command(origin, stanza) end elseif _rol and not _aff then local role = self:get_role(self:get_occupant_jid(actor)) or self:get_default_role(affiliation); - if role == "moderator" then + if valid_roles[role or "none"] >= valid_roles.moderator then if _rol == "none" then _rol = nil; end - self:send_occupant_list(actor, function(occupant_jid, occupant) return occupant.role == _rol end); + local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin"); + -- TODO: whois check here? (though fully anonymous rooms are not supported) + for occupant_jid, occupant in self:each_occupant() do + if occupant.role == _rol then + local nick = select(3,jid_split(occupant_jid)); + self:build_item_list(occupant, reply, false, nick); + end + end + origin.send(reply:up()); return true; else origin.send(st.error_reply(stanza, "auth", "forbidden")); -- cgit v1.2.3 From 0b749352c28b79d5ebc582d479591adb8aa7ae2f Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 3 Apr 2014 18:36:28 -0400 Subject: plugins/muc/muc.lib: Turn get_default_role into an event --- plugins/muc/muc.lib.lua | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index fdddcfbe..4f8aa15d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -39,16 +39,30 @@ function room_mt:get_occupant_jid(real_jid) end function room_mt:get_default_role(affiliation) - if affiliation == "owner" or affiliation == "admin" then + local role = module:fire_event("muc-get-default-role", { + room = self; + affiliation = affiliation; + affiliation_rank = valid_affiliations[affiliation or "none"]; + }); + return role, valid_roles[role or "none"]; +end +module:hook("muc-get-default-role", function(event) + if event.affiliation_rank >= valid_affiliations.admin then return "moderator"; - elseif affiliation == "member" then + elseif event.affiliation_rank >= valid_affiliations.member then return "participant"; - elseif not affiliation then - if not self:get_members_only() then - return self:get_moderated() and "visitor" or "participant"; - end end -end +end); +module:hook("muc-get-default-role", function(event) + if not event.affiliation and event.room:get_members_only() then + return false; + end +end); +module:hook("muc-get-default-role", function(event) + if not event.affiliation then + return event.room:get_moderated() and "visitor" or "participant"; + end +end, -1); --- Occupant functions function room_mt:new_occupant(bare_real_jid, nick) -- cgit v1.2.3 From cd72b2acc4eb00c11c30eab62e1cbf496996c36e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 4 Apr 2014 11:20:20 -0400 Subject: plugins/muc/muc.lib: Move members_only into seperate file --- plugins/muc/members_only.lib.lua | 89 ++++++++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 66 ++--------------------------- 2 files changed, 93 insertions(+), 62 deletions(-) create mode 100644 plugins/muc/members_only.lib.lua diff --git a/plugins/muc/members_only.lib.lua b/plugins/muc/members_only.lib.lua new file mode 100644 index 00000000..84a17699 --- /dev/null +++ b/plugins/muc/members_only.lib.lua @@ -0,0 +1,89 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local muc_util = module:require "muc/util"; +local valid_roles, valid_affiliations = muc_util.valid_roles, muc_util.valid_affiliations; + +local function get_members_only(room) + return room._data.members_only; +end + +local function set_members_only(room, members_only) + members_only = members_only and true or nil; + if room._data.members_only == members_only then return false; end + room._data.members_only = members_only; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_members_only(event.room) and "muc_membersonly" or "muc_open"}):up(); +end); + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_membersonly"; + type = "boolean"; + label = "Make Room Members-Only?"; + value = get_members_only(event.room); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_membersonly"]; + if new ~= nil and set_members_only(event.room, new) then + event.status_codes["104"] = true; + end +end); + +-- No affiliation => role of "none" +module:hook("muc-get-default-role", function(event) + if not event.affiliation and get_members_only(event.room) then + return false; + end +end); + +-- registration required for entering members-only room +module:hook("muc-occupant-pre-join", function(event) + local room, stanza = event.room, event.stanza; + local affiliation = room:get_affiliation(stanza.attr.from); + if affiliation == nil and get_members_only(event.room) then + local reply = st.error_reply(stanza, "auth", "registration-required"):up(); + reply.tags[1].attr.code = "407"; + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end +end, -5); + +-- Invitation privileges in members-only rooms SHOULD be restricted to room admins; +-- if a member without privileges to edit the member list attempts to invite another user +-- the service SHOULD return a error to the occupant +module:hook("muc-pre-invite", function(event) + local room, stanza = event.room, event.stanza; + if get_members_only(room) and room:get_affiliation(stanza.attr.from) or "none" < valid_affiliations.admin then + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end +end); + +-- When an invite is sent; add an affiliation for the invitee +module:hook("muc-invite", function(event) + local room, stanza = event.room, event.stanza; + local invitee = stanza.attr.to; + if get_members_only(room) and not room:get_affiliation(invitee) then + local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from; + module:log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); + room:set_affiliation(from, invitee, "member", "Invited by " .. from); -- This might fail; ignore for now + end +end); + +return { + get = get_members_only; + set = set_members_only; +}; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 4f8aa15d..d03a32f3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -53,11 +53,6 @@ module:hook("muc-get-default-role", function(event) return "participant"; end end); -module:hook("muc-get-default-role", function(event) - if not event.affiliation and event.room:get_members_only() then - return false; - end -end); module:hook("muc-get-default-role", function(event) if not event.affiliation then return event.room:get_moderated() and "visitor" or "participant"; @@ -285,9 +280,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_members_only() and "muc_membersonly" or "muc_open"}):up(); -end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_persistent() and "muc_persistent" or "muc_temporary"}):up(); end); @@ -357,16 +349,6 @@ end function room_mt:get_moderated() return self._data.moderated; end -function room_mt:set_members_only(members_only) - members_only = members_only and true or nil; - if self._data.members_only ~= members_only then - self._data.members_only = members_only; - if self.save then self:save(true); end - end -end -function room_mt:get_members_only() - return self._data.members_only; -end function room_mt:set_persistent(persistent) persistent = persistent and true or nil; if self._data.persistent ~= persistent then @@ -409,17 +391,6 @@ module:hook("muc-room-pre-create", function(event) event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); end, -1); --- registration required for entering members-only room -module:hook("muc-occupant-pre-join", function(event) - local room, stanza = event.room, event.stanza; - local affiliation = room:get_affiliation(stanza.attr.from); - if affiliation == nil and event.room:get_members_only() then - local reply = st.error_reply(stanza, "auth", "registration-required"):up(); - reply.tags[1].attr.code = "407"; - event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; - end -end, -5); -- check if user is banned module:hook("muc-occupant-pre-join", function(event) @@ -732,14 +703,6 @@ module:hook("muc-config-form", function(event) value = event.room:get_moderated() }); end); -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_membersonly', - type = 'boolean', - label = 'Make Room Members-Only?', - value = event.room:get_members_only() - }); -end); function room_mt:process_form(origin, stanza) local form = stanza.tags[1]:get_child("x", "jabber:x:data"); @@ -787,9 +750,6 @@ end); module:hook("muc-config-submitted", function(event) event.update_option("moderated", "muc#roomconfig_moderatedroom"); end); -module:hook("muc-config-submitted", function(event) - event.update_option("members_only", "muc#roomconfig_membersonly"); -end); module:hook("muc-config-submitted", function(event) event.update_option("public", "muc#roomconfig_publicroom"); end); @@ -997,17 +957,6 @@ module:hook("muc-pre-invite", function(event) end end); --- Invitation privileges in members-only rooms SHOULD be restricted to room admins; --- if a member without privileges to edit the member list attempts to invite another user --- the service SHOULD return a error to the occupant -module:hook("muc-pre-invite", function(event) - local room, stanza = event.room, event.stanza; - if room:get_members_only() and valid_affiliations[room:get_affiliation(stanza.attr.from) or "none"] < valid_affiliations.admin then - event.origin.send(st.error_reply(stanza, "auth", "forbidden")); - return true; - end -end); - function room_mt:handle_mediated_invite(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); local invitee = jid_prep(payload.attr.to); @@ -1049,17 +998,6 @@ module:hook("muc-invite", function(event) :up(); end); --- When an invite is sent; add an affiliation for the invitee -module:hook("muc-invite", function(event) - local room, stanza = event.room, event.stanza; - local invitee = stanza.attr.to - if room:get_members_only() and not room:get_affiliation(invitee) then - local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from - log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); - room:set_affiliation(from, invitee, "member", "Invited by " .. from); -- This might fail; ignore for now - end -end); - function room_mt:handle_mediated_decline(origin, stanza) local payload = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline"); local declinee = jid_prep(payload.attr.to); @@ -1278,6 +1216,10 @@ local whois = module:require "muc/whois"; room_mt.get_whois = whois.get; room_mt.set_whois = whois.set; +local members_only = module:require "muc/members_only"; +room_mt.get_members_only = members_only.get; +room_mt.set_members_only = members_only.set; + local history = module:require "muc/history"; room_mt.send_history = history.send; room_mt.get_historylength = history.get_length; -- cgit v1.2.3 From 0f4dd8a1b5de82bef12ea4b4c5696188fd740be6 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 15 Apr 2014 17:06:04 -0400 Subject: plugins/muc: Move persistent room configuration to own module --- plugins/muc/mod_muc.lua | 8 ++++--- plugins/muc/muc.lib.lua | 29 ++++--------------------- plugins/muc/persistent.lib.lua | 49 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 plugins/muc/persistent.lib.lua diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 6f6094b4..865e07b9 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -26,6 +26,7 @@ end local muclib = module:require "muc"; local muc_new_room = muclib.new_room; +local persistent = module:require "muc/persistent"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; @@ -65,8 +66,9 @@ end local function room_save(room, forced) local node = jid_split(room.jid); - persistent_rooms[room.jid] = room._data.persistent; - if room._data.persistent then + local is_persistent = persistent.get(room); + persistent_rooms[room.jid] = is_persistent; + if is_persistent then local history = room._data.history; room._data.history = nil; local data = { @@ -138,7 +140,7 @@ end) module:hook("muc-occupant-left",function(event) local room = event.room - if not next(room._occupants) and not persistent_rooms[room.jid] then -- empty, non-persistent room + if not next(room._occupants) and not persistent.get(room) then -- empty, non-persistent room module:fire_event("muc-room-destroyed", { room = room }); end end); diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d03a32f3..87f3d69c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -280,9 +280,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_persistent() and "muc_persistent" or "muc_temporary"}):up(); -end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_hidden() and "muc_hidden" or "muc_public"}):up(); end); @@ -349,16 +346,6 @@ end function room_mt:get_moderated() return self._data.moderated; end -function room_mt:set_persistent(persistent) - persistent = persistent and true or nil; - if self._data.persistent ~= persistent then - self._data.persistent = persistent; - if self.save then self:save(true); end - end -end -function room_mt:get_persistent() - return self._data.persistent; -end function room_mt:set_hidden(hidden) hidden = hidden and true or nil; if self._data.hidden ~= hidden then @@ -671,14 +658,6 @@ function room_mt:get_form_layout(actor) }); return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; end -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_persistentroom', - type = 'boolean', - label = 'Make Room Persistent?', - value = event.room:get_persistent() - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_publicroom', @@ -744,9 +723,6 @@ function room_mt:process_form(origin, stanza) end return true; end -module:hook("muc-config-submitted", function(event) - event.update_option("persistent", "muc#roomconfig_persistentroom"); -end); module:hook("muc-config-submitted", function(event) event.update_option("moderated", "muc#roomconfig_moderatedroom"); end); @@ -780,7 +756,6 @@ function room_mt:destroy(newjid, reason, password) if password then x:tag("password"):text(password):up(); end x:up(); self:clear(x); - self:set_persistent(false); module:fire_event("muc-room-destroyed", { room = self }); end @@ -1220,6 +1195,10 @@ local members_only = module:require "muc/members_only"; room_mt.get_members_only = members_only.get; room_mt.set_members_only = members_only.set; +local persistent = module:require "muc/persistent"; +room_mt.get_persistent = persistent.get; +room_mt.set_persistent = persistent.set; + local history = module:require "muc/history"; room_mt.send_history = history.send; room_mt.get_historylength = history.get_length; diff --git a/plugins/muc/persistent.lib.lua b/plugins/muc/persistent.lib.lua new file mode 100644 index 00000000..ccf6fffe --- /dev/null +++ b/plugins/muc/persistent.lib.lua @@ -0,0 +1,49 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local function get_persistent(room) + return room._data.persistent; +end + +local function set_persistent(room, persistent) + persistent = persistent and true or nil; + if get_persistent(room) == persistent then return false; end + room._data.persistent = persistent; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_persistentroom"; + type = "boolean"; + label = "Make Room Persistent?"; + value = get_persistent(event.room); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_persistentroom"]; + if new ~= nil and set_persistent(event.room, new) then + event.status_codes["104"] = true; + end +end); + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_persistent(event.room) and "muc_persistent" or "muc_temporary"}):up(); +end); + +module:hook("muc-room-destroyed", function(event) + set_persistent(event.room, false); +end); + +return { + get = get_persistent; + set = set_persistent; +}; -- cgit v1.2.3 From bc3b5d98d53223906060c78c2a5dd284a7f1fa03 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 15 Apr 2014 18:20:56 -0400 Subject: plugins/muc/muc.lib: Refactor subject logic; fix bug of mixed up subject/author --- plugins/muc/muc.lib.lua | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 87f3d69c..782d4fe2 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -297,7 +297,7 @@ function room_mt:get_disco_items(stanza) end function room_mt:get_subject() - return self._data['subject'], self._data['subject_from'] + return self._data.subject_from, self._data.subject; end local function create_subject_message(from, subject) return st.message({from = from; type = "groupchat"}) @@ -878,7 +878,25 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) end end +module:hook("muc-subject-change", function(event) + local room, stanza = event.room, event.stanza; + local occupant = room:get_occupant_by_real_jid(stanza.attr.from); + if occupant.role == "moderator" or + ( occupant.role == "participant" and room:get_changesubject() ) then -- and participant + local subject = stanza:get_child_text("subject"); + room:set_subject(occupant.nick, subject); + else + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); + end + return true; +end); + function room_mt:handle_groupchat_to_room(origin, stanza) + -- Prosody has made the decision that messages with are exclusively subject changes + -- e.g. body will be ignored; even if the subject change was not allowed + if stanza:get_child("subject") then + return module:fire_event("muc-subject-change", {room = self, origin = origin, stanza = stanza}); + end local from = stanza.attr.from; local occupant = self:get_occupant_by_real_jid(from); if not occupant then -- not in room @@ -887,24 +905,11 @@ function room_mt:handle_groupchat_to_room(origin, stanza) elseif occupant.role == "visitor" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; - else - local from = stanza.attr.from; - stanza.attr.from = occupant.nick; - local subject = stanza:get_child_text("subject"); - if subject then - if occupant.role == "moderator" or - ( self:get_changesubject() and occupant.role == "participant" ) then -- and participant - self:set_subject(occupant.nick, subject); - else - stanza.attr.from = from; - origin.send(st.error_reply(stanza, "auth", "forbidden")); - end - else - self:broadcast_message(stanza); - end - stanza.attr.from = from; - return true; end + stanza.attr.from = occupant.nick; + self:broadcast_message(stanza); + stanza.attr.from = from; + return true; end -- hack - some buggy clients send presence updates to the room rather than their nick -- cgit v1.2.3 From 68f64d281702713a8a342807b6b4b51616f3d40d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 16 Apr 2014 13:54:51 -0400 Subject: plugins/muc: Move subject code to seperate module --- plugins/muc/muc.lib.lua | 68 ++++----------------------------- plugins/muc/subject.lib.lua | 93 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 61 deletions(-) create mode 100644 plugins/muc/subject.lib.lua diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 782d4fe2..26b2d335 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -296,28 +296,6 @@ function room_mt:get_disco_items(stanza) return reply; end -function room_mt:get_subject() - return self._data.subject_from, self._data.subject; -end -local function create_subject_message(from, subject) - return st.message({from = from; type = "groupchat"}) - :tag('subject'):text(subject):up(); -end -function room_mt:send_subject(to) - local msg = create_subject_message(self:get_subject()); - msg.attr.to = to; - self:route_stanza(msg); -end -function room_mt:set_subject(current_nick, subject) - if subject == "" then subject = nil; end - self._data['subject'] = subject; - self._data['subject_from'] = current_nick; - if self.save then self:save(); end - local msg = create_subject_message(current_nick, subject); - self:broadcast_message(msg); - return true; -end - function room_mt:handle_kickable(origin, stanza) local real_jid = stanza.attr.from; local occupant = self:get_occupant_by_real_jid(real_jid); @@ -362,16 +340,6 @@ end function room_mt:set_public(public) return self:set_hidden(not public); end -function room_mt:set_changesubject(changesubject) - changesubject = changesubject and true or nil; - if self._data.changesubject ~= changesubject then - self._data.changesubject = changesubject; - if self.save then self:save(true); end - end -end -function room_mt:get_changesubject() - return self._data.changesubject; -end -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) @@ -400,11 +368,6 @@ module:hook("muc-occupant-joined", function(event) end); end, 80); --- Send subject to joining user -module:hook("muc-occupant-joined", function(event) - event.room:send_subject(event.stanza.attr.from); -end, 20); - function room_mt:handle_presence_to_occupant(origin, stanza) local type = stanza.attr.type; if type == "error" then -- error, kick em out! @@ -666,14 +629,6 @@ module:hook("muc-config-form", function(event) value = not event.room:get_hidden() }); end); -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_changesubject', - type = 'boolean', - label = 'Allow Occupants to Change Subject?', - value = event.room:get_changesubject() - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_moderatedroom', @@ -729,9 +684,6 @@ end); module:hook("muc-config-submitted", function(event) event.update_option("public", "muc#roomconfig_publicroom"); end); -module:hook("muc-config-submitted", function(event) - event.update_option("changesubject", "muc#roomconfig_changesubject"); -end); -- Removes everyone from the room function room_mt:clear(x) @@ -878,19 +830,6 @@ function room_mt:handle_owner_query_set_to_room(origin, stanza) end end -module:hook("muc-subject-change", function(event) - local room, stanza = event.room, event.stanza; - local occupant = room:get_occupant_by_real_jid(stanza.attr.from); - if occupant.role == "moderator" or - ( occupant.role == "participant" and room:get_changesubject() ) then -- and participant - local subject = stanza:get_child_text("subject"); - room:set_subject(occupant.nick, subject); - else - event.origin.send(st.error_reply(stanza, "auth", "forbidden")); - end - return true; -end); - function room_mt:handle_groupchat_to_room(origin, stanza) -- Prosody has made the decision that messages with are exclusively subject changes -- e.g. body will be ignored; even if the subject change was not allowed @@ -1204,6 +1143,13 @@ local persistent = module:require "muc/persistent"; room_mt.get_persistent = persistent.get; room_mt.set_persistent = persistent.set; +local subject = module:require "muc/subject"; +room_mt.get_changesubject = subject.get_changesubject; +room_mt.set_changesubject = subject.set_changesubject; +room_mt.get_subject = subject.get; +room_mt.set_subject = subject.set; +room_mt.send_subject = subject.send; + local history = module:require "muc/history"; room_mt.send_history = history.send; room_mt.get_historylength = history.get_length; diff --git a/plugins/muc/subject.lib.lua b/plugins/muc/subject.lib.lua new file mode 100644 index 00000000..a42a18c5 --- /dev/null +++ b/plugins/muc/subject.lib.lua @@ -0,0 +1,93 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local st = require "util.stanza"; + +local function create_subject_message(from, subject) + return st.message({from = from; type = "groupchat"}) + :tag("subject"):text(subject):up(); +end + +local function get_changesubject(room) + return room._data.changesubject; +end + +local function set_changesubject(room, changesubject) + changesubject = changesubject and true or nil; + if get_changesubject(room) == changesubject then return false; end + room._data.changesubject = changesubject; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_changesubject"; + type = "boolean"; + label = "Allow Occupants to Change Subject?"; + value = get_changesubject(event.room); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_changesubject"]; + if new ~= nil and set_changesubject(event.room, new) then + event.status_codes["104"] = true; + end +end); + +local function get_subject(room) + return room._data.subject_from, room._data.subject; +end + +local function send_subject(room, to) + local msg = create_subject_message(get_subject(room)); + msg.attr.to = to; + room:route_stanza(msg); +end + +local function set_subject(room, from, subject) + if subject == "" then subject = nil; end + local old_from, old_subject = get_subject(room); + if old_subject == subject and old_from == from then return false; end + room._data.subject_from = from; + room._data.subject = subject; + if room.save then room:save(); end + local msg = create_subject_message(from, subject); + room:broadcast_message(msg); + return true; +end + +-- Send subject to joining user +module:hook("muc-occupant-joined", function(event) + event.room:send_subject(event.stanza.attr.from); +end, 20); + +-- Role check for subject changes +module:hook("muc-subject-change", function(event) + local room, stanza = event.room, event.stanza; + local occupant = room:get_occupant_by_real_jid(stanza.attr.from); + if occupant.role == "moderator" or + ( occupant.role == "participant" and get_changesubject(room) ) then -- and participant + local subject = stanza:get_child_text("subject"); + set_subject(room, occupant.nick, subject); + return true; + else + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end +end); + +return { + get_changesubject = get_changesubject; + set_changesubject = set_changesubject; + get = get_subject; + set = set_subject; + send = send_subject; +}; -- cgit v1.2.3 From aa2da859d066615a8382a7179b7c1896e2211998 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 16 Apr 2014 14:16:14 -0400 Subject: plugins/muc: Move 'hidden' ('public') code to own file --- plugins/muc/hidden.lib.lua | 45 +++++++++++++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 40 ++++++++++------------------------------ 2 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 plugins/muc/hidden.lib.lua diff --git a/plugins/muc/hidden.lib.lua b/plugins/muc/hidden.lib.lua new file mode 100644 index 00000000..5ce43db8 --- /dev/null +++ b/plugins/muc/hidden.lib.lua @@ -0,0 +1,45 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local function get_hidden(room) + return room._data.hidden; +end + +local function set_hidden(room, hidden) + hidden = hidden and true or nil; + if get_hidden(room) == hidden then return false; end + room._data.hidden = hidden; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_publicroom"; + type = "boolean"; + label = "Make Room Publicly Searchable?"; + value = not get_hidden(event.room); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_publicroom"]; + if new ~= nil and set_hidden(event.room, not new) then + event.status_codes["104"] = true; + end +end); + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_hidden(event.room) and "muc_hidden" or "muc_public"}):up(); +end); + +return { + get = get_hidden; + set = set_hidden; +}; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 26b2d335..fde6e17b 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -280,9 +280,6 @@ end); module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_hidden() and "muc_hidden" or "muc_public"}):up(); -end); module:hook("muc-disco#info", function(event) local count = iterators.count(event.room:each_occupant()); table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); @@ -324,22 +321,6 @@ end function room_mt:get_moderated() return self._data.moderated; end -function room_mt:set_hidden(hidden) - hidden = hidden and true or nil; - if self._data.hidden ~= hidden then - self._data.hidden = hidden; - if self.save then self:save(true); end - end -end -function room_mt:get_hidden() - return self._data.hidden; -end -function room_mt:get_public() - return not self:get_hidden(); -end -function room_mt:set_public(public) - return self:set_hidden(not public); -end -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) @@ -621,14 +602,6 @@ function room_mt:get_form_layout(actor) }); return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; end -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_publicroom', - type = 'boolean', - label = 'Make Room Publicly Searchable?', - value = not event.room:get_hidden() - }); -end); module:hook("muc-config-form", function(event) table.insert(event.form, { name = 'muc#roomconfig_moderatedroom', @@ -681,9 +654,6 @@ end module:hook("muc-config-submitted", function(event) event.update_option("moderated", "muc#roomconfig_moderatedroom"); end); -module:hook("muc-config-submitted", function(event) - event.update_option("public", "muc#roomconfig_publicroom"); -end); -- Removes everyone from the room function room_mt:clear(x) @@ -1127,6 +1097,16 @@ local description = module:require "muc/description"; room_mt.get_description = description.get; room_mt.set_description = description.set; +local hidden = module:require "muc/hidden"; +room_mt.get_hidden = hidden.get; +room_mt.set_hidden = hidden.set; +function room_mt:get_public() + return not self:get_hidden(); +end +function room_mt:set_public(public) + return self:set_hidden(not public); +end + local password = module:require "muc/password"; room_mt.get_password = password.get; room_mt.set_password = password.set; -- cgit v1.2.3 From 7c55b4a2e2bf8564efa21b5bf6815107d44364bc Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 18 Apr 2014 12:19:04 -0400 Subject: plugins/muc: Move 'moderated' code to seperate file; changes default "muc-get-default-role" behaviour --- plugins/muc/moderated.lib.lua | 53 +++++++++++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 37 ++++-------------------------- 2 files changed, 58 insertions(+), 32 deletions(-) create mode 100644 plugins/muc/moderated.lib.lua diff --git a/plugins/muc/moderated.lib.lua b/plugins/muc/moderated.lib.lua new file mode 100644 index 00000000..c375b7ad --- /dev/null +++ b/plugins/muc/moderated.lib.lua @@ -0,0 +1,53 @@ +-- Prosody IM +-- Copyright (C) 2008-2010 Matthew Wild +-- Copyright (C) 2008-2010 Waqas Hussain +-- Copyright (C) 2014 Daurnimator +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + +local function get_moderated(room) + return room._data.moderated; +end + +local function set_moderated(room, moderated) + moderated = moderated and true or nil; + if get_moderated(room) == moderated then return false; end + room._data.moderated = moderated; + if room.save then room:save(true); end + return true; +end + +module:hook("muc-disco#info", function(event) + event.reply:tag("feature", {var = get_moderated(event.room) and "muc_moderated" or "muc_unmoderated"}):up(); +end); + +module:hook("muc-config-form", function(event) + table.insert(event.form, { + name = "muc#roomconfig_moderatedroom"; + type = "boolean"; + label = "Make Room Moderated?"; + value = get_moderated(event.room); + }); +end); + +module:hook("muc-config-submitted", function(event) + local new = event.fields["muc#roomconfig_moderatedroom"]; + if new ~= nil and set_moderated(event.room, new) then + event.status_codes["104"] = true; + end +end); + +module:hook("muc-get-default-role", function(event) + if event.affiliation == nil then + if get_moderated(event.room) then + return "visitor" + end + end +end, 1); + +return { + get = get_moderated; + set = set_moderated; +}; diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index fde6e17b..53f91b83 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -49,15 +49,10 @@ end module:hook("muc-get-default-role", function(event) if event.affiliation_rank >= valid_affiliations.admin then return "moderator"; - elseif event.affiliation_rank >= valid_affiliations.member then + elseif event.affiliation_rank >= valid_affiliations.none then return "participant"; end end); -module:hook("muc-get-default-role", function(event) - if not event.affiliation then - return event.room:get_moderated() and "visitor" or "participant"; - end -end, -1); --- Occupant functions function room_mt:new_occupant(bare_real_jid, nick) @@ -277,9 +272,6 @@ end module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = "http://jabber.org/protocol/muc"}):up(); end); -module:hook("muc-disco#info", function(event) - event.reply:tag("feature", {var = event.room:get_moderated() and "muc_moderated" or "muc_unmoderated"}):up(); -end); module:hook("muc-disco#info", function(event) local count = iterators.count(event.room:each_occupant()); table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants", value = tostring(count) }); @@ -311,23 +303,11 @@ function room_mt:handle_kickable(origin, stanza) return true; end -function room_mt:set_moderated(moderated) - moderated = moderated and true or nil; - if self._data.moderated ~= moderated then - self._data.moderated = moderated; - if self.save then self:save(true); end - end -end -function room_mt:get_moderated() - return self._data.moderated; -end - -- Give the room creator owner affiliation module:hook("muc-room-pre-create", function(event) event.room:set_affiliation(true, jid_bare(event.stanza.attr.from), "owner"); end, -1); - -- check if user is banned module:hook("muc-occupant-pre-join", function(event) local room, stanza = event.room, event.stanza; @@ -602,14 +582,6 @@ function room_mt:get_form_layout(actor) }); return module:fire_event("muc-config-form", { room = self, actor = actor, form = form }) or form; end -module:hook("muc-config-form", function(event) - table.insert(event.form, { - name = 'muc#roomconfig_moderatedroom', - type = 'boolean', - label = 'Make Room Moderated?', - value = event.room:get_moderated() - }); -end); function room_mt:process_form(origin, stanza) local form = stanza.tags[1]:get_child("x", "jabber:x:data"); @@ -651,9 +623,6 @@ function room_mt:process_form(origin, stanza) end return true; end -module:hook("muc-config-submitted", function(event) - event.update_option("moderated", "muc#roomconfig_moderatedroom"); -end); -- Removes everyone from the room function room_mt:clear(x) @@ -1119,6 +1088,10 @@ local members_only = module:require "muc/members_only"; room_mt.get_members_only = members_only.get; room_mt.set_members_only = members_only.set; +local moderated = module:require "muc/moderated"; +room_mt.get_moderated = moderated.get; +room_mt.set_moderated = moderated.set; + local persistent = module:require "muc/persistent"; room_mt.get_persistent = persistent.get; room_mt.set_persistent = persistent.set; -- cgit v1.2.3 From 544685b84c9618705ba70bb0c1a2ebb5258d67b2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 18 Apr 2014 12:19:33 -0400 Subject: plugins/muc/muc.lib: When user leaves; set their role to nil --- plugins/muc/muc.lib.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 53f91b83..61520354 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -405,6 +405,7 @@ function room_mt:handle_presence_to_occupant(origin, stanza) local dest_nick; if dest_occupant == nil then -- Session is leaving log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); + orig_occupant.role = nil; orig_occupant:set_session(real_jid, stanza); else log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick); -- cgit v1.2.3 From 70a87e5d5e5fe24252d03a5263243c19745ea72d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 18 Apr 2014 12:20:07 -0400 Subject: plugins/muc/subject.lib: If subject is not set by an occupant, it should come from room jid itself --- plugins/muc/subject.lib.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/muc/subject.lib.lua b/plugins/muc/subject.lib.lua index a42a18c5..44fc915c 100644 --- a/plugins/muc/subject.lib.lua +++ b/plugins/muc/subject.lib.lua @@ -43,7 +43,8 @@ module:hook("muc-config-submitted", function(event) end); local function get_subject(room) - return room._data.subject_from, room._data.subject; + -- a stanza from the room JID (or from the occupant JID of the entity that set the subject) + return room._data.subject_from or room.jid, room._data.subject; end local function send_subject(room, to) @@ -66,7 +67,7 @@ end -- Send subject to joining user module:hook("muc-occupant-joined", function(event) - event.room:send_subject(event.stanza.attr.from); + send_subject(event.room, event.stanza.attr.from); end, 20); -- Role check for subject changes -- cgit v1.2.3 From 126eb3e5b9ea0372a264ddff6b4be37bd2029529 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 21 Apr 2014 17:39:18 -0400 Subject: plugins/muc/muc.lib: Remove reversed conditionals when firing pre- events --- plugins/muc/muc.lib.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 61520354..2e48ade1 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -822,7 +822,7 @@ function room_mt:handle_mediated_invite(origin, stanza) if not invitee then origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); return true; - elseif not module:fire_event("muc-pre-invite", {room = self, origin = origin, stanza = stanza}) then + elseif module:fire_event("muc-pre-invite", {room = self, origin = origin, stanza = stanza}) then return true; end local invite = st.message({from = self.jid, to = invitee, id = stanza.attr.id}) @@ -863,7 +863,7 @@ function room_mt:handle_mediated_decline(origin, stanza) if not declinee then origin.send(st.error_reply(stanza, "cancel", "jid-malformed")); return true; - elseif not module:fire_event("muc-pre-decline", {room = self, origin = origin, stanza = stanza}) then + elseif module:fire_event("muc-pre-decline", {room = self, origin = origin, stanza = stanza}) then return true; end local decline = st.message({from = self.jid, to = declinee, id = stanza.attr.id}) -- cgit v1.2.3 From 9592b3a68c66c5d2ea10ea04ce3f47041cf2921d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 21 Apr 2014 17:49:57 -0400 Subject: plugins/muc/members_only.lib: Compare affiliations via rank; wrap some long lines --- plugins/muc/members_only.lib.lua | 48 ++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/plugins/muc/members_only.lib.lua b/plugins/muc/members_only.lib.lua index 84a17699..b0999f0b 100644 --- a/plugins/muc/members_only.lib.lua +++ b/plugins/muc/members_only.lib.lua @@ -51,13 +51,16 @@ end); -- registration required for entering members-only room module:hook("muc-occupant-pre-join", function(event) - local room, stanza = event.room, event.stanza; - local affiliation = room:get_affiliation(stanza.attr.from); - if affiliation == nil and get_members_only(event.room) then - local reply = st.error_reply(stanza, "auth", "registration-required"):up(); - reply.tags[1].attr.code = "407"; - event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); - return true; + local room = event.room; + if get_members_only(room) then + local stanza = event.stanza; + local affiliation = room:get_affiliation(stanza.attr.from); + if valid_affiliations[affiliation or "none"] <= valid_affiliations.none then + local reply = st.error_reply(stanza, "auth", "registration-required"):up(); + reply.tags[1].attr.code = "407"; + event.origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"})); + return true; + end end end, -5); @@ -65,21 +68,32 @@ end, -5); -- if a member without privileges to edit the member list attempts to invite another user -- the service SHOULD return a error to the occupant module:hook("muc-pre-invite", function(event) - local room, stanza = event.room, event.stanza; - if get_members_only(room) and room:get_affiliation(stanza.attr.from) or "none" < valid_affiliations.admin then - event.origin.send(st.error_reply(stanza, "auth", "forbidden")); - return true; + local room = event.room; + if get_members_only(room) then + local stanza = event.stanza; + local affiliation = room:get_affiliation(stanza.attr.from); + if valid_affiliations[affiliation or "none"] < valid_affiliations.admin then + event.origin.send(st.error_reply(stanza, "auth", "forbidden")); + return true; + end end end); -- When an invite is sent; add an affiliation for the invitee module:hook("muc-invite", function(event) - local room, stanza = event.room, event.stanza; - local invitee = stanza.attr.to; - if get_members_only(room) and not room:get_affiliation(invitee) then - local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite").attr.from; - module:log("debug", "%s invited %s into members only room %s, granting membership", from, invitee, room.jid); - room:set_affiliation(from, invitee, "member", "Invited by " .. from); -- This might fail; ignore for now + local room = event.room; + if get_members_only(room) then + local stanza = event.stanza; + local invitee = stanza.attr.to; + local affiliation = room:get_affiliation(invitee); + if valid_affiliations[affiliation or "none"] <= valid_affiliations.none then + local from = stanza:get_child("x", "http://jabber.org/protocol/muc#user") + :get_child("invite").attr.from; + module:log("debug", "%s invited %s into members only room %s, granting membership", + from, invitee, room.jid); + -- This might fail; ignore for now + room:set_affiliation(from, invitee, "member", "Invited by " .. from); + end end end); -- cgit v1.2.3 From 09fa17a7efd4502c7fdf21ec2492946bb59239e3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 21 Apr 2014 17:51:32 -0400 Subject: plugins/muc/muc.lib: Move sending of occupant list to joining user out of hook, and into main flow: It has to occur before publication of their status --- plugins/muc/history.lib.lua | 2 +- plugins/muc/muc.lib.lua | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) 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 9c5384c1f1343bf2b8c60623f264869f4f1a0f07 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 002467dc48dba878613d7aa9872217de0812a149 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 28 Apr 2014 16:30:53 -0400 Subject: plugins/muc/mod_muc: Move affiliation monkey patch into own scope --- plugins/muc/mod_muc.lua | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 865e07b9..2aa55a37 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -26,6 +26,7 @@ end local muclib = module:require "muc"; local muc_new_room = muclib.new_room; +room_mt = muclib.room_mt; -- Yes, global. local persistent = module:require "muc/persistent"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; @@ -52,16 +53,18 @@ local function is_admin(jid) return um_is_admin(jid, module.host); end -room_mt = muclib.room_mt; -- Yes, global. -local _set_affiliation = room_mt.set_affiliation; -local _get_affiliation = room_mt.get_affiliation; -function muclib.room_mt:get_affiliation(jid) - if is_admin(jid) then return "owner"; end - return _get_affiliation(self, jid); -end -function muclib.room_mt:set_affiliation(actor, jid, affiliation, callback, reason) - if is_admin(jid) then return nil, "modify", "not-acceptable"; end - return _set_affiliation(self, actor, jid, affiliation, callback, reason); +do -- Monkey patch to make server admins room owners + local _get_affiliation = room_mt.get_affiliation; + function room_mt:get_affiliation(jid) + if is_admin(jid) then return "owner"; end + return _get_affiliation(self, jid); + end + + local _set_affiliation = room_mt.set_affiliation; + function room_mt:set_affiliation(actor, jid, ...) + if is_admin(jid) then return nil, "modify", "not-acceptable"; end + return _set_affiliation(self, actor, jid, ...); + end end local function room_save(room, forced) -- cgit v1.2.3 From c2b74c312945d2afbaaf75a1ba6e24dc4bb0fe02 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 28 Apr 2014 16:33:54 -0400 Subject: plugins/muc/mod_muc: Use get_option_string instead of get_option and checking --- plugins/muc/mod_muc.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 2aa55a37..8a7ae9c7 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -13,8 +13,6 @@ if module:get_host_type() ~= "component" then end local muc_host = module:get_host(); -local muc_name = module:get_option("name"); -if type(muc_name) ~= "string" then muc_name = "Prosody Chatrooms"; end local restrict_room_creation = module:get_option("restrict_room_creation"); if restrict_room_creation then if restrict_room_creation == true then @@ -44,7 +42,7 @@ local room_configs = module:open_store("config"); muclib.set_max_history_length(module:get_option_number("max_history_messages")); module:depends("disco"); -module:add_identity("conference", "text", muc_name); +module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms")); module:add_feature("http://jabber.org/protocol/muc"); module:depends "muc_unique" module:require "muc/lock"; -- cgit v1.2.3 From c34f4b0ae3b32f17ad4ccffbf6356d593e4128d2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 28 Apr 2014 16:31:21 -0400 Subject: plugins/muc/mod_muc: No need to treat the host room specially --- plugins/muc/mod_muc.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 8a7ae9c7..ecc136f1 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -12,7 +12,6 @@ if module:get_host_type() ~= "component" then error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0); end -local muc_host = module:get_host(); local restrict_room_creation = module:get_option("restrict_room_creation"); if restrict_room_creation then if restrict_room_creation == true then @@ -120,10 +119,6 @@ for jid in pairs(persistent_rooms) do end if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end -local host_room = muc_new_room(muc_host); -host_room.save = room_save; -rooms[muc_host] = host_room; - module:hook("host-disco-items", function(event) local reply = event.reply; module:log("debug", "host-disco-items called"); @@ -227,7 +222,6 @@ function shutdown_component() for roomjid, room in pairs(rooms) do room:clear(x); end - host_room:clear(x); end end module.unload = shutdown_component; -- cgit v1.2.3 From 51e7021e0ce200dc4dd2b8e2d5b91b4b9625c945 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 12:54:04 -0400 Subject: plugins/muc: Add room:has_occupant() method --- plugins/muc/mod_muc.lua | 2 +- plugins/muc/muc.lib.lua | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index ecc136f1..f0458e16 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -136,7 +136,7 @@ end) module:hook("muc-occupant-left",function(event) local room = event.room - if not next(room._occupants) and not persistent.get(room) then -- empty, non-persistent room + if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room module:fire_event("muc-room-destroyed", { room = room }); end end); diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 6aca5c62..7c039ce3 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -79,6 +79,10 @@ do end end +function room_mt:has_occupant() + return next(self._occupants, nil) ~= nil +end + function room_mt:get_occupant_by_real_jid(real_jid) local occupant_jid = self:get_occupant_jid(real_jid); if occupant_jid == nil then return nil end -- cgit v1.2.3 From 0c0b9b11b7fb62219cf1e6d56bcb97af14d99430 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 14:24:50 -0400 Subject: plugins/muc/mod_muc: Move room persistence to own block --- plugins/muc/mod_muc.lua | 104 ++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index f0458e16..3262905e 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -24,7 +24,6 @@ end local muclib = module:require "muc"; local muc_new_room = muclib.new_room; room_mt = muclib.room_mt; -- Yes, global. -local persistent = module:require "muc/persistent"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; @@ -33,9 +32,6 @@ local hosts = prosody.hosts; rooms = {}; local rooms = rooms; -local persistent_rooms_storage = module:open_store("persistent"); -local persistent_rooms = persistent_rooms_storage:get() or {}; -local room_configs = module:open_store("config"); -- Configurable options muclib.set_max_history_length(module:get_option_number("max_history_messages")); @@ -64,32 +60,8 @@ do -- Monkey patch to make server admins room owners end end -local function room_save(room, forced) - local node = jid_split(room.jid); - local is_persistent = persistent.get(room); - persistent_rooms[room.jid] = is_persistent; - if is_persistent then - local history = room._data.history; - room._data.history = nil; - local data = { - jid = room.jid; - _data = room._data; - _affiliations = room._affiliations; - }; - room_configs:set(node, data); - room._data.history = history; - elseif forced then - room_configs:set(node, nil); - if not next(room._occupants) then -- Room empty - rooms[room.jid] = nil; - end - end - if forced then persistent_rooms_storage:set(nil, persistent_rooms); end -end - function create_room(jid) local room = muc_new_room(jid); - room.save = room_save; rooms[jid] = room; module:fire_event("muc-room-created", { room = room }); return room; @@ -103,21 +75,64 @@ function get_room_from_jid(room_jid) return rooms[room_jid] end -local persistent_errors = false; -for jid in pairs(persistent_rooms) do - local node = jid_split(jid); - local data = room_configs:get(node); - if data then - local room = create_room(jid); - room._data = data._data; - room._affiliations = data._affiliations; - else -- missing room data - persistent_rooms[jid] = nil; - module:log("error", "Missing data for room '%s', removing from persistent room list", jid); - persistent_errors = true; +do -- Persistent rooms + local persistent = module:require "muc/persistent"; + local persistent_rooms_storage = module:open_store("persistent"); + local persistent_rooms = persistent_rooms_storage:get() or {}; + local room_configs = module:open_store("config"); + + local function room_save(room, forced) + local node = jid_split(room.jid); + local is_persistent = persistent.get(room); + persistent_rooms[room.jid] = is_persistent; + if is_persistent then + local history = room._data.history; + room._data.history = nil; + local data = { + jid = room.jid; + _data = room._data; + _affiliations = room._affiliations; + }; + room_configs:set(node, data); + room._data.history = history; + elseif forced then + room_configs:set(node, nil); + if not next(room._occupants) then -- Room empty + rooms[room.jid] = nil; + end + end + if forced then persistent_rooms_storage:set(nil, persistent_rooms); end end + + -- When room is created, over-ride 'save' method + module:hook("muc-room-created", function(event) + event.room.save = room_save; + end); + + -- Automatically destroy empty non-persistent rooms + module:hook("muc-occupant-left",function(event) + local room = event.room + if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room + module:fire_event("muc-room-destroyed", { room = room }); + end + end); + + local persistent_errors = false; + for jid in pairs(persistent_rooms) do + local node = jid_split(jid); + local data = room_configs:get(node); + if data then + local room = create_room(jid); + room._data = data._data; + room._affiliations = data._affiliations; + else -- missing room data + persistent_rooms[jid] = nil; + module:log("error", "Missing data for room '%s', removing from persistent room list", jid); + persistent_errors = true; + end + end + if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end end -if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end module:hook("host-disco-items", function(event) local reply = event.reply; @@ -134,13 +149,6 @@ module:hook("muc-room-destroyed",function(event) forget_room(room.jid) end) -module:hook("muc-occupant-left",function(event) - local room = event.room - if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room - module:fire_event("muc-room-destroyed", { room = room }); - end -end); - -- Watch presence to create rooms local function attempt_room_creation(event) local origin, stanza = event.origin, event.stanza; -- cgit v1.2.3 From 0de9e9485c7f6018acf7b4413126ffb0f86f5da2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 14:50:13 -0400 Subject: plugins/muc/mod_muc: Remove support for `host_session.muc` --- plugins/muc/mod_muc.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 3262905e..d7d3ac37 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -205,8 +205,6 @@ for event_name, method in pairs { end, -2) end -hosts[module:get_host()].muc = { rooms = rooms }; - local saved = false; module.save = function() saved = true; @@ -220,7 +218,6 @@ module.restore = function(data) room._data = oldroom._data; room._affiliations = oldroom._affiliations; end - hosts[module:get_host()].muc = { rooms = rooms }; end function shutdown_component() -- cgit v1.2.3 From 940094d4fa9737ad53cfaa3f667a05bae520f98c Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 15:13:06 -0400 Subject: plugins/muc: Move 'module:get_option_number("max_history_messages")' from mod_muc into history lib; remove from muclib exports --- plugins/muc/history.lib.lua | 3 ++- plugins/muc/mod_muc.lua | 2 -- plugins/muc/muc.lib.lua | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/muc/history.lib.lua b/plugins/muc/history.lib.lua index 596f92da..1c14cf84 100644 --- a/plugins/muc/history.lib.lua +++ b/plugins/muc/history.lib.lua @@ -11,7 +11,8 @@ local gettime = os.time; local datetime = require "util.datetime"; local st = require "util.stanza"; -local default_history_length, max_history_length = 20, math.huge; +local default_history_length = 20; +local max_history_length = module:get_option_number("max_history_messages", math.huge); local function set_max_history_length(_max_history_length) max_history_length = _max_history_length or math.huge; diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index d7d3ac37..91b69829 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -33,8 +33,6 @@ local hosts = prosody.hosts; rooms = {}; local rooms = rooms; --- Configurable options -muclib.set_max_history_length(module:get_option_number("max_history_messages")); module:depends("disco"); module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms")); diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 7c039ce3..27d738de 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1114,8 +1114,6 @@ room_mt.set_historylength = history.set_length; local _M = {}; -- module "muc" -_M.set_max_history_length = history.set_max_length; - function _M.new_room(jid, config) return setmetatable({ jid = jid; -- cgit v1.2.3 From bbd26576162f33990375f01db262a90d4121f091 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 17:18:14 -0400 Subject: plugins/muc/mod_muc: Use module:shared instead of save/restore --- plugins/muc/mod_muc.lua | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 91b69829..751491c2 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -30,9 +30,8 @@ local st = require "util.stanza"; local um_is_admin = require "core.usermanager".is_admin; local hosts = prosody.hosts; -rooms = {}; -local rooms = rooms; - +local rooms = module:shared "rooms"; +_G.rooms = rooms; module:depends("disco"); module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms")); @@ -203,31 +202,13 @@ for event_name, method in pairs { end, -2) end -local saved = false; -module.save = function() - saved = true; - return {rooms = rooms}; -end -module.restore = function(data) - for jid, oldroom in pairs(data.rooms or {}) do - local room = create_room(jid); - room._jid_nick = oldroom._jid_nick; - room._occupants = oldroom._occupants; - room._data = oldroom._data; - room._affiliations = oldroom._affiliations; - end -end - function shutdown_component() - if not saved then - local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) - :tag("status", { code = "332"}):up(); - for roomjid, room in pairs(rooms) do - room:clear(x); - end + local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + :tag("status", { code = "332"}):up(); + for roomjid, room in pairs(rooms) do + room:clear(x); end end -module.unload = shutdown_component; module:hook_global("server-stopping", shutdown_component); -- Ad-hoc commands -- cgit v1.2.3 From 202f300c96a891adbd5b1cceaf68bff73161faff Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 18:50:30 -0400 Subject: plugins/muc/lock.lib: lock inside of pre-create instead of 'created' --- plugins/muc/lock.lib.lua | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/plugins/muc/lock.lib.lua b/plugins/muc/lock.lib.lua index 7cf19be3..319a6973 100644 --- a/plugins/muc/lock.lib.lua +++ b/plugins/muc/lock.lib.lua @@ -23,7 +23,10 @@ local function is_locked(room) end if lock_rooms then - module:hook("muc-room-created", function(event) + module:hook("muc-room-pre-create", function(event) + -- Older groupchat protocol doesn't lock + if not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then return end + -- Lock room at creation local room = event.room; lock(room); if lock_room_timeout and lock_room_timeout > 0 then @@ -33,16 +36,9 @@ if lock_rooms then end end); end - end); + end, 10); end --- Older groupchat protocol doesn't lock -module:hook("muc-room-pre-create", function(event) - if is_locked(event.room) and not event.stanza:get_child("x", "http://jabber.org/protocol/muc") then - unlock(event.room); - end -end, 10); - -- Don't let users into room while it is locked module:hook("muc-occupant-pre-join", function(event) if not event.is_new_room and is_locked(event.room) then -- Deny entry -- cgit v1.2.3 From c73eccaaac9d389cf63c68d57c66aa8b51c97e63 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 19:00:45 -0400 Subject: plugins/muc/mod_muc: Move `restrict_room_creation` into own area. now uses pre-create hook --- plugins/muc/mod_muc.lua | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 751491c2..0940bc43 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -12,15 +12,6 @@ if module:get_host_type() ~= "component" then error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0); end -local restrict_room_creation = module:get_option("restrict_room_creation"); -if restrict_room_creation then - if restrict_room_creation == true then - restrict_room_creation = "admin"; - elseif restrict_room_creation ~= "admin" and restrict_room_creation ~= "local" then - restrict_room_creation = nil; - end -end - local muclib = module:require "muc"; local muc_new_room = muclib.new_room; room_mt = muclib.room_mt; -- Yes, global. @@ -146,20 +137,31 @@ module:hook("muc-room-destroyed",function(event) forget_room(room.jid) end) +do + local restrict_room_creation = module:get_option("restrict_room_creation"); + if restrict_room_creation == true then + restrict_room_creation = "admin"; + end + if restrict_room_creation then + local host_suffix = module.host:gsub("^[^%.]+%.", ""); + module:hook("muc-room-pre-create", function(event) + local user_jid = event.stanza.attr.from; + if not is_admin(user_jid) and not ( + restrict_room_creation == "local" and + select(2, jid_split(user_jid)) == host_suffix + ) then + origin.send(st.error_reply(stanza, "cancel", "not-allowed")); + return true; + end + end); + end +end + -- Watch presence to create rooms local function attempt_room_creation(event) local origin, stanza = event.origin, event.stanza; local room_jid = jid_bare(stanza.attr.to); - if stanza.attr.type == nil and - get_room_from_jid(room_jid) == nil and - ( - not(restrict_room_creation) or - is_admin(stanza.attr.from) or - ( - restrict_room_creation == "local" and - select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "") - ) - ) then + if stanza.attr.type == nil and get_room_from_jid(room_jid) == nil then create_room(room_jid); end end -- cgit v1.2.3 From 63e9b9a510218fe91990a3fcaf1e62b74415c409 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 29 Apr 2014 19:35:25 -0400 Subject: plugins/muc/mod_muc: Remove attempt_room_creation and create_room function. Instead have a 'track_room' function called from the end of the pre-create hook, and just create an un-tracked room object when we get a presence --- plugins/muc/mod_muc.lua | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 0940bc43..519b1e34 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -13,7 +13,6 @@ if module:get_host_type() ~= "component" then end local muclib = module:require "muc"; -local muc_new_room = muclib.new_room; room_mt = muclib.room_mt; -- Yes, global. local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; @@ -48,11 +47,8 @@ do -- Monkey patch to make server admins room owners end end -function create_room(jid) - local room = muc_new_room(jid); - rooms[jid] = room; - module:fire_event("muc-room-created", { room = room }); - return room; +function track_room(room) + rooms[room.jid] = room; end function forget_room(jid) @@ -93,9 +89,9 @@ do -- Persistent rooms end -- When room is created, over-ride 'save' method - module:hook("muc-room-created", function(event) + module:hook("muc-occupant-pre-create", function(event) event.room.save = room_save; - end); + end, 1000); -- Automatically destroy empty non-persistent rooms module:hook("muc-occupant-left",function(event) @@ -110,9 +106,10 @@ do -- Persistent rooms local node = jid_split(jid); local data = room_configs:get(node); if data then - local room = create_room(jid); + local room = muclib.new_room(jid); room._data = data._data; room._affiliations = data._affiliations; + track_room(room); else -- missing room data persistent_rooms[jid] = nil; module:log("error", "Missing data for room '%s', removing from persistent room list", jid); @@ -132,6 +129,10 @@ module:hook("host-disco-items", function(event) end end); +module:hook("muc-room-pre-create", function(event) + track_room(event.room); +end, -1000); + module:hook("muc-room-destroyed",function(event) local room = event.room forget_room(room.jid) @@ -157,18 +158,6 @@ do end end --- Watch presence to create rooms -local function attempt_room_creation(event) - local origin, stanza = event.origin, event.stanza; - local room_jid = jid_bare(stanza.attr.to); - if stanza.attr.type == nil and get_room_from_jid(room_jid) == nil then - create_room(room_jid); - end -end -module:hook("presence/full", attempt_room_creation, -1) -module:hook("presence/bare", attempt_room_creation, -1) -module:hook("presence/host", attempt_room_creation, -1) - for event_name, method in pairs { -- Normal room interactions ["iq-get/bare/http://jabber.org/protocol/disco#info:query"] = "handle_disco_info_get_query" ; @@ -195,10 +184,16 @@ for event_name, method in pairs { } do module:hook(event_name, function (event) local origin, stanza = event.origin, event.stanza; - local room = get_room_from_jid(jid_bare(stanza.attr.to)) + local room_jid = jid_bare(stanza.attr.to); + local room = get_room_from_jid(room_jid); if room == nil then - origin.send(st.error_reply(stanza, "cancel", "not-allowed")); - return true; + -- Watch presence to create rooms + if stanza.attr.type == nil and stanza.name == "presence" then + room = muclib.new_room(room_jid); + else + origin.send(st.error_reply(stanza, "cancel", "not-allowed")); + return true; + end end return room[method](room, origin, stanza); end, -2) -- cgit v1.2.3 From 2c6ed6291abaae98e7afcf8f27ef16f013215c48 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 30 Apr 2014 13:12:32 -0400 Subject: plugins/muc/mod_muc.lua: Add "each_room" function to iterate over rooms (instead of accessing directly) --- plugins/muc/mod_muc.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 519b1e34..5ad86025 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -14,6 +14,7 @@ end local muclib = module:require "muc"; room_mt = muclib.room_mt; -- Yes, global. +local iterators = require "util.iterators"; local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; @@ -59,6 +60,10 @@ function get_room_from_jid(room_jid) return rooms[room_jid] end +function each_room() + return iterators.values(rooms); +end + do -- Persistent rooms local persistent = module:require "muc/persistent"; local persistent_rooms_storage = module:open_store("persistent"); @@ -122,9 +127,9 @@ end module:hook("host-disco-items", function(event) local reply = event.reply; module:log("debug", "host-disco-items called"); - for jid, room in pairs(rooms) do + for room in each_room() do if not room:get_hidden() then - reply:tag("item", {jid=jid, name=room:get_name()}):up(); + reply:tag("item", {jid=room.jid, name=room:get_name()}):up(); end end end); @@ -202,7 +207,7 @@ end function shutdown_component() local x = st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("status", { code = "332"}):up(); - for roomjid, room in pairs(rooms) do + for room in each_room() do room:clear(x); end end @@ -211,7 +216,6 @@ module:hook_global("server-stopping", shutdown_component); -- Ad-hoc commands module:depends("adhoc") local t_concat = table.concat; -local keys = require "util.iterators".keys; local adhoc_new = module:require "adhoc".new; local adhoc_initial = require "util.adhoc".new_initial_data_form; local dataforms_new = require "util.dataforms".new; @@ -225,7 +229,7 @@ local destroy_rooms_layout = dataforms_new { }; local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function() - return { rooms = array.collect(keys(rooms)):sort() }; + return { rooms = array.collect(each_room):pluck("jid"):sort(); }; end, function(fields, errors) if errors then local errmsg = {}; -- cgit v1.2.3 From e6e40efd2fedc95dd8882fd7e4405a5fb01bdfc3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 30 Apr 2014 12:43:04 -0400 Subject: plugins/muc/mod_muc: Don't use rooms object directory in adhoc section --- plugins/muc/mod_muc.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 5ad86025..9f022b3a 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -239,8 +239,7 @@ end, function(fields, errors) return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; end for _, room in ipairs(fields.rooms) do - rooms[room]:destroy(); - rooms[room] = nil; + get_room_from_jid(room):destroy(); end return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") }; end); -- cgit v1.2.3 From ab23352301da2beecdd00a8ab1d3d5dc189cfd24 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 30 Apr 2014 12:43:47 -0400 Subject: plugins/muc/mod_muc: Place adhoc section into own scope --- plugins/muc/mod_muc.lua | 66 ++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 9f022b3a..79123ab5 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -6,8 +6,6 @@ -- COPYING file in the source package for more information. -- -local array = require "util.array"; - if module:get_host_type() ~= "component" then error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0); end @@ -213,36 +211,38 @@ function shutdown_component() end module:hook_global("server-stopping", shutdown_component); --- Ad-hoc commands -module:depends("adhoc") -local t_concat = table.concat; -local adhoc_new = module:require "adhoc".new; -local adhoc_initial = require "util.adhoc".new_initial_data_form; -local dataforms_new = require "util.dataforms".new; - -local destroy_rooms_layout = dataforms_new { - title = "Destroy rooms"; - instructions = "Select the rooms to destroy"; - - { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#destroy" }; - { name = "rooms", type = "list-multi", required = true, label = "Rooms to destroy:"}; -}; - -local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function() - return { rooms = array.collect(each_room):pluck("jid"):sort(); }; -end, function(fields, errors) - if errors then - local errmsg = {}; - for name, err in pairs(errors) do - errmsg[#errmsg + 1] = name .. ": " .. err; +do -- Ad-hoc commands + module:depends "adhoc"; + local t_concat = table.concat; + local adhoc_new = module:require "adhoc".new; + local adhoc_initial = require "util.adhoc".new_initial_data_form; + local array = require "util.array"; + local dataforms_new = require "util.dataforms".new; + + local destroy_rooms_layout = dataforms_new { + title = "Destroy rooms"; + instructions = "Select the rooms to destroy"; + + { name = "FORM_TYPE", type = "hidden", value = "http://prosody.im/protocol/muc#destroy" }; + { name = "rooms", type = "list-multi", required = true, label = "Rooms to destroy:"}; + }; + + local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function() + return { rooms = array.collect(each_room):pluck("jid"):sort(); }; + end, function(fields, errors) + if errors then + local errmsg = {}; + for name, err in pairs(errors) do + errmsg[#errmsg + 1] = name .. ": " .. err; + end + return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; end - return { status = "completed", error = { message = t_concat(errmsg, "\n") } }; - end - for _, room in ipairs(fields.rooms) do - get_room_from_jid(room):destroy(); - end - return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") }; -end); -local destroy_rooms_desc = adhoc_new("Destroy Rooms", "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin"); + for _, room in ipairs(fields.rooms) do + get_room_from_jid(room):destroy(); + end + return { status = "completed", info = "The following rooms were destroyed:\n"..t_concat(fields.rooms, "\n") }; + end); + local destroy_rooms_desc = adhoc_new("Destroy Rooms", "http://prosody.im/protocol/muc#destroy", destroy_rooms_handler, "admin"); -module:provides("adhoc", destroy_rooms_desc); + module:provides("adhoc", destroy_rooms_desc); +end -- cgit v1.2.3 From c83e3a24ae55f509d17571a90f7397b1274808ff Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sat, 10 May 2014 21:30:00 +0100 Subject: mod_muc/muc.lib: Fix parameters to send_history() (thanks Peter Villeneuve) --- plugins/muc/muc.lib.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5debb4a3..f1e42d36 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -238,11 +238,11 @@ module:hook("muc-get-history", function(event) return true; end) -function room_mt:send_history(stanza) +function room_mt:send_history(to, stanza) local maxchars, maxstanzas, since = parse_history(stanza) local event = { room = self; - to = stanza.attr.from; -- `to` is required to calculate the character count for `maxchars` + to = to; -- `to` is required to calculate the character count for `maxchars` maxchars = maxchars, maxstanzas = maxstanzas, since = since; next_stanza = function() end; -- events should define this iterator } -- cgit v1.2.3 From 2ca609d4fd0bd1a4414de184b657425df86bafc3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 19 May 2014 13:39:45 -0400 Subject: plugins/muc/mod_muc: Get rid of room global; use module:shared to get it --- plugins/muc/mod_muc.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 79123ab5..628c72b3 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -20,7 +20,6 @@ local um_is_admin = require "core.usermanager".is_admin; local hosts = prosody.hosts; local rooms = module:shared "rooms"; -_G.rooms = rooms; module:depends("disco"); module:add_identity("conference", "text", module:get_option_string("name", "Prosody Chatrooms")); -- cgit v1.2.3 From 4cc91481bf8eaa34ba1d5350dcd1119e7b45f8a7 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 19 May 2014 13:40:24 -0400 Subject: plugins/muc/muc: Always send occupant list to joining sessions --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 27d738de..df823180 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -453,7 +453,7 @@ function room_mt:handle_presence_to_occupant(origin, stanza) end self:save_occupant(dest_occupant); - if orig_occupant == nil and is_first_dest_session then + if orig_occupant == nil then -- Send occupant list to newly joined user self:send_occupant_list(real_jid, function(nick, occupant) -- Don't include self -- cgit v1.2.3 From 0d12eda7d73df356c24e9db0e7d3899962a1ce87 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 19 May 2014 13:40:54 -0400 Subject: plugins/muc/muc: Better check for live sessions --- plugins/muc/muc.lib.lua | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index df823180..5838cbe9 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -100,11 +100,21 @@ function room_mt:save_occupant(occupant) self._jid_nick[real_jid] = nil; end end - if occupant.role ~= nil and next(occupant.sessions) then + + local has_live_session = false + if occupant.role ~= nil then for real_jid, presence in occupant:each_session() do - self._jid_nick[real_jid] = occupant.nick; + if presence.attr.type == nil then + has_live_session = true + self._jid_nick[real_jid] = occupant.nick; + end end - else + if not has_live_session then + -- Has no live sessions left; they have left the room. + occupant.role = nil + end + end + if not has_live_session then occupant = nil end self._occupants[id] = occupant -- cgit v1.2.3 From ae461b42728b11d7e241ca26c6cded24b6bb0d6f Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 19 May 2014 13:47:28 -0400 Subject: plugins/muc/occupant: Check for type == nil rather than type ~= unavailable --- plugins/muc/occupant.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/occupant.lib.lua b/plugins/muc/occupant.lib.lua index 5cecb139..6a3f7df4 100644 --- a/plugins/muc/occupant.lib.lua +++ b/plugins/muc/occupant.lib.lua @@ -53,7 +53,7 @@ end -- finds another session to be the primary (there might not be one) function occupant_mt:choose_new_primary() for jid, pr in self:each_session() do - if pr.attr.type ~= "unavailable" then + if pr.attr.type == nil then return jid; end end -- cgit v1.2.3 From 975283ba9cb3fe63488a31e30a23f4d65bc0910b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 21 May 2014 13:11:00 -0400 Subject: plugins/muc/muc: Only set role to nil if it's the last session to leave --- plugins/muc/muc.lib.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5838cbe9..34934213 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -410,7 +410,9 @@ function room_mt:handle_presence_to_occupant(origin, stanza) local dest_nick; if dest_occupant == nil then -- Session is leaving log("debug", "session %s is leaving occupant %s", real_jid, orig_occupant.nick); - orig_occupant.role = nil; + if is_last_orig_session then + orig_occupant.role = nil; + end orig_occupant:set_session(real_jid, stanza); else log("debug", "session %s is changing from occupant %s to %s", real_jid, orig_occupant.nick, dest_occupant.nick); -- cgit v1.2.3 From 9e8f0a984c05647db2346160d1cf62139560fefc Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 21 May 2014 13:19:57 -0400 Subject: plugins/muc/muc.lib: Even unavailable session need to be routed to sometimes (e.g. their own leave) --- plugins/muc/muc.lib.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 de5aae8a217c09893ab549a6d39cebbe58172e02 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 e6e3829b31622b39e98271e937d4cb433f6ec911 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 12b7a1141a7a752f71f9bfbae4767fd25f6dd092 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Wed, 28 May 2014 21:52:57 +0200 Subject: mod_pep_plus: Expose get_pep_service() --- plugins/mod_pep_plus.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ed9c56bd85bb93b747d123b8ba87e4258ba31080 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 0c26e52848bb6c5509f8507b4e6933c4f3051125 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 29 May 2014 13:27:41 -0400 Subject: plugins/muc/muc.lib: Fire event for presence broadcast --- plugins/muc/muc.lib.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 0b361763..1fc922d4 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -207,6 +207,9 @@ function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason return get_base_presence(occupant):add_child(x), x; end local full_p, full_x = get_presence(false); + + module:fire_event("muc-broadcast-presence", {room = self; stanza = full_p; x = full_x;}); + local anon_p, anon_x; local function get_anon_p() if anon_p == nil then -- cgit v1.2.3 From 13bb4e436c4327c969f49984eac471a4c82de9df Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Sun, 1 Jun 2014 04:42:55 -0400 Subject: plugins/muc/muc.lib: Fix whois check when broadcasting occupant presence --- plugins/muc/muc.lib.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1fc922d4..db7c3592 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -224,7 +224,7 @@ function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason for nick, n_occupant in self:each_occupant() do if nick ~= occupant.nick then local pr; - if can_see_real_jids(whois, occupant) or occupant.bare_jid == n_occupant.bare_jid then + if can_see_real_jids(whois, n_occupant) or occupant.bare_jid == n_occupant.bare_jid then pr = full_p; else pr = get_anon_p(); -- cgit v1.2.3 From bb07b5a54fb3bd2979ab84d494b702b35c8a445a Mon Sep 17 00:00:00 2001 From: daurnimator Date: Sun, 1 Jun 2014 15:40:04 -0400 Subject: plugins/muc/muc.lib: Fix incorrect whois logic (thanks mva) --- plugins/muc/muc.lib.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index db7c3592..5c639779 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -252,14 +252,14 @@ end function room_mt:send_occupant_list(to, filter) local to_bare = jid_bare(to); - local is_anonymous = true; + local is_anonymous = false; local whois = self:get_whois(); if whois ~= "anyone" then local affiliation = self:get_affiliation(to); if affiliation ~= "admin" and affiliation ~= "owner" then local occupant = self:get_occupant_by_real_jid(to); - if not occupant or can_see_real_jids(whois, occupant) then - is_anonymous = false; + if not (occupant and can_see_real_jids(whois, occupant)) then + is_anonymous = true; end end end -- cgit v1.2.3 From cc0208bd0602f2472db06cd8d54e988c782f2891 Mon Sep 17 00:00:00 2001 From: Waqas Hussain Date: Thu, 5 Jun 2014 17:15:04 -0400 Subject: mod_admin_telnet: Update muc:room(jid) and muc:list(host) to use the new MUC API --- plugins/mod_admin_telnet.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua index 66560d44..0b96b68d 100644 --- a/plugins/mod_admin_telnet.lua +++ b/plugins/mod_admin_telnet.lua @@ -957,7 +957,7 @@ function def_env.muc:room(room_jid) if not room_name then return room_name, host; end - local room_obj = hosts[host].modules.muc.rooms[room_jid]; + local room_obj = hosts[host].modules.muc.get_room_from_jid(room_jid); if not room_obj then return nil, "No such room: "..room_jid; end @@ -970,8 +970,8 @@ function def_env.muc:list(host) return nil, "Please supply the address of a local MUC component"; end local c = 0; - for name in keys(host_session.modules.muc.rooms) do - print(name); + for room in host_session.modules.muc.each_room() do + print(room.jid); c = c + 1; end return true, c.." rooms"; -- cgit v1.2.3 From 38f33840a2670fffdb019284b49c5fd23c75a7a4 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 5 Jun 2014 17:07:14 -0400 Subject: plugins/muc: Move 'x' filtering from occupant to util --- plugins/muc/occupant.lib.lua | 17 +++-------------- plugins/muc/util.lib.lua | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/plugins/muc/occupant.lib.lua b/plugins/muc/occupant.lib.lua index 6a3f7df4..d59252e2 100644 --- a/plugins/muc/occupant.lib.lua +++ b/plugins/muc/occupant.lib.lua @@ -2,21 +2,10 @@ local next = next; local pairs = pairs; local setmetatable = setmetatable; local st = require "util.stanza"; +local util = module:require "muc/util"; -local get_filtered_presence do - local presence_filters = { - ["http://jabber.org/protocol/muc"] = true; - ["http://jabber.org/protocol/muc#user"] = true; - } - local function presence_filter(tag) - if presence_filters[tag.attr.xmlns] then - return nil; - end - return tag; - end - function get_filtered_presence(stanza) - return st.clone(stanza):maptags(presence_filter); - end +local function get_filtered_presence(stanza) + return util.filter_muc_x(st.clone(stanza)); end local occupant_mt = {}; diff --git a/plugins/muc/util.lib.lua b/plugins/muc/util.lib.lua index 90a3a183..16deb543 100644 --- a/plugins/muc/util.lib.lua +++ b/plugins/muc/util.lib.lua @@ -41,4 +41,18 @@ function _M.is_kickable_error(stanza) return kickable_error_conditions[cond]; end +local muc_x_filters = { + ["http://jabber.org/protocol/muc"] = true; + ["http://jabber.org/protocol/muc#user"] = true; +} +local function muc_x_filter(tag) + if muc_x_filters[tag.attr.xmlns] then + return nil; + end + return tag; +end +function _M.filter_muc_x(stanza) + return stanza:maptags(muc_x_filter); +end + return _M; -- cgit v1.2.3 From 557a75123d1298996951212d5dd3307d84e3334e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 5 Jun 2014 17:08:05 -0400 Subject: plugins/muc/muc: When forwarding mediated invites; use filtered version of original invite instead of new object --- plugins/muc/muc.lib.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 5c639779..af7202da 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -841,8 +841,10 @@ function room_mt:handle_mediated_invite(origin, stanza) elseif module:fire_event("muc-pre-invite", {room = self, origin = origin, stanza = stanza}) then return true; end - local invite = st.message({from = self.jid, to = invitee, id = stanza.attr.id}) - :tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) + local invite = muc_util.filter_muc_x(st.clone(stanza)); + invite.attr.from = self.jid; + invite.attr.to = invitee; + invite:tag('x', {xmlns='http://jabber.org/protocol/muc#user'}) :tag('invite', {from = stanza.attr.from;}) :tag('reason'):text(payload:get_child_text("reason")):up() :up() -- cgit v1.2.3 From 9318230b51773a234e4e6018275f984938e3ead3 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 6 Jun 2014 14:33:43 -0400 Subject: plugins/muc/muc.lib: Use original decline as template for medated decline --- plugins/muc/muc.lib.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index af7202da..a7b6ef9c 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -884,8 +884,10 @@ function room_mt:handle_mediated_decline(origin, stanza) elseif module:fire_event("muc-pre-decline", {room = self, origin = origin, stanza = stanza}) then return true; end - local decline = st.message({from = self.jid, to = declinee, id = stanza.attr.id}) - :tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) + local decline = muc_util.filter_muc_x(st.clone(stanza)); + decline.attr.from = self.jid; + decline.attr.to = declinee; + decline:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"}) :tag("decline", {from = stanza.attr.from}) :tag("reason"):text(payload:get_child_text("reason")):up() :up() -- cgit v1.2.3 From 473c08dc107419c8f0df2bb8a8c23a28369a5312 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 6 Jun 2014 14:34:01 -0400 Subject: plugins/muc/muc.lib: Don't add invite/decline bodies if they already have one --- plugins/muc/muc.lib.lua | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index a7b6ef9c..57cc3721 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -868,11 +868,13 @@ end); -- Add a plain message for clients which don't support invites module:hook("muc-invite", function(event) local room, stanza = event.room, event.stanza; - local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); - local reason = invite:get_child_text("reason") or ""; - stanza:tag("body") - :text(invite.attr.from.." invited you to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) - :up(); + if not stanza:get_child("body") then + local invite = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("invite"); + local reason = invite:get_child_text("reason") or ""; + stanza:tag("body") + :text(invite.attr.from.." invited you to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) + :up(); + end end); function room_mt:handle_mediated_decline(origin, stanza) @@ -906,11 +908,13 @@ end -- Add a plain message for clients which don't support declines module:hook("muc-decline", function(event) local room, stanza = event.room, event.stanza; - local decline = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline"); - local reason = decline:get_child_text("reason") or ""; - stanza:tag("body") - :text(decline.attr.from.." declined your invite to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) - :up(); + if not stanza:get_child("body") then + local decline = stanza:get_child("x", "http://jabber.org/protocol/muc#user"):get_child("decline"); + local reason = decline:get_child_text("reason") or ""; + stanza:tag("body") + :text(decline.attr.from.." declined your invite to the room "..room.jid..(reason == "" and (" ("..reason..")") or "")) + :up(); + end end); function room_mt:handle_message_to_room(origin, stanza) -- cgit v1.2.3 From be47daf8e124b7f96a7081d9873a277bf1403858 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 11 Jun 2014 14:59:59 -0400 Subject: plugins/muc/muc.lib: Deliver declines to in-room jids correctly --- plugins/muc/muc.lib.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 57cc3721..85c256a2 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -895,7 +895,11 @@ function room_mt:handle_mediated_decline(origin, stanza) :up() :up(); if not module:fire_event("muc-decline", {room = self, stanza = decline, origin = origin, incoming = stanza}) then - local occupant = self:get_occupant_by_real_jid(decline.attr.to); + local declinee = decline.attr.to; -- re-fetch, in case event modified it + local occupant + if jid_bare(declinee) == self.jid then -- declinee jid is already an in-room jid + occupant = self:get_occupant_by_nick(declinee); + end if occupant then self:route_to_occupant(occupant, decline); else -- cgit v1.2.3 From 75d78ae9e40c0d0b972842d30a35387f161823d5 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 11 Jun 2014 17:54:23 -0400 Subject: plugins/muc/mod_muc: fix wrong event for persistence --- plugins/muc/mod_muc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 628c72b3..4cd20245 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -91,7 +91,7 @@ do -- Persistent rooms end -- When room is created, over-ride 'save' method - module:hook("muc-occupant-pre-create", function(event) + module:hook("muc-room-pre-create", function(event) event.room.save = room_save; end, 1000); -- cgit v1.2.3 From 1694d8908a486fdc1fcee99b98cf5a8df65be13b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 17 Jun 2014 15:18:43 -0400 Subject: plugins/muc: Fix use of incorrect event on occupant join --- plugins/muc/history.lib.lua | 2 +- plugins/muc/muc.lib.lua | 7 +++++-- plugins/muc/subject.lib.lua | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/muc/history.lib.lua b/plugins/muc/history.lib.lua index 1c14cf84..4129e7fa 100644 --- a/plugins/muc/history.lib.lua +++ b/plugins/muc/history.lib.lua @@ -130,7 +130,7 @@ local function send_history(room, stanza) end -- Send history on join -module:hook("muc-occupant-joined", function(event) +module:hook("muc-occupant-session-new", function(event) send_history(event.room, event.stanza); end, 50); -- Before subject(20) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 85c256a2..c645869e 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -487,8 +487,11 @@ function room_mt:handle_presence_to_occupant(origin, stanza) self:route_stanza(pr); end - if orig_occupant == nil and is_first_dest_session then - module:fire_event("muc-occupant-joined", {room = self; nick = dest_occupant.nick; stanza = stanza;}); + if orig_occupant == nil then + if is_first_dest_session then + module:fire_event("muc-occupant-joined", {room = self; nick = dest_occupant.nick; stanza = stanza;}); + end + module:fire_event("muc-occupant-session-new", {room = self; nick = dest_occupant.nick; stanza = stanza; jid = real_jid;}); end end elseif type ~= 'result' then -- bad type diff --git a/plugins/muc/subject.lib.lua b/plugins/muc/subject.lib.lua index 44fc915c..34f9a5d4 100644 --- a/plugins/muc/subject.lib.lua +++ b/plugins/muc/subject.lib.lua @@ -66,7 +66,7 @@ local function set_subject(room, from, subject) end -- Send subject to joining user -module:hook("muc-occupant-joined", function(event) +module:hook("muc-occupant-session-new", function(event) send_subject(event.room, event.stanza.attr.from); end, 20); -- cgit v1.2.3 From 3a09673e61b5d394fcade54e8e09dadcc840fdb0 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Tue, 17 Jun 2014 15:27:00 -0400 Subject: plugins/muc/muc: Fire broadcast presences event before creating full/anon presences --- plugins/muc/muc.lib.lua | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) 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 b124e4e22644b0f9d10ec7638d532fbff016e276 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 2cc4b6a6aef216d80e788eac02e0e41633a22f07 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 8 Jul 2014 07:32:45 +0200 Subject: mod_pep_plus: Only broadcast newly added subscriptions --- plugins/mod_pep_plus.lua | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) 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 bb40012ca2d5a7eb38d86f4cef5488b31f6da0d0 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 4aba920b7e87b05c8ed6750149bf0ff6a9a65ab8 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 25 Jul 2014 18:32:15 +0100 Subject: mod_muc/muc.lib: Pass all info to muc-broadcast-presence handlers that would be required to use room:build_item_list() - useful for plugins --- plugins/muc/muc.lib.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 3ee17b03..1d95b6f8 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -201,7 +201,11 @@ function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason base_presence = base_presence or st.presence {from = occupant.nick; type = "unavailable";}; -- Fire event (before full_p and anon_p are created) - module:fire_event("muc-broadcast-presence", {room = self; stanza = base_presence; x = base_x;}); + module:fire_event("muc-broadcast-presence", { + room = self; stanza = base_presence; x = base_x; + occupant = occupant; is_anonymous = is_anonymous; + nick = nick; actor = actor; reason = reason; + }); local function get_presence(is_anonymous) local x = st.clone(base_x); -- cgit v1.2.3 From 6f4e24a0fc86a839792d0dda5a99746179231fd0 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 25 Jul 2014 18:34:43 +0100 Subject: mod_muc/muc.lib: Remove is_anonymous from event (fix for ec57067c1e0d) --- plugins/muc/muc.lib.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 1d95b6f8..041187e5 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -203,8 +203,8 @@ function room_mt:publicise_occupant_status(occupant, base_x, nick, actor, reason -- Fire event (before full_p and anon_p are created) module:fire_event("muc-broadcast-presence", { room = self; stanza = base_presence; x = base_x; - occupant = occupant; is_anonymous = is_anonymous; - nick = nick; actor = actor; reason = reason; + occupant = occupant; nick = nick; actor = actor; + reason = reason; }); local function get_presence(is_anonymous) -- cgit v1.2.3 From 069a36e4758fac28ad5c89cb8a5f8a4bb3fed71f Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Fri, 25 Jul 2014 21:12:21 +0200 Subject: mod_s2s: Remove unused locals --- plugins/mod_s2s/mod_s2s.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua index f0d465c0..f177bf82 100644 --- a/plugins/mod_s2s/mod_s2s.lua +++ b/plugins/mod_s2s/mod_s2s.lua @@ -15,7 +15,6 @@ local core_process_stanza = prosody.core_process_stanza; local tostring, type = tostring, type; local t_insert = table.insert; local xpcall, traceback = xpcall, debug.traceback; -local NULL = {}; local add_task = require "util.timer".add_task; local st = require "util.stanza"; @@ -26,7 +25,6 @@ local s2s_new_incoming = require "core.s2smanager".new_incoming; local s2s_new_outgoing = require "core.s2smanager".new_outgoing; local s2s_destroy_session = require "core.s2smanager".destroy_session; local uuid_gen = require "util.uuid".generate; -local cert_verify_identity = require "util.x509".verify_identity; local fire_global_event = prosody.events.fire_event; local s2sout = module:require("s2sout"); -- cgit v1.2.3 From dfc87dc7ebe5d424022eae8e10fa8d7a1f404e0f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 5 Aug 2014 09:16:29 +0100 Subject: mod_muc: Fix use of undefined global. Fixes #431. --- plugins/muc/mod_muc.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 4cd20245..af46714c 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -148,7 +148,8 @@ do if restrict_room_creation then local host_suffix = module.host:gsub("^[^%.]+%.", ""); module:hook("muc-room-pre-create", function(event) - local user_jid = event.stanza.attr.from; + local origin, stanza = event.origin, event.stanza; + local user_jid = stanza.attr.from; if not is_admin(user_jid) and not ( restrict_room_creation == "local" and select(2, jid_split(user_jid)) == host_suffix -- cgit v1.2.3 From e77035761a9de8dc2efafcd771fd29a197bb8b0b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 5 Aug 2014 09:55:08 +0100 Subject: mod_muc: Import util.stanza into the config handler modules that need it. Fixes #432. --- plugins/muc/lock.lib.lua | 2 ++ plugins/muc/members_only.lib.lua | 2 ++ plugins/muc/password.lib.lua | 2 ++ 3 files changed, 6 insertions(+) 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 ea09a4527815cec3d42583dc3f751662d861b9dd 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 1b133aa8a0cb4f862ffda75ce24f9fd45e2ddef2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 7 Aug 2014 12:16:16 -0400 Subject: plugins/mod_storage_sql2: Add map store support --- plugins/mod_storage_sql2.lua | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/plugins/mod_storage_sql2.lua b/plugins/mod_storage_sql2.lua index 0531c905..ce21d368 100644 --- a/plugins/mod_storage_sql2.lua +++ b/plugins/mod_storage_sql2.lua @@ -216,6 +216,40 @@ function keyval_store:users() return iterator(result); end +local map_store = {}; +map_store.__index = map_store; +function map_store:get(username, key) + return engine:transaction(function() + if type(key) == "string" and key ~= "" then + local iter, state, first = engine:select("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", + host, username, self.store, key or ""); + local row = iter(state, first); + if row then + return deserialize(row.type, row.value); + else + return nil; + end + else + error("TODO: non-string keys"); + end + end); +end +function map_store:set(username, key, data) + return engine:transaction(function() + if data == nil then + engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", + host, username, self.store, key or ""); + elseif type(key) == "string" and key ~= "" then + local t, value = assert(serialize(data)); + engine:update("UPDATE `prosody` SET `type`=?, `value`=? WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", + t, value, host, username, self.store, key); + else + error("TODO: non-string keys"); + end + return true; + end); +end + local archive_store = {} archive_store.__index = archive_store function archive_store:append(username, key, when, with, value) @@ -341,6 +375,7 @@ end local stores = { keyval = keyval_store; + map = map_store; archive = archive_store; }; -- cgit v1.2.3 From 306d70fb8afe003bb6e613caa99ba27f9e53ab31 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 7 Aug 2014 12:34:51 -0400 Subject: plugins/muc/mod_muc: Remove unused import --- plugins/muc/mod_muc.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index af46714c..15949c1b 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -17,7 +17,6 @@ local jid_split = require "util.jid".split; local jid_bare = require "util.jid".bare; local st = require "util.stanza"; local um_is_admin = require "core.usermanager".is_admin; -local hosts = prosody.hosts; local rooms = module:shared "rooms"; -- cgit v1.2.3 From 0f02217cb01316c6fcbfded5df0ca39346d63752 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 7 Aug 2014 12:35:12 -0400 Subject: plugins/muc/mod_muc: Use map store for room persistence --- plugins/muc/mod_muc.lua | 131 ++++++++++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 59 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 15949c1b..bffb6579 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -44,80 +44,93 @@ do -- Monkey patch to make server admins room owners end end +local persistent = module:require "muc/persistent"; +local persistent_rooms_storage = module:open_store("persistent"); +local persistent_rooms = module:open_store("persistent", "map"); +local room_configs = module:open_store("config"); + +local function room_save(room, forced) + local node = jid_split(room.jid); + local is_persistent = persistent.get(room); + persistent_rooms:set(room.jid, is_persistent); + if is_persistent then + local history = room._data.history; + room._data.history = nil; + local data = { + jid = room.jid; + _data = room._data; + _affiliations = room._affiliations; + }; + room_configs:set(node, data); + room._data.history = history; + elseif forced then + room_configs:set(node, nil); + if not next(room._occupants) then -- Room empty + rooms[room.jid] = nil; + end + end +end + +-- Automatically destroy empty non-persistent rooms +module:hook("muc-occupant-left",function(event) + local room = event.room + if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room + module:fire_event("muc-room-destroyed", { room = room }); + end +end); + function track_room(room) rooms[room.jid] = room; + -- When room is created, over-ride 'save' method + room.save = room_save; +end + +local function restore_room(jid) + local node = jid_split(jid); + local data = room_configs:get(node); + if data then + local room = muclib.new_room(jid); + room._data = data._data; + room._affiliations = data._affiliations; + track_room(room); + return room; + end end function forget_room(jid) rooms[jid] = nil; + local node = jid_split(room.jid); + room_configs:set(node, nil); + if persistent.get(room_jid) then + persistent_rooms:set(room_jid, nil); + end end function get_room_from_jid(room_jid) - return rooms[room_jid] -end - -function each_room() - return iterators.values(rooms); -end - -do -- Persistent rooms - local persistent = module:require "muc/persistent"; - local persistent_rooms_storage = module:open_store("persistent"); - local persistent_rooms = persistent_rooms_storage:get() or {}; - local room_configs = module:open_store("config"); - - local function room_save(room, forced) - local node = jid_split(room.jid); - local is_persistent = persistent.get(room); - persistent_rooms[room.jid] = is_persistent; - if is_persistent then - local history = room._data.history; - room._data.history = nil; - local data = { - jid = room.jid; - _data = room._data; - _affiliations = room._affiliations; - }; - room_configs:set(node, data); - room._data.history = history; - elseif forced then - room_configs:set(node, nil); - if not next(room._occupants) then -- Room empty - rooms[room.jid] = nil; + local room = rooms[room_jid]; + if room == nil then + -- Check if in persistent storage + if persistent_rooms:get(room_jid) then + room = restore_room(room_jid); + if room == nil then + module:log("error", "Missing data for room '%s', removing from persistent room list", room_jid); + persistent_rooms:set(room_jid, nil); end end - if forced then persistent_rooms_storage:set(nil, persistent_rooms); end end + return room +end - -- When room is created, over-ride 'save' method - module:hook("muc-room-pre-create", function(event) - event.room.save = room_save; - end, 1000); - - -- Automatically destroy empty non-persistent rooms - module:hook("muc-occupant-left",function(event) - local room = event.room - if not room:has_occupant() and not persistent.get(room) then -- empty, non-persistent room - module:fire_event("muc-room-destroyed", { room = room }); - end - end); - - local persistent_errors = false; - for jid in pairs(persistent_rooms) do - local node = jid_split(jid); - local data = room_configs:get(node); - if data then - local room = muclib.new_room(jid); - room._data = data._data; - room._affiliations = data._affiliations; - track_room(room); - else -- missing room data - persistent_rooms[jid] = nil; - module:log("error", "Missing data for room '%s', removing from persistent room list", jid); - persistent_errors = true; +function each_room() + for room_jid in pairs(persistent_rooms_storage:get(nil) or {}) do + if rooms[room_jid] == nil then -- Don't restore rooms that already exist + local room = restore_room(room_jid); + if room == nil then + module:log("error", "Missing data for room '%s', omitting from iteration", room_jid); + end end end - if persistent_errors then persistent_rooms_storage:set(nil, persistent_rooms); end + return iterators.values(rooms); end module:hook("host-disco-items", function(event) -- cgit v1.2.3 From adf4b5a52e544766157ddfef5cef9db76768af4a Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 7 Aug 2014 18:03:31 -0400 Subject: plugins/muc/mod_muc: persistent rooms keyval store needs username as nil --- plugins/muc/mod_muc.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index bffb6579..3f01ee11 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -52,7 +52,7 @@ local room_configs = module:open_store("config"); local function room_save(room, forced) local node = jid_split(room.jid); local is_persistent = persistent.get(room); - persistent_rooms:set(room.jid, is_persistent); + persistent_rooms:set(nil, room.jid, is_persistent); if is_persistent then local history = room._data.history; room._data.history = nil; @@ -102,7 +102,7 @@ function forget_room(jid) local node = jid_split(room.jid); room_configs:set(node, nil); if persistent.get(room_jid) then - persistent_rooms:set(room_jid, nil); + persistent_rooms:set(nil, room_jid, nil); end end @@ -110,11 +110,11 @@ function get_room_from_jid(room_jid) local room = rooms[room_jid]; if room == nil then -- Check if in persistent storage - if persistent_rooms:get(room_jid) then + if persistent_rooms:get(nil, room_jid) then room = restore_room(room_jid); if room == nil then module:log("error", "Missing data for room '%s', removing from persistent room list", room_jid); - persistent_rooms:set(room_jid, nil); + persistent_rooms:set(nil, room_jid, nil); end end end -- cgit v1.2.3 From 06903bc73909fd1557c326f2cc90404aed4f7dcc Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 7 Aug 2014 18:34:51 -0400 Subject: plugins/mod_storage_sql2: Return correct arguments from map_store operations --- plugins/mod_storage_sql2.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 edd380b6cd178d2b0eb11b44c659874420393e4c 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 0437582ce056715d99a91bf0afe958b045d0cc6a Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 9 Aug 2014 21:52:32 +0200 Subject: mod_private: Use map store --- plugins/mod_private.lua | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/plugins/mod_private.lua b/plugins/mod_private.lua index 872d3760..05f05708 100644 --- a/plugins/mod_private.lua +++ b/plugins/mod_private.lua @@ -9,7 +9,7 @@ local st = require "util.stanza" -local private_storage = module:open_store(); +local private_storage = module:open_store("private", "map"); module:add_feature("jabber:iq:private"); @@ -21,25 +21,22 @@ module:hook("iq/self/jabber:iq:private:query", function(event) end local tag = query.tags[1]; local key = tag.name..":"..tag.attr.xmlns; - local data, err = private_storage:get(origin.username); - if err then - return origin.send(st.error_reply(stanza, "wait", "internal-server-error", err)); - end if stanza.attr.type == "get" then - if data and data[key] then + local data, err = private_storage:get(origin.username, key); + if data then return origin.send(st.reply(stanza):query("jabber:iq:private"):add_child(st.deserialize(data))); + elseif err then + return origin.send(st.error_reply(stanza, "wait", "internal-server-error", err)); else return origin.send(st.reply(stanza):add_child(query)); end else -- type == set - if not data then data = {}; end; - if #tag == 0 then - data[key] = nil; - else - data[key] = st.preserialize(tag); + local data; + if #tag ~= 0 then + data = st.preserialize(tag); end -- TODO delete datastore if empty - local ok, err = private_storage:set(origin.username, data); + local ok, err = private_storage:set(origin.username, key, data); if not ok then return origin.send(st.error_reply(stanza, "wait", "internal-server-error", err)); end -- cgit v1.2.3 From b699ac03fbf586261ecc605007993c563e15050b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 11 Aug 2014 11:36:30 +0200 Subject: mod_muc: Fix tracebacks (thanks nick1) --- plugins/muc/mod_muc.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 3f01ee11..5abde57c 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -97,11 +97,12 @@ local function restore_room(jid) end end -function forget_room(jid) - rooms[jid] = nil; +function forget_room(room) + local room_jid = room.jid; local node = jid_split(room.jid); + rooms[room_jid] = nil; room_configs:set(node, nil); - if persistent.get(room_jid) then + if persistent.get(room) then persistent_rooms:set(nil, room_jid, nil); end end @@ -148,8 +149,7 @@ module:hook("muc-room-pre-create", function(event) end, -1000); module:hook("muc-room-destroyed",function(event) - local room = event.room - forget_room(room.jid) + return forget_room(event.room); end) do -- cgit v1.2.3 From 7eb2ec7a56e3ffca8ddbe06ece12ba20c4125fac Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 12 Aug 2014 11:38:12 +0200 Subject: mod_storage_sql2: DELETE then INSERT in map stores --- plugins/mod_storage_sql2.lua | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/plugins/mod_storage_sql2.lua b/plugins/mod_storage_sql2.lua index c0e2e6cd..e5357416 100644 --- a/plugins/mod_storage_sql2.lua +++ b/plugins/mod_storage_sql2.lua @@ -221,13 +221,8 @@ map_store.__index = map_store; function map_store:get(username, key) local ok, result = engine:transaction(function() if type(key) == "string" and key ~= "" then - local iter, state, first = engine:select("SELECT * FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", - host, username, self.store, key or ""); - local row = iter(state, first); - if row then - return deserialize(row.type, row.value); - else - return nil; + for row in engine:select("SELECT `type`, `value` FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", host, username or "", self.store, key) do + return deserialize(row[1], row[2]); end else error("TODO: non-string keys"); @@ -238,13 +233,13 @@ function map_store:get(username, key) end function map_store:set(username, key, data) local ok, result = engine:transaction(function() - if data == nil then + if type(key) == "string" and key ~= "" then engine:delete("DELETE FROM `prosody` WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", - host, username, self.store, key or ""); - elseif type(key) == "string" and key ~= "" then - local t, value = assert(serialize(data)); - engine:update("UPDATE `prosody` SET `type`=?, `value`=? WHERE `host`=? AND `user`=? AND `store`=? AND `key`=?", - t, value, host, username, self.store, key); + host, username or "", self.store, key); + if data ~= nil then + local t, value = assert(serialize(data)); + engine:insert("INSERT INTO `prosody` (`host`,`user`,`store`,`key`,`type`,`value`) VALUES (?,?,?,?,?,?)", host, username or "", self.store, key, t, value); + end else error("TODO: non-string keys"); end -- cgit v1.2.3 From 46105d64bf9627c8618624281c966ec784f4fc12 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sun, 31 Aug 2014 20:33:47 +0200 Subject: mod_muc: Fix 'destroy rooms' adhoc command (Thanks Florob) --- plugins/muc/mod_muc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua index 5abde57c..5428d270 100644 --- a/plugins/muc/mod_muc.lua +++ b/plugins/muc/mod_muc.lua @@ -240,7 +240,7 @@ do -- Ad-hoc commands }; local destroy_rooms_handler = adhoc_initial(destroy_rooms_layout, function() - return { rooms = array.collect(each_room):pluck("jid"):sort(); }; + return { rooms = array.collect(each_room()):pluck("jid"):sort(); }; end, function(fields, errors) if errors then local errmsg = {}; -- cgit v1.2.3 From 136d5aee859f4b064efb6618fa0de99e491bd271 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 5 Sep 2014 11:19:16 -0400 Subject: plugins/muc/muc.lib: Add instant room support --- plugins/muc/muc.lib.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 041187e5..d430f984 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -614,10 +614,15 @@ function room_mt:process_form(origin, stanza) if form.attr.type == "cancel" then origin.send(st.reply(stanza)); elseif form.attr.type == "submit" then - local fields = self:get_form_layout(stanza.attr.from):data(form); - if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then - origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); - return true; + local fields; + if form.tags[1] == nil then -- Instant room + fields = {}; + else + fields = self:get_form_layout(stanza.attr.from):data(form); + if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then + origin.send(st.error_reply(stanza, "cancel", "bad-request", "Form is not of type room configuration")); + return true; + end end local event = {room = self; origin = origin; stanza = stanza; fields = fields; status_codes = {};}; -- cgit v1.2.3 From f4e4cfbd02d830efd60bc07af0b7d953c77bab88 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 5 Sep 2014 11:20:54 -0400 Subject: plugins/muc/muc.lib: Use get_affilation() inside of set_affiliation(), so that the override in mod_muc works --- plugins/muc/muc.lib.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index d430f984..31b5c559 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -984,10 +984,9 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason) local is_downgrade = valid_affiliations[target_affiliation or "none"] > valid_affiliations[affiliation or "none"]; if actor ~= true then - local actor_bare = jid_bare(actor); - local actor_affiliation = self._affiliations[actor_bare]; + local actor_affiliation = self:get_affiliation(actor); if actor_affiliation == "owner" then - if actor_bare == jid then -- self change + if jid_bare(actor) == jid then -- self change -- need at least one owner local is_last = true; for j, aff in pairs(self._affiliations) do if j ~= jid and aff == "owner" then is_last = false; break; end end -- cgit v1.2.3 From 8728ee5e5a3925588e5502a87a3466ec8eebc44a Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 5 Sep 2014 11:28:28 -0400 Subject: plugins/muc/muc.lib: Add muc-set-affiliation event --- plugins/muc/muc.lib.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 31b5c559..4e461df6 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -1049,6 +1049,17 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason) end if self.save then self:save(); end + + module:fire_event("muc-set-affiliation", { + room = self; + actor = actor; + jid = jid; + affiliation = affiliation or "none"; + reason = reason; + previous_affiliation = target_affiliation; + in_room = next(occupants_updated) ~= nil; + }); + return true; end -- cgit v1.2.3 From 1e987242449ef303547f9df10b99d4313e21e3be Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 5 Sep 2014 12:16:53 -0400 Subject: plugins/muc: Add affiliation_notify config option to send out status code 101 --- plugins/muc/affiliation_notify.lib.lua | 66 ++++++++++++++++++++++++++++++++++ plugins/muc/muc.lib.lua | 2 ++ 2 files changed, 68 insertions(+) create mode 100644 plugins/muc/affiliation_notify.lib.lua 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 15303b0d3a84572090e81dc7c737b575184135b6 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