aboutsummaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/adhoc/adhoc.lib.lua2
-rw-r--r--plugins/adhoc/mod_adhoc.lua4
-rw-r--r--plugins/mod_admin_shell.lua121
-rw-r--r--plugins/mod_c2s.lua6
-rw-r--r--plugins/mod_csi_simple.lua5
-rw-r--r--plugins/mod_debug_stanzas/watcher.lib.lua220
-rw-r--r--plugins/mod_mam/mod_mam.lua8
-rw-r--r--plugins/mod_s2s.lua53
-rw-r--r--plugins/mod_s2s_auth_certs.lua6
-rw-r--r--plugins/mod_saslauth.lua21
-rw-r--r--plugins/mod_smacks.lua86
-rw-r--r--plugins/mod_storage_sql.lua3
-rw-r--r--plugins/mod_tls.lua12
13 files changed, 448 insertions, 99 deletions
diff --git a/plugins/adhoc/adhoc.lib.lua b/plugins/adhoc/adhoc.lib.lua
index 4cf6911d..eb91f252 100644
--- a/plugins/adhoc/adhoc.lib.lua
+++ b/plugins/adhoc/adhoc.lib.lua
@@ -34,6 +34,8 @@ function _M.handle_cmd(command, origin, stanza)
local cmdtag = stanza.tags[1]
local sessionid = cmdtag.attr.sessionid or uuid.generate();
local dataIn = {
+ origin = origin;
+ stanza = stanza;
to = stanza.attr.to;
from = stanza.attr.from;
action = cmdtag.attr.action or "execute";
diff --git a/plugins/adhoc/mod_adhoc.lua b/plugins/adhoc/mod_adhoc.lua
index 09a72075..9d6ff77a 100644
--- a/plugins/adhoc/mod_adhoc.lua
+++ b/plugins/adhoc/mod_adhoc.lua
@@ -79,12 +79,12 @@ module:hook("iq-set/host/"..xmlns_cmd..":command", function (event)
or (command.permission == "global_admin" and not global_admin)
or (command.permission == "local_user" and hostname ~= module.host) then
origin.send(st.error_reply(stanza, "auth", "forbidden", "You don't have permission to execute this command"):up()
- :add_child(commands[node]:cmdtag("canceled")
+ :add_child(command:cmdtag("canceled")
:tag("note", {type="error"}):text("You don't have permission to execute this command")));
return true
end
-- User has permission now execute the command
- adhoc_handle_cmd(commands[node], origin, stanza);
+ adhoc_handle_cmd(command, origin, stanza);
return true;
end
end, 500);
diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua
index 94530cf1..4033d868 100644
--- a/plugins/mod_admin_shell.lua
+++ b/plugins/mod_admin_shell.lua
@@ -36,6 +36,7 @@ local serialization = require "util.serialization";
local serialize_config = serialization.new ({ fatal = false, unquoted = true});
local time = require "util.time";
local promise = require "util.promise";
+local logger = require "util.logger";
local t_insert = table.insert;
local t_concat = table.concat;
@@ -83,8 +84,8 @@ function runner_callbacks:error(err)
self.data.print("Error: "..tostring(err));
end
-local function send_repl_output(session, line)
- return session.send(st.stanza("repl-output"):text(tostring(line)));
+local function send_repl_output(session, line, attr)
+ return session.send(st.stanza("repl-output", attr):text(tostring(line)));
end
function console:new_session(admin_session)
@@ -99,8 +100,14 @@ function console:new_session(admin_session)
end
return send_repl_output(admin_session, table.concat(t, "\t"));
end;
+ write = function (t)
+ return send_repl_output(admin_session, t, { eol = "0" });
+ end;
serialize = tostring;
disconnect = function () admin_session:close(); end;
+ is_connected = function ()
+ return not not admin_session.conn;
+ end
};
session.env = setmetatable({}, default_env_mt);
@@ -126,6 +133,11 @@ local function handle_line(event)
session = console:new_session(event.origin);
event.origin.shell_session = session;
end
+
+ local default_width = 132; -- The common default of 80 is a bit too narrow for e.g. s2s:show(), 132 was another common width for hardware terminals
+ local margin = 2; -- To account for '| ' when lines are printed
+ session.width = (tonumber(event.stanza.attr.width) or default_width)-margin;
+
local line = event.stanza:get_text();
local useglobalenv;
@@ -212,7 +224,7 @@ function commands.help(session, data)
print [[Commands are divided into multiple sections. For help on a particular section, ]]
print [[type: help SECTION (for example, 'help c2s'). Sections are: ]]
print [[]]
- local row = format_table({ { title = "Section"; width = 7 }; { title = "Description"; width = "100%" } })
+ local row = format_table({ { title = "Section", width = 7 }, { title = "Description", width = "100%" } }, session.width)
print(row())
print(row { "c2s"; "Commands to manage local client-to-server sessions" })
print(row { "s2s"; "Commands to manage sessions between this server and others" })
@@ -228,6 +240,7 @@ function commands.help(session, data)
print(row { "dns"; "Commands to manage and inspect the internal DNS resolver" })
print(row { "xmpp"; "Commands for sending XMPP stanzas" })
print(row { "debug"; "Commands for debugging the server" })
+ print(row { "watch"; "Commands for watching live logs from the server" })
print(row { "config"; "Reloading the configuration, etc." })
print(row { "columns"; "Information about customizing session listings" })
print(row { "console"; "Help regarding the console itself" })
@@ -304,6 +317,9 @@ function commands.help(session, data)
print [[debug:logevents(host) - Enable logging of fired events on host]]
print [[debug:events(host, event) - Show registered event handlers]]
print [[debug:timers() - Show information about scheduled timers]]
+ elseif section == "watch" then
+ print [[watch:log() - Follow debug logs]]
+ print [[watch:stanzas(target, filter) - Watch live stanzas matching the specified target and filter]]
elseif section == "console" then
print [[Hey! Welcome to Prosody's admin console.]]
print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]]
@@ -334,7 +350,7 @@ function commands.help(session, data)
meta_columns[2].width = math.max(meta_columns[2].width or 0, #(spec.title or ""));
meta_columns[3].width = math.max(meta_columns[3].width or 0, #(spec.description or ""));
end
- local row = format_table(meta_columns, 120)
+ local row = format_table(meta_columns, session.width)
print(row());
for column, spec in iterators.sorted_pairs(available_columns) do
print(row({ column, spec.title, spec.description }));
@@ -480,6 +496,16 @@ function def_env.module:info(name, hosts)
local function item_name(item) return item.name; end
+ local function task_timefmt(t)
+ if not t then
+ return "no last run time"
+ elseif os.difftime(os.time(), t) < 86400 then
+ return os.date("last run today at %H:%M", t);
+ else
+ return os.date("last run %A at %H:%M", t);
+ end
+ end
+
local friendly_descriptions = {
["adhoc-provider"] = "Ad-hoc commands",
["auth-provider"] = "Authentication provider",
@@ -497,12 +523,22 @@ function def_env.module:info(name, hosts)
["auth-provider"] = item_name,
["storage-provider"] = item_name,
["http-provider"] = function(item, mod) return mod:http_url(item.name, item.default_path); end,
- ["net-provider"] = item_name,
+ ["net-provider"] = function(item)
+ local service_name = item.name;
+ local ports_list = {};
+ for _, interface, port in portmanager.get_active_services():iter(service_name, nil, nil) do
+ table.insert(ports_list, "["..interface.."]:"..port);
+ end
+ if not ports_list[1] then
+ return service_name..": not listening on any ports";
+ end
+ return service_name..": "..table.concat(ports_list, ", ");
+ end,
["measure"] = function(item) return item.name .. " (" .. suf(item.conf and item.conf.unit, " ") .. item.type .. ")"; end,
["metric"] = function(item)
return ("%s (%s%s)%s"):format(item.name, suf(item.mf.unit, " "), item.mf.type_, pre(": ", item.mf.description));
end,
- ["task"] = function (item) return string.format("%s (%s)", item.name or item.id, item.when); end
+ ["task"] = function (item) return string.format("%s (%s, %s)", item.name or item.id, item.when, task_timefmt(item.last)); end
};
for host in hosts do
@@ -800,9 +836,7 @@ available_columns = {
mapper = function(conn, session)
if not session.secure then return "insecure"; end
if not conn or not conn:ssl() then return "secure" end
- local sock = conn and conn:socket();
- if not sock then return "secure"; end
- local tls_info = sock.info and sock:info();
+ local tls_info = conn.ssl_info and conn:ssl_info();
return tls_info and tls_info.protocol or "secure";
end;
};
@@ -812,8 +846,7 @@ available_columns = {
width = 30;
key = "conn";
mapper = function(conn)
- local sock = conn:socket();
- local info = sock and sock.info and sock:info();
+ local info = conn and conn.ssl_info and conn:ssl_info();
if info then return info.cipher end
end;
};
@@ -931,7 +964,7 @@ end
function def_env.c2s:show(match_jid, colspec)
local print = self.session.print;
local columns = get_colspec(colspec, { "id"; "jid"; "ipv"; "status"; "secure"; "smacks"; "csi" });
- local row = format_table(columns, 120);
+ local row = format_table(columns, self.session.width);
local function match(session)
local jid = get_jid(session)
@@ -1014,7 +1047,7 @@ end
function def_env.s2s:show(match_jid, colspec)
local print = self.session.print;
local columns = get_colspec(colspec, { "id"; "host"; "dir"; "remote"; "ipv"; "secure"; "s2s_sasl"; "dialback" });
- local row = format_table(columns, 132);
+ local row = format_table(columns, self.session.width);
local function match(session)
local host, remote = get_s2s_hosts(session);
@@ -1500,7 +1533,7 @@ function def_env.xmpp:ping(localhost, remotehost, timeout)
module:unhook("s2sin-established", onestablished);
module:unhook("s2s-destroyed", ondestroyed);
end):next(function(pong)
- return ("pong from %s in %gs"):format(pong.stanza.attr.from, time.now() - time_start);
+ return ("pong from %s on %s in %gs"):format(pong.stanza.attr.from, pong.origin.id, time.now() - time_start);
end);
end
@@ -1552,7 +1585,7 @@ function def_env.http:list(hosts)
local output = format_table({
{ title = "Module", width = "20%" },
{ title = "URL", width = "80%" },
- }, 132);
+ }, self.session.width);
for _, host in ipairs(hosts) do
local http_apps = modulemanager.get_items("http-provider", host);
@@ -1583,6 +1616,60 @@ function def_env.http:list(hosts)
return true;
end
+def_env.watch = {};
+
+function def_env.watch:log()
+ local writing = false;
+ local sink = logger.add_simple_sink(function (source, level, message)
+ if writing then return; end
+ writing = true;
+ self.session.print(source, level, message);
+ writing = false;
+ end);
+
+ while self.session.is_connected() do
+ async.sleep(3);
+ end
+ if not logger.remove_sink(sink) then
+ module:log("warn", "Unable to remove watch:log() sink");
+ end
+end
+
+local stanza_watchers = module:require("mod_debug_stanzas/watcher");
+function def_env.watch:stanzas(target_spec, filter_spec)
+ local function handler(event_type, stanza, session)
+ if stanza then
+ if event_type == "sent" then
+ self.session.print(("\n<!-- sent to %s -->"):format(session.id));
+ elseif event_type == "received" then
+ self.session.print(("\n<!-- received from %s -->"):format(session.id));
+ else
+ self.session.print(("\n<!-- %s (%s) -->"):format(event_type, session.id));
+ end
+ self.session.print(stanza);
+ elseif session then
+ self.session.print("\n<!-- session "..session.id.." "..event_type.." -->");
+ elseif event_type then
+ self.session.print("\n<!-- "..event_type.." -->");
+ end
+ end
+
+ stanza_watchers.add({
+ target_spec = {
+ jid = target_spec;
+ };
+ filter_spec = filter_spec and {
+ with_jid = filter_spec;
+ };
+ }, handler);
+
+ while self.session.is_connected() do
+ async.sleep(3);
+ end
+
+ stanza_watchers.remove(handler);
+end
+
def_env.debug = {};
function def_env.debug:logevents(host)
@@ -1926,6 +2013,10 @@ function def_env.stats:show(name_filter)
end
+function module.unload()
+ stanza_watchers.cleanup();
+end
+
-------------
diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua
index c8f54fa7..8c0844ae 100644
--- a/plugins/mod_c2s.lua
+++ b/plugins/mod_c2s.lua
@@ -117,8 +117,7 @@ function stream_callbacks._streamopened(session, attr)
session.secure = true;
session.encrypted = true;
- local sock = session.conn:socket();
- local info = sock.info and sock:info();
+ local info = session.conn:ssl_info();
if type(info) == "table" then
(session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher);
session.compressed = info.compression;
@@ -295,8 +294,7 @@ function listener.onconnect(conn)
session.encrypted = true;
-- Check if TLS compression is used
- local sock = conn:socket();
- local info = sock.info and sock:info();
+ local info = conn:ssl_info();
if type(info) == "table" then
(session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher);
session.compressed = info.compression;
diff --git a/plugins/mod_csi_simple.lua b/plugins/mod_csi_simple.lua
index 569916b0..b9a470f5 100644
--- a/plugins/mod_csi_simple.lua
+++ b/plugins/mod_csi_simple.lua
@@ -116,6 +116,9 @@ local flush_reasons = module:metric(
{ "reason" }
);
+local flush_sizes = module:metric("histogram", "flush_stanza_count", "", "Number of stanzas flushed at once", {},
+ { buckets = { 0, 1, 2, 4, 8, 16, 32, 64, 128, 256 } }):with_labels();
+
local function manage_buffer(stanza, session)
local ctr = session.csi_counter or 0;
if session.state ~= "inactive" then
@@ -129,6 +132,7 @@ local function manage_buffer(stanza, session)
session.csi_measure_buffer_hold = nil;
end
flush_reasons:with_labels(why or "important"):add(1);
+ flush_sizes:sample(ctr);
session.log("debug", "Flushing buffer (%s; queue size is %d)", why or "important", session.csi_counter);
session.state = "flushing";
module:fire_event("csi-flushing", { session = session });
@@ -147,6 +151,7 @@ local function flush_buffer(data, session)
session.log("debug", "Flushing buffer (%s; queue size is %d)", "client activity", session.csi_counter);
session.state = "flushing";
module:fire_event("csi-flushing", { session = session });
+ flush_sizes:sample(ctr);
flush_reasons:with_labels("client activity"):add(1);
if session.csi_measure_buffer_hold then
session.csi_measure_buffer_hold();
diff --git a/plugins/mod_debug_stanzas/watcher.lib.lua b/plugins/mod_debug_stanzas/watcher.lib.lua
new file mode 100644
index 00000000..e21fc946
--- /dev/null
+++ b/plugins/mod_debug_stanzas/watcher.lib.lua
@@ -0,0 +1,220 @@
+local filters = require "util.filters";
+local jid = require "util.jid";
+local set = require "util.set";
+
+local client_watchers = {};
+
+-- active_filters[session] = {
+-- filter_func = filter_func;
+-- downstream = { cb1, cb2, ... };
+-- }
+local active_filters = {};
+
+local function subscribe_session_stanzas(session, handler, reason)
+ if active_filters[session] then
+ table.insert(active_filters[session].downstream, handler);
+ if reason then
+ handler(reason, nil, session);
+ end
+ return;
+ end
+ local downstream = { handler };
+ active_filters[session] = {
+ filter_in = function (stanza)
+ module:log("debug", "NOTIFY WATCHER %d", #downstream);
+ for i = 1, #downstream do
+ downstream[i]("received", stanza, session);
+ end
+ return stanza;
+ end;
+ filter_out = function (stanza)
+ module:log("debug", "NOTIFY WATCHER %d", #downstream);
+ for i = 1, #downstream do
+ downstream[i]("sent", stanza, session);
+ end
+ return stanza;
+ end;
+ downstream = downstream;
+ };
+ filters.add_filter(session, "stanzas/in", active_filters[session].filter_in);
+ filters.add_filter(session, "stanzas/out", active_filters[session].filter_out);
+ if reason then
+ handler(reason, nil, session);
+ end
+end
+
+local function unsubscribe_session_stanzas(session, handler, reason)
+ local active_filter = active_filters[session];
+ if not active_filter then
+ return;
+ end
+ for i = #active_filter.downstream, 1, -1 do
+ if active_filter.downstream[i] == handler then
+ table.remove(active_filter.downstream, i);
+ if reason then
+ handler(reason, nil, session);
+ end
+ end
+ end
+ if #active_filter.downstream == 0 then
+ filters.remove_filter(session, "stanzas/in", active_filter.filter_in);
+ filters.remove_filter(session, "stanzas/out", active_filter.filter_out);
+ end
+ active_filters[session] = nil;
+end
+
+local function unsubscribe_all_from_session(session, reason)
+ local active_filter = active_filters[session];
+ if not active_filter then
+ return;
+ end
+ for i = #active_filter.downstream, 1, -1 do
+ local handler = table.remove(active_filter.downstream, i);
+ if reason then
+ handler(reason, nil, session);
+ end
+ end
+ filters.remove_filter(session, "stanzas/in", active_filter.filter_in);
+ filters.remove_filter(session, "stanzas/out", active_filter.filter_out);
+ active_filters[session] = nil;
+end
+
+local function unsubscribe_handler_from_all(handler, reason)
+ for session in pairs(active_filters) do
+ unsubscribe_session_stanzas(session, handler, reason);
+ end
+end
+
+local s2s_watchers = {};
+
+module:hook("s2sin-established", function (event)
+ for _, watcher in ipairs(s2s_watchers) do
+ if watcher.target_spec == event.session.from_host then
+ subscribe_session_stanzas(event.session, watcher.handler, "opened");
+ end
+ end
+end);
+
+module:hook("s2sout-established", function (event)
+ for _, watcher in ipairs(s2s_watchers) do
+ if watcher.target_spec == event.session.to_host then
+ subscribe_session_stanzas(event.session, watcher.handler, "opened");
+ end
+ end
+end);
+
+module:hook("s2s-closed", function (event)
+ unsubscribe_all_from_session(event.session, "closed");
+end);
+
+local watched_hosts = set.new();
+
+local handler_map = setmetatable({}, { __mode = "kv" });
+
+local function add_stanza_watcher(spec, orig_handler)
+ local function filtering_handler(event_type, stanza, session)
+ if stanza and spec.filter_spec then
+ if spec.filter_spec.with_jid then
+ if event_type == "sent" and (not stanza.attr.from or not jid.compare(stanza.attr.from, spec.filter_spec.with_jid)) then
+ return;
+ elseif event_type == "received" and (not stanza.attr.to or not jid.compare(stanza.attr.to, spec.filter_spec.with_jid)) then
+ return;
+ end
+ end
+ end
+ return orig_handler(event_type, stanza, session);
+ end
+ handler_map[orig_handler] = filtering_handler;
+ if spec.target_spec.jid then
+ local target_is_remote_host = not jid.node(spec.target_spec.jid) and not prosody.hosts[spec.target_spec.jid];
+
+ if target_is_remote_host then
+ -- Watch s2s sessions
+ table.insert(s2s_watchers, {
+ target_spec = spec.target_spec.jid;
+ handler = filtering_handler;
+ orig_handler = orig_handler;
+ });
+
+ -- Scan existing s2sin for matches
+ for session in pairs(prosody.incoming_s2s) do
+ if spec.target_spec.jid == session.from_host then
+ subscribe_session_stanzas(session, filtering_handler, "attached");
+ end
+ end
+ -- Scan existing s2sout for matches
+ for local_host, local_session in pairs(prosody.hosts) do --luacheck: ignore 213/local_host
+ for remote_host, remote_session in pairs(local_session.s2sout) do
+ if spec.target_spec.jid == remote_host then
+ subscribe_session_stanzas(remote_session, filtering_handler, "attached");
+ end
+ end
+ end
+ else
+ table.insert(client_watchers, {
+ target_spec = spec.target_spec.jid;
+ handler = filtering_handler;
+ orig_handler = orig_handler;
+ });
+ local host = jid.host(spec.target_spec.jid);
+ if not watched_hosts:contains(host) and prosody.hosts[host] then
+ module:context(host):hook("resource-bind", function (event)
+ for _, watcher in ipairs(client_watchers) do
+ module:log("debug", "NEW CLIENT: %s vs %s", event.session.full_jid, watcher.target_spec);
+ if jid.compare(event.session.full_jid, watcher.target_spec) then
+ module:log("debug", "MATCH");
+ subscribe_session_stanzas(event.session, watcher.handler, "opened");
+ else
+ module:log("debug", "NO MATCH");
+ end
+ end
+ end);
+
+ module:context(host):hook("resource-unbind", function (event)
+ unsubscribe_all_from_session(event.session, "closed");
+ end);
+
+ watched_hosts:add(host);
+ end
+ for full_jid, session in pairs(prosody.full_sessions) do
+ if jid.compare(full_jid, spec.target_spec.jid) then
+ subscribe_session_stanzas(session, filtering_handler, "attached");
+ end
+ end
+ end
+ else
+ error("No recognized target selector");
+ end
+end
+
+local function remove_stanza_watcher(orig_handler)
+ local handler = handler_map[orig_handler];
+ unsubscribe_handler_from_all(handler, "detached");
+ handler_map[orig_handler] = nil;
+
+ for i = #client_watchers, 1, -1 do
+ if client_watchers[i].orig_handler == orig_handler then
+ table.remove(client_watchers, i);
+ end
+ end
+
+ for i = #s2s_watchers, 1, -1 do
+ if s2s_watchers[i].orig_handler == orig_handler then
+ table.remove(s2s_watchers, i);
+ end
+ end
+end
+
+local function cleanup(reason)
+ client_watchers = {};
+ s2s_watchers = {};
+ for session in pairs(active_filters) do
+ unsubscribe_all_from_session(session, reason or "cancelled");
+ end
+end
+
+return {
+ add = add_stanza_watcher;
+ remove = remove_stanza_watcher;
+ cleanup = cleanup;
+};
diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua
index 50095e2f..083ae90d 100644
--- a/plugins/mod_mam/mod_mam.lua
+++ b/plugins/mod_mam/mod_mam.lua
@@ -53,8 +53,12 @@ if not archive.find then
end
local use_total = module:get_option_boolean("mam_include_total", true);
-function schedule_cleanup()
- -- replaced later if cleanup is enabled
+function schedule_cleanup(_username, _date) -- luacheck: ignore 212
+ -- Called to make a note of which users have messages on which days, which in
+ -- turn is used to optimize the message expiry routine.
+ --
+ -- This noop is conditionally replaced later depending on retention settings
+ -- and storage backend capabilities.
end
-- Handle prefs.
diff --git a/plugins/mod_s2s.lua b/plugins/mod_s2s.lua
index e810c6cd..dd585ac7 100644
--- a/plugins/mod_s2s.lua
+++ b/plugins/mod_s2s.lua
@@ -146,17 +146,17 @@ local function bounce_sendq(session, reason)
elseif type(reason) == "string" then
reason_text = reason;
end
- for i, data in ipairs(sendq) do
- local reply = data[2];
- if reply and not(reply.attr.xmlns) and bouncy_stanzas[reply.name] then
- reply.attr.type = "error";
- reply:tag("error", {type = error_type, by = session.from_host})
- :tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
- if reason_text then
- reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"})
- :text("Server-to-server connection failed: "..reason_text):up();
- end
+ for i, stanza in ipairs(sendq) do
+ if not stanza.attr.xmlns and bouncy_stanzas[stanza.name] and stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
+ local reply = st.error_reply(
+ stanza,
+ error_type,
+ condition,
+ reason_text and ("Server-to-server connection failed: "..reason_text) or nil
+ );
core_process_stanza(dummy, reply);
+ else
+ (session.log or log)("debug", "Not eligible for bouncing, discarding %s", stanza:top_tag());
end
sendq[i] = nil;
end
@@ -182,15 +182,11 @@ function route_to_existing_session(event)
(host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host);
-- Queue stanza until we are able to send it
- local queued_item = {
- tostring(stanza),
- stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza);
- };
if host.sendq then
- t_insert(host.sendq, queued_item);
+ t_insert(host.sendq, st.clone(stanza));
else
-- luacheck: ignore 122
- host.sendq = { queued_item };
+ host.sendq = { st.clone(stanza) };
end
host.log("debug", "stanza [%s] queued ", stanza.name);
return true;
@@ -215,7 +211,7 @@ function route_to_new_session(event)
-- Store in buffer
host_session.bounce_sendq = bounce_sendq;
- host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} };
+ host_session.sendq = { st.clone(stanza) };
log("debug", "stanza [%s] queued until connection complete", stanza.name);
-- FIXME Cleaner solution to passing extra data from resolvers to net.server
-- This mt-clone allows resolvers to add extra data, currently used for DANE TLSA records
@@ -324,8 +320,8 @@ function mark_connected(session)
if sendq then
session.log("debug", "sending %d queued stanzas across new outgoing connection to %s", #sendq, session.to_host);
local send = session.sends2s;
- for i, data in ipairs(sendq) do
- send(data[1]);
+ for i, stanza in ipairs(sendq) do
+ send(stanza);
sendq[i] = nil;
end
session.sendq = nil;
@@ -389,10 +385,10 @@ end
--- Helper to check that a session peer's certificate is valid
local function check_cert_status(session)
local host = session.direction == "outgoing" and session.to_host or session.from_host
- local conn = session.conn:socket()
+ local conn = session.conn
local cert
- if conn.getpeercertificate then
- cert = conn:getpeercertificate()
+ if conn.ssl_peercertificate then
+ cert = conn:ssl_peercertificate()
end
return module:fire_event("s2s-check-certificate", { host = host, session = session, cert = cert });
@@ -404,8 +400,7 @@ local function session_secure(session)
session.secure = true;
session.encrypted = true;
- local sock = session.conn:socket();
- local info = sock.info and sock:info();
+ local info = session.conn:ssl_info();
if type(info) == "table" then
(session.log or log)("info", "Stream encrypted (%s with %s)", info.protocol, info.cipher);
session.compressed = info.compression;
@@ -935,6 +930,16 @@ local function friendly_cert_error(session) --> string
elseif cert_errors:contains("self signed certificate") then
return "is self-signed";
end
+
+ local chain_errors = set.new(session.cert_chain_errors[2]);
+ for i, e in pairs(session.cert_chain_errors) do
+ if i > 2 then chain_errors:add_list(e); end
+ end
+ if chain_errors:contains("certificate has expired") then
+ return "has an expired certificate chain";
+ elseif chain_errors:contains("No matching DANE TLSA records") then
+ return "does not match any DANE TLSA records";
+ end
end
return "is not trusted"; -- for some other reason
elseif session.cert_identity_status == "invalid" then
diff --git a/plugins/mod_s2s_auth_certs.lua b/plugins/mod_s2s_auth_certs.lua
index 992ee934..bde3cb82 100644
--- a/plugins/mod_s2s_auth_certs.lua
+++ b/plugins/mod_s2s_auth_certs.lua
@@ -9,7 +9,7 @@ local measure_cert_statuses = module:metric("counter", "checked", "", "Certifica
module:hook("s2s-check-certificate", function(event)
local session, host, cert = event.session, event.host, event.cert;
- local conn = session.conn:socket();
+ local conn = session.conn;
local log = session.log or log;
if not cert then
@@ -18,8 +18,8 @@ module:hook("s2s-check-certificate", function(event)
end
local chain_valid, errors;
- if conn.getpeerverification then
- chain_valid, errors = conn:getpeerverification();
+ if conn.ssl_peerverification then
+ chain_valid, errors = conn:ssl_peerverification();
else
chain_valid, errors = false, { { "Chain verification not supported by this version of LuaSec" } };
end
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua
index ab863aa3..0b350c74 100644
--- a/plugins/mod_saslauth.lua
+++ b/plugins/mod_saslauth.lua
@@ -242,7 +242,7 @@ module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:abort", function(event)
end);
local function tls_unique(self)
- return self.userdata["tls-unique"]:getpeerfinished();
+ return self.userdata["tls-unique"]:ssl_peerfinished();
end
local mechanisms_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' };
@@ -258,22 +258,23 @@ module:hook("stream-features", function(event)
end
local sasl_handler = usermanager_get_sasl_handler(module.host, origin)
origin.sasl_handler = sasl_handler;
+ local channel_bindings = set.new()
if origin.encrypted then
-- check whether LuaSec has the nifty binding to the function needed for tls-unique
-- FIXME: would be nice to have this check only once and not for every socket
if sasl_handler.add_cb_handler then
- local socket = origin.conn:socket();
- local info = socket.info and socket:info();
- if info.protocol == "TLSv1.3" then
+ local info = origin.conn:ssl_info();
+ if info and info.protocol == "TLSv1.3" then
log("debug", "Channel binding 'tls-unique' undefined in context of TLS 1.3");
- elseif socket.getpeerfinished and socket:getpeerfinished() then
+ elseif origin.conn.ssl_peerfinished and origin.conn:ssl_peerfinished() then
log("debug", "Channel binding 'tls-unique' supported");
sasl_handler:add_cb_handler("tls-unique", tls_unique);
+ channel_bindings:add("tls-unique");
else
log("debug", "Channel binding 'tls-unique' not supported (by LuaSec?)");
end
sasl_handler["userdata"] = {
- ["tls-unique"] = socket;
+ ["tls-unique"] = origin.conn;
};
else
log("debug", "Channel binding not supported by SASL handler");
@@ -305,6 +306,14 @@ module:hook("stream-features", function(event)
for mechanism in usable_mechanisms do
mechanisms:tag("mechanism"):text(mechanism):up();
end
+ if not channel_bindings:empty() then
+ -- XXX XEP-0440 is Experimental
+ mechanisms:tag("sasl-channel-binding", {xmlns='urn:xmpp:sasl-cb:0'})
+ for channel_binding in channel_bindings do
+ mechanisms:tag("channel-binding", {type=channel_binding}):up()
+ end
+ mechanisms:up();
+ end
features:add_child(mechanisms);
return;
end
diff --git a/plugins/mod_smacks.lua b/plugins/mod_smacks.lua
index 3a4c7b84..e2bbff9c 100644
--- a/plugins/mod_smacks.lua
+++ b/plugins/mod_smacks.lua
@@ -2,7 +2,7 @@
--
-- Copyright (C) 2010-2015 Matthew Wild
-- Copyright (C) 2010 Waqas Hussain
--- Copyright (C) 2012-2021 Kim Alvefur
+-- Copyright (C) 2012-2022 Kim Alvefur
-- Copyright (C) 2012 Thijs Alkemade
-- Copyright (C) 2014 Florian Zeitz
-- Copyright (C) 2016-2020 Thilo Molitor
@@ -10,6 +10,7 @@
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
+-- TODO unify sendq and smqueue
local tonumber = tonumber;
local tostring = tostring;
@@ -83,6 +84,22 @@ local all_old_sessions = module:open_store("smacks_h");
local old_session_registry = module:open_store("smacks_h", "map");
local session_registry = module:shared "/*/smacks/resumption-tokens"; -- > user@host/resumption-token --> resource
+local function track_session(session, id)
+ session_registry[jid.join(session.username, session.host, id or session.resumption_token)] = session;
+ session.resumption_token = id;
+end
+
+local function save_old_session(session)
+ session_registry[jid.join(session.username, session.host, session.resumption_token)] = nil;
+ return old_session_registry:set(session.username, session.resumption_token,
+ { h = session.handled_stanza_count; t = os.time() })
+end
+
+local function clear_old_session(session, id)
+ session_registry[jid.join(session.username, session.host, id or session.resumption_token)] = nil;
+ return old_session_registry:set(session.username, id or session.resumption_token, nil)
+end
+
local ack_errors = require"util.error".init("mod_smacks", xmlns_sm3, {
head = { condition = "undefined-condition"; text = "Client acknowledged more stanzas than sent by server" };
tail = { condition = "undefined-condition"; text = "Client acknowledged less stanzas than already acknowledged" };
@@ -155,13 +172,12 @@ end
local function request_ack(session, reason)
local queue = session.outgoing_stanza_queue;
- session.log("debug", "Sending <r> (inside timer, before send) from %s - #queue=%d", reason, queue:count_unacked());
+ session.log("debug", "Sending <r> from %s - #queue=%d", reason, queue:count_unacked());
session.awaiting_ack = true;
(session.sends2s or session.send)(st.stanza("r", { xmlns = session.smacks }))
if session.destroyed then return end -- sending something can trigger destruction
-- expected_h could be lower than this expression e.g. more stanzas added to the queue meanwhile)
session.last_requested_h = queue:count_acked() + queue:count_unacked();
- session.log("debug", "Sending <r> (inside timer, after send) from %s - #queue=%d", reason, queue:count_unacked());
if not session.delayed_ack_timer then
session.delayed_ack_timer = timer.add_task(delayed_ack_timeout, function()
ack_delayed(session, nil); -- we don't know if this is the only new stanza in the queue
@@ -234,8 +250,7 @@ module:hook("pre-session-close", function(event)
if session.smacks == nil then return end
if session.resumption_token then
session.log("debug", "Revoking resumption token");
- session_registry[jid.join(session.username, session.host, session.resumption_token)] = nil;
- old_session_registry:set(session.username, session.resumption_token, nil);
+ clear_old_session(session);
session.resumption_token = nil;
else
session.log("debug", "Session not resumable");
@@ -284,7 +299,7 @@ function handle_enable(session, stanza, xmlns_sm)
if session.username then
local old_sessions, err = all_old_sessions:get(session.username);
- module:log("debug", "Old sessions: %q", old_sessions)
+ session.log("debug", "Old sessions: %q", old_sessions)
if old_sessions then
local keep, count = {}, 0;
for token, info in it.sorted_pairs(old_sessions, function(a, b)
@@ -296,11 +311,11 @@ function handle_enable(session, stanza, xmlns_sm)
end
all_old_sessions:set(session.username, keep);
elseif err then
- module:log("error", "Unable to retrieve old resumption counters: %s", err);
+ session.log("error", "Unable to retrieve old resumption counters: %s", err);
end
end
- module:log("debug", "Enabling stream management");
+ session.log("debug", "Enabling stream management");
session.smacks = xmlns_sm;
wrap_session(session, false);
@@ -310,8 +325,7 @@ function handle_enable(session, stanza, xmlns_sm)
local resume = stanza.attr.resume;
if resume == "true" or resume == "1" then
resume_token = new_id();
- session_registry[jid.join(session.username, session.host, resume_token)] = session;
- session.resumption_token = resume_token;
+ track_session(session, resume_token);
resume_max = tostring(resume_timeout);
end
(session.sends2s or session.send)(st.stanza("enabled", { xmlns = xmlns_sm, id = resume_token, resume = resume, max = resume_max }));
@@ -320,29 +334,23 @@ end
module:hook_tag(xmlns_sm2, "enable", function (session, stanza) return handle_enable(session, stanza, xmlns_sm2); end, 100);
module:hook_tag(xmlns_sm3, "enable", function (session, stanza) return handle_enable(session, stanza, xmlns_sm3); end, 100);
-module:hook_tag("http://etherx.jabber.org/streams", "features",
- function (session, stanza)
- -- Needs to be done after flushing sendq since those aren't stored as
- -- stanzas and counting them is weird.
- -- TODO unify sendq and smqueue
- timer.add_task(1e-6, function ()
- if can_do_smacks(session) then
- if stanza:get_child("sm", xmlns_sm3) then
- session.sends2s(st.stanza("enable", sm3_attr));
- session.smacks = xmlns_sm3;
- elseif stanza:get_child("sm", xmlns_sm2) then
- session.sends2s(st.stanza("enable", sm2_attr));
- session.smacks = xmlns_sm2;
- else
- return;
- end
- wrap_session_out(session, false);
- end
- end);
- end);
+module:hook_tag("http://etherx.jabber.org/streams", "features", function(session, stanza)
+ if can_do_smacks(session) then
+ session.smacks_feature = stanza:get_child("sm", xmlns_sm3) or stanza:get_child("sm", xmlns_sm2);
+ end
+end);
+
+module:hook("s2sout-established", function (event)
+ local session = event.session;
+ if not session.smacks_feature then return end
+
+ session.smacks = session.smacks_feature.attr.xmlns;
+ wrap_session_out(session, false);
+ session.sends2s(st.stanza("enable", { xmlns = session.smacks }));
+end);
function handle_enabled(session, stanza, xmlns_sm) -- luacheck: ignore 212/stanza
- module:log("debug", "Enabling stream management");
+ session.log("debug", "Enabling stream management");
session.smacks = xmlns_sm;
wrap_session_in(session, false);
@@ -356,10 +364,10 @@ module:hook_tag(xmlns_sm3, "enabled", function (session, stanza) return handle_e
function handle_r(origin, stanza, xmlns_sm) -- luacheck: ignore 212/stanza
if not origin.smacks then
- module:log("debug", "Received ack request from non-smack-enabled session");
+ origin.log("debug", "Received ack request from non-smack-enabled session");
return;
end
- module:log("debug", "Received ack request, acking for %d", origin.handled_stanza_count);
+ origin.log("debug", "Received ack request, acking for %d", origin.handled_stanza_count);
-- Reply with <a>
(origin.sends2s or origin.send)(st.stanza("a", { xmlns = xmlns_sm, h = format_h(origin.handled_stanza_count) }));
-- piggyback our own ack request if needed (see request_ack_if_needed() for explanation of last_requested_h)
@@ -412,13 +420,14 @@ local function handle_unacked_stanzas(session)
local queue = session.outgoing_stanza_queue;
local unacked = queue:count_unacked()
if unacked > 0 then
+ local error_from = jid.join(session.username, session.host or module.host);
tx_dropped_stanzas:sample(unacked);
session.smacks = false; -- Disable queueing
session.outgoing_stanza_queue = nil;
for stanza in queue._queue:consume() do
if not module:fire_event("delivery/failure", { session = session, stanza = stanza }) then
if stanza.attr.type ~= "error" and stanza.attr.from ~= session.full_jid then
- local reply = st.error_reply(stanza, "cancel", "recipient-unavailable");
+ local reply = st.error_reply(stanza, "cancel", "recipient-unavailable", nil, error_from);
module:send(reply);
end
end
@@ -485,9 +494,7 @@ module:hook("pre-resource-unbind", function (event)
end
session.log("debug", "Destroying session for hibernating too long");
- session_registry[jid.join(session.username, session.host, session.resumption_token)] = nil;
- old_session_registry:set(session.username, session.resumption_token,
- { h = session.handled_stanza_count; t = os.time() });
+ save_old_session(session);
session.resumption_token = nil;
session.resending_unacked = true; -- stop outgoing_stanza_filter from re-queueing anything anymore
sessionmanager.destroy_session(session, "Hibernating too long");
@@ -544,7 +551,7 @@ function handle_resume(session, stanza, xmlns_sm)
session.send(st.stanza("failed", { xmlns = xmlns_sm, h = format_h(old_session.h) })
:tag("item-not-found", { xmlns = xmlns_errors })
);
- old_session_registry:set(session.username, id, nil);
+ clear_old_session(session, id);
resumption_expired(1);
else
session.log("debug", "Tried to resume non-existent session with id %s", id);
@@ -701,8 +708,7 @@ module:hook_global("server-stopping", function(event)
for _, user in pairs(local_sessions) do
for _, session in pairs(user.sessions) do
if session.resumption_token then
- if old_session_registry:set(session.username, session.resumption_token,
- { h = session.handled_stanza_count; t = os.time() }) then
+ if save_old_session(session) then
session.resumption_token = nil;
-- Deal with unacked stanzas
diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua
index 3bfe1739..8749bcc4 100644
--- a/plugins/mod_storage_sql.lua
+++ b/plugins/mod_storage_sql.lua
@@ -382,8 +382,7 @@ local function archive_where(query, args, where)
-- Set of ids
if query.ids then
local nids, nargs = #query.ids, #args;
- -- COMPAT Lua 5.1: No separator argument to string.rep
- where[#where + 1] = "\"key\" IN (" .. string.rep("?,", nids):sub(1,-2) .. ")";
+ where[#where + 1] = "\"key\" IN (" .. string.rep("?", nids, ",") .. ")";
for i, id in ipairs(query.ids) do
args[nargs+i] = id;
end
diff --git a/plugins/mod_tls.lua b/plugins/mod_tls.lua
index afc1653a..fc35b1d0 100644
--- a/plugins/mod_tls.lua
+++ b/plugins/mod_tls.lua
@@ -80,6 +80,9 @@ end
module:hook_global("config-reloaded", module.load);
local function can_do_tls(session)
+ if session.secure then
+ return false;
+ end
if session.conn and not session.conn.starttls then
if not session.secure then
session.log("debug", "Underlying connection does not support STARTTLS");
@@ -126,6 +129,13 @@ end);
module:hook("stanza/urn:ietf:params:xml:ns:xmpp-tls:starttls", function(event)
local origin = event.origin;
if can_do_tls(origin) then
+ if origin.conn.block_reads then
+ -- we need to ensure that no data is read anymore, otherwise we could end up in a situation where
+ -- <proceed/> is sent and the socket receives the TLS handshake (and passes the data to lua) before
+ -- it is asked to initiate TLS
+ -- (not with the classical single-threaded server backends)
+ origin.conn:block_reads()
+ end
(origin.sends2s or origin.send)(starttls_proceed);
if origin.destroyed then return end
origin:reset_stream();
@@ -183,7 +193,7 @@ module:hook_tag(xmlns_starttls, "proceed", function (session, stanza) -- luachec
if session.type == "s2sout_unauthed" and can_do_tls(session) then
module:log("debug", "Proceeding with TLS on s2sout...");
session:reset_stream();
- session.conn:starttls(session.ssl_ctx);
+ session.conn:starttls(session.ssl_ctx, session.to_host);
session.secure = false;
return true;
end