aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/certmanager.lua95
-rw-r--r--core/componentmanager.lua162
-rw-r--r--core/configmanager.lua177
-rw-r--r--core/eventmanager.lua33
-rw-r--r--core/hostmanager.lua73
-rw-r--r--core/loggingmanager.lua97
-rw-r--r--core/modulemanager.lua167
-rw-r--r--core/offlinemanager.lua41
-rw-r--r--core/rostermanager.lua12
-rw-r--r--core/s2smanager.lua165
-rw-r--r--core/sessionmanager.lua33
-rw-r--r--core/stanza_router.lua38
-rw-r--r--core/storagemanager.lua100
-rw-r--r--core/usermanager.lua155
-rw-r--r--core/xmlhandlers.lua167
15 files changed, 726 insertions, 789 deletions
diff --git a/core/certmanager.lua b/core/certmanager.lua
index 3dd06585..7f1ca42e 100644
--- a/core/certmanager.lua
+++ b/core/certmanager.lua
@@ -1,3 +1,11 @@
+-- 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 configmanager = require "core.configmanager";
local log = require "util.logger".init("certmanager");
local ssl = ssl;
@@ -6,54 +14,63 @@ local ssl_newcontext = ssl and ssl.newcontext;
local setmetatable, tostring = setmetatable, tostring;
local prosody = prosody;
+local resolve_path = configmanager.resolve_relative_path;
+local config_path = prosody.paths.config;
module "certmanager"
--- These are the defaults if not overridden in the config
-local default_ssl_ctx = { mode = "client", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
-local default_ssl_ctx_in = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
-
-local default_ssl_ctx_mt = { __index = default_ssl_ctx };
-local default_ssl_ctx_in_mt = { __index = default_ssl_ctx_in };
-
-- Global SSL options if not overridden per-host
local default_ssl_config = configmanager.get("*", "core", "ssl");
+local default_capath = "/etc/ssl/certs";
-function create_context(host, mode, config)
- local ssl_config = config and config.core.ssl or default_ssl_config;
- if ssl and ssl_config then
- local ctx, err = ssl_newcontext(setmetatable(ssl_config, mode == "client" and default_ssl_ctx_mt or default_ssl_ctx_in_mt));
- if not ctx then
- err = err or "invalid ssl config"
- local file = err:match("^error loading (.-) %(");
- if file then
- if file == "private key" then
- file = ssl_config.key or "your private key";
- elseif file == "certificate" then
- file = ssl_config.certificate or "your certificate file";
- end
- local reason = err:match("%((.+)%)$") or "some reason";
- if reason == "Permission denied" then
- reason = "Check that the permissions allow Prosody to read this file.";
- elseif reason == "No such file or directory" then
- reason = "Check that the path is correct, and the file exists.";
- elseif reason == "system lib" then
- reason = "Previous error (see logs), or other system error.";
- elseif reason == "(null)" or not reason then
- reason = "Check that the file exists and the permissions are correct";
- else
- reason = "Reason: "..tostring(reason):lower();
- end
- log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
+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;
+ 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 "none";
+ options = user_ssl_config.options or "no_sslv2";
+ ciphers = user_ssl_config.ciphers;
+ depth = user_ssl_config.depth;
+ };
+
+ local ctx, err = ssl_newcontext(ssl_config);
+ if not ctx then
+ err = err or "invalid ssl config"
+ local file = err:match("^error loading (.-) %(");
+ if file then
+ if file == "private key" then
+ file = ssl_config.key or "your private key";
+ elseif file == "certificate" then
+ file = ssl_config.certificate or "your certificate file";
+ end
+ local reason = err:match("%((.+)%)$") or "some reason";
+ if reason == "Permission denied" then
+ reason = "Check that the permissions allow Prosody to read this file.";
+ elseif reason == "No such file or directory" then
+ reason = "Check that the path is correct, and the file exists.";
+ elseif reason == "system lib" then
+ reason = "Previous error (see logs), or other system error.";
+ elseif reason == "(null)" or not reason then
+ reason = "Check that the file exists and the permissions are correct";
else
- log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
+ reason = "Reason: "..tostring(reason):lower();
end
- end
- return ctx, err;
- elseif not ssl then
- return nil, "LuaSec (required for encryption) was not found";
+ log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
+ else
+ log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
+ end
end
- return nil, "No SSL/TLS configuration present for "..host;
+ return ctx, err;
end
function reload_ssl_config()
diff --git a/core/componentmanager.lua b/core/componentmanager.lua
deleted file mode 100644
index 48e27984..00000000
--- a/core/componentmanager.lua
+++ /dev/null
@@ -1,162 +0,0 @@
--- 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 prosody = _G.prosody;
-local log = require "util.logger".init("componentmanager");
-local certmanager = require "core.certmanager";
-local configmanager = require "core.configmanager";
-local modulemanager = require "core.modulemanager";
-local jid_split = require "util.jid".split;
-local fire_event = require "core.eventmanager".fire_event;
-local events_new = require "util.events".new;
-local st = require "util.stanza";
-local prosody, hosts = prosody, prosody.hosts;
-local ssl = ssl;
-local uuid_gen = require "util.uuid".generate;
-
-local pairs, setmetatable, type, tostring = pairs, setmetatable, type, tostring;
-
-local components = {};
-
-local disco_items = require "util.multitable".new();
-local NULL = {};
-
-module "componentmanager"
-
-local function default_component_handler(origin, stanza)
- log("warn", "Stanza being handled by default component; bouncing error for: %s", stanza:top_tag());
- if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
- origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
- end
-end
-
-function load_enabled_components(config)
- local defined_hosts = config or configmanager.getconfig();
-
- for host, host_config in pairs(defined_hosts) do
- if host ~= "*" and ((host_config.core.enabled == nil or host_config.core.enabled) and type(host_config.core.component_module) == "string") then
- hosts[host] = create_component(host);
- hosts[host].connected = false;
- components[host] = default_component_handler;
- local ok, err = modulemanager.load(host, host_config.core.component_module);
- if not ok then
- log("error", "Error loading %s component %s: %s", tostring(host_config.core.component_module), tostring(host), tostring(err));
- else
- fire_event("component-activated", host, host_config);
- log("debug", "Activated %s component: %s", host_config.core.component_module, host);
- end
- end
- end
-end
-
-if prosody and prosody.events then
- prosody.events.add_handler("server-starting", load_enabled_components);
-end
-
-function handle_stanza(origin, stanza)
- local node, host = jid_split(stanza.attr.to);
- local component = nil;
- if host then
- if node then component = components[node.."@"..host]; end -- hack to allow hooking node@server
- if not component then component = components[host]; end
- end
- if component then
- log("debug", "%s stanza being handled by component: %s", stanza.name, host);
- component(origin, stanza, hosts[host]);
- else
- log("error", "Component manager recieved a stanza for a non-existing component: "..tostring(stanza));
- default_component_handler(origin, stanza);
- end
-end
-
-function create_component(host, component, events)
- -- TODO check for host well-formedness
- local ssl_ctx, ssl_ctx_in;
- if host and ssl then
- -- We need to find SSL context to use...
- -- Discussion in prosody@ concluded that
- -- 1 level back is usually enough by default
- local base_host = host:gsub("^[^%.]+%.", "");
- if hosts[base_host] then
- ssl_ctx = hosts[base_host].ssl_ctx;
- ssl_ctx_in = hosts[base_host].ssl_ctx_in;
- else
- -- We have no cert, and no parent host to borrow a cert from
- -- Use global/default cert if there is one
- ssl_ctx = certmanager.create_context(host, "client");
- ssl_ctx_in = certmanager.create_context(host, "server");
- end
- end
- return { type = "component", host = host, connected = true, s2sout = {},
- ssl_ctx = ssl_ctx, ssl_ctx_in = ssl_ctx_in, events = events or events_new(),
- dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen() };
-end
-
-function register_component(host, component, session)
- if not hosts[host] or (hosts[host].type == 'component' and not hosts[host].connected) then
- local old_events = hosts[host] and hosts[host].events;
-
- components[host] = component;
- hosts[host] = session or create_component(host, component, old_events);
-
- -- Add events object if not already one
- if not hosts[host].events then
- hosts[host].events = old_events or events_new();
- end
-
- if not hosts[host].dialback_secret then
- hosts[host].dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen();
- end
-
- -- add to disco_items
- if not(host:find("@", 1, true) or host:find("/", 1, true)) and host:find(".", 1, true) then
- disco_items:set(host:sub(host:find(".", 1, true)+1), host, true);
- end
- modulemanager.load(host, "dialback");
- modulemanager.load(host, "tls");
- log("debug", "component added: "..host);
- return session or hosts[host];
- else
- log("error", "Attempt to set component for existing host: "..host);
- end
-end
-
-function deregister_component(host)
- if components[host] then
- modulemanager.unload(host, "tls");
- modulemanager.unload(host, "dialback");
- hosts[host].connected = nil;
- local host_config = configmanager.getconfig()[host];
- if host_config and ((host_config.core.enabled == nil or host_config.core.enabled) and type(host_config.core.component_module) == "string") then
- -- Set default handler
- components[host] = default_component_handler;
- else
- -- Component not in config, or disabled, remove
- hosts[host] = nil; -- FIXME do proper unload of all modules and other cleanup before removing
- components[host] = nil;
- end
- -- remove from disco_items
- if not(host:find("@", 1, true) or host:find("/", 1, true)) and host:find(".", 1, true) then
- disco_items:remove(host:sub(host:find(".", 1, true)+1), host);
- end
- log("debug", "component removed: "..host);
- return true;
- else
- log("error", "Attempt to remove component for non-existing host: "..host);
- end
-end
-
-function set_component_handler(host, handler)
- components[host] = handler;
-end
-
-function get_children(host)
- return disco_items:get(host) or NULL;
-end
-
-return _M;
diff --git a/core/configmanager.lua b/core/configmanager.lua
index 54fb0a9a..4cc3ef46 100644
--- a/core/configmanager.lua
+++ b/core/configmanager.lua
@@ -6,34 +6,34 @@
-- COPYING file in the source package for more information.
--
-
-
local _G = _G;
-local setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table, format =
- setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table, string.format;
+local setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table =
+ setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table;
+local format, math_max = string.format, math.max;
+local fire_event = prosody and prosody.events.fire_event or function () end;
-local eventmanager = require "core.eventmanager";
+local lfs = require "lfs";
+local path_sep = package.config:sub(1,1);
module "configmanager"
local parsers = {};
-local config = { ["*"] = { core = {} } };
-
-local global_config = config["*"];
+local config_mt = { __index = function (t, k) return rawget(t, "*"); end};
+local config = setmetatable({ ["*"] = { core = {} } }, config_mt);
-- When host not found, use global
-setmetatable(config, { __index = function () return global_config; end});
-local host_mt = { __index = global_config };
+local host_mt = { };
-- When key not found in section, check key in global's section
function section_mt(section_name)
return { __index = function (t, k)
- local section = rawget(global_config, section_name);
- if not section then return nil; end
- return section[k];
- end };
+ local section = rawget(config["*"], section_name);
+ if not section then return nil; end
+ return section[k];
+ end
+ };
end
function getconfig()
@@ -47,8 +47,17 @@ function get(host, section, key)
end
return nil;
end
+function _M.rawget(host, section, key)
+ local hostconfig = rawget(config, host);
+ if hostconfig then
+ local sectionconfig = rawget(hostconfig, section);
+ if sectionconfig then
+ return rawget(sectionconfig, key);
+ end
+ end
+end
-function set(host, section, key, value)
+local function set(config, host, section, key, value)
if host and section and key then
local hostconfig = rawget(config, host);
if not hostconfig then
@@ -63,16 +72,62 @@ function set(host, section, key, value)
return false;
end
+function _M.set(host, section, key, value)
+ return set(config, host, section, key, value);
+end
+
+-- Helper function to resolve relative paths (needed by config)
+do
+ local rel_path_start = ".."..path_sep;
+ 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) ~= ":\\") 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+$");
if parsers[format] and parsers[format].load then
local f, err = io.open(filename);
if f then
- local ok, err = parsers[format].load(f:read("*a"), filename);
+ local new_config = setmetatable({ ["*"] = { core = {} } }, config_mt);
+ local ok, err = parsers[format].load(f:read("*a"), filename, new_config);
f:close();
if ok then
- eventmanager.fire_event("config-reloaded", { filename = filename, format = format });
+ config = new_config;
+ fire_event("config-reloaded", {
+ filename = filename,
+ format = format,
+ config = config
+ });
end
return ok, "parser", err;
end
@@ -109,19 +164,23 @@ do
local loadstring, pcall, setmetatable = _G.loadstring, _G.pcall, _G.setmetatable;
local setfenv, rawget, tostring = _G.setfenv, _G.rawget, _G.tostring;
parsers.lua = {};
- function parsers.lua.load(data, filename)
+ function parsers.lua.load(data, config_file, config)
local env;
-- The ' = true' are needed so as not to set off __newindex when we assign the functions below
- env = setmetatable({ Host = true, host = true, VirtualHost = true, Component = true, component = true,
- Include = true, include = true, RunScript = dofile }, { __index = function (t, k)
- return rawget(_G, k) or
- function (settings_table)
- config[__currenthost or "*"][k] = settings_table;
- end;
- end,
- __newindex = function (t, k, v)
- set(env.__currenthost or "*", "core", k, v);
- end});
+ env = setmetatable({
+ Host = true, host = true, VirtualHost = true,
+ Component = true, component = true,
+ Include = true, include = true, RunScript = true }, {
+ __index = function (t, k)
+ return rawget(_G, k) or
+ function (settings_table)
+ config[__currenthost or "*"][k] = settings_table;
+ end;
+ end,
+ __newindex = function (t, k, v)
+ set(config, env.__currenthost or "*", "core", k, v);
+ end
+ });
rawset(env, "__currenthost", "*") -- Default is global
function env.VirtualHost(name)
@@ -131,7 +190,13 @@ do
end
rawset(env, "__currenthost", name);
-- Needs at least one setting to logically exist :)
- set(name or "*", "core", "defined", true);
+ set(config, name or "*", "core", "defined", true);
+ return function (config_options)
+ rawset(env, "__currenthost", "*"); -- Return to global scope
+ for option_name, option_value in pairs(config_options) do
+ set(config, name or "*", "core", option_name, option_value);
+ end
+ end;
end
env.Host, env.host = env.VirtualHost, env.VirtualHost;
@@ -140,32 +205,62 @@ do
error(format("Component %q clashes with previously defined Host %q, for services use a sub-domain like conference.%s",
name, name, name), 0);
end
- set(name, "core", "component_module", "component");
+ set(config, name, "core", "component_module", "component");
-- Don't load the global modules by default
- set(name, "core", "load_global_modules", false);
+ set(config, name, "core", "load_global_modules", false);
rawset(env, "__currenthost", name);
+ local function handle_config_options(config_options)
+ rawset(env, "__currenthost", "*"); -- Return to global scope
+ for option_name, option_value in pairs(config_options) do
+ set(config, name or "*", "core", option_name, option_value);
+ end
+ end
return function (module)
if type(module) == "string" then
- set(name, "core", "component_module", module);
+ set(config, name, "core", "component_module", module);
+ return handle_config_options;
end
+ return handle_config_options(module);
end
end
env.component = env.Component;
- function env.Include(file)
- local f, err = io.open(file);
- if f then
- local data = f:read("*a");
- local ok, err = parsers.lua.load(data, file);
- if not ok then error(err:gsub("%[string.-%]", file), 0); end
+ function env.Include(file, wildcard)
+ if file:match("[*?]") then
+ local path_pos, glob = file:match("()([^"..path_sep.."]+)$");
+ local path = file:sub(1, math_max(path_pos-2,0));
+ local config_path = config_file:gsub("[^"..path_sep.."]+$", "");
+ if #path > 0 then
+ path = resolve_relative_path(config_path, path);
+ else
+ path = config_path;
+ end
+ local patt = glob_to_pattern(glob);
+ for f in lfs.dir(path) do
+ if f:sub(1,1) ~= "." and f:match(patt) then
+ env.Include(path..path_sep..f);
+ end
+ end
+ else
+ local f, err = io.open(file);
+ if f then
+ local data = f:read("*a");
+ local file = resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file);
+ local ret, err = parsers.lua.load(data, file, config);
+ if not ret then error(err:gsub("%[string.-%]", file), 0); end
+ end
+ if not f then error("Error loading included "..file..": "..err, 0); end
+ return f, err;
end
- if not f then error("Error loading included "..file..": "..err, 0); end
- return f, err;
end
env.include = env.Include;
- local chunk, err = loadstring(data, "@"..filename);
+ function env.RunScript(file)
+ return dofile(resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file));
+ end
+
+ local chunk, err = loadstring(data, "@"..config_file);
if not chunk then
return nil, err;
diff --git a/core/eventmanager.lua b/core/eventmanager.lua
deleted file mode 100644
index 0e766c30..00000000
--- a/core/eventmanager.lua
+++ /dev/null
@@ -1,33 +0,0 @@
--- 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 t_insert = table.insert;
-local ipairs = ipairs;
-
-module "eventmanager"
-
-local event_handlers = {};
-
-function add_event_hook(name, handler)
- if not event_handlers[name] then
- event_handlers[name] = {};
- end
- t_insert(event_handlers[name] , handler);
-end
-
-function fire_event(name, ...)
- local event_handlers = event_handlers[name];
- if event_handlers then
- for name, handler in ipairs(event_handlers) do
- handler(...);
- end
- end
-end
-
-return _M; \ No newline at end of file
diff --git a/core/hostmanager.lua b/core/hostmanager.lua
index c8928b27..9e74cd6b 100644
--- a/core/hostmanager.lua
+++ b/core/hostmanager.lua
@@ -6,25 +6,25 @@
-- COPYING file in the source package for more information.
--
-local ssl = ssl
-
-local hosts = hosts;
-local certmanager = require "core.certmanager";
local configmanager = require "core.configmanager";
-local eventmanager = require "core.eventmanager";
local modulemanager = require "core.modulemanager";
local events_new = require "util.events".new;
+local disco_items = require "util.multitable".new();
+local NULL = {};
local uuid_gen = require "util.uuid".generate;
+local log = require "util.logger".init("hostmanager");
+
+local hosts = hosts;
+local prosody_events = prosody.events;
if not _G.prosody.incoming_s2s then
require "core.s2smanager";
end
local incoming_s2s = _G.prosody.incoming_s2s;
-local log = require "util.logger".init("hostmanager");
-
local pairs, setmetatable = pairs, setmetatable;
+local tostring, type = tostring, type;
module "hostmanager"
@@ -35,8 +35,10 @@ local function load_enabled_hosts(config)
local activated_any_host;
for host, host_config in pairs(defined_hosts) do
- if host ~= "*" and host_config.core.enabled ~= false and not host_config.core.component_module then
- activated_any_host = true;
+ if host ~= "*" and host_config.core.enabled ~= false then
+ if not host_config.core.component_module then
+ activated_any_host = true;
+ end
activate(host, host_config);
end
end
@@ -45,39 +47,53 @@ local function load_enabled_hosts(config)
log("error", "No active VirtualHost entries in the config file. This may cause unexpected behaviour as no modules will be loaded.");
end
- eventmanager.fire_event("hosts-activated", defined_hosts);
+ prosody_events.fire_event("hosts-activated", defined_hosts);
hosts_loaded_once = true;
end
-eventmanager.add_event_hook("server-starting", load_enabled_hosts);
+prosody_events.add_handler("server-starting", load_enabled_hosts);
function activate(host, host_config)
- hosts[host] = {type = "local", connected = true, sessions = {},
- host = host, s2sout = {}, events = events_new(),
- disallow_s2s = configmanager.get(host, "core", "disallow_s2s")
- or (configmanager.get(host, "core", "anonymous_login")
- and (configmanager.get(host, "core", "disallow_s2s") ~= false));
- dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen();
- };
+ if hosts[host] then return nil, "The host "..host.." is already activated"; end
+ host_config = host_config or configmanager.getconfig()[host];
+ if not host_config then return nil, "Couldn't find the host "..tostring(host).." defined in the current config"; end
+ local host_session = {
+ host = host;
+ s2sout = {};
+ events = events_new();
+ dialback_secret = configmanager.get(host, "core", "dialback_secret") or uuid_gen();
+ disallow_s2s = configmanager.get(host, "core", "disallow_s2s");
+ };
+ if not host_config.core.component_module then -- host
+ host_session.type = "local";
+ host_session.sessions = {};
+ else -- component
+ host_session.type = "component";
+ end
+ hosts[host] = host_session;
+ if not host:match("[@/]") then
+ disco_items:set(host:match("%.(.*)") or "*", host, true);
+ end
for option_name in pairs(host_config.core) do
if option_name:match("_ports$") or option_name:match("_interface$") then
log("warn", "%s: Option '%s' has no effect for virtual hosts - put it in the server-wide section instead", host, option_name);
end
end
- hosts[host].ssl_ctx = certmanager.create_context(host, "client", host_config); -- for outgoing connections
- hosts[host].ssl_ctx_in = certmanager.create_context(host, "server", host_config); -- for incoming connections
-
log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
- eventmanager.fire_event("host-activated", host, host_config);
+ prosody_events.fire_event("host-activated", host, host_config);
+ return true;
end
function deactivate(host, reason)
local host_session = hosts[host];
+ if not host_session then return nil, "The host "..tostring(host).." is not activated"; end
log("info", "Deactivating host: %s", host);
- eventmanager.fire_event("host-deactivating", host, host_session);
+ prosody_events.fire_event("host-deactivating", host, host_session);
- reason = reason or { condition = "host-gone", text = "This server has stopped serving "..host };
+ 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
if host_session.sessions then
@@ -111,11 +127,16 @@ function deactivate(host, reason)
end
hosts[host] = nil;
- eventmanager.fire_event("host-deactivated", host);
+ if not host:match("[@/]") then
+ disco_items:remove(host:match("%.(.*)") or "*", host);
+ end
+ prosody_events.fire_event("host-deactivated", host);
log("info", "Deactivated host: %s", host);
+ return true;
end
-function getconfig(name)
+function get_children(host)
+ return disco_items:get(host) or NULL;
end
return _M;
diff --git a/core/loggingmanager.lua b/core/loggingmanager.lua
index 3ec696d5..88f2bbbf 100644
--- a/core/loggingmanager.lua
+++ b/core/loggingmanager.lua
@@ -10,12 +10,12 @@
local format, rep = string.format, string.rep;
local pcall = pcall;
local debug = debug;
-local tostring, setmetatable, rawset, pairs, ipairs, type =
+local tostring, setmetatable, rawset, pairs, ipairs, type =
tostring, setmetatable, rawset, pairs, ipairs, type;
local io_open, io_write = io.open, io.write;
local math_max, rep = math.max, string.rep;
local os_date, os_getenv = os.date, os.getenv;
-local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
+local getstyle, setstyle = require "util.termcolours".getstyle, require "util.termcolours".setstyle;
if os.getenv("__FLUSH_LOG") then
local io_flush = io.flush;
@@ -24,20 +24,19 @@ if os.getenv("__FLUSH_LOG") then
end
local config = require "core.configmanager";
-local eventmanager = require "core.eventmanager";
local logger = require "util.logger";
-local debug_mode = config.get("*", "core", "debug");
+local prosody = prosody;
_G.log = logger.init("general");
module "loggingmanager"
--- The log config used if none specified in the config file
-local default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } };
-local default_file_logging = { { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true } };
+-- The log config used if none specified in the config file (see reload_logging for initialization)
+local default_logging;
+local default_file_logging;
local default_timestamp = "%b %d %H:%M:%S";
-- The actual config loggingmanager is using
-local logging_config = config.get("*", "core", "log") or default_logging;
+local logging_config;
local apply_sink_rules;
local log_sink_types = setmetatable({}, { __newindex = function (t, k, v) rawset(t, k, v); apply_sink_rules(k); end; });
@@ -88,9 +87,31 @@ end
-- the log_sink_types table.
function apply_sink_rules(sink_type)
if type(logging_config) == "table" then
- for _, sink_config in pairs(logging_config) do
- if sink_config.to == sink_type then
+
+ for _, level in ipairs(logging_levels) do
+ if type(logging_config[level]) == "string" then
+ local value = logging_config[level];
+ if sink_type == "file" then
+ add_rule({
+ to = sink_type;
+ filename = value;
+ timestamps = true;
+ levels = { min = level };
+ });
+ elseif value == "*"..sink_type then
+ add_rule({
+ to = sink_type;
+ levels = { min = level };
+ });
+ 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);
+ elseif (type(sink_config) == "string" and sink_config:match("^%*(.+)") == sink_type) then
+ add_rule({ levels = { min = "debug" }, to = sink_type });
end
end
elseif type(logging_config) == "string" and (not logging_config:match("^%*")) and sink_type == "file" then
@@ -138,6 +159,38 @@ function get_levels(criteria, set)
return set;
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("*", "core", "debug");
+
+ default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } };
+ default_file_logging = {
+ { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true }
+ };
+ default_timestamp = "%b %d %H:%M:%S";
+
+ logging_config = config.get("*", "core", "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
+
+reload_logging();
+prosody.events.add_handler("config-reloaded", reload_logging);
+
--- Definition of built-in logging sinks ---
-- Null sink, must enter log_sink_types *first*
@@ -148,7 +201,7 @@ end
-- Column width for "source" (used by stdout and console)
local sourcewidth = 20;
-function log_sink_types.stdout()
+function log_sink_types.stdout(config)
local timestamps = config.timestamps;
if timestamps == true then
@@ -170,7 +223,7 @@ function log_sink_types.stdout()
end
do
- local do_pretty_printing = not os_getenv("WINDIR");
+ local do_pretty_printing = true;
local logstyles = {};
if do_pretty_printing then
@@ -197,10 +250,14 @@ do
if timestamps then
io_write(os_date(timestamps), " ");
end
+ io_write(name, rep(" ", sourcewidth-namelen));
+ setstyle(logstyles[level]);
+ io_write(level);
+ setstyle();
if ... then
- io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", format(message, ...), "\n");
+ io_write("\t", format(message, ...), "\n");
else
- io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", message, "\n");
+ io_write("\t", message, "\n");
end
end
end
@@ -215,18 +272,6 @@ function log_sink_types.file(config)
end
local write, flush = logfile.write, logfile.flush;
- eventmanager.add_event_hook("reopen-log-files", function ()
- if logfile then
- logfile:close();
- end
- logfile = io_open(log, "a+");
- if not logfile then
- write, flush = empty_function, empty_function;
- else
- write, flush = logfile.write, logfile.flush;
- end
- end);
-
local timestamps = config.timestamps;
if timestamps == nil or timestamps == true then
diff --git a/core/modulemanager.lua b/core/modulemanager.lua
index 8e62aecb..07a2b1c9 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -6,11 +6,8 @@
-- COPYING file in the source package for more information.
--
-local plugin_dir = CFG_PLUGINDIR or "./plugins/";
-
local logger = require "util.logger";
local log = logger.init("modulemanager");
-local eventmanager = require "core.eventmanager";
local config = require "core.configmanager";
local multitable_new = require "util.multitable".new;
local st = require "util.stanza";
@@ -18,6 +15,7 @@ local pluginloader = require "util.pluginloader";
local hosts = hosts;
local prosody = prosody;
+local prosody_events = prosody.events;
local loadfile, pcall, xpcall = loadfile, pcall, xpcall;
local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv;
@@ -34,12 +32,13 @@ local unpack, select = unpack, select;
pcall = function(f, ...)
local n = select("#", ...);
local params = {...};
- return xpcall(function() f(unpack(params, 1, n)) end, function(e) return tostring(e).."\n"..debug_traceback(); end);
+ return xpcall(function() return f(unpack(params, 1, n)) end, function(e) return tostring(e).."\n"..debug_traceback(); end);
end
local array, set = require "util.array", require "util.set";
-local autoload_modules = {"presence", "message", "iq"};
+local autoload_modules = {"presence", "message", "iq", "offline"};
+local component_inheritable_modules = {"tls", "dialback", "iq"};
-- We need this to let modules access the real global namespace
local _G = _G;
@@ -51,66 +50,52 @@ local api = api; -- Module API container
local modulemap = { ["*"] = {} };
-local stanza_handlers = multitable_new();
-local handler_info = {};
-
local modulehelpers = setmetatable({}, { __index = _G });
-local handler_table = multitable_new();
-local hooked = multitable_new();
local hooks = multitable_new();
-local event_hooks = multitable_new();
local NULL = {};
-- Load modules when a host is activated
function load_modules_for_host(host)
- local disabled_set = {};
- local modules_disabled = config.get(host, "core", "modules_disabled");
- if modules_disabled then
- for _, module in ipairs(modules_disabled) do
- disabled_set[module] = true;
- end
- end
-
- -- Load auto-loaded modules for this host
- if hosts[host].type == "local" then
- for _, module in ipairs(autoload_modules) do
- if not disabled_set[module] then
- load(host, module);
- end
- end
+ local component = config.get(host, "core", "component_module");
+
+ local global_modules_enabled = config.get("*", "core", "modules_enabled");
+ local global_modules_disabled = config.get("*", "core", "modules_disabled");
+ local host_modules_enabled = config.get(host, "core", "modules_enabled");
+ local host_modules_disabled = config.get(host, "core", "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
-
- -- Load modules from global section
- if config.get(host, "core", "load_global_modules") ~= false then
- local modules_enabled = config.get("*", "core", "modules_enabled");
- if modules_enabled then
- for _, module in ipairs(modules_enabled) do
- if not disabled_set[module] and not is_loaded(host, module) then
- load(host, module);
- end
- end
- 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
- -- Load modules from just this host
- local modules_enabled = config.get(host, "core", "modules_enabled");
- if modules_enabled and modules_enabled ~= config.get("*", "core", "modules_enabled") then
- for _, module in pairs(modules_enabled) do
- if not is_loaded(host, module) then
- load(host, module);
- end
- end
+ if component then
+ load(host, component);
+ end
+ for module in modules do
+ load(host, module);
end
end
-eventmanager.add_event_hook("host-activated", load_modules_for_host);
-eventmanager.add_event_hook("component-activated", load_modules_for_host);
+prosody_events.add_handler("host-activated", load_modules_for_host);
--
function load(host, module_name, config)
if not (host and module_name) then
return nil, "insufficient-parameters";
+ elseif not hosts[host] then
+ return nil, "unknown-host";
end
if not modulemap[host] then
@@ -132,18 +117,12 @@ function load(host, module_name, config)
end
local _log = logger.init(host..":"..module_name);
- local api_instance = setmetatable({ name = module_name, host = host, config = config, _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
+ local api_instance = setmetatable({ name = module_name, host = host, path = err, config = config, _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
api_instance.environment = pluginenv;
setfenv(mod, pluginenv);
- if not hosts[host] then
- local create_component = _G.require "core.componentmanager".create_component;
- hosts[host] = create_component(host);
- hosts[host].connected = false;
- log("debug", "Created new component: %s", host);
- end
hosts[host].modules = modulemap[host];
modulemap[host][module_name] = pluginenv;
@@ -192,15 +171,6 @@ function unload(host, name, ...)
log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err);
end
end
- local params = handler_table:get(host, name); -- , {module.host, origin_type, tag, xmlns}
- for _, param in pairs(params or NULL) do
- local handlers = stanza_handlers:get(param[1], param[2], param[3], param[4]);
- if handlers then
- handler_info[handlers[1]] = nil;
- stanza_handlers:remove(param[1], param[2], param[3], param[4]);
- end
- end
- event_hooks:remove(host, name);
-- unhook event handlers hooked by module:hook
for event, handlers in pairs(hooks:get(host, name) or NULL) do
for handler in pairs(handlers or NULL) do
@@ -264,36 +234,6 @@ function reload(host, name, ...)
return ok, err;
end
-function handle_stanza(host, origin, stanza)
- local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type;
- if name == "iq" and xmlns == "jabber:client" then
- if stanza.attr.type == "get" or stanza.attr.type == "set" then
- xmlns = stanza.tags[1].attr.xmlns or "jabber:client";
- log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns);
- else
- log("debug", "Discarding %s from %s of type: %s", name, origin_type, stanza.attr.type);
- return true;
- end
- end
- local handlers = stanza_handlers:get(host, origin_type, name, xmlns);
- if not handlers then handlers = stanza_handlers:get("*", origin_type, name, xmlns); end
- if handlers then
- log("debug", "Passing stanza to mod_%s", handler_info[handlers[1]].name);
- (handlers[1])(origin, stanza);
- return true;
- else
- if stanza.attr.xmlns == nil then
- log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
- if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" 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
- log("warn", "Unhandled %s stream element: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it
- origin:close("unsupported-stanza-type");
- end
- end
-end
-
function module_has_method(module, method)
return type(module.module[method]) == "function";
end
@@ -332,33 +272,6 @@ function api:set_global()
self._log = _log;
end
-local function _add_handler(module, origin_type, tag, xmlns, handler)
- local handlers = stanza_handlers:get(module.host, origin_type, tag, xmlns);
- local msg = (tag == "iq") and "namespace" or "payload namespace";
- if not handlers then
- stanza_handlers:add(module.host, origin_type, tag, xmlns, handler);
- handler_info[handler] = module;
- handler_table:add(module.host, module.name, {module.host, origin_type, tag, xmlns});
- --module:log("debug", "I now handle tag '%s' [%s] with %s '%s'", tag, origin_type, msg, xmlns);
- else
- module:log("warn", "I wanted to handle tag '%s' [%s] with %s '%s' but mod_%s already handles that", tag, origin_type, msg, xmlns, handler_info[handlers[1]].module.name);
- end
-end
-
-function api:add_handler(origin_type, tag, xmlns, handler)
- if not (origin_type and tag and xmlns and handler) then return false; end
- if type(origin_type) == "table" then
- for _, origin_type in ipairs(origin_type) do
- _add_handler(self, origin_type, tag, xmlns, handler);
- end
- else
- _add_handler(self, origin_type, tag, xmlns, handler);
- end
-end
-function api:add_iq_handler(origin_type, xmlns, handler)
- self:add_handler(origin_type, "iq", xmlns, handler);
-end
-
function api:add_feature(xmlns)
self:add_item("feature", xmlns);
end
@@ -366,20 +279,6 @@ function api:add_identity(category, type, name)
self:add_item("identity", {category = category, type = type, name = name});
end
-local event_hook = function(host, mod_name, event_name, ...)
- if type((...)) == "table" and (...).host and (...).host ~= host then return; end
- for handler in pairs(event_hooks:get(host, mod_name, event_name) or NULL) do
- handler(...);
- end
-end;
-function api:add_event_hook(name, handler)
- if not hooked:get(self.host, self.name, name) then
- eventmanager.add_event_hook(name, function(...) event_hook(self.host, self.name, name, ...); end);
- hooked:set(self.host, self.name, name, true);
- end
- event_hooks:set(self.host, self.name, name, handler, true);
-end
-
function api:fire_event(...)
return (hosts[self.host] or prosody).events.fire_event(...);
end
diff --git a/core/offlinemanager.lua b/core/offlinemanager.lua
deleted file mode 100644
index 97781e82..00000000
--- a/core/offlinemanager.lua
+++ /dev/null
@@ -1,41 +0,0 @@
--- 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 datamanager = require "util.datamanager";
-local st = require "util.stanza";
-local datetime = require "util.datetime";
-local ipairs = ipairs;
-
-module "offlinemanager"
-
-function store(node, host, stanza)
- stanza.attr.stamp = datetime.datetime();
- stanza.attr.stamp_legacy = datetime.legacy();
- return datamanager.list_append(node, host, "offline", st.preserialize(stanza));
-end
-
-function load(node, host)
- local data = datamanager.list_load(node, host, "offline");
- if not data then return; end
- for k, v in ipairs(data) do
- local stanza = st.deserialize(v);
- stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203
- stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated)
- stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
- data[k] = stanza;
- end
- return data;
-end
-
-function deleteAll(node, host)
- return datamanager.list_store(node, host, "offline", nil);
-end
-
-return _M;
diff --git a/core/rostermanager.lua b/core/rostermanager.lua
index 506cf205..59ba6579 100644
--- a/core/rostermanager.lua
+++ b/core/rostermanager.lua
@@ -190,7 +190,19 @@ function process_inbound_unsubscribe(username, host, jid)
end
end
+local function _get_online_roster_subscription(jidA, jidB)
+ local user = bare_sessions[jidA];
+ local item = user and (user.roster[jidB] or { subscription = "none" });
+ return item and item.subscription;
+end
function is_contact_subscribed(username, host, jid)
+ do
+ local selfjid = username.."@"..host;
+ local subscription = _get_online_roster_subscription(selfjid, jid);
+ if subscription then return (subscription == "both" or subscription == "from"); end
+ local subscription = _get_online_roster_subscription(jid, selfjid);
+ if subscription then return (subscription == "both" or subscription == "to"); end
+ end
local roster, err = load_roster(username, host);
local item = roster[jid];
return item and (item.subscription == "from" or item.subscription == "both"), err;
diff --git a/core/s2smanager.lua b/core/s2smanager.lua
index 0c29da14..fd9a72d0 100644
--- a/core/s2smanager.lua
+++ b/core/s2smanager.lua
@@ -16,20 +16,19 @@ local socket = require "socket";
local format = string.format;
local t_insert, t_sort = table.insert, table.sort;
local get_traceback = debug.traceback;
-local tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber,
- setmetatable
- = tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber,
- setmetatable;
+local tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber, setmetatable
+ = tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber, setmetatable;
local idna_to_ascii = require "util.encodings".idna.to_ascii;
local connlisteners_get = require "net.connlisteners".get;
+local initialize_filters = require "util.filters".initialize;
local wrapclient = require "net.server".wrapclient;
local modulemanager = require "core.modulemanager";
local st = require "stanza";
local stanza = st.stanza;
local nameprep = require "util.encodings".stringprep.nameprep;
-local fire_event = require "core.eventmanager".fire_event;
+local fire_event = prosody.events.fire_event;
local uuid_gen = require "util.uuid".generate;
local logger_init = require "util.logger".init;
@@ -41,11 +40,14 @@ local sha256_hash = require "util.hashes".sha256;
local adns, dns = require "net.adns", require "net.dns";
local config = require "core.configmanager";
local connect_timeout = config.get("*", "core", "s2s_timeout") or 60;
-local dns_timeout = config.get("*", "core", "dns_timeout") or 60;
+local dns_timeout = config.get("*", "core", "dns_timeout") or 15;
local max_dns_depth = config.get("*", "core", "dns_max_depth") or 3;
+dns.settimeout(dns_timeout);
+
+local prosody = _G.prosody;
incoming_s2s = {};
-_G.prosody.incoming_s2s = incoming_s2s;
+prosody.incoming_s2s = incoming_s2s;
local incoming_s2s = incoming_s2s;
module "s2smanager"
@@ -54,6 +56,7 @@ function compare_srv_priorities(a,b)
return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight);
end
+local bouncy_stanzas = { message = true, presence = true, iq = true };
local function bounce_sendq(session, reason)
local sendq = session.sendq;
if sendq then
@@ -67,13 +70,13 @@ local function bounce_sendq(session, reason)
};
for i, data in ipairs(sendq) do
local reply = data[2];
- local xmlns = reply.attr.xmlns;
- if not xmlns then
+ if reply and not(reply.attr.xmlns) and bouncy_stanzas[reply.name] then
reply.attr.type = "error";
reply:tag("error", {type = "cancel"})
:tag("remote-server-not-found", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
if reason then
- reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):text("Connection failed: "..reason):up();
+ reply:tag("text", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"})
+ :text("Server-to-server connection failed: "..reason):up();
end
core_process_stanza(dummy, reply);
end
@@ -95,13 +98,14 @@ function send_to_host(from_host, to_host, data)
(host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host);
-- Queue stanza until we are able to send it
- if host.sendq then t_insert(host.sendq, {tostring(data), st.reply(data)});
- else host.sendq = { {tostring(data), st.reply(data)} }; end
+ if host.sendq then t_insert(host.sendq, {tostring(data), data.attr.type ~= "error" and data.attr.type ~= "result" and st.reply(data)});
+ else host.sendq = { {tostring(data), data.attr.type ~= "error" and data.attr.type ~= "result" and st.reply(data)} }; end
host.log("debug", "stanza [%s] queued ", data.name);
elseif host.type == "local" or host.type == "component" then
log("error", "Trying to send a stanza to ourselves??")
log("error", "Traceback: %s", get_traceback());
log("error", "Stanza: %s", tostring(data));
+ return false;
else
(host.log or log)("debug", "going to send stanza to "..to_host.." from "..from_host);
-- FIXME
@@ -117,13 +121,18 @@ function send_to_host(from_host, to_host, data)
local host_session = new_outgoing(from_host, to_host);
-- Store in buffer
- host_session.sendq = { {tostring(data), st.reply(data)} };
+ host_session.sendq = { {tostring(data), data.attr.type ~= "error" and data.attr.type ~= "result" and st.reply(data)} };
log("debug", "stanza [%s] queued until connection complete", tostring(data.name));
if (not host_session.connecting) and (not host_session.conn) then
log("warn", "Connection to %s failed already, destroying session...", to_host);
- destroy_session(host_session);
+ if not destroy_session(host_session, "Connection failed") then
+ -- Already destroyed, we need to bounce our stanza
+ bounce_sendq(host_session, host_session.destruction_reason);
+ end
+ return false;
end
end
+ return true;
end
local open_sessions = 0;
@@ -137,7 +146,19 @@ function new_incoming(conn)
open_sessions = open_sessions + 1;
local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
session.log = log;
- session.sends2s = function (t) log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)")); w(conn, tostring(t)); end
+ local filter = initialize_filters(session);
+ session.sends2s = function (t)
+ log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)"));
+ if t.name then
+ t = filter("stanzas/out", t);
+ end
+ if t then
+ t = filter("bytes/out", tostring(t));
+ if t then
+ return w(conn, t);
+ end
+ end
+ end
incoming_s2s[session] = true;
add_task(connect_timeout, function ()
if session.conn ~= conn or
@@ -145,7 +166,7 @@ function new_incoming(conn)
return; -- Ok, we're connect[ed|ing]
end
-- Not connected, need to close session and clean up
- (session.log or log)("warn", "Destroying incomplete session %s->%s due to inactivity",
+ (session.log or log)("debug", "Destroying incomplete session %s->%s due to inactivity",
session.from_host or "(unknown)", session.to_host or "(unknown)");
session:close("connection-timeout");
end);
@@ -159,6 +180,8 @@ function new_outgoing(from_host, to_host, connect)
hosts[from_host].s2sout[to_host] = host_session;
+ host_session.close = destroy_session; -- This gets replaced by xmppserver_listener later
+
local log;
do
local conn_name = "s2sout"..tostring(host_session):match("[a-f0-9]*$");
@@ -166,9 +189,15 @@ function new_outgoing(from_host, to_host, connect)
host_session.log = log;
end
+ initialize_filters(host_session);
+
if connect ~= false then
-- Kick the connection attempting machine into life
- attempt_connection(host_session);
+ if not attempt_connection(host_session) then
+ -- Intentionally not returning here, the
+ -- session is needed, connected or not
+ destroy_session(host_session);
+ end
end
if not host_session.sends2s then
@@ -234,13 +263,6 @@ function attempt_connection(host_session, err)
end
end, "_xmpp-server._tcp."..connect_host..".", "SRV");
- -- Set handler for DNS timeout
- add_task(dns_timeout, function ()
- if handle then
- adns.cancel(handle, true);
- end
- end);
-
return true; -- Attempt in progress
elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV
host_session.srv_choice = host_session.srv_choice + 1;
@@ -265,7 +287,7 @@ end
function try_connect(host_session, connect_host, connect_port)
host_session.connecting = true;
local handle;
- handle = adns.lookup(function (reply)
+ handle = adns.lookup(function (reply, err)
handle = nil;
host_session.connecting = nil;
@@ -283,23 +305,23 @@ function try_connect(host_session, connect_host, connect_port)
if reply and reply[#reply] and reply[#reply].a then
log("debug", "DNS reply for %s gives us %s", connect_host, reply[#reply].a);
- return make_connect(host_session, reply[#reply].a, connect_port);
+ local ok, err = make_connect(host_session, reply[#reply].a, connect_port);
+ if not ok then
+ if not attempt_connection(host_session, err or "closed") then
+ err = err and (": "..err) or "";
+ destroy_session(host_session, "Connection failed"..err);
+ end
+ end
else
log("debug", "DNS lookup failed to get a response for %s", connect_host);
if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can
log("debug", "No other records to try for %s - destroying", host_session.to_host);
- destroy_session(host_session, "DNS resolution failed"); -- End of the line, we can't
+ err = err and (": "..err) or "";
+ destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
end
end
end, connect_host, "A", "IN");
- -- Set handler for DNS timeout
- add_task(dns_timeout, function ()
- if handle then
- adns.cancel(handle, true);
- end
- end);
-
return true;
end
@@ -309,7 +331,7 @@ function make_connect(host_session, connect_host, connect_port)
local from_host, to_host = host_session.from_host, host_session.to_host;
- local conn, handler = socket.tcp()
+ local conn, handler = socket.tcp();
if not conn then
log("warn", "Failed to create outgoing connection, system error: %s", handler);
@@ -327,13 +349,25 @@ function make_connect(host_session, connect_host, connect_port)
conn = wrapclient(conn, connect_host, connect_port, cl, cl.default_mode or 1 );
host_session.conn = conn;
+ local filter = initialize_filters(host_session);
+ local w, log = conn.write, host_session.log;
+ host_session.sends2s = function (t)
+ log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?"));
+ if t.name then
+ t = filter("stanzas/out", t);
+ end
+ if t then
+ t = filter("bytes/out", tostring(t));
+ if t then
+ return w(conn, tostring(t));
+ end
+ end
+ end
+
-- Register this outgoing connection so that xmppserver_listener knows about it
-- otherwise it will assume it is a new incoming connection
cl.register_outgoing(conn, host_session);
- local w, log = conn.write, host_session.log;
- host_session.sends2s = function (t) log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); w(conn, tostring(t)); end
-
host_session:open_stream(from_host, to_host);
log("debug", "Connection attempt in progress...");
@@ -375,10 +409,22 @@ function streamopened(session, attr)
session.streamid = uuid_gen();
(session.log or log)("debug", "incoming s2s received <stream:stream>");
- if session.to_host and not hosts[session.to_host] then
- -- Attempting to connect to a host we don't serve
- session:close({ condition = "host-unknown"; text = "This host does not serve "..session.to_host });
- return;
+ if session.to_host then
+ if not hosts[session.to_host] then
+ -- Attempting to connect to a host we don't serve
+ session:close({
+ condition = "host-unknown";
+ text = "This host does not serve "..session.to_host
+ });
+ return;
+ elseif hosts[session.to_host].disallow_s2s then
+ -- Attempting to connect to a host that disallows s2s
+ session:close({
+ condition = "policy-violation";
+ text = "Server-to-server communication is not allowed to this host";
+ });
+ return;
+ end
end
send("<?xml version='1.0'?>");
send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
@@ -463,9 +509,11 @@ function make_authenticated(session, host)
elseif session.type == "s2sin_unauthed" then
session.type = "s2sin";
if host then
+ if not session.hosts[host] then session.hosts[host] = {}; end
session.hosts[host].authed = true;
end
elseif session.type == "s2sin" and host then
+ if not session.hosts[host] then session.hosts[host] = {}; end
session.hosts[host].authed = true;
else
return false;
@@ -486,8 +534,16 @@ function mark_connected(session)
session.log("info", session.direction.." s2s connection "..from.."->"..to.." complete");
local send_to_host = send_to_host;
- function session.send(data) send_to_host(to, from, data); end
+ function session.send(data) return send_to_host(to, from, data); end
+ local event_data = { session = session };
+ if session.type == "s2sout" then
+ prosody.events.fire_event("s2sout-established", event_data);
+ hosts[session.from_host].events.fire_event("s2sout-established", event_data);
+ else
+ prosody.events.fire_event("s2sin-established", event_data);
+ hosts[session.to_host].events.fire_event("s2sin-established", event_data);
+ end
if session.direction == "outgoing" then
if sendq then
@@ -512,9 +568,10 @@ local resting_session = { -- Resting, not dead
close = function (session)
session.log("debug", "Attempt to close already-closed session");
end;
+ filter = function (type, data) return data; end;
}; resting_session.__index = resting_session;
-function retire_session(session)
+function retire_session(session, reason)
local log = session.log or log;
for k in pairs(session) do
if k ~= "trace" and k ~= "log" and k ~= "id" then
@@ -522,6 +579,8 @@ function retire_session(session)
end
end
+ session.destruction_reason = reason;
+
function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); end
function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
return setmetatable(session, resting_session);
@@ -529,7 +588,7 @@ end
function destroy_session(session, reason)
if session.destroyed then return; end
- (session.log or log)("info", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host));
+ (session.log or log)("debug", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host));
if session.direction == "outgoing" then
hosts[session.from_host].s2sout[session.to_host] = nil;
@@ -538,7 +597,21 @@ function destroy_session(session, reason)
incoming_s2s[session] = nil;
end
- retire_session(session); -- Clean session until it is GC'd
+ local event_data = { session = session, reason = reason };
+ if session.type == "s2sout" then
+ prosody.events.fire_event("s2sout-destroyed", event_data);
+ if hosts[session.from_host] then
+ hosts[session.from_host].events.fire_event("s2sout-destroyed", event_data);
+ end
+ elseif session.type == "s2sin" then
+ prosody.events.fire_event("s2sin-destroyed", event_data);
+ if hosts[session.to_host] then
+ 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
return _M;
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index e1f1a802..426763f5 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -27,7 +27,8 @@ local nameprep = require "util.encodings".stringprep.nameprep;
local resourceprep = require "util.encodings".stringprep.resourceprep;
local nodeprep = require "util.encodings".stringprep.nodeprep;
-local fire_event = require "core.eventmanager".fire_event;
+local initialize_filters = require "util.filters".initialize;
+local fire_event = prosody.events.fire_event;
local add_task = require "util.timer".add_task;
local gettime = require "socket".gettime;
@@ -50,8 +51,20 @@ function new_session(conn)
end
open_sessions = open_sessions + 1;
log("debug", "open sessions now: ".. open_sessions);
+
+ local filter = initialize_filters(session);
local w = conn.write;
- session.send = function (t) w(conn, tostring(t)); end
+ session.send = function (t)
+ if t.name then
+ t = filter("stanzas/out", t);
+ end
+ if t then
+ t = filter("bytes/out", tostring(t));
+ if t then
+ return w(conn, t);
+ end
+ end
+ end
session.ip = conn:ip();
local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
session.log = logger.init(conn_name);
@@ -73,6 +86,7 @@ local resting_session = { -- Resting, not dead
close = function (session)
session.log("debug", "Attempt to close already-closed session");
end;
+ filter = function (type, data) return data; end;
}; resting_session.__index = resting_session;
function retire_session(session)
@@ -94,16 +108,23 @@ function destroy_session(session, err)
-- Remove session/resource from user's session list
if session.full_jid then
- hosts[session.host].sessions[session.username].sessions[session.resource] = nil;
+ 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(hosts[session.host].sessions[session.username].sessions) then
+ if not next(host_session.sessions[session.username].sessions) then
log("debug", "All resources of %s are now offline", session.username);
- hosts[session.host].sessions[session.username] = nil;
+ host_session.sessions[session.username] = nil;
bare_sessions[session.username..'@'..session.host] = nil;
end
- hosts[session.host].events.fire_event("resource-unbind", {session=session, error=err});
+ host_session.events.fire_event("resource-unbind", {session=session, error=err});
end
retire_session(session);
diff --git a/core/stanza_router.lua b/core/stanza_router.lua
index d6dd5306..406ad2f0 100644
--- a/core/stanza_router.lua
+++ b/core/stanza_router.lua
@@ -12,14 +12,35 @@ local hosts = _G.prosody.hosts;
local tostring = tostring;
local st = require "util.stanza";
local send_s2s = require "core.s2smanager".send_to_host;
-local modules_handle_stanza = require "core.modulemanager".handle_stanza;
-local component_handle_stanza = require "core.componentmanager".handle_stanza;
local jid_split = require "util.jid".split;
local jid_prepped_split = require "util.jid".prepped_split;
local full_sessions = _G.prosody.full_sessions;
local bare_sessions = _G.prosody.bare_sessions;
+local function handle_unhandled_stanza(host, origin, stanza)
+ local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type;
+ if name == "iq" and xmlns == "jabber:client" then
+ if stanza.attr.type == "get" or stanza.attr.type == "set" then
+ xmlns = stanza.tags[1].attr.xmlns or "jabber:client";
+ log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns);
+ else
+ log("debug", "Discarding %s from %s of type: %s", name, origin_type, stanza.attr.type);
+ return true;
+ end
+ end
+ if stanza.attr.xmlns == nil then
+ log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
+ if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" 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
+ log("warn", "Unhandled %s stream element: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it
+ origin:close("unsupported-stanza-type");
+ end
+end
+
+local iq_types = { set=true, get=true, result=true, error=true };
function core_process_stanza(origin, stanza)
(origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:top_tag())
@@ -27,8 +48,8 @@ function core_process_stanza(origin, stanza)
if stanza.attr.type == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log
if stanza.name == "iq" then
if not stanza.attr.id then stanza.attr.id = ""; end -- COMPAT Jabiru doesn't send the id attribute on roster requests
- if (stanza.attr.type == "set" or stanza.attr.type == "get") and (#stanza.tags ~= 1) then
- origin.send(st.error_reply(stanza, "modify", "bad-request"));
+ if not iq_types[stanza.attr.type] or ((stanza.attr.type == "set" or stanza.attr.type == "get") and (#stanza.tags ~= 1)) then
+ origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid IQ type or incorrect number of children"));
return;
end
end
@@ -114,7 +135,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
- modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
+ handle_unhandled_stanza(host or origin.host or origin.to_host, origin, stanza);
end
end
@@ -151,12 +172,7 @@ function core_post_stanza(origin, stanza, preevents)
if h then
if h.events.fire_event(stanza.name..to_type, event_data) then return; end -- do processing
if to_self and h.events.fire_event(stanza.name..'/self', event_data) then return; end -- do processing
-
- if h.type == "component" then
- component_handle_stanza(origin, stanza);
- return;
- end
- modules_handle_stanza(h.host, origin, stanza);
+ handle_unhandled_stanza(h.host, origin, stanza);
else
core_route_stanza(origin, stanza);
end
diff --git a/core/storagemanager.lua b/core/storagemanager.lua
new file mode 100644
index 00000000..c96ef3ec
--- /dev/null
+++ b/core/storagemanager.lua
@@ -0,0 +1,100 @@
+
+local error, type, pairs = error, type, pairs;
+local setmetatable = setmetatable;
+
+local config = require "core.configmanager";
+local datamanager = require "util.datamanager";
+local modulemanager = require "core.modulemanager";
+local multitable = require "util.multitable";
+local hosts = hosts;
+local log = require "util.logger".init("storagemanager");
+
+local prosody = prosody;
+
+module("storagemanager")
+
+local olddm = {}; -- maintain old datamanager, for backwards compatibility
+for k,v in pairs(datamanager) do olddm[k] = v; end
+_M.olddm = olddm;
+
+local null_storage_method = function () return false, "no data storage active"; end
+local null_storage_driver = setmetatable(
+ {
+ name = "null",
+ open = function (self) return self; end
+ }, {
+ __index = function (self, method)
+ return null_storage_method;
+ end
+ }
+);
+
+local stores_available = multitable.new();
+
+function initialize_host(host)
+ local host_session = hosts[host];
+ host_session.events.add_handler("item-added/data-driver", function (event)
+ local item = event.item;
+ stores_available:set(host, item.name, item);
+ end);
+
+ host_session.events.add_handler("item-removed/data-driver", function (event)
+ local item = event.item;
+ stores_available:set(host, item.name, nil);
+ end);
+end
+prosody.events.add_handler("host-activated", initialize_host, 101);
+
+function load_driver(host, driver_name)
+ if driver_name == "null" then
+ return null_storage_provider;
+ end
+ local driver = stores_available:get(host, driver_name);
+ if driver then return driver; end
+ local ok, err = modulemanager.load(host, "storage_"..driver_name);
+ if not ok then
+ log("error", "Failed to load storage driver plugin %s on %s: %s", driver_name, host, err);
+ end
+ return stores_available:get(host, driver_name);
+end
+
+function open(host, store, typ)
+ local storage = config.get(host, "core", "storage");
+ local driver_name;
+ local option_type = type(storage);
+ if option_type == "string" then
+ driver_name = storage;
+ elseif option_type == "table" then
+ driver_name = storage[store];
+ end
+ if not driver_name then
+ driver_name = config.get(host, "core", "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);
+ driver_name = "null";
+ driver = null_storage_driver;
+ end
+
+ local ret, err = driver:open(store, typ);
+ if not ret then
+ if err == "unsupported-store" then
+ log("debug", "Storage driver %s does not support store %s (%s), falling back to null driver",
+ driver_name, store, typ);
+ ret = null_storage_driver;
+ err = nil;
+ end
+ end
+ return ret, err;
+end
+
+function datamanager.load(username, host, datastore)
+ return open(host, datastore):get(username);
+end
+function datamanager.store(username, host, datastore, data)
+ return open(host, datastore):set(username, data);
+end
+
+return _M;
diff --git a/core/usermanager.lua b/core/usermanager.lua
index 698d2f10..0152afd7 100644
--- a/core/usermanager.lua
+++ b/core/usermanager.lua
@@ -6,95 +6,136 @@
-- COPYING file in the source package for more information.
--
-local datamanager = require "util.datamanager";
+local modulemanager = require "core.modulemanager";
local log = require "util.logger".init("usermanager");
local type = type;
-local error = error;
local ipairs = ipairs;
-local hashes = require "util.hashes";
local jid_bare = require "util.jid".bare;
local config = require "core.configmanager";
local hosts = hosts;
+local sasl_new = require "util.sasl".new;
-local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false;
+local prosody = _G.prosody;
+
+local setmetatable = setmetatable;
+
+local default_provider = "internal_plain";
module "usermanager"
-local function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
+function new_null_provider()
+ local function dummy() return nil, "method not implemented"; end;
+ local function dummy_get_sasl_handler() return sasl_new(nil, {}); end
+ return setmetatable({name = "null", get_sasl_handler = dummy_get_sasl_handler}, {
+ __index = function(self, method) return dummy; end
+ });
+end
-function validate_credentials(host, username, password, method)
- log("debug", "User '%s' is being validated", username);
- if is_cyrus(host) then return nil, "Legacy auth not supported with Cyrus SASL."; end
- local credentials = datamanager.load(username, host, "accounts") or {};
+local provider_mt = { __index = new_null_provider() };
- if method == nil then method = "PLAIN"; end
- if method == "PLAIN" and credentials.password then -- PLAIN, do directly
- if password == credentials.password then
- return true;
- else
- return nil, "Auth failed. Invalid username or password.";
+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, "core", "authentication") or default_provider;
+ if config.get(host, "core", "anonymous_login") then auth_provider = "anonymous"; end -- COMPAT 0.7
+ if provider.name == auth_provider then
+ host_session.users = setmetatable(provider, provider_mt);
end
- end
- -- must do md5
- -- make credentials md5
- local pwd = credentials.password;
- if not pwd then pwd = credentials.md5; else pwd = hashes.md5(pwd, true); end
- -- make password md5
- if method == "PLAIN" then
- password = hashes.md5(password or "", true);
- elseif method ~= "DIGEST-MD5" then
- return nil, "Unsupported auth method";
- end
- -- compare
- if password == pwd then
- return true;
- else
- return nil, "Auth failed. Invalid username or password.";
+ 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);
+ end
+ end);
+ host_session.events.add_handler("item-removed/auth-provider", function (event)
+ local provider = event.item;
+ if host_session.users == provider then
+ host_session.users = new_null_provider();
+ end
+ end);
+ host_session.users = new_null_provider(); -- Start with the default usermanager provider
+ local auth_provider = config.get(host, "core", "authentication") or default_provider;
+ if config.get(host, "core", "anonymous_login") then auth_provider = "anonymous"; end -- COMPAT 0.7
+ if auth_provider ~= "null" then
+ modulemanager.load(host, "auth_"..auth_provider);
end
+end;
+prosody.events.add_handler("host-activated", initialize_host, 100);
+
+function test_password(username, host, password)
+ return hosts[host].users.test_password(username, password);
end
function get_password(username, host)
- if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
- return (datamanager.load(username, host, "accounts") or {}).password
+ return hosts[host].users.get_password(username);
end
-function set_password(username, host, password)
- if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
- local account = datamanager.load(username, host, "accounts");
- if account then
- account.password = password;
- return datamanager.store(username, host, "accounts", account);
- end
- return nil, "Account not available.";
+
+function set_password(username, password, host)
+ return hosts[host].users.set_password(username, password);
end
function user_exists(username, host)
- if not(require_provisioning) and is_cyrus(host) then return true; end
- local account, err = datamanager.load(username, host, "accounts");
- return (account or err) ~= nil; -- FIXME also check for empty credentials
+ return hosts[host].users.user_exists(username);
end
function create_user(username, password, host)
- if not(require_provisioning) and is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
- return datamanager.store(username, host, "accounts", {password = password});
+ return hosts[host].users.create_user(username, password);
end
-function get_supported_methods(host)
- return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
+function delete_user(username, host)
+ return hosts[host].users.delete_user(username);
+end
+
+function get_sasl_handler(host)
+ return hosts[host].users.get_sasl_handler();
+end
+
+function get_provider(host)
+ return hosts[host].users;
end
function is_admin(jid, host)
+ if host and not hosts[host] then return false; end
+
+ local is_admin;
+ jid = jid_bare(jid);
host = host or "*";
- local admins = config.get(host, "core", "admins");
- if host ~= "*" and admins == config.get("*", "core", "admins") then
- return nil;
+
+ local host_admins = config.get(host, "core", "admins");
+ local global_admins = config.get("*", "core", "admins");
+
+ if host_admins and host_admins ~= global_admins then
+ if type(host_admins) == "table" then
+ for _,admin in ipairs(host_admins) do
+ if admin == jid then
+ is_admin = true;
+ break;
+ end
+ end
+ elseif host_admins then
+ log("error", "Option 'admins' for host '%s' is not a list", host);
+ end
end
- if type(admins) == "table" then
- jid = jid_bare(jid);
- for _,admin in ipairs(admins) do
- if admin == jid then return true; end
+
+ if not is_admin and global_admins then
+ if type(global_admins) == "table" then
+ for _,admin in ipairs(global_admins) do
+ if admin == jid then
+ is_admin = true;
+ break;
+ end
+ end
+ elseif global_admins then
+ log("error", "Global option 'admins' is not a list");
end
- elseif admins then log("warn", "Option 'admins' for host '%s' is not a table", host); end
- return nil;
+ 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);
+ end
+ return is_admin or false;
end
return _M;
diff --git a/core/xmlhandlers.lua b/core/xmlhandlers.lua
deleted file mode 100644
index 47db75d3..00000000
--- a/core/xmlhandlers.lua
+++ /dev/null
@@ -1,167 +0,0 @@
--- 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.
---
-
-
-
-require "util.stanza"
-
-local st = stanza;
-local tostring = tostring;
-local t_insert = table.insert;
-local t_concat = table.concat;
-
-local default_log = require "util.logger".init("xmlhandlers");
-
--- COMPAT: w/LuaExpat 1.1.0
-local lxp_supports_doctype = pcall(lxp.new, { StartDoctypeDecl = false });
-
-if not lxp_supports_doctype then
- default_log("warn", "The version of LuaExpat on your system leaves Prosody "
- .."vulnerable to denial-of-service attacks. You should upgrade to "
- .."LuaExpat 1.1.1 or higher as soon as possible. See "
- .."http://prosody.im/doc/depends#luaexpat for more information.");
-end
-
-local error = error;
-
-module "xmlhandlers"
-
-local ns_prefixes = {
- ["http://www.w3.org/XML/1998/namespace"] = "xml";
-};
-
-local xmlns_streams = "http://etherx.jabber.org/streams";
-
-local ns_separator = "\1";
-local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
-
-function init_xmlhandlers(session, stream_callbacks)
- local chardata = {};
- local xml_handlers = {};
- local log = session.log or default_log;
-
- local cb_streamopened = stream_callbacks.streamopened;
- local cb_streamclosed = stream_callbacks.streamclosed;
- local cb_error = stream_callbacks.error or function(session, e) error("XML stream error: "..tostring(e)); end;
- local cb_handlestanza = stream_callbacks.handlestanza;
-
- local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
- local stream_tag = stream_ns..ns_separator..(stream_callbacks.stream_tag or "stream");
- local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error");
-
- local stream_default_ns = stream_callbacks.default_ns;
-
- local stanza;
- function xml_handlers:StartElement(tagname, attr)
- if stanza and #chardata > 0 then
- -- We have some character data in the buffer
- stanza:text(t_concat(chardata));
- chardata = {};
- end
- local curr_ns,name = tagname:match(ns_pattern);
- if name == "" then
- curr_ns, name = "", curr_ns;
- end
-
- if curr_ns ~= stream_default_ns then
- attr.xmlns = curr_ns;
- end
-
- -- FIXME !!!!!
- for i=1,#attr do
- local k = attr[i];
- attr[i] = nil;
- local ns, nm = k:match(ns_pattern);
- if nm ~= "" then
- ns = ns_prefixes[ns];
- if ns then
- attr[ns..":"..nm] = attr[k];
- attr[k] = nil;
- end
- end
- end
-
- if not stanza then --if we are not currently inside a stanza
- if session.notopen then
- if tagname == stream_tag then
- if cb_streamopened then
- cb_streamopened(session, attr);
- end
- else
- -- Garbage before stream?
- cb_error(session, "no-stream");
- end
- return;
- end
- if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
- cb_error(session, "invalid-top-level-element");
- end
-
- stanza = st.stanza(name, attr);
- else -- we are inside a stanza, so add a tag
- attr.xmlns = nil;
- if curr_ns ~= stream_default_ns then
- attr.xmlns = curr_ns;
- end
- stanza:tag(name, attr);
- end
- end
- function xml_handlers:CharacterData(data)
- if stanza then
- t_insert(chardata, data);
- end
- end
- function xml_handlers:EndElement(tagname)
- if stanza then
- if #chardata > 0 then
- -- We have some character data in the buffer
- stanza:text(t_concat(chardata));
- chardata = {};
- end
- -- Complete stanza
- if #stanza.last_add == 0 then
- if tagname ~= stream_error_tag then
- cb_handlestanza(session, stanza);
- else
- cb_error(session, "stream-error", stanza);
- end
- stanza = nil;
- else
- stanza:up();
- end
- else
- if tagname == stream_tag then
- if cb_streamclosed then
- cb_streamclosed(session);
- end
- else
- local curr_ns,name = tagname:match(ns_pattern);
- if name == "" then
- curr_ns, name = "", curr_ns;
- end
- cb_error(session, "parse-error", "unexpected-element-close", name);
- end
- stanza, chardata = nil, {};
- end
- end
-
- local function restricted_handler()
- cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
- end
-
- if lxp_supports_doctype then
- xml_handlers.StartDoctypeDecl = restricted_handler;
- end
- xml_handlers.Comment = restricted_handler;
- xml_handlers.StartCdataSection = restricted_handler;
- xml_handlers.ProcessingInstruction = restricted_handler;
-
- return xml_handlers;
-end
-
-return init_xmlhandlers;