aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.luacheckrc1
-rw-r--r--CHANGES4
-rw-r--r--core/features.lua8
-rw-r--r--doc/doap.xml5
-rw-r--r--net/http/files.lua2
-rw-r--r--net/server_epoll.lua27
-rw-r--r--net/unbound.lua6
-rw-r--r--plugins/mod_admin_shell.lua12
-rw-r--r--plugins/mod_announce.lua88
-rw-r--r--plugins/mod_blocklist.lua24
-rw-r--r--plugins/mod_bosh.lua2
-rw-r--r--plugins/mod_c2s.lua3
-rw-r--r--plugins/mod_cron.lua14
-rw-r--r--plugins/mod_disco.lua4
-rw-r--r--plugins/mod_http_file_share.lua2
-rw-r--r--plugins/mod_invites.lua48
-rw-r--r--plugins/mod_invites_adhoc.lua4
-rw-r--r--plugins/mod_pep.lua19
-rw-r--r--plugins/mod_posix.lua163
-rw-r--r--plugins/mod_pubsub/mod_pubsub.lua43
-rw-r--r--plugins/mod_pubsub/pubsub.lib.lua6
-rw-r--r--plugins/mod_s2s.lua2
-rw-r--r--plugins/mod_s2s_auth_certs.lua9
-rw-r--r--plugins/mod_s2s_auth_dane_in.lua57
-rw-r--r--plugins/mod_saslauth.lua2
-rw-r--r--plugins/mod_server_contact_info.lua38
-rw-r--r--plugins/mod_server_info.lua55
-rw-r--r--plugins/mod_smacks.lua2
-rw-r--r--plugins/mod_storage_internal.lua8
-rw-r--r--plugins/mod_version.lua7
-rw-r--r--plugins/muc/hats.lib.lua17
-rw-r--r--plugins/muc/muc.lib.lua15
-rw-r--r--spec/scansion/disco_self.scs26
-rw-r--r--spec/scansion/muc_outcast_reason.scs72
-rw-r--r--spec/scansion/muc_subject_issue_667.scs29
-rw-r--r--spec/util_bitcompat_spec.lua4
-rw-r--r--spec/util_ip_spec.lua4
-rw-r--r--spec/util_rfc6724_spec.lua97
-rw-r--r--spec/util_strbitop_spec.lua (renamed from spec/util_strbitop.lua)44
-rw-r--r--teal-src/prosody/plugins/mod_cron.tl14
-rw-r--r--teal-src/prosody/util/crypto.d.tl52
-rw-r--r--teal-src/prosody/util/hashes.d.tl8
-rw-r--r--teal-src/prosody/util/strbitop.d.tl1
-rwxr-xr-xtools/test_mutants.sh.lua2
-rw-r--r--util-src/signal.c90
-rw-r--r--util-src/strbitop.c37
-rw-r--r--util/bit53.lua3
-rw-r--r--util/ip.lua28
-rw-r--r--util/prosodyctl/check.lua51
-rw-r--r--util/prosodyctl/shell.lua2
-rw-r--r--util/pubsub.lua2
-rw-r--r--util/rfc6724.lua141
-rw-r--r--util/startup.lua196
53 files changed, 1062 insertions, 538 deletions
diff --git a/.luacheckrc b/.luacheckrc
index 6eb4c526..90e18ce5 100644
--- a/.luacheckrc
+++ b/.luacheckrc
@@ -161,7 +161,6 @@ if os.getenv("PROSODY_STRICT_LINT") ~= "1" then
"spec/util_http_spec.lua";
"spec/util_ip_spec.lua";
"spec/util_multitable_spec.lua";
- "spec/util_rfc6724_spec.lua";
"spec/util_throttle_spec.lua";
"tools/ejabberd2prosody.lua";
diff --git a/CHANGES b/CHANGES
index 75eb127e..70aa13c3 100644
--- a/CHANGES
+++ b/CHANGES
@@ -24,6 +24,7 @@ TRUNK
- Public rooms can only be created by local users (parent host) by default
- muc_room_allow_public = false restricts to admins
- Commands to show occupants and affiliations in the Shell
+- Save 'reason' text supplied with affiliation change
### Security and authentication
@@ -65,11 +66,14 @@ TRUNK
- Intervals of mod_cron managed periodic jobs made configurable
- When mod_smacks is enabled, s2s connections not responding to ack requests are closed.
- Arguments to `prosodyctl shell` that start with ':' are now turned into method calls
+- Support for Type=notify and notify-reload systemd service type added
+- Support for the roster *group* access_model in mod_pep
## Removed
- Lua 5.1 support
- XEP-0090 support removed from mod_time
+- util.rfc6724
0.12.0
======
diff --git a/core/features.lua b/core/features.lua
index db1bc986..99edde51 100644
--- a/core/features.lua
+++ b/core/features.lua
@@ -4,6 +4,8 @@ return {
available = set.new{
-- mod_bookmarks bundled
"mod_bookmarks";
+ -- mod_server_info bundled
+ "mod_server_info";
-- Roles, module.may and per-session authz
"permissions";
-- prosody.* namespace
@@ -21,5 +23,11 @@ return {
"getopt-interval";
"getopt-period";
"getopt-integer";
+
+ -- new module.ready()
+ "module-ready";
+
+ -- SIGUSR1 and 2 events
+ "signal-events";
};
};
diff --git a/doc/doap.xml b/doc/doap.xml
index 79ef9d68..62e4063c 100644
--- a/doc/doap.xml
+++ b/doc/doap.xml
@@ -67,6 +67,7 @@
<implements rdf:resource="https://datatracker.ietf.org/doc/draft-cridland-xmpp-session/">
<!-- since=0.6.0 note=Added in hg:0bbbc9042361 -->
</implements>
+ <implements rdf:resource="https://datatracker.ietf.org/doc/draft-ietf-dance-client-auth"/>
<implements rdf:resource="http://www.unicode.org/reports/tr39/"/>
<implements>
<xmpp:SupportedXep>
@@ -698,8 +699,8 @@
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0317.html"/>
- <xmpp:version>0.1</xmpp:version>
- <xmpp:status>planned</xmpp:status>
+ <xmpp:version>0.2.0</xmpp:version>
+ <xmpp:status>complete</xmpp:status>
<xmpp:since>0.12.0</xmpp:since>
<xmpp:note>muc/hats</xmpp:note>
</xmpp:SupportedXep>
diff --git a/net/http/files.lua b/net/http/files.lua
index 24d9f204..8ef054e2 100644
--- a/net/http/files.lua
+++ b/net/http/files.lua
@@ -58,7 +58,7 @@ local function serve(opts)
local cache = new_cache(opts.cache_size or 256);
local cache_max_file_size = tonumber(opts.cache_max_file_size) or 1024
-- luacheck: ignore 431
- local base_path = opts.path;
+ local base_path = assert(opts.path, "invalid argument to net.http.files.path(), missing required 'path'");
local dir_indices = opts.index_files or { "index.html", "index.htm" };
local directory_index = opts.directory_index;
local function serve_file(event, path)
diff --git a/net/server_epoll.lua b/net/server_epoll.lua
index fe60dc78..c946a751 100644
--- a/net/server_epoll.lua
+++ b/net/server_epoll.lua
@@ -29,6 +29,7 @@ local new_id = require "prosody.util.id".short;
local xpcall = require "prosody.util.xpcall".xpcall;
local sslconfig = require "prosody.util.sslconfig";
local tls_impl = require "prosody.net.tls_luasec";
+local have_signal, signal = pcall(require, "prosody.util.signal");
local poller = require "prosody.util.poll"
local EEXIST = poller.EEXIST;
@@ -630,30 +631,35 @@ end
function interface:ssl_info()
local sock = self.conn;
+ if not sock then return nil, "not-connected" end
if not sock.info then return nil, "not-implemented"; end
return sock:info();
end
function interface:ssl_peercertificate()
local sock = self.conn;
+ if not sock then return nil, "not-connected" end
if not sock.getpeercertificate then return nil, "not-implemented"; end
return sock:getpeercertificate();
end
function interface:ssl_peerverification()
local sock = self.conn;
+ if not sock then return nil, "not-connected" end
if not sock.getpeerverification then return nil, { { "Chain verification not supported" } }; end
return sock:getpeerverification();
end
function interface:ssl_peerfinished()
local sock = self.conn;
+ if not sock then return nil, "not-connected" end
if not sock.getpeerfinished then return nil, "not-implemented"; end
return sock:getpeerfinished();
end
function interface:ssl_exportkeyingmaterial(label, len, context)
local sock = self.conn;
+ if not sock then return nil, "not-connected" end
if sock.exportkeyingmaterial then
return sock:exportkeyingmaterial(label, len, context);
end
@@ -1138,6 +1144,26 @@ local function loop(once)
return quitting;
end
+local hook_signal;
+if have_signal and signal.signalfd then
+ local function dispatch(self)
+ return self:on("signal", self.conn:read());
+ end
+
+ function hook_signal(signum, cb)
+ local sigfd = signal.signalfd(signum);
+ if not sigfd then
+ log("error", "Could not hook signal %d", signum);
+ return nil, "failed";
+ end
+ local watch = watchfd(sigfd, dispatch);
+ watch.listeners = { onsignal = cb };
+ watch.close = nil; -- revert to default
+ watch:noise("Signal handler %d ready", signum);
+ return watch;
+ end
+end
+
return {
get_backend = function () return "epoll"; end;
addserver = addserver;
@@ -1163,6 +1189,7 @@ return {
set_config = function (newconfig)
cfg = setmetatable(newconfig, default_config);
end;
+ hook_signal = hook_signal;
tls_builder = function(basedir)
return sslconfig._new(tls_impl.new_context, basedir)
diff --git a/net/unbound.lua b/net/unbound.lua
index c3ccb9ea..176a6156 100644
--- a/net/unbound.lua
+++ b/net/unbound.lua
@@ -80,8 +80,12 @@ local answer_mt = {
h = h .. s_format(", Bogus: %s", self.bogus);
end
local t = { h };
+ local qname = self.canonname or self.qname;
+ if self.canonname then
+ table.insert(t, self.qname .. "\t" .. classes[self.qclass] .. "\tCNAME\t" .. self.canonname);
+ end
for i = 1, #self do
- t[i+1]=self.qname.."\t"..classes[self.qclass].."\t"..types[self.qtype].."\t"..tostring(self[i]);
+ table.insert(t, qname .. "\t" .. classes[self.qclass] .. "\t" .. types[self.qtype] .. "\t" .. tostring(self[i]));
end
local _string = t_concat(t, "\n");
self._string = _string;
diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua
index 45a891f4..d085ce43 100644
--- a/plugins/mod_admin_shell.lua
+++ b/plugins/mod_admin_shell.lua
@@ -867,6 +867,18 @@ available_columns = {
end
end;
};
+ created = {
+ title = "Connection Created";
+ description = "Time when connection was created";
+ width = #"YYYY MM DD HH:MM:SS";
+ align = "right";
+ key = "conn";
+ mapper = function(conn)
+ if conn then
+ return os.date("%F %T", math.floor(conn.created));
+ end
+ end;
+ };
dir = {
title = "Dir";
description = "Direction of server-to-server connection";
diff --git a/plugins/mod_announce.lua b/plugins/mod_announce.lua
index ba62c11b..f54d2db9 100644
--- a/plugins/mod_announce.lua
+++ b/plugins/mod_announce.lua
@@ -6,11 +6,15 @@
-- COPYING file in the source package for more information.
--
-local st, jid = require "prosody.util.stanza", require "prosody.util.jid";
+local usermanager = require "prosody.core.usermanager";
+local id = require "prosody.util.id";
+local jid = require "prosody.util.jid";
+local st = require "prosody.util.stanza";
local hosts = prosody.hosts;
function send_to_online(message, host)
+ host = host or module.host;
local sessions;
if host then
sessions = { [host] = hosts[host] };
@@ -33,6 +37,28 @@ function send_to_online(message, host)
return c;
end
+function send_to_all(message, host)
+ host = host or module.host;
+ local c = 0;
+ for username in usermanager.users(host) do
+ message.attr.to = username.."@"..host;
+ module:send(st.clone(message));
+ c = c + 1;
+ end
+ return c;
+end
+
+function send_to_role(message, role, host)
+ host = host or module.host;
+ local c = 0;
+ for _, recipient_jid in ipairs(usermanager.get_jids_with_role(role, host)) do
+ message.attr.to = recipient_jid;
+ module:send(st.clone(message));
+ c = c + 1;
+ end
+ return c;
+end
+
module:default_permission("prosody:admin", ":send-announcement");
-- Old <message>-based jabberd-style announcement sending
@@ -82,8 +108,10 @@ function announce_handler(_, data, state)
local fields = announce_layout:data(data.form);
module:log("info", "Sending server announcement to all online users");
- local message = st.message({type = "headline"}, fields.announcement):up()
- :tag("subject"):text(fields.subject or "Announcement");
+ local message = st.message({type = "headline"}, fields.announcement):up();
+ if fields.subject and fields.subject ~= "" then
+ message:text_tag("subject", fields.subject);
+ end
local count = send_to_online(message, data.to);
@@ -99,3 +127,57 @@ local adhoc_new = module:require "adhoc".new;
local announce_desc = adhoc_new("Send Announcement to Online Users", "http://jabber.org/protocol/admin#announce", announce_handler, "admin");
module:provides("adhoc", announce_desc);
+module:add_item("shell-command", {
+ section = "announce";
+ section_desc = "Broadcast announcements to users";
+ name = "all";
+ desc = "Send announcement to all users on the host";
+ args = {
+ { name = "host", type = "string" };
+ { name = "text", type = "string" };
+ };
+ host_selector = "host";
+ handler = function(self, host, text) --luacheck: ignore 212/self
+ local msg = st.message({ from = host, id = id.short() })
+ :text_tag("body", text);
+ local count = send_to_all(msg, host);
+ return true, ("Announcement sent to %d users"):format(count);
+ end;
+});
+
+module:add_item("shell-command", {
+ section = "announce";
+ section_desc = "Broadcast announcements to users";
+ name = "online";
+ desc = "Send announcement to all online users on the host";
+ args = {
+ { name = "host", type = "string" };
+ { name = "text", type = "string" };
+ };
+ host_selector = "host";
+ handler = function(self, host, text) --luacheck: ignore 212/self
+ local msg = st.message({ from = host, id = id.short(), type = "headline" })
+ :text_tag("body", text);
+ local count = send_to_online(msg, host);
+ return true, ("Announcement sent to %d users"):format(count);
+ end;
+});
+
+module:add_item("shell-command", {
+ section = "announce";
+ section_desc = "Broadcast announcements to users";
+ name = "role";
+ desc = "Send announcement to users with a specific role on the host";
+ args = {
+ { name = "host", type = "string" };
+ { name = "role", type = "string" };
+ { name = "text", type = "string" };
+ };
+ host_selector = "host";
+ handler = function(self, host, role, text) --luacheck: ignore 212/self
+ local msg = st.message({ from = host, id = id.short() })
+ :text_tag("body", text);
+ local count = send_to_role(msg, role, host);
+ return true, ("Announcement sent to %d users"):format(count);
+ end;
+});
diff --git a/plugins/mod_blocklist.lua b/plugins/mod_blocklist.lua
index 3a621753..6587c8b1 100644
--- a/plugins/mod_blocklist.lua
+++ b/plugins/mod_blocklist.lua
@@ -262,7 +262,20 @@ local function drop_stanza(event)
local to, from = attr.to, attr.from;
to = to and jid_split(to);
if to and from then
- return is_blocked(to, from);
+ if is_blocked(to, from) then
+ return true;
+ end
+
+ -- Check mediated MUC inviter
+ if stanza.name == "message" then
+ local invite = stanza:find("{http://jabber.org/protocol/muc#user}x/invite");
+ if invite then
+ from = jid_prep(invite.attr.from);
+ if is_blocked(to, from) then
+ return true;
+ end
+ end
+ end
end
end
@@ -322,8 +335,13 @@ local prio_in, prio_out = 100, 100;
module:hook("presence/bare", drop_stanza, prio_in);
module:hook("presence/full", drop_stanza, prio_in);
-module:hook("message/bare", bounce_message, prio_in);
-module:hook("message/full", bounce_message, prio_in);
+if module:get_option_boolean("bounce_blocked_messages", false) then
+ module:hook("message/bare", bounce_message, prio_in);
+ module:hook("message/full", bounce_message, prio_in);
+else
+ module:hook("message/bare", drop_stanza, prio_in);
+ module:hook("message/full", drop_stanza, prio_in);
+end
module:hook("iq/bare", bounce_iq, prio_in);
module:hook("iq/full", bounce_iq, prio_in);
diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua
index f4fec8f0..091a7d81 100644
--- a/plugins/mod_bosh.lua
+++ b/plugins/mod_bosh.lua
@@ -325,7 +325,7 @@ function stream_callbacks.streamopened(context, attr)
sid = new_uuid();
-- TODO use util.session
local session = {
- type = "c2s_unauthed", conn = request.conn, sid = sid, host = attr.to,
+ base_type = "c2s", type = "c2s_unauthed", conn = request.conn, sid = sid, host = attr.to,
rid = rid - 1, -- Hack for initial session setup, "previous" rid was $current_request - 1
bosh_version = attr.ver, bosh_wait = wait, streamid = sid,
bosh_max_inactive = bosh_max_inactivity, bosh_responses = cache.new(BOSH_HOLD+1):table();
diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua
index 4dabf34b..1a24c27c 100644
--- a/plugins/mod_c2s.lua
+++ b/plugins/mod_c2s.lua
@@ -252,6 +252,9 @@ end
local function disconnect_user_sessions(reason, leave_resource)
return function (event)
local username, host, resource = event.username, event.host, event.resource;
+ if not (hosts[host] and hosts[host].type == "local") then
+ return -- not a local VirtualHost so no sessions
+ end
local user = hosts[host].sessions[username];
if user and user.sessions then
for r, session in pairs(user.sessions) do
diff --git a/plugins/mod_cron.lua b/plugins/mod_cron.lua
index 077dc80e..29c1aa93 100644
--- a/plugins/mod_cron.lua
+++ b/plugins/mod_cron.lua
@@ -2,6 +2,10 @@ module:set_global();
local async = require("prosody.util.async");
+local cron_initial_delay = module:get_option_number("cron_initial_delay", 1);
+local cron_check_delay = module:get_option_number("cron_check_delay", 3600);
+local cron_spread_factor = module:get_option_number("cron_spread_factor", 0);
+
local active_hosts = {}
function module.add_host(host_module)
@@ -46,15 +50,19 @@ local function run_task(task)
task:save(started_at);
end
+local function spread(t, factor)
+ return t * (1 - factor + 2*factor*math.random());
+end
+
local task_runner = async.runner(run_task);
-scheduled = module:add_timer(1, function()
+scheduled = module:add_timer(cron_initial_delay, function()
module:log("info", "Running periodic tasks");
- local delay = 3600;
+ local delay = spread(cron_check_delay, cron_spread_factor);
for host in pairs(active_hosts) do
module:log("debug", "Running periodic tasks for host %s", host);
for _, task in ipairs(module:context(host):get_host_items("task")) do task_runner:run(task); end
end
- module:log("debug", "Wait %ds", delay);
+ module:log("debug", "Wait %gs", delay);
return delay
end);
diff --git a/plugins/mod_disco.lua b/plugins/mod_disco.lua
index 540678e2..3517344d 100644
--- a/plugins/mod_disco.lua
+++ b/plugins/mod_disco.lua
@@ -173,6 +173,8 @@ module:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", function(
if not stanza.attr.to or (expose_admins and target_is_admin) or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
if node and node ~= "" then
local reply = st.reply(stanza):tag('query', {xmlns='http://jabber.org/protocol/disco#info', node=node});
+ reply:tag("feature", { var = "http://jabber.org/protocol/disco#info" }):up();
+ reply:tag("feature", { var = "http://jabber.org/protocol/disco#items" }):up();
if not reply.attr.from then reply.attr.from = origin.username.."@"..origin.host; end -- COMPAT To satisfy Psi when querying own account
local node_event = { origin = origin, stanza = stanza, reply = reply, node = node, exists = false};
local ret = module:fire_event("account-disco-info-node", node_event);
@@ -193,6 +195,8 @@ module:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", function(
else
reply:tag('identity', {category='account', type='registered'}):up();
end
+ reply:tag("feature", { var = "http://jabber.org/protocol/disco#info" }):up();
+ reply:tag("feature", { var = "http://jabber.org/protocol/disco#items" }):up();
module:fire_event("account-disco-info", { origin = origin, reply = reply });
origin.send(reply);
return true;
diff --git a/plugins/mod_http_file_share.lua b/plugins/mod_http_file_share.lua
index 718b4d71..cfc647d4 100644
--- a/plugins/mod_http_file_share.lua
+++ b/plugins/mod_http_file_share.lua
@@ -452,7 +452,7 @@ function handle_download(event, path) -- GET /uploads/:slot+filename
return response:send_file(handle);
end
-if expiry >= 0 and not external_base_url then
+if expiry < math.huge and not external_base_url then
-- TODO HTTP DELETE to the external endpoint?
local array = require "prosody.util.array";
local async = require "prosody.util.async";
diff --git a/plugins/mod_invites.lua b/plugins/mod_invites.lua
index 04265070..559170cc 100644
--- a/plugins/mod_invites.lua
+++ b/plugins/mod_invites.lua
@@ -4,6 +4,7 @@ local url = require "socket.url";
local jid_node = require "prosody.util.jid".node;
local jid_split = require "prosody.util.jid".split;
local argparse = require "prosody.util.argparse";
+local human_io = require "prosody.util.human.io";
local default_ttl = module:get_option_period("invite_expiry", "1 week");
@@ -248,26 +249,10 @@ function module.command(arg)
end
function subcommands.generate(arg)
-
- local sm = require "prosody.core.storagemanager";
- local mm = require "prosody.core.modulemanager";
-
- local host = table.remove(arg, 1); -- pop host
- assert(prosody.hosts[host], "Host "..tostring(host).." does not exist");
- sm.initialize_host(host);
- module.host = host; --luacheck: ignore 122/module
- token_storage = module:open_store("invite_token", "map");
-
- local opts = argparse.parse(arg, {
- short_params = { h = "help"; ["?"] = "help"; g = "group" };
- value_params = { group = true; reset = true; role = true };
- array_params = { group = true; role = true };
- });
-
-
- if opts.help then
+ local function help(short)
print("usage: prosodyctl mod_" .. module.name .. " generate DOMAIN --reset USERNAME")
print("usage: prosodyctl mod_" .. module.name .. " generate DOMAIN [--admin] [--role ROLE] [--group GROUPID]...")
+ if short then return 2 end
print()
print("This command has two modes: password reset and new account.")
print("If --reset is given, the command operates in password reset mode and in new account mode otherwise.")
@@ -283,6 +268,7 @@ function subcommands.generate(arg)
print(" --role ROLE Grant the given ROLE to the new user")
print(" --group GROUPID Add the user to the group with the given ID")
print(" Can be specified multiple times")
+ print(" --expires-after T Time until the invite expires (e.g. '1 week')")
print()
print("--group can be specified multiple times; the user will be added to all groups.")
print()
@@ -290,6 +276,30 @@ function subcommands.generate(arg)
return 2
end
+ local earlyopts = argparse.parse(arg, { short_params = { h = "help"; ["?"] = "help" } });
+ if earlyopts.help or not earlyopts[1] then
+ return help();
+ end
+
+ local sm = require "prosody.core.storagemanager";
+ local mm = require "prosody.core.modulemanager";
+
+ local host = table.remove(arg, 1); -- pop host
+ if not host then return help(true) end
+ sm.initialize_host(host);
+ module.host = host; --luacheck: ignore 122/module
+ token_storage = module:open_store("invite_token", "map");
+
+ local opts = argparse.parse(arg, {
+ short_params = { h = "help"; ["?"] = "help"; g = "group" };
+ value_params = { group = true; reset = true; role = true };
+ array_params = { group = true; role = true };
+ });
+
+ if opts.help then
+ return help();
+ end
+
-- Load mod_invites
local invites = module:depends("invites");
-- Optional community module that if used, needs to be loaded here
@@ -332,7 +342,7 @@ function subcommands.generate(arg)
invite = assert(invites.create_account(nil, {
roles = roles,
groups = groups
- }));
+ }, opts.expires_after and human_io.parse_duration(opts.expires_after)));
end
print(invite.landing_page or invite.uri);
diff --git a/plugins/mod_invites_adhoc.lua b/plugins/mod_invites_adhoc.lua
index 02e6a7dd..c9954d8c 100644
--- a/plugins/mod_invites_adhoc.lua
+++ b/plugins/mod_invites_adhoc.lua
@@ -67,7 +67,7 @@ module:provides("adhoc", new_adhoc("Create new contact invite", "urn:xmpp:invite
--TODO: check errors
return {
status = "completed";
- form = {
+ result = {
layout = invite_result_form;
values = {
uri = invite.uri;
@@ -88,7 +88,7 @@ module:provides("adhoc", new_adhoc("Create new account invite", "urn:xmpp:invite
--TODO: check errors
return {
status = "completed";
- form = {
+ result = {
layout = invite_result_form;
values = {
uri = invite.uri;
diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua
index fbc06fdb..33eee2ec 100644
--- a/plugins/mod_pep.lua
+++ b/plugins/mod_pep.lua
@@ -5,7 +5,7 @@ local jid_join = require "prosody.util.jid".join;
local set_new = require "prosody.util.set".new;
local st = require "prosody.util.stanza";
local calculate_hash = require "prosody.util.caps".calculate_hash;
-local is_contact_subscribed = require "prosody.core.rostermanager".is_contact_subscribed;
+local rostermanager = require "prosody.core.rostermanager";
local cache = require "prosody.util.cache";
local set = require "prosody.util.set";
local new_id = require "prosody.util.id".medium;
@@ -16,6 +16,8 @@ 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 is_contact_subscribed = rostermanager.is_contact_subscribed;
+
local lib_pubsub = module:require "pubsub";
local empty_set = set_new();
@@ -84,6 +86,7 @@ function check_node_config(node, actor, new_config) -- luacheck: ignore 212/node
return false;
end
if new_config["access_model"] ~= "presence"
+ and new_config["access_model"] ~= "roster"
and new_config["access_model"] ~= "whitelist"
and new_config["access_model"] ~= "open" then
return false;
@@ -256,6 +259,20 @@ function get_pep_service(username)
end
return "outcast";
end;
+ roster = function (jid, node)
+ jid = jid_bare(jid);
+ local allowed_groups = set_new(node.config.roster_groups_allowed);
+ local roster = rostermanager.load_roster(username, host);
+ if not roster[jid] then
+ return "outcast";
+ end
+ for group in pairs(roster[jid].groups) do
+ if allowed_groups:contains(group) then
+ return "member";
+ end
+ end
+ return "outcast";
+ end;
};
jid = user_bare;
diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua
index 3aa6a895..101e6e62 100644
--- a/plugins/mod_posix.lua
+++ b/plugins/mod_posix.lua
@@ -6,167 +6,6 @@
-- COPYING file in the source package for more information.
--
-
-local want_pposix_version = "0.4.0";
-
-local pposix = assert(require "prosody.util.pposix");
-if pposix._VERSION ~= want_pposix_version then
- module:log("warn", "Unknown version (%s) of binary pposix module, expected %s."
- .. "Perhaps you need to recompile?", tostring(pposix._VERSION), want_pposix_version);
-end
-
-local have_signal, signal = pcall(require, "prosody.util.signal");
-if not have_signal then
- module:log("warn", "Couldn't load signal library, won't respond to SIGTERM");
-end
-
-local lfs = require "lfs";
-local stat = lfs.attributes;
-
-local prosody = _G.prosody;
-
module:set_global(); -- we're a global module
-local umask = module:get_option_string("umask", "027");
-pposix.umask(umask);
-
--- Don't even think about it!
-if not prosody.start_time then -- server-starting
- if pposix.getuid() == 0 and not module:get_option_boolean("run_as_root") then
- module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
- module:log("error", "For more information on running Prosody as root, see https://prosody.im/doc/root");
- prosody.shutdown("Refusing to run as root", 1);
- end
-end
-
-local pidfile;
-local pidfile_handle;
-
-local function remove_pidfile()
- if pidfile_handle then
- pidfile_handle:close();
- os.remove(pidfile);
- pidfile, pidfile_handle = nil, nil;
- end
-end
-
-local function write_pidfile()
- if pidfile_handle then
- remove_pidfile();
- end
- pidfile = module:get_option_path("pidfile", nil, "data");
- if pidfile then
- local err;
- local mode = stat(pidfile) and "r+" or "w+";
- pidfile_handle, err = io.open(pidfile, mode);
- if not pidfile_handle then
- module:log("error", "Couldn't write pidfile at %s; %s", pidfile, err);
- prosody.shutdown("Couldn't write pidfile", 1);
- else
- if not lfs.lock(pidfile_handle, "w") then -- Exclusive lock
- local other_pid = pidfile_handle:read("*a");
- module:log("error", "Another Prosody instance seems to be running with PID %s, quitting", other_pid);
- pidfile_handle = nil;
- prosody.shutdown("Prosody already running", 1);
- else
- pidfile_handle:close();
- pidfile_handle, err = io.open(pidfile, "w+");
- if not pidfile_handle then
- module:log("error", "Couldn't write pidfile at %s; %s", pidfile, err);
- prosody.shutdown("Couldn't write pidfile", 1);
- else
- if lfs.lock(pidfile_handle, "w") then
- pidfile_handle:write(tostring(pposix.getpid()));
- pidfile_handle:flush();
- end
- end
- end
- end
- end
-end
-
-local daemonize = prosody.opts.daemonize;
-
-if daemonize == nil then
- -- Fall back to config file if not specified on command-line
- daemonize = module:get_option_boolean("daemonize", nil);
- if daemonize ~= nil then
- module:log("warn", "The 'daemonize' option has been deprecated, specify -D or -F on the command line instead.");
- -- TODO: Write some docs and include a link in the warning.
- end
-end
-
-local function remove_log_sinks()
- local lm = require "prosody.core.loggingmanager";
- lm.register_sink_type("console", nil);
- lm.register_sink_type("stdout", nil);
- lm.reload_logging();
-end
-
-if daemonize then
- local function daemonize_server()
- module:log("info", "Prosody is about to detach from the console, disabling further console output");
- remove_log_sinks();
- local ok, ret = pposix.daemonize();
- if not ok then
- module:log("error", "Failed to daemonize: %s", ret);
- elseif ret and ret > 0 then
- os.exit(0);
- else
- module:log("info", "Successfully daemonized to PID %d", pposix.getpid());
- write_pidfile();
- end
- end
- module:hook("server-started", daemonize_server)
-else
- -- Not going to daemonize, so write the pid of this process
- write_pidfile();
-end
-
-module:hook("server-stopped", remove_pidfile);
-
--- Set signal handlers
-if have_signal then
- module:add_timer(0, function ()
- signal.signal("SIGTERM", function ()
- module:log("warn", "Received SIGTERM");
- prosody.main_thread:run(function ()
- prosody.unlock_globals();
- prosody.shutdown("Received SIGTERM");
- prosody.lock_globals();
- end);
- end);
-
- signal.signal("SIGHUP", function ()
- module:log("info", "Received SIGHUP");
- prosody.main_thread:run(function ()
- prosody.reload_config();
- end);
- -- this also reloads logging
- end);
-
- signal.signal("SIGINT", function ()
- module:log("info", "Received SIGINT");
- prosody.main_thread:run(function ()
- prosody.unlock_globals();
- prosody.shutdown("Received SIGINT");
- prosody.lock_globals();
- end);
- end);
-
- signal.signal("SIGUSR1", function ()
- module:log("info", "Received SIGUSR1");
- module:fire_event("signal/SIGUSR1");
- end);
-
- signal.signal("SIGUSR2", function ()
- module:log("info", "Received SIGUSR2");
- module:fire_event("signal/SIGUSR2");
- end);
- end);
-end
-
--- For other modules to reference
-features = {
- signal_events = true;
-};
+-- TODO delete this whole concept
diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua
index de09ec7d..4f83088a 100644
--- a/plugins/mod_pubsub/mod_pubsub.lua
+++ b/plugins/mod_pubsub/mod_pubsub.lua
@@ -250,3 +250,46 @@ function module.load()
normalize_jid = jid_bare;
}));
end
+
+local function get_service(service_jid)
+ return assert(assert(prosody.hosts[service_jid], "Unknown pubsub service").modules.pubsub, "Not a pubsub service").service;
+end
+
+module:add_item("shell-command", {
+ section = "pubsub";
+ section_desc = "Manage publish/subscribe nodes";
+ name = "create_node";
+ desc = "Create a node with the specified name";
+ args = {
+ { name = "service_jid", type = "string" };
+ { name = "node_name", type = "string" };
+ };
+ host_selector = "service_jid";
+
+ handler = function (self, service_jid, node_name) --luacheck: ignore 212/self
+ return get_service(service_jid):create(node_name, true);
+ end;
+});
+
+module:add_item("shell-command", {
+ section = "pubsub";
+ section_desc = "Manage publish/subscribe nodes";
+ name = "list_nodes";
+ desc = "List nodes on a pubsub service";
+ args = {
+ { name = "service_jid", type = "string" };
+ };
+ host_selector = "service_jid";
+
+ handler = function (self, service_jid) --luacheck: ignore 212/self
+ -- luacheck: ignore 431/service
+ local service = get_service(service_jid);
+ local nodes = select(2, assert(service:get_nodes(true)));
+ local count = 0;
+ for node_name in pairs(nodes) do
+ count = count + 1;
+ self.session.print(node_name);
+ end
+ return true, ("%d nodes"):format(count);
+ end;
+});
diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua
index 28b7be50..8ae0a896 100644
--- a/plugins/mod_pubsub/pubsub.lib.lua
+++ b/plugins/mod_pubsub/pubsub.lib.lua
@@ -110,6 +110,12 @@ local node_config_form = dataform {
};
};
{
+ type = "list-multi"; -- TODO some way to inject options
+ name = "roster_groups_allowed";
+ var = "pubsub#roster_groups_allowed";
+ label = "Roster groups allowed to subscribe";
+ };
+ {
type = "list-single";
name = "publish_model";
var = "pubsub#publish_model";
diff --git a/plugins/mod_s2s.lua b/plugins/mod_s2s.lua
index fcdfbca8..88b73eba 100644
--- a/plugins/mod_s2s.lua
+++ b/plugins/mod_s2s.lua
@@ -1015,6 +1015,8 @@ function check_auth_policy(event)
-- In practice most cases are configuration mistakes or forgotten
-- certificate renewals. We think it's better to let the other party
-- know about the problem so that they can fix it.
+ --
+ -- Note: Bounce message must not include name of server, as it may leak half your JID in semi-anon MUCs.
session:close({ condition = "not-authorized", text = "Your server's certificate "..reason },
nil, "Remote server's certificate "..reason);
return false;
diff --git a/plugins/mod_s2s_auth_certs.lua b/plugins/mod_s2s_auth_certs.lua
index 3606a6a0..2517c95f 100644
--- a/plugins/mod_s2s_auth_certs.lua
+++ b/plugins/mod_s2s_auth_certs.lua
@@ -1,7 +1,6 @@
module:set_global();
local cert_verify_identity = require "prosody.util.x509".verify_identity;
-local NULL = {};
local log = module._log;
local measure_cert_statuses = module:metric("counter", "checked", "", "Certificate validation results",
@@ -23,8 +22,12 @@ module:hook("s2s-check-certificate", function(event)
-- Is there any interest in printing out all/the number of errors here?
if not chain_valid then
log("debug", "certificate chain validation result: invalid");
- for depth, t in pairs(errors or NULL) do
- log("debug", "certificate error(s) at depth %d: %s", depth-1, table.concat(t, ", "))
+ if type(errors) == "table" then
+ for depth, t in pairs(errors) do
+ log("debug", "certificate error(s) at depth %d: %s", depth-1, table.concat(t, ", "));
+ end
+ else
+ log("debug", "certificate error: %s", errors);
end
session.cert_chain_status = "invalid";
session.cert_chain_errors = errors;
diff --git a/plugins/mod_s2s_auth_dane_in.lua b/plugins/mod_s2s_auth_dane_in.lua
index 777fa582..9167e8a9 100644
--- a/plugins/mod_s2s_auth_dane_in.lua
+++ b/plugins/mod_s2s_auth_dane_in.lua
@@ -24,6 +24,25 @@ local function ensure_secure(r)
return r;
end
+local function ensure_nonempty(r)
+ assert(r[1], "empty");
+ return r;
+end
+
+local function flatten(a)
+ local seen = {};
+ local ret = {};
+ for _, rrset in ipairs(a) do
+ for _, rr in ipairs(rrset) do
+ if not seen[tostring(rr)] then
+ table.insert(ret, rr);
+ seen[tostring(rr)] = true;
+ end
+ end
+ end
+ return ret;
+end
+
local lazy_tlsa_mt = {
__index = function(t, i)
if i == 1 then
@@ -73,36 +92,32 @@ module:hook("s2s-check-certificate", function(event)
if rr.srv.target == "." then return {}; end
table.insert(tlsas, resolver:lookup_promise(("_%d._tcp.%s"):format(rr.srv.port, rr.srv.target), "TLSA"):next(ensure_secure));
end
- return promise.all(tlsas);
+ return promise.all(tlsas):next(flatten);
end
- local ret = async.wait_for(promise.all({
- resolver:lookup_promise("_xmpps-server._tcp." .. dns_domain, "SRV"):next(ensure_secure):next(fetch_tlsa);
- resolver:lookup_promise("_xmpp-server._tcp." .. dns_domain, "SRV"):next(ensure_secure):next(fetch_tlsa);
- }));
+ local ret = async.wait_for(resolver:lookup_promise("_xmpp-server." .. dns_domain, "TLSA"):next(ensure_secure):next(ensure_nonempty):catch(function()
+ return promise.all({
+ resolver:lookup_promise("_xmpps-server._tcp." .. dns_domain, "SRV"):next(ensure_secure):next(fetch_tlsa);
+ resolver:lookup_promise("_xmpp-server._tcp." .. dns_domain, "SRV"):next(ensure_secure):next(fetch_tlsa);
+ }):next(flatten);
+ end));
if not ret then
return
end
local found_supported = false;
- for _, by_proto in ipairs(ret) do
- for _, by_srv in ipairs(by_proto) do
- for _, by_target in ipairs(by_srv) do
- for _, rr in ipairs(by_target) do
- if rr.tlsa.use == 3 and by_select_match[rr.tlsa.select] and rr.tlsa.match <= 2 then
- found_supported = true;
- if rr.tlsa.data == by_select_match[rr.tlsa.select][rr.tlsa.match] then
- module:log("debug", "%s matches", rr)
- session.cert_chain_status = "valid";
- session.cert_identity_status = "valid";
- return true;
- end
- else
- log("debug", "Unsupported DANE TLSA record: %s", rr);
- end
- end
+ for _, rr in ipairs(ret) do
+ if rr.tlsa.use == 3 and by_select_match[rr.tlsa.select] and rr.tlsa.match <= 2 then
+ found_supported = true;
+ if rr.tlsa.data == by_select_match[rr.tlsa.select][rr.tlsa.match] then
+ module:log("debug", "%s matches", rr)
+ session.cert_chain_status = "valid";
+ session.cert_identity_status = "valid";
+ return true;
end
+ else
+ log("debug", "Unsupported DANE TLSA record: %s", rr);
end
end
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua
index b219d711..b6cd31c8 100644
--- a/plugins/mod_saslauth.lua
+++ b/plugins/mod_saslauth.lua
@@ -335,6 +335,8 @@ module:hook("stream-features", function(event)
log("debug", "Channel binding 'tls-exporter' supported");
sasl_handler:add_cb_handler("tls-exporter", sasl_tls_exporter);
channel_bindings:add("tls-exporter");
+ else
+ log("debug", "Channel binding 'tls-exporter' not supported");
end
elseif origin.conn.ssl_peerfinished and origin.conn:ssl_peerfinished() then
log("debug", "Channel binding 'tls-unique' supported");
diff --git a/plugins/mod_server_contact_info.lua b/plugins/mod_server_contact_info.lua
index b7f4c7f3..67fed752 100644
--- a/plugins/mod_server_contact_info.lua
+++ b/plugins/mod_server_contact_info.lua
@@ -7,21 +7,22 @@
--
local array = require "prosody.util.array";
-local dataforms = require "prosody.util.dataforms";
+local it = require "prosody.util.iterators";
local jid = require "prosody.util.jid";
local url = require "socket.url";
+module:depends("server_info");
+
-- Source: http://xmpp.org/registrar/formtypes.html#http:--jabber.org-network-serverinfo
-local form_layout = dataforms.new({
- { var = "FORM_TYPE"; type = "hidden"; value = "http://jabber.org/network/serverinfo" };
- { type = "list-multi"; name = "abuse"; var = "abuse-addresses" };
- { type = "list-multi"; name = "admin"; var = "admin-addresses" };
- { type = "list-multi"; name = "feedback"; var = "feedback-addresses" };
- { type = "list-multi"; name = "sales"; var = "sales-addresses" };
- { type = "list-multi"; name = "security"; var = "security-addresses" };
- { type = "list-multi"; name = "status"; var = "status-addresses" };
- { type = "list-multi"; name = "support"; var = "support-addresses" };
-});
+local address_types = {
+ abuse = "abuse-addresses";
+ admin = "admin-addresses";
+ feedback = "feedback-addresses";
+ sales = "sales-addresses";
+ security = "security-addresses";
+ status = "status-addresses";
+ support = "support-addresses";
+};
-- JIDs of configured service admins are used as fallback
local admins = module:get_option_inherited_set("admins", {});
@@ -30,4 +31,17 @@ local contact_config = module:get_option("contact_info", {
admin = array.collect(admins / jid.prep / function(admin) return url.build({scheme = "xmpp"; path = admin}); end);
});
-module:add_extension(form_layout:form(contact_config, "result"));
+local fields = {};
+
+for key, field_var in it.sorted_pairs(address_types) do
+ if contact_config[key] then
+ table.insert(fields, {
+ type = "list-multi";
+ name = key;
+ var = field_var;
+ value = contact_config[key];
+ });
+ end
+end
+
+module:add_item("server-info-fields", fields);
diff --git a/plugins/mod_server_info.lua b/plugins/mod_server_info.lua
new file mode 100644
index 00000000..5469bf02
--- /dev/null
+++ b/plugins/mod_server_info.lua
@@ -0,0 +1,55 @@
+local dataforms = require "prosody.util.dataforms";
+
+local server_info_config = module:get_option("server_info", {});
+local server_info_custom_fields = module:get_option_array("server_info_extensions");
+
+-- Source: http://xmpp.org/registrar/formtypes.html#http:--jabber.org-network-serverinfo
+local form_layout = dataforms.new({
+ { var = "FORM_TYPE"; type = "hidden"; value = "http://jabber.org/network/serverinfo" };
+});
+
+if server_info_custom_fields then
+ for _, field in ipairs(server_info_custom_fields) do
+ table.insert(form_layout, field);
+ end
+end
+
+local generated_form;
+
+function update_form()
+ local new_form = form_layout:form(server_info_config, "result");
+ if generated_form then
+ module:remove_item("extension", generated_form);
+ end
+ generated_form = new_form;
+ module:add_item("extension", generated_form);
+end
+
+function add_fields(event)
+ local fields = event.item;
+ for _, field in ipairs(fields) do
+ table.insert(form_layout, field);
+ end
+ update_form();
+end
+
+function remove_fields(event)
+ local removed_fields = event.item;
+ for _, removed_field in ipairs(removed_fields) do
+ local removed_var = removed_field.var or removed_field.name;
+ for i, field in ipairs(form_layout) do
+ local var = field.var or field.name
+ if var == removed_var then
+ table.remove(form_layout, i);
+ break;
+ end
+ end
+ end
+ update_form();
+end
+
+module:handle_items("server-info-fields", add_fields, remove_fields);
+
+function module.load()
+ update_form();
+end
diff --git a/plugins/mod_smacks.lua b/plugins/mod_smacks.lua
index 486f611a..d4f0f371 100644
--- a/plugins/mod_smacks.lua
+++ b/plugins/mod_smacks.lua
@@ -39,7 +39,7 @@ local resumption_age = module:metric(
"histogram",
"resumption_age", "seconds", "time the session had been hibernating at the time of a resumption",
{},
- {buckets = { 0, 1, 2, 5, 10, 30, 60, 120, 300, 600 }}
+ {buckets = {0, 1, 12, 60, 360, 900, 1440, 3600, 14400, 86400}}
):with_labels();
local sessions_expired = module:measure("sessions_expired", "counter");
local sessions_started = module:measure("sessions_started", "counter");
diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua
index deab7dfd..a43dd272 100644
--- a/plugins/mod_storage_internal.lua
+++ b/plugins/mod_storage_internal.lua
@@ -200,15 +200,11 @@ function archive:find(username, query)
end
if query.start then
if not query.reverse then
- local wi, exact = binary_search(list, function(item)
+ local wi = binary_search(list, function(item)
local when = item.when or datetime.parse(item.attr.stamp);
return query.start - when;
end);
- if exact then
- i = wi - 1;
- elseif wi then
- i = wi;
- end
+ i = wi - 1;
else
iter = it.filter(function(item)
local when = item.when or datetime.parse(item.attr.stamp);
diff --git a/plugins/mod_version.lua b/plugins/mod_version.lua
index d9d3844c..72b13387 100644
--- a/plugins/mod_version.lua
+++ b/plugins/mod_version.lua
@@ -22,7 +22,12 @@ if not module:get_option_boolean("hide_os_type") then
local os_version_command = module:get_option_string("os_version_command");
local ok, pposix = pcall(require, "prosody.util.pposix");
if not os_version_command and (ok and pposix and pposix.uname) then
- platform = pposix.uname().sysname;
+ local uname, err = pposix.uname();
+ if not uname then
+ module:log("debug", "Could not retrieve OS name: %s", err);
+ else
+ platform = uname.sysname;
+ end
end
if not platform then
local uname = io.popen(os_version_command or "uname");
diff --git a/plugins/muc/hats.lib.lua b/plugins/muc/hats.lib.lua
index e1587974..492dc72c 100644
--- a/plugins/muc/hats.lib.lua
+++ b/plugins/muc/hats.lib.lua
@@ -1,7 +1,10 @@
local st = require "prosody.util.stanza";
local muc_util = module:require "muc/util";
-local xmlns_hats = "xmpp:prosody.im/protocol/hats:1";
+local hats_compat = module:get_option_boolean("muc_hats_compat", true); -- COMPAT for pre-XEP namespace, TODO reconsider default for next release
+
+local xmlns_hats_legacy = "xmpp:prosody.im/protocol/hats:1";
+local xmlns_hats = "urn:xmpp:hats:0";
-- Strip any hats claimed by the client (to prevent spoofing)
muc_util.add_filtered_namespace(xmlns_hats);
@@ -13,14 +16,26 @@ module:hook("muc-build-occupant-presence", function (event)
local hats = aff_data and aff_data.hats;
if not hats then return; end
local hats_el;
+ local legacy_hats_el;
for hat_id, hat_data in pairs(hats) do
if hat_data.active then
if not hats_el then
hats_el = st.stanza("hats", { xmlns = xmlns_hats });
end
hats_el:tag("hat", { uri = hat_id, title = hat_data.title }):up();
+
+ if hats_compat then
+ if not hats_el then
+ legacy_hats_el = st.stanza("hats", { xmlns = xmlns_hats_legacy });
+ end
+ legacy_hats_el:tag("hat", { uri = hat_id, title = hat_data.title }):up();
+ end
end
end
if not hats_el then return; end
event.stanza:add_direct_child(hats_el);
+
+ if legacy_hats_el then
+ event.stanza:add_direct_child(legacy_hats_el);
+ end
end);
diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua
index 76b722ba..b8f276cf 100644
--- a/plugins/muc/muc.lib.lua
+++ b/plugins/muc/muc.lib.lua
@@ -1079,7 +1079,10 @@ function room_mt:handle_admin_query_set_command(origin, stanza)
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 registration_data;
+ local registration_data = self:get_affiliation_data(item.attr.jid) or {};
+ if reason then
+ registration_data.reason = reason;
+ end
if item.attr.nick then
local room_nick = self.jid.."/"..item.attr.nick;
local existing_occupant = self:get_occupant_by_nick(room_nick);
@@ -1088,7 +1091,7 @@ function room_mt:handle_admin_query_set_command(origin, stanza)
self:set_role(true, room_nick, nil, "This nickname is reserved");
end
module:log("debug", "Reserving %s for %s (%s)", item.attr.nick, item.attr.jid, item.attr.affiliation);
- registration_data = { reserved_nickname = item.attr.nick };
+ registration_data.reserved_nickname = item.attr.nick;
end
success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, reason, registration_data);
elseif item.attr.role and item.attr.nick and not item.attr.affiliation then
@@ -1119,9 +1122,13 @@ function room_mt:handle_admin_query_get_command(origin, stanza)
if (affiliation_rank >= valid_affiliations.admin and affiliation_rank >= _aff_rank)
or (self:get_members_only() and self:get_whois() == "anyone" and affiliation_rank >= valid_affiliations.member) then
local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
- for jid in self:each_affiliation(_aff or "none") do
+ for jid, _, data in self:each_affiliation(_aff or "none") do
local nick = self:get_registered_nick(jid);
- reply:tag("item", {affiliation = _aff, jid = jid, nick = nick }):up();
+ reply:tag("item", {affiliation = _aff, jid = jid, nick = nick });
+ if data and data.reason then
+ reply:text_tag("reason", data.reason);
+ end
+ reply:up();
end
origin.send(reply:up());
return true;
diff --git a/spec/scansion/disco_self.scs b/spec/scansion/disco_self.scs
new file mode 100644
index 00000000..6782e884
--- /dev/null
+++ b/spec/scansion/disco_self.scs
@@ -0,0 +1,26 @@
+# Basic login and initial presence
+
+[Client] Romeo
+ jid: discoverer@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+Romeo sends:
+ <iq type="get" id="info1">
+ <query xmlns="http://jabber.org/protocol/disco#info"/>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="info1">
+ <query xmlns="http://jabber.org/protocol/disco#info" scansion:strict="false">
+ <identity xmlns="http://jabber.org/protocol/disco#info" category="account" type="registered"/>
+ <feature var="http://jabber.org/protocol/disco#info"/>
+ <feature var="http://jabber.org/protocol/disco#items"/>
+ </query>
+ </iq>
+
+Romeo disconnects
+
diff --git a/spec/scansion/muc_outcast_reason.scs b/spec/scansion/muc_outcast_reason.scs
new file mode 100644
index 00000000..e2725653
--- /dev/null
+++ b/spec/scansion/muc_outcast_reason.scs
@@ -0,0 +1,72 @@
+# Save ban reason
+
+[Client] Romeo
+ password: password
+ jid: user@localhost
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="muc-outcast-reason@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from="muc-outcast-reason@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <status code="201"/>
+ <item jid="${Romeo's full JID}" role="moderator" affiliation="owner"/>
+ <status code="110"/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type="groupchat" from="muc-outcast-reason@conference.localhost">
+ <subject/>
+ </message>
+
+Romeo sends:
+ <iq id="lx5" to="muc-outcast-reason@conference.localhost" type="set">
+ <query xmlns="http://jabber.org/protocol/muc#admin">
+ <item affiliation="outcast" jid="tybalt@localhost">
+ <reason>Hey calm down</reason>
+ </item>
+ </query>
+ </iq>
+
+Romeo receives:
+ <message from="muc-outcast-reason@conference.localhost">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <status code="301"/>
+ <item jid="tybalt@localhost" affiliation="outcast">
+ <reason>Hey calm down</reason>
+ </item>
+ </x>
+ </message>
+
+Romeo receives:
+ <iq id="lx5" type="result" from="muc-outcast-reason@conference.localhost"/>
+
+Romeo sends:
+ <iq id="lx6" to="muc-outcast-reason@conference.localhost" type="get">
+ <query xmlns="http://jabber.org/protocol/muc#admin">
+ <item affiliation="outcast"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="lx6" type="result" from="muc-outcast-reason@conference.localhost">
+ <query xmlns="http://jabber.org/protocol/muc#admin">
+ <item jid="tybalt@localhost" affiliation="outcast">
+ <reason>Hey calm down</reason>
+ </item>
+ </query>
+ </iq>
+
+Romeo disconnects
+
+Romeo sends:
+ <presence type='unavailable'/>
+
diff --git a/spec/scansion/muc_subject_issue_667.scs b/spec/scansion/muc_subject_issue_667.scs
index 74980073..a4544ce4 100644
--- a/spec/scansion/muc_subject_issue_667.scs
+++ b/spec/scansion/muc_subject_issue_667.scs
@@ -42,6 +42,21 @@ Romeo receives:
<body>Hello everyone</body>
</message>
+# this should be treated as a normal message
+Romeo sends:
+ <message to="issue667@conference.localhost" type="groupchat">
+ <subject>New thread</subject>
+ <thread>498acea5-5894-473f-b4c6-c77319d11c75</thread>
+ <store xmlns="urn:xmpp:hints"/>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>New thread</subject>
+ <thread>498acea5-5894-473f-b4c6-c77319d11c75</thread>
+ <store xmlns="urn:xmpp:hints"/>
+ </message>
+
# Resync
Romeo sends:
<presence to="issue667@conference.localhost/Romeo">
@@ -63,6 +78,13 @@ Romeo receives:
<body>Hello everyone</body>
</message>
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>New thread</subject>
+ <thread>498acea5-5894-473f-b4c6-c77319d11c75</thread>
+ <store xmlns="urn:xmpp:hints"/>
+ </message>
+
# the still empty subject
Romeo receives:
<message type="groupchat" from="issue667@conference.localhost">
@@ -116,6 +138,13 @@ Romeo receives:
Romeo receives:
<message type="groupchat" from="issue667@conference.localhost/Romeo">
+ <subject>New thread</subject>
+ <thread>498acea5-5894-473f-b4c6-c77319d11c75</thread>
+ <store xmlns="urn:xmpp:hints"/>
+ </message>
+
+Romeo receives:
+ <message type="groupchat" from="issue667@conference.localhost/Romeo">
<body>Lorem ipsum dolor sit amet</body>
</message>
diff --git a/spec/util_bitcompat_spec.lua b/spec/util_bitcompat_spec.lua
index 34a87f5b..99642821 100644
--- a/spec/util_bitcompat_spec.lua
+++ b/spec/util_bitcompat_spec.lua
@@ -24,4 +24,8 @@ describe("util.bitcompat", function ()
it("lshift works", function ()
assert.equal(0xFF00, bit.lshift(0xFF, 8));
end);
+
+ it("bnot works", function ()
+ assert.equal(0x0000FF00, bit.band(0xFFFFFFFF, bit.bnot(0xFFFF00FF)));
+ end);
end);
diff --git a/spec/util_ip_spec.lua b/spec/util_ip_spec.lua
index 2725ba3a..a0287ee7 100644
--- a/spec/util_ip_spec.lua
+++ b/spec/util_ip_spec.lua
@@ -36,6 +36,8 @@ describe("util.ip", function()
assert.are.equal(match(_"8.8.8.8", _"8.8.0.0", 16), true);
assert.are.equal(match(_"8.8.4.4", _"8.8.0.0", 16), true);
+
+ assert.are.equal(match(_"fe80::1", _"fec0::", 10), false);
end);
end);
@@ -98,6 +100,8 @@ describe("util.ip", function()
assert_cpl6("abcd::1", "abcd::1", 128);
assert_cpl6("abcd::abcd", "abcd::", 112);
assert_cpl6("abcd::abcd", "abcd::abcd:abcd", 96);
+
+ assert_cpl6("fe80::1", "fec0::", 9);
end);
end);
diff --git a/spec/util_rfc6724_spec.lua b/spec/util_rfc6724_spec.lua
deleted file mode 100644
index 30e935b6..00000000
--- a/spec/util_rfc6724_spec.lua
+++ /dev/null
@@ -1,97 +0,0 @@
-
-local rfc6724 = require "util.rfc6724";
-local new_ip = require"util.ip".new_ip;
-
-describe("util.rfc6724", function()
- describe("#source()", function()
- it("should work", function()
- assert.are.equal(rfc6724.source(new_ip("2001:db8:1::1", "IPv6"),
- {new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr,
- "2001:db8:3::1",
- "prefer appropriate scope");
- assert.are.equal(rfc6724.source(new_ip("ff05::1", "IPv6"),
- {new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr,
- "2001:db8:3::1",
- "prefer appropriate scope");
- assert.are.equal(rfc6724.source(new_ip("2001:db8:1::1", "IPv6"),
- {new_ip("2001:db8:1::1", "IPv6"), new_ip("2001:db8:2::1", "IPv6")}).addr,
- "2001:db8:1::1",
- "prefer same address"); -- "2001:db8:1::1" should be marked "deprecated" here, we don't handle that right now
- assert.are.equal(rfc6724.source(new_ip("fe80::1", "IPv6"),
- {new_ip("fe80::2", "IPv6"), new_ip("2001:db8:1::1", "IPv6")}).addr,
- "fe80::2",
- "prefer appropriate scope"); -- "fe80::2" should be marked "deprecated" here, we don't handle that right now
- assert.are.equal(rfc6724.source(new_ip("2001:db8:1::1", "IPv6"),
- {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::2", "IPv6")}).addr,
- "2001:db8:1::2",
- "longest matching prefix");
- --[[ "2001:db8:1::2" should be a care-of address and "2001:db8:3::2" a home address, we can't handle this and would fail
- assert.are.equal(rfc6724.source(new_ip("2001:db8:1::1", "IPv6"),
- {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::2", "IPv6")}).addr,
- "2001:db8:3::2",
- "prefer home address");
- ]]
- assert.are.equal(rfc6724.source(new_ip("2002:c633:6401::1", "IPv6"),
- {new_ip("2002:c633:6401::d5e3:7953:13eb:22e8", "IPv6"), new_ip("2001:db8:1::2", "IPv6")}).addr,
- "2002:c633:6401::d5e3:7953:13eb:22e8",
- "prefer matching label"); -- "2002:c633:6401::d5e3:7953:13eb:22e8" should be marked "temporary" here, we don't handle that right now
- assert.are.equal(rfc6724.source(new_ip("2001:db8:1::d5e3:0:0:1", "IPv6"),
- {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:1::d5e3:7953:13eb:22e8", "IPv6")}).addr,
- "2001:db8:1::d5e3:7953:13eb:22e8",
- "prefer temporary address") -- "2001:db8:1::2" should be marked "public" and "2001:db8:1::d5e3:7953:13eb:22e8" should be marked "temporary" here, we don't handle that right now
- end);
- end);
- describe("#destination()", function()
- it("should work", function()
- local order;
- order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("198.51.100.121", "IPv4")},
- {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("169.254.13.78", "IPv4")})
- assert.are.equal(order[1].addr, "2001:db8:1::1", "prefer matching scope");
- assert.are.equal(order[2].addr, "198.51.100.121", "prefer matching scope");
-
- order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("198.51.100.121", "IPv4")},
- {new_ip("fe80::1", "IPv6"), new_ip("198.51.100.117", "IPv4")})
- assert.are.equal(order[1].addr, "198.51.100.121", "prefer matching scope");
- assert.are.equal(order[2].addr, "2001:db8:1::1", "prefer matching scope");
-
- order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("10.1.2.3", "IPv4")},
- {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("10.1.2.4", "IPv4")})
- assert.are.equal(order[1].addr, "2001:db8:1::1", "prefer higher precedence");
- assert.are.equal(order[2].addr, "10.1.2.3", "prefer higher precedence");
-
- order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")},
- {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")})
- assert.are.equal(order[1].addr, "fe80::1", "prefer smaller scope");
- assert.are.equal(order[2].addr, "2001:db8:1::1", "prefer smaller scope");
-
- --[[ "2001:db8:1::2" and "fe80::2" should be marked "care-of address", while "2001:db8:3::1" should be marked "home address", we can't currently handle this and would fail the test
- order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")},
- {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3::1", "IPv6"), new_ip("fe80::2", "IPv6")})
- assert.are.equal(order[1].addr, "2001:db8:1::1", "prefer home address");
- assert.are.equal(order[2].addr, "fe80::1", "prefer home address");
- ]]
-
- --[[ "fe80::2" should be marked "deprecated", we can't currently handle this and would fail the test
- order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("fe80::1", "IPv6")},
- {new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")})
- assert.are.equal(order[1].addr, "2001:db8:1::1", "avoid deprecated addresses");
- assert.are.equal(order[2].addr, "fe80::1", "avoid deprecated addresses");
- ]]
-
- order = rfc6724.destination({new_ip("2001:db8:1::1", "IPv6"), new_ip("2001:db8:3ffe::1", "IPv6")},
- {new_ip("2001:db8:1::2", "IPv6"), new_ip("2001:db8:3f44::2", "IPv6"), new_ip("fe80::2", "IPv6")})
- assert.are.equal(order[1].addr, "2001:db8:1::1", "longest matching prefix");
- assert.are.equal(order[2].addr, "2001:db8:3ffe::1", "longest matching prefix");
-
- order = rfc6724.destination({new_ip("2002:c633:6401::1", "IPv6"), new_ip("2001:db8:1::1", "IPv6")},
- {new_ip("2002:c633:6401::2", "IPv6"), new_ip("fe80::2", "IPv6")})
- assert.are.equal(order[1].addr, "2002:c633:6401::1", "prefer matching label");
- assert.are.equal(order[2].addr, "2001:db8:1::1", "prefer matching label");
-
- order = rfc6724.destination({new_ip("2002:c633:6401::1", "IPv6"), new_ip("2001:db8:1::1", "IPv6")},
- {new_ip("2002:c633:6401::2", "IPv6"), new_ip("2001:db8:1::2", "IPv6"), new_ip("fe80::2", "IPv6")})
- assert.are.equal(order[1].addr, "2001:db8:1::1", "prefer higher precedence");
- assert.are.equal(order[2].addr, "2002:c633:6401::1", "prefer higher precedence");
- end);
- end);
-end);
diff --git a/spec/util_strbitop.lua b/spec/util_strbitop_spec.lua
index 58a13772..963c9516 100644
--- a/spec/util_strbitop.lua
+++ b/spec/util_strbitop_spec.lua
@@ -38,4 +38,48 @@ describe("util.strbitop", function ()
assert.equal("hello", strbitop.sxor("hello", ""));
end);
end);
+
+ describe("common_prefix_bits()", function ()
+ local function B(s)
+ assert(#s%8==0, "Invalid test input: B(s): s should be a multiple of 8 bits in length");
+ local byte = 0;
+ local out_str = {};
+ for i = 1, #s do
+ local bit_ascii = s:byte(i);
+ if bit_ascii == 49 then -- '1'
+ byte = byte + 2^((7-(i-1))%8);
+ elseif bit_ascii ~= 48 then
+ error("Invalid test input: B(s): s should contain only '0' or '1' characters");
+ end
+ if (i-1)%8 == 7 then
+ table.insert(out_str, string.char(byte));
+ byte = 0;
+ end
+ end
+ return table.concat(out_str);
+ end
+
+ local _cpb = strbitop.common_prefix_bits;
+ local function test(a, b)
+ local Ba, Bb = B(a), B(b);
+ local ret1 = _cpb(Ba, Bb);
+ local ret2 = _cpb(Bb, Ba);
+ assert(ret1 == ret2, ("parameter order should not make a difference to the result (%s, %s) = %d, reversed = %d"):format(a, b, ret1, ret2));
+ return ret1;
+ end
+
+ it("works on single bytes", function ()
+ assert.equal(0, test("00000000", "11111111"));
+ assert.equal(1, test("10000000", "11111111"));
+ assert.equal(0, test("01000000", "11111111"));
+ assert.equal(0, test("01000000", "11111111"));
+ assert.equal(8, test("11111111", "11111111"));
+ end);
+
+ it("works on multiple bytes", function ()
+ for i = 0, 16 do
+ assert.equal(i, test(string.rep("1", i)..string.rep("0", 16-i), "1111111111111111"));
+ end
+ end);
+ end);
end);
diff --git a/teal-src/prosody/plugins/mod_cron.tl b/teal-src/prosody/plugins/mod_cron.tl
index 9c6f1601..4defc808 100644
--- a/teal-src/prosody/plugins/mod_cron.tl
+++ b/teal-src/prosody/plugins/mod_cron.tl
@@ -2,6 +2,10 @@ module:set_global();
local async = require "prosody.util.async";
+local cron_initial_delay = module:get_option_number("cron_initial_delay", 1);
+local cron_check_delay = module:get_option_number("cron_check_delay", 3600);
+local cron_spread_factor = module:get_option_number("cron_spread_factor", 0);
+
local record map_store<K,V>
-- TODO move to somewhere sensible
get : function (map_store<K,V>, string, K) : V
@@ -90,17 +94,21 @@ local function run_task(task : task_spec)
task:save(started_at);
end
+local function spread(t : number, factor : number) : number
+ return t * (1 - factor + 2*factor*math.random());
+end
+
local task_runner : async.runner_t<task_spec> = async.runner(run_task);
-scheduled = module:add_timer(1, function() : integer
+scheduled = module:add_timer(cron_initial_delay, function() : number
module:log("info", "Running periodic tasks");
- local delay = 3600;
+ local delay = spread(cron_check_delay, cron_spread_factor);
for host in pairs(active_hosts) do
module:log("debug", "Running periodic tasks for host %s", host);
for _, task in ipairs(module:context(host):get_host_items("task") as { task_spec } ) do
task_runner:run(task);
end
end
- module:log("debug", "Wait %ds", delay);
+ module:log("debug", "Wait %gs", delay);
return delay;
end);
diff --git a/teal-src/prosody/util/crypto.d.tl b/teal-src/prosody/util/crypto.d.tl
index cf0b0d1b..866185d0 100644
--- a/teal-src/prosody/util/crypto.d.tl
+++ b/teal-src/prosody/util/crypto.d.tl
@@ -5,23 +5,51 @@ local record lib
get_type : function (key) : string
end
- generate_ed25519_keypair : function () : key
- ed25519_sign : function (key, string) : string
- ed25519_verify : function (key, string, string) : boolean
+ type base_evp_sign = function (key, message : string) : string
+ type base_evp_verify = function (key, message : string, signature : string) : boolean
+
+ ed25519_sign : base_evp_sign
+ ed25519_verify : base_evp_verify
+
+ ecdsa_sha256_sign : base_evp_sign
+ ecdsa_sha256_verify : base_evp_verify
+ ecdsa_sha384_sign : base_evp_sign
+ ecdsa_sha384_verify : base_evp_verify
+ ecdsa_sha512_sign : base_evp_sign
+ ecdsa_sha512_verify : base_evp_verify
+
+ rsassa_pkcs1_sha256_sign : base_evp_sign
+ rsassa_pkcs1_sha256_verify : base_evp_verify
+ rsassa_pkcs1_sha384_sign : base_evp_sign
+ rsassa_pkcs1_sha384_verify : base_evp_verify
+ rsassa_pkcs1_sha512_sign : base_evp_sign
+ rsassa_pkcs1_sha512_verify : base_evp_verify
+
+ rsassa_pss_sha256_sign : base_evp_sign
+ rsassa_pss_sha256_verify : base_evp_verify
+ rsassa_pss_sha384_sign : base_evp_sign
+ rsassa_pss_sha384_verify : base_evp_verify
+ rsassa_pss_sha512_sign : base_evp_sign
+ rsassa_pss_sha512_verify : base_evp_verify
- ecdsa_sha256_sign : function (key, string) : string
- ecdsa_sha256_verify : function (key, string, string) : boolean
- parse_ecdsa_signature : function (string) : string, string
- build_ecdsa_signature : function (string, string) : string
+ type Levp_encrypt = function (key : string, iv : string, plaintext : string) : string
+ type Levp_decrypt = function (key : string, iv : string, ciphertext : string) : string, string
+
+ aes_128_gcm_encrypt : Levp_encrypt
+ aes_128_gcm_decrypt : Levp_decrypt
+ aes_256_gcm_encrypt : Levp_encrypt
+ aes_256_gcm_decrypt : Levp_decrypt
+
+ aes_256_ctr_encrypt : Levp_encrypt
+ aes_256_ctr_decrypt : Levp_decrypt
+
+ generate_ed25519_keypair : function () : key
import_private_pem : function (string) : key
import_public_pem : function (string) : key
- aes_128_gcm_encrypt : function (key, string, string) : string
- aes_128_gcm_decrypt : function (key, string, string) : string
- aes_256_gcm_encrypt : function (key, string, string) : string
- aes_256_gcm_decrypt : function (key, string, string) : string
-
+ parse_ecdsa_signature : function (string, integer) : string, string
+ build_ecdsa_signature : function (r : string, s : string) : string
version : string
_LIBCRYPTO_VERSION : string
diff --git a/teal-src/prosody/util/hashes.d.tl b/teal-src/prosody/util/hashes.d.tl
index 5c249627..64c5a12b 100644
--- a/teal-src/prosody/util/hashes.d.tl
+++ b/teal-src/prosody/util/hashes.d.tl
@@ -4,8 +4,8 @@ local type kdf = function (pass : string, salt : string, i : integer) : string
local record lib
sha1 : hash
- sha256 : hash
sha224 : hash
+ sha256 : hash
sha384 : hash
sha512 : hash
md5 : hash
@@ -14,16 +14,20 @@ local record lib
blake2s256 : hash
blake2b512 : hash
hmac_sha1 : hmac
- hmac_sha256 : hmac
hmac_sha224 : hmac
+ hmac_sha256 : hmac
hmac_sha384 :hmac
hmac_sha512 : hmac
hmac_md5 : hmac
hmac_sha3_256 : hmac
hmac_sha3_512 : hmac
+ hmac_blake2s256 : hmac
+ hmac_blake2b512 : hmac
scram_Hi_sha1 : kdf
pbkdf2_hmac_sha1 : kdf
pbkdf2_hmac_sha256 : kdf
+ hkdf_hmac_sha256 : kdf
+ hkdf_hmac_sha384 : kdf
equals : function (string, string) : boolean
version : string
_LIBCRYPTO_VERSION : string
diff --git a/teal-src/prosody/util/strbitop.d.tl b/teal-src/prosody/util/strbitop.d.tl
index 010efdb8..86577ef2 100644
--- a/teal-src/prosody/util/strbitop.d.tl
+++ b/teal-src/prosody/util/strbitop.d.tl
@@ -2,5 +2,6 @@ local record mod
sand : function (string, string) : string
sor : function (string, string) : string
sxor : function (string, string) : string
+ common_prefix_bits : function (string, string) : integer
end
return mod
diff --git a/tools/test_mutants.sh.lua b/tools/test_mutants.sh.lua
index a0a55a8e..6e2423db 100755
--- a/tools/test_mutants.sh.lua
+++ b/tools/test_mutants.sh.lua
@@ -33,7 +33,7 @@ if [[ "$SPEC_FILE" == "" || ! -f "$SPEC_FILE" ]]; then
exit 1;
fi
-if ! busted "$SPEC_FILE"; then
+if ! busted --helper=loader "$SPEC_FILE"; then
echo "EE: Tests fail on original source. Fix it"\!;
exit 1;
fi
diff --git a/util-src/signal.c b/util-src/signal.c
index a55b6f87..76d25d6f 100644
--- a/util-src/signal.c
+++ b/util-src/signal.c
@@ -32,6 +32,10 @@
#include <signal.h>
#include <stdlib.h>
+#ifdef __linux__
+#include <unistd.h>
+#include <sys/signalfd.h>
+#endif
#include "lua.h"
#include "lauxlib.h"
@@ -368,12 +372,81 @@ static int l_kill(lua_State *L) {
#endif
+#ifdef __linux__
+struct lsignalfd {
+ int fd;
+ sigset_t mask;
+};
+
+static int l_signalfd(lua_State *L) {
+ struct lsignalfd *sfd = lua_newuserdata(L, sizeof(struct lsignalfd));
+
+ sigemptyset(&sfd->mask);
+ sigaddset(&sfd->mask, luaL_checkinteger(L, 1));
+
+ if (sigprocmask(SIG_BLOCK, &sfd->mask, NULL) != 0) {
+ lua_pushnil(L);
+ return 1;
+ };
+
+ sfd->fd = signalfd(-1, &sfd->mask, SFD_NONBLOCK);
+
+ if(sfd->fd == -1) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ luaL_setmetatable(L, "signalfd");
+ return 1;
+}
+
+static int l_signalfd_getfd(lua_State *L) {
+ struct lsignalfd *sfd = luaL_checkudata(L, 1, "signalfd");
+
+ if (sfd->fd == -1) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushinteger(L, sfd->fd);
+ return 1;
+}
+
+static int l_signalfd_read(lua_State *L) {
+ struct lsignalfd *sfd = luaL_checkudata(L, 1, "signalfd");
+ struct signalfd_siginfo siginfo;
+
+ if(read(sfd->fd, &siginfo, sizeof(siginfo)) < 0) {
+ return 0;
+ }
+
+ lua_pushinteger(L, siginfo.ssi_signo);
+ return 1;
+}
+
+static int l_signalfd_close(lua_State *L) {
+ struct lsignalfd *sfd = luaL_checkudata(L, 1, "signalfd");
+
+ if(close(sfd->fd) != 0) {
+ lua_pushboolean(L, 0);
+ return 1;
+ }
+
+ sfd->fd = -1;
+ lua_pushboolean(L, 1);
+ return 1;
+}
+#endif
+
static const struct luaL_Reg lsignal_lib[] = {
{"signal", l_signal},
{"raise", l_raise},
#if defined(__unix__) || defined(__APPLE__)
{"kill", l_kill},
#endif
+#ifdef __linux__
+ {"signalfd", l_signalfd},
+#endif
{NULL, NULL}
};
@@ -381,6 +454,23 @@ int luaopen_prosody_util_signal(lua_State *L) {
luaL_checkversion(L);
int i = 0;
+#ifdef __linux__
+ luaL_newmetatable(L, "signalfd");
+ lua_pushcfunction(L, l_signalfd_close);
+ lua_setfield(L, -2, "__gc");
+ lua_createtable(L, 0, 1);
+ {
+ lua_pushcfunction(L, l_signalfd_getfd);
+ lua_setfield(L, -2, "getfd");
+ lua_pushcfunction(L, l_signalfd_read);
+ lua_setfield(L, -2, "read");
+ lua_pushcfunction(L, l_signalfd_close);
+ lua_setfield(L, -2, "close");
+ }
+ lua_setfield(L, -2, "__index");
+ lua_pop(L, 1);
+#endif
+
/* add the library */
lua_newtable(L);
luaL_setfuncs(L, lsignal_lib, 0);
diff --git a/util-src/strbitop.c b/util-src/strbitop.c
index 75cfea81..2f6bf6e6 100644
--- a/util-src/strbitop.c
+++ b/util-src/strbitop.c
@@ -8,6 +8,8 @@
#include <lua.h>
#include <lauxlib.h>
+#include <sys/param.h>
+#include <limits.h>
/* TODO Deduplicate code somehow */
@@ -74,11 +76,46 @@ static int strop_xor(lua_State *L) {
return 1;
}
+unsigned int clz(unsigned char c) {
+#if __GNUC__
+ return __builtin_clz((unsigned int) c) - ((sizeof(int)-1)*CHAR_BIT);
+#else
+ if(c & 0x80) return 0;
+ if(c & 0x40) return 1;
+ if(c & 0x20) return 2;
+ if(c & 0x10) return 3;
+ if(c & 0x08) return 4;
+ if(c & 0x04) return 5;
+ if(c & 0x02) return 6;
+ if(c & 0x01) return 7;
+ return 8;
+#endif
+}
+
+LUA_API int strop_common_prefix_bits(lua_State *L) {
+ size_t a, b, i;
+ const char *str_a = luaL_checklstring(L, 1, &a);
+ const char *str_b = luaL_checklstring(L, 2, &b);
+
+ size_t min_len = MIN(a, b);
+
+ for(i=0; i<min_len; i++) {
+ if(str_a[i] != str_b[i]) {
+ lua_pushinteger(L, i*8 + (clz(str_a[i] ^ str_b[i])));
+ return 1;
+ }
+ }
+
+ lua_pushinteger(L, i*8);
+ return 1;
+}
+
LUA_API int luaopen_prosody_util_strbitop(lua_State *L) {
luaL_Reg exports[] = {
{ "sand", strop_and },
{ "sor", strop_or },
{ "sxor", strop_xor },
+ { "common_prefix_bits", strop_common_prefix_bits },
{ NULL, NULL }
};
diff --git a/util/bit53.lua b/util/bit53.lua
index b5c473a3..42f17ce8 100644
--- a/util/bit53.lua
+++ b/util/bit53.lua
@@ -27,6 +27,9 @@ return {
end
return ret;
end;
+ bnot = function (x)
+ return ~x;
+ end;
rshift = function (a, n) return a >> n end;
lshift = function (a, n) return a << n end;
};
diff --git a/util/ip.lua b/util/ip.lua
index 268b7d10..d820e72d 100644
--- a/util/ip.lua
+++ b/util/ip.lua
@@ -6,7 +6,7 @@
--
local net = require "prosody.util.net";
-local hex = require "prosody.util.hex";
+local strbit = require "prosody.util.strbitop";
local ip_methods = {};
@@ -28,13 +28,6 @@ ip_mt.__eq = function (ipA, ipB)
return ipA.packed == ipB.packed;
end
-local hex2bits = {
- ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011",
- ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111",
- ["8"] = "1000", ["9"] = "1001", ["A"] = "1010", ["B"] = "1011",
- ["C"] = "1100", ["D"] = "1101", ["E"] = "1110", ["F"] = "1111",
-};
-
local function new_ip(ipStr, proto)
local zone;
if (not proto or proto == "IPv6") and ipStr:find('%', 1, true) then
@@ -66,27 +59,18 @@ function ip_methods:normal()
return net.ntop(self.packed);
end
-function ip_methods.bits(ip)
- return hex.encode(ip.packed):upper():gsub(".", hex2bits);
-end
-
-function ip_methods.bits_full(ip)
+-- Returns the longest packed representation, i.e. IPv4 will be mapped
+function ip_methods.packed_full(ip)
if ip.proto == "IPv4" then
ip = ip.toV4mapped;
end
- return ip.bits;
+ return ip.packed;
end
local match;
local function commonPrefixLength(ipA, ipB)
- ipA, ipB = ipA.bits_full, ipB.bits_full;
- for i = 1, 128 do
- if ipA:sub(i,i) ~= ipB:sub(i,i) then
- return i-1;
- end
- end
- return 128;
+ return strbit.common_prefix_bits(ipA.packed_full, ipB.packed_full);
end
-- Instantiate once
@@ -238,7 +222,7 @@ function match(ipA, ipB, bits)
bits = bits + (128 - 32);
end
end
- return ipA.bits:sub(1, bits) == ipB.bits:sub(1, bits);
+ return strbit.common_prefix_bits(ipA.packed, ipB.packed) >= bits;
end
local function is_ip(obj)
diff --git a/util/prosodyctl/check.lua b/util/prosodyctl/check.lua
index 5e7087c5..8d085c85 100644
--- a/util/prosodyctl/check.lua
+++ b/util/prosodyctl/check.lua
@@ -735,6 +735,57 @@ local function check(arg)
end
end
+ -- Check hostname validity
+ do
+ local idna = require "prosody.util.encodings".idna;
+ local invalid_hosts = {};
+ local alabel_hosts = {};
+ for host in it.filter("*", pairs(configmanager.getconfig())) do
+ local _, h, _ = jid_split(host);
+ if not h or not idna.to_ascii(h) then
+ table.insert(invalid_hosts, host);
+ else
+ for label in h:gmatch("[^%.]+") do
+ if label:match("^xn%-%-") then
+ table.insert(alabel_hosts, host);
+ break;
+ end
+ end
+ end
+ end
+
+ if #invalid_hosts > 0 then
+ table.sort(invalid_hosts);
+ print("");
+ print(" Your configuration contains invalid host names:");
+ print(" "..table.concat(invalid_hosts, "\n "));
+ print("");
+ print(" Clients may not be able to log in to these hosts, or you may not be able to");
+ print(" communicate with remote servers.");
+ print(" Use a valid domain name to correct this issue.");
+ end
+
+ if #alabel_hosts > 0 then
+ table.sort(alabel_hosts);
+ print("");
+ print(" Your configuration contains incorrectly-encoded hostnames:");
+ for _, ahost in ipairs(alabel_hosts) do
+ print((" '%s' (should be '%s')"):format(ahost, idna.to_unicode(ahost)));
+ end
+ print("");
+ print(" Clients may not be able to log in to these hosts, or you may not be able to");
+ print(" communicate with remote servers.");
+ print(" To correct this issue, use the Unicode version of the domain in Prosody's config file.");
+ end
+
+ if #invalid_hosts > 0 or #alabel_hosts > 0 then
+ print("");
+ print("WARNING: Changing the name of a VirtualHost in Prosody's config file");
+ print(" WILL NOT migrate any existing data (user accounts, etc.) to the new name.");
+ ok = false;
+ end
+ end
+
print("Done.\n");
end
function checks.dns()
diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua
index f3279e75..05f81f15 100644
--- a/util/prosodyctl/shell.lua
+++ b/util/prosodyctl/shell.lua
@@ -83,7 +83,7 @@ local function start(arg) --luacheck: ignore 212/arg
for i = 3, #arg do
if arg[i]:sub(1, 1) == ":" then
table.insert(fmt, i, ")%s(");
- elseif i > 3 and fmt[i - 1] == "%q" then
+ elseif i > 3 and fmt[i - 1]:match("%%q$") then
table.insert(fmt, i, ", %q");
else
table.insert(fmt, i, "%q");
diff --git a/util/pubsub.lua b/util/pubsub.lua
index e089b08c..ccde8b53 100644
--- a/util/pubsub.lua
+++ b/util/pubsub.lua
@@ -263,7 +263,7 @@ function service:get_default_affiliation(node, actor) --> affiliation
if self.config.access_models then
local check = self.config.access_models[access_model];
if check then
- local aff = check(actor);
+ local aff = check(actor, node_obj);
if aff then
return aff;
end
diff --git a/util/rfc6724.lua b/util/rfc6724.lua
deleted file mode 100644
index 33477ed7..00000000
--- a/util/rfc6724.lua
+++ /dev/null
@@ -1,141 +0,0 @@
--- Prosody IM
--- Copyright (C) 2011-2013 Florian Zeitz
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
--- This is used to sort destination addresses by preference
--- during S2S connections.
--- We can't hand this off to getaddrinfo, since it blocks
-
-local ip_commonPrefixLength = require"prosody.util.ip".commonPrefixLength
-
-local function commonPrefixLength(ipA, ipB)
- local len = ip_commonPrefixLength(ipA, ipB);
- return len < 64 and len or 64;
-end
-
-local function t_sort(t, comp)
- for i = 1, (#t - 1) do
- for j = (i + 1), #t do
- local a, b = t[i], t[j];
- if not comp(a,b) then
- t[i], t[j] = b, a;
- end
- end
- end
-end
-
-local function source(dest, candidates)
- local function comp(ipA, ipB)
- -- Rule 1: Prefer same address
- if dest == ipA then
- return true;
- elseif dest == ipB then
- return false;
- end
-
- -- Rule 2: Prefer appropriate scope
- if ipA.scope < ipB.scope then
- if ipA.scope < dest.scope then
- return false;
- else
- return true;
- end
- elseif ipA.scope > ipB.scope then
- if ipB.scope < dest.scope then
- return true;
- else
- return false;
- end
- end
-
- -- Rule 3: Avoid deprecated addresses
- -- XXX: No way to determine this
- -- Rule 4: Prefer home addresses
- -- XXX: Mobility Address related, no way to determine this
- -- Rule 5: Prefer outgoing interface
- -- XXX: Interface to address relation. No way to determine this
- -- Rule 6: Prefer matching label
- if ipA.label == dest.label and ipB.label ~= dest.label then
- return true;
- elseif ipB.label == dest.label and ipA.label ~= dest.label then
- return false;
- end
-
- -- Rule 7: Prefer temporary addresses (over public ones)
- -- XXX: No way to determine this
- -- Rule 8: Use longest matching prefix
- if commonPrefixLength(ipA, dest) > commonPrefixLength(ipB, dest) then
- return true;
- else
- return false;
- end
- end
-
- t_sort(candidates, comp);
- return candidates[1];
-end
-
-local function destination(candidates, sources)
- local sourceAddrs = {};
- local function comp(ipA, ipB)
- local ipAsource = sourceAddrs[ipA];
- local ipBsource = sourceAddrs[ipB];
- -- Rule 1: Avoid unusable destinations
- -- XXX: No such information
- -- Rule 2: Prefer matching scope
- if ipA.scope == ipAsource.scope and ipB.scope ~= ipBsource.scope then
- return true;
- elseif ipA.scope ~= ipAsource.scope and ipB.scope == ipBsource.scope then
- return false;
- end
-
- -- Rule 3: Avoid deprecated addresses
- -- XXX: No way to determine this
- -- Rule 4: Prefer home addresses
- -- XXX: Mobility Address related, no way to determine this
- -- Rule 5: Prefer matching label
- if ipAsource.label == ipA.label and ipBsource.label ~= ipB.label then
- return true;
- elseif ipBsource.label == ipB.label and ipAsource.label ~= ipA.label then
- return false;
- end
-
- -- Rule 6: Prefer higher precedence
- if ipA.precedence > ipB.precedence then
- return true;
- elseif ipA.precedence < ipB.precedence then
- return false;
- end
-
- -- Rule 7: Prefer native transport
- -- XXX: No way to determine this
- -- Rule 8: Prefer smaller scope
- if ipA.scope < ipB.scope then
- return true;
- elseif ipA.scope > ipB.scope then
- return false;
- end
-
- -- Rule 9: Use longest matching prefix
- if commonPrefixLength(ipA, ipAsource) > commonPrefixLength(ipB, ipBsource) then
- return true;
- elseif commonPrefixLength(ipA, ipAsource) < commonPrefixLength(ipB, ipBsource) then
- return false;
- end
-
- -- Rule 10: Otherwise, leave order unchanged
- return true;
- end
- for _, ip in ipairs(candidates) do
- sourceAddrs[ip] = source(ip, sources);
- end
-
- t_sort(candidates, comp);
- return candidates;
-end
-
-return {source = source,
- destination = destination};
diff --git a/util/startup.lua b/util/startup.lua
index 0066fb8c..507c0528 100644
--- a/util/startup.lua
+++ b/util/startup.lua
@@ -392,6 +392,8 @@ function startup.load_secondary_libraries()
require "prosody.util.stanza"
require "prosody.util.jid"
+
+ prosody.features = require "prosody.core.features".available;
end
function startup.init_http_client()
@@ -529,21 +531,30 @@ function startup.force_console_logging()
config.set("*", "log", { { levels = { min = log_level or "info" }, to = "console" } });
end
+local function check_posix()
+ if prosody.platform ~= "posix" then return end
+
+ local want_pposix_version = "0.4.0";
+ local have_pposix, pposix = pcall(require, "prosody.util.pposix");
+
+ if pposix._VERSION ~= want_pposix_version then
+ print(string.format("Unknown version (%s) of binary pposix module, expected %s",
+ tostring(pposix._VERSION), want_pposix_version));
+ os.exit(1);
+ end
+ if have_pposix and pposix then
+ return pposix;
+ end
+end
+
function startup.switch_user()
-- Switch away from root and into the prosody user --
-- NOTE: This function is only used by prosodyctl.
-- The prosody process is built with the assumption that
-- it is already started as the appropriate user.
- local want_pposix_version = "0.4.0";
- local have_pposix, pposix = pcall(require, "prosody.util.pposix");
-
- if have_pposix and pposix then
- if pposix._VERSION ~= want_pposix_version then
- print(string.format("Unknown version (%s) of binary pposix module, expected %s",
- tostring(pposix._VERSION), want_pposix_version));
- os.exit(1);
- end
+ local pposix = check_posix()
+ if pposix then
prosody.current_uid = pposix.getuid();
local arg_root = prosody.opts.root;
if prosody.current_uid == 0 and config.get("*", "run_as_root") ~= true and not arg_root then
@@ -669,6 +680,168 @@ function startup.make_dummy_hosts()
end
end
+function startup.posix_umask()
+ if prosody.platform ~= "posix" then return end
+ local pposix = require "prosody.util.pposix";
+ local umask = config.get("*", "umask") or "027";
+ pposix.umask(umask);
+end
+
+function startup.check_user()
+ local pposix = check_posix();
+ if not pposix then return end
+ -- Don't even think about it!
+ if pposix.getuid() == 0 and not config.get("*", "run_as_root") then
+ print("Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
+ print("For more information on running Prosody as root, see https://prosody.im/doc/root");
+ os.exit(1); -- Refusing to run as root
+ end
+end
+
+local function remove_pidfile()
+ local pidfile = prosody.pidfile;
+ if prosody.pidfile_handle then
+ prosody.pidfile_handle:close();
+ os.remove(pidfile);
+ prosody.pidfile, prosody.pidfile_handle = nil, nil;
+ end
+end
+
+function startup.write_pidfile()
+ local pposix = check_posix();
+ if not pposix then return end
+ local lfs = require "lfs";
+ local stat = lfs.attributes;
+ local pidfile = config.get("*", "pidfile") or nil;
+ if not pidfile then return end
+ pidfile = config.resolve_relative_path(prosody.paths.data, pidfile);
+ local mode = stat(pidfile) and "r+" or "w+";
+ local pidfile_handle, err = io.open(pidfile, mode);
+ if not pidfile_handle then
+ log("error", "Couldn't write pidfile at %s; %s", pidfile, err);
+ os.exit(1);
+ else
+ prosody.pidfile = pidfile;
+ if not lfs.lock(pidfile_handle, "w") then -- Exclusive lock
+ local other_pid = pidfile_handle:read("*a");
+ log("error", "Another Prosody instance seems to be running with PID %s, quitting", other_pid);
+ prosody.pidfile_handle = nil;
+ os.exit(1);
+ else
+ pidfile_handle:close();
+ pidfile_handle, err = io.open(pidfile, "w+");
+ if not pidfile_handle then
+ log("error", "Couldn't write pidfile at %s; %s", pidfile, err);
+ os.exit(1);
+ else
+ if lfs.lock(pidfile_handle, "w") then
+ pidfile_handle:write(tostring(pposix.getpid()));
+ pidfile_handle:flush();
+ prosody.pidfile_handle = pidfile_handle;
+ end
+ end
+ end
+ end
+ prosody.events.add_handler("server-stopped", remove_pidfile);
+end
+
+local function remove_log_sinks()
+ local lm = require "prosody.core.loggingmanager";
+ lm.register_sink_type("console", nil);
+ lm.register_sink_type("stdout", nil);
+ lm.reload_logging();
+end
+
+function startup.posix_daemonize()
+ if not prosody.opts.daemonize then return end
+ local pposix = check_posix();
+ log("info", "Prosody is about to detach from the console, disabling further console output");
+ remove_log_sinks();
+ local ok, ret = pposix.daemonize();
+ if not ok then
+ log("error", "Failed to daemonize: %s", ret);
+ elseif ret and ret > 0 then
+ os.exit(0);
+ else
+ log("info", "Successfully daemonized to PID %d", pposix.getpid());
+ end
+end
+
+function startup.hook_posix_signals()
+ if prosody.platform ~= "posix" then return end
+ local have_signal, signal = pcall(require, "prosody.util.signal");
+ if not have_signal then
+ log("warn", "Couldn't load signal library, won't respond to SIGTERM");
+ return
+ end
+ signal.signal("SIGTERM", function()
+ log("warn", "Received SIGTERM");
+ prosody.main_thread:run(function()
+ prosody.unlock_globals();
+ prosody.shutdown("Received SIGTERM");
+ prosody.lock_globals();
+ end);
+ end);
+
+ signal.signal("SIGHUP", function()
+ log("info", "Received SIGHUP");
+ prosody.main_thread:run(function() prosody.reload_config(); end);
+ -- this also reloads logging
+ end);
+
+ signal.signal("SIGINT", function()
+ log("info", "Received SIGINT");
+ prosody.main_thread:run(function()
+ prosody.unlock_globals();
+ prosody.shutdown("Received SIGINT");
+ prosody.lock_globals();
+ end);
+ end);
+
+ signal.signal("SIGUSR1", function()
+ log("info", "Received SIGUSR1");
+ prosody.events.fire_event("signal/SIGUSR1");
+ end);
+
+ signal.signal("SIGUSR2", function()
+ log("info", "Received SIGUSR2");
+ prosody.events.fire_event("signal/SIGUSR2");
+ end);
+end
+
+function startup.systemd_notify()
+ local notify_socket_name = os.getenv("NOTIFY_SOCKET");
+ if not notify_socket_name then return end
+ local have_unix, unix = pcall(require, "socket.unix");
+ if not have_unix or type(unix) ~= "table" then
+ log("error", "LuaSocket without UNIX socket support, can't notify systemd.")
+ return os.exit(1);
+ end
+ log("debug", "Will notify on socket %q", notify_socket_name);
+ notify_socket_name = notify_socket_name:gsub("^@", "\0");
+ local notify_socket = unix.dgram();
+ local ok, err = notify_socket:setpeername(notify_socket_name);
+ if not ok then
+ log("error", "Could not connect to systemd notification socket %q: %q", notify_socket_name, err);
+ return os.exit(1);
+ end
+ local time = require "prosody.util.time";
+
+ prosody.notify_socket = notify_socket;
+ prosody.events.add_handler("server-started", function()
+ notify_socket:send("READY=1");
+ end);
+ prosody.events.add_handler("reloading-config", function()
+ notify_socket:send(string.format("RELOADING=1\nMONOTONIC_USEC=%d", math.floor(time.monotonic() * 1000000)));
+ end);
+ prosody.events.add_handler("config-reloaded", function()
+ notify_socket:send("READY=1");
+ end);
+ prosody.events.add_handler("server-stopping", function()
+ notify_socket:send("STOPPING=1");
+ end);
+end
+
function startup.cleanup()
prosody.log("info", "Shutdown status: Cleaning up");
prosody.events.fire_event("server-cleanup");
@@ -724,6 +897,7 @@ function startup.prosody()
startup.parse_args();
startup.init_global_state();
startup.read_config();
+ startup.check_user();
startup.init_logging();
startup.init_gc();
startup.init_errors();
@@ -746,6 +920,10 @@ function startup.prosody()
startup.init_http_client();
startup.init_data_store();
startup.init_global_protection();
+ startup.posix_daemonize();
+ startup.write_pidfile();
+ startup.hook_posix_signals();
+ startup.systemd_notify();
startup.prepare_to_start();
startup.notify_started();
end