aboutsummaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/mod_admin_telnet.lua72
-rw-r--r--plugins/mod_bosh.lua29
-rw-r--r--plugins/mod_c2s.lua10
-rw-r--r--plugins/mod_component.lua2
-rw-r--r--plugins/mod_http.lua33
-rw-r--r--plugins/mod_http_errors.lua25
-rw-r--r--plugins/mod_mam/mod_mam.lua77
-rw-r--r--plugins/mod_pep.lua17
-rw-r--r--plugins/mod_pep_simple.lua1
-rw-r--r--plugins/mod_presence.lua12
-rw-r--r--plugins/mod_pubsub/mod_pubsub.lua7
-rw-r--r--plugins/mod_s2s/mod_s2s.lua3
-rw-r--r--plugins/mod_saslauth.lua3
-rw-r--r--plugins/mod_storage_memory.lua8
-rw-r--r--plugins/mod_storage_sql.lua2
-rw-r--r--plugins/mod_tls.lua24
-rw-r--r--plugins/mod_websocket.lua42
-rw-r--r--plugins/muc/mod_muc.lua2
-rw-r--r--plugins/muc/muc.lib.lua78
-rw-r--r--plugins/muc/subject.lib.lua6
20 files changed, 278 insertions, 175 deletions
diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua
index 1cbe27a4..7fae8983 100644
--- a/plugins/mod_admin_telnet.lua
+++ b/plugins/mod_admin_telnet.lua
@@ -22,6 +22,7 @@ local prosody = _G.prosody;
local console_listener = { default_port = 5582; default_mode = "*a"; interface = "127.0.0.1" };
+local unpack = table.unpack or unpack; -- luacheck: ignore 113
local iterators = require "util.iterators";
local keys, values = iterators.keys, iterators.values;
local jid_bare, jid_split, jid_join = import("util.jid", "bare", "prepped_split", "join");
@@ -30,6 +31,8 @@ local cert_verify_identity = require "util.x509".verify_identity;
local envload = require "util.envload".envload;
local envloadfile = require "util.envload".envloadfile;
local has_pposix, pposix = pcall(require, "util.pposix");
+local async = require "util.async";
+local serialize = require "util.serialization".new({ fatal = false, unquoted = true});
local commands = module:shared("commands")
local def_env = module:shared("env");
@@ -47,6 +50,21 @@ end
console = {};
+local runner_callbacks = {};
+
+function runner_callbacks:ready()
+ self.data.conn:resume();
+end
+
+function runner_callbacks:waiting()
+ self.data.conn:pause();
+end
+
+function runner_callbacks:error(err)
+ module:log("error", "Traceback[telnet]: %s", err);
+end
+
+
function console:new_session(conn)
local w = function(s) conn:write(s:gsub("\n", "\r\n")); end;
local session = { conn = conn;
@@ -62,6 +80,11 @@ function console:new_session(conn)
};
session.env = setmetatable({}, default_env_mt);
+ session.thread = async.runner(function (line)
+ console:process_line(session, line);
+ session.send(string.char(0));
+ end, runner_callbacks, session);
+
-- Load up environment with helper objects
for name, t in pairs(def_env) do
if type(t) == "table" then
@@ -150,8 +173,7 @@ function console_listener.onincoming(conn, data)
for line in data:gmatch("[^\n]*[\n\004]") do
if session.closed then return end
- console:process_line(session, line);
- session.send(string.char(0));
+ session.thread:run(line);
end
session.partial_data = data:match("[^\n]+$");
end
@@ -474,9 +496,12 @@ function def_env.config:load(filename, format)
return true, "Config loaded";
end
-function def_env.config:get(host, section, key)
+function def_env.config:get(host, key)
+ if key == nil then
+ host, key = "*", host;
+ end
local config_get = require "core.configmanager".get
- return true, tostring(config_get(host, section, key));
+ return true, serialize(config_get(host, key));
end
function def_env.config:reload()
@@ -520,6 +545,9 @@ local function session_flags(session, line)
if session.remote then
line[#line+1] = "(remote)";
end
+ if session.is_bidi then
+ line[#line+1] = "(bidi)";
+ end
return table.concat(line, " ");
end
@@ -1062,13 +1090,33 @@ end
def_env.xmpp = {};
local st = require "util.stanza";
-function def_env.xmpp:ping(localhost, remotehost)
- if prosody.hosts[localhost] then
- module:send(st.iq{ from=localhost, to=remotehost, type="get", id="ping" }
- :tag("ping", {xmlns="urn:xmpp:ping"}), prosody.hosts[localhost]);
- return true, "Sent ping";
+local new_id = require "util.id".medium;
+function def_env.xmpp:ping(localhost, remotehost, timeout)
+ localhost = select(2, jid_split(localhost));
+ remotehost = select(2, jid_split(remotehost));
+ if not localhost then
+ return nil, "Invalid sender hostname";
+ elseif not prosody.hosts[localhost] then
+ return nil, "No such local host";
+ end
+ if not remotehost then
+ return nil, "Invalid destination hostname";
+ elseif prosody.hosts[remotehost] then
+ return nil, "Both hosts are local";
+ end
+ local iq = st.iq{ from=localhost, to=remotehost, type="get", id=new_id()}
+ :tag("ping", {xmlns="urn:xmpp:ping"});
+ local ret, err;
+ local wait, done = async.waiter();
+ module:context(localhost):send_iq(iq, nil, timeout)
+ :next(function (ret_) ret = ret_; end,
+ function (err_) err = err_; end)
+ :finally(done);
+ wait();
+ if ret then
+ return true, "pong from " .. ret.stanza.attr.from;
else
- return nil, "No such host";
+ return false, tostring(err);
end
end
@@ -1207,7 +1255,7 @@ local function format_stat(type, value, ref_value)
--do return tostring(value) end
if type == "duration" then
if ref_value < 0.001 then
- return ("%d µs"):format(value*1000000);
+ return ("%g µs"):format(value*1000000);
elseif ref_value < 0.9 then
return ("%0.2f ms"):format(value*1000);
end
@@ -1495,7 +1543,7 @@ function def_env.stats:show(filter)
local stats, changed, extra = require "core.statsmanager".get_stats();
local available, displayed = 0, 0;
local displayed_stats = new_stats_context(self);
- for name, value in pairs(stats) do
+ for name, value in iterators.sorted_pairs(stats) do
available = available + 1;
if not filter or name:match(filter) then
displayed = displayed + 1;
diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua
index d4701148..082ed961 100644
--- a/plugins/mod_bosh.lua
+++ b/plugins/mod_bosh.lua
@@ -44,10 +44,11 @@ local bosh_max_polling = module:get_option_number("bosh_max_polling", 5);
local bosh_max_wait = module:get_option_number("bosh_max_wait", 120);
local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure");
-local cross_domain = module:get_option("cross_domain_bosh", false);
+local cross_domain = module:get_option("cross_domain_bosh");
-if cross_domain == true then cross_domain = "*"; end
-if type(cross_domain) == "table" then cross_domain = table.concat(cross_domain, ", "); end
+if cross_domain ~= nil then
+ module:log("info", "The 'cross_domain_bosh' option has been deprecated");
+end
local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
@@ -91,22 +92,6 @@ function check_inactive(now, session, context, reason) -- luacheck: ignore 212/n
end
end
-local function set_cross_domain_headers(response)
- local headers = response.headers;
- headers.access_control_allow_methods = "GET, POST, OPTIONS";
- headers.access_control_allow_headers = "Content-Type";
- headers.access_control_max_age = "7200";
- headers.access_control_allow_origin = cross_domain;
- return response;
-end
-
-function handle_OPTIONS(event)
- if cross_domain and event.request.headers.origin then
- set_cross_domain_headers(event.response);
- end
- return "";
-end
-
function handle_POST(event)
log("debug", "Handling new request %s: %s\n----------", tostring(event.request), tostring(event.request.body));
@@ -121,10 +106,6 @@ function handle_POST(event)
local headers = response.headers;
headers.content_type = "text/xml; charset=utf-8";
- if cross_domain and request.headers.origin then
- set_cross_domain_headers(response);
- end
-
-- stream:feed() calls the stream_callbacks, so all stanzas in
-- the body are processed in this next line before it returns.
-- In particular, the streamopened() stream callback is where
@@ -511,8 +492,6 @@ module:provides("http", {
route = {
["GET"] = GET_response;
["GET /"] = GET_response;
- ["OPTIONS"] = handle_OPTIONS;
- ["OPTIONS /"] = handle_OPTIONS;
["POST"] = handle_POST;
["POST /"] = handle_POST;
};
diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua
index 8e31a968..8d7b92fe 100644
--- a/plugins/mod_c2s.lua
+++ b/plugins/mod_c2s.lua
@@ -106,7 +106,13 @@ function stream_callbacks.streamopened(session, attr)
if features.tags[1] or session.full_jid then
send(features);
else
- (session.log or log)("warn", "No stream features to offer");
+ if session.secure then
+ -- Normally STARTTLS would be offered
+ (session.log or log)("warn", "No stream features to offer on secure session. Check authentication settings.");
+ else
+ -- Here SASL should be offered
+ (session.log or log)("warn", "No stream features to offer on insecure session. Check encryption and security settings.");
+ end
session:close{ condition = "undefined-condition", text = "No stream features to proceed with" };
end
end
@@ -284,7 +290,7 @@ function listener.onconnect(conn)
if data then
local ok, err = stream:feed(data);
if not ok then
- log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
+ log("debug", "Received invalid XML (%s) %d bytes: %q", tostring(err), #data, data:sub(1, 300));
session:close("not-well-formed");
end
end
diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua
index b41204a2..b8c87dee 100644
--- a/plugins/mod_component.lua
+++ b/plugins/mod_component.lua
@@ -310,7 +310,7 @@ function listener.onconnect(conn)
function session.data(_, data)
local ok, err = stream:feed(data);
if ok then return; end
- module:log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
+ log("debug", "Received invalid XML (%s) %d bytes: %q", tostring(err), #data, data:sub(1, 300));
session:close("not-well-formed");
end
diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua
index a1d409bd..829c2d02 100644
--- a/plugins/mod_http.lua
+++ b/plugins/mod_http.lua
@@ -14,6 +14,7 @@ local moduleapi = require "core.moduleapi";
local url_parse = require "socket.url".parse;
local url_build = require "socket.url".build;
local normalize_path = require "util.http".normalize_path;
+local set = require "util.set";
local server = require "net.http.server";
@@ -22,6 +23,11 @@ server.set_default_host(module:get_option_string("http_default_host"));
server.set_option("body_size_limit", module:get_option_number("http_max_content_size"));
server.set_option("buffer_size_limit", module:get_option_number("http_max_buffer_size"));
+-- CORS settigs
+local opt_methods = module:get_option_set("access_control_allow_methods", { "GET", "OPTIONS" });
+local opt_headers = module:get_option_set("access_control_allow_headers", { "Content-Type" });
+local opt_max_age = module:get_option_number("access_control_max_age", 2 * 60 * 60);
+
local function get_http_event(host, app_path, key)
local method, path = key:match("^(%S+)%s+(.+)$");
if not method then -- No path specified, default to "" (base path)
@@ -83,6 +89,13 @@ function moduleapi.http_url(module, app_name, default_path)
return "http://disabled.invalid/";
end
+local function apply_cors_headers(response, methods, headers, max_age, origin)
+ response.headers.access_control_allow_methods = tostring(methods);
+ response.headers.access_control_allow_headers = tostring(headers);
+ response.headers.access_control_max_age = tostring(max_age)
+ response.headers.access_control_allow_origin = origin or "*";
+end
+
function module.add_host(module)
local host = module.host;
if host ~= "*" then
@@ -101,9 +114,27 @@ function module.add_host(module)
end
apps[app_name] = apps[app_name] or {};
local app_handlers = apps[app_name];
+
+ local app_methods = opt_methods;
+
+ local function cors_handler(event_data)
+ local request, response = event_data.request, event_data.response;
+ apply_cors_headers(response, app_methods, opt_headers, opt_max_age, request.headers.origin);
+ end
+
+ local function options_handler(event_data)
+ cors_handler(event_data);
+ return "";
+ end
+
for key, handler in pairs(event.item.route or {}) do
local event_name = get_http_event(host, app_path, key);
if event_name then
+ local method = event_name:match("^%S+");
+ if not app_methods:contains(method) then
+ app_methods = app_methods + set.new{ method };
+ end
+ local options_event_name = event_name:gsub("^%S+", "OPTIONS");
if type(handler) ~= "function" then
local data = handler;
handler = function () return data; end
@@ -121,6 +152,8 @@ function module.add_host(module)
if not app_handlers[event_name] then
app_handlers[event_name] = handler;
module:hook_object_event(server, event_name, handler);
+ module:hook_object_event(server, event_name, cors_handler, 1);
+ module:hook_object_event(server, options_event_name, options_handler, -1);
else
module:log("warn", "App %s added handler twice for '%s', ignoring", app_name, event_name);
end
diff --git a/plugins/mod_http_errors.lua b/plugins/mod_http_errors.lua
index 13473219..2bb13298 100644
--- a/plugins/mod_http_errors.lua
+++ b/plugins/mod_http_errors.lua
@@ -26,21 +26,24 @@ local html = [[
<meta charset="utf-8">
<title>{title}</title>
<style>
-body{
- margin-top:14%;
- text-align:center;
- background-color:#F8F8F8;
- font-family:sans-serif;
+body {
+ margin-top : 14%;
+ text-align : center;
+ background-color : #F8F8F8;
+ font-family : sans-serif
}
-h1{
- font-size:xx-large;
+
+h1 {
+ font-size : xx-large
}
-p{
- font-size:x-large;
+
+p {
+ font-size : x-large
}
+
p+p {
- font-size:large;
- font-family:courier;
+ font-size : large;
+ font-family : courier
}
</style>
</head>
diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua
index 94bedbb1..67bf177e 100644
--- a/plugins/mod_mam/mod_mam.lua
+++ b/plugins/mod_mam/mod_mam.lua
@@ -33,7 +33,7 @@ local is_stanza = st.is_stanza;
local tostring = tostring;
local time_now = os.time;
local m_min = math.min;
-local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse;
+local timestamp, timestamp_parse, datestamp = import( "util.datetime", "datetime", "parse", "date");
local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50);
local strip_tags = module:get_option_set("dont_archive_namespaces", { "http://jabber.org/protocol/chatstates" });
@@ -46,13 +46,8 @@ if not archive.find then
end
local use_total = module:get_option_boolean("mam_include_total", true);
-local cleanup;
-
-local function schedule_cleanup(username)
- if cleanup and not cleanup[username] then
- table.insert(cleanup, username);
- cleanup[username] = true;
- end
+function schedule_cleanup()
+ -- replaced by non-noop later if cleanup is enabled
end
-- Handle prefs.
@@ -96,7 +91,6 @@ module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
local qid = query.attr.queryid;
get_prefs(origin.username, true);
- schedule_cleanup(origin.username);
-- Search query parameters
local qwith, qstart, qend;
@@ -212,6 +206,7 @@ end
local function shall_store(user, who)
-- TODO Cache this?
if not um.user_exists(user, host) then
+ module:log("debug", "%s@%s does not exist", user, host)
return false;
end
local prefs = get_prefs(user);
@@ -329,6 +324,9 @@ module:hook("pre-message/full", strip_stanza_id_after_other_events, -1);
local cleanup_after = module:get_option_string("archive_expires_after", "1w");
local cleanup_interval = module:get_option_number("archive_cleanup_interval", 4 * 60 * 60);
if cleanup_after ~= "never" then
+ local cleanup_storage = module:open_store("archive_cleanup");
+ local cleanup_map = module:open_store("archive_cleanup", "map");
+
local day = 86400;
local multipliers = { d = day, w = day * 7, m = 31 * day, y = 365.2425 * day };
local n, m = cleanup_after:lower():match("(%d+)%s*([dwmy]?)");
@@ -346,33 +344,50 @@ if cleanup_after ~= "never" then
return false;
end
- -- Set of known users to do message expiry for
- -- Populated either below or when new messages are added
- cleanup = {};
+ -- For each day, store a set of users that have new messages. To expire
+ -- messages, we collect the union of sets of users from dates that fall
+ -- outside the cleanup range.
- -- Iterating over users is not supported by all authentication modules
- -- Catch and ignore error if not supported
- pcall(function ()
- -- If this works, then we schedule cleanup for all known users on startup
- for user in um.users(module.host) do
- schedule_cleanup(user);
+ function schedule_cleanup(username, date)
+ cleanup_map:set(date or datestamp(), username, true);
+ end
+ local cleanup_time = module:measure("cleanup", "times");
+
+ cleanup_runner = require "util.async".runner(function ()
+ local cleanup_done = cleanup_time();
+ local users = {};
+ local cut_off = datestamp(os.time() - cleanup_after);
+ for date in cleanup_storage:users() do
+ if date <= cut_off then
+ module:log("debug", "Messages from %q should be expired", date);
+ local messages_this_day = cleanup_storage:get(date);
+ if messages_this_day then
+ for user in pairs(messages_this_day) do
+ users[user] = true;
+ end
+ if date < cut_off then
+ -- Messages from the same day as the cut-off might not have expired yet,
+ -- but all earlier will have, so clear storage for those days.
+ cleanup_storage:set(date, nil);
+ end
+ end
+ end
end
- end);
-
- -- At odd intervals, delete old messages for one user
- module:add_timer(math.random(10, 60), function()
- local user = table.remove(cleanup, 1);
- if user then
- module:log("debug", "Removing old messages for user %q", user);
+ local sum, num_users = 0, 0;
+ for user in pairs(users) do
local ok, err = archive:delete(user, { ["end"] = os.time() - cleanup_after; })
- if not ok then
- module:log("warn", "Could not expire archives for user %s: %s", user, err);
- elseif type(ok) == "number" then
- module:log("debug", "Removed %d messages", ok);
+ if ok then
+ num_users = num_users + 1;
+ sum = sum + (tonumber(ok) or 0);
end
- cleanup[user] = nil;
end
- return math.random(cleanup_interval, cleanup_interval * 2);
+ module:log("info", "Deleted %d expired messages for %d users", sum, num_users);
+ cleanup_done();
+ end);
+
+ cleanup_task = module:add_timer(1, function ()
+ cleanup_runner:run(true);
+ return cleanup_interval;
end);
else
module:log("debug", "Archive expiry disabled");
diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua
index 7a4aac2b..92a09bb0 100644
--- a/plugins/mod_pep.lua
+++ b/plugins/mod_pep.lua
@@ -8,6 +8,7 @@ local calculate_hash = require "util.caps".calculate_hash;
local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
local cache = require "util.cache";
local set = require "util.set";
+local new_id = require "util.id".medium;
local storagemanager = require "core.storagemanager";
local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
@@ -138,9 +139,7 @@ local function get_broadcaster(username)
if kind == "retract" then
kind = "items"; -- XEP-0060 signals retraction in an <items> container
end
- local message = st.message({ from = user_bare, type = "headline" })
- :tag("event", { xmlns = xmlns_pubsub_event })
- :tag(kind, { node = node });
+
if item then
item = st.clone(item);
item.attr.xmlns = nil; -- Clear the pubsub namespace
@@ -149,8 +148,17 @@ local function get_broadcaster(username)
item:maptags(function () return nil; end);
end
end
+ end
+
+ local id = new_id();
+ local message = st.message({ from = user_bare, type = "headline", id = id })
+ :tag("event", { xmlns = xmlns_pubsub_event })
+ :tag(kind, { node = node });
+
+ if item then
message:add_child(item);
end
+
for jid in pairs(jids) do
module:log("debug", "Sending notification to %s from %s: %s", jid, user_bare, tostring(item));
message.attr.to = jid;
@@ -252,9 +260,6 @@ 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
diff --git a/plugins/mod_pep_simple.lua b/plugins/mod_pep_simple.lua
index f0b5d7ef..f91e5448 100644
--- a/plugins/mod_pep_simple.lua
+++ b/plugins/mod_pep_simple.lua
@@ -14,6 +14,7 @@ local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed
local pairs = pairs;
local next = next;
local type = type;
+local unpack = table.unpack or unpack; -- luacheck: ignore 113
local calculate_hash = require "util.caps".calculate_hash;
local core_post_stanza = prosody.core_post_stanza;
local bare_sessions = prosody.bare_sessions;
diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua
index 268a2f0c..f7f458ca 100644
--- a/plugins/mod_presence.lua
+++ b/plugins/mod_presence.lua
@@ -81,8 +81,14 @@ function handle_normal_presence(origin, stanza)
res.presence.attr.to = nil;
end
end
- for jid in pairs(roster[false].pending) do -- resend incoming subscription requests
- origin.send(st.presence({type="subscribe", from=jid})); -- TODO add to attribute? Use original?
+ for jid, pending_request in pairs(roster[false].pending) do -- resend incoming subscription requests
+ if type(pending_request) == "table" then
+ local subscribe = st.deserialize(pending_request);
+ subscribe.attr.type, subscribe.attr.from = "subscribe", jid;
+ origin.send(subscribe);
+ else
+ origin.send(st.presence({type="subscribe", from=jid}));
+ end
end
local request = st.presence({type="subscribe", from=origin.username.."@"..origin.host});
for jid, item in pairs(roster) do -- resend outgoing subscription requests
@@ -226,7 +232,7 @@ function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_b
else
core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- acknowledging receipt
if not rostermanager.is_contact_pending_in(node, host, from_bare) then
- if rostermanager.set_contact_pending_in(node, host, from_bare) then
+ if rostermanager.set_contact_pending_in(node, host, from_bare, stanza) then
sessionmanager.send_to_available_resources(node, host, stanza);
end -- TODO else return error, unable to save
end
diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua
index 855c5fd2..05f80365 100644
--- a/plugins/mod_pubsub/mod_pubsub.lua
+++ b/plugins/mod_pubsub/mod_pubsub.lua
@@ -75,7 +75,7 @@ function simple_broadcast(kind, node, jids, item, actor, node_obj)
local msg_type = node_obj and node_obj.config.message_type or "headline";
local message = st.message({ from = module.host, type = msg_type, id = id })
:tag("event", { xmlns = xmlns_pubsub_event })
- :tag(kind, { node = node })
+ :tag(kind, { node = node });
if item then
message:add_child(item);
@@ -101,11 +101,12 @@ function simple_broadcast(kind, node, jids, item, actor, node_obj)
end
local max_max_items = module:get_option_number("pubsub_max_items", 256);
-function check_node_config(node, actor, new_config) -- luacheck: ignore 212/actor 212/node
+function check_node_config(node, actor, new_config) -- luacheck: ignore 212/node 212/actor
if (new_config["max_items"] or 1) > max_max_items then
return false;
end
- if new_config["access_model"] ~= "whitelist" and new_config["access_model"] ~= "open" then
+ if new_config["access_model"] ~= "whitelist"
+ and new_config["access_model"] ~= "open" then
return false;
end
return true;
diff --git a/plugins/mod_s2s/mod_s2s.lua b/plugins/mod_s2s/mod_s2s.lua
index aae37b7f..79308847 100644
--- a/plugins/mod_s2s/mod_s2s.lua
+++ b/plugins/mod_s2s/mod_s2s.lua
@@ -595,8 +595,7 @@ local function initialize_session(session)
if data then
local ok, err = stream:feed(data);
if ok then return; end
- log("warn", "Received invalid XML: %s", data);
- log("warn", "Problem was: %s", err);
+ log("debug", "Received invalid XML (%s) %d bytes: %q", tostring(err), #data, data:sub(1, 300));
session:close("not-well-formed");
end
end
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua
index fba84ef8..ba30b9e6 100644
--- a/plugins/mod_saslauth.lua
+++ b/plugins/mod_saslauth.lua
@@ -275,7 +275,8 @@ module:hook("stream-features", function(event)
if mechanisms[1] then
features:add_child(mechanisms);
elseif not next(sasl_mechanisms) then
- log("warn", "No available SASL mechanisms, verify that the configured authentication module is working");
+ local authmod = module:get_option_string("authentication", "internal_plain");
+ log("error", "No available SASL mechanisms, verify that the configured authentication module '%s' is loaded and configured correctly", authmod);
else
log("warn", "All available authentication mechanisms are either disabled or not suitable for an insecure connection");
end
diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua
index 71205ee0..745e394b 100644
--- a/plugins/mod_storage_memory.lua
+++ b/plugins/mod_storage_memory.lua
@@ -23,6 +23,10 @@ local function _purge_store(self, username)
return true;
end
+local function _users(self)
+ return next, self.store, nil;
+end
+
local keyval_store = {};
keyval_store.__index = keyval_store;
@@ -40,9 +44,13 @@ end
keyval_store.purge = _purge_store;
+keyval_store.users = _users;
+
local archive_store = {};
archive_store.__index = archive_store;
+archive_store.users = _users;
+
function archive_store:append(username, key, value, when, with)
if is_stanza(value) then
value = st.preserialize(value);
diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua
index 56cef569..5c0c0208 100644
--- a/plugins/mod_storage_sql.lua
+++ b/plugins/mod_storage_sql.lua
@@ -11,7 +11,7 @@ local is_stanza = require"util.stanza".is_stanza;
local t_concat = table.concat;
local noop = function() end
-local unpack = table.unpack or unpack;
+local unpack = table.unpack or unpack; -- luacheck: ignore 113
local function iterator(result)
return function(result_)
local row = result_();
diff --git a/plugins/mod_tls.lua b/plugins/mod_tls.lua
index 029ddd1d..4ead60dc 100644
--- a/plugins/mod_tls.lua
+++ b/plugins/mod_tls.lua
@@ -35,9 +35,10 @@ local host = hosts[module.host];
local ssl_ctx_c2s, ssl_ctx_s2sout, ssl_ctx_s2sin;
local ssl_cfg_c2s, ssl_cfg_s2sout, ssl_cfg_s2sin;
+local err_c2s, err_s2sin, err_s2sout;
function module.load()
- local NULL, err = {};
+ local NULL = {};
local modhost = module.host;
local parent = modhost:match("%.(.*)$");
@@ -52,14 +53,14 @@ function module.load()
local parent_s2s = rawgetopt(parent, "s2s_ssl") or NULL;
local host_s2s = rawgetopt(modhost, "s2s_ssl") or parent_s2s;
- ssl_ctx_c2s, err, ssl_cfg_c2s = create_context(host.host, "server", host_c2s, host_ssl, global_c2s); -- for incoming client connections
- if not ssl_ctx_c2s then module:log("error", "Error creating context for c2s: %s", err); end
+ ssl_ctx_c2s, err_c2s, ssl_cfg_c2s = create_context(host.host, "server", host_c2s, host_ssl, global_c2s); -- for incoming client connections
+ if not ssl_ctx_c2s then module:log("error", "Error creating context for c2s: %s", err_c2s); end
- ssl_ctx_s2sout, err, ssl_cfg_s2sout = create_context(host.host, "client", host_s2s, host_ssl, global_s2s); -- for outgoing server connections
- if not ssl_ctx_s2sout then module:log("error", "Error creating contexts for s2sout: %s", err); end
+ ssl_ctx_s2sout, err_s2sout, ssl_cfg_s2sout = create_context(host.host, "client", host_s2s, host_ssl, global_s2s); -- for outgoing server connections
+ if not ssl_ctx_s2sout then module:log("error", "Error creating contexts for s2sout: %s", err_s2sout); end
- ssl_ctx_s2sin, err, ssl_cfg_s2sin = create_context(host.host, "server", host_s2s, host_ssl, global_s2s); -- for incoming server connections
- if not ssl_ctx_s2sin then module:log("error", "Error creating contexts for s2sin: %s", err); end
+ ssl_ctx_s2sin, err_s2sin, ssl_cfg_s2sin = create_context(host.host, "server", host_s2s, host_ssl, global_s2s); -- for incoming server connections
+ if not ssl_ctx_s2sin then module:log("error", "Error creating contexts for s2sin: %s", err_s2sin); end
end
module:hook_global("config-reloaded", module.load);
@@ -74,12 +75,21 @@ local function can_do_tls(session)
return session.ssl_ctx;
end
if session.type == "c2s_unauthed" then
+ if not ssl_ctx_c2s and c2s_require_encryption then
+ session.log("error", "No TLS context available for c2s. Earlier error was: %s", err_c2s);
+ end
session.ssl_ctx = ssl_ctx_c2s;
session.ssl_cfg = ssl_cfg_c2s;
elseif session.type == "s2sin_unauthed" and allow_s2s_tls then
+ if not ssl_ctx_s2sin and s2s_require_encryption then
+ session.log("error", "No TLS context available for s2sin. Earlier error was: %s", err_s2sin);
+ end
session.ssl_ctx = ssl_ctx_s2sin;
session.ssl_cfg = ssl_cfg_s2sin;
elseif session.direction == "outgoing" and allow_s2s_tls then
+ if not ssl_ctx_s2sout and s2s_require_encryption then
+ session.log("error", "No TLS context available for s2sout. Earlier error was: %s", err_s2sout);
+ end
session.ssl_ctx = ssl_ctx_s2sout;
session.ssl_cfg = ssl_cfg_s2sout;
else
diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua
index b4aba338..008f6823 100644
--- a/plugins/mod_websocket.lua
+++ b/plugins/mod_websocket.lua
@@ -29,18 +29,10 @@ local t_concat = table.concat;
local stream_close_timeout = module:get_option_number("c2s_close_timeout", 5);
local consider_websocket_secure = module:get_option_boolean("consider_websocket_secure");
-local cross_domain = module:get_option_set("cross_domain_websocket", {});
-if cross_domain:contains("*") or cross_domain:contains(true) then
- cross_domain = true;
+local cross_domain = module:get_option("cross_domain_websocket");
+if cross_domain ~= nil then
+ module:log("info", "The 'cross_domain_websocket' option has been deprecated");
end
-
-local function check_origin(origin)
- if cross_domain == true then
- return true;
- end
- return cross_domain:contains(origin);
-end
-
local xmlns_framing = "urn:ietf:params:xml:ns:xmpp-framing";
local xmlns_streams = "http://etherx.jabber.org/streams";
local xmlns_client = "jabber:client";
@@ -158,11 +150,6 @@ function handle_request(event)
return 501;
end
- if not check_origin(request.headers.origin or "") then
- module:log("debug", "Origin %s is not allowed by 'cross_domain_websocket' [ %s ]", request.headers.origin or "(missing header)", cross_domain);
- return 403;
- end
-
local function websocket_close(code, message)
conn:write(build_close(code, message));
conn:close();
@@ -329,27 +316,4 @@ module:provides("http", {
function module.add_host(module)
module:hook("c2s-read-timeout", keepalive, -0.9);
-
- if cross_domain ~= true then
- local url = require "socket.url";
- local ws_url = module:http_url("websocket", "xmpp-websocket");
- local url_components = url.parse(ws_url);
- -- The 'Origin' consists of the base URL without path
- url_components.path = nil;
- local this_origin = url.build(url_components);
- local local_cross_domain = module:get_option_set("cross_domain_websocket", { this_origin });
- if local_cross_domain:contains(true) then
- module:log("error", "cross_domain_websocket = true only works in the global section");
- return;
- end
-
- -- Don't add / remove something added by another host
- -- This might be weird with random load order
- local_cross_domain:exclude(cross_domain);
- cross_domain:include(local_cross_domain);
- module:log("debug", "cross_domain = %s", tostring(cross_domain));
- function module.unload()
- cross_domain:exclude(local_cross_domain);
- end
- end
end
diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua
index 954bae92..89e67744 100644
--- a/plugins/muc/mod_muc.lua
+++ b/plugins/muc/mod_muc.lua
@@ -453,7 +453,7 @@ for event_name, method in pairs {
if room == nil then
-- Watch presence to create rooms
- if stanza.attr.type == nil and stanza.name == "presence" then
+ if stanza.attr.type == nil and stanza.name == "presence" and stanza:get_child("x", "http://jabber.org/protocol/muc") then
room = muclib.new_room(room_jid);
return room:handle_first_presence(origin, stanza);
elseif stanza.attr.type ~= "error" then
diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua
index 9648ea78..a8d3d790 100644
--- a/plugins/muc/muc.lib.lua
+++ b/plugins/muc/muc.lib.lua
@@ -23,6 +23,7 @@ local resourceprep = require "util.encodings".stringprep.resourceprep;
local st = require "util.stanza";
local base64 = require "util.encodings".base64;
local md5 = require "util.hashes".md5;
+local new_id = require "util.id".medium;
local log = module._log;
@@ -39,7 +40,7 @@ function room_mt:__tostring()
end
function room_mt.save()
- -- overriden by mod_muc.lua
+ -- overridden by mod_muc.lua
end
function room_mt:get_occupant_jid(real_jid)
@@ -279,7 +280,7 @@ function room_mt:publicise_occupant_status(occupant, x, nick, actor, reason)
self_p = st.clone(base_presence):add_child(self_x);
end
- -- General populance
+ -- General populace
for occupant_nick, n_occupant in self:each_occupant() do
if occupant_nick ~= occupant.nick then
local pr;
@@ -428,13 +429,6 @@ module:hook("muc-occupant-pre-change", function(event)
end, 1);
function room_mt:handle_first_presence(origin, stanza)
- if not stanza:get_child("x", "http://jabber.org/protocol/muc") then
- module:log("debug", "Room creation without <x>, possibly desynced");
-
- origin.send(st.error_reply(stanza, "cancel", "item-not-found"));
- return true;
- end
-
local real_jid = stanza.attr.from;
local dest_jid = stanza.attr.to;
local bare_jid = jid_bare(real_jid);
@@ -609,7 +603,7 @@ function room_mt:handle_normal_presence(origin, stanza)
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
+ dest_nick = nil; -- set dest_nick to nil; so general populace doesn't see it for whole orig_occupant
end
end
@@ -967,7 +961,7 @@ function room_mt:handle_admin_query_get_command(origin, stanza)
local _aff_rank = valid_affiliations[_aff or "none"];
local _rol = item.attr.role;
if _aff and _aff_rank and not _rol then
- -- You need to be at least an admin, and be requesting info about your affifiliation or lower
+ -- You need to be at least an admin, and be requesting info about your affiliation or lower
-- e.g. an admin can't ask for a list of owners
local affiliation_rank = valid_affiliations[affiliation or "none"];
if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank)
@@ -1044,6 +1038,9 @@ end
function room_mt:handle_groupchat_to_room(origin, stanza)
local from = stanza.attr.from;
local occupant = self:get_occupant_by_real_jid(from);
+ if not stanza.attr.id then
+ stanza.attr.id = new_id()
+ end
if module:fire_event("muc-occupant-groupchat", {
room = self; origin = origin; stanza = stanza; from = from; occupant = occupant;
}) then return true; end
@@ -1292,7 +1289,7 @@ function room_mt:set_affiliation(actor, jid, affiliation, reason, data)
-- Outcast can be by host.
is_host_only and affiliation == "outcast" and select(2, jid_split(occupant.bare_jid)) == host
) then
- -- need to publcize in all cases; as affiliation in <item/> has changed.
+ -- need to publicize in all cases; as affiliation in <item/> has changed.
occupants_updated[occupant] = occupant.role;
if occupant.role ~= role and (
is_downgrade or
@@ -1371,6 +1368,42 @@ function room_mt:get_role(nick)
return occupant and occupant.role or nil;
end
+function room_mt:may_set_role(actor, occupant, role)
+ local event = {
+ room = self,
+ actor = actor,
+ occupant = occupant,
+ role = role,
+ };
+
+ module:fire_event("muc-pre-set-role", event);
+ if event.allowed ~= nil then
+ return event.allowed, event.error, event.condition;
+ end
+
+ -- Can't do anything to other owners or admins
+ local occupant_affiliation = self:get_affiliation(occupant.bare_jid);
+ if occupant_affiliation == "owner" or 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
+
+ return true;
+end
+
function room_mt:set_role(actor, occupant_jid, role, reason)
if not actor then return nil, "modify", "not-acceptable"; end
@@ -1385,24 +1418,9 @@ function room_mt:set_role(actor, occupant_jid, role, reason)
if actor == true then
actor = nil -- So we can pass it safely to 'publicise_occupant_status' below
else
- -- Can't do anything to other owners or admins
- local occupant_affiliation = self:get_affiliation(occupant.bare_jid);
- if occupant_affiliation == "owner" or 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";
+ local allowed, err, condition = self:may_set_role(actor, occupant, role)
+ if not allowed then
+ return allowed, err, condition;
end
end
diff --git a/plugins/muc/subject.lib.lua b/plugins/muc/subject.lib.lua
index 938abf61..c8b99cc7 100644
--- a/plugins/muc/subject.lib.lua
+++ b/plugins/muc/subject.lib.lua
@@ -94,6 +94,12 @@ module:hook("muc-occupant-groupchat", function(event)
local stanza = event.stanza;
local subject = stanza:get_child("subject");
if subject then
+ if stanza:get_child("body") or stanza:get_child("thread") then
+ -- Note: A message with a <subject/> and a <body/> or a <subject/> and
+ -- a <thread/> is a legitimate message, but it SHALL NOT be interpreted
+ -- as a subject change.
+ return;
+ end
local room = event.room;
local occupant = event.occupant;
-- Role check for subject changes