diff options
-rw-r--r-- | core/certmanager.lua | 14 | ||||
-rw-r--r-- | core/moduleapi.lua | 1 | ||||
-rw-r--r-- | net/http.lua | 3 | ||||
-rw-r--r-- | net/tls_luasec.lua | 5 | ||||
-rw-r--r-- | plugins/mod_admin_shell.lua | 3 | ||||
-rw-r--r-- | plugins/mod_auth_internal_hashed.lua | 3 | ||||
-rw-r--r-- | plugins/mod_auth_internal_plain.lua | 3 | ||||
-rw-r--r-- | plugins/mod_c2s.lua | 18 | ||||
-rw-r--r-- | plugins/mod_csi.lua | 21 | ||||
-rw-r--r-- | plugins/mod_invites.lua | 2 | ||||
-rw-r--r-- | plugins/mod_s2s.lua | 30 | ||||
-rw-r--r-- | plugins/mod_s2s_bidi.lua | 7 | ||||
-rw-r--r-- | util/prosodyctl/cert.lua | 2 | ||||
-rw-r--r-- | util/sslconfig.lua | 14 |
14 files changed, 110 insertions, 16 deletions
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/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_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..73c081b1 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 + inactive = inactive + 1; + elseif session.state == "flushing" then + inactive = inactive + 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/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 |