diff options
Diffstat (limited to 'util')
-rw-r--r-- | util/argparse.lua | 81 | ||||
-rw-r--r-- | util/dnsregistry.lua | 3 | ||||
-rw-r--r-- | util/prosodyctl.lua | 7 | ||||
-rw-r--r-- | util/prosodyctl/check.lua | 345 | ||||
-rw-r--r-- | util/prosodyctl/shell.lua | 26 | ||||
-rw-r--r-- | util/sasl.lua | 11 | ||||
-rw-r--r-- | util/sql.lua | 2 | ||||
-rw-r--r-- | util/startup.lua | 31 | ||||
-rw-r--r-- | util/x509.lua | 51 |
9 files changed, 455 insertions, 102 deletions
diff --git a/util/argparse.lua b/util/argparse.lua index 7a55cb0b..75b1c2f9 100644 --- a/util/argparse.lua +++ b/util/argparse.lua @@ -2,6 +2,9 @@ local function parse(arg, config) local short_params = config and config.short_params or {}; local value_params = config and config.value_params or {}; local array_params = config and config.array_params or {}; + local kv_params = config and config.kv_params or {}; + local strict = config and config.strict; + local stop_on_positional = not config or config.stop_on_positional ~= false; local parsed_opts = {}; @@ -15,51 +18,69 @@ local function parse(arg, config) end local prefix = raw_param:match("^%-%-?"); - if not prefix then + if not prefix and stop_on_positional then break; elseif prefix == "--" and raw_param == "--" then table.remove(arg, 1); break; end - local param = table.remove(arg, 1):sub(#prefix+1); - if #param == 1 and short_params then - param = short_params[param]; - end - if not param then - return nil, "param-not-found", raw_param; - end + if prefix then + local param = table.remove(arg, 1):sub(#prefix+1); + if #param == 1 and short_params then + param = short_params[param]; + end - local param_k, param_v; - if value_params[param] or array_params[param] then - param_k, param_v = param, table.remove(arg, 1); - if not param_v then - return nil, "missing-value", raw_param; + if not param then + return nil, "param-not-found", raw_param; end - else - param_k, param_v = param:match("^([^=]+)=(.+)$"); - if not param_k then - if param:match("^no%-") then - param_k, param_v = param:sub(4), false; - else - param_k, param_v = param, true; + + local uparam = param:match("^[^=]*"):gsub("%-", "_"); + + local param_k, param_v; + if value_params[uparam] or array_params[uparam] then + param_k = uparam; + param_v = param:match("^=(.*)$", #uparam+1); + if not param_v then + param_v = table.remove(arg, 1); + if not param_v then + return nil, "missing-value", raw_param; + end + end + else + param_k, param_v = param:match("^([^=]+)=(.+)$"); + if not param_k then + if param:match("^no%-") then + param_k, param_v = param:sub(4), false; + else + param_k, param_v = param, true; + end + end + param_k = param_k:gsub("%-", "_"); + if strict and not kv_params[param_k] then + return nil, "param-not-found", raw_param; end end - param_k = param_k:gsub("%-", "_"); - end - if array_params[param] then - if parsed_opts[param_k] then - table.insert(parsed_opts[param_k], param_v); + if array_params[uparam] then + if parsed_opts[param_k] then + table.insert(parsed_opts[param_k], param_v); + else + parsed_opts[param_k] = { param_v }; + end else - parsed_opts[param_k] = { param_v }; + parsed_opts[param_k] = param_v; end - else - parsed_opts[param_k] = param_v; + elseif not stop_on_positional then + table.insert(parsed_opts, table.remove(arg, 1)); end end - for i = 1, #arg do - parsed_opts[i] = arg[i]; + + if stop_on_positional then + for i = 1, #arg do + parsed_opts[i] = arg[i]; + end end + return parsed_opts; end diff --git a/util/dnsregistry.lua b/util/dnsregistry.lua index b65debe0..56c76cac 100644 --- a/util/dnsregistry.lua +++ b/util/dnsregistry.lua @@ -1,5 +1,5 @@ -- Source: https://www.iana.org/assignments/dns-parameters/dns-parameters.xml --- Generated on 2024-10-26 +-- Generated on 2025-02-09 return { classes = { ["IN"] = 1; [1] = "IN"; @@ -72,6 +72,7 @@ return { ["ZONEMD"] = 63; [63] = "ZONEMD"; ["SVCB"] = 64; [64] = "SVCB"; ["HTTPS"] = 65; [65] = "HTTPS"; + ["DSYNC"] = 66; [66] = "DSYNC"; ["SPF"] = 99; [99] = "SPF"; ["NID"] = 104; [104] = "NID"; ["L32"] = 105; [105] = "L32"; diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua index 9cb4b4dd..c6856526 100644 --- a/util/prosodyctl.lua +++ b/util/prosodyctl.lua @@ -40,6 +40,7 @@ local error_messages = setmetatable({ ["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?"; ["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see https://prosody.im/doc/prosodyctl#pidfile for help"; ["invalid-pidfile"] = "The 'pidfile' option in the configuration file is not a string, see https://prosody.im/doc/prosodyctl#pidfile for help"; + ["pidfile-not-locked"] = "Stale pidfile found. Prosody is probably not running."; ["no-posix"] = "The mod_posix module is not enabled in the Prosody config file, see https://prosody.im/doc/prosodyctl for more info"; ["no-such-method"] = "This module has no commands"; ["not-running"] = "Prosody is not running"; @@ -143,8 +144,14 @@ local function getpid() return false, "pidfile-read-failed", err; end + -- Check for a lock on the file local locked, err = lfs.lock(file, "w"); -- luacheck: ignore 211/err if locked then + -- Prosody keeps the pidfile locked while it is running. + -- We successfully locked the file, which means Prosody is not + -- running and the pidfile is stale (somehow it was not + -- cleaned up). We'll abort here, to avoid sending signals to + -- a non-Prosody PID. file:close(); return false, "pidfile-not-locked"; end diff --git a/util/prosodyctl/check.lua b/util/prosodyctl/check.lua index ac8cc9c1..0a0ab878 100644 --- a/util/prosodyctl/check.lua +++ b/util/prosodyctl/check.lua @@ -11,6 +11,7 @@ local jid_split = require "prosody.util.jid".prepped_split; local modulemanager = require "prosody.core.modulemanager"; local async = require "prosody.util.async"; local httputil = require "prosody.util.http"; +local human_units = require "prosody.util.human.units"; local function api(host) return setmetatable({ name = "prosodyctl.check"; host = host; log = prosody.log }, { __index = moduleapi }) @@ -325,7 +326,12 @@ local function check(arg) local ok = true; local function contains_match(hayset, needle) for member in hayset do if member:find(needle) then return true end end end local function disabled_hosts(host, conf) return host ~= "*" and conf.enabled ~= false; end - local function enabled_hosts() return it.filter(disabled_hosts, pairs(configmanager.getconfig())); end + local function is_user_host(host, conf) return host ~= "*" and conf.component_module == nil; end + local function is_component_host(host, conf) return host ~= "*" and conf.component_module ~= nil; end + local function enabled_hosts() return it.filter(disabled_hosts, it.sorted_pairs(configmanager.getconfig())); end + local function enabled_user_hosts() return it.filter(is_user_host, it.sorted_pairs(configmanager.getconfig())); end + local function enabled_components() return it.filter(is_component_host, it.sorted_pairs(configmanager.getconfig())); end + local checks = {}; function checks.disabled() local disabled_hosts_set = set.new(); @@ -632,6 +638,12 @@ local function check(arg) print(" Both mod_pep_simple and mod_pep are enabled but they conflict"); print(" with each other. Remove one."); end + if all_modules:contains("posix") then + print(""); + print(" mod_posix is loaded in your configuration file, but it has"); + print(" been deprecated. You can safely remove it."); + end + for host, host_config in pairs(config) do --luacheck: ignore 213/host if type(rawget(host_config, "storage")) == "string" and rawget(host_config, "default_storage") then print(""); @@ -790,12 +802,28 @@ local function check(arg) 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."); + 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 + -- Check features + do + local missing_features = {}; + for host in enabled_user_hosts() do + local all_features = checks.features(host, true); + if not all_features then + table.insert(missing_features, host); + end + end + if #missing_features > 0 then + print(""); + print(" Some of your hosts may be missing features due to a lack of configuration."); + print(" For more details, use the 'prosodyctl check features' command."); + end + end + print("Done.\n"); end function checks.dns() @@ -901,7 +929,11 @@ local function check(arg) local unknown_addresses = set.new(); - for jid in enabled_hosts() do + local function is_valid_domain(domain) + return idna.to_ascii(domain) ~= nil; + end + + for jid in it.filter(is_valid_domain, enabled_hosts()) do local all_targets_ok, some_targets_ok = true, false; local node, host = jid_split(jid); @@ -1444,6 +1476,311 @@ local function check(arg) end end end + + function checks.features(check_host, quiet) + if not quiet then + print("Feature report"); + end + + local common_subdomains = { + http_file_share = "share"; + muc = "groups"; + }; + + local recommended_component_modules = { + muc = { "muc_mam" }; + }; + + local function print_feature_status(feature, host) + if quiet then return; end + print("", feature.ok and "OK" or "(!)", feature.name); + if feature.desc then + print("", "", feature.desc); + print(""); + end + if not feature.ok then + if feature.lacking_modules then + table.sort(feature.lacking_modules); + print("", "", "Suggested modules: "); + for _, module in ipairs(feature.lacking_modules) do + print("", "", (" - %s: https://prosody.im/doc/modules/mod_%s"):format(module, module)); + end + end + if feature.lacking_components then + table.sort(feature.lacking_components); + for _, component_module in ipairs(feature.lacking_components) do + local subdomain = common_subdomains[component_module]; + local recommended_mods = recommended_component_modules[component_module]; + if subdomain then + print("", "", "Suggested component:"); + print(""); + print("", "", "", ("-- Documentation: https://prosody.im/doc/modules/mod_%s"):format(component_module)); + print("", "", "", ("Component %q %q"):format(subdomain.."."..host, component_module)); + if recommended_mods then + print("", "", "", " modules_enabled = {"); + table.sort(recommended_mods); + for _, mod in ipairs(recommended_mods) do + print("", "", "", (" %q;"):format(mod)); + end + print("", "", "", " }"); + end + else + print("", "", ("Suggested component: %s"):format(component_module)); + end + end + print(""); + print("", "", "If you have already configured any of these components, they may not be"); + print("", "", "linked correctly to "..host..". For more info see https://prosody.im/doc/components"); + end + if feature.lacking_component_modules then + table.sort(feature.lacking_component_modules, function (a, b) + return a.host < b.host; + end); + for _, problem in ipairs(feature.lacking_component_modules) do + local hostapi = api(problem.host); + local current_modules_enabled = hostapi:get_option_array("modules_enabled", {}); + print("", "", ("Component %q is missing the following modules: %s"):format(problem.host, table.concat(problem.missing_mods))); + print(""); + print("","", "Add the missing modules to your modules_enabled under the Component, like this:"); + print(""); + print(""); + print("", "", "", ("-- Documentation: https://prosody.im/doc/modules/mod_%s"):format(problem.component_module)); + print("", "", "", ("Component %q %q"):format(problem.host, problem.component_module)); + print("", "", "", (" modules_enabled = {")); + for _, mod in ipairs(current_modules_enabled) do + print("", "", "", (" %q;"):format(mod)); + end + for _, mod in ipairs(problem.missing_mods) do + print("", "", "", (" %q; -- Add this!"):format(mod)); + end + print("", "", "", (" }")); + end + end + end + if feature.meta then + for k, v in it.sorted_pairs(feature.meta) do + print("", "", (" - %s: %s"):format(k, v)); + end + end + print(""); + end + + local all_ok = true; + + local config = configmanager.getconfig(); + + local f, s, v; + if check_host then + f, s, v = it.values({ check_host }); + else + f, s, v = enabled_user_hosts(); + end + + for host in f, s, v do + local modules_enabled = set.new(config["*"].modules_enabled); + modules_enabled:include(set.new(config[host].modules_enabled)); + + -- { [component_module] = { hostname1, hostname2, ... } } + local host_components = setmetatable({}, { __index = function (t, k) return rawset(t, k, {})[k]; end }); + + do + local hostapi = api(host); + + -- Find implicitly linked components + for other_host in enabled_components() do + local parent_host = other_host:match("^[^.]+%.(.+)$"); + if parent_host == host then + local component_module = configmanager.get(other_host, "component_module"); + if component_module then + table.insert(host_components[component_module], other_host); + end + end + end + + -- And components linked explicitly + for _, disco_item in ipairs(hostapi:get_option_array("disco_items", {})) do + local other_host = disco_item[1]; + local component_module = configmanager.get(other_host, "component_module"); + if component_module then + table.insert(host_components[component_module], other_host); + end + end + end + + local current_feature; + + local function check_module(suggested, alternate, ...) + if set.intersection(modules_enabled, set.new({suggested, alternate, ...})):empty() then + current_feature.lacking_modules = current_feature.lacking_modules or {}; + table.insert(current_feature.lacking_modules, suggested); + end + end + + local function check_component(suggested, alternate, ...) + local found; + for _, component_module in ipairs({ suggested, alternate, ... }) do + found = host_components[component_module][1]; + if found then + local enabled_component_modules = api(found):get_option_inherited_set("modules_enabled"); + local recommended_mods = recommended_component_modules[component_module]; + if recommended_mods then + local missing_mods = {}; + for _, mod in ipairs(recommended_mods) do + if not enabled_component_modules:contains(mod) then + table.insert(missing_mods, mod); + end + end + if #missing_mods > 0 then + if not current_feature.lacking_component_modules then + current_feature.lacking_component_modules = {}; + end + table.insert(current_feature.lacking_component_modules, { + host = found; + component_module = component_module; + missing_mods = missing_mods; + }); + end + end + break; + end + end + if not found then + current_feature.lacking_components = current_feature.lacking_components or {}; + table.insert(current_feature.lacking_components, suggested); + end + return found; + end + + local features = { + { + name = "Basic functionality"; + desc = "Support for secure connections, authentication and messaging"; + check = function () + check_module("disco"); + check_module("roster"); + check_module("saslauth"); + check_module("tls"); + end; + }; + { + name = "Multi-device messaging and data synchronization"; + desc = "Multiple clients connected to the same account stay in sync"; + check = function () + check_module("carbons"); + check_module("mam"); + check_module("bookmarks"); + check_module("pep"); + end; + }; + { + name = "Mobile optimizations"; + desc = "Help mobile clients reduce battery and data usage"; + check = function () + check_module("smacks"); + check_module("csi_simple", "csi_battery_saver"); + end; + }; + { + name = "Web connections"; + desc = "Allow connections from browser-based web clients"; + check = function () + check_module("bosh"); + check_module("websocket"); + end; + }; + { + name = "User profiles"; + desc = "Enable users to publish profile information"; + check = function () + check_module("vcard_legacy", "vcard"); + end; + }; + { + name = "Blocking"; + desc = "Block communication with chosen entities"; + check = function () + check_module("blocklist"); + end; + }; + { + name = "Push notifications"; + desc = "Receive notifications on platforms that don't support persistent connections"; + check = function () + check_module("cloud_notify"); + end; + }; + { + name = "Audio/video calls and P2P"; + desc = "Assist clients in setting up connections between each other"; + check = function () + check_module( + "turn_external", + "external_services", + "turncredentials", + "extdisco" + ); + end; + }; + { + name = "File sharing"; + desc = "Sharing of files to groups and offline users"; + check = function (self) + local service = check_component("http_file_share", "http_upload", "http_upload_external"); + if service then + local size_limit; + if api(service):get_option("component_module") == "http_file_share" then + size_limit = api(service):get_option_number("http_file_share_size_limit", 10*1024*1024); + end + if size_limit then + self.meta = { + ["Size limit"] = human_units.format(size_limit, "b", "b"); + }; + end + end + end; + }; + { + name = "Group chats"; + desc = "Create group chats and channels"; + check = function () + check_component("muc"); + end; + }; + }; + + if not quiet then + print(host); + end + + for _, feature in ipairs(features) do + current_feature = feature; + feature:check(); + feature.ok = ( + not feature.lacking_modules and + not feature.lacking_components and + not feature.lacking_component_modules + ); + -- For improved presentation, we group the (ok) and (not ok) features + if feature.ok then + print_feature_status(feature, host); + end + end + + for _, feature in ipairs(features) do + if not feature.ok then + all_ok = false; + print_feature_status(feature, host); + end + end + + if not quiet then + print(""); + end + end + + return all_ok; + end + if what == nil or what == "all" then local ret; ret = checks.disabled(); diff --git a/util/prosodyctl/shell.lua b/util/prosodyctl/shell.lua index d6d885d8..31936989 100644 --- a/util/prosodyctl/shell.lua +++ b/util/prosodyctl/shell.lua @@ -29,8 +29,8 @@ local function read_line(prompt_string) end end -local function send_line(client, line) - client.send(st.stanza("repl-input", { width = tostring(term_width()) }):text(line)); +local function send_line(client, line, interactive) + client.send(st.stanza("repl-input", { width = tostring(term_width()), repl = interactive == false and "0" or "1" }):text(line)); end local function repl(client) @@ -64,6 +64,13 @@ local function printbanner() print("https://prosody.im/doc/console\n"); end +local function check() + local lfs = require "lfs"; + local socket_path = path.resolve_relative_path(prosody.paths.data, config.get("*", "admin_socket") or "prosody.sock"); + local state = lfs.attributes(socket_path, "mode"); + return state == "socket"; +end + local function start(arg) --luacheck: ignore 212/arg local client = adminstream.client(); local opts, err, where = parse_args(arg); @@ -80,21 +87,11 @@ local function start(arg) --luacheck: ignore 212/arg if arg[1] then if arg[2] then - local fmt = { "%s"; ":%s("; ")" }; - for i = 3, #arg do - if arg[i]:sub(1, 1) == ":" then - table.insert(fmt, i, ")%s("); - elseif i > 3 and fmt[i - 1]:match("%%q$") then - table.insert(fmt, i, ", %q"); - else - table.insert(fmt, i, "%q"); - end - end - arg[1] = string.format(table.concat(fmt), table.unpack(arg)); + arg[1] = ("{"..string.rep("%q", #arg, ", ").."}"):format(table.unpack(arg, 1, #arg)); end client.events.add_handler("connected", function() - send_line(client, arg[1]); + send_line(client, arg[1], false); return true; end, 1); @@ -180,4 +177,5 @@ end return { shell = start; + available = check; }; diff --git a/util/sasl.lua b/util/sasl.lua index c3c22a1c..dc11d426 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -67,7 +67,7 @@ local function registerMechanism(name, backends, f, cb_backends) end -- create a new SASL object which can be used to authenticate clients -local function new(realm, profile) +local function new(realm, profile, userdata) local mechanisms = profile.mechanisms; if not mechanisms then mechanisms = {}; @@ -80,7 +80,12 @@ local function new(realm, profile) end profile.mechanisms = mechanisms; end - return setmetatable({ profile = profile, realm = realm, mechs = mechanisms }, method); + return setmetatable({ + profile = profile, + realm = realm, + mechs = mechanisms, + userdata = userdata + }, method); end -- add a channel binding handler @@ -94,7 +99,7 @@ end -- get a fresh clone with the same realm and profile function method:clean_clone() - return new(self.realm, self.profile) + return new(self.realm, self.profile, self.userdata) end -- get a list of possible SASL mechanisms to use diff --git a/util/sql.lua b/util/sql.lua index 2f0ec493..06550455 100644 --- a/util/sql.lua +++ b/util/sql.lua @@ -84,7 +84,7 @@ function engine:connect() dbh:autocommit(false); -- don't commit automatically self.conn = dbh; self.prepared = {}; - if params.password then + if params.driver == "SQLite3" and params.password then local ok, err = self:execute(("PRAGMA key='%s'"):format(dbh:quote(params.password))); if not ok then return ok, err; diff --git a/util/startup.lua b/util/startup.lua index c54fa56d..15f07fdf 100644 --- a/util/startup.lua +++ b/util/startup.lua @@ -89,6 +89,14 @@ function startup.read_config() end end prosody.config_file = filename + local credentials_directory = os.getenv("CREDENTIALS_DIRECTORY"); + if credentials_directory then + config.set_credentials_directory(credentials_directory); + elseif prosody.process_type == "prosody" then + config.set_credential_fallback_mode("error"); + else + config.set_credential_fallback_mode("warn"); + end local ok, level, err = config.load(filename); if not ok then print("\n"); @@ -271,7 +279,6 @@ function startup.init_global_state() config = CFG_CONFIGDIR or "."; plugins = CFG_PLUGINDIR or "plugins"; data = "data"; - credentials = os.getenv("CREDENTIALS_DIRECTORY"); }; prosody.arg = _G.arg; @@ -425,6 +432,19 @@ function startup.init_async() async.set_schedule_function(timer.add_task); end +function startup.instrument() + local statsmanager = require "prosody.core.statsmanager"; + local timed = require"prosody.util.openmetrics".timed; + + local adns = require "prosody.net.adns"; + if adns.instrument then + local m = statsmanager.metric("histogram", "prosody_dns", "seconds", "DNS lookups", { "qclass"; "qtype" }, { + buckets = { 1 / 1024; 1 / 256; 1 / 64; 1 / 16; 1 / 4; 1; 4 }; + }); + adns.instrument(function(qclass, qtype) return timed(m:with_labels(qclass, qtype)); end); + end +end + function startup.init_data_store() require "prosody.core.storagemanager"; end @@ -814,12 +834,12 @@ function startup.hook_posix_signals() end); end -function startup.systemd_notify() +function startup.notification_socket() 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.") + log("error", "LuaSocket without UNIX socket support, can't notify process manager.") return os.exit(1); end log("debug", "Will notify on socket %q", notify_socket_name); @@ -827,7 +847,7 @@ function startup.systemd_notify() 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); + log("error", "Could not connect to notification socket %q: %q", notify_socket_name, err); return os.exit(1); end local time = require "prosody.util.time"; @@ -922,13 +942,14 @@ function startup.prosody() startup.load_secondary_libraries(); startup.init_promise(); startup.init_async(); + startup.instrument(); 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.notification_socket(); startup.prepare_to_start(); startup.notify_started(); end diff --git a/util/x509.lua b/util/x509.lua index 9ecb5b60..6d856be0 100644 --- a/util/x509.lua +++ b/util/x509.lua @@ -11,7 +11,8 @@ -- IDN libraries complicate that. --- [TLS-CERTS] - https://www.rfc-editor.org/rfc/rfc6125.html +-- [TLS-CERTS] - https://www.rfc-editor.org/rfc/rfc6125.html -- Obsolete +-- [TLS-IDENT] - https://www.rfc-editor.org/rfc/rfc9525.html -- [XMPP-CORE] - https://www.rfc-editor.org/rfc/rfc6120.html -- [SRV-ID] - https://www.rfc-editor.org/rfc/rfc4985.html -- [IDNA] - https://www.rfc-editor.org/rfc/rfc5890.html @@ -35,10 +36,8 @@ local oid_subjectaltname = "2.5.29.17"; -- [PKIX] 4.2.1.6 local oid_xmppaddr = "1.3.6.1.5.5.7.8.5"; -- [XMPP-CORE] local oid_dnssrv = "1.3.6.1.5.5.7.8.7"; -- [SRV-ID] --- Compare a hostname (possibly international) with asserted names --- extracted from a certificate. --- This function follows the rules laid out in --- sections 6.4.1 and 6.4.2 of [TLS-CERTS] +-- Compare a hostname (possibly international) with asserted names extracted from a certificate. +-- This function follows the rules laid out in section 6.3 of [TLS-IDENT] -- -- A wildcard ("*") all by itself is allowed only as the left-most label local function compare_dnsname(host, asserted_names) @@ -159,61 +158,25 @@ local function verify_identity(host, service, cert) if ext[oid_subjectaltname] then local sans = ext[oid_subjectaltname]; - -- Per [TLS-CERTS] 6.3, 6.4.4, "a client MUST NOT seek a match for a - -- reference identifier if the presented identifiers include a DNS-ID - -- SRV-ID, URI-ID, or any application-specific identifier types" - local had_supported_altnames = false - if sans[oid_xmppaddr] then - had_supported_altnames = true if service == "_xmpp-client" or service == "_xmpp-server" then if compare_xmppaddr(host, sans[oid_xmppaddr]) then return true end end end if sans[oid_dnssrv] then - had_supported_altnames = true -- Only check srvNames if the caller specified a service if service and compare_srvname(host, service, sans[oid_dnssrv]) then return true end end if sans["dNSName"] then - had_supported_altnames = true if compare_dnsname(host, sans["dNSName"]) then return true end end - - -- We don't need URIs, but [TLS-CERTS] is clear. - if sans["uniformResourceIdentifier"] then - had_supported_altnames = true - end - - if had_supported_altnames then return false end - end - - -- Extract a common name from the certificate, and check it as if it were - -- a dNSName subjectAltName (wildcards may apply for, and receive, - -- cat treats) - -- - -- Per [TLS-CERTS] 1.8, a CN-ID is the Common Name from a cert subject - -- which has one and only one Common Name - local subject = cert:subject() - local cn = nil - for i=1,#subject do - local dn = subject[i] - if dn["oid"] == oid_commonname then - if cn then - log("info", "Certificate has multiple common names") - return false - end - - cn = dn["value"]; - end end - if cn then - -- Per [TLS-CERTS] 6.4.4, follow the comparison rules for dNSName SANs. - return compare_dnsname(host, { cn }) - end + -- Per [TLS-IDENT] ignore the Common Name + -- The server identity can only be expressed in the subjectAltNames extension; + -- it is no longer valid to use the commonName RDN, known as CN-ID in [TLS-CERTS]. -- If all else fails, well, why should we be any different? return false |