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