aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/actions.lua27
-rw-r--r--core/certmanager.lua35
-rw-r--r--core/componentmanager.lua27
-rw-r--r--core/configmanager.lua15
-rw-r--r--core/hostmanager.lua29
-rw-r--r--core/loggingmanager.lua6
-rw-r--r--core/modulemanager.lua116
-rw-r--r--core/objectmanager.lua68
-rw-r--r--core/s2smanager.lua97
-rw-r--r--core/sessionmanager.lua43
-rw-r--r--core/stanza_router.lua10
-rw-r--r--core/xmlhandlers.lua181
12 files changed, 369 insertions, 285 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..e5fabdce
--- /dev/null
+++ b/core/certmanager.lua
@@ -0,0 +1,35 @@
+local configmanager = require "core.configmanager";
+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 get_context(host, mode, config)
+ local ssl_config = config and config.core.ssl or default_ssl_config;
+ if ssl and ssl_config then
+ return ssl_newcontext(setmetatable(ssl_config, mode == "client" and default_ssl_ctx_mt or default_ssl_ctx_in_mt));
+ 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..a7c24617 100644
--- a/core/componentmanager.lua
+++ b/core/componentmanager.lua
@@ -14,9 +14,11 @@ 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 +75,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;
+ elseif prosody.global_ssl_ctx then
+ -- We have no cert, and no parent host to borrow a cert from
+ -- Use global/default cert if there is one
+ ssl_ctx = ssl.newcontext(prosody.global_ssl_ctx);
+ ssl_ctx_in = ssl.newcontext(setmetatable({ mode = "server" }, { __index = prosody.global_ssl_ctx }));
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 +102,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..ada14051 100644
--- a/core/configmanager.lua
+++ b/core/configmanager.lua
@@ -9,8 +9,8 @@
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 =
+ setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table;
local eventmanager = require "core.eventmanager";
@@ -67,7 +67,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 +94,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;
diff --git a/core/hostmanager.lua b/core/hostmanager.lua
index f89eaeba..038085c3 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;
@@ -46,11 +45,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 +58,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.get_context(host, "client", host_config); -- for outgoing connections
+ hosts[host].ssl_ctx_in = certmanager.get_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 c26fdc71..4154e1a7 100644
--- a/core/loggingmanager.lua
+++ b/core/loggingmanager.lua
@@ -17,6 +17,12 @@ 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;
+if os.getenv("__FLUSH_LOG") then
+ local io_flush = io.flush;
+ local _io_write = io_write;
+ io_write = function(...) _io_write(...); io_flush(); end
+end
+
local config = require "core.configmanager";
local eventmanager = require "core.eventmanager";
local logger = require "util.logger";
diff --git a/core/modulemanager.lua b/core/modulemanager.lua
index 9cd56187..d8418d83 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"};
@@ -156,6 +157,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[host].events.fire_event("module-loaded", { module = module_name, host = host });
return true;
else -- load failed, unloading
unload(api_instance.host, module_name);
@@ -172,7 +174,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 +199,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].events.fire_event("module-unloaded", { module = name, host = host });
return true;
end
@@ -278,7 +290,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 +299,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()
@@ -400,6 +412,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 +531,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 3613707c..d8ceb4ef 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;
@@ -52,7 +50,7 @@ module "s2smanager"
local function compare_srv_priorities(a,b) return a.priority < b.priority or 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));
@@ -70,6 +68,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;
@@ -79,15 +80,15 @@ local function bounce_sendq(session)
end
function send_to_host(from_host, to_host, data)
+ if not hosts[from_host] then
+ log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host);
+ return false;
+ end
local host = hosts[from_host].s2sout[to_host];
if host then
-- We have a connection to this host already
- if host.type == "s2sout_unauthed" and data.name ~= "db:verify" and ((not data.xmlns) or data.xmlns == "jabber:client" or data.xmlns == "jabber:server") then
+ if host.type == "s2sout_unauthed" and (data.name ~= "db:verify" or not host.dialback_key) and ((not data.xmlns) or data.xmlns == "jabber:client" or data.xmlns == "jabber:server") then
(host.log or log)("debug", "trying to send over unauthed s2sout to "..to_host);
- if not host.notopen and not host.dialback_key and host.sends2s then
- host.log("debug", "dialback had not been initiated");
- initiate_dialback(host);
- end
-- Queue stanza until we are able to send it
if host.sendq then t_insert(host.sendq, {tostring(data), st.reply(data)});
@@ -110,6 +111,7 @@ function send_to_host(from_host, to_host, data)
else
log("debug", "opening a new outgoing connection for this stanza");
local host_session = new_outgoing(from_host, to_host);
+
-- Store in buffer
host_session.sendq = { {tostring(data), st.reply(data)} };
log("debug", "stanza [%s] queued until connection complete", tostring(data.name));
@@ -131,7 +133,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
@@ -139,16 +141,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;
@@ -159,10 +162,12 @@ function new_outgoing(from_host, to_host)
host_session.log = log;
end
- -- This is the first call, can't fail (the first step is DNS lookup)
- 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
@@ -176,7 +181,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;
@@ -187,6 +191,10 @@ function attempt_connection(host_session, err)
local from_host, to_host = host_session.from_host, host_session.to_host;
local connect_host, connect_port = idna_to_ascii(to_host), 5269;
+ if not connect_host then
+ return false;
+ end
+
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;
@@ -217,7 +225,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");
@@ -277,7 +285,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");
@@ -288,7 +296,7 @@ function try_connect(host_session, connect_host, connect_port)
adns.cancel(handle, true);
end
end);
-
+
return true;
end
@@ -299,6 +307,11 @@ 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()
+
+ if not conn then
+ log("warn", "Failed to create outgoing connection, system error: %s", handler);
+ return false, handler;
+ end
conn:settimeout(0);
local success, err = conn:connect(connect_host, connect_port);
@@ -308,7 +321,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
@@ -316,9 +329,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
@@ -327,13 +341,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;
@@ -345,7 +366,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
@@ -357,17 +377,17 @@ 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 });
else
@@ -384,7 +404,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
@@ -406,7 +426,6 @@ function streamopened(session, attr)
end
end
end
-
session.notopen = nil;
end
@@ -426,7 +445,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)
@@ -434,6 +453,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
@@ -479,12 +508,12 @@ function mark_connected(session)
end
end
-function destroy_session(session)
+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
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index 08e70d44..8d520d12 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);
@@ -106,7 +106,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
@@ -201,22 +202,32 @@ function streamclosed(session)
end
function send_to_available_resources(user, host, stanza)
+ local jid = user.."@"..host;
local count = 0;
- local to = stanza.attr.to;
- stanza.attr.to = nil;
- local h = hosts[host];
- if h and h.type == "local" then
- local u = h.sessions[user];
- if u then
- for k, session in pairs(u.sessions) do
- if session.presence then
- session.send(stanza);
- count = count + 1;
- end
+ local user = bare_sessions[jid];
+ if user then
+ for k, session in pairs(user.sessions) do
+ if session.presence then
+ session.send(stanza);
+ count = count + 1;
+ end
+ end
+ end
+ return count;
+end
+
+function send_to_interested_resources(user, host, stanza)
+ local jid = user.."@"..host;
+ local count = 0;
+ local user = bare_sessions[jid];
+ if user then
+ for k, session in pairs(user.sessions) do
+ if session.interested then
+ session.send(stanza);
+ count = count + 1;
end
end
end
- stanza.attr.to = to;
return count;
end
diff --git a/core/stanza_router.lua b/core/stanza_router.lua
index 00c37ed7..12133a8e 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,7 +119,7 @@ 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
@@ -143,7 +143,7 @@ function core_post_stanza(origin, stanza)
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];
@@ -180,7 +180,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 +191,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 d679af97..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 not 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 ns and 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 not 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