aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/actions.lua27
-rw-r--r--core/certmanager.lua62
-rw-r--r--core/componentmanager.lua28
-rw-r--r--core/configmanager.lua24
-rw-r--r--core/hostmanager.lua38
-rw-r--r--core/loggingmanager.lua12
-rw-r--r--core/modulemanager.lua119
-rw-r--r--core/objectmanager.lua68
-rw-r--r--core/s2smanager.lua84
-rw-r--r--core/sessionmanager.lua19
-rw-r--r--core/stanza_router.lua15
-rw-r--r--core/xmlhandlers.lua181
12 files changed, 395 insertions, 282 deletions
diff --git a/core/actions.lua b/core/actions.lua
deleted file mode 100644
index 5c2525e0..00000000
--- a/core/actions.lua
+++ /dev/null
@@ -1,27 +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 actions = {};
-
-function register(path, t)
- local curr = actions;
- for comp in path:gmatch("([^/]+)/") do
- if curr[comp] == nil then
- curr[comp] = {};
- end
- curr = curr[comp];
- if type(curr) ~= "table" then
- return nil, "path-taken";
- end
- end
- curr[path:match("/([^/]+)$")] = t;
- return true;
-end
-
-return { actions = actions, register= register }; \ No newline at end of file
diff --git a/core/certmanager.lua b/core/certmanager.lua
new file mode 100644
index 00000000..5794ba6e
--- /dev/null
+++ b/core/certmanager.lua
@@ -0,0 +1,62 @@
+local configmanager = require "core.configmanager";
+local log = require "util.logger".init("certmanager");
+local ssl = ssl;
+local ssl_newcontext = ssl and ssl.newcontext;
+
+local setmetatable = setmetatable;
+
+local prosody = prosody;
+
+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");
+
+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.";
+ else
+ reason = "Reason: "..tostring(reason or "unknown"):lower();
+ end
+ 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
+ ssl = false
+ end
+ return ctx, err;
+ end
+ return nil;
+end
+
+function reload_ssl_config()
+ default_ssl_config = config.get("*", "core", "ssl");
+end
+
+prosody.events.add_handler("config-reloaded", reload_ssl_config);
+
+return _M;
diff --git a/core/componentmanager.lua b/core/componentmanager.lua
index a16c01d2..cc505894 100644
--- a/core/componentmanager.lua
+++ b/core/componentmanager.lua
@@ -8,15 +8,18 @@
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 hosts = hosts;
+local prosody, hosts = prosody, prosody.hosts;
+local ssl = ssl;
+local uuid_gen = require "util.uuid".generate;
-local pairs, type, tostring = pairs, type, tostring;
+local pairs, setmetatable, type, tostring = pairs, setmetatable, type, tostring;
local components = {};
@@ -73,18 +76,25 @@ end
function create_component(host, component, events)
-- TODO check for host well-formedness
- local ssl_ctx;
- if host then
+ 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, events = events or events_new() };
+ 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)
@@ -93,12 +103,16 @@ function register_component(host, component, session)
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);
diff --git a/core/configmanager.lua b/core/configmanager.lua
index 1fbe83b8..0f20fd3e 100644
--- a/core/configmanager.lua
+++ b/core/configmanager.lua
@@ -9,8 +9,9 @@
local _G = _G;
-local setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type =
- setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type;
+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 eventmanager = require "core.eventmanager";
@@ -67,7 +68,7 @@ function load(filename, format)
if parsers[format] and parsers[format].load then
local f, err = io.open(filename);
- if f then
+ if f then
local ok, err = parsers[format].load(f:read("*a"), filename);
f:close();
if ok then
@@ -94,6 +95,15 @@ function addparser(format, parser)
end
end
+-- _M needed to avoid name clash with local 'parsers'
+function _M.parsers()
+ local p = {};
+ for format in pairs(parsers) do
+ table.insert(p, format);
+ end
+ return p;
+end
+
-- Built-in Lua parser
do
local loadstring, pcall, setmetatable = _G.loadstring, _G.pcall, _G.setmetatable;
@@ -115,6 +125,10 @@ do
rawset(env, "__currenthost", "*") -- Default is global
function env.Host(name)
+ if rawget(config, name) and rawget(config[name].core, "component_module") then
+ error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s",
+ name, config[name].core.component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0);
+ end
rawset(env, "__currenthost", name);
-- Needs at least one setting to logically exist :)
set(name or "*", "core", "defined", true);
@@ -122,6 +136,10 @@ do
env.host = env.Host;
function env.Component(name)
+ if rawget(config, name) and rawget(config[name].core, "defined") and not rawget(config[name].core, "component_module") then
+ 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");
-- Don't load the global modules by default
set(name, "core", "load_global_modules", false);
diff --git a/core/hostmanager.lua b/core/hostmanager.lua
index f89eaeba..7071296f 100644
--- a/core/hostmanager.lua
+++ b/core/hostmanager.lua
@@ -9,20 +9,19 @@
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 uuid_gen = require "util.uuid".generate;
+
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, setmetatable = pairs, setmetatable;
@@ -33,12 +32,19 @@ local hosts_loaded_once;
local function load_enabled_hosts(config)
local defined_hosts = config or configmanager.getconfig();
+ local activated_any_host;
for host, host_config in pairs(defined_hosts) do
- if host ~= "*" and (host_config.core.enabled == nil or host_config.core.enabled) and not host_config.core.component_module then
+ if host ~= "*" and host_config.core.enabled ~= false and not host_config.core.component_module then
+ activated_any_host = true;
activate(host, host_config);
end
end
+
+ if not activated_any_host then
+ log("error", "No hosts defined in the config file. This may cause unexpected behaviour as no modules will be loaded.");
+ end
+
eventmanager.fire_event("hosts-activated", defined_hosts);
hosts_loaded_once = true;
end
@@ -46,11 +52,12 @@ end
eventmanager.add_event_hook("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))
+ 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();
};
for option_name in pairs(host_config.core) do
if option_name:match("_ports$") then
@@ -58,14 +65,9 @@ function activate(host, host_config)
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
-
+ 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);
end
diff --git a/core/loggingmanager.lua b/core/loggingmanager.lua
index 4154e1a7..1bf90db1 100644
--- a/core/loggingmanager.lua
+++ b/core/loggingmanager.lua
@@ -94,7 +94,7 @@ function apply_sink_rules(sink_type)
end
end
elseif type(logging_config) == "string" and (not logging_config:match("^%*")) and sink_type == "file" then
- -- User specified simply a filename, and the "file" sink type
+ -- User specified simply a filename, and the "file" sink type
-- was just added
for _, sink_config in pairs(default_file_logging) do
sink_config.filename = logging_config;
@@ -128,7 +128,7 @@ function get_levels(criteria, set)
return set;
elseif in_range then
set[level] = true;
- end
+ end
end
end
@@ -161,12 +161,12 @@ function log_sink_types.stdout()
if timestamps then
io_write(os_date(timestamps), " ");
end
- if ... then
+ if ... then
io_write(name, rep(" ", sourcewidth-namelen), level, "\t", format(message, ...), "\n");
else
io_write(name, rep(" ", sourcewidth-namelen), level, "\t", message, "\n");
end
- end
+ end
end
do
@@ -197,7 +197,7 @@ do
if timestamps then
io_write(os_date(timestamps), " ");
end
- if ... then
+ if ... then
io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", format(message, ...), "\n");
else
io_write(name, rep(" ", sourcewidth-namelen), getstring(logstyles[level], level), "\t", message, "\n");
@@ -237,7 +237,7 @@ function log_sink_types.file(config)
if timestamps then
write(logfile, os_date(timestamps), " ");
end
- if ... then
+ if ... then
write(logfile, name, "\t", level, "\t", format(message, ...), "\n");
else
write(logfile, name, "\t" , level, "\t", message, "\n");
diff --git a/core/modulemanager.lua b/core/modulemanager.lua
index 9cd56187..1174352b 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -13,7 +13,6 @@ local log = logger.init("modulemanager");
local eventmanager = require "core.eventmanager";
local config = require "core.configmanager";
local multitable_new = require "util.multitable".new;
-local register_actions = require "core.actions".register;
local st = require "util.stanza";
local pluginloader = require "util.pluginloader";
@@ -28,7 +27,9 @@ local type = type;
local next = next;
local rawget = rawget;
local error = error;
-local tostring = tostring;
+local tostring, tonumber = tostring, tonumber;
+
+local array, set = require "util.array", require "util.set";
local autoload_modules = {"presence", "message", "iq"};
@@ -126,6 +127,7 @@ function load(host, module_name, config)
local api_instance = setmetatable({ name = module_name, host = host, 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
@@ -156,6 +158,7 @@ function load(host, module_name, config)
log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil");
end
if success then
+ (hosts[api_instance.host] or prosody).events.fire_event("module-loaded", { module = module_name, host = host });
return true;
else -- load failed, unloading
unload(api_instance.host, module_name);
@@ -172,7 +175,7 @@ function is_loaded(host, name)
end
function unload(host, name, ...)
- local mod = get_module(host, name);
+ local mod = get_module(host, name);
if not mod then return nil, "module-not-loaded"; end
if module_has_method(mod, "unload") then
@@ -197,7 +200,17 @@ function unload(host, name, ...)
end
end
hooks:remove(host, name);
+ if mod.module.items then -- remove items
+ for key,t in pairs(mod.module.items) do
+ for i = #t,1,-1 do
+ local value = t[i];
+ t[i] = nil;
+ hosts[host].events.fire_event("item-removed/"..key, {source = self, item = value});
+ end
+ end
+ end
modulemap[host][name] = nil;
+ (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host });
return true;
end
@@ -278,7 +291,7 @@ function module_has_method(module, method)
end
function call_module_method(module, method, ...)
- if module_has_method(module, method) then
+ if module_has_method(module, method) then
local f = module.module[method];
return pcall(f, ...);
else
@@ -287,7 +300,7 @@ function call_module_method(module, method, ...)
end
----- API functions exposed to modules -----------
--- Must all be in api.*
+-- Must all be in api.*
-- Returns the name of the current module
function api:get_name()
@@ -385,7 +398,7 @@ function api:require(lib)
f, n = pluginloader.load_code(lib, lib..".lib.lua");
end
if not f then error("Failed to load plugin library '"..lib.."', error: "..n); end -- FIXME better error message
- setfenv(f, setmetatable({ module = self }, { __index = _G }));
+ setfenv(f, self.environment);
return f();
end
@@ -400,6 +413,85 @@ function api:get_option(name, default_value)
return value;
end
+function api:get_option_string(name, default_value)
+ local value = self:get_option(name, default_value);
+ if type(value) == "table" then
+ if #value > 1 then
+ self:log("error", "Config option '%s' does not take a list, using just the first item", name);
+ end
+ value = value[1];
+ end
+ if value == nil then
+ return nil;
+ end
+ return tostring(value);
+end
+
+function api:get_option_number(name, ...)
+ local value = self:get_option(name, ...);
+ if type(value) == "table" then
+ if #value > 1 then
+ self:log("error", "Config option '%s' does not take a list, using just the first item", name);
+ end
+ value = value[1];
+ end
+ local ret = tonumber(value);
+ if value ~= nil and ret == nil then
+ self:log("error", "Config option '%s' not understood, expecting a number", name);
+ end
+ return ret;
+end
+
+function api:get_option_boolean(name, ...)
+ local value = self:get_option(name, ...);
+ if type(value) == "table" then
+ if #value > 1 then
+ self:log("error", "Config option '%s' does not take a list, using just the first item", name);
+ end
+ value = value[1];
+ end
+ if value == nil then
+ return nil;
+ end
+ local ret = value == true or value == "true" or value == 1 or nil;
+ if ret == nil then
+ ret = (value == false or value == "false" or value == 0);
+ if ret then
+ ret = false;
+ else
+ ret = nil;
+ end
+ end
+ if ret == nil then
+ self:log("error", "Config option '%s' not understood, expecting true/false", name);
+ end
+ return ret;
+end
+
+function api:get_option_array(name, ...)
+ local value = self:get_option(name, ...);
+
+ if value == nil then
+ return nil;
+ end
+
+ if type(value) ~= "table" then
+ return array{ value }; -- Assume any non-list is a single-item list
+ end
+
+ return array():append(value); -- Clone
+end
+
+function api:get_option_set(name, ...)
+ local value = self:get_option_array(name, ...);
+
+ if value == nil then
+ return nil;
+ end
+
+ return set.new(value);
+end
+
local t_remove = _G.table.remove;
local module_items = multitable_new();
function api:add_item(key, value)
@@ -440,19 +532,4 @@ function api:get_host_items(key)
return result;
end
---------------------------------------------------------------------
-
-local actions = {};
-
-function actions.load(params)
- --return true, "Module loaded ("..params.module.." on "..params.host..")";
- return load(params.host, params.module);
-end
-
-function actions.unload(params)
- return unload(params.host, params.module);
-end
-
-register_actions("/modules", actions);
-
return _M;
diff --git a/core/objectmanager.lua b/core/objectmanager.lua
deleted file mode 100644
index e96cbd90..00000000
--- a/core/objectmanager.lua
+++ /dev/null
@@ -1,68 +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 new_multitable = require "util.multitable".new;
-local t_insert = table.insert;
-local t_concat = table.concat;
-local tostring = tostring;
-local unpack = unpack;
-local pairs = pairs;
-local error = error;
-local type = type;
-local _G = _G;
-
-local data = new_multitable();
-
-module "objectmanager"
-
-function set(...)
- return data:set(...);
-end
-function remove(...)
- return data:remove(...);
-end
-function get(...)
- return data:get(...);
-end
-
-local function get_path(path)
- if type(path) == "table" then return path; end
- local s = {};
- for part in tostring(path):gmatch("[%w_]+") do
- t_insert(s, part);
- end
- return s;
-end
-
-function get_object(path)
- path = get_path(path)
- return data:get(unpack(path)), path;
-end
-function set_object(path, object)
- path = get_path(path);
- data:set(unpack(path), object);
-end
-
-data:set("ls", function(_dir)
- local obj, dir = get_object(_dir);
- if not obj then error("object not found: " .. t_concat(dir, '/')); end
- local r = {};
- if type(obj) == "table" then
- for key, val in pairs(obj) do
- r[key] = type(val);
- end
- end
- return r;
-end);
-data:set("get", get_object);
-data:set("set", set_object);
-data:set("echo", function(...) return {...}; end);
-data:set("_G", _G);
-
-return _M;
diff --git a/core/s2smanager.lua b/core/s2smanager.lua
index bfa3069a..16ede7b6 100644
--- a/core/s2smanager.lua
+++ b/core/s2smanager.lua
@@ -36,8 +36,6 @@ local log = logger_init("s2smanager");
local sha256_hash = require "util.hashes".sha256;
-local dialback_secret = uuid_gen();
-
local adns, dns = require "net.adns", require "net.dns";
local config = require "core.configmanager";
local connect_timeout = config.get("*", "core", "s2s_timeout") or 60;
@@ -54,7 +52,7 @@ function compare_srv_priorities(a,b)
return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight);
end
-local function bounce_sendq(session)
+local function bounce_sendq(session, reason)
local sendq = session.sendq;
if sendq then
session.log("info", "sending error replies for "..#sendq.." queued stanzas because of failed outgoing connection to "..tostring(session.to_host));
@@ -72,6 +70,9 @@ local function bounce_sendq(session)
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();
+ end
core_process_stanza(dummy, reply);
end
sendq[i] = nil;
@@ -134,7 +135,7 @@ 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", tostring(t)); w(tostring(t)); end
+ session.sends2s = function (t) log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)")); w(conn, tostring(t)); end
incoming_s2s[session] = true;
add_task(connect_timeout, function ()
if session.conn ~= conn or
@@ -142,16 +143,17 @@ 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)("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, host = from_host,
- notopen = true, type = "s2sout_unauthed", direction = "outgoing" };
+function new_outgoing(from_host, to_host, connect)
+ local host_session = { to_host = to_host, from_host = from_host, host = from_host,
+ notopen = true, type = "s2sout_unauthed", direction = "outgoing",
+ open_stream = session_open_stream };
hosts[from_host].s2sout[to_host] = host_session;
@@ -162,10 +164,12 @@ function new_outgoing(from_host, to_host)
host_session.log = log;
end
- -- Kick the connection attempting machine
- attempt_connection(host_session);
+ if connect ~= false then
+ -- Kick the connection attempting machine into life
+ attempt_connection(host_session);
+ end
- if not host_session.sends2s then
+ if not host_session.sends2s then
-- A sends2s which buffers data (until the stream is opened)
-- note that data in this buffer will be sent before the stream is authed
-- and will not be ack'd in any way, successful or otherwise
@@ -179,7 +183,6 @@ function new_outgoing(from_host, to_host)
buffer[#buffer+1] = data;
log("debug", "Buffered item %d: %s", #buffer, tostring(data));
end
-
end
return host_session;
@@ -224,7 +227,7 @@ function attempt_connection(host_session, err)
if not ok then
if not attempt_connection(host_session, err) then
-- No more attempts will be made
- destroy_session(host_session);
+ destroy_session(host_session, err);
end
end
end, "_xmpp-server._tcp."..connect_host..".", "SRV");
@@ -284,7 +287,7 @@ function try_connect(host_session, connect_host, connect_port)
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
+ destroy_session(host_session, "DNS resolution failed"); -- End of the line, we can't
end
end
end, connect_host, "A", "IN");
@@ -295,12 +298,12 @@ function try_connect(host_session, connect_host, connect_port)
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);
+ (host_session.log or 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
local from_host, to_host = host_session.from_host, host_session.to_host;
@@ -320,7 +323,7 @@ function make_connect(host_session, connect_host, connect_port)
end
local cl = connlisteners_get("xmppserver");
- conn = wrapclient(conn, connect_host, connect_port, cl, cl.default_mode or 1, hosts[from_host].ssl_ctx, false );
+ conn = wrapclient(conn, connect_host, connect_port, cl, cl.default_mode or 1 );
host_session.conn = conn;
-- Register this outgoing connection so that xmppserver_listener knows about it
@@ -328,9 +331,10 @@ function make_connect(host_session, connect_host, connect_port)
cl.register_outgoing(conn, host_session);
local w, log = conn.write, host_session.log;
- host_session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end
+ 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);
- 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
@@ -339,13 +343,20 @@ function make_connect(host_session, connect_host, connect_port)
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.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
+function session_open_stream(session, from, to)
+ session.sends2s(st.stanza("stream:stream", {
+ xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
+ ["xmlns:stream"]='http://etherx.jabber.org/streams',
+ from=from, to=to, version='1.0', ["xml:lang"]='en'}):top_tag());
+end
+
function streamopened(session, attr)
local send = session.sends2s;
@@ -357,7 +368,6 @@ function streamopened(session, attr)
end
if session.version >= 1.0 and not (attr.to and attr.from) then
-
(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
@@ -369,19 +379,19 @@ function streamopened(session, attr)
session.streamid = uuid_gen();
(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, 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
+ 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, to=session.from_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
if session.version >= 1.0 then
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 });
+ hosts[session.to_host].events.fire_event("s2s-stream-features", { origin = 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
@@ -396,7 +406,7 @@ function streamopened(session, attr)
-- Send unauthed buffer
-- (stanzas which are fine to send before dialback)
- -- Note that this is *not* the stanza queue (which
+ -- Note that this is *not* the stanza queue (which
-- we can only send if auth succeeds) :)
local send_buffer = session.send_buffer;
if send_buffer and #send_buffer > 0 then
@@ -418,7 +428,6 @@ function streamopened(session, attr)
end
end
end
-
session.notopen = nil;
end
@@ -438,7 +447,7 @@ function initiate_dialback(session)
end
function generate_dialback(id, to, from)
- return sha256_hash(id..to..from..dialback_secret, true);
+ return sha256_hash(id..to..from..hosts[from].dialback_secret, true);
end
function verify_dialback(id, to, from, key)
@@ -446,6 +455,16 @@ function verify_dialback(id, to, from, key)
end
function make_authenticated(session, host)
+ if not session.secure then
+ local local_host = session.direction == "incoming" and session.to_host or session.from_host;
+ if config.get(local_host, "core", "s2s_require_encryption") then
+ session:close({
+ condition = "policy-violation",
+ text = "Encrypted server-to-server communication is required but was not "
+ ..((session.direction == "outgoing" and "offered") or "used")
+ });
+ end
+ end
if session.type == "s2sout_unauthed" then
session.type = "s2sout";
elseif session.type == "s2sin_unauthed" then
@@ -491,12 +510,14 @@ function mark_connected(session)
end
end
-function destroy_session(session)
+local function null_data_handler(conn, data) log("debug", "Discarding data from destroyed s2s session: %s", data); end
+
+function destroy_session(session, reason)
(session.log or log)("info", "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;
- bounce_sendq(session);
+ bounce_sendq(session, reason);
elseif session.direction == "incoming" then
incoming_s2s[session] = nil;
end
@@ -506,6 +527,7 @@ function destroy_session(session)
session[k] = nil;
end
end
+ session.data = null_data_handler;
end
return _M;
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index 5e7fe06d..29adcfbb 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -10,7 +10,6 @@
local tonumber, tostring = tonumber, tostring;
local ipairs, pairs, print, next= ipairs, pairs, print, next;
-local collectgarbage = collectgarbage;
local format = import("string", "format");
local hosts = hosts;
@@ -25,6 +24,7 @@ local uuid_generate = require "util.uuid".generate;
local rm_load_roster = require "core.rostermanager".load_roster;
local config_get = require "core.configmanager".get;
local nameprep = require "util.encodings".stringprep.nameprep;
+local resourceprep = require "util.encodings".stringprep.resourceprep;
local fire_event = require "core.eventmanager".fire_event;
local add_task = require "util.timer".add_task;
@@ -50,8 +50,8 @@ function new_session(conn)
open_sessions = open_sessions + 1;
log("debug", "open sessions now: ".. open_sessions);
local w = conn.write;
- session.send = function (t) w(tostring(t)); end
- session.ip = conn.ip();
+ session.send = function (t) w(conn, tostring(t)); end
+ session.ip = conn:ip();
local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
session.log = logger.init(conn_name);
@@ -66,21 +66,23 @@ function new_session(conn)
return session;
end
+local function null_data_handler(conn, data) log("debug", "Discarding data from destroyed c2s session: %s", data); end
+
function destroy_session(session, err)
(session.log or log)("info", "Destroying session for %s (%s@%s)", session.full_jid or "(unknown)", session.username or "(unknown)", session.host or "(unknown)");
-- Remove session/resource from user's session list
if session.full_jid then
- hosts[session.host].events.fire_event("resource-unbind", {session=session, error=err});
-
hosts[session.host].sessions[session.username].sessions[session.resource] = nil;
full_sessions[session.full_jid] = nil;
-
+
if not next(hosts[session.host].sessions[session.username].sessions) then
log("debug", "All resources of %s are now offline", session.username);
hosts[session.host].sessions[session.username] = nil;
bare_sessions[session.username..'@'..session.host] = nil;
end
+
+ hosts[session.host].events.fire_event("resource-unbind", {session=session, error=err});
end
for k in pairs(session) do
@@ -88,6 +90,7 @@ function destroy_session(session, err)
session[k] = nil;
end
end
+ session.data = null_data_handler;
end
function make_authenticated(session, username)
@@ -106,7 +109,8 @@ function bind_resource(session, resource)
if session.resource then return nil, "cancel", "already-bound", "Cannot bind multiple resources on a single connection"; end
-- We don't support binding multiple resources
- resource = resource or uuid_generate();
+ resource = resourceprep(resource);
+ resource = resource ~= "" and resource or uuid_generate();
--FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing
if not hosts[session.host].sessions[session.username] then
@@ -189,6 +193,7 @@ function streamopened(session, attr)
end
local features = st.stanza("stream:features");
+ hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
fire_event("stream-features", session, features);
send(features);
diff --git a/core/stanza_router.lua b/core/stanza_router.lua
index 00c37ed7..72ddebd1 100644
--- a/core/stanza_router.lua
+++ b/core/stanza_router.lua
@@ -98,7 +98,7 @@ function core_process_stanza(origin, stanza)
return; -- FIXME what should we do here? does this work with subdomains?
end
end
- core_post_stanza(origin, stanza);
+ core_post_stanza(origin, stanza, origin.full_jid);
else
local h = hosts[stanza.attr.to or origin.host or origin.to_host];
if h then
@@ -119,12 +119,12 @@ function core_process_stanza(origin, stanza)
end
end
-function core_post_stanza(origin, stanza)
+function core_post_stanza(origin, stanza, preevents)
local to = stanza.attr.to;
local node, host, resource = jid_split(to);
local to_bare = node and (node.."@"..host) or host; -- bare JID
- local to_type;
+ local to_type, to_self;
if node then
if resource then
to_type = '/full';
@@ -132,6 +132,7 @@ function core_post_stanza(origin, stanza)
to_type = '/bare';
if node == origin.username and host == origin.host then
stanza.attr.to = nil;
+ to_self = true;
end
end
else
@@ -139,16 +140,18 @@ function core_post_stanza(origin, stanza)
to_type = '/host';
else
to_type = '/bare';
+ to_self = true;
end
end
local event_data = {origin=origin, stanza=stanza};
- if origin.full_jid == stanza.attr.from then -- c2s connection
+ if preevents then -- c2s connection
if hosts[origin.host].events.fire_event('pre-'..stanza.name..to_type, event_data) then return; end -- do preprocessing
end
local h = hosts[to_bare] or hosts[host or origin.host];
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);
@@ -180,7 +183,7 @@ function core_route_stanza(origin, stanza)
local xmlns = stanza.attr.xmlns;
--stanza.attr.xmlns = "jabber:server";
stanza.attr.xmlns = nil;
- log("debug", "sending s2s stanza: %s", tostring(stanza));
+ log("debug", "sending s2s stanza: %s", tostring(stanza.top_tag and stanza:top_tag()) or stanza);
send_s2s(origin.host, host, stanza); -- TODO handle remote routing errors
stanza.attr.xmlns = xmlns; -- reset
else
@@ -191,6 +194,6 @@ function core_route_stanza(origin, stanza)
log("debug", "Routing outgoing stanza for %s to %s", from_host, host);
send_s2s(from_host, host, stanza);
else
- log("warn", "received stanza from unhandled connection type: %s", origin.type);
+ log("warn", "received %s stanza from unhandled connection type: %s", tostring(stanza.name), tostring(origin.type));
end
end
diff --git a/core/xmlhandlers.lua b/core/xmlhandlers.lua
index 82c2d0b8..77f00bea 100644
--- a/core/xmlhandlers.lua
+++ b/core/xmlhandlers.lua
@@ -12,8 +12,6 @@ require "util.stanza"
local st = stanza;
local tostring = tostring;
-local pairs = pairs;
-local ipairs = ipairs;
local t_insert = table.insert;
local t_concat = table.concat;
@@ -24,103 +22,92 @@ local error = error;
module "xmlhandlers"
local ns_prefixes = {
- ["http://www.w3.org/XML/1998/namespace"] = "xml";
- }
+ ["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 ns_stack = { "" };
- local curr_tag;
- 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_tag = stream_callbacks.stream_tag;
- 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("^([^\1]*)\1?(.*)$");
- if name == "" then
- curr_ns, name = "", curr_ns;
- end
+ 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("^([^\1]*)\1?(.*)$");
- 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);
- curr_tag = stanza;
- 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
+ if curr_ns ~= stream_default_ns then
+ attr.xmlns = curr_ns;
end
- function xml_handlers:CharacterData(data)
- if stanza then
- t_insert(chardata, data);
+
+ -- 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
- function xml_handlers:EndElement(tagname)
- local curr_ns,name = tagname:match("^([^\1]*)\1?(.*)$");
- if name == "" then
- curr_ns, name = "", curr_ns;
- end
- if (not stanza) or (#stanza.last_add > 0 and name ~= stanza.last_add[#stanza.last_add].name) then
+
+ if not stanza then --if we are not currently inside a stanza
+ if session.notopen then
if tagname == stream_tag then
- if cb_streamclosed then
- cb_streamclosed(session);
+ if cb_streamopened then
+ cb_streamopened(session, attr);
end
- elseif name == "error" then
- cb_error(session, "stream-error", stanza);
else
- cb_error(session, "parse-error", "unexpected-element-close", name);
+ -- Garbage before stream?
+ cb_error(session, "no-stream");
end
- stanza, chardata = nil, {};
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));
@@ -128,12 +115,30 @@ function init_xmlhandlers(session, stream_callbacks)
end
-- Complete stanza
if #stanza.last_add == 0 then
- cb_handlestanza(session, stanza);
+ 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
return xml_handlers;
end