aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGES5
-rw-r--r--core/certmanager.lua14
-rw-r--r--core/moduleapi.lua1
-rw-r--r--net/http.lua3
-rw-r--r--net/server_epoll.lua40
-rw-r--r--net/server_event.lua17
-rw-r--r--net/tls_luasec.lua5
-rw-r--r--plugins/mod_admin_shell.lua3
-rw-r--r--plugins/mod_auth_internal_hashed.lua3
-rw-r--r--plugins/mod_auth_internal_plain.lua3
-rw-r--r--plugins/mod_blocklist.lua9
-rw-r--r--plugins/mod_c2s.lua18
-rw-r--r--plugins/mod_csi.lua21
-rw-r--r--plugins/mod_invites.lua2
-rw-r--r--plugins/mod_s2s.lua30
-rw-r--r--plugins/mod_s2s_bidi.lua7
-rw-r--r--plugins/muc/mod_muc.lua10
-rw-r--r--plugins/muc/restrict_pm.lib.lua119
-rw-r--r--spec/scansion/prosody.cfg.lua2
-rw-r--r--spec/scansion/pubsub_config.scs2
-rw-r--r--spec/scansion/pubsub_max_items.scs1
-rw-r--r--spec/scansion/pubsub_multi_items.scs1
-rw-r--r--spec/scansion/pubsub_preconditions.scs2
-rw-r--r--spec/util_xtemplate_spec.lua4
-rw-r--r--teal-src/prosody/util/xtemplate.tl5
-rw-r--r--util/prosodyctl.lua24
-rw-r--r--util/prosodyctl/cert.lua2
-rw-r--r--util/sslconfig.lua14
-rw-r--r--util/xtemplate.lua3
29 files changed, 348 insertions, 22 deletions
diff --git a/CHANGES b/CHANGES
index 70aa13c3..fbeec13c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -16,6 +16,10 @@ TRUNK
### MUC
+- Component admins are no longer room owners by default. This can be reverted
+ to the old behaviour with `component_admins_as_room_owners = true`, but this
+ has known incompatibilities with some clients. Instead, use the shell or
+ ad-hoc commands to gain ownership of rooms when necessary.
- Permissions updates:
- Room creation restricted to local users (of the parent host) by default
- restrict_room_creation = true restricts to admins, false disables all restrictions
@@ -68,6 +72,7 @@ TRUNK
- 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
+- Support for systemd socket activation in server_epoll
## Removed
diff --git a/core/certmanager.lua b/core/certmanager.lua
index 263797e5..9e0ace6a 100644
--- a/core/certmanager.lua
+++ b/core/certmanager.lua
@@ -213,6 +213,18 @@ local core_defaults = {
dane = tls.features.capabilities.dane and configmanager.get("*", "use_dane") and { "no_ee_namechecks" };
}
+-- https://datatracker.ietf.org/doc/html/rfc7919#appendix-A.1
+local ffdhe2048 = [[
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
+87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
+YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
+7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
+ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
+-----END DH PARAMETERS-----
+]]
+
local mozilla_ssl_configs = {
-- https://wiki.mozilla.org/Security/Server_Side_TLS
-- Version 5.7 as of 2023-07-09
@@ -225,7 +237,7 @@ local mozilla_ssl_configs = {
};
intermediate = {
protocol = "tlsv1_2+";
- dhparam = nil; -- ffdhe2048.txt
+ dhparam = ffdhe2048;
options = { cipher_server_preference = false };
ciphers = {
"ECDHE-ECDSA-AES128-GCM-SHA256";
diff --git a/core/moduleapi.lua b/core/moduleapi.lua
index 31d1b1bd..fa5086cf 100644
--- a/core/moduleapi.lua
+++ b/core/moduleapi.lua
@@ -653,6 +653,7 @@ end
function api:metric(type_, name, unit, description, label_keys, conf)
local metric = require "prosody.core.statsmanager".metric;
local is_scoped = self.host ~= "*"
+ label_keys = label_keys or {};
if is_scoped then
-- prepend `host` label to label keys if this is not a global module
local orig_labels = label_keys
diff --git a/net/http.lua b/net/http.lua
index bea1e905..35a92d57 100644
--- a/net/http.lua
+++ b/net/http.lua
@@ -317,6 +317,9 @@ local function request(self, u, ex, callback)
if ex and ex.use_dane ~= nil then
use_dane = ex.use_dane;
end
+ if not sslctx then
+ error("Attempt to make HTTPS request but no 'sslctx' provided in options");
+ end
end
if self.pool then
diff --git a/net/server_epoll.lua b/net/server_epoll.lua
index c946a751..6c110241 100644
--- a/net/server_epoll.lua
+++ b/net/server_epoll.lua
@@ -35,6 +35,38 @@ local poller = require "prosody.util.poll"
local EEXIST = poller.EEXIST;
local ENOENT = poller.ENOENT;
+-- systemd socket activation
+local SD_LISTEN_FDS_START = 3;
+local SD_LISTEN_FDS = tonumber(os.getenv("LISTEN_FDS")) or 0;
+
+local inherited_sockets = setmetatable({}, {
+ __index = function(t, k)
+ local serv_mt = debug.getregistry()["tcp{server}"];
+ for i = 1, SD_LISTEN_FDS do
+ local serv = socket.tcp();
+ if serv:getfd() ~= _SOCKETINVALID then
+ -- If LuaSocket allocated a FD for then we can't really close it and it would leak.
+ log("error", "LuaSocket not compatible with socket activation. Upgrade LuaSocket or disable socket activation.");
+ setmetatable(t, nil);
+ break
+ end
+ serv:setfd(SD_LISTEN_FDS_START + i - 1);
+ debug.setmetatable(serv, serv_mt);
+ serv:settimeout(0);
+ local ip, port = serv:getsockname();
+ t[ip .. ":" .. port] = serv;
+ if ip == "0.0.0.0" then
+ -- LuaSocket treats '*' as an alias for '0.0.0.0'
+ t["*:" .. port] = serv;
+ end
+ end
+
+ -- Disable lazy-loading mechanism once performed
+ setmetatable(t, nil);
+ return t[k];
+ end;
+});
+
local poll = assert(poller.new());
local _ENV = nil;
@@ -944,6 +976,14 @@ local function wrapserver(conn, addr, port, listeners, config)
end
local function listen(addr, port, listeners, config)
+ local inherited = inherited_sockets[addr .. ":" .. port];
+ if inherited then
+ local conn = wrapserver(inherited, addr, port, listeners, config);
+ -- sockets created by systemd must not be :close() since we may not have
+ -- privileges to create them
+ conn.destroy = interface.del;
+ return conn;
+ end
local conn, err = socket.bind(addr, port, cfg.tcp_backlog);
if not conn then return conn, err; end
conn:settimeout(0);
diff --git a/net/server_event.lua b/net/server_event.lua
index 3bad3474..44222aa3 100644
--- a/net/server_event.lua
+++ b/net/server_event.lua
@@ -735,7 +735,10 @@ local function handleserver( server, addr, port, pattern, listener, sslctx, star
debug( "maximal connections reached, refuse client connection; accept delay:", delay )
return EV_TIMEOUT, delay -- delay for next accept attempt
end
- local client_ip, client_port = client:getpeername( )
+ local client_ip, client_port = addr, port;
+ if client.getpeername then -- Only IP sockets have this method, UNIX sockets don't
+ client_ip, client_port = client:getpeername( )
+ end
interface._connections = interface._connections + 1 -- increase connection count
local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, sslctx )
--vdebug( "client id:", clientinterface, "startssl:", startssl )
@@ -758,6 +761,17 @@ local function handleserver( server, addr, port, pattern, listener, sslctx, star
return interface
end
+local function wrapserver(conn, addr, port, listeners, config)
+ config = config or {}
+ if config.sslctx and not has_luasec then
+ debug "fatal error: luasec not found"
+ return nil, "luasec not found"
+ end
+ local interface = handleserver( conn, addr, port, config.read_size, listeners, config.tls_ctx, config.tls_direct) -- new server handler
+ debug( "new server created with id:", tostring(interface))
+ return interface
+end
+
local function listen(addr, port, listener, config)
config = config or {}
if config.sslctx and not has_luasec then
@@ -947,6 +961,7 @@ return {
listen = listen,
addclient = addclient,
wrapclient = wrapclient,
+ wrapserver = wrapserver,
setquitting = setquitting,
closeall = closeallservers,
get_backend = get_backend,
diff --git a/net/tls_luasec.lua b/net/tls_luasec.lua
index 3af2fc6b..4e4e92ed 100644
--- a/net/tls_luasec.lua
+++ b/net/tls_luasec.lua
@@ -54,7 +54,10 @@ local function new_context(cfg, builder)
-- LuaSec expects dhparam to be a callback that takes two arguments.
-- We ignore those because it is mostly used for having a separate
-- set of params for EXPORT ciphers, which we don't have by default.
- if type(cfg.dhparam) == "string" then
+ if type(cfg.dhparam) == "string" and cfg.dhparam:sub(1, 10) == "-----BEGIN" then
+ local dhparam = cfg.dhparam;
+ cfg.dhparam = function() return dhparam; end
+ elseif type(cfg.dhparam) == "string" then
local f, err = io_open(cfg.dhparam);
if not f then return nil, "Could not open DH parameters: "..err end
local dhparam = f:read("*a");
diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua
index d085ce43..e6b44f00 100644
--- a/plugins/mod_admin_shell.lua
+++ b/plugins/mod_admin_shell.lua
@@ -978,7 +978,7 @@ available_columns = {
return capitalize(cert_status);
end
-- no certificate status,
- if session.cert_chain_errors then
+ if type(session.cert_chain_errors) == "table" then
local cert_errors = set.new(session.cert_chain_errors[1]);
if cert_errors:contains("certificate has expired") then
return "Expired";
@@ -989,6 +989,7 @@ available_columns = {
-- TODO borrow more logic from mod_s2s/friendly_cert_error()
return "Untrusted";
end
+ -- TODO cert_chain_errors can be a string, handle that
return "Unknown";
end;
};
diff --git a/plugins/mod_auth_internal_hashed.lua b/plugins/mod_auth_internal_hashed.lua
index 4840f431..806eb9bd 100644
--- a/plugins/mod_auth_internal_hashed.lua
+++ b/plugins/mod_auth_internal_hashed.lua
@@ -37,6 +37,9 @@ local provider = {};
function provider.test_password(username, password)
log("debug", "test password for user '%s'", username);
local credentials = accounts:get(username) or {};
+ if credentials.disabled then
+ return nil, "Account disabled.";
+ end
password = saslprep(password);
if not password then
return nil, "Password fails SASLprep.";
diff --git a/plugins/mod_auth_internal_plain.lua b/plugins/mod_auth_internal_plain.lua
index 98df1983..6cced803 100644
--- a/plugins/mod_auth_internal_plain.lua
+++ b/plugins/mod_auth_internal_plain.lua
@@ -22,6 +22,9 @@ local provider = {};
function provider.test_password(username, password)
log("debug", "test password for user '%s'", username);
local credentials = accounts:get(username) or {};
+ if credentials.disabled then
+ return nil, "Account disabled.";
+ end
password = saslprep(password);
if not password then
return nil, "Password fails SASLprep.";
diff --git a/plugins/mod_blocklist.lua b/plugins/mod_blocklist.lua
index e9a103c8..6587c8b1 100644
--- a/plugins/mod_blocklist.lua
+++ b/plugins/mod_blocklist.lua
@@ -335,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_c2s.lua b/plugins/mod_c2s.lua
index 1a24c27c..09d4be08 100644
--- a/plugins/mod_c2s.lua
+++ b/plugins/mod_c2s.lua
@@ -30,6 +30,12 @@ local stream_close_timeout = module:get_option_period("c2s_close_timeout", 5);
local opt_keepalives = module:get_option_boolean("c2s_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true));
local stanza_size_limit = module:get_option_integer("c2s_stanza_size_limit", 1024*256,10000);
+local advertised_idle_timeout = 14*60; -- default in all net.server implementations
+local network_settings = module:get_option("network_settings");
+if type(network_settings) == "table" and type(network_settings.read_timeout) == "number" then
+ advertised_idle_timeout = network_settings.read_timeout;
+end
+
local measure_connections = module:metric("gauge", "connections", "", "Established c2s connections", {"host", "type", "ip_family"});
local sessions = module:shared("sessions");
@@ -130,10 +136,16 @@ function stream_callbacks._streamopened(session, attr)
local features = st.stanza("stream:features");
hosts[session.host].events.fire_event("stream-features", { origin = session, features = features, stream = attr });
if features.tags[1] or session.full_jid then
- if stanza_size_limit then
+ if stanza_size_limit or advertised_idle_timeout then
features:reset();
- features:tag("limits", { xmlns = "urn:xmpp:stream-limits:0" })
- :text_tag("max-bytes", string.format("%d", stanza_size_limit)):up();
+ local limits = features:tag("limits", { xmlns = "urn:xmpp:stream-limits:0" });
+ if stanza_size_limit then
+ limits:text_tag("max-bytes", string.format("%d", stanza_size_limit));
+ end
+ if advertised_idle_timeout then
+ limits:text_tag("idle-seconds", string.format("%d", advertised_idle_timeout));
+ end
+ limits:reset();
end
send(features);
else
diff --git a/plugins/mod_csi.lua b/plugins/mod_csi.lua
index 82efd831..76a5afd4 100644
--- a/plugins/mod_csi.lua
+++ b/plugins/mod_csi.lua
@@ -3,6 +3,7 @@ local xmlns_csi = "urn:xmpp:csi:0";
local csi_feature = st.stanza("csi", { xmlns = xmlns_csi });
local change = module:metric("counter", "changes", "events", "CSI state changes", {"csi_state"});
+local count = module:metric("gauge", "state", "sessions", "", { "csi_state" });
module:hook("stream-features", function (event)
if event.origin.username then
@@ -23,3 +24,23 @@ end
module:hook("stanza/"..xmlns_csi..":active", refire_event("csi-client-active"));
module:hook("stanza/"..xmlns_csi..":inactive", refire_event("csi-client-inactive"));
+
+module:hook_global("stats-update", function()
+ local sessions = prosody.hosts[module.host].sessions;
+ if not sessions then return end
+ local active, inactive, flushing = 0, 0, 0;
+ for _, user_session in pairs(sessions) do
+ for _, session in pairs(user_session.sessions) do
+ if session.state == "inactive" then
+ inactive = inactive + 1;
+ elseif session.state == "active" then
+ active = active + 1;
+ elseif session.state == "flushing" then
+ flushing = flushing + 1;
+ end
+ end
+ end
+ count:with_labels("active"):set(active);
+ count:with_labels("inactive"):set(inactive);
+ count:with_labels("flushing"):set(flushing);
+end);
diff --git a/plugins/mod_invites.lua b/plugins/mod_invites.lua
index 559170cc..5ee9430a 100644
--- a/plugins/mod_invites.lua
+++ b/plugins/mod_invites.lua
@@ -193,7 +193,7 @@ function get(token, username)
type = token_info and token_info.type or "roster";
uri = token_info and token_info.uri or get_uri("roster", username.."@"..module.host, token);
additional_data = token_info and token_info.additional_data or nil;
- reusable = token_info.reusable;
+ reusable = token_info and token_info.reusable or false;
}, valid_invite_mt);
end
diff --git a/plugins/mod_s2s.lua b/plugins/mod_s2s.lua
index 88b73eba..04fd5bc3 100644
--- a/plugins/mod_s2s.lua
+++ b/plugins/mod_s2s.lua
@@ -43,6 +43,12 @@ local secure_domains, insecure_domains =
local require_encryption = module:get_option_boolean("s2s_require_encryption", true);
local stanza_size_limit = module:get_option_integer("s2s_stanza_size_limit", 1024*512, 10000);
+local advertised_idle_timeout = 14*60; -- default in all net.server implementations
+local network_settings = module:get_option("network_settings");
+if type(network_settings) == "table" and type(network_settings.read_timeout) == "number" then
+ advertised_idle_timeout = network_settings.read_timeout;
+end
+
local measure_connections_inbound = module:metric(
"gauge", "connections_inbound", "",
"Established incoming s2s connections",
@@ -258,10 +264,15 @@ function module.add_host(module)
module:hook("route/remote", route_to_existing_session, -1);
module:hook("route/remote", route_to_new_session, -10);
module:hook("s2sout-stream-features", function (event)
+ if not (stanza_size_limit or advertised_idle_timeout) then return end
+ local limits = event.features:tag("limits", { xmlns = "urn:xmpp:stream-limits:0" })
if stanza_size_limit then
- event.features:tag("limits", { xmlns = "urn:xmpp:stream-limits:0" })
- :text_tag("max-bytes", string.format("%d", stanza_size_limit)):up();
+ limits:text_tag("max-bytes", string.format("%d", stanza_size_limit));
end
+ if advertised_idle_timeout then
+ limits:text_tag("idle-seconds", string.format("%d", advertised_idle_timeout));
+ end
+ limits:up();
end);
module:hook_tag("urn:xmpp:bidi", "bidi", function(session, stanza)
-- Advertising features on bidi connections where no <stream:features> is sent in the other direction
@@ -551,10 +562,16 @@ function stream_callbacks._streamopened(session, attr)
end
if ( session.type == "s2sin" or session.type == "s2sout" ) or features.tags[1] then
- if stanza_size_limit then
+ if stanza_size_limit or advertised_idle_timeout then
+ features:reset();
+ local limits = features:tag("limits", { xmlns = "urn:xmpp:stream-limits:0" });
+ if stanza_size_limit then
+ limits:text_tag("max-bytes", string.format("%d", stanza_size_limit));
+ end
+ if advertised_idle_timeout then
+ limits:text_tag("idle-seconds", string.format("%d", advertised_idle_timeout));
+ end
features:reset();
- features:tag("limits", { xmlns = "urn:xmpp:stream-limits:0" })
- :text_tag("max-bytes", string.format("%d", stanza_size_limit)):up();
end
log("debug", "Sending stream features: %s", features);
@@ -969,7 +986,7 @@ end
-- Complete the sentence "Your certificate " with what's wrong
local function friendly_cert_error(session) --> string
if session.cert_chain_status == "invalid" then
- if session.cert_chain_errors then
+ if type(session.cert_chain_errors) == "table" then
local cert_errors = set.new(session.cert_chain_errors[1]);
if cert_errors:contains("certificate has expired") then
return "has expired";
@@ -989,6 +1006,7 @@ local function friendly_cert_error(session) --> string
return "does not match any DANE TLSA records";
end
end
+ -- TODO cert_chain_errors can be a string, handle that
return "is not trusted"; -- for some other reason
elseif session.cert_identity_status == "invalid" then
return "is not valid for this name";
diff --git a/plugins/mod_s2s_bidi.lua b/plugins/mod_s2s_bidi.lua
index 22415293..8588ce59 100644
--- a/plugins/mod_s2s_bidi.lua
+++ b/plugins/mod_s2s_bidi.lua
@@ -12,10 +12,15 @@ local xmlns_bidi = "urn:xmpp:bidi";
local require_encryption = module:get_option_boolean("s2s_require_encryption", true);
+local offers_sent = module:metric("counter", "offers_sent", "", "Bidirectional connection offers sent", {});
+local offers_recv = module:metric("counter", "offers_recv", "", "Bidirectional connection offers received", {});
+local offers_taken = module:metric("counter", "offers_taken", "", "Bidirectional connection offers taken", {});
+
module:hook("s2s-stream-features", function(event)
local origin, features = event.origin, event.features;
if origin.type == "s2sin_unauthed" and (not require_encryption or origin.secure) then
features:tag("bidi", { xmlns = xmlns_bidi_feature }):up();
+ offers_sent:with_labels():add(1);
end
end);
@@ -28,6 +33,7 @@ module:hook_tag("http://etherx.jabber.org/streams", "features", function (sessio
local request_bidi = st.stanza("bidi", { xmlns = xmlns_bidi });
module:fire_event("s2sout-stream-features", { origin = session, features = request_bidi });
session.sends2s(request_bidi);
+ offers_taken:with_labels():add(1);
end
end
end, 200);
@@ -36,6 +42,7 @@ module:hook_tag("urn:xmpp:bidi", "bidi", function(session)
if session.type == "s2sin_unauthed" and (not require_encryption or session.secure) then
session.log("debug", "Requested bidirectional stream");
session.outgoing = true;
+ offers_recv:with_labels():add(1);
return true;
end
end);
diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua
index b3f30eed..1dc99f07 100644
--- a/plugins/muc/mod_muc.lua
+++ b/plugins/muc/mod_muc.lua
@@ -86,6 +86,12 @@ room_mt.get_registered_nick = register.get_registered_nick;
room_mt.get_registered_jid = register.get_registered_jid;
room_mt.handle_register_iq = register.handle_register_iq;
+local restrict_pm = module:require "muc/restrict_pm";
+room_mt.get_allow_pm = restrict_pm.get_allow_pm;
+room_mt.set_allow_pm = restrict_pm.set_allow_pm;
+room_mt.get_allow_modpm = restrict_pm.get_allow_modpm;
+room_mt.set_allow_modpm = restrict_pm.set_allow_modpm;
+
local presence_broadcast = module:require "muc/presence_broadcast";
room_mt.get_presence_broadcast = presence_broadcast.get;
room_mt.set_presence_broadcast = presence_broadcast.set;
@@ -119,7 +125,7 @@ module:default_permissions("prosody:guest", {
":list-rooms";
});
-if module:get_option_boolean("component_admins_as_room_owners", true) then
+if module:get_option_boolean("component_admins_as_room_owners", false) then
-- Monkey patch to make server admins room owners
local _get_affiliation = room_mt.get_affiliation;
function room_mt:get_affiliation(jid)
@@ -293,6 +299,8 @@ local function set_room_defaults(room, lang)
room:set_language(lang or module:get_option_string("muc_room_default_language"));
room:set_presence_broadcast(module:get_option_enum("muc_room_default_presence_broadcast", room:get_presence_broadcast(), "visitor", "participant",
"moderator"));
+ room:set_allow_pm(module:get_option_enum("muc_room_default_allow_pm", room:get_allow_pm(), "visitor", "participant", "moderator"));
+ room:set_allow_modpm(module:get_option_boolean("muc_room_default_always_allow_moderator_pms", room:get_allow_modpm()));
end
function create_room(room_jid, config)
diff --git a/plugins/muc/restrict_pm.lib.lua b/plugins/muc/restrict_pm.lib.lua
new file mode 100644
index 00000000..e0b25cc8
--- /dev/null
+++ b/plugins/muc/restrict_pm.lib.lua
@@ -0,0 +1,119 @@
+-- Based on code from mod_muc_restrict_pm in prosody-modules@d82c0383106a
+-- by Nicholas George <wirlaburla@worlio.com>
+
+local st = require "util.stanza";
+local muc_util = module:require "muc/util";
+local valid_roles = muc_util.valid_roles;
+
+-- COMPAT w/ prosody-modules allow_pm
+local compat_map = {
+ everyone = "visitor";
+ participants = "participant";
+ moderators = "moderator";
+ members = "affiliated";
+};
+
+local function get_allow_pm(room)
+ local val = room._data.allow_pm;
+ return compat_map[val] or val or "visitor";
+end
+
+local function set_allow_pm(room, val)
+ if get_allow_pm(room) == val then return false; end
+ room._data.allow_pm = val;
+ return true;
+end
+
+local function get_allow_modpm(room)
+ return room._data.allow_modpm or false;
+end
+
+local function set_allow_modpm(room, val)
+ if get_allow_modpm(room) == val then return false; end
+ room._data.allow_modpm = val;
+ return true;
+end
+
+module:hook("muc-config-form", function(event)
+ local pmval = get_allow_pm(event.room);
+ table.insert(event.form, {
+ name = 'muc#roomconfig_allowpm';
+ type = 'list-single';
+ label = 'Allow private messages from';
+ options = {
+ { value = 'visitor', label = 'Everyone', default = pmval == 'visitor' };
+ { value = 'participant', label = 'Participants', default = pmval == 'participant' };
+ { value = 'moderator', label = 'Moderators', default = pmval == 'moderator' };
+ { value = 'affiliated', label = "Members", default = pmval == "affiliated" };
+ { value = 'none', label = 'No one', default = pmval == 'none' };
+ }
+ });
+ table.insert(event.form, {
+ name = '{xmpp:prosody.im}muc#allow_modpm';
+ type = 'boolean';
+ label = 'Always allow private messages to moderators';
+ value = get_allow_modpm(event.room)
+ });
+end);
+
+module:hook("muc-config-submitted/muc#roomconfig_allowpm", function(event)
+ if set_allow_pm(event.room, event.value) then
+ event.status_codes["104"] = true;
+ end
+end);
+
+module:hook("muc-config-submitted/{xmpp:prosody.im}muc#allow_modpm", function(event)
+ if set_allow_modpm(event.room, event.value) then
+ event.status_codes["104"] = true;
+ end
+end);
+
+local who_restricted = {
+ none = "in this group";
+ participant = "from guests";
+ moderator = "from non-moderators";
+ affiliated = "from non-members";
+};
+
+module:hook("muc-private-message", function(event)
+ local stanza, room = event.stanza, event.room;
+ local from_occupant = room:get_occupant_by_nick(stanza.attr.from);
+ local to_occupant = room:get_occupant_by_nick(stanza.attr.to);
+
+ -- To self is always okay
+ if to_occupant.bare_jid == from_occupant.bare_jid then return; end
+
+ if get_allow_modpm(room) then
+ if to_occupant and to_occupant.role == 'moderator'
+ or from_occupant and from_occupant.role == "moderator" then
+ return; -- Allow to/from moderators
+ end
+ end
+
+ local pmval = get_allow_pm(room);
+
+ if pmval ~= "none" then
+ if pmval == "affiliated" and room:get_affiliation(from_occupant.bare_jid) then
+ return; -- Allow from affiliated users
+ elseif valid_roles[from_occupant.role] >= valid_roles[pmval] then
+ module:log("debug", "Allowing PM: %s(%d) >= %s(%d)", from_occupant.role, valid_roles[from_occupant.role], pmval, valid_roles[pmval]);
+ return; -- Allow from a permitted role
+ end
+ end
+
+ local msg = ("Private messages are restricted %s"):format(who_restricted[pmval]);
+ module:log("debug", "Blocking PM from %s %s: %s", from_occupant.role, stanza.attr.from, msg);
+
+ room:route_to_occupant(
+ from_occupant,
+ st.error_reply(stanza, "cancel", "policy-violation", msg, room.jid)
+ );
+ return false;
+end, 1);
+
+return {
+ get_allow_pm = get_allow_pm;
+ set_allow_pm = set_allow_pm;
+ get_allow_modpm = get_allow_modpm;
+ set_allow_modpm = set_allow_modpm;
+};
diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua
index 48183fc7..58889fd7 100644
--- a/spec/scansion/prosody.cfg.lua
+++ b/spec/scansion/prosody.cfg.lua
@@ -103,6 +103,8 @@ storage = "memory"
mam_smart_enable = true
+bounce_blocked_messages = true
+
-- For the "sql" backend, you can uncomment *one* of the below to configure:
--sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename.
--sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
diff --git a/spec/scansion/pubsub_config.scs b/spec/scansion/pubsub_config.scs
index 57f275cd..a2b2ab68 100644
--- a/spec/scansion/pubsub_config.scs
+++ b/spec/scansion/pubsub_config.scs
@@ -74,6 +74,7 @@ Romeo receives:
</option>
<value>presence</value>
</field>
+ <field type="list-multi" var="pubsub#roster_groups_allowed" label="Roster groups allowed to subscribe"/>
<field var="pubsub#publish_model" label="Specify the publisher model" type="list-single">
<option label="publishers">
<value>publishers</value>
@@ -176,6 +177,7 @@ Romeo sends:
</option>
<value>presence</value>
</field>
+ <field type="list-multi" var="pubsub#roster_groups_allowed" label="Roster groups allowed to subscribe"/>
<field var="pubsub#publish_model" type="list-single" label="Specify the publisher model">
<option label="publishers">
<value>publishers</value>
diff --git a/spec/scansion/pubsub_max_items.scs b/spec/scansion/pubsub_max_items.scs
index 31480b2e..b0ead5bf 100644
--- a/spec/scansion/pubsub_max_items.scs
+++ b/spec/scansion/pubsub_max_items.scs
@@ -69,6 +69,7 @@ Alice receives:
</option>
<value>open</value>
</field>
+ <field type="list-multi" var="pubsub#roster_groups_allowed" label="Roster groups allowed to subscribe"/>
<field var="pubsub#publish_model" label="Specify the publisher model" type="list-single">
<option label="publishers">
<value>publishers</value>
diff --git a/spec/scansion/pubsub_multi_items.scs b/spec/scansion/pubsub_multi_items.scs
index 7f365b94..8c6872e4 100644
--- a/spec/scansion/pubsub_multi_items.scs
+++ b/spec/scansion/pubsub_multi_items.scs
@@ -69,6 +69,7 @@ Alice receives:
</option>
<value>open</value>
</field>
+ <field type="list-multi" var="pubsub#roster_groups_allowed" label="Roster groups allowed to subscribe"/>
<field var="pubsub#publish_model" label="Specify the publisher model" type="list-single">
<option label="publishers">
<value>publishers</value>
diff --git a/spec/scansion/pubsub_preconditions.scs b/spec/scansion/pubsub_preconditions.scs
index 286e84d6..fe056fc6 100644
--- a/spec/scansion/pubsub_preconditions.scs
+++ b/spec/scansion/pubsub_preconditions.scs
@@ -73,6 +73,7 @@ Romeo receives:
</option>
<value>presence</value>
</field>
+ <field type="list-multi" var="pubsub#roster_groups_allowed" label="Roster groups allowed to subscribe"/>
<field var="pubsub#publish_model" label="Specify the publisher model" type="list-single">
<option label="publishers">
<value>publishers</value>
@@ -175,6 +176,7 @@ Romeo sends:
</option>
<value>presence</value>
</field>
+ <field type="list-multi" var="pubsub#roster_groups_allowed" label="Roster groups allowed to subscribe"/>
<field var="pubsub#publish_model" type="list-single" label="Specify the publisher model">
<option label="publishers">
<value>publishers</value>
diff --git a/spec/util_xtemplate_spec.lua b/spec/util_xtemplate_spec.lua
index 4561378e..421be43f 100644
--- a/spec/util_xtemplate_spec.lua
+++ b/spec/util_xtemplate_spec.lua
@@ -38,6 +38,10 @@ describe("util.xtemplate", function ()
x:reset();
assert.same("12345", xtemplate.render("{foo/bar|each(i){{#}}}", x));
end)
+ it("handles missing inputs", function ()
+ local x = st.stanza("root");
+ assert.same("", xtemplate.render("{foo/bar|each(i){{#}}}", x));
+ end)
end)
end)
end)
diff --git a/teal-src/prosody/util/xtemplate.tl b/teal-src/prosody/util/xtemplate.tl
index 9b6b678b..84003051 100644
--- a/teal-src/prosody/util/xtemplate.tl
+++ b/teal-src/prosody/util/xtemplate.tl
@@ -54,7 +54,10 @@ local function render(template : string, root : st.stanza_t, escape : escape_t,
if tmpl then tmpl = s_sub(tmpl, 2, -2); end
if args then args = s_sub(args, 2, -2); end
- if func == "each" and tmpl and st.is_stanza(value) then
+ if func == "each" and tmpl then
+ if not st.is_stanza(value) then
+ return pre_blank..post_blank;
+ end
if not args then value, args = root, path; end
local ns, name = s_match(args, "^(%b{})(.*)$");
if ns then ns = s_sub(ns, 2, -2); else name, ns = args, nil; end
diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua
index d256e558..9cb4b4dd 100644
--- a/util/prosodyctl.lua
+++ b/util/prosodyctl.lua
@@ -15,9 +15,13 @@ local usermanager = require "prosody.core.usermanager";
local interpolation = require "prosody.util.interpolation";
local signal = require "prosody.util.signal";
local set = require "prosody.util.set";
+local path = require"prosody.util.paths";
local lfs = require "lfs";
local type = type;
+local have_socket_unix, socket_unix = pcall(require, "socket.unix");
+have_socket_unix = have_socket_unix and type(socket_unix) == "table"; -- was a function in older LuaSocket
+
local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep;
local io, os = io, os;
@@ -177,11 +181,31 @@ local function start(source_dir, lua)
if ret then
return false, "already-running";
end
+ local notify_socket;
+ if have_socket_unix then
+ local notify_path = path.join(prosody.paths.data, "notify.sock");
+ os.remove(notify_path);
+ lua = string.format("NOTIFY_SOCKET=%q %s", notify_path, lua);
+ notify_socket = socket_unix.dgram();
+ local ok = notify_socket:setsockname(notify_path);
+ if not ok then return false, "notify-failed"; end
+ end
if not source_dir then
os.execute(lua .. "./prosody -D");
else
os.execute(lua .. source_dir.."/../../bin/prosody -D");
end
+
+ if notify_socket then
+ for i = 1, 5 do
+ notify_socket:settimeout(i);
+ if notify_socket:receivefrom() == "READY=1" then
+ return true;
+ end
+ end
+ return false, "not-ready";
+ end
+
return true;
end
diff --git a/util/prosodyctl/cert.lua b/util/prosodyctl/cert.lua
index aea61c20..70c09443 100644
--- a/util/prosodyctl/cert.lua
+++ b/util/prosodyctl/cert.lua
@@ -163,7 +163,7 @@ local function copy(from, to, umask, owner, group)
local attrs = lfs.attributes(to);
if attrs then -- Move old file out of the way
local backup = to..".bkp~"..os.date("%FT%T", attrs.change);
- os.rename(to, backup);
+ assert(os.rename(to, backup));
end
-- FIXME friendlier error handling, maybe move above backup back?
local input = assert(io.open(from));
diff --git a/util/sslconfig.lua b/util/sslconfig.lua
index 7b0ed34a..01a8adb5 100644
--- a/util/sslconfig.lua
+++ b/util/sslconfig.lua
@@ -84,8 +84,18 @@ end
finalisers.certificate = finalisers.key;
finalisers.cafile = finalisers.key;
finalisers.capath = finalisers.key;
--- XXX: copied from core/certmanager.lua, but this seems odd, because it would remove a dhparam function from the config
-finalisers.dhparam = finalisers.key;
+
+function finalisers.dhparam(value, config)
+ if type(value) == "string" then
+ if value:sub(1, 10) == "-----BEGIN" then
+ -- literal value
+ return value;
+ else
+ -- assume a filename
+ return resolve_path(config._basedir, value);
+ end
+ end
+end
-- protocol = "x" should enable only that protocol
-- protocol = "x+" should enable x and later versions
diff --git a/util/xtemplate.lua b/util/xtemplate.lua
index 56413012..e23b1a01 100644
--- a/util/xtemplate.lua
+++ b/util/xtemplate.lua
@@ -39,7 +39,8 @@ local function render(template, root, escape, filters)
if tmpl then tmpl = s_sub(tmpl, 2, -2); end
if args then args = s_sub(args, 2, -2); end
- if func == "each" and tmpl and st.is_stanza(value) then
+ if func == "each" and tmpl then
+ if not st.is_stanza(value) then return pre_blank .. post_blank end
if not args then value, args = root, path; end
local ns, name = s_match(args, "^(%b{})(.*)$");
if ns then