aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.luacheckrc5
-rw-r--r--GNUmakefile3
-rw-r--r--core/certmanager.lua7
-rw-r--r--core/configmanager.lua10
-rw-r--r--core/modulemanager.lua70
-rw-r--r--core/portmanager.lua6
-rw-r--r--net/server_epoll.lua2
-rw-r--r--plugins/mod_admin_shell.lua117
-rw-r--r--plugins/mod_authz_internal.lua6
-rw-r--r--plugins/mod_http.lua16
-rw-r--r--plugins/mod_invites_register.lua16
-rw-r--r--plugins/mod_storage_internal.lua18
-rw-r--r--plugins/mod_storage_sql.lua71
-rw-r--r--spec/core_storagemanager_spec.lua38
-rw-r--r--spec/tls/README11
-rwxr-xr-xspec/tls/config1/assert.sh10
-rwxr-xr-xspec/tls/config1/prepare.sh14
-rw-r--r--spec/tls/config1/prosody.cfg.lua6
-rwxr-xr-xspec/tls/config2/assert.sh10
-rwxr-xr-xspec/tls/config2/prepare.sh14
-rw-r--r--spec/tls/config2/prosody.cfg.lua6
-rwxr-xr-xspec/tls/config3/assert.sh25
-rwxr-xr-xspec/tls/config3/prepare.sh28
-rw-r--r--spec/tls/config3/prosody.cfg.lua28
-rw-r--r--spec/tls/lib.sh45
-rwxr-xr-xspec/tls/run.sh37
-rw-r--r--util/pluginloader.lua9
-rw-r--r--util/prosodyctl/check.lua27
28 files changed, 584 insertions, 71 deletions
diff --git a/.luacheckrc b/.luacheckrc
index 09225d01..1a392dab 100644
--- a/.luacheckrc
+++ b/.luacheckrc
@@ -131,6 +131,11 @@ files["spec/"] = {
std = "+busted";
globals = { "randomize" };
}
+files["spec/tls"] = {
+ -- luacheck complains about the config files here,
+ -- but we don't really care about them
+ only = {};
+}
files["prosody.cfg.lua"] = {
ignore = { "131" };
globals = {
diff --git a/GNUmakefile b/GNUmakefile
index ec51c893..e47b258f 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -122,6 +122,9 @@ integration-test-%: all
$(RUNWITH) prosodyctl --config ./spec/scansion/prosody.cfg.lua stop \
exit $$R
+integration-test-tls: all
+ cd ./spec/tls && ./run.sh
+
coverage:
-rm -- luacov.*
$(BUSTED) --lua=$(RUNWITH) -c
diff --git a/core/certmanager.lua b/core/certmanager.lua
index 3acddf73..b13d57b3 100644
--- a/core/certmanager.lua
+++ b/core/certmanager.lua
@@ -116,14 +116,17 @@ local function index_certs(dir, files_by_name, depth_limit)
else
log("debug", "Skipping expired certificate: %s", full);
end
+ else
+ log("debug", "Skipping non-certificate (based on contents): %s", full);
end
f:close();
elseif err then
- log("debug", "Failed to open file for indexing: %s", full);
+ log("debug", "Skipping file due to error: %s", err);
end
+ else
+ log("debug", "Skipping non-certificate (based on filename): %s", full);
end
end
- log("debug", "Certificate index in %s: %q", dir, files_by_name);
-- | hostname | filename | service |
return files_by_name;
end
diff --git a/core/configmanager.lua b/core/configmanager.lua
index 6c6b670b..fc475b6b 100644
--- a/core/configmanager.lua
+++ b/core/configmanager.lua
@@ -227,7 +227,17 @@ do
host = env.__currenthost or "*";
option_name = k;
}, config_option_proxy_mt);
+ elseif val == nil then
+ t_insert(
+ warnings,
+ ("%s: %d: unrecognized value: %s (you may be missing quotes around it)"):format(
+ config_file,
+ get_line_number(config_file),
+ k
+ )
+ );
end
+
return val;
end
diff --git a/core/modulemanager.lua b/core/modulemanager.lua
index 7295ba25..4d144a86 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -54,50 +54,58 @@ local _G = _G;
local _ENV = nil;
-- luacheck: std none
-local loader = pluginloader.init({
- load_filter_cb = function (path, content)
- local metadata = {};
- for line in content:gmatch("([^\r\n]+)\r?\n") do
- local key, value = line:match("^%-%-%% *([%w_]+): *(.+)$");
- if key then
- value = value:gsub("%s+$", "");
- metadata[key] = value;
- end
+local function plugin_load_filter_cb(path, content)
+ local metadata = {};
+ for line in content:gmatch("([^\r\n]+)\r?\n") do
+ local key, value = line:match("^%-%-%% *([%w_]+): *(.+)$");
+ if key then
+ value = value:gsub("%s+$", "");
+ metadata[key] = value;
end
+ end
- if metadata.lua then
- local supported = false;
- for supported_lua_version in metadata.lua:gmatch("[^, ]+") do
- if supported_lua_version == lua_version then
- supported = true;
- break;
- end
+ if metadata.lua then
+ local supported = false;
+ for supported_lua_version in metadata.lua:gmatch("[^, ]+") do
+ if supported_lua_version == lua_version then
+ supported = true;
+ break;
end
- if not supported then
+ end
+ if not supported then
+ if prosody.process_type ~= "prosodyctl" then
log("warn", "Not loading module, we have Lua %s but the module requires one of (%s): %s", lua_version, metadata.lua, path);
- return; -- Don't load this module
end
+ return nil, "incompatible with Lua "..lua_version; -- Don't load this module
end
+ end
- if metadata.conflicts then
- local conflicts_features = set.new(array.collect(metadata.conflicts:gmatch("[^, ]+")));
- local conflicted_features = set.intersection(conflicts_features, core_features);
- if not conflicted_features:empty() then
- log("warn", "Not loading module, due to conflicting features '%s': %s", conflicted_features, path);
- return; -- Don't load this module
+ if metadata.conflicts then
+ local conflicts_features = set.new(array.collect(metadata.conflicts:gmatch("[^, ]+")));
+ local conflicted_features = set.intersection(conflicts_features, core_features);
+ if not conflicted_features:empty() then
+ if prosody.process_type ~= "prosodyctl" then
+ log("warn", "Not loading module, due to conflict with built-in features '%s': %s", conflicted_features, path);
end
+ return nil, "conflict with built-in feature"; -- Don't load this module
end
- if metadata.requires then
- local required_features = set.new(array.collect(metadata.requires:gmatch("[^, ]+")));
- local missing_features = required_features - core_features;
- if not missing_features:empty() then
+ end
+ if metadata.requires then
+ local required_features = set.new(array.collect(metadata.requires:gmatch("[^, ]+")));
+ local missing_features = required_features - core_features;
+ if not missing_features:empty() then
+ if prosody.process_type ~= "prosodyctl" then
log("warn", "Not loading module, due to missing features '%s': %s", missing_features, path);
- return; -- Don't load this module
end
+ return nil, "Prosody version missing required feature"; -- Don't load this module
end
+ end
- return path, content, metadata;
- end;
+ return path, content, metadata;
+end;
+
+local loader = pluginloader.init({
+ load_filter_cb = plugin_load_filter_cb;
});
local load_modules_for_host, load, unload, reload, get_module, get_items;
diff --git a/core/portmanager.lua b/core/portmanager.lua
index 88bd7b61..3b9b8d67 100644
--- a/core/portmanager.lua
+++ b/core/portmanager.lua
@@ -253,12 +253,14 @@ local function add_sni_host(host, service)
-- TODO should this be some generic thing? e.g. in the service definition
alternate_host = config.get(host, "http_host");
end
- local ssl, err, cfg = certmanager.create_context(alternate_host or host, "server", prefix_ssl_config, active_service.tls_cfg);
+ local autocert = certmanager.find_host_cert(alternate_host or host);
+ local ssl, err, cfg = certmanager.create_context(alternate_host or host, "server", prefix_ssl_config, autocert, active_service.tls_cfg);
if not ssl then
log("error", "Error creating TLS context for SNI host %s: %s", host, err);
else
+ log("debug", "Using certificate %s for %s (%s) on %s (%s)", cfg.certificate, service or name, name, alternate_host or host, host)
local ok, err = active_service.server:sslctx():set_sni_host(
- host,
+ alternate_host or host,
cfg.certificate,
cfg.key
);
diff --git a/net/server_epoll.lua b/net/server_epoll.lua
index 44ab4f69..ca5a950c 100644
--- a/net/server_epoll.lua
+++ b/net/server_epoll.lua
@@ -772,7 +772,7 @@ function interface:starttls(tls_ctx)
self.onreadable = interface.inittls;
self:set(true, true);
self:setreadtimeout(false);
- self:setwritetimeout(cfg.ssl_handshake_timeout);
+ self:setwritetimeout(self._connected and cfg.ssl_handshake_timeout or cfg.connect_timeout);
self:debug("Prepared to start TLS");
end
end
diff --git a/plugins/mod_admin_shell.lua b/plugins/mod_admin_shell.lua
index c2b921b4..de345484 100644
--- a/plugins/mod_admin_shell.lua
+++ b/plugins/mod_admin_shell.lua
@@ -139,6 +139,8 @@ Built-in roles are:
prosody:admin - Host administrator
prosody:operator - Server administrator
+To view roles and policies, see the commands in 'help role'.
+
Roles can be assigned using the user management commands (see 'help user').
]];
@@ -2401,6 +2403,121 @@ function def_env.debug:async(runner_id)
return true, ("%d runners pending"):format(c);
end
+describe_command [[debug:cert_index([path]) - Show Prosody's view of a directory of certs]]
+function def_env.debug:cert_index(path)
+ local print = self.session.print;
+ local cm = require "core.certmanager";
+
+ path = path or module:get_option("certificates", "certs");
+
+ local sink = logger.add_simple_sink(function (source, level, message)
+ if source == "certmanager" then
+ if level == "debug" or level == "info" then
+ level = "II";
+ elseif level == "warn" or level == "error" then
+ level = "EE";
+ end
+ self.session.print(level..": "..message);
+ end
+ end);
+
+ print("II: Scanning "..path.."...");
+
+ local index = {};
+ cm.index_certs(path, index)
+
+ if not logger.remove_sink(sink) then
+ module:log("warn", "Unable to remove log sink");
+ end
+
+ local c, max_domain = 0, 8;
+ for domain in pairs(index) do
+ if #domain > max_domain then
+ max_domain = #domain;
+ end
+ end
+
+ print("");
+
+ local row = format_table({
+ { title = "Domain", width = max_domain };
+ { title = "Certificate", width = "100%" };
+ { title = "Service", width = 5 };
+ }, self.session.width);
+ print(row());
+ print(("-"):rep(self.session.width or 80));
+ for domain, certs in it.sorted_pairs(index) do
+ for cert_file, services in it.sorted_pairs(certs) do
+ for service in it.sorted_pairs(services) do
+ c = c + 1;
+ print(row({ domain, cert_file, service }));
+ end
+ end
+ end
+
+ print("");
+
+ return true, ("Showing %d certificates in %s"):format(c, path);
+end
+
+def_env.role = new_section("Role and access management");
+
+describe_command [[role:list(host) - List known roles]]
+function def_env.role:list(host)
+ if not host then
+ return nil, "Specify which host to list roles for";
+ end
+ local role_list = {};
+ for _, role in it.sorted_pairs(um.get_all_roles(host)) do
+ table.insert(role_list, role);
+ end
+ table.sort(role_list, function (a, b)
+ if a.priority ~= b.priority then
+ return (a.priority or 0) > (b.priority or 0);
+ end
+ return a.name < b.name;
+ end);
+ for _, role in ipairs(role_list) do
+ self.session.print(role.name);
+ end
+ return true, ("Showing %d roles on %s"):format(#role_list, host);
+end
+
+describe_command [[role:show(host, role_name) - Show information about a role]]
+function def_env.role:show(host, role_name)
+ if not host or not role_name then
+ return nil, "Specify the host and role to show";
+ end
+
+ local print = self.session.print;
+ local role = um.get_role_by_name(role_name, host);
+
+ if not role then
+ return nil, ("Unable to find role %s on host %s"):format(role_name, host);
+ end
+
+ local inherits = {};
+ for _, inherited_role in ipairs(role.inherits or {}) do
+ table.insert(inherits, inherited_role.name);
+ end
+
+ local permissions = {};
+ for permission, is_allowed in role:policies() do
+ permissions[permission] = is_allowed and "allowed" or "denied";
+ end
+
+ print("Name: ", role.name);
+ print("Inherits:", table.concat(inherits, ", "));
+ print("Policies:");
+ local c = 0;
+ for permission, policy in it.sorted_pairs(permissions) do
+ c = c + 1;
+ print(" ["..(policy == "allowed" and "+" or " ").."] " .. permission);
+ end
+ print("");
+ return true, ("Showing role %s with %d policies"):format(role.name, c);
+end
+
def_env.stats = new_section("Commands to show internal statistics");
local short_units = {
diff --git a/plugins/mod_authz_internal.lua b/plugins/mod_authz_internal.lua
index f683d90c..1282f617 100644
--- a/plugins/mod_authz_internal.lua
+++ b/plugins/mod_authz_internal.lua
@@ -298,7 +298,11 @@ function add_default_permission(role_name, action, policy)
end
function get_role_by_name(role_name)
- return assert(role_registry[role_name], role_name);
+ local role = role_registry[role_name];
+ if not role then
+ return error("Unknown role: "..role_name);
+ end
+ return role, role_name;
end
function get_all_roles()
diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua
index c13a2363..b5d0084c 100644
--- a/plugins/mod_http.lua
+++ b/plugins/mod_http.lua
@@ -331,7 +331,7 @@ local trusted_proxies = module:get_option_set("trusted_proxies", { "127.0.0.1",
--- deal with [ipv6]:port / ip:port format
local function normal_ip(ip)
- return ip:match("^%[([%x:]*)%]") or ip:match("^([%d.]+)") or ip;
+ return ip:match("^%[([%x:]*)%]") or ip:match("^%d+%.%d+%.%d+%.%d+") or ip;
end
local function is_trusted_proxy(ip)
@@ -339,7 +339,8 @@ local function is_trusted_proxy(ip)
if trusted_proxies[ip] then
return true;
end
- local parsed_ip = new_ip(ip)
+ local parsed_ip, err = new_ip(ip);
+ if not parsed_ip then return nil, err; end
for trusted_proxy in trusted_proxies do
if match_ip(parsed_ip, parse_cidr(trusted_proxy)) then
return true;
@@ -357,10 +358,14 @@ local function get_forwarded_connection_info(request) --> ip:string, secure:bool
request.forwarded = forwarded;
for i = #forwarded, 1, -1 do
local proxy = forwarded[i]
- if is_trusted_proxy(ip) then
+ local trusted, err = is_trusted_proxy(ip);
+ if trusted then
ip = normal_ip(proxy["for"]);
secure = secure and proxy.proto == "https";
else
+ if err then
+ request.log("warn", "Could not parse forwarded connection details: %s");
+ end
break
end
end
@@ -387,7 +392,10 @@ function get_forwarded_connection_info(request) --> ip:string, secure:boolean
-- Case d) If all IPs are in trusted proxies, something went obviously wrong and the logic never overwrites `ip`, leaving it at the original request IP.
forwarded_for = forwarded_for..", "..ip;
for forwarded_ip in forwarded_for:gmatch("[^%s,]+") do
- if not is_trusted_proxy(forwarded_ip) then
+ local trusted, err = is_trusted_proxy(forwarded_ip);
+ if err then
+ request.log("warn", "Could not parse forwarded connection details: %s");
+ elseif not trusted then
ip = forwarded_ip;
end
end
diff --git a/plugins/mod_invites_register.lua b/plugins/mod_invites_register.lua
index d9274ce4..76b644c7 100644
--- a/plugins/mod_invites_register.lua
+++ b/plugins/mod_invites_register.lua
@@ -101,8 +101,20 @@ module:hook("user-registering", function (event)
-- for this module to do...
return;
end
- if validated_invite and validated_invite.additional_data and validated_invite.additional_data.allow_reset then
- event.allow_reset = validated_invite.additional_data.allow_reset;
+ if validated_invite then
+ local username = validated_invite.username;
+ if username and username ~= event.username then
+ event.allowed = false;
+ event.reason = "The chosen username is not valid with this invitation";
+ end
+ local reset_username = validated_invite.additional_data and validated_invite.additional_data.allow_reset;
+ if reset_username then
+ if reset_username ~= event.username then
+ event.allowed = false;
+ event.reason = "Incorrect username for password reset";
+ end
+ event.allow_reset = reset_username;
+ end
end
end);
diff --git a/plugins/mod_storage_internal.lua b/plugins/mod_storage_internal.lua
index 1332ae75..d5ef7112 100644
--- a/plugins/mod_storage_internal.lua
+++ b/plugins/mod_storage_internal.lua
@@ -205,12 +205,11 @@ function archive:find(username, query)
return query.start - when;
end);
i = wi - 1;
- else
- iter = it.filter(function(item)
- local when = item.when or datetime.parse(item.attr.stamp);
- return when >= query.start;
- end, iter);
end
+ iter = it.filter(function(item)
+ local when = item.when or datetime.parse(item.attr.stamp);
+ return when >= query.start;
+ end, iter);
end
if query["end"] then
if query.reverse then
@@ -221,12 +220,11 @@ function archive:find(username, query)
if wi then
i = wi + 1;
end
- else
- iter = it.filter(function(item)
- local when = item.when or datetime.parse(item.attr.stamp);
- return when <= query["end"];
- end, iter);
end
+ iter = it.filter(function(item)
+ local when = item.when or datetime.parse(item.attr.stamp);
+ return when <= query["end"];
+ end, iter);
end
if query.after then
local found = false;
diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua
index f053f729..6d9af68a 100644
--- a/plugins/mod_storage_sql.lua
+++ b/plugins/mod_storage_sql.lua
@@ -13,20 +13,24 @@ local t_concat = table.concat;
local have_dbisql, dbisql = pcall(require, "prosody.util.sql");
local have_sqlite, sqlite = pcall(require, "prosody.util.sqlite3");
-if not have_dbisql then
- module:log("debug", "Could not load LuaDBI: %s", dbisql)
- dbisql = nil;
-end
-if not have_sqlite then
- module:log("debug", "Could not load LuaSQLite3: %s", sqlite)
- sqlite = nil;
-end
if not (have_dbisql or have_sqlite) then
module:log("error", "LuaDBI or LuaSQLite3 are required for using SQL databases but neither are installed");
module:log("error", "Please install at least one of LuaDBI and LuaSQLite3. See https://prosody.im/doc/depends");
+ module:log("debug", "Could not load LuaDBI: %s", dbisql);
+ module:log("debug", "Could not load LuaSQLite3: %s", sqlite);
error("No SQL library available")
end
+local function get_sql_lib(driver)
+ if driver == "SQLite3" and have_sqlite then
+ return sqlite;
+ elseif have_dbisql then
+ return dbisql;
+ else
+ error(dbisql);
+ end
+end
+
local noop = function() end
local unpack = table.unpack;
local function iterator(result)
@@ -42,11 +46,11 @@ end
local function has_upsert(engine)
if engine.params.driver == "SQLite3" then
-- SQLite3 >= 3.24.0
- return engine.sqlite_version and (engine.sqlite_version[2] or 0) >= 24;
+ return engine.sqlite_version and (engine.sqlite_version[2] or 0) >= 24 and engine.has_upsert_index;
elseif engine.params.driver == "PostgreSQL" then
-- PostgreSQL >= 9.5
-- Versions without support have long since reached end of life.
- return true;
+ return engine.has_upsert_index;
end
-- We don't support UPSERT on MySQL/MariaDB, they seem to have a completely different syntax, uncertaint from which versions.
return false
@@ -757,7 +761,7 @@ end
local function create_table(engine) -- luacheck: ignore 431/engine
- local sql = engine.params.driver == "SQLite3" and sqlite or dbisql;
+ local sql = get_sql_lib(engine.params.driver);
local Table, Column, Index = sql.Table, sql.Column, sql.Index;
local ProsodyTable = Table {
@@ -798,7 +802,7 @@ end
local function upgrade_table(engine, params, apply_changes) -- luacheck: ignore 431/engine
local changes = false;
if params.driver == "MySQL" then
- local sql = dbisql;
+ local sql = get_sql_lib("MySQL");
local success,err = engine:transaction(function()
do
local result = assert(engine:execute("SHOW COLUMNS FROM \"prosody\" WHERE \"Field\"='value' and \"Type\"='text'"));
@@ -879,7 +883,7 @@ local function upgrade_table(engine, params, apply_changes) -- luacheck: ignore
indices[row[1]] = true;
end
elseif params.driver == "PostgreSQL" then
- for row in engine:select [[SELECT "indexname" FROM "pg_indexes" WHERE "tablename"='prosody' AND "indexname"='prosody_index';]] do
+ for row in engine:select [[SELECT "indexname" FROM "pg_indexes" WHERE "tablename"='prosody';]] do
indices[row[1]] = true;
end
end
@@ -893,6 +897,12 @@ local function upgrade_table(engine, params, apply_changes) -- luacheck: ignore
return false;
end
end
+ if not indices["prosody_unique_index"] then
+ module:log("warn", "Index \"prosody_unique_index\" does not exist, performance may be worse than normal!");
+ engine.has_upsert_index = false;
+ else
+ engine.has_upsert_index = true;
+ end
end
return changes;
end
@@ -920,7 +930,7 @@ end
function module.load()
local engines = module:shared("/*/sql/connections");
local params = normalize_params(module:get_option("sql", default_params));
- local sql = params.driver == "SQLite3" and sqlite or dbisql;
+ local sql = get_sql_lib(params.driver);
local db_uri = sql.db2uri(params);
engine = engines[db_uri];
if not engine then
@@ -1012,7 +1022,7 @@ function module.command(arg)
local uris = {};
for host in pairs(prosody.hosts) do -- luacheck: ignore 431/host
local params = normalize_params(config.get(host, "sql") or default_params);
- local sql = engine.params.driver == "SQLite3" and sqlite or dbisql;
+ local sql = get_sql_lib(engine.params.driver);
uris[sql.db2uri(params)] = params;
end
print("We will check and upgrade the following databases:\n");
@@ -1028,7 +1038,7 @@ function module.command(arg)
-- Upgrade each one
for _, params in pairs(uris) do
print("Checking "..params.database.."...");
- local sql = params.driver == "SQLite3" and sqlite or dbisql;
+ local sql = get_sql_lib(params.driver);
engine = sql:create_engine(params);
upgrade_table(engine, params, true);
end
@@ -1040,3 +1050,32 @@ function module.command(arg)
print("","upgrade - Perform database upgrade");
end
end
+
+module:add_item("shell-command", {
+ section = "sql";
+ section_desc = "SQL management commands";
+ name = "create";
+ desc = "Create the tables and indices used by Prosody (again)";
+ args = { { name = "host"; type = "string" } };
+ host_selector = "host";
+ handler = function(shell, _host)
+ local logger = require "prosody.util.logger";
+ local writing = false;
+ local sink = logger.add_simple_sink(function (source, _level, message)
+ local print = shell.session.print;
+ if writing or source ~= "sql" then return; end
+ writing = true;
+ print(message);
+ writing = false;
+ end);
+
+ local debug_enabled = engine._debug;
+ engine:debug(true);
+ create_table(engine);
+ engine:debug(debug_enabled);
+
+ if not logger.remove_sink(sink) then
+ module:log("warn", "Unable to remove log sink");
+ end
+ end;
+})
diff --git a/spec/core_storagemanager_spec.lua b/spec/core_storagemanager_spec.lua
index 32a8d6f0..a6857d0c 100644
--- a/spec/core_storagemanager_spec.lua
+++ b/spec/core_storagemanager_spec.lua
@@ -436,6 +436,44 @@ describe("storagemanager", function ()
assert.equal(#test_data - 3, count);
end);
+ it("by time (start before first item)", function ()
+ -- luacheck: ignore 211/err
+ local data, err = archive:find("user", {
+ ["start"] = test_time-5;
+ });
+ assert.truthy(data);
+ local count = 0;
+ for id, item, when in data do
+ count = count + 1;
+ assert.truthy(id);
+ assert(st.is_stanza(item));
+ assert.equal("test", item.name);
+ assert.equal("urn:example:foo", item.attr.xmlns);
+ assert.equal(2, #item.tags);
+ assert(when >= test_time-5, ("%d >= %d"):format(when, test_time-5));
+ end
+ assert.equal(#test_data, count);
+ end);
+
+ it("by time (start after last item)", function ()
+ -- luacheck: ignore 211/err
+ local data, err = archive:find("user", {
+ ["start"] = test_time+5;
+ });
+ assert.truthy(data);
+ local count = 0;
+ for id, item, when in data do
+ count = count + 1;
+ assert.truthy(id);
+ assert(st.is_stanza(item));
+ assert.equal("test", item.name);
+ assert.equal("urn:example:foo", item.attr.xmlns);
+ assert.equal(2, #item.tags);
+ assert(when >= test_time+5, ("%d >= %d"):format(when, test_time+5));
+ end
+ assert.equal(0, count);
+ end);
+
it("by time (start+end)", function ()
-- luacheck: ignore 211/err
local data, err = archive:find("user", {
diff --git a/spec/tls/README b/spec/tls/README
new file mode 100644
index 00000000..58201728
--- /dev/null
+++ b/spec/tls/README
@@ -0,0 +1,11 @@
+These tests check that SSL/TLS configuration is working as expected.
+
+Just run ./run.sh in this directory (or from the top level,
+`make integration-test-tls`.
+
+Known issues:
+ - The tests do not thorougly clean up after themselves (certs, logs, etc.).
+ This is partly intentional, so they can be inspected in case of failures.
+ - Certs are regenerated every time. Could be smarter about this. But it also
+ helps to guard against incorrect Prosody instances running and hogging the
+ ports, etc.
diff --git a/spec/tls/config1/assert.sh b/spec/tls/config1/assert.sh
new file mode 100755
index 00000000..f7d41c26
--- /dev/null
+++ b/spec/tls/config1/assert.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+#set -x
+
+. ../lib.sh
+
+expect_cert "certs/example.com.crt" "localhost:5222" "example.com" "xmpp"
+expect_cert "certs/share.example.com.crt" "localhost:5281" "share.example.com" "tls"
+
+exit "$failures"
diff --git a/spec/tls/config1/prepare.sh b/spec/tls/config1/prepare.sh
new file mode 100755
index 00000000..a8ec2822
--- /dev/null
+++ b/spec/tls/config1/prepare.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+certs="./certs"
+
+for domain in {,share.}example.com; do
+ openssl req -x509 \
+ -newkey rsa:4096 \
+ -keyout "${certs}/${domain}.key" \
+ -out "${certs}/${domain}.crt" \
+ -sha256 \
+ -days 365 \
+ -nodes \
+ -subj "/CN=${domain}" 2>/dev/null;
+done
diff --git a/spec/tls/config1/prosody.cfg.lua b/spec/tls/config1/prosody.cfg.lua
new file mode 100644
index 00000000..9e94de58
--- /dev/null
+++ b/spec/tls/config1/prosody.cfg.lua
@@ -0,0 +1,6 @@
+Include "prosody-default.cfg.lua"
+
+VirtualHost "example.com"
+ enabled = true
+
+Component "share.example.com" "http_file_share"
diff --git a/spec/tls/config2/assert.sh b/spec/tls/config2/assert.sh
new file mode 100755
index 00000000..d1af0f51
--- /dev/null
+++ b/spec/tls/config2/assert.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+#set -x
+
+. ../lib.sh
+
+expect_cert "certs/xmpp.example.com.crt" "localhost:5281" "xmpp.example.com" "tls"
+expect_cert "certs/example.com.crt" "localhost:5222" "example.com" "xmpp"
+
+exit "$failures"
diff --git a/spec/tls/config2/prepare.sh b/spec/tls/config2/prepare.sh
new file mode 100755
index 00000000..1d67af4e
--- /dev/null
+++ b/spec/tls/config2/prepare.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+certs="./certs"
+
+for domain in {,xmpp.}example.com; do
+ openssl req -x509 \
+ -newkey rsa:4096 \
+ -keyout "${certs}/${domain}.key" \
+ -out "${certs}/${domain}.crt" \
+ -sha256 \
+ -days 365 \
+ -nodes \
+ -subj "/CN=${domain}" 2>/dev/null;
+done
diff --git a/spec/tls/config2/prosody.cfg.lua b/spec/tls/config2/prosody.cfg.lua
new file mode 100644
index 00000000..a5728516
--- /dev/null
+++ b/spec/tls/config2/prosody.cfg.lua
@@ -0,0 +1,6 @@
+Include "prosody-default.cfg.lua"
+
+VirtualHost "example.com"
+ enabled = true
+ modules_enabled = { "http" }
+ http_host = "xmpp.example.com"
diff --git a/spec/tls/config3/assert.sh b/spec/tls/config3/assert.sh
new file mode 100755
index 00000000..e36f7fb1
--- /dev/null
+++ b/spec/tls/config3/assert.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+#set -x
+
+. ../lib.sh
+
+expect_cert "certs/xmpp.example.com.crt" "localhost:5281" "xmpp.example.com" "tls"
+expect_cert "certs/example.com.crt" "localhost:5222" "example.com" "xmpp"
+expect_cert "certs/example.com.crt" "localhost:5223" "example.com" "xmpps"
+
+# Weirdly configured host, just to test manual override behaviour
+expect_cert "certs/example.com.crt" "localhost:5222" "example.net" "xmpp"
+expect_cert "certs/example.com.crt" "localhost:5222" "example.net" "xmpp"
+expect_cert "certs/example.com.crt" "localhost:5223" "example.net" "tls"
+expect_cert "certs/example.com.crt" "localhost:5281" "example.net" "tls"
+
+# Three domains using a single cert with SANs
+expect_cert "certs/example.org.crt" "localhost:5222" "example.org" "xmpp"
+expect_cert "certs/example.org.crt" "localhost:5223" "example.org" "xmpps"
+expect_cert "certs/example.org.crt" "localhost:5269" "example.org" "xmpp-server"
+expect_cert "certs/example.org.crt" "localhost:5269" "share.example.org" "xmpp-server"
+expect_cert "certs/example.org.crt" "localhost:5269" "groups.example.org" "xmpp-server"
+expect_cert "certs/example.org.crt" "localhost:5281" "share.example.org" "tls"
+
+exit "$failures"
diff --git a/spec/tls/config3/prepare.sh b/spec/tls/config3/prepare.sh
new file mode 100755
index 00000000..89269d73
--- /dev/null
+++ b/spec/tls/config3/prepare.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+certs="./certs"
+
+for domain in {,xmpp.}example.com example.net; do
+ openssl req -x509 \
+ -newkey rsa:4096 \
+ -keyout "${certs}/${domain}.key" \
+ -out "${certs}/${domain}.crt" \
+ -sha256 \
+ -days 365 \
+ -nodes \
+ -quiet \
+ -subj "/CN=${domain}" 2>/dev/null;
+done
+
+for domain in example.org; do
+ openssl req -x509 \
+ -newkey rsa:4096 \
+ -keyout "${certs}/${domain}.key" \
+ -out "${certs}/${domain}.crt" \
+ -sha256 \
+ -days 365 \
+ -nodes \
+ -subj "/CN=${domain}" \
+ -addext "subjectAltName = DNS:${domain}, DNS:groups.${domain}, DNS:share.${domain}" \
+ 2>/dev/null;
+done
diff --git a/spec/tls/config3/prosody.cfg.lua b/spec/tls/config3/prosody.cfg.lua
new file mode 100644
index 00000000..a92dbfa8
--- /dev/null
+++ b/spec/tls/config3/prosody.cfg.lua
@@ -0,0 +1,28 @@
+Include "prosody-default.cfg.lua"
+
+c2s_direct_tls_ports = { 5223 }
+
+VirtualHost "example.com"
+ enabled = true
+ modules_enabled = { "http" }
+ http_host = "xmpp.example.com"
+
+VirtualHost "example.net"
+ ssl = {
+ certificate = "certs/example.com.crt";
+ key = "certs/example.com.key";
+ }
+
+ https_ssl = {
+ certificate = "certs/example.com.crt";
+ key = "certs/example.com.key";
+ }
+
+ c2s_direct_tls_ssl = {
+ certificate = "certs/example.com.crt";
+ key = "certs/example.com.key";
+ }
+
+VirtualHost "example.org"
+Component "share.example.org" "http_file_share"
+Component "groups.example.org" "muc"
diff --git a/spec/tls/lib.sh b/spec/tls/lib.sh
new file mode 100644
index 00000000..d072802a
--- /dev/null
+++ b/spec/tls/lib.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+test_name="$(basename "$PWD")"
+export failures=0
+
+get_net_cert () {
+ address="${1?}"
+ sni="${2?}"
+ proto="${3?}"
+ local flags=()
+ case "$proto" in
+ "xmpp") flags=(-starttls xmpp -name "$sni");;
+ "xmpps") flags=(-alpn xmpp-client);;
+ "xmpp-server") flags=(-starttls xmpp-server -name "$sni");;
+ "xmpps-server") flags=(-alpn xmpp-server);;
+ "tls") ;;
+ *) printf "EE: Unknown protocol: %s\n" "$proto" >&2; exit 1;;
+ esac
+ openssl s_client -connect "$address" -servername "$sni" "${flags[@]}" 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'
+}
+
+get_file_cert () {
+ fn="${1?}"
+ sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' "$fn"
+}
+
+expect_cert () {
+ fn="${1?}"
+ address="${2?}"
+ sni="${3?}"
+ proto="${4?}"
+ net_cert="$(get_net_cert "$address" "$sni" "$proto")"
+ file_cert="$(get_file_cert "$fn")"
+ if [[ "$file_cert" != "$net_cert" ]]; then
+ echo "---"
+ echo "NOT OK: $test_name: Expected $fn on $address (SNI $sni)"
+ echo "Received:"
+ openssl x509 -in <(echo "$net_cert") -text
+ echo "---"
+ failures=1;
+ return 1;
+ fi
+ echo "OK: $test_name: $fn observed on $address (SNI $sni)"
+ return 0;
+}
diff --git a/spec/tls/run.sh b/spec/tls/run.sh
new file mode 100755
index 00000000..8bceddb2
--- /dev/null
+++ b/spec/tls/run.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+export LUA_PATH="../../../?.lua;;"
+export LUA_CPATH="../../../?.so;;"
+
+any_failed=0
+
+for config in config*; do
+ echo "# Preparing $config"
+ pushd "$config";
+ cp ../../../prosody.cfg.lua.dist ./prosody-default.cfg.lua
+ echo 'VirtualHost "*" {pidfile = "prosody.pid";log={debug="prosody.log"}}' >> ./prosody-default.cfg.lua
+ ln -s ../../../plugins plugins
+ mkdir -p certs data
+ ./prepare.sh
+ ../../../prosody -D
+ sleep 1;
+ echo "# Testing $config"
+ ./assert.sh
+ status=$?
+ ../../../prosodyctl stop
+ rm plugins #prosody-default.cfg.lua
+ popd
+ if [[ "$status" != "0" ]]; then
+ echo -n "NOT ";
+ any_failed=1
+ fi
+ echo "OK: $config";
+done
+
+if [[ "$any_failed" != "0" ]]; then
+ echo "NOT OK: One or more TLS tests failed";
+ exit 1;
+fi
+
+echo "OK: All TLS tests passed";
+exit 0;
diff --git a/util/pluginloader.lua b/util/pluginloader.lua
index 634bd6f8..4d05ea8d 100644
--- a/util/pluginloader.lua
+++ b/util/pluginloader.lua
@@ -25,6 +25,7 @@ local pluginloader_mt = { __index = pluginloader_methods };
function pluginloader_methods:load_file(names)
local file, err, path;
local load_filter_cb = self._options.load_filter_cb;
+ local last_filter_path, last_filter_err;
for i=1,#plugin_dir do
for j=1,#names do
path = plugin_dir[i]..names[j];
@@ -36,12 +37,18 @@ function pluginloader_methods:load_file(names)
if load_filter_cb then
path, content, metadata = load_filter_cb(path, content);
end
- if content and path then
+ if path and content then
return content, path, metadata;
+ else
+ last_filter_path = plugin_dir[i]..names[j];
+ last_filter_err = content or "skipped";
end
end
end
end
+ if last_filter_err then
+ return nil, err..(" (%s skipped because of %s)"):format(last_filter_path, last_filter_err);
+ end
return file, err;
end
diff --git a/util/prosodyctl/check.lua b/util/prosodyctl/check.lua
index 75ff5da4..9d3c3cb9 100644
--- a/util/prosodyctl/check.lua
+++ b/util/prosodyctl/check.lua
@@ -643,6 +643,31 @@ local function check(arg)
print(" mod_posix is loaded in your configuration file, but it has");
print(" been deprecated. You can safely remove it.");
end
+ if all_modules:contains("admin_telnet") then
+ print("");
+ print(" mod_admin_telnet is being replaced by mod_admin_shell (prosodyctl shell).");
+ print(" To update and ensure all commands are available, simply change \"admin_telnet\" to \"admin_shell\"");
+ print(" in your modules_enabled list.");
+ end
+
+ local load_failures = {};
+ for mod_name in all_modules do
+ local mod, err = modulemanager.loader:load_resource(mod_name, nil);
+ if not mod then
+ load_failures[mod_name] = err;
+ end
+ end
+
+ if next(load_failures) ~= nil then
+ print("");
+ print(" The following modules failed to load:");
+ print("");
+ for mod_name, err in it.sorted_pairs(load_failures) do
+ print((" mod_%s: %s"):format(mod_name, err));
+ end
+ print("")
+ print(" Check for typos and remove any obsolete/incompatible modules from your config.");
+ 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
@@ -1313,7 +1338,7 @@ local function check(arg)
http_loaded = false;
end
if http_loaded and not x509_verify_identity(http_host, nil, cert) then
- print(" Not valid for HTTPS connections to "..host..".")
+ print(" Not valid for HTTPS connections to "..http_host..".")
cert_ok = false
end
if use_dane then