diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/certmanager.lua | 155 | ||||
-rw-r--r-- | core/configmanager.lua | 63 | ||||
-rw-r--r-- | core/hostmanager.lua | 20 | ||||
-rw-r--r-- | core/loggingmanager.lua | 48 | ||||
-rw-r--r-- | core/moduleapi.lua | 67 | ||||
-rw-r--r-- | core/modulemanager.lua | 30 | ||||
-rw-r--r-- | core/portmanager.lua | 47 | ||||
-rw-r--r-- | core/rostermanager.lua | 48 | ||||
-rw-r--r-- | core/s2smanager.lua | 8 | ||||
-rw-r--r-- | core/sessionmanager.lua | 43 | ||||
-rw-r--r-- | core/stanza_router.lua | 6 | ||||
-rw-r--r-- | core/statsmanager.lua | 69 | ||||
-rw-r--r-- | core/storagemanager.lua | 6 | ||||
-rw-r--r-- | core/usermanager.lua | 13 |
14 files changed, 370 insertions, 253 deletions
diff --git a/core/certmanager.lua b/core/certmanager.lua index 624bd841..b40c13c1 100644 --- a/core/certmanager.lua +++ b/core/certmanager.lua @@ -1,97 +1,127 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- +local softreq = require"util.dependencies".softreq; +local ssl = softreq"ssl"; +if not ssl then + return { + create_context = function () + return nil, "LuaSec (required for encryption) was not found"; + end; + reload_ssl_config = function () end; + } +end + local configmanager = require "core.configmanager"; local log = require "util.logger".init("certmanager"); -local ssl = ssl; -local ssl_newcontext = ssl and ssl.newcontext; +local ssl_context = ssl.context or softreq"ssl.context"; +local ssl_x509 = ssl.x509 or softreq"ssl.x509"; +local ssl_newcontext = ssl.newcontext; +local new_config = require"util.sslconfig".new; local tostring = tostring; +local pairs = pairs; local type = type; local io_open = io.open; +local select = select; local prosody = prosody; -local resolve_path = configmanager.resolve_relative_path; +local resolve_path = require"util.paths".resolve_relative_path; local config_path = prosody.paths.config; -local luasec_has_noticket, luasec_has_verifyext, luasec_has_no_compression; -if ssl then - local luasec_major, luasec_minor = ssl._VERSION:match("^(%d+)%.(%d+)"); - luasec_has_noticket = tonumber(luasec_major)>0 or tonumber(luasec_minor)>=4; - luasec_has_verifyext = tonumber(luasec_major)>0 or tonumber(luasec_minor)>=5; - luasec_has_no_compression = tonumber(luasec_major)>0 or tonumber(luasec_minor)>=5; -end +local luasec_major, luasec_minor = ssl._VERSION:match("^(%d+)%.(%d+)"); +local luasec_version = luasec_major * 100 + luasec_minor; +local luasec_has = { + -- TODO If LuaSec ever starts exposing these things itself, use that instead + cipher_server_preference = luasec_version >= 2; + no_ticket = luasec_version >= 4; + no_compression = luasec_version >= 5; + single_dh_use = luasec_version >= 2; + single_ecdh_use = luasec_version >= 2; +}; module "certmanager" -- Global SSL options if not overridden per-host -local default_ssl_config = configmanager.get("*", "ssl"); -local default_capath = "/etc/ssl/certs"; -local default_verify = (ssl and ssl.x509 and { "peer", "client_once", }) or "none"; -local default_options = { "no_sslv2", "no_sslv3", "cipher_server_preference", luasec_has_noticket and "no_ticket" or nil }; -local default_verifyext = { "lsec_continue", "lsec_ignore_purpose" }; - -if ssl and not luasec_has_verifyext and ssl.x509 then +local global_ssl_config = configmanager.get("*", "ssl"); + +-- Built-in defaults +local core_defaults = { + capath = "/etc/ssl/certs"; + depth = 9; + protocol = "tlsv1+"; + verify = (ssl_x509 and { "peer", "client_once", }) or "none"; + options = { + cipher_server_preference = luasec_has.cipher_server_preference; + no_ticket = luasec_has.no_ticket; + no_compression = luasec_has.no_compression and configmanager.get("*", "ssl_compression") ~= true; + single_dh_use = luasec_has.single_dh_use; + single_ecdh_use = luasec_has.single_ecdh_use; + }; + verifyext = { "lsec_continue", "lsec_ignore_purpose" }; + curve = "secp384r1"; + ciphers = "HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL"; +} +local path_options = { -- These we pass through resolve_path() + key = true, certificate = true, cafile = true, capath = true, dhparam = true +} + +if luasec_version < 5 and ssl_x509 then -- COMPAT mw/luasec-hg - for i=1,#default_verifyext do -- Remove lsec_ prefix - default_verify[#default_verify+1] = default_verifyext[i]:sub(6); + for i=1,#core_defaults.verifyext do -- Remove lsec_ prefix + core_defaults.verify[#core_defaults.verify+1] = core_defaults.verifyext[i]:sub(6); end end -if luasec_has_no_compression and configmanager.get("*", "ssl_compression") ~= true then - default_options[#default_options+1] = "no_compression"; -end -if luasec_has_no_compression then -- Has no_compression? Then it has these too... - default_options[#default_options+1] = "single_dh_use"; - default_options[#default_options+1] = "single_ecdh_use"; -end +function create_context(host, mode, ...) + local cfg = new_config(); + cfg:apply(core_defaults); + cfg:apply(global_ssl_config); + cfg:apply({ + mode = mode, + -- We can't read the password interactively when daemonized + password = function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end; + }); + + for i = select('#', ...), 1, -1 do + cfg:apply(select(i, ...)); + end + local user_ssl_config = cfg:final(); -function create_context(host, mode, user_ssl_config) - user_ssl_config = user_ssl_config or default_ssl_config; - - if not ssl then return nil, "LuaSec (required for encryption) was not found"; end - if not user_ssl_config then return nil, "No SSL/TLS configuration present for "..host; end - - local ssl_config = { - mode = mode; - protocol = user_ssl_config.protocol or "sslv23"; - key = resolve_path(config_path, user_ssl_config.key); - password = user_ssl_config.password or function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end; - certificate = resolve_path(config_path, user_ssl_config.certificate); - capath = resolve_path(config_path, user_ssl_config.capath or default_capath); - cafile = resolve_path(config_path, user_ssl_config.cafile); - verify = user_ssl_config.verify or default_verify; - verifyext = user_ssl_config.verifyext or default_verifyext; - options = user_ssl_config.options or default_options; - depth = user_ssl_config.depth; - curve = user_ssl_config.curve or "secp384r1"; - ciphers = user_ssl_config.ciphers or "HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL"; - dhparam = user_ssl_config.dhparam; - }; + if mode == "server" then + if not user_ssl_config.key then return nil, "No key present in SSL/TLS configuration for "..host; end + if not user_ssl_config.certificate then return nil, "No certificate 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]); + 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(ssl_config.dhparam) == "string" then - local f, err = io_open(resolve_path(config_path, ssl_config.dhparam)); + 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(); - ssl_config.dhparam = function() return dhparam; end + user_ssl_config.dhparam = function() return dhparam; end end - local ctx, err = ssl_newcontext(ssl_config); + local ctx, err = ssl_newcontext(user_ssl_config); - -- COMPAT: LuaSec 0.4.1 ignores the cipher list from the config, so we have to take - -- care of it ourselves... - if ctx and ssl_config.ciphers then + -- 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, ssl_config.ciphers); + success, err = ssl_context.setcipher(ctx, user_ssl_config.ciphers); if not success then ctx = nil; end end @@ -100,9 +130,9 @@ function create_context(host, mode, user_ssl_config) local file = err:match("^error loading (.-) %("); if file then if file == "private key" then - file = ssl_config.key or "your private key"; + file = user_ssl_config.key or "your private key"; elseif file == "certificate" then - file = ssl_config.certificate or "your certificate file"; + file = user_ssl_config.certificate or "your certificate file"; end local reason = err:match("%((.+)%)$") or "some reason"; if reason == "Permission denied" then @@ -121,11 +151,14 @@ function create_context(host, mode, user_ssl_config) log("error", "SSL/TLS: Error initialising for %s: %s", host, err); end end - return ctx, err; + return ctx, err, user_ssl_config; end function reload_ssl_config() - default_ssl_config = configmanager.get("*", "ssl"); + global_ssl_config = configmanager.get("*", "ssl"); + if luasec_has.no_compression then + core_defaults.options.no_compression = configmanager.get("*", "ssl_compression") ~= true; + end end prosody.events.add_handler("config-reloaded", reload_ssl_config); diff --git a/core/configmanager.lua b/core/configmanager.lua index c8aa7b9a..48f039ea 100644 --- a/core/configmanager.lua +++ b/core/configmanager.lua @@ -1,7 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -15,6 +15,8 @@ local fire_event = prosody and prosody.events.fire_event or function () end; 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 path_sep = package.config:sub(1,1); local have_encodings, encodings = pcall(require, "util.encodings"); @@ -22,6 +24,8 @@ local nameprep = have_encodings and encodings.stringprep.nameprep or function (h module "configmanager" +_M.resolve_relative_path = resolve_relative_path; -- COMPAT + local parsers = {}; local config_mt = { __index = function (t, k) return rawget(t, "*"); end}; @@ -69,41 +73,6 @@ function _M.set(host, key, value, _oldvalue) return set(config, host, key, value); end --- Helper function to resolve relative paths (needed by config) -do - function resolve_relative_path(parent_path, path) - if path then - -- Some normalization - parent_path = parent_path:gsub("%"..path_sep.."+$", ""); - path = path:gsub("^%.%"..path_sep.."+", ""); - - local is_relative; - if path_sep == "/" and path:sub(1,1) ~= "/" then - is_relative = true; - elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and (path:sub(2,3) ~= ":\\" and path:sub(2,3) ~= ":/")) then - is_relative = true; - end - if is_relative then - return parent_path..path_sep..path; - end - end - return path; - end -end - --- Helper function to convert a glob to a Lua pattern -local function glob_to_pattern(glob) - return "^"..glob:gsub("[%p*?]", function (c) - if c == "*" then - return ".*"; - elseif c == "?" then - return "."; - else - return "%"..c; - end - end).."$"; -end - function load(filename, format) format = format or filename:match("%w+$"); @@ -170,7 +139,7 @@ do set(config, env.__currenthost or "*", k, v); end }); - + rawset(env, "__currenthost", "*") -- Default is global function env.VirtualHost(name) name = nameprep(name); @@ -189,7 +158,7 @@ do end; end env.Host, env.host = env.VirtualHost, env.VirtualHost; - + function env.Component(name) name = nameprep(name); if rawget(config, name) and rawget(config[name], "defined") and not rawget(config[name], "component_module") then @@ -206,7 +175,7 @@ do set(config, name or "*", option_name, option_value); end end - + return function (module) if type(module) == "string" then set(config, name, "component_module", module); @@ -216,7 +185,7 @@ do end end env.component = env.Component; - + function env.Include(file) if file:match("[*?]") then local lfs = deps.softreq "lfs"; @@ -249,26 +218,26 @@ do end end env.include = env.Include; - + function env.RunScript(file) return dofile(resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file)); end - + local chunk, err = envload(data, "@"..config_file, env); - + if not chunk then return nil, err; end - + local ok, err = pcall(chunk); - + if not ok then return nil, err; end - + return true; end - + end return _M; diff --git a/core/hostmanager.lua b/core/hostmanager.lua index 06ba72a1..d7329cd2 100644 --- a/core/hostmanager.lua +++ b/core/hostmanager.lua @@ -1,7 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -13,7 +13,6 @@ local disco_items = require "util.multitable".new(); local NULL = {}; local jid_split = require "util.jid".split; -local uuid_gen = require "util.uuid".generate; local log = require "util.logger".init("hostmanager"); @@ -35,7 +34,7 @@ local hosts_loaded_once; local function load_enabled_hosts(config) local defined_hosts = config or configmanager.getconfig(); local activated_any_host; - + for host, host_config in pairs(defined_hosts) do if host ~= "*" and host_config.enabled ~= false then if not host_config.component_module then @@ -44,11 +43,11 @@ local function load_enabled_hosts(config) activate(host, host_config); end end - + if not activated_any_host then log("error", "No active VirtualHost entries in the config file. This may cause unexpected behaviour as no modules will be loaded."); end - + prosody_events.fire_event("hosts-activated", defined_hosts); hosts_loaded_once = true; end @@ -56,8 +55,8 @@ end prosody_events.add_handler("server-starting", load_enabled_hosts); local function host_send(stanza) - local name, type = stanza.name, stanza.attr.type; - if type == "error" or (name == "iq" and type == "result") then + local name, stanza_type = stanza.name, stanza.attr.type; + if stanza_type == "error" or (name == "iq" and stanza_type == "result") then local dest_host_name = select(2, jid_split(stanza.attr.to)); local dest_host = hosts[dest_host_name] or { type = "unknown" }; log("warn", "Unhandled response sent to %s host %s: %s", dest_host.type, dest_host_name, tostring(stanza)); @@ -74,7 +73,6 @@ function activate(host, host_config) host = host; s2sout = {}; events = events_new(); - dialback_secret = configmanager.get(host, "dialback_secret") or uuid_gen(); send = host_send; modules = {}; }; @@ -93,7 +91,7 @@ function activate(host, host_config) log("warn", "%s: Option '%s' has no effect for virtual hosts - put it in the server-wide section instead", host, option_name); end end - + log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host); prosody_events.fire_event("host-activated", host); return true; @@ -104,11 +102,11 @@ function deactivate(host, reason) if not host_session then return nil, "The host "..tostring(host).." is not activated"; end log("info", "Deactivating host: %s", host); prosody_events.fire_event("host-deactivating", { host = host, host_session = host_session, reason = reason }); - + if type(reason) ~= "table" then reason = { condition = "host-gone", text = tostring(reason or "This server has stopped serving "..host) }; end - + -- Disconnect local users, s2s connections -- TODO: These should move to mod_c2s and mod_s2s (how do they know they're being unloaded and not reloaded?) if host_session.sessions then diff --git a/core/loggingmanager.lua b/core/loggingmanager.lua index c69dede8..f348dbdf 100644 --- a/core/loggingmanager.lua +++ b/core/loggingmanager.lua @@ -1,7 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -45,16 +45,16 @@ local logging_levels = { "debug", "info", "warn", "error" } -- This function is called automatically when a new sink type is added [see apply_sink_rules()] local function add_rule(sink_config) local sink_maker = log_sink_types[sink_config.to]; - if sink_maker then - -- Create sink - local sink = sink_maker(sink_config); - - -- Set sink for all chosen levels - for level in pairs(get_levels(sink_config.levels or logging_levels)) do - logger.add_level_sink(level, sink); - end - else - -- No such sink type + if not sink_maker then + return; -- No such sink type + end + + -- Create sink + local sink = sink_maker(sink_config); + + -- Set sink for all chosen levels + for level in pairs(get_levels(sink_config.levels or logging_levels)) do + logger.add_level_sink(level, sink); end end @@ -63,7 +63,7 @@ end -- the log_sink_types table. function apply_sink_rules(sink_type) if type(logging_config) == "table" then - + for _, level in ipairs(logging_levels) do if type(logging_config[level]) == "string" then local value = logging_config[level]; @@ -82,7 +82,7 @@ function apply_sink_rules(sink_type) end end end - + for _, sink_config in ipairs(logging_config) do if (type(sink_config) == "table" and sink_config.to == sink_type) then add_rule(sink_config); @@ -128,7 +128,7 @@ function get_levels(criteria, set) end end end - + for _, level in ipairs(criteria) do set[level] = true; end @@ -138,12 +138,12 @@ end -- Initialize config, etc. -- function reload_logging() local old_sink_types = {}; - + for name, sink_maker in pairs(log_sink_types) do old_sink_types[name] = sink_maker; log_sink_types[name] = nil; end - + logger.reset(); local debug_mode = config.get("*", "debug"); @@ -155,12 +155,12 @@ function reload_logging() default_timestamp = "%b %d %H:%M:%S"; logging_config = config.get("*", "log") or default_logging; - - + + for name, sink_maker in pairs(old_sink_types) do log_sink_types[name] = sink_maker; end - + prosody.events.fire_event("logging-reloaded"); end @@ -179,11 +179,11 @@ local sourcewidth = 20; function log_sink_types.stdout(config) local timestamps = config.timestamps; - + if timestamps == true then timestamps = default_timestamp; -- Default format end - + return function (name, level, message, ...) sourcewidth = math_max(#name+2, sourcewidth); local namelen = #name; @@ -200,7 +200,7 @@ end do local do_pretty_printing = true; - + local logstyles = {}; if do_pretty_printing then logstyles["info"] = getstyle("bold"); @@ -212,7 +212,7 @@ do if not do_pretty_printing then return log_sink_types.stdout(config); end - + local timestamps = config.timestamps; if timestamps == true then @@ -222,7 +222,7 @@ do return function (name, level, message, ...) sourcewidth = math_max(#name+2, sourcewidth); local namelen = #name; - + if timestamps then io_write(os_date(timestamps), " "); end diff --git a/core/moduleapi.lua b/core/moduleapi.lua index ed75669b..f3326295 100644 --- a/core/moduleapi.lua +++ b/core/moduleapi.lua @@ -1,23 +1,26 @@ -- Prosody IM -- Copyright (C) 2008-2012 Matthew Wild -- Copyright (C) 2008-2012 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- local config = require "core.configmanager"; -local modulemanager = require "modulemanager"; -- This is necessary to avoid require loops +local modulemanager; -- This gets set from modulemanager local array = require "util.array"; local set = require "util.set"; local logger = require "util.logger"; local pluginloader = require "util.pluginloader"; local timer = require "util.timer"; +local resolve_relative_path = require"util.paths".resolve_relative_path; +local measure = require "core.statsmanager".measure; local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; local error, setmetatable, type = error, setmetatable, type; local ipairs, pairs, select, unpack = ipairs, pairs, select, unpack; local tonumber, tostring = tonumber, tostring; +local require = require; local prosody = prosody; local hosts = prosody.hosts; @@ -44,7 +47,7 @@ function api:get_host() end function api:get_host_type() - return self.host ~= "*" and hosts[self.host].type or nil; + return (self.host == "*" and "global") or hosts[self.host].type or "local"; end function api:set_global() @@ -74,7 +77,7 @@ end function api:has_identity(category, type, name) for _, id in ipairs(self:get_host_items("identity")) do if id.category == category and id.type == type and id.name == name then - return true; + return true; end end return false; @@ -113,6 +116,22 @@ function api:hook_tag(xmlns, name, handler, priority) end api.hook_stanza = api.hook_tag; -- COMPAT w/pre-0.9 +function api:unhook(event, handler) + return self:unhook_object_event((hosts[self.host] or prosody).events, event, handler); +end + +function api:wrap_object_event(events_object, event, handler) + return self:hook_object_event(assert(events_object.wrappers, "no wrappers"), event, handler); +end + +function api:wrap_event(event, handler) + return self:wrap_object_event((hosts[self.host] or prosody).events, event, handler); +end + +function api:wrap_global(event, handler) + return self:hook_object_event(prosody.events, event, handler, priority); +end + function api:require(lib) local f, n = pluginloader.load_code(self.name, lib..".lib.lua", self.environment); if not f then @@ -252,21 +271,21 @@ function api:get_option_array(name, ...) if value == nil then return nil; end - + if type(value) ~= "table" then return array{ value }; -- Assume any non-list is a single-item list end - + return array():append(value); -- Clone end function api:get_option_set(name, ...) local value = self:get_option_array(name, ...); - + if value == nil then return nil; end - + return set.new(value); end @@ -356,12 +375,40 @@ function api:get_directory() end function api:load_resource(path, mode) - path = config.resolve_relative_path(self:get_directory(), path); + path = resolve_relative_path(self:get_directory(), path); return io.open(path, mode); end function api:open_store(name, type) - return storagemanager.open(self.host, name or self.name, type); + return require"core.storagemanager".open(self.host, name or self.name, type); +end + +function api:measure(name, type) + return measure(type, "/"..self.host.."/mod_"..self.name.."/"..name); +end + +function api:measure_object_event(events_object, event_name, stat_name) + local m = self:measure(stat_name or event_name, "duration"); + local function handler(handlers, event_name, event_data) + local finished = m(); + local ret = handlers(event_name, event_data); + finished(); + return ret; + end + return self:hook_object_event(events_object, event_name, handler); +end + +function api:measure_event(event_name, stat_name) + return self:hook_object_event((hosts[self.host] or prosody).events.wrappers, event_name, handler); +end + +function api:measure_global_event(event_name, stat_name) + return self:hook_object_event(prosody.events.wrappers, event_name, handler); +end + +function api.init(mm) + modulemanager = mm; + return api; end return api; diff --git a/core/modulemanager.lua b/core/modulemanager.lua index 4df95069..92372ac3 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -1,7 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -29,7 +29,7 @@ pcall = function(f, ...) return xpcall(function() return f(unpack(params, 1, n)) end, function(e) return tostring(e).."\n"..debug_traceback(); end); end -local autoload_modules = {"presence", "message", "iq", "offline", "c2s", "s2s"}; +local autoload_modules = {prosody.platform, "presence", "message", "iq", "offline", "c2s", "s2s", "s2s_auth_certs"}; local component_inheritable_modules = {"tls", "saslauth", "dialback", "iq", "s2s"}; -- We need this to let modules access the real global namespace @@ -37,7 +37,7 @@ local _G = _G; module "modulemanager" -local api = _G.require "core.moduleapi"; -- Module API container +local api = _G.require "core.moduleapi".init(_M); -- Module API container -- [host] = { [module] = module_env } local modulemap = { ["*"] = {} }; @@ -45,28 +45,28 @@ local modulemap = { ["*"] = {} }; -- Load modules when a host is activated function load_modules_for_host(host) local component = config.get(host, "component_module"); - + local global_modules_enabled = config.get("*", "modules_enabled"); local global_modules_disabled = config.get("*", "modules_disabled"); local host_modules_enabled = config.get(host, "modules_enabled"); local host_modules_disabled = config.get(host, "modules_disabled"); - + if host_modules_enabled == global_modules_enabled then host_modules_enabled = nil; end if host_modules_disabled == global_modules_disabled then host_modules_disabled = nil; end - + local global_modules = set.new(autoload_modules) + set.new(global_modules_enabled) - set.new(global_modules_disabled); if component then global_modules = set.intersection(set.new(component_inheritable_modules), global_modules); end local modules = (global_modules + set.new(host_modules_enabled)) - set.new(host_modules_disabled); - + -- COMPAT w/ pre 0.8 if modules:contains("console") then log("error", "The mod_console plugin has been renamed to mod_admin_telnet. Please update your config."); modules:remove("console"); modules:add("admin_telnet"); end - + if component then load(host, component); end @@ -84,18 +84,18 @@ end); local function do_unload_module(host, name) local mod = get_module(host, name); if not mod then return nil, "module-not-loaded"; end - + if module_has_method(mod, "unload") then local ok, err = call_module_method(mod, "unload"); if (not ok) and err then log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err); end end - + for object, event, handler in mod.module.event_handlers:iter(nil, nil, nil) do object.remove_handler(event, handler); end - + if mod.module.items then -- remove items local events = (host == "*" and prosody.events) or hosts[host].events; for key,t in pairs(mod.module.items) do @@ -117,11 +117,11 @@ local function do_load_module(host, module_name, state) elseif not hosts[host] and host ~= "*"then return nil, "unknown-host"; end - + if not modulemap[host] then modulemap[host] = hosts[host].modules; end - + if modulemap[host][module_name] then log("debug", "%s is already loaded for %s, so not loading again", module_name, host); return nil, "module-already-loaded"; @@ -147,7 +147,7 @@ local function do_load_module(host, module_name, state) end return nil, "global-module-already-loaded"; end - + local _log = logger.init(host..":"..module_name); @@ -158,7 +158,7 @@ local function do_load_module(host, module_name, state) local pluginenv = setmetatable({ module = api_instance }, { __index = _G }); api_instance.environment = pluginenv; - + local mod, err = pluginloader.load_code(module_name, nil, pluginenv); if not mod then log("error", "Unable to load module '%s': %s", module_name or "nil", err or "nil"); diff --git a/core/portmanager.lua b/core/portmanager.lua index 421d7fc6..eab2412a 100644 --- a/core/portmanager.lua +++ b/core/portmanager.lua @@ -9,7 +9,7 @@ local set = require "util.set"; local table = table; local setmetatable, rawset, rawget = setmetatable, rawset, rawget; -local type, tonumber, tostring, ipairs, pairs = type, tonumber, tostring, ipairs, pairs; +local type, tonumber, tostring, ipairs = type, tonumber, tostring, ipairs; local prosody = prosody; local fire_event = prosody.events.fire_event; @@ -72,16 +72,6 @@ prosody.events.add_handler("item-removed/net-provider", function (event) unregister_service(item.name, item); end); -local function duplicate_ssl_config(ssl_config) - local ssl_config = type(ssl_config) == "table" and ssl_config or {}; - - local _config = {}; - for k, v in pairs(ssl_config) do - _config[k] = v; - end - return _config; -end - --- Public API function activate(service_name) @@ -89,7 +79,7 @@ function activate(service_name) if not service_info then return nil, "Unknown service: "..service_name; end - + local listener = service_info.listener; local config_prefix = (service_info.config_prefix or service_name).."_"; @@ -105,7 +95,7 @@ function activate(service_name) or listener.default_interface -- COMPAT w/pre0.9 or default_interfaces bind_interfaces = set.new(type(bind_interfaces)~="table" and {bind_interfaces} or bind_interfaces); - + local bind_ports = config.get("*", config_prefix.."ports") or service_info.default_ports or {service_info.default_port @@ -115,7 +105,7 @@ function activate(service_name) local mode, ssl = listener.default_mode or default_mode; local hooked_ports = {}; - + for interface in bind_interfaces do for port in bind_ports do local port_number = tonumber(port); @@ -127,24 +117,15 @@ function activate(service_name) local err; -- Create SSL context for this service/port if service_info.encryption == "ssl" then - local ssl_config = duplicate_ssl_config((config.get("*", config_prefix.."ssl") and config.get("*", config_prefix.."ssl")[interface]) - or (config.get("*", config_prefix.."ssl") and config.get("*", config_prefix.."ssl")[port]) - or config.get("*", config_prefix.."ssl") - or (config.get("*", "ssl") and config.get("*", "ssl")[interface]) - or (config.get("*", "ssl") and config.get("*", "ssl")[port]) - or config.get("*", "ssl")); - -- add default entries for, or override ssl configuration - if ssl_config and service_info.ssl_config then - for key, value in pairs(service_info.ssl_config) do - if not service_info.ssl_config_override and not ssl_config[key] then - ssl_config[key] = value; - elseif service_info.ssl_config_override then - ssl_config[key] = value; - end - end - end - - ssl, err = certmanager.create_context(service_info.name.." port "..port, "server", ssl_config); + local global_ssl_config = config.get("*", "ssl") or {}; + local prefix_ssl_config = config.get("*", config_prefix.."ssl") or global_ssl_config; + ssl, err = certmanager.create_context(service_info.name.." port "..port, "server", + service_info.ssl_config or {}, + prefix_ssl_config[interface], + prefix_ssl_config[port], + prefix_ssl_config, + global_ssl_config[interface], + global_ssl_config[port]); if not ssl then log("error", "Error binding encrypted port for %s: %s", service_info.name, error_to_friendly_message(service_name, port_number, err) or "unknown error"); end @@ -190,7 +171,7 @@ function register_service(service_name, service_info) log("error", "Failed to activate service '%s': %s", service_name, err or "unknown error"); end end - + fire_event("service-added", { name = service_name, service = service_info }); return true; end diff --git a/core/rostermanager.lua b/core/rostermanager.lua index 5e06e3f7..612a349f 100644 --- a/core/rostermanager.lua +++ b/core/rostermanager.lua @@ -1,7 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -13,9 +13,10 @@ local log = require "util.logger".init("rostermanager"); local pairs = pairs; local tostring = tostring; +local type = type; local hosts = hosts; -local bare_sessions = bare_sessions; +local bare_sessions = prosody.bare_sessions; local datamanager = require "util.datamanager" local um_user_exists = require "core.usermanager".user_exists; @@ -54,7 +55,7 @@ function remove_from_roster(session, jid) end function roster_push(username, host, jid) - local roster = jid and jid ~= "pending" and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster; + local roster = jid and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster; if roster then local item = hosts[host].sessions[username].roster[jid]; local stanza = st.iq({type="set"}); @@ -79,6 +80,21 @@ function roster_push(username, host, jid) end end +local function roster_metadata(roster, err) + local metadata = roster[false]; + if not metadata then + metadata = { broken = err or nil }; + roster[false] = metadata; + end + if roster.pending and type(roster.pending.subscription) ~= "string" then + metadata.pending = roster.pending; + roster.pending = nil; + elseif not metadata.pending then + metadata.pending = {}; + end + return metadata; +end + function load_roster(username, host) local jid = username.."@"..host; log("debug", "load_roster: asked for: %s", jid); @@ -94,13 +110,13 @@ function load_roster(username, host) local data, err = datamanager.load(username, host, "roster"); roster = data or {}; if user then user.roster = roster; end - if not roster[false] then roster[false] = { broken = err or nil }; end + roster_metadata(roster, err); if roster[jid] then roster[jid] = nil; log("warn", "roster for %s has a self-contact", jid); end if not err then - hosts[host].events.fire_event("roster-load", username, host, roster); + hosts[host].events.fire_event("roster-load", { username = username, host = host, roster = roster }); end return roster, err; end @@ -120,15 +136,11 @@ function save_roster(username, host, roster) --end end if roster then - local metadata = roster[false]; - if not metadata then - metadata = {}; - roster[false] = metadata; - end + local metadata = roster_metadata(roster); if metadata.version ~= true then metadata.version = (metadata.version or 0) + 1; end - if roster[false].broken then return nil, "Not saving broken roster" end + if metadata.broken then return nil, "Not saving broken roster" end return datamanager.store(username, host, "roster", roster); end log("warn", "save_roster: user had no roster to save"); @@ -176,7 +188,7 @@ function process_inbound_unsubscribe(username, host, jid) local item = roster[jid]; local changed = nil; if is_contact_pending_in(username, host, jid) then - roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty? + roster[false].pending[jid] = nil; changed = true; end if item then @@ -213,16 +225,15 @@ end function is_contact_pending_in(username, host, jid) local roster = load_roster(username, host); - return roster.pending and roster.pending[jid]; + return roster[false].pending[jid]; end -function set_contact_pending_in(username, host, jid, pending) +function set_contact_pending_in(username, host, jid) local roster = load_roster(username, host); local item = roster[jid]; if item and (item.subscription == "from" or item.subscription == "both") then return; -- false end - if not roster.pending then roster.pending = {}; end - roster.pending[jid] = true; + roster[false].pending[jid] = true; return save_roster(username, host, roster); end function is_contact_pending_out(username, host, jid) @@ -272,8 +283,7 @@ function subscribed(username, host, jid) else -- subscription == to item.subscription = "both"; end - roster.pending[jid] = nil; - -- TODO maybe remove roster.pending if empty + roster[false].pending[jid] = nil; return save_roster(username, host, roster); end -- TODO else implement optional feature pre-approval (ask = subscribed) end @@ -282,7 +292,7 @@ function unsubscribed(username, host, jid) local item = roster[jid]; local pending = is_contact_pending_in(username, host, jid); if pending then - roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty? + roster[false].pending[jid] = nil; end local subscribed; if item then diff --git a/core/s2smanager.lua b/core/s2smanager.lua index 06d3f2c9..59c1831b 100644 --- a/core/s2smanager.lua +++ b/core/s2smanager.lua @@ -1,7 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -70,14 +70,14 @@ end function destroy_session(session, reason) if session.destroyed then return; end (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host)..(reason and (": "..reason) or "")); - + if session.direction == "outgoing" then hosts[session.from_host].s2sout[session.to_host] = nil; session:bounce_sendq(reason); elseif session.direction == "incoming" then incoming_s2s[session] = nil; end - + local event_data = { session = session, reason = reason }; if session.type == "s2sout" then fire_event("s2sout-destroyed", event_data); @@ -90,7 +90,7 @@ function destroy_session(session, reason) hosts[session.to_host].events.fire_event("s2sin-destroyed", event_data); end end - + retire_session(session, reason); -- Clean session until it is GC'd return true; end diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index 4b014d18..d833dbe5 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -1,7 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -10,8 +10,8 @@ local tostring, setmetatable = tostring, setmetatable; local pairs, next= pairs, next; local hosts = hosts; -local full_sessions = full_sessions; -local bare_sessions = bare_sessions; +local full_sessions = prosody.full_sessions; +local bare_sessions = prosody.bare_sessions; local logger = require "util.logger"; local log = logger.init("sessionmanager"); @@ -44,7 +44,7 @@ function new_session(conn) session.ip = conn:ip(); local conn_name = "c2s"..tostring(session):match("[a-f0-9]+$"); session.log = logger.init(conn_name); - + return session; end @@ -67,25 +67,26 @@ function retire_session(session) function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); return false; end function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end + session.thread = { run = function (_, data) return session.data(data) end }; return setmetatable(session, resting_session); end function destroy_session(session, err) (session.log or log)("debug", "Destroying session for %s (%s@%s)%s", session.full_jid or "(unknown)", session.username or "(unknown)", session.host or "(unknown)", err and (": "..err) or ""); if session.destroyed then return; end - + -- Remove session/resource from user's session list if session.full_jid then local host_session = hosts[session.host]; - + -- Allow plugins to prevent session destruction if host_session.events.fire_event("pre-resource-unbind", {session=session, error=err}) then return; end - + host_session.sessions[session.username].sessions[session.resource] = nil; full_sessions[session.full_jid] = nil; - + if not next(host_session.sessions[session.username].sessions) then log("debug", "All resources of %s are now offline", session.username); host_session.sessions[session.username] = nil; @@ -94,7 +95,7 @@ function destroy_session(session, err) host_session.events.fire_event("resource-unbind", {session=session, error=err}); end - + retire_session(session); end @@ -116,10 +117,20 @@ function bind_resource(session, resource) if session.resource then return nil, "cancel", "not-allowed", "Cannot bind multiple resources on a single connection"; end -- We don't support binding multiple resources + local event_payload = { session = session, resource = resource }; + if hosts[session.host].events.fire_event("pre-resource-bind", event_payload) == false then + local err = event_payload.error; + if err then return nil, err.type, err.condition, err.text; end + return nil, "cancel", "not-allowed"; + else + -- In case a plugin wants to poke at it + resource = event_payload.resource; + end + resource = resourceprep(resource); resource = resource ~= "" and resource or uuid_generate(); --FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing - + if not hosts[session.host].sessions[session.username] then local sessions = { sessions = {} }; hosts[session.host].sessions[session.username] = sessions; @@ -156,12 +167,12 @@ function bind_resource(session, resource) end end end - + session.resource = resource; session.full_jid = session.username .. '@' .. session.host .. '/' .. resource; hosts[session.host].sessions[session.username].sessions[resource] = session; full_sessions[session.full_jid] = session; - + local err; session.roster, err = rm_load_roster(session.username, session.host); if err then @@ -176,14 +187,14 @@ function bind_resource(session, resource) session.log("error", "Roster loading failed: %s", err); return nil, "cancel", "internal-server-error", "Error loading roster"; end - + hosts[session.host].events.fire_event("resource-bind", {session=session}); - + return true; end -function send_to_available_resources(user, host, stanza) - local jid = user.."@"..host; +function send_to_available_resources(username, host, stanza) + local jid = username.."@"..host; local count = 0; local user = bare_sessions[jid]; if user then diff --git a/core/stanza_router.lua b/core/stanza_router.lua index a2c7b396..8e86b16f 100644 --- a/core/stanza_router.lua +++ b/core/stanza_router.lua @@ -1,7 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -46,7 +46,7 @@ local function handle_unhandled_stanza(host, origin, stanza) if origin.send then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end - elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features + else log("warn", "Unhandled %s stream element or stanza: %s; xmlns=%s: %s", origin_type, name, xmlns, tostring(stanza)); -- we didn't handle it origin:close("unsupported-stanza-type"); end @@ -199,7 +199,7 @@ function core_route_stanza(origin, stanza) -- Auto-detect origin if not specified origin = origin or hosts[from_host]; if not origin then return false; end - + if hosts[host] then -- old stanza routing code removed core_post_stanza(origin, stanza); diff --git a/core/statsmanager.lua b/core/statsmanager.lua new file mode 100644 index 00000000..d6cbd2bc --- /dev/null +++ b/core/statsmanager.lua @@ -0,0 +1,69 @@ + +local stats = require "util.statistics".new(); +local config = require "core.configmanager"; +local log = require "util.logger".init("stats"); +local timer = require "util.timer"; +local fire_event = prosody.events.fire_event; + +local stats_config = config.get("*", "statistics_interval"); +local stats_interval = tonumber(stats_config); +if stats_config and not stats_interval then + log("error", "Invalid 'statistics_interval' setting, statistics will be disabled"); +end + +local measure, collect; +local latest_stats = {}; +local changed_stats = {}; +local stats_extra = {}; + +if stats_interval then + log("debug", "Statistics collection is enabled every %d seconds", stats_interval); + function measure(type, name) + local f = assert(stats[type], "unknown stat type: "..type); + return f(name); + end + + local mark_collection_start = measure("times", "stats.collection"); + local mark_processing_start = measure("times", "stats.processing"); + + function collect() + local mark_collection_done = mark_collection_start(); + fire_event("stats-update"); + changed_stats, stats_extra = {}, {}; + for stat_name, getter in pairs(stats.get_stats()) do + local type, value, extra = getter(); + local old_value = latest_stats[stat_name]; + latest_stats[stat_name] = value; + if value ~= old_value then + changed_stats[stat_name] = value; + end + if extra then + stats_extra[stat_name] = extra; + end + end + mark_collection_done(); + local mark_processing_done = mark_processing_start(); + fire_event("stats-updated", { stats = latest_stats, changed_stats = changed_stats, stats_extra = stats_extra }); + mark_processing_done(); + return stats_interval; + end + + timer.add_task(stats_interval, collect); + prosody.events.add_handler("server-started", function () collect() end, -1); +else + log("debug", "Statistics collection is disabled"); + -- nop + function measure() + return measure; + end + function collect() + end +end + +return { + measure = measure; + collect = collect; + get_stats = function () + return latest_stats, changed_stats, stats_extra; + end; +}; diff --git a/core/storagemanager.lua b/core/storagemanager.lua index 1c82af6d..ad31eb80 100644 --- a/core/storagemanager.lua +++ b/core/storagemanager.lua @@ -1,5 +1,5 @@ -local error, type, pairs = error, type, pairs; +local type, pairs = type, pairs; local setmetatable = setmetatable; local config = require "core.configmanager"; @@ -37,7 +37,7 @@ function initialize_host(host) local item = event.item; stores_available:set(host, item.name, item); end); - + host_session.events.add_handler("item-removed/storage-provider", function (event) local item = event.item; stores_available:set(host, item.name, nil); @@ -70,7 +70,7 @@ function get_driver(host, store) if not driver_name then driver_name = config.get(host, "default_storage") or "internal"; end - + local driver = load_driver(host, driver_name); if not driver then log("warn", "Falling back to null driver for %s storage on %s", store, host); diff --git a/core/usermanager.lua b/core/usermanager.lua index 08343bee..900531ca 100644 --- a/core/usermanager.lua +++ b/core/usermanager.lua @@ -10,7 +10,6 @@ local modulemanager = require "core.modulemanager"; local log = require "util.logger".init("usermanager"); local type = type; local ipairs = ipairs; -local pairs = pairs; local jid_bare = require "util.jid".bare; local jid_prep = require "util.jid".prep; local config = require "core.configmanager"; @@ -39,7 +38,7 @@ local provider_mt = { __index = new_null_provider() }; function initialize_host(host) local host_session = hosts[host]; if host_session.type ~= "local" then return; end - + host_session.events.add_handler("item-added/auth-provider", function (event) local provider = event.item; local auth_provider = config.get(host, "authentication") or default_provider; @@ -51,7 +50,7 @@ function initialize_host(host) host_session.users = setmetatable(provider, provider_mt); end if host_session.users ~= nil and host_session.users.name ~= nil then - log("debug", "host '%s' now set to use user provider '%s'", host, host_session.users.name); + log("debug", "Host '%s' now set to use user provider '%s'", host, host_session.users.name); end end); host_session.events.add_handler("item-removed/auth-provider", function (event) @@ -115,10 +114,10 @@ function is_admin(jid, host) local is_admin; jid = jid_bare(jid); host = host or "*"; - + local host_admins = config.get(host, "admins"); local global_admins = config.get("*", "admins"); - + if host_admins and host_admins ~= global_admins then if type(host_admins) == "table" then for _,admin in ipairs(host_admins) do @@ -131,7 +130,7 @@ function is_admin(jid, host) log("error", "Option 'admins' for host '%s' is not a list", host); end end - + if not is_admin and global_admins then if type(global_admins) == "table" then for _,admin in ipairs(global_admins) do @@ -144,7 +143,7 @@ function is_admin(jid, host) log("error", "Global option 'admins' is not a list"); end end - + -- Still not an admin, check with auth provider if not is_admin and host ~= "*" and hosts[host].users and hosts[host].users.is_admin then is_admin = hosts[host].users.is_admin(jid); |