diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/certmanager.lua | 118 | ||||
-rw-r--r-- | core/configmanager.lua | 113 | ||||
-rw-r--r-- | core/features.lua | 25 | ||||
-rw-r--r-- | core/hostmanager.lua | 12 | ||||
-rw-r--r-- | core/loggingmanager.lua | 12 | ||||
-rw-r--r-- | core/moduleapi.lua | 250 | ||||
-rw-r--r-- | core/modulemanager.lua | 24 | ||||
-rw-r--r-- | core/portmanager.lua | 52 | ||||
-rw-r--r-- | core/rostermanager.lua | 12 | ||||
-rw-r--r-- | core/s2smanager.lua | 4 | ||||
-rw-r--r-- | core/sessionmanager.lua | 81 | ||||
-rw-r--r-- | core/stanza_router.lua | 14 | ||||
-rw-r--r-- | core/statsmanager.lua | 14 | ||||
-rw-r--r-- | core/storagemanager.lua | 105 | ||||
-rw-r--r-- | core/usermanager.lua | 263 |
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; }; |