aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/certmanager.lua118
-rw-r--r--core/configmanager.lua113
-rw-r--r--core/features.lua25
-rw-r--r--core/hostmanager.lua12
-rw-r--r--core/loggingmanager.lua12
-rw-r--r--core/moduleapi.lua250
-rw-r--r--core/modulemanager.lua24
-rw-r--r--core/portmanager.lua52
-rw-r--r--core/rostermanager.lua12
-rw-r--r--core/s2smanager.lua4
-rw-r--r--core/sessionmanager.lua81
-rw-r--r--core/stanza_router.lua14
-rw-r--r--core/statsmanager.lua14
-rw-r--r--core/storagemanager.lua105
-rw-r--r--core/usermanager.lua263
15 files changed, 805 insertions, 294 deletions
diff --git a/core/certmanager.lua b/core/certmanager.lua
index 6a46588c..9e0ace6a 100644
--- a/core/certmanager.lua
+++ b/core/certmanager.lua
@@ -6,15 +6,13 @@
-- COPYING file in the source package for more information.
--
-local ssl = require "ssl";
-local configmanager = require "core.configmanager";
-local log = require "util.logger".init("certmanager");
-local ssl_context = ssl.context or require "ssl.context";
-local ssl_newcontext = ssl.newcontext;
-local new_config = require"util.sslconfig".new;
+local configmanager = require "prosody.core.configmanager";
+local log = require "prosody.util.logger".init("certmanager");
+local new_config = require"prosody.net.server".tls_builder;
+local tls = require "prosody.net.tls_luasec";
local stat = require "lfs".attributes;
-local x509 = require "util.x509";
+local x509 = require "prosody.util.x509";
local lfs = require "lfs";
local tonumber, tostring = tonumber, tostring;
@@ -28,33 +26,10 @@ local next = next;
local pcall = pcall;
local prosody = prosody;
-local pathutil = require"util.paths";
+local pathutil = require"prosody.util.paths";
local resolve_path = pathutil.resolve_relative_path;
local config_path = prosody.paths.config or ".";
-local function test_option(option)
- return not not ssl_newcontext({mode="server",protocol="sslv23",options={ option }});
-end
-
-local luasec_major, luasec_minor = ssl._VERSION:match("^(%d+)%.(%d+)");
-local luasec_version = tonumber(luasec_major) * 100 + tonumber(luasec_minor);
-local luasec_has = ssl.config or {
- algorithms = {
- ec = luasec_version >= 5;
- };
- capabilities = {
- curves_list = luasec_version >= 7;
- };
- options = {
- cipher_server_preference = test_option("cipher_server_preference");
- no_ticket = test_option("no_ticket");
- no_compression = test_option("no_compression");
- single_dh_use = test_option("single_dh_use");
- single_ecdh_use = test_option("single_ecdh_use");
- no_renegotiation = test_option("no_renegotiation");
- };
-};
-
local _ENV = nil;
-- luacheck: std none
@@ -122,7 +97,7 @@ local function index_certs(dir, files_by_name, depth_limit)
local firstline = f:read();
if firstline == "-----BEGIN CERTIFICATE-----" and lfs.attributes(find_matching_key(full), "mode") == "file" then
f:seek("set")
- local cert = ssl.loadcertificate(f:read("*a"))
+ local cert = tls.load_certificate(f:read("*a"))
-- TODO if more than one cert is found for a name, the most recently
-- issued one should be used.
-- for now, just filter out expired certs
@@ -207,18 +182,18 @@ local core_defaults = {
protocol = "tlsv1+";
verify = "none";
options = {
- cipher_server_preference = luasec_has.options.cipher_server_preference;
- no_ticket = luasec_has.options.no_ticket;
- no_compression = luasec_has.options.no_compression and configmanager.get("*", "ssl_compression") ~= true;
- single_dh_use = luasec_has.options.single_dh_use;
- single_ecdh_use = luasec_has.options.single_ecdh_use;
- no_renegotiation = luasec_has.options.no_renegotiation;
+ cipher_server_preference = tls.features.options.cipher_server_preference;
+ no_ticket = tls.features.options.no_ticket;
+ no_compression = tls.features.options.no_compression and configmanager.get("*", "ssl_compression") ~= true;
+ single_dh_use = tls.features.options.single_dh_use;
+ single_ecdh_use = tls.features.options.single_ecdh_use;
+ no_renegotiation = tls.features.options.no_renegotiation;
};
verifyext = {
"lsec_continue", -- Continue past certificate verification errors
"lsec_ignore_purpose", -- Validate client certificates as if they were server certificates
};
- curve = luasec_has.algorithms.ec and not luasec_has.capabilities.curves_list and "secp384r1";
+ curve = tls.features.algorithms.ec and not tls.features.capabilities.curves_list and "secp384r1";
curveslist = {
"X25519",
"P-384",
@@ -235,9 +210,21 @@ local core_defaults = {
"!3DES", -- 3DES - slow and of questionable security
"!aNULL", -- Ciphers that does not authenticate the connection
};
- dane = luasec_has.capabilities.dane and configmanager.get("*", "use_dane") and { "no_ee_namechecks" };
+ 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
@@ -250,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";
@@ -304,9 +291,9 @@ local mozilla_ssl_configs = {
};
-if luasec_has.curves then
+if tls.features.curves then
for i = #core_defaults.curveslist, 1, -1 do
- if not luasec_has.curves[ core_defaults.curveslist[i] ] then
+ if not tls.features.curves[ core_defaults.curveslist[i] ] then
t_remove(core_defaults.curveslist, i);
end
end
@@ -314,10 +301,6 @@ else
core_defaults.curveslist = nil;
end
-local path_options = { -- These we pass through resolve_path()
- key = true, certificate = true, cafile = true, capath = true, dhparam = true
-}
-
local function create_context(host, mode, ...)
local cfg = new_config();
cfg:apply(core_defaults);
@@ -351,39 +334,12 @@ local function create_context(host, mode, ...)
if mode == "server" then
if not user_ssl_config.certificate then
- log("info", "No certificate present in SSL/TLS configuration for %s. SNI will be required.", host);
+ log("debug", "No certificate present in SSL/TLS configuration for %s. SNI will be required.", host);
end
if user_ssl_config.certificate and not user_ssl_config.key then return nil, "No key present in SSL/TLS configuration for "..host; end
end
- for option in pairs(path_options) do
- if type(user_ssl_config[option]) == "string" then
- user_ssl_config[option] = resolve_path(config_path, user_ssl_config[option]);
- else
- user_ssl_config[option] = nil;
- end
- end
-
- -- 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(user_ssl_config.dhparam) == "string" then
- local f, err = io_open(user_ssl_config.dhparam);
- if not f then return nil, "Could not open DH parameters: "..err end
- local dhparam = f:read("*a");
- f:close();
- user_ssl_config.dhparam = function() return dhparam; end
- end
-
- local ctx, err = ssl_newcontext(user_ssl_config);
-
- -- COMPAT Older LuaSec ignores the cipher list from the config, so we have to take care
- -- of it ourselves (W/A for #x)
- if ctx and user_ssl_config.ciphers then
- local success;
- success, err = ssl_context.setcipher(ctx, user_ssl_config.ciphers);
- if not success then ctx = nil; end
- end
+ local ctx, err = cfg:build();
if not ctx then
err = err or "invalid ssl config"
@@ -422,10 +378,16 @@ end
local function reload_ssl_config()
global_ssl_config = configmanager.get("*", "ssl");
global_certificates = configmanager.get("*", "certificates") or "certs";
- if luasec_has.options.no_compression then
+ if tls.features.options.no_compression then
core_defaults.options.no_compression = configmanager.get("*", "ssl_compression") ~= true;
end
- core_defaults.dane = configmanager.get("*", "use_dane") or false;
+ if not configmanager.get("*", "use_dane") then
+ core_defaults.dane = false;
+ elseif tls.features.capabilities.dane then
+ core_defaults.dane = { "no_ee_namechecks" };
+ else
+ core_defaults.dane = true;
+ end
cert_index = index_certs(resolve_path(config_path, global_certificates));
end
diff --git a/core/configmanager.lua b/core/configmanager.lua
index 092b3946..bd12e169 100644
--- a/core/configmanager.lua
+++ b/core/configmanager.lua
@@ -11,14 +11,15 @@ local setmetatable, rawget, rawset, io, os, error, dofile, type, pairs, ipairs =
setmetatable, rawget, rawset, io, os, error, dofile, type, pairs, ipairs;
local format, math_max, t_insert = string.format, math.max, table.insert;
-local envload = require"util.envload".envload;
-local deps = require"util.dependencies";
-local resolve_relative_path = require"util.paths".resolve_relative_path;
-local glob_to_pattern = require"util.paths".glob_to_pattern;
+local envload = require"prosody.util.envload".envload;
+local deps = require"prosody.util.dependencies";
+local it = require"prosody.util.iterators";
+local resolve_relative_path = require"prosody.util.paths".resolve_relative_path;
+local glob_to_pattern = require"prosody.util.paths".glob_to_pattern;
local path_sep = package.config:sub(1,1);
-local get_traceback_table = require "util.debug".get_traceback_table;
+local get_traceback_table = require "prosody.util.debug".get_traceback_table;
-local encodings = deps.softreq"util.encodings";
+local encodings = deps.softreq"prosody.util.encodings";
local nameprep = encodings and encodings.stringprep.nameprep or function (host) return host:lower(); end
local _M = {};
@@ -40,16 +41,10 @@ function _M.getconfig()
return config;
end
-function _M.get(host, key, _oldkey)
- if key == "core" then
- key = _oldkey; -- COMPAT with code that still uses "core"
- end
+function _M.get(host, key)
return config[host][key];
end
-function _M.rawget(host, key, _oldkey)
- if key == "core" then
- key = _oldkey; -- COMPAT with code that still uses "core"
- end
+function _M.rawget(host, key)
local hostconfig = rawget(config, host);
if hostconfig then
return rawget(hostconfig, key);
@@ -68,10 +63,17 @@ local function set(config_table, host, key, value)
return false;
end
-function _M.set(host, key, value, _oldvalue)
- if key == "core" then
- key, value = value, _oldvalue; --COMPAT with code that still uses "core"
+local function rawget_option(config_table, host, key)
+ if host and key then
+ local hostconfig = rawget(config_table, host);
+ if not hostconfig then
+ return nil;
+ end
+ return rawget(hostconfig, key);
end
+end
+
+function _M.set(host, key, value)
return set(config, host, key, value);
end
@@ -114,6 +116,51 @@ do
end
end
end
+
+ local config_option_proxy_mt = {
+ __index = setmetatable({
+ append = function (self, value)
+ local original_option = self:value();
+ if original_option == nil then
+ original_option = {};
+ end
+ if type(value) ~= "table" then
+ error("'append' operation expects a list of values to append to the existing list", 2);
+ end
+ if value[1] ~= nil then
+ for _, v in ipairs(value) do
+ t_insert(original_option, v);
+ end
+ else
+ for k, v in pairs(value) do
+ original_option[k] = v;
+ end
+ end
+ set(self.config_table, self.host, self.option_name, original_option);
+ return self;
+ end;
+ value = function (self)
+ return rawget_option(self.config_table, self.host, self.option_name);
+ end;
+ values = function (self)
+ return it.values(self:value());
+ end;
+ }, {
+ __index = function (t, k) --luacheck: ignore 212/t
+ error("Unknown config option operation: '"..k.."'", 2);
+ end;
+ });
+
+ __call = function (self, v2)
+ local v = self:value() or {};
+ if type(v) == "table" and type(v2) == "table" then
+ return self:append(v2);
+ end
+
+ error("Invalid syntax - missing '=' perhaps?", 2);
+ end;
+ };
+
parser = {};
function parser.load(data, config_file, config_table)
local set_options = {}; -- set_options[host.."/"..option_name] = true (when the option has been set already in this file)
@@ -128,7 +175,37 @@ do
if k:match("^ENV_") then
return os.getenv(k:sub(5));
end
- return rawget(_G, k);
+ if k == "Lua" then
+ return _G;
+ end
+ local val = rawget_option(config_table, env.__currenthost or "*", k);
+
+ local g_val = rawget(_G, k);
+
+ if val ~= nil or g_val == nil then
+ if type(val) == "table" then
+ return setmetatable({
+ config_table = config_table;
+ host = env.__currenthost or "*";
+ option_name = k;
+ }, config_option_proxy_mt);
+ end
+ return val;
+ end
+
+ if g_val ~= nil then
+ t_insert(
+ warnings,
+ ("%s:%d: direct usage of the Lua API is deprecated - replace `%s` with `Lua.%s`"):format(
+ config_file,
+ get_line_number(config_file),
+ k,
+ k
+ )
+ );
+ end
+
+ return g_val;
end,
__newindex = function (_, k, v)
local host = env.__currenthost or "*";
diff --git a/core/features.lua b/core/features.lua
index 35df5636..99edde51 100644
--- a/core/features.lua
+++ b/core/features.lua
@@ -1,10 +1,33 @@
-local set = require "util.set";
+local set = require "prosody.util.set";
return {
available = set.new{
-- mod_bookmarks bundled
"mod_bookmarks";
+ -- mod_server_info bundled
+ "mod_server_info";
+ -- Roles, module.may and per-session authz
+ "permissions";
+ -- prosody.* namespace
+ "loader";
+ -- "keyval+" store
+ "keyval+";
"s2sout-pre-connect-event";
+
+ -- prosody:guest, prosody:registered, prosody:member
+ "split-user-roles";
+
+ -- new moduleapi methods
+ "getopt-enum";
+ "getopt-interval";
+ "getopt-period";
+ "getopt-integer";
+
+ -- new module.ready()
+ "module-ready";
+
+ -- SIGUSR1 and 2 events
+ "signal-events";
};
};
diff --git a/core/hostmanager.lua b/core/hostmanager.lua
index f33a3e1e..e27250de 100644
--- a/core/hostmanager.lua
+++ b/core/hostmanager.lua
@@ -6,18 +6,18 @@
-- COPYING file in the source package for more information.
--
-local configmanager = require "core.configmanager";
-local modulemanager = require "core.modulemanager";
-local events_new = require "util.events".new;
-local disco_items = require "util.multitable".new();
+local configmanager = require "prosody.core.configmanager";
+local modulemanager = require "prosody.core.modulemanager";
+local events_new = require "prosody.util.events".new;
+local disco_items = require "prosody.util.multitable".new();
local NULL = {};
-local log = require "util.logger".init("hostmanager");
+local log = require "prosody.util.logger".init("hostmanager");
local hosts = prosody.hosts;
local prosody_events = prosody.events;
if not _G.prosody.incoming_s2s then
- require "core.s2smanager";
+ require "prosody.core.s2smanager";
end
local incoming_s2s = _G.prosody.incoming_s2s;
local core_route_stanza = _G.prosody.core_route_stanza;
diff --git a/core/loggingmanager.lua b/core/loggingmanager.lua
index 5da77354..d33eef36 100644
--- a/core/loggingmanager.lua
+++ b/core/loggingmanager.lua
@@ -6,20 +6,20 @@
-- COPYING file in the source package for more information.
--
-local format = require "util.format".format;
+local format = require "prosody.util.format".format;
local setmetatable, rawset, pairs, ipairs, type =
setmetatable, rawset, pairs, ipairs, type;
local stdout = io.stdout;
local io_open = io.open;
local math_max, rep = math.max, string.rep;
local os_date = os.date;
-local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
-local st = require "util.stanza";
+local getstyle, getstring = require "prosody.util.termcolours".getstyle, require "prosody.util.termcolours".getstring;
+local st = require "prosody.util.stanza";
-local config = require "core.configmanager";
-local logger = require "util.logger";
+local config = require "prosody.core.configmanager";
+local logger = require "prosody.util.logger";
-local have_pposix, pposix = pcall(require, "util.pposix");
+local have_pposix, pposix = pcall(require, "prosody.util.pposix");
have_pposix = have_pposix and pposix._VERSION == "0.4.0";
local _ENV = nil;
diff --git a/core/moduleapi.lua b/core/moduleapi.lua
index cbb2da9c..fa5086cf 100644
--- a/core/moduleapi.lua
+++ b/core/moduleapi.lua
@@ -6,28 +6,30 @@
-- COPYING file in the source package for more information.
--
-local array = require "util.array";
-local set = require "util.set";
-local it = require "util.iterators";
-local logger = require "util.logger";
-local timer = require "util.timer";
-local resolve_relative_path = require"util.paths".resolve_relative_path;
-local st = require "util.stanza";
-local cache = require "util.cache";
-local errors = require "util.error";
-local promise = require "util.promise";
-local time_now = require "util.time".now;
-local format = require "util.format".format;
-local jid_node = require "util.jid".node;
-local jid_resource = require "util.jid".resource;
+local array = require "prosody.util.array";
+local set = require "prosody.util.set";
+local it = require "prosody.util.iterators";
+local logger = require "prosody.util.logger";
+local timer = require "prosody.util.timer";
+local resolve_relative_path = require"prosody.util.paths".resolve_relative_path;
+local st = require "prosody.util.stanza";
+local cache = require "prosody.util.cache";
+local errors = require "prosody.util.error";
+local promise = require "prosody.util.promise";
+local time_now = require "prosody.util.time".now;
+local format = require "prosody.util.format".format;
+local jid_node = require "prosody.util.jid".node;
+local jid_split = require "prosody.util.jid".split;
+local jid_resource = require "prosody.util.jid".resource;
+local human_io = require "prosody.util.human.io";
local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
local error, setmetatable, type = error, setmetatable, type;
local ipairs, pairs, select = ipairs, pairs, select;
local tonumber, tostring = tonumber, tostring;
local require = require;
-local pack = table.pack or require "util.table".pack; -- table.pack is only in 5.2
-local unpack = table.unpack or unpack; --luacheck: ignore 113 -- renamed in 5.2
+local pack = table.pack;
+local unpack = table.unpack;
local prosody = prosody;
local hosts = prosody.hosts;
@@ -128,14 +130,14 @@ function api:wrap_global(event, handler)
end
function api:require(lib)
- local modulemanager = require"core.modulemanager";
+ local modulemanager = require"prosody.core.modulemanager";
local f, n = modulemanager.loader:load_code_ext(self.name, lib, "lib.lua", self.environment);
if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message
return f();
end
function api:depends(name)
- local modulemanager = require"core.modulemanager";
+ local modulemanager = require"prosody.core.modulemanager";
if self:get_option_inherited_set("modules_disabled", {}):contains(name) then
error("Dependency on disabled module mod_"..name);
end
@@ -168,6 +170,10 @@ function api:depends(name)
end
end
self.dependencies[name] = true;
+ if not mod.module.reverse_dependencies then
+ mod.module.reverse_dependencies = {};
+ end
+ mod.module.reverse_dependencies[self.name] = true;
return mod;
end
@@ -200,7 +206,7 @@ function api:shared(path)
end
function api:get_option(name, default_value)
- local config = require "core.configmanager";
+ local config = require "prosody.core.configmanager";
local value = config.get(self.host, name);
if value == nil then
value = default_value;
@@ -227,12 +233,91 @@ function api:get_option_string(name, default_value)
return tostring(value);
end
-function api:get_option_number(name, ...)
- local value = self:get_option_scalar(name, ...);
+function api:get_option_number(name, default_value, min, max)
+ local value = self:get_option_scalar(name, default_value);
local ret = tonumber(value);
if value ~= nil and ret == nil then
self:log("error", "Config option '%s' not understood, expecting a number", name);
end
+ if ret == default_value then
+ -- skip interval checks for default or nil
+ return ret;
+ end
+ if min and ret < min then
+ self:log("warn", "Config option '%s' out of bounds %g < %g", name, ret, min);
+ return min;
+ end
+ if max and ret > max then
+ self:log("warn", "Config option '%s' out of bounds %g > %g", name, ret, max);
+ return max;
+ end
+ return ret;
+end
+
+function api:get_option_integer(name, default_value, min, max)
+ local value = self:get_option_number(name, default_value, min or math.mininteger or -2 ^ 52, max or math.maxinteger or 2 ^ 53);
+ if value == default_value then
+ -- pass default trough unaltered, violates ranges sometimes
+ return value;
+ end
+ if math.type(value) == "float" then
+ self:log("warn", "Config option '%s' expected an integer, not a float (%g)", name, value)
+ return math.floor(value);
+ end
+ -- nil or an integer
+ return value;
+end
+
+function api:get_option_period(name, default_value, min, max)
+ local value = self:get_option_scalar(name, default_value);
+
+ local ret;
+ if value == "never" or value == false then
+ -- usually for disabling some periodic thing
+ return math.huge;
+ elseif type(value) == "number" then
+ -- assume seconds
+ ret = value;
+ elseif type(value) == "string" then
+ ret = human_io.parse_duration(value);
+ if value ~= nil and ret == nil then
+ ret = human_io.parse_duration_lax(value);
+ if ret then
+ local num = value:match("%d+");
+ self:log("error", "Config option '%s' is set to ambiguous period '%s' - use full syntax e.g. '%s months' or '%s minutes'", name, value, num, num);
+ -- COMPAT: w/more relaxed behaviour in post-0.12 trunk. Return nil for this case too, eventually.
+ else
+ self:log("error", "Config option '%s' not understood, expecting a period (e.g. \"2 days\")", name);
+ return nil;
+ end
+ end
+ elseif value ~= nil then
+ self:log("error", "Config option '%s' expects a number or a period description string (e.g. \"3 hours\"), not %s", name, type(value));
+ return nil;
+ else
+ return nil;
+ end
+
+ if ret < 0 then
+ self:log("debug", "Treating negative period as infinity");
+ return math.huge;
+ end
+
+ if type(min) == "string" then
+ min = human_io.parse_duration(min);
+ end
+ if min and ret < min then
+ self:log("warn", "Config option '%s' out of bounds %g < %g", name, ret, min);
+ return min;
+ end
+ if type(max) == "string" then
+ max = human_io.parse_duration(max);
+ end
+ if max and ret > max then
+ self:log("warn", "Config option '%s' out of bounds %g > %g", name, ret, max);
+ return max;
+ end
+
return ret;
end
@@ -305,6 +390,15 @@ function api:get_option_path(name, default, parent)
return resolve_relative_path(parent, value);
end
+function api:get_option_enum(name, default, ...)
+ local value = self:get_option_scalar(name, default);
+ if value == nil then return nil; end
+ local options = set.new{default, ...};
+ if not options:contains(value) then
+ self:log("error", "Config option '%s' not in set of allowed values (one of: %s)", name, options);
+ end
+ return value;
+end
function api:context(host)
return setmetatable({ host = host or "*", global = "*" == host }, { __index = self, __newindex = self });
@@ -328,7 +422,7 @@ function api:remove_item(key, value)
end
function api:get_host_items(key)
- local modulemanager = require"core.modulemanager";
+ local modulemanager = require"prosody.core.modulemanager";
local result = modulemanager.get_items(key, self.host) or {};
return result;
end
@@ -537,11 +631,12 @@ function api:load_resource(path, mode)
end
function api:open_store(name, store_type)
- return require"core.storagemanager".open(self.host, name or self.name, store_type);
+ if self.host == "*" then return nil, "global-storage-not-supported"; end
+ return require"prosody.core.storagemanager".open(self.host, name or self.name, store_type);
end
function api:measure(name, stat_type, conf)
- local measure = require "core.statsmanager".measure;
+ local measure = require "prosody.core.statsmanager".measure;
local fixed_label_key, fixed_label_value
if self.host ~= "*" then
fixed_label_key = "host"
@@ -556,7 +651,7 @@ function api:measure(name, stat_type, conf)
end
function api:metric(type_, name, unit, description, label_keys, conf)
- local metric = require "core.statsmanager".metric;
+ local metric = require "prosody.core.statsmanager".metric;
local is_scoped = self.host ~= "*"
label_keys = label_keys or {};
if is_scoped then
@@ -579,7 +674,7 @@ local status_priorities = { error = 3, warn = 2, info = 1, core = 0 };
function api:set_status(status_type, status_message, override)
local priority = status_priorities[status_type];
if not priority then
- self:log("error", "set_status: Invalid status type '%s', assuming 'info'");
+ self:log("error", "set_status: Invalid status type '%s', assuming 'info'", status_type);
status_type, priority = "info", status_priorities.info;
end
local current_priority = status_priorities[self.status_type] or 0;
@@ -602,4 +697,107 @@ function api:get_status()
return self.status_type, self.status_message, self.status_time;
end
+function api:default_permission(role_name, permission)
+ permission = permission:gsub("^:", self.name..":");
+ if self.host == "*" then
+ for _, host in pairs(hosts) do
+ if host.authz then
+ host.authz.add_default_permission(role_name, permission);
+ end
+ end
+ return
+ end
+ hosts[self.host].authz.add_default_permission(role_name, permission);
+end
+
+function api:default_permissions(role_name, permissions)
+ for _, permission in ipairs(permissions) do
+ self:default_permission(role_name, permission);
+ end
+end
+
+function api:could(action, context)
+ return self:may(action, context, true);
+end
+
+function api:may(action, context, peek)
+ if action:byte(1) == 58 then -- action begins with ':'
+ action = self.name..action; -- prepend module name
+ end
+
+ do
+ -- JID-based actor
+ local actor_jid = type(context) == "string" and context or context.actor_jid;
+ if actor_jid then -- check JID permissions
+ local role;
+ local node, host = jid_split(actor_jid);
+ if host == self.host then
+ role = hosts[host].authz.get_user_role(node);
+ else
+ role = hosts[self.host].authz.get_jid_role(actor_jid);
+ end
+ if not role then
+ if not peek then
+ self:log("debug", "Access denied: JID <%s> may not %s (no role found)", actor_jid, action);
+ end
+ return false;
+ end
+ local permit = role:may(action);
+ if not permit then
+ if not peek then
+ self:log("debug", "Access denied: JID <%s> may not %s (not permitted by role %s)", actor_jid, action, role.name);
+ end
+ end
+ return permit;
+ end
+ end
+
+ -- Session-based actor
+ local session = context.origin or context.session;
+ if type(session) ~= "table" then
+ error("Unable to identify actor session from context");
+ end
+ if session.type == "c2s" and session.host == self.host then
+ local role = session.role;
+ if not role then
+ if not peek then
+ self:log("warn", "Access denied: session %s has no role assigned");
+ end
+ return false;
+ end
+ local permit = role:may(action, context);
+ if not permit and not peek then
+ self:log("debug", "Access denied: session %s (%s) may not %s (not permitted by role %s)",
+ session.id, session.full_jid, action, role.name
+ );
+ end
+ return permit;
+ else
+ local actor_jid = context.stanza.attr.from;
+ local role = hosts[self.host].authz.get_jid_role(actor_jid);
+ if not role then
+ if not peek then
+ self:log("debug", "Access denied: JID <%s> may not %s (no role found)", actor_jid, action);
+ end
+ return false;
+ end
+ local permit = role:may(action, context);
+ if not permit and not peek then
+ self:log("debug", "Access denied: JID <%s> may not %s (not permitted by role %s)", actor_jid, action, role.name);
+ end
+ return permit;
+ end
+end
+
+-- Execute a function, once, but only after startup is complete
+function api:on_ready(f) --luacheck: ignore 212/self
+ return prosody.started:next(f);
+end
+
+-- COMPAT w/post 0.12 trunk
+function api:once(f)
+ self:log("warn", "This module uses deprecated module:once() - switch to module:on_ready() or (better) expose function module.ready()");
+ return self:on_ready(f);
+end
+
return api;
diff --git a/core/modulemanager.lua b/core/modulemanager.lua
index 0ddf175b..873e08e5 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -6,23 +6,23 @@
-- COPYING file in the source package for more information.
--
-local array = require "util.array";
-local logger = require "util.logger";
+local array = require "prosody.util.array";
+local logger = require "prosody.util.logger";
local log = logger.init("modulemanager");
-local config = require "core.configmanager";
-local pluginloader = require "util.pluginloader";
-local envload = require "util.envload";
-local set = require "util.set";
+local config = require "prosody.core.configmanager";
+local pluginloader = require "prosody.util.pluginloader";
+local envload = require "prosody.util.envload";
+local set = require "prosody.util.set";
-local core_features = require "core.features".available;
+local core_features = require "prosody.core.features".available;
-local new_multitable = require "util.multitable".new;
-local api = require "core.moduleapi"; -- Module API container
+local new_multitable = require "prosody.util.multitable".new;
+local api = require "prosody.core.moduleapi"; -- Module API container
local prosody = prosody;
local hosts = prosody.hosts;
-local xpcall = require "util.xpcall".xpcall;
+local xpcall = require "prosody.util.xpcall".xpcall;
local debug_traceback = debug.traceback;
local setmetatable, rawget = setmetatable, rawget;
local ipairs, pairs, type, t_insert = ipairs, pairs, type, table.insert;
@@ -293,6 +293,10 @@ local function do_load_module(host, module_name, state)
ok, err = do_load_module(host, module_name);
end
end
+
+ if module_has_method(pluginenv, "ready") then
+ pluginenv.module:on_ready(pluginenv.module.ready);
+ end
end
if not ok then
modulemap[api_instance.host][module_name] = nil;
diff --git a/core/portmanager.lua b/core/portmanager.lua
index 38c74b66..904c979c 100644
--- a/core/portmanager.lua
+++ b/core/portmanager.lua
@@ -1,11 +1,11 @@
-local config = require "core.configmanager";
-local certmanager = require "core.certmanager";
-local server = require "net.server";
+local config = require "prosody.core.configmanager";
+local certmanager = require "prosody.core.certmanager";
+local server = require "prosody.net.server";
local socket = require "socket";
-local log = require "util.logger".init("portmanager");
-local multitable = require "util.multitable";
-local set = require "util.set";
+local log = require "prosody.util.logger".init("portmanager");
+local multitable = require "prosody.util.multitable";
+local set = require "prosody.util.set";
local table = table;
local setmetatable, rawset, rawget = setmetatable, rawset, rawget;
@@ -48,14 +48,11 @@ local function error_to_friendly_message(service_name, port, err) --luacheck: ig
if err:match(" in use") then
-- FIXME: Use service_name here
if port == 5222 or port == 5223 or port == 5269 then
- friendly_message = "check that Prosody or another XMPP server is "
- .."not already running and using this port";
- elseif port == 80 or port == 81 then
- friendly_message = "check that a HTTP server is not already using "
- .."this port";
+ friendly_message = "check that Prosody or another XMPP server is not already running and using this port";
+ elseif port == 80 or port == 81 or port == 443 then
+ friendly_message = "check that a HTTP server is not already using this port";
elseif port == 5280 then
- friendly_message = "check that Prosody or a BOSH connection manager "
- .."is not already running";
+ friendly_message = "check that Prosody or a BOSH connection manager is not already running";
else
friendly_message = "this port is in use by another application";
end
@@ -222,6 +219,13 @@ function get_service_at(interface, port)
return data.service, data.server;
end
+local function get_tls_config_at(interface, port)
+ local data = active_services:search(nil, interface, port);
+ if not data or not data[1] or not data[1][1] then return nil, "not-found"; end
+ data = data[1][1];
+ return data.tls_cfg;
+end
+
local function get_service(service_name)
return (services[service_name] or {})[1];
end
@@ -240,21 +244,22 @@ local function add_sni_host(host, service)
log("debug", "Gathering certificates for SNI for host %s, %s service", host, service or "default");
for name, interface, port, n, active_service --luacheck: ignore 213
in active_services:iter(service, nil, nil, nil) do
- if active_service.server.hosts and active_service.tls_cfg then
- local config_prefix = (active_service.config_prefix or name).."_";
- if config_prefix == "_" then config_prefix = ""; end
- local prefix_ssl_config = config.get(host, config_prefix.."ssl");
+ if active_service.server and active_service.tls_cfg then
local alternate_host = name and config.get(host, name.."_host");
if not alternate_host and name == "https" then
-- TODO should this be some generic thing? e.g. in the service definition
alternate_host = config.get(host, "http_host");
end
local autocert = certmanager.find_host_cert(alternate_host or host);
- -- luacheck: ignore 211/cfg
- local ssl, err, cfg = certmanager.create_context(host, "server", prefix_ssl_config, autocert, active_service.tls_cfg);
- if ssl then
- active_service.server.hosts[alternate_host or host] = ssl;
- else
+ local manualcert = active_service.tls_cfg;
+ local certificate = (autocert and autocert.certificate) or manualcert.certificate;
+ local key = (autocert and autocert.key) or manualcert.key;
+ local ok, err = active_service.server:sslctx():set_sni_host(
+ host,
+ certificate,
+ key
+ );
+ if not ok then
log("error", "Error creating TLS context for SNI host %s: %s", host, err);
end
end
@@ -277,7 +282,7 @@ prosody.events.add_handler("host-deactivated", function (host)
for name, interface, port, n, active_service --luacheck: ignore 213
in active_services:iter(nil, nil, nil, nil) do
if active_service.tls_cfg then
- active_service.server.hosts[host] = nil;
+ active_service.server:sslctx():remove_sni_host(host)
end
end
end);
@@ -312,6 +317,7 @@ return {
unregister_service = unregister_service;
close = close;
get_service_at = get_service_at;
+ get_tls_config_at = get_tls_config_at;
get_service = get_service;
get_active_services = get_active_services;
get_registered_services = get_registered_services;
diff --git a/core/rostermanager.lua b/core/rostermanager.lua
index efb80abb..3ada40c9 100644
--- a/core/rostermanager.lua
+++ b/core/rostermanager.lua
@@ -9,10 +9,10 @@
-local log = require "util.logger".init("rostermanager");
+local log = require "prosody.util.logger".init("rostermanager");
-local new_id = require "util.id".short;
-local new_cache = require "util.cache".new;
+local new_id = require "prosody.util.id".short;
+local new_cache = require "prosody.util.cache".new;
local pairs = pairs;
local tostring = tostring;
@@ -21,9 +21,9 @@ local type = type;
local hosts = prosody.hosts;
local bare_sessions = prosody.bare_sessions;
-local um_user_exists = require "core.usermanager".user_exists;
-local st = require "util.stanza";
-local storagemanager = require "core.storagemanager";
+local um_user_exists = require "prosody.core.usermanager".user_exists;
+local st = require "prosody.util.stanza";
+local storagemanager = require "prosody.core.storagemanager";
local _ENV = nil;
-- luacheck: std none
diff --git a/core/s2smanager.lua b/core/s2smanager.lua
index b9190993..d2c58a54 100644
--- a/core/s2smanager.lua
+++ b/core/s2smanager.lua
@@ -11,8 +11,8 @@
local hosts = prosody.hosts;
local pairs, setmetatable = pairs, setmetatable;
-local logger_init = require "util.logger".init;
-local sessionlib = require "util.session";
+local logger_init = require "prosody.util.logger".init;
+local sessionlib = require "prosody.util.session";
local log = logger_init("s2smanager");
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index f8279bb4..750007fb 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -10,20 +10,20 @@
local tostring, setmetatable = tostring, setmetatable;
local pairs, next= pairs, next;
-local hosts = prosody.hosts;
+local prosody, hosts = prosody, prosody.hosts;
local full_sessions = prosody.full_sessions;
local bare_sessions = prosody.bare_sessions;
-local logger = require "util.logger";
+local logger = require "prosody.util.logger";
local log = logger.init("sessionmanager");
-local rm_load_roster = require "core.rostermanager".load_roster;
-local config_get = require "core.configmanager".get;
-local resourceprep = require "util.encodings".stringprep.resourceprep;
-local nodeprep = require "util.encodings".stringprep.nodeprep;
-local generate_identifier = require "util.id".short;
-local sessionlib = require "util.session";
-
-local initialize_filters = require "util.filters".initialize;
+local rm_load_roster = require "prosody.core.rostermanager".load_roster;
+local config_get = require "prosody.core.configmanager".get;
+local resourceprep = require "prosody.util.encodings".stringprep.resourceprep;
+local nodeprep = require "prosody.util.encodings".stringprep.nodeprep;
+local generate_identifier = require "prosody.util.id".short;
+local sessionlib = require "prosody.util.session";
+
+local initialize_filters = require "prosody.util.filters".initialize;
local gettime = require "socket".gettime;
local _ENV = nil;
@@ -92,6 +92,51 @@ local function retire_session(session)
return setmetatable(session, resting_session);
end
+-- Update a session with a new one (transplanting connection, filters, etc.)
+-- new_session should be discarded after this call returns
+local function update_session(to_session, from_session)
+ to_session.log("debug", "Updating with parameters from session %s", from_session.id);
+ from_session.log("debug", "Session absorbed into %s", to_session.id);
+
+ local replaced_conn = to_session.conn;
+ if replaced_conn then
+ to_session.conn = nil;
+ end
+
+ to_session.since = from_session.since;
+ to_session.ip = from_session.ip;
+ to_session.conn = from_session.conn;
+ to_session.rawsend = from_session.rawsend;
+ to_session.rawsend.session = to_session;
+ to_session.rawsend.conn = to_session.conn;
+ to_session.send = from_session.send;
+ to_session.send.session = to_session;
+ to_session.close = from_session.close;
+ to_session.filter = from_session.filter;
+ to_session.filter.session = to_session;
+ to_session.filters = from_session.filters;
+ to_session.send.filter = to_session.filter;
+ to_session.sasl_handler = from_session.sasl_handler;
+ to_session.stream = from_session.stream;
+ to_session.secure = from_session.secure;
+ to_session.hibernating = nil;
+ to_session.resumption_counter = (to_session.resumption_counter or 0) + 1;
+ from_session.log = to_session.log;
+ from_session.type = to_session.type;
+ -- Inform xmppstream of the new session (passed to its callbacks)
+ to_session.stream:set_session(to_session);
+
+ -- Notify modules, allowing them to copy further fields or update state
+ prosody.events.fire_event("c2s-session-updated", {
+ session = to_session;
+ from_session = from_session;
+ replaced_conn = replaced_conn;
+ });
+
+ -- Retire the session we've pulled from, to avoid two sessions on the same connection
+ retire_session(from_session);
+end
+
local function destroy_session(session, err)
if session.destroyed then return; end
@@ -130,15 +175,24 @@ local function destroy_session(session, err)
retire_session(session);
end
-local function make_authenticated(session, username, scope)
+local function make_authenticated(session, username, role_name)
username = nodeprep(username);
if not username or #username == 0 then return nil, "Invalid username"; end
session.username = username;
if session.type == "c2s_unauthed" then
session.type = "c2s_unbound";
end
- session.auth_scope = scope;
- session.log("info", "Authenticated as %s@%s", username, session.host or "(unknown)");
+
+ local role;
+ if role_name then
+ role = hosts[session.host].authz.get_role_by_name(role_name);
+ else
+ role = hosts[session.host].authz.get_user_role(username);
+ end
+ if role then
+ sessionlib.set_role(session, role);
+ end
+ session.log("info", "Authenticated as %s@%s [%s]", username, session.host or "(unknown)", role and role.name or "no role");
return true;
end
@@ -265,6 +319,7 @@ end
return {
new_session = new_session;
retire_session = retire_session;
+ update_session = update_session;
destroy_session = destroy_session;
make_authenticated = make_authenticated;
bind_resource = bind_resource;
diff --git a/core/stanza_router.lua b/core/stanza_router.lua
index b54ea1ab..197c5f64 100644
--- a/core/stanza_router.lua
+++ b/core/stanza_router.lua
@@ -6,14 +6,14 @@
-- COPYING file in the source package for more information.
--
-local log = require "util.logger".init("stanzarouter")
+local log = require "prosody.util.logger".init("stanzarouter")
local hosts = _G.prosody.hosts;
local tostring = tostring;
-local st = require "util.stanza";
-local jid_split = require "util.jid".split;
-local jid_host = require "util.jid".host;
-local jid_prepped_split = require "util.jid".prepped_split;
+local st = require "prosody.util.stanza";
+local jid_split = require "prosody.util.jid".split;
+local jid_host = require "prosody.util.jid".host;
+local jid_prepped_split = require "prosody.util.jid".prepped_split;
local full_sessions = _G.prosody.full_sessions;
local bare_sessions = _G.prosody.bare_sessions;
@@ -127,7 +127,7 @@ function core_process_stanza(origin, stanza)
end
core_post_stanza(origin, stanza, origin.full_jid);
else
- local h = hosts[stanza.attr.to or origin.host or origin.to_host];
+ local h = hosts[stanza.attr.to or origin.host];
if h then
local event;
if xmlns == nil then
@@ -143,7 +143,7 @@ function core_process_stanza(origin, stanza)
if h.events.fire_event(event, {origin = origin, stanza = stanza}) then return; end
end
if host and not hosts[host] then host = nil; end -- COMPAT: workaround for a Pidgin bug which sets 'to' to the SRV result
- handle_unhandled_stanza(host or origin.host or origin.to_host, origin, stanza);
+ handle_unhandled_stanza(host or origin.host, origin, stanza);
end
end
diff --git a/core/statsmanager.lua b/core/statsmanager.lua
index 686fc895..f5b7e356 100644
--- a/core/statsmanager.lua
+++ b/core/statsmanager.lua
@@ -1,10 +1,10 @@
-local config = require "core.configmanager";
-local log = require "util.logger".init("stats");
-local timer = require "util.timer";
+local config = require "prosody.core.configmanager";
+local log = require "prosody.util.logger".init("stats");
+local timer = require "prosody.util.timer";
local fire_event = prosody.events.fire_event;
-local array = require "util.array";
-local timed = require "util.openmetrics".timed;
+local array = require "prosody.util.array";
+local timed = require "prosody.util.openmetrics".timed;
local stats_interval_config = config.get("*", "statistics_interval");
local stats_interval = tonumber(stats_interval_config);
@@ -26,8 +26,8 @@ if stats_interval_config == "manual" then
end
local builtin_providers = {
- internal = "util.statistics";
- statsd = "util.statsd";
+ internal = "prosody.util.statistics";
+ statsd = "prosody.util.statsd";
};
diff --git a/core/storagemanager.lua b/core/storagemanager.lua
index 856acad3..238c7612 100644
--- a/core/storagemanager.lua
+++ b/core/storagemanager.lua
@@ -3,12 +3,12 @@ local type, pairs = type, pairs;
local setmetatable = setmetatable;
local rawset = rawset;
-local config = require "core.configmanager";
-local datamanager = require "util.datamanager";
-local modulemanager = require "core.modulemanager";
-local multitable = require "util.multitable";
-local log = require "util.logger".init("storagemanager");
-local async = require "util.async";
+local config = require "prosody.core.configmanager";
+local datamanager = require "prosody.util.datamanager";
+local modulemanager = require "prosody.core.modulemanager";
+local multitable = require "prosody.util.multitable";
+local log = require "prosody.util.logger".init("storagemanager");
+local async = require "prosody.util.async";
local debug = debug;
local prosody = prosody;
@@ -91,24 +91,8 @@ local function load_driver(host, driver_name)
end
local function get_storage_config(host)
- -- COMPAT w/ unreleased Prosody 0.10 and the once-experimental mod_storage_sql2 in peoples' config files
- local storage_config = config.get(host, "storage");
- local found_sql2;
- if storage_config == "sql2" then
- storage_config, found_sql2 = "sql", true;
- elseif type(storage_config) == "table" then
- for store_name, driver_name in pairs(storage_config) do
- if driver_name == "sql2" then
- storage_config[store_name] = "sql";
- found_sql2 = true;
- end
- end
- end
- if found_sql2 then
- log("error", "The temporary 'sql2' storage module has now been renamed to 'sql', "
- .."please update your config file: https://prosody.im/doc/modules/mod_storage_sql2");
- end
- return storage_config;
+ -- Here used to be some some compat checks
+ return config.get(host, "storage");
end
local function get_driver(host, store)
@@ -203,6 +187,37 @@ local map_shim_mt = {
};
}
+local combined_store_mt = {
+ __index = {
+ -- keyval
+ get = function (self, name)
+ return self.keyval_store:get(name);
+ end;
+ set = function (self, name, data)
+ return self.keyval_store:set(name, data);
+ end;
+ items = function (self)
+ return self.keyval_store:users();
+ end;
+ -- map
+ get_key = function (self, name, key)
+ return self.map_store:get(name, key);
+ end;
+ set_key = function (self, name, key, value)
+ return self.map_store:set(name, key, value);
+ end;
+ set_keys = function (self, name, map)
+ return self.map_store:set_keys(name, map);
+ end;
+ get_key_from_all = function (self, key)
+ return self.map_store:get_all(key);
+ end;
+ delete_key_from_all = function (self, key)
+ return self.map_store:delete_all(key);
+ end;
+ };
+};
+
local open; -- forward declaration
local function create_map_shim(host, store)
@@ -213,7 +228,49 @@ local function create_map_shim(host, store)
}, map_shim_mt);
end
+local function open_combined(host, store)
+ local driver, driver_name = get_driver(host, store);
+
+ -- Open keyval
+ local keyval_store, err = driver:open(store, "keyval");
+ if not keyval_store then
+ if err == "unsupported-store" then
+ log("debug", "Storage driver %s does not support store %s (keyval), falling back to null driver",
+ driver_name, store);
+ keyval_store, err = null_storage_driver, nil;
+ end
+ end
+
+ local map_store;
+ if keyval_store then
+ -- Open map
+ map_store, err = driver:open(store, "map");
+ if not map_store then
+ if err == "unsupported-store" then
+ log("debug", "Storage driver %s does not support store %s (map), falling back to shim",
+ driver_name, store);
+ map_store, err = setmetatable({ keyval_store = keyval_store }, map_shim_mt), nil;
+ end
+ end
+ end
+
+ if not(keyval_store and map_store) then
+ return nil, err;
+ end
+ local combined_store = setmetatable({
+ keyval_store = keyval_store;
+ map_store = map_store;
+ remove = map_store.remove;
+ }, combined_store_mt);
+ local event_data = { host = host, store_name = store, store_type = "keyval+", store = combined_store };
+ hosts[host].events.fire_event("store-opened", event_data);
+ return event_data.store, event_data.store_err;
+end
+
function open(host, store, typ)
+ if typ == "keyval+" then -- TODO: default in some release?
+ return open_combined(host, store);
+ end
local driver, driver_name = get_driver(host, store);
local ret, err = driver:open(store, typ);
if not ret then
diff --git a/core/usermanager.lua b/core/usermanager.lua
index 45f104fa..793e7af6 100644
--- a/core/usermanager.lua
+++ b/core/usermanager.lua
@@ -6,17 +6,13 @@
-- COPYING file in the source package for more information.
--
-local modulemanager = require "core.modulemanager";
-local log = require "util.logger".init("usermanager");
+local modulemanager = require "prosody.core.modulemanager";
+local log = require "prosody.util.logger".init("usermanager");
local type = type;
-local it = require "util.iterators";
-local jid_bare = require "util.jid".bare;
-local jid_split = require "util.jid".split;
-local jid_prep = require "util.jid".prep;
-local config = require "core.configmanager";
-local sasl_new = require "util.sasl".new;
-local storagemanager = require "core.storagemanager";
-local set = require "util.set";
+local jid_split = require "prosody.util.jid".split;
+local config = require "prosody.core.configmanager";
+local sasl_new = require "prosody.util.sasl".new;
+local storagemanager = require "prosody.core.storagemanager";
local prosody = _G.prosody;
local hosts = prosody.hosts;
@@ -25,6 +21,8 @@ local setmetatable = setmetatable;
local default_provider = "internal_hashed";
+local debug = debug;
+
local _ENV = nil;
-- luacheck: std none
@@ -36,26 +34,26 @@ local function new_null_provider()
});
end
-local global_admins_config = config.get("*", "admins");
-if type(global_admins_config) ~= "table" then
- global_admins_config = nil; -- TODO: factor out moduleapi magic config handling and use it here
-end
-local global_admins = set.new(global_admins_config) / jid_prep;
+local fallback_authz_provider = {
+ -- luacheck: ignore 212
+ get_jids_with_role = function (role) end;
-local admin_role = { ["prosody:admin"] = true };
-local global_authz_provider = {
- get_user_roles = function (user) end; --luacheck: ignore 212/user
- get_jid_roles = function (jid)
- if global_admins:contains(jid) then
- return admin_role;
- end
- end;
- get_jids_with_role = function (role)
- if role ~= "prosody:admin" then return {}; end
- return it.to_array(global_admins);
- end;
- set_user_roles = function (user, roles) end; -- luacheck: ignore 212
- set_jid_roles = function (jid, roles) end; -- luacheck: ignore 212
+ get_user_role = function (user) end;
+ set_user_role = function (user, role_name) end;
+
+ get_user_secondary_roles = function (user) end;
+ add_user_secondary_role = function (user, host, role_name) end;
+ remove_user_secondary_role = function (user, host, role_name) end;
+
+ user_can_assume_role = function(user, role_name) end;
+
+ get_jid_role = function (jid) end;
+ set_jid_role = function (jid, role) end;
+
+ get_users_with_role = function (role_name) end;
+ add_default_permission = function (role_name, action, policy) end;
+ get_role_by_name = function (role_name) end;
+ get_all_roles = function () end;
};
local provider_mt = { __index = new_null_provider() };
@@ -66,7 +64,7 @@ local function initialize_host(host)
local authz_provider_name = config.get(host, "authorization") or "internal";
local authz_mod = modulemanager.load(host, "authz_"..authz_provider_name);
- host_session.authz = authz_mod or global_authz_provider;
+ host_session.authz = authz_mod or fallback_authz_provider;
if host_session.type ~= "local" then return; end
@@ -116,6 +114,12 @@ local function set_password(username, password, host, resource)
return ok, err;
end
+local function get_account_info(username, host)
+ local method = hosts[host].users.get_account_info;
+ if not method then return nil, "method not supported"; end
+ return method(username);
+end
+
local function user_exists(username, host)
if hosts[host].sessions[username] then return true; end
return hosts[host].users.user_exists(username);
@@ -132,6 +136,43 @@ local function delete_user(username, host)
return storagemanager.purge(username, host);
end
+local function user_is_enabled(username, host)
+ local method = hosts[host].users.is_enabled;
+ if method then return method(username); end
+
+ -- Fallback
+ local info, err = get_account_info(username, host);
+ if info and info.enabled ~= nil then
+ return info.enabled;
+ elseif err ~= "method not implemented" then
+ -- Storage issues etetc
+ return info, err;
+ end
+
+ -- API unsupported implies users are always enabled
+ return true;
+end
+
+local function enable_user(username, host)
+ local method = hosts[host].users.enable;
+ if not method then return nil, "method not supported"; end
+ local ret, err = method(username);
+ if ret then
+ prosody.events.fire_event("user-enabled", { username = username, host = host });
+ end
+ return ret, err;
+end
+
+local function disable_user(username, host, meta)
+ local method = hosts[host].users.disable;
+ if not method then return nil, "method not supported"; end
+ local ret, err = method(username, meta);
+ if ret then
+ prosody.events.fire_event("user-disabled", { username = username, host = host, meta = meta });
+ end
+ return ret, err;
+end
+
local function users(host)
return hosts[host].users.users();
end
@@ -144,70 +185,143 @@ local function get_provider(host)
return hosts[host].users;
end
-local function get_roles(jid, host)
+local function get_user_role(user, host)
if host and not hosts[host] then return false; end
- if type(jid) ~= "string" then return false; end
+ if type(user) ~= "string" then return false; end
- jid = jid_bare(jid);
- host = host or "*";
+ return hosts[host].authz.get_user_role(user);
+end
- local actor_user, actor_host = jid_split(jid);
- local roles;
+local function set_user_role(user, host, role_name)
+ if host and not hosts[host] then return false; end
+ if type(user) ~= "string" then return false; end
- local authz_provider = (host ~= "*" and hosts[host].authz) or global_authz_provider;
+ local role, err = hosts[host].authz.set_user_role(user, role_name);
+ if role then
+ prosody.events.fire_event("user-role-changed", {
+ username = user, host = host, role = role;
+ });
+ end
+ return role, err;
+end
- if actor_user and actor_host == host then -- Local user
- roles = authz_provider.get_user_roles(actor_user);
- else -- Remote user/JID
- roles = authz_provider.get_jid_roles(jid);
+local function create_user_with_role(username, password, host, role)
+ local ok, err = create_user(username, nil, host);
+ if not ok then return ok, err; end
+
+ local role_ok, role_err = set_user_role(username, host, role);
+ if not role_ok then
+ delete_user(username, host);
+ return nil, "Failed to assign role: "..role_err;
end
- return roles;
+ if password then
+ local pw_ok, pw_err = set_password(username, password, host);
+ if not pw_ok then
+ return nil, "Failed to set password: "..pw_err;
+ end
+
+ local enable_ok, enable_err = enable_user(username, host);
+ if not enable_ok and enable_err ~= "method not implemented" then
+ return enable_ok, "Failed to enable account: "..enable_err;
+ end
+ end
+
+ return true;
end
-local function set_roles(jid, host, roles)
+local function user_can_assume_role(user, host, role_name)
if host and not hosts[host] then return false; end
- if type(jid) ~= "string" then return false; end
+ if type(user) ~= "string" then return false; end
- jid = jid_bare(jid);
- host = host or "*";
+ return hosts[host].authz.user_can_assume_role(user, role_name);
+end
- local actor_user, actor_host = jid_split(jid);
+local function add_user_secondary_role(user, host, role_name)
+ if host and not hosts[host] then return false; end
+ if type(user) ~= "string" then return false; end
- local authz_provider = (host ~= "*" and hosts[host].authz) or global_authz_provider;
- if actor_user and actor_host == host then -- Local user
- local ok, err = authz_provider.set_user_roles(actor_user, roles);
- if ok then
- prosody.events.fire_event("user-roles-changed", {
- username = actor_user, host = actor_host
- });
- end
- return ok, err;
- else -- Remote entity
- return authz_provider.set_jid_roles(jid, roles)
+ local role, err = hosts[host].authz.add_user_secondary_role(user, role_name);
+ if role then
+ prosody.events.fire_event("user-role-added", {
+ username = user, host = host, role = role;
+ });
end
+ return role, err;
end
+local function remove_user_secondary_role(user, host, role_name)
+ if host and not hosts[host] then return false; end
+ if type(user) ~= "string" then return false; end
+
+ local ok, err = hosts[host].authz.remove_user_secondary_role(user, role_name);
+ if ok then
+ prosody.events.fire_event("user-role-removed", {
+ username = user, host = host, role_name = role_name;
+ });
+ end
+ return ok, err;
+end
+
+local function get_user_secondary_roles(user, host)
+ if host and not hosts[host] then return false; end
+ if type(user) ~= "string" then return false; end
+
+ return hosts[host].authz.get_user_secondary_roles(user);
+end
+
+local function get_jid_role(jid, host)
+ local jid_node, jid_host = jid_split(jid);
+ if host == jid_host and jid_node then
+ return hosts[host].authz.get_user_role(jid_node);
+ end
+ return hosts[host].authz.get_jid_role(jid);
+end
+
+local function set_jid_role(jid, host, role_name)
+ local _, jid_host = jid_split(jid);
+ if host == jid_host then
+ return nil, "unexpected-local-jid";
+ end
+ return hosts[host].authz.set_jid_role(jid, role_name)
+end
+
+local strict_deprecate_is_admin;
+local legacy_admin_roles = { ["prosody:admin"] = true, ["prosody:operator"] = true };
local function is_admin(jid, host)
- local roles = get_roles(jid, host);
- return roles and roles["prosody:admin"];
+ if strict_deprecate_is_admin == nil then
+ strict_deprecate_is_admin = (config.get("*", "strict_deprecate_is_admin") == true);
+ end
+ if strict_deprecate_is_admin then
+ log("error", "Attempt to use deprecated is_admin() API: %s", debug.traceback());
+ return false;
+ end
+ log("warn", "Usage of legacy is_admin() API, which will be disabled in a future build: %s", debug.traceback());
+ log("warn", "See https://prosody.im/doc/developers/permissions about the new permissions API");
+ return legacy_admin_roles[get_jid_role(jid, host)] or false;
end
local function get_users_with_role(role, host)
if not hosts[host] then return false; end
if type(role) ~= "string" then return false; end
-
return hosts[host].authz.get_users_with_role(role);
end
local function get_jids_with_role(role, host)
if host and not hosts[host] then return false; end
if type(role) ~= "string" then return false; end
+ return hosts[host].authz.get_jids_with_role(role);
+end
- host = host or "*";
+local function get_role_by_name(role_name, host)
+ if host and not hosts[host] then return false; end
+ if type(role_name) ~= "string" then return false; end
+ return hosts[host].authz.get_role_by_name(role_name);
+end
- local authz_provider = (host ~= "*" and hosts[host].authz) or global_authz_provider;
- return authz_provider.get_jids_with_role(role);
+local function get_all_roles(host)
+ if host and not hosts[host] then return false; end
+ return hosts[host].authz.get_all_roles();
end
return {
@@ -216,15 +330,30 @@ return {
test_password = test_password;
get_password = get_password;
set_password = set_password;
+ get_account_info = get_account_info;
user_exists = user_exists;
create_user = create_user;
+ create_user_with_role = create_user_with_role;
delete_user = delete_user;
+ user_is_enabled = user_is_enabled;
+ enable_user = enable_user;
+ disable_user = disable_user;
users = users;
get_sasl_handler = get_sasl_handler;
get_provider = get_provider;
- get_roles = get_roles;
- set_roles = set_roles;
- is_admin = is_admin;
+ get_user_role = get_user_role;
+ set_user_role = set_user_role;
+ user_can_assume_role = user_can_assume_role;
+ add_user_secondary_role = add_user_secondary_role;
+ remove_user_secondary_role = remove_user_secondary_role;
+ get_user_secondary_roles = get_user_secondary_roles;
get_users_with_role = get_users_with_role;
+ get_jid_role = get_jid_role;
+ set_jid_role = set_jid_role;
get_jids_with_role = get_jids_with_role;
+ get_role_by_name = get_role_by_name;
+ get_all_roles = get_all_roles;
+
+ -- Deprecated
+ is_admin = is_admin;
};