diff options
29 files changed, 348 insertions, 22 deletions
@@ -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 |