aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/actions.lua27
-rw-r--r--core/certmanager.lua65
-rw-r--r--core/componentmanager.lua19
-rw-r--r--core/configmanager.lua11
-rw-r--r--core/hostmanager.lua31
-rw-r--r--core/loggingmanager.lua12
-rw-r--r--core/modulemanager.lua120
-rw-r--r--core/objectmanager.lua68
-rw-r--r--core/rostermanager.lua26
-rw-r--r--core/s2smanager.lua99
-rw-r--r--core/sessionmanager.lua72
-rw-r--r--core/stanza_router.lua7
-rw-r--r--core/usermanager.lua23
-rw-r--r--core/xmlhandlers.lua205
14 files changed, 469 insertions, 316 deletions
diff --git a/core/actions.lua b/core/actions.lua
deleted file mode 100644
index ee5abc22..00000000
--- a/core/actions.lua
+++ /dev/null
@@ -1,27 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-local 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..3dd06585
--- /dev/null
+++ b/core/certmanager.lua
@@ -0,0 +1,65 @@
+local configmanager = require "core.configmanager";
+local log = require "util.logger".init("certmanager");
+local ssl = ssl;
+local ssl_newcontext = ssl and ssl.newcontext;
+
+local setmetatable, tostring = setmetatable, tostring;
+
+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.";
+ elseif reason == "(null)" or not reason then
+ reason = "Check that the file exists and the permissions are correct";
+ else
+ reason = "Reason: "..tostring(reason):lower();
+ end
+ log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
+ else
+ log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
+ end
+ end
+ return ctx, err;
+ elseif not ssl then
+ return nil, "LuaSec (required for encryption) was not found";
+ end
+ return nil, "No SSL/TLS configuration present for "..host;
+end
+
+function reload_ssl_config()
+ default_ssl_config = configmanager.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 660d26fe..48e27984 100644
--- a/core/componentmanager.lua
+++ b/core/componentmanager.lua
@@ -8,6 +8,7 @@
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;
@@ -16,6 +17,7 @@ local events_new = require "util.events".new;
local st = require "util.stanza";
local prosody, hosts = prosody, prosody.hosts;
local ssl = ssl;
+local uuid_gen = require "util.uuid".generate;
local pairs, setmetatable, type, tostring = pairs, setmetatable, type, tostring;
@@ -83,15 +85,16 @@ function create_component(host, component, events)
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
+ else
-- 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 }));
+ ssl_ctx = certmanager.create_context(host, "client");
+ ssl_ctx_in = certmanager.create_context(host, "server");
end
end
return { type = "component", host = host, connected = true, s2sout = {},
- ssl_ctx = ssl_ctx, ssl_ctx_in = ssl_ctx_in, events = events or events_new() };
+ 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)
@@ -100,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 6350d80b..54fb0a9a 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
+ if f then
local ok, err = parsers[format].load(f:read("*a"), filename);
f:close();
if ok then
@@ -95,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;
diff --git a/core/hostmanager.lua b/core/hostmanager.lua
index b549e6f7..c8928b27 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", options = "no_sslv2"; };
-local default_ssl_ctx_in = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
-
local log = require "util.logger".init("hostmanager");
local pairs, setmetatable = pairs, setmetatable;
@@ -36,7 +35,7 @@ local function load_enabled_hosts(config)
local activated_any_host;
for host, host_config in pairs(defined_hosts) do
- if host ~= "*" and (host_config.core.enabled == 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
@@ -53,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$") or option_name:match("_interface$") then
@@ -65,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 162d5b2b..3ec696d5 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 3ad02b30..8e62aecb 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -13,14 +13,13 @@ 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";
local hosts = hosts;
local prosody = prosody;
-local loadfile, pcall = loadfile, pcall;
+local loadfile, pcall, xpcall = loadfile, pcall, xpcall;
local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv;
local pairs, ipairs = pairs, ipairs;
local t_insert, t_concat = table.insert, table.concat;
@@ -28,7 +27,17 @@ local type = type;
local next = next;
local rawget = rawget;
local error = error;
-local tostring = tostring;
+local tostring, tonumber = tostring, tonumber;
+
+local debug_traceback = debug.traceback;
+local unpack, select = unpack, select;
+pcall = function(f, ...)
+ local n = select("#", ...);
+ local params = {...};
+ return xpcall(function() f(unpack(params, 1, n)) end, function(e) return tostring(e).."\n"..debug_traceback(); end);
+end
+
+local array, set = require "util.array", require "util.set";
local autoload_modules = {"presence", "message", "iq"};
@@ -126,6 +135,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 +166,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 +183,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
@@ -207,6 +218,7 @@ function unload(host, name, ...)
end
end
modulemap[host][name] = nil;
+ (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host });
return true;
end
@@ -287,7 +299,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
@@ -296,7 +308,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()
@@ -394,7 +406,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
@@ -409,6 +421,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)
@@ -449,19 +540,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 684ed807..00000000
--- a/core/objectmanager.lua
+++ /dev/null
@@ -1,68 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-local 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/rostermanager.lua b/core/rostermanager.lua
index 08ab3565..506cf205 100644
--- a/core/rostermanager.lua
+++ b/core/rostermanager.lua
@@ -93,15 +93,18 @@ function load_roster(username, host)
else -- Attempt to load roster for non-loaded user
log("debug", "load_roster: loading for offline user: "..username.."@"..host);
end
- roster = datamanager.load(username, host, "roster") or {};
+ local data, err = datamanager.load(username, host, "roster");
+ roster = data or {};
if user then user.roster = roster; end
- if not roster[false] then roster[false] = { }; end
+ if not roster[false] then roster[false] = { broken = err or nil }; 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;
+ if not err then
+ hosts[host].events.fire_event("roster-load", username, host, roster);
+ end
+ return roster, err;
end
function save_roster(username, host, roster)
@@ -114,8 +117,15 @@ function save_roster(username, host, roster)
--end
end
if roster then
- if not roster[false] then roster[false] = {}; end
- roster[false].version = (roster[false].version or 0) + 1;
+ local metadata = roster[false];
+ if not metadata then
+ metadata = {};
+ roster[false] = metadata;
+ end
+ if metadata.version ~= true then
+ metadata.version = (metadata.version or 0) + 1;
+ end
+ if roster[false].broken then return nil, "Not saving broken roster" end
return datamanager.store(username, host, "roster", roster);
end
log("warn", "save_roster: user had no roster to save");
@@ -181,9 +191,9 @@ function process_inbound_unsubscribe(username, host, jid)
end
function is_contact_subscribed(username, host, jid)
- local roster = load_roster(username, host);
+ local roster, err = load_roster(username, host);
local item = roster[jid];
- return item and (item.subscription == "from" or item.subscription == "both");
+ return item and (item.subscription == "from" or item.subscription == "both"), err;
end
function is_contact_pending_in(username, host, jid)
diff --git a/core/s2smanager.lua b/core/s2smanager.lua
index ea6fe96d..0c29da14 100644
--- a/core/s2smanager.lua
+++ b/core/s2smanager.lua
@@ -16,8 +16,10 @@ local socket = require "socket";
local format = string.format;
local t_insert, t_sort = table.insert, table.sort;
local get_traceback = debug.traceback;
-local tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber
- = tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber;
+local tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber,
+ setmetatable
+ = tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber,
+ setmetatable;
local idna_to_ascii = require "util.encodings".idna.to_ascii;
local connlisteners_get = require "net.connlisteners".get;
@@ -36,8 +38,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;
@@ -137,7 +137,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
@@ -145,16 +145,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;
@@ -165,10 +166,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
@@ -182,7 +185,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;
@@ -191,7 +193,7 @@ end
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;
+ local connect_host, connect_port = to_host and idna_to_ascii(to_host), 5269;
if not connect_host then
return false;
@@ -239,7 +241,6 @@ function attempt_connection(host_session, err)
end
end);
- log("debug", "DNS lookup for %s sent, waiting for response before we can connect", to_host);
return true; -- Attempt in progress
elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV
host_session.srv_choice = host_session.srv_choice + 1;
@@ -298,7 +299,7 @@ function try_connect(host_session, connect_host, connect_port)
adns.cancel(handle, true);
end
end);
-
+
return true;
end
@@ -323,7 +324,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
@@ -331,9 +332,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
@@ -342,13 +344,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;
@@ -372,13 +381,13 @@ function streamopened(session, attr)
return;
end
send("<?xml version='1.0'?>");
- send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
+ 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
@@ -393,13 +402,13 @@ 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
log("debug", "Sending s2s send_buffer now...");
for i, data in ipairs(send_buffer) do
- session.sends2s(data);
+ session.sends2s(tostring(data));
send_buffer[i] = nil;
end
end
@@ -415,16 +424,12 @@ function streamopened(session, attr)
end
end
end
-
session.notopen = nil;
end
function streamclosed(session)
- (session.log or log)("debug", "</stream:stream>");
- if session.sends2s then
- session.sends2s("</stream:stream>");
- end
- session.notopen = true;
+ (session.log or log)("debug", "Received </stream:stream>");
+ session:close();
end
function initiate_dialback(session)
@@ -435,7 +440,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)
@@ -498,9 +503,32 @@ function mark_connected(session)
end
end
-local function null_data_handler(conn, data) log("debug", "Discarding data from destroyed s2s session: %s", data); end
+local resting_session = { -- Resting, not dead
+ destroyed = true;
+ type = "s2s_destroyed";
+ open_stream = function (session)
+ session.log("debug", "Attempt to open stream on resting session");
+ end;
+ close = function (session)
+ session.log("debug", "Attempt to close already-closed session");
+ end;
+ }; resting_session.__index = resting_session;
+
+function retire_session(session)
+ local log = session.log or log;
+ for k in pairs(session) do
+ if k ~= "trace" and k ~= "log" and k ~= "id" then
+ session[k] = nil;
+ end
+ end
+
+ function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); end
+ function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
+ return setmetatable(session, resting_session);
+end
function destroy_session(session, reason)
+ if session.destroyed then return; end
(session.log or log)("info", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host));
if session.direction == "outgoing" then
@@ -510,12 +538,7 @@ function destroy_session(session, reason)
incoming_s2s[session] = nil;
end
- for k in pairs(session) do
- if k ~= "trace" then
- session[k] = nil;
- end
- end
- session.data = null_data_handler;
+ retire_session(session); -- Clean session until it is GC'd
end
return _M;
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index ad4ad0c9..e1f1a802 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -8,7 +8,7 @@
-local tonumber, tostring = tonumber, tostring;
+local tonumber, tostring, setmetatable = tonumber, tostring, setmetatable;
local ipairs, pairs, print, next= ipairs, pairs, print, next;
local format = import("string", "format");
@@ -25,6 +25,7 @@ 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 nodeprep = require "util.encodings".stringprep.nodeprep;
local fire_event = require "core.eventmanager".fire_event;
local add_task = require "util.timer".add_task;
@@ -50,8 +51,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,34 +67,51 @@ 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
+local resting_session = { -- Resting, not dead
+ destroyed = true;
+ type = "c2s_destroyed";
+ close = function (session)
+ session.log("debug", "Attempt to close already-closed session");
+ end;
+ }; resting_session.__index = resting_session;
+
+function retire_session(session)
+ local log = session.log or log;
+ for k in pairs(session) do
+ if k ~= "trace" and k ~= "log" and k ~= "id" then
+ session[k] = nil;
+ end
+ end
+
+ function session.send(data) log("debug", "Discarding data sent to resting session: %s", tostring(data)); end
+ function session.data(data) log("debug", "Discarding data received from resting session: %s", tostring(data)); end
+ return setmetatable(session, resting_session);
+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)");
+ if session.destroyed then return; end
-- 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
- if k ~= "trace" then
- session[k] = nil;
- end
- end
- session.data = null_data_handler;
+ retire_session(session);
end
function make_authenticated(session, username)
+ username = nodeprep(username);
+ if not username or #username == 0 then return nil, "Invalid username"; end
session.username = username;
if session.type == "c2s_unauthed" then
session.type = "c2s";
@@ -121,7 +139,7 @@ function bind_resource(session, resource)
local sessions = hosts[session.host].sessions[session.username].sessions;
local limit = config_get(session.host, "core", "max_resources") or 10;
if #sessions >= limit then
- return nil, "cancel", "conflict", "Resource limit reached; only "..limit.." resources allowed";
+ return nil, "cancel", "resource-constraint", "Resource limit reached; only "..limit.." resources allowed";
end
if sessions[resource] then
-- Resource conflict
@@ -159,7 +177,19 @@ function bind_resource(session, resource)
hosts[session.host].sessions[session.username].sessions[resource] = session;
full_sessions[session.full_jid] = session;
- session.roster = rm_load_roster(session.username, session.host);
+ local err;
+ session.roster, err = rm_load_roster(session.username, session.host);
+ if err then
+ full_sessions[session.full_jid] = nil;
+ hosts[session.host].sessions[session.username].sessions[resource] = nil;
+ session.full_jid = nil;
+ session.resource = nil;
+ if next(bare_sessions[session.username..'@'..session.host].sessions) == nil then
+ bare_sessions[session.username..'@'..session.host] = nil;
+ hosts[session.host].sessions[session.username] = nil;
+ end
+ return nil, "cancel", "internal-server-error", "Error loading roster";
+ end
hosts[session.host].events.fire_event("resource-bind", {session=session});
@@ -168,7 +198,12 @@ end
function streamopened(session, attr)
local send = session.send;
- session.host = attr.to or error("Client failed to specify destination hostname");
+ session.host = attr.to;
+ if not session.host then
+ session:close{ condition = "improper-addressing",
+ text = "A 'to' attribute is required on stream headers" };
+ return;
+ end
session.host = nameprep(session.host);
session.version = tonumber(attr.version) or 0;
session.streamid = uuid_generate();
@@ -193,6 +228,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);
@@ -200,8 +236,8 @@ function streamopened(session, attr)
end
function streamclosed(session)
- session.send("</stream:stream>");
- session.notopen = true;
+ session.log("debug", "Received </stream:stream>");
+ session:close();
end
function send_to_available_resources(user, host, stanza)
diff --git a/core/stanza_router.lua b/core/stanza_router.lua
index 7ebfb2bb..d6dd5306 100644
--- a/core/stanza_router.lua
+++ b/core/stanza_router.lua
@@ -123,7 +123,7 @@ function core_post_stanza(origin, stanza, preevents)
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';
@@ -131,6 +131,7 @@ function core_post_stanza(origin, stanza, preevents)
to_type = '/bare';
if node == origin.username and host == origin.host then
stanza.attr.to = nil;
+ to_self = true;
end
end
else
@@ -138,6 +139,7 @@ function core_post_stanza(origin, stanza, preevents)
to_type = '/host';
else
to_type = '/bare';
+ to_self = true;
end
end
@@ -148,6 +150,7 @@ function core_post_stanza(origin, stanza, preevents)
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);
@@ -179,7 +182,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
diff --git a/core/usermanager.lua b/core/usermanager.lua
index 6b19b651..698d2f10 100644
--- a/core/usermanager.lua
+++ b/core/usermanager.lua
@@ -14,11 +14,17 @@ local ipairs = ipairs;
local hashes = require "util.hashes";
local jid_bare = require "util.jid".bare;
local config = require "core.configmanager";
+local hosts = hosts;
+
+local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false;
module "usermanager"
+local function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
+
function validate_credentials(host, username, password, method)
log("debug", "User '%s' is being validated", username);
+ if is_cyrus(host) then return nil, "Legacy auth not supported with Cyrus SASL."; end
local credentials = datamanager.load(username, host, "accounts") or {};
if method == nil then method = "PLAIN"; end
@@ -48,14 +54,27 @@ function validate_credentials(host, username, password, method)
end
function get_password(username, host)
- return (datamanager.load(username, host, "accounts") or {}).password
+ if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
+ return (datamanager.load(username, host, "accounts") or {}).password
+end
+function set_password(username, host, password)
+ if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
+ local account = datamanager.load(username, host, "accounts");
+ if account then
+ account.password = password;
+ return datamanager.store(username, host, "accounts", account);
+ end
+ return nil, "Account not available.";
end
function user_exists(username, host)
- return datamanager.load(username, host, "accounts") ~= nil; -- FIXME also check for empty credentials
+ if not(require_provisioning) and is_cyrus(host) then return true; end
+ local account, err = datamanager.load(username, host, "accounts");
+ return (account or err) ~= nil; -- FIXME also check for empty credentials
end
function create_user(username, password, host)
+ if not(require_provisioning) and is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
return datamanager.store(username, host, "accounts", {password = password});
end
diff --git a/core/xmlhandlers.lua b/core/xmlhandlers.lua
index eb7e3ba1..d86ffe7d 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;
@@ -34,103 +32,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));
@@ -138,26 +125,44 @@ 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
- end
-
- local function restricted_handler(parser)
- cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
- if not parser:stop() then
- error("Failed to abort parsing");
+ 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
-
- if lxp_supports_doctype then
- xml_handlers.StartDoctypeDecl = restricted_handler;
+ end
+
+ local function restricted_handler(parser)
+ cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1.");
+ if not parser:stop() then
+ error("Failed to abort parsing");
end
- xml_handlers.Comment = restricted_handler;
- xml_handlers.ProcessingInstruction = restricted_handler;
-
+ end
+
+ if lxp_supports_doctype then
+ xml_handlers.StartDoctypeDecl = restricted_handler;
+ end
+ xml_handlers.Comment = restricted_handler;
+ xml_handlers.ProcessingInstruction = restricted_handler;
+
return xml_handlers;
end