aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/componentmanager.lua61
-rw-r--r--core/configmanager.lua8
-rw-r--r--core/discomanager.lua66
-rw-r--r--core/hostmanager.lua62
-rw-r--r--core/loggingmanager.lua1
-rw-r--r--core/modulemanager.lua186
-rw-r--r--core/rostermanager.lua67
-rw-r--r--core/s2smanager.lua116
-rw-r--r--core/sessionmanager.lua40
-rw-r--r--core/stanza_router.lua10
-rw-r--r--core/usermanager.lua15
-rw-r--r--core/xmlhandlers.lua10
12 files changed, 382 insertions, 260 deletions
diff --git a/core/componentmanager.lua b/core/componentmanager.lua
index 08868236..a16c01d2 100644
--- a/core/componentmanager.lua
+++ b/core/componentmanager.lua
@@ -6,18 +6,15 @@
-- COPYING file in the source package for more information.
--
-
-
-local prosody = prosody;
+local prosody = _G.prosody;
local log = require "util.logger".init("componentmanager");
local configmanager = require "core.configmanager";
local modulemanager = require "core.modulemanager";
-local core_route_stanza = core_route_stanza;
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 hosts = hosts;
-local serialize = require "util.serialization".serialize
local pairs, type, tostring = pairs, type, tostring;
@@ -25,45 +22,38 @@ local components = {};
local disco_items = require "util.multitable".new();
local NULL = {};
-require "core.discomanager".addDiscoItemsHandler("*host", function(reply, to, from, node)
- if #node == 0 and hosts[to] then
- for jid in pairs(disco_items:get(to) or NULL) do
- reply:tag("item", {jid = jid}):up();
- end
- return true;
- end
-end);
-
-prosody.events.add_handler("server-starting", function () core_route_stanza = _G.core_route_stanza; end);
module "componentmanager"
local function default_component_handler(origin, stanza)
- log("warn", "Stanza being handled by default component, bouncing error");
- if stanza.attr.type ~= "error" then
- core_route_stanza(nil, st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
+ 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
-local components_loaded_once;
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] = { type = "component", host = host, connected = false, s2sout = {}, events = events_new() };
+ 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
-prosody.events.add_handler("server-starting", load_enabled_components);
+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);
@@ -76,13 +66,25 @@ function handle_stanza(origin, stanza)
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: " .. (stanza.attr.to or serialize(stanza)));
+ 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)
+function create_component(host, component, events)
-- TODO check for host well-formedness
- return { type = "component", host = host, connected = true, s2sout = {}, events = events_new() };
+ local ssl_ctx;
+ if host 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;
+ end
+ end
+ return { type = "component", host = host, connected = true, s2sout = {},
+ ssl_ctx = ssl_ctx, events = events or events_new() };
end
function register_component(host, component, session)
@@ -90,7 +92,7 @@ function register_component(host, component, session)
local old_events = hosts[host] and hosts[host].events;
components[host] = component;
- hosts[host] = session or create_component(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
@@ -101,8 +103,8 @@ function register_component(host, component, session)
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
- -- FIXME only load for a.b.c if b.c has dialback, and/or check in config
modulemanager.load(host, "dialback");
+ modulemanager.load(host, "tls");
log("debug", "component added: "..host);
return session or hosts[host];
else
@@ -112,6 +114,7 @@ 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];
@@ -120,7 +123,7 @@ function deregister_component(host)
components[host] = default_component_handler;
else
-- Component not in config, or disabled, remove
- hosts[host] = nil;
+ hosts[host] = nil; -- FIXME do proper unload of all modules and other cleanup before removing
components[host] = nil;
end
-- remove from disco_items
@@ -138,4 +141,8 @@ 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 b7ee605f..1fbe83b8 100644
--- a/core/configmanager.lua
+++ b/core/configmanager.lua
@@ -68,7 +68,7 @@ function load(filename, format)
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"));
+ local ok, err = parsers[format].load(f:read("*a"), filename);
f:close();
if ok then
eventmanager.fire_event("config-reloaded", { filename = filename, format = format });
@@ -99,7 +99,7 @@ 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)
+ function parsers.lua.load(data, filename)
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; Component = true, component = true,
@@ -139,7 +139,7 @@ do
local f, err = io.open(file);
if f then
local data = f:read("*a");
- local ok, err = parsers.lua.load(data);
+ local ok, err = parsers.lua.load(data, file);
if not ok then error(err:gsub("%[string.-%]", file), 0); end
end
if not f then error("Error loading included "..file..": "..err, 0); end
@@ -147,7 +147,7 @@ do
end
env.include = env.Include;
- local chunk, err = loadstring(data);
+ local chunk, err = loadstring(data, "@"..filename);
if not chunk then
return nil, err;
diff --git a/core/discomanager.lua b/core/discomanager.lua
deleted file mode 100644
index 742907dd..00000000
--- a/core/discomanager.lua
+++ /dev/null
@@ -1,66 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-
-local helper = require "util.discohelper".new();
-local hosts = hosts;
-local jid_split = require "util.jid".split;
-local jid_bare = require "util.jid".bare;
-local usermanager_user_exists = require "core.usermanager".user_exists;
-local rostermanager_is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
-local print = print;
-
-do
- helper:addDiscoInfoHandler("*host", function(reply, to, from, node)
- if hosts[to] then
- reply:tag("identity", {category="server", type="im", name="Prosody"}):up();
- return true;
- end
- end);
- helper:addDiscoInfoHandler("*node", function(reply, to, from, node)
- local node, host = jid_split(to);
- if hosts[host] and rostermanager_is_contact_subscribed(node, host, jid_bare(from)) then
- reply:tag("identity", {category="account", type="registered"}):up();
- return true;
- end
- end);
- helper:addDiscoItemsHandler("*host", function(reply, to, from, node)
- if hosts[to] and hosts[to].type == "local" then
- return true;
- end
- end);
-end
-
-module "discomanager"
-
-function handle(stanza)
- return helper:handle(stanza);
-end
-
-function addDiscoItemsHandler(jid, func)
- return helper:addDiscoItemsHandler(jid, func);
-end
-
-function addDiscoInfoHandler(jid, func)
- return helper:addDiscoInfoHandler(jid, func);
-end
-
-function set(plugin, var, origin)
- -- TODO handle origin and host based on plugin.
- local handler = function(reply, to, from, node) -- service discovery
- if #node == 0 then
- reply:tag("feature", {var = var}):up();
- return true;
- end
- end
- addDiscoInfoHandler("*node", handler);
- addDiscoInfoHandler("*host", handler);
-end
-
-return _M;
diff --git a/core/hostmanager.lua b/core/hostmanager.lua
index ba363273..f89eaeba 100644
--- a/core/hostmanager.lua
+++ b/core/hostmanager.lua
@@ -6,15 +6,26 @@
-- COPYING file in the source package for more information.
--
+local ssl = ssl
local hosts = hosts;
local configmanager = require "core.configmanager";
local eventmanager = require "core.eventmanager";
+local modulemanager = require "core.modulemanager";
local events_new = require "util.events".new;
+if not _G.prosody.incoming_s2s then
+ require "core.s2smanager";
+end
+local incoming_s2s = _G.prosody.incoming_s2s;
+
+-- These are the defaults if not overridden in the config
+local default_ssl_ctx = { mode = "client", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
+local default_ssl_ctx_in = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
+
local log = require "util.logger".init("hostmanager");
-local pairs = pairs;
+local pairs, setmetatable = pairs, setmetatable;
module "hostmanager"
@@ -24,7 +35,7 @@ local function load_enabled_hosts(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) then
+ if host ~= "*" and (host_config.core.enabled == nil or host_config.core.enabled) and not host_config.core.component_module then
activate(host, host_config);
end
end
@@ -46,23 +57,57 @@ function activate(host, host_config)
log("warn", "%s: Option '%s' has no effect for virtual hosts - put it in global Host \"*\" instead", host, option_name);
end
end
+
+ if ssl then
+ local ssl_config = host_config.core.ssl or configmanager.get("*", "core", "ssl");
+ if ssl_config then
+ hosts[host].ssl_ctx = ssl.newcontext(setmetatable(ssl_config, { __index = default_ssl_ctx }));
+ hosts[host].ssl_ctx_in = ssl.newcontext(setmetatable(ssl_config, { __index = default_ssl_ctx_in }));
+ end
+ end
+
log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
eventmanager.fire_event("host-activated", host, host_config);
end
-function deactivate(host)
+function deactivate(host, reason)
local host_session = hosts[host];
log("info", "Deactivating host: %s", host);
eventmanager.fire_event("host-deactivating", host, host_session);
+ reason = reason or { condition = "host-gone", text = "This server has stopped serving "..host };
+
-- Disconnect local users, s2s connections
- for user, session_list in pairs(host_session.sessions) do
- for resource, session in pairs(session_list) do
- session:close("host-gone");
+ if host_session.sessions then
+ for username, user in pairs(host_session.sessions) do
+ for resource, session in pairs(user.sessions) do
+ log("debug", "Closing connection for %s@%s/%s", username, host, resource);
+ session:close(reason);
+ end
end
end
- -- Components?
-
+ if host_session.s2sout then
+ for remotehost, session in pairs(host_session.s2sout) do
+ if session.close then
+ log("debug", "Closing outgoing connection to %s", remotehost);
+ if session.srv_hosts then session.srv_hosts = nil; end
+ session:close(reason);
+ end
+ end
+ end
+ for remote_session in pairs(incoming_s2s) do
+ if remote_session.to_host == host then
+ log("debug", "Closing incoming connection from %s", remote_session.from_host or "<unknown>");
+ remote_session:close(reason);
+ end
+ end
+
+ if host_session.modules then
+ for module in pairs(host_session.modules) do
+ modulemanager.unload(host, module);
+ end
+ end
+
hosts[host] = nil;
eventmanager.fire_event("host-deactivated", host);
log("info", "Deactivated host: %s", host);
@@ -71,3 +116,4 @@ end
function getconfig(name)
end
+return _M;
diff --git a/core/loggingmanager.lua b/core/loggingmanager.lua
index d701511e..c26fdc71 100644
--- a/core/loggingmanager.lua
+++ b/core/loggingmanager.lua
@@ -187,6 +187,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/modulemanager.lua b/core/modulemanager.lua
index c2e6e68e..9cd56187 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -6,13 +6,10 @@
-- 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 addDiscoInfoHandler = require "core.discomanager".addDiscoInfoHandler;
local eventmanager = require "core.eventmanager";
local config = require "core.configmanager";
local multitable_new = require "util.multitable".new;
@@ -50,8 +47,6 @@ local handler_info = {};
local modulehelpers = setmetatable({}, { __index = _G });
-local features_table = multitable_new();
-local identities_table = multitable_new();
local handler_table = multitable_new();
local hooked = multitable_new();
local hooks = multitable_new();
@@ -61,22 +56,27 @@ 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
+ end
+
+ -- Load modules from global section
if config.get(host, "core", "load_global_modules") ~= false then
- -- Load modules from global section
local modules_enabled = config.get("*", "core", "modules_enabled");
- local modules_disabled = config.get(host, "core", "modules_disabled");
- local disabled_set = {};
if modules_enabled then
- if modules_disabled then
- for _, module in ipairs(modules_disabled) do
- disabled_set[module] = true;
- end
- end
- for _, module in ipairs(autoload_modules) do
- if not disabled_set[module] then
- load(host, module);
- end
- end
for _, module in ipairs(modules_enabled) do
if not disabled_set[module] and not is_loaded(host, module) then
load(host, module);
@@ -96,6 +96,7 @@ function load_modules_for_host(host)
end
end
eventmanager.add_event_hook("host-activated", load_modules_for_host);
+eventmanager.add_event_hook("component-activated", load_modules_for_host);
--
function load(host, module_name, config)
@@ -127,29 +128,39 @@ function load(host, module_name, config)
local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
setfenv(mod, pluginenv);
- if not hosts[host] then hosts[host] = { type = "component", host = host, connected = false, s2sout = {} }; end
-
- local success, ret = pcall(mod);
- if not success then
- log("error", "Error initialising module '%s': %s", module_name or "nil", ret or "nil");
- return nil, ret;
+ 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;
- if module_has_method(pluginenv, "load") then
- local ok, err = call_module_method(pluginenv, "load");
- if (not ok) and err then
- log("warn", "Error loading module '%s' on '%s': %s", module_name, host, err);
+ local success, err = pcall(mod);
+ if success then
+ if module_has_method(pluginenv, "load") then
+ success, err = call_module_method(pluginenv, "load");
+ if not success then
+ log("warn", "Error loading module '%s' on '%s': %s", module_name, host, err or "nil");
+ end
end
- end
- -- Use modified host, if the module set one
- modulemap[api_instance.host][module_name] = pluginenv;
-
- if api_instance.host == "*" and host ~= "*" then
- api_instance:set_global();
+ -- Use modified host, if the module set one
+ if api_instance.host == "*" and host ~= "*" then
+ modulemap[host][module_name] = nil;
+ modulemap["*"][module_name] = pluginenv;
+ api_instance:set_global();
+ end
+ else
+ log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil");
+ end
+ if success then
+ return true;
+ else -- load failed, unloading
+ unload(api_instance.host, module_name);
+ return nil, err;
end
-
- return true;
end
function get_module(host, name)
@@ -170,9 +181,6 @@ function unload(host, name, ...)
log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err);
end
end
- modulemap[host][name] = nil;
- features_table:remove(host, name);
- identities_table:remove(host, name);
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]);
@@ -189,6 +197,7 @@ function unload(host, name, ...)
end
end
hooks:remove(host, name);
+ modulemap[host][name] = nil;
return true;
end
@@ -235,7 +244,7 @@ function reload(host, name, ...)
end
function handle_stanza(host, origin, stanza)
- local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns, origin.type;
+ 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";
@@ -252,12 +261,13 @@ function handle_stanza(host, origin, stanza)
(handlers[1])(origin, stanza);
return true;
else
- log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
if stanza.attr.xmlns == "jabber:client" 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
@@ -328,50 +338,11 @@ function api:add_iq_handler(origin_type, xmlns, handler)
self:add_handler(origin_type, "iq", xmlns, handler);
end
-addDiscoInfoHandler("*host", function(reply, to, from, node)
- if #node == 0 then
- local done = {};
- for module, identities in pairs(identities_table:get(to) or NULL) do -- for each module
- for identity, attr in pairs(identities) do
- if not done[identity] then
- reply:tag("identity", attr):up(); -- TODO cache
- done[identity] = true;
- end
- end
- end
- for module, identities in pairs(identities_table:get("*") or NULL) do -- for each module
- for identity, attr in pairs(identities) do
- if not done[identity] then
- reply:tag("identity", attr):up(); -- TODO cache
- done[identity] = true;
- end
- end
- end
- for module, features in pairs(features_table:get(to) or NULL) do -- for each module
- for feature in pairs(features) do
- if not done[feature] then
- reply:tag("feature", {var = feature}):up(); -- TODO cache
- done[feature] = true;
- end
- end
- end
- for module, features in pairs(features_table:get("*") or NULL) do -- for each module
- for feature in pairs(features) do
- if not done[feature] then
- reply:tag("feature", {var = feature}):up(); -- TODO cache
- done[feature] = true;
- end
- end
- end
- return next(done) ~= nil;
- end
-end);
-
function api:add_feature(xmlns)
- features_table:set(self.host, self.name, xmlns, true);
+ self:add_item("feature", xmlns);
end
-function api:add_identity(category, type)
- identities_table:set(self.host, self.name, category.."\0"..type, {category = category, type = type});
+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, ...)
@@ -419,7 +390,54 @@ function api:require(lib)
end
function api:get_option(name, default_value)
- return config.get(self.host, self.name, name) or config.get(self.host, "core", name) or default_value;
+ local value = config.get(self.host, self.name, name);
+ if value == nil then
+ value = config.get(self.host, "core", name);
+ if value == nil then
+ value = default_value;
+ end
+ end
+ return value;
+end
+
+local t_remove = _G.table.remove;
+local module_items = multitable_new();
+function api:add_item(key, value)
+ self.items = self.items or {};
+ self.items[key] = self.items[key] or {};
+ t_insert(self.items[key], value);
+ self:fire_event("item-added/"..key, {source = self, item = value});
+end
+function api:remove_item(key, value)
+ local t = self.items and self.items[key] or NULL;
+ for i = #t,1,-1 do
+ if t[i] == value then
+ t_remove(self.items[key], i);
+ self:fire_event("item-removed/"..key, {source = self, item = value});
+ return value;
+ end
+ end
+end
+
+function api:get_host_items(key)
+ local result = {};
+ for mod_name, module in pairs(modulemap[self.host]) do
+ module = module.module;
+ if module.items then
+ for _, item in ipairs(module.items[key] or NULL) do
+ t_insert(result, item);
+ end
+ end
+ end
+ for mod_name, module in pairs(modulemap["*"]) do
+ module = module.module;
+ if module.items then
+ for _, item in ipairs(module.items[key] or NULL) do
+ t_insert(result, item);
+ end
+ end
+ end
+ return result;
end
--------------------------------------------------------------------
diff --git a/core/rostermanager.lua b/core/rostermanager.lua
index 0163e343..516983a9 100644
--- a/core/rostermanager.lua
+++ b/core/rostermanager.lua
@@ -18,6 +18,7 @@ local pairs, ipairs = pairs, ipairs;
local tostring = tostring;
local hosts = hosts;
+local bare_sessions = bare_sessions;
local datamanager = require "util.datamanager"
local st = require "util.stanza";
@@ -81,33 +82,41 @@ function roster_push(username, host, jid)
end
function load_roster(username, host)
- log("debug", "load_roster: asked for: "..username.."@"..host);
+ local jid = username.."@"..host;
+ log("debug", "load_roster: asked for: "..jid);
+ local user = bare_sessions[jid];
local roster;
- if hosts[host] and hosts[host].sessions[username] then
- roster = hosts[host].sessions[username].roster;
- if not roster then
- log("debug", "load_roster: loading for new user: "..username.."@"..host);
- roster = datamanager.load(username, host, "roster") or {};
- if not roster[false] then roster[false] = { }; end
- hosts[host].sessions[username].roster = roster;
- hosts[host].events.fire_event("roster-load", username, host, roster);
- end
- return roster;
+ if user then
+ roster = user.roster;
+ if roster then return roster; end
+ log("debug", "load_roster: loading for new user: "..username.."@"..host);
+ else -- Attempt to load roster for non-loaded user
+ log("debug", "load_roster: loading for offline user: "..username.."@"..host);
end
-
- -- Attempt to load roster for non-loaded user
- log("debug", "load_roster: loading for offline user: "..username.."@"..host);
roster = datamanager.load(username, host, "roster") or {};
+ if user then user.roster = roster; end
+ if not roster[false] then roster[false] = { }; end
+ if roster[jid] then
+ roster[jid] = nil;
+ log("warn", "roster for "..jid.." has a self-contact");
+ end
hosts[host].events.fire_event("roster-load", username, host, roster);
return roster;
end
-function save_roster(username, host)
+function save_roster(username, host, roster)
log("debug", "save_roster: saving roster for "..username.."@"..host);
- if hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster then
- local roster = hosts[host].sessions[username].roster;
- roster[false].version = (roster[false].version or 1) + 1;
- return datamanager.store(username, host, "roster", hosts[host].sessions[username].roster);
+ if not roster then
+ roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
+ --if not roster then
+ -- --roster = load_roster(username, host);
+ -- return true; -- roster unchanged, no reason to save
+ --end
+ end
+ if roster then
+ if not roster[false] then roster[false] = {}; end
+ roster[false].version = (roster[false].version or 0) + 1;
+ return datamanager.store(username, host, "roster", roster);
end
log("warn", "save_roster: user had no roster to save");
return nil;
@@ -123,7 +132,7 @@ function process_inbound_subscription_approval(username, host, jid)
item.subscription = "both";
end
item.ask = nil;
- return datamanager.store(username, host, "roster", roster);
+ return save_roster(username, host, roster);
end
end
@@ -145,7 +154,7 @@ function process_inbound_subscription_cancellation(username, host, jid)
end
end
if changed then
- return datamanager.store(username, host, "roster", roster);
+ return save_roster(username, host, roster);
end
end
@@ -167,7 +176,7 @@ function process_inbound_unsubscribe(username, host, jid)
end
end
if changed then
- return datamanager.store(username, host, "roster", roster);
+ return save_roster(username, host, roster);
end
end
@@ -189,7 +198,7 @@ function set_contact_pending_in(username, host, jid, pending)
end
if not roster.pending then roster.pending = {}; end
roster.pending[jid] = true;
- return datamanager.store(username, host, "roster", roster);
+ return save_roster(username, host, roster);
end
function is_contact_pending_out(username, host, jid)
local roster = load_roster(username, host);
@@ -208,7 +217,7 @@ function set_contact_pending_out(username, host, jid) -- subscribe
end
item.ask = "subscribe";
log("debug", "set_contact_pending_out: saving roster; set "..username.."@"..host..".roster["..jid.."].ask=subscribe");
- return datamanager.store(username, host, "roster", roster);
+ return save_roster(username, host, roster);
end
function unsubscribe(username, host, jid)
local roster = load_roster(username, host);
@@ -223,7 +232,7 @@ function unsubscribe(username, host, jid)
elseif item.subscription == "to" then
item.subscription = "none";
end
- return datamanager.store(username, host, "roster", roster);
+ return save_roster(username, host, roster);
end
function subscribed(username, host, jid)
if is_contact_pending_in(username, host, jid) then
@@ -240,7 +249,7 @@ function subscribed(username, host, jid)
end
roster.pending[jid] = nil;
-- TODO maybe remove roster.pending if empty
- return datamanager.store(username, host, "roster", roster);
+ return save_roster(username, host, roster);
end -- TODO else implement optional feature pre-approval (ask = subscribed)
end
function unsubscribed(username, host, jid)
@@ -262,7 +271,7 @@ function unsubscribed(username, host, jid)
end
end
if changed then
- return datamanager.store(username, host, "roster", roster);
+ return save_roster(username, host, roster);
end
end
@@ -271,7 +280,7 @@ function process_outbound_subscription_request(username, host, jid)
local item = roster[jid];
if item and (item.subscription == "none" or item.subscription == "from") then
item.ask = "subscribe";
- return datamanager.store(username, host, "roster", roster);
+ return save_roster(username, host, roster);
end
end
@@ -280,7 +289,7 @@ end
local item = roster[jid];
if item and (item.subscription == "none" or item.subscription == "from" then
item.ask = "subscribe";
- return datamanager.store(username, host, "roster", roster);
+ return save_roster(username, host, roster);
end
end]]
diff --git a/core/s2smanager.lua b/core/s2smanager.lua
index 0589e024..3613707c 100644
--- a/core/s2smanager.lua
+++ b/core/s2smanager.lua
@@ -27,6 +27,7 @@ local st = require "stanza";
local stanza = st.stanza;
local nameprep = require "util.encodings".stringprep.nameprep;
+local fire_event = require "core.eventmanager".fire_event;
local uuid_gen = require "util.uuid".generate;
local logger_init = require "util.logger".init;
@@ -37,11 +38,14 @@ local sha256_hash = require "util.hashes".sha256;
local dialback_secret = uuid_gen();
-local adns = require "net.adns";
-
+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 max_dns_depth = config.get("*", "core", "dns_max_depth") or 3;
incoming_s2s = {};
+_G.prosody.incoming_s2s = incoming_s2s;
local incoming_s2s = incoming_s2s;
module "s2smanager"
@@ -126,13 +130,26 @@ function new_incoming(conn)
end
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", tostring(t)); w(tostring(t)); end
incoming_s2s[session] = true;
+ add_task(connect_timeout, function ()
+ if session.conn ~= conn or
+ session.type == "s2sin" then
+ 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.from_host or "(unknown)", session.to_host or "(unknown)");
+ session:close("connection-timeout");
+ end);
return session;
end
function new_outgoing(from_host, to_host)
- local host_session = { to_host = to_host, from_host = from_host, notopen = true, type = "s2sout_unauthed", direction = "outgoing" };
+ local host_session = { to_host = to_host, from_host = from_host, host = from_host,
+ notopen = true, type = "s2sout_unauthed", direction = "outgoing" };
+
hosts[from_host].s2sout[to_host] = host_session;
local log;
@@ -173,7 +190,7 @@ function attempt_connection(host_session, err)
if not err then -- This is our first attempt
log("debug", "First attempt to connect to %s, starting with SRV lookup...", to_host);
host_session.connecting = true;
- local answer, handle;
+ local handle;
handle = adns.lookup(function (answer)
handle = nil;
host_session.connecting = nil;
@@ -235,6 +252,47 @@ function attempt_connection(host_session, err)
end
function try_connect(host_session, connect_host, connect_port)
+ host_session.connecting = true;
+ local handle;
+ handle = adns.lookup(function (reply)
+ handle = nil;
+ host_session.connecting = nil;
+
+ -- COMPAT: This is a compromise for all you CNAME-(ab)users :)
+ if not (reply and reply[#reply] and reply[#reply].a) then
+ local count = max_dns_depth;
+ reply = dns.peek(connect_host, "CNAME", "IN");
+ while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do
+ log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count);
+ reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN");
+ count = count - 1;
+ end
+ end
+ -- end of CNAME resolving
+
+ 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);
+ 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); -- 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
+
+function make_connect(host_session, connect_host, connect_port)
host_session.log("info", "Beginning new connection attempt to %s (%s:%d)", host_session.to_host, connect_host, connect_port);
-- Ok, we're going to try to connect
@@ -257,11 +315,22 @@ function try_connect(host_session, connect_host, connect_port)
-- otherwise it will assume it is a new incoming connection
cl.register_outgoing(conn, host_session);
- local w = conn.write;
+ local w, log = conn.write, host_session.log;
host_session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end
conn.write(format([[<stream:stream xmlns='jabber:server' xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' from='%s' to='%s' version='1.0' xml:lang='en'>]], from_host, to_host));
log("debug", "Connection attempt in progress...");
+ add_task(connect_timeout, function ()
+ if host_session.conn ~= conn or
+ host_session.type == "s2sout" or
+ host_session.connecting then
+ return; -- Ok, we're connect[ed|ing]
+ end
+ -- Not connected, need to close session and clean up
+ (host_session.log or log)("warn", "Destroying incomplete session %s->%s due to inactivity",
+ host_session.from_host or "(unknown)", host_session.to_host or "(unknown)");
+ host_session:close("connection-timeout");
+ end);
return true;
end
@@ -269,10 +338,16 @@ function streamopened(session, attr)
local send = session.sends2s;
-- TODO: #29: SASL/TLS on s2s streams
- session.version = 0; --tonumber(attr.version) or 0;
+ session.version = tonumber(attr.version) or 0;
+
+ if session.secure == false then
+ session.secure = true;
+ end
if session.version >= 1.0 and not (attr.to and attr.from) then
- log("warn", (session.to_host or "(unknown)").." failed to specify 'to' or 'from' hostname as per RFC");
+
+ (session.log or log)("warn", "Remote of stream "..(session.from_host or "(unknown)").."->"..(session.to_host or "(unknown)")
+ .." failed to specify to (%s) and/or from (%s) hostname as per RFC", tostring(attr.to), tostring(attr.from));
end
if session.direction == "incoming" then
@@ -284,15 +359,23 @@ function streamopened(session, attr)
(session.log or log)("debug", "incoming s2s received <stream:stream>");
send("<?xml version='1.0'?>");
send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
- ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host }):top_tag());
+ ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
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;
end
if session.version >= 1.0 then
- send(st.stanza("stream:features")
- :tag("dialback", { xmlns='urn:xmpp:features:dialback' }):tag("optional"):up():up());
+ local features = st.stanza("stream:features");
+
+ if session.to_host then
+ hosts[session.to_host].events.fire_event("s2s-stream-features", { session = session, features = features });
+ else
+ (session.log or log)("warn", "No 'to' on stream header from %s means we can't offer any features", session.from_host or "unknown host");
+ end
+
+ log("debug", "Sending stream features: %s", tostring(features));
+ send(features);
end
elseif session.direction == "outgoing" then
-- If we are just using the connection for verifying dialback keys, we won't try and auth it
@@ -313,10 +396,14 @@ function streamopened(session, attr)
end
session.send_buffer = nil;
- if not session.dialback_verifying then
- initiate_dialback(session);
- else
- mark_connected(session);
+ -- If server is pre-1.0, don't wait for features, just do dialback
+ if session.version < 1.0 then
+ if not session.dialback_verifying then
+ log("debug", "Initiating dialback...");
+ initiate_dialback(session);
+ else
+ mark_connected(session);
+ end
end
end
@@ -366,6 +453,7 @@ function make_authenticated(session, host)
return true;
end
+-- Stream is authorised, and ready for normal stanzas
function mark_connected(session)
local sendq, send = session.sendq, session.sends2s;
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index 1b1b36df..08e70d44 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -11,7 +11,6 @@
local tonumber, tostring = tonumber, tostring;
local ipairs, pairs, print, next= ipairs, pairs, print, next;
local collectgarbage = collectgarbage;
-local m_random = import("math", "random");
local format = import("string", "format");
local hosts = hosts;
@@ -19,7 +18,8 @@ local full_sessions = full_sessions;
local bare_sessions = bare_sessions;
local modulemanager = require "core.modulemanager";
-local log = require "util.logger".init("sessionmanager");
+local logger = require "util.logger";
+local log = logger.init("sessionmanager");
local error = error;
local uuid_generate = require "util.uuid".generate;
local rm_load_roster = require "core.rostermanager".load_roster;
@@ -27,11 +27,13 @@ local config_get = require "core.configmanager".get;
local nameprep = require "util.encodings".stringprep.nameprep;
local fire_event = require "core.eventmanager".fire_event;
-
+local add_task = require "util.timer".add_task;
local gettime = require "socket".gettime;
local st = require "util.stanza";
+local c2s_timeout = config_get("*", "core", "c2s_timeout");
+
local newproxy = newproxy;
local getmetatable = getmetatable;
@@ -50,6 +52,17 @@ function new_session(conn)
local w = conn.write;
session.send = function (t) w(tostring(t)); end
session.ip = conn.ip();
+ local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
+ session.log = logger.init(conn_name);
+
+ if c2s_timeout then
+ add_task(c2s_timeout, function ()
+ if session.type == "c2s_unauthed" then
+ session:close("connection-timeout");
+ end
+ end);
+ end
+
return session;
end
@@ -154,31 +167,32 @@ function streamopened(session, attr)
session.host = attr.to or error("Client failed to specify destination hostname");
session.host = nameprep(session.host);
session.version = tonumber(attr.version) or 0;
- session.streamid = m_random(1000000, 99999999);
+ session.streamid = uuid_generate();
(session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
-
- send("<?xml version='1.0'?>");
- send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host));
if not hosts[session.host] then
-- We don't serve this host...
session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
return;
end
-
+
+ send("<?xml version='1.0'?>");
+ send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host));
+
+ (session.log or log)("debug", "Sent reply <stream:stream> to client");
+ session.notopen = nil;
+
-- If session.secure is *false* (not nil) then it means we /were/ encrypting
-- since we now have a new stream header, session is secured
if session.secure == false then
session.secure = true;
end
-
+
local features = st.stanza("stream:features");
fire_event("stream-features", session, features);
-
+
send(features);
-
- (session.log or log)("debug", "Sent reply <stream:stream> to client");
- session.notopen = nil;
+
end
function streamclosed(session)
diff --git a/core/stanza_router.lua b/core/stanza_router.lua
index dac098bb..00c37ed7 100644
--- a/core/stanza_router.lua
+++ b/core/stanza_router.lua
@@ -8,7 +8,7 @@
local log = require "util.logger".init("stanzarouter")
-local hosts = _G.hosts;
+local hosts = _G.prosody.hosts;
local tostring = tostring;
local st = require "util.stanza";
local send_s2s = require "core.s2smanager".send_to_host;
@@ -17,6 +17,9 @@ 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;
+
function core_process_stanza(origin, stanza)
(origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:top_tag())
@@ -26,7 +29,8 @@ function core_process_stanza(origin, stanza)
-- TODO verify validity of stanza (as well as JID validity)
if stanza.attr.type == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log
if stanza.name == "iq" then
- if (stanza.attr.type == "set" or stanza.attr.type == "get") and #stanza.tags ~= 1 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"));
return;
end
@@ -110,7 +114,7 @@ function core_process_stanza(origin, stanza)
end
if h.events.fire_event(event, {origin = origin, stanza = stanza}) then return; end
end
- if host and not hosts[host] then host = nil; end -- workaround for a Pidgin bug which sets 'to' to the SRV result
+ 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);
end
end
diff --git a/core/usermanager.lua b/core/usermanager.lua
index 6c36fa29..925ac774 100644
--- a/core/usermanager.lua
+++ b/core/usermanager.lua
@@ -6,10 +6,7 @@
-- COPYING file in the source package for more information.
--
-
-
-require "util.datamanager"
-local datamanager = datamanager;
+local datamanager = require "util.datamanager";
local log = require "util.logger".init("usermanager");
local type = type;
local error = error;
@@ -66,14 +63,18 @@ function get_supported_methods(host)
return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
end
-function is_admin(jid)
- local admins = config.get("*", "core", "admins");
+function is_admin(jid, host)
+ host = host or "*";
+ local admins = config.get(host, "core", "admins");
+ if host ~= "*" and admins == config.get("*", "core", "admins") then
+ return nil;
+ end
if type(admins) == "table" then
jid = jid_bare(jid);
for _,admin in ipairs(admins) do
if admin == jid then return true; end
end
- else log("debug", "Option core.admins is not a table"); end
+ elseif admins then log("warn", "Option 'admins' for host '%s' is not a table", host); end
return nil;
end
diff --git a/core/xmlhandlers.lua b/core/xmlhandlers.lua
index 7f47cf70..d679af97 100644
--- a/core/xmlhandlers.lua
+++ b/core/xmlhandlers.lua
@@ -29,7 +29,6 @@ local ns_prefixes = {
function init_xmlhandlers(session, stream_callbacks)
local ns_stack = { "" };
- local curr_ns, name = "";
local curr_tag;
local chardata = {};
local xml_handlers = {};
@@ -50,7 +49,7 @@ function init_xmlhandlers(session, stream_callbacks)
stanza:text(t_concat(chardata));
chardata = {};
end
- local curr_ns,name = tagname:match("^(.-)|?([^%|]-)$");
+ local curr_ns,name = tagname:match("^([^\1]*)\1?(.*)$");
if not name then
curr_ns, name = "", curr_ns;
end
@@ -63,7 +62,7 @@ function init_xmlhandlers(session, stream_callbacks)
for i=1,#attr do
local k = attr[i];
attr[i] = nil;
- local ns, nm = k:match("^([^|]+)|?([^|]-)$")
+ local ns, nm = k:match("^([^\1]*)\1?(.*)$");
if ns and nm then
ns = ns_prefixes[ns];
if ns then
@@ -105,7 +104,7 @@ function init_xmlhandlers(session, stream_callbacks)
end
end
function xml_handlers:EndElement(tagname)
- curr_ns,name = tagname:match("^(.-)|?([^%|]-)$");
+ local curr_ns,name = tagname:match("^([^\1]*)\1?(.*)$");
if not name then
curr_ns, name = "", curr_ns;
end
@@ -114,12 +113,13 @@ function init_xmlhandlers(session, stream_callbacks)
if cb_streamclosed then
cb_streamclosed(session);
end
- return;
elseif name == "error" then
cb_error(session, "stream-error", stanza);
else
cb_error(session, "parse-error", "unexpected-element-close", name);
end
+ stanza, chardata = nil, {};
+ return;
end
if #chardata > 0 then
-- We have some character data in the buffer