aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Wild <mwild1@gmail.com>2012-04-29 02:10:55 +0100
committerMatthew Wild <mwild1@gmail.com>2012-04-29 02:10:55 +0100
commitbd5f9b8cbfd168a21141607218446222ab9d8642 (patch)
treee78baabff21e9b6fefaeef4a709098ca30b35922
parent9b89441dd008a8b3fda12d9217ff6f9ba240df9a (diff)
parenta2055895d362fed58ff314820bb7fddd53e40436 (diff)
downloadprosody-bd5f9b8cbfd168a21141607218446222ab9d8642.tar.gz
prosody-bd5f9b8cbfd168a21141607218446222ab9d8642.zip
Merge 0.9->trunk
-rw-r--r--Makefile10
-rwxr-xr-xconfigure5
-rw-r--r--core/certmanager.lua2
-rw-r--r--core/configmanager.lua3
-rw-r--r--core/hostmanager.lua8
-rw-r--r--core/loggingmanager.lua36
-rw-r--r--core/moduleapi.lua342
-rw-r--r--core/modulemanager.lua428
-rw-r--r--core/portmanager.lua206
-rw-r--r--core/s2smanager.lua501
-rw-r--r--core/sessionmanager.lua61
-rw-r--r--core/stanza_router.lua28
-rw-r--r--core/storagemanager.lua2
-rw-r--r--core/usermanager.lua11
-rw-r--r--net/connlisteners.lua84
-rw-r--r--net/dns.lua1
-rw-r--r--net/http.lua52
-rw-r--r--net/http/codes.lua67
-rw-r--r--net/http/parser.lua139
-rw-r--r--net/http/server.lua273
-rw-r--r--net/httpclient_listener.lua66
-rw-r--r--net/httpserver.lua240
-rw-r--r--net/httpserver_listener.lua46
-rw-r--r--net/multiplex_listener.lua50
-rw-r--r--net/server_event.lua28
-rw-r--r--net/server_select.lua23
-rw-r--r--net/xmppcomponent_listener.lua220
-rw-r--r--net/xmppserver_listener.lua209
-rw-r--r--plugins/adhoc/adhoc.lib.lua11
-rw-r--r--plugins/adhoc/mod_adhoc.lua16
-rw-r--r--plugins/mod_admin_adhoc.lua4
-rw-r--r--plugins/mod_admin_telnet.lua113
-rw-r--r--plugins/mod_auth_anonymous.lua1
-rw-r--r--plugins/mod_auth_internal_hashed.lua35
-rw-r--r--plugins/mod_auth_internal_plain.lua12
-rw-r--r--plugins/mod_bosh.lua232
-rw-r--r--plugins/mod_c2s.lua (renamed from net/xmppclient_listener.lua)140
-rw-r--r--plugins/mod_component.lua333
-rw-r--r--plugins/mod_compression.lua6
-rw-r--r--plugins/mod_dialback.lua38
-rw-r--r--plugins/mod_http.lua113
-rw-r--r--plugins/mod_http_errors.lua77
-rw-r--r--plugins/mod_http_files.lua57
-rw-r--r--plugins/mod_httpserver.lua97
-rw-r--r--plugins/mod_iq.lua8
-rw-r--r--plugins/mod_message.lua1
-rw-r--r--plugins/mod_motd.lua21
-rw-r--r--plugins/mod_net_multiplex.lua70
-rw-r--r--plugins/mod_posix.lua2
-rw-r--r--plugins/mod_proxy65.lua187
-rw-r--r--plugins/mod_saslauth.lua1
-rw-r--r--plugins/mod_tls.lua2
-rw-r--r--plugins/muc/mod_muc.lua8
-rw-r--r--plugins/muc/muc.lib.lua39
-rw-r--r--plugins/s2s/mod_s2s.lua486
-rw-r--r--plugins/s2s/s2sout.lib.lua352
-rwxr-xr-xprosody81
-rw-r--r--prosody.cfg.lua.dist6
-rwxr-xr-xprosodyctl101
-rw-r--r--tests/test.lua1
-rw-r--r--tests/test_util_rfc3484.lua51
-rw-r--r--tools/ejabberdsql2prosody.lua7
-rw-r--r--tools/migration/migrator/jabberd14.lua15
-rw-r--r--util/array.lua49
-rw-r--r--util/datamanager.lua6
-rw-r--r--util/debug.lua98
-rw-r--r--util/dependencies.lua8
-rw-r--r--util/helpers.lua33
-rw-r--r--util/httpstream.lua3
-rw-r--r--util/ip.lua176
-rw-r--r--util/iterators.lua27
-rw-r--r--util/logger.lua31
-rw-r--r--util/prosodyctl.lua15
-rw-r--r--util/rfc3484.lua133
-rw-r--r--util/set.lua6
-rw-r--r--util/stanza.lua11
-rw-r--r--util/template.lua16
-rw-r--r--util/termcolours.lua23
-rw-r--r--util/timer.lua3
-rw-r--r--util/x509.lua109
-rw-r--r--util/xmppstream.lua48
81 files changed, 4065 insertions, 2595 deletions
diff --git a/Makefile b/Makefile
index 0fa8b28e..1acdd051 100644
--- a/Makefile
+++ b/Makefile
@@ -27,17 +27,15 @@ install: prosody.install prosodyctl.install prosody.cfg.lua.install util/encodin
install -m755 ./prosody.install $(BIN)/prosody
install -m755 ./prosodyctl.install $(BIN)/prosodyctl
install -m644 core/* $(SOURCE)/core
- install -m644 net/* $(SOURCE)/net
+ install -m644 net/*.lua $(SOURCE)/net
+ install -d $(SOURCE)/net/http
+ install -m644 net/http/*.lua $(SOURCE)/net/http
install -m644 util/*.lua $(SOURCE)/util
install -m644 util/*.so $(SOURCE)/util
install -d $(SOURCE)/util/sasl
install -m644 util/sasl/* $(SOURCE)/util/sasl
- install -m644 plugins/*.lua $(MODULES)
- install -d $(MODULES)/muc
- install -m644 plugins/muc/* $(MODULES)/muc
+ umask 0022 && cp -r plugins/* $(MODULES)
install -m644 certs/* $(CONFIG)/certs
- install -d $(MODULES)/adhoc
- install -m644 plugins/adhoc/*.lua $(MODULES)/adhoc
install -m644 man/prosodyctl.man $(MAN)/man1/prosodyctl.1
test -e $(CONFIG)/prosody.cfg.lua || install -m644 prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
test -e prosody.version && install prosody.version $(SOURCE)/prosody.version || true
diff --git a/configure b/configure
index af046223..dd6af49c 100755
--- a/configure
+++ b/configure
@@ -112,7 +112,7 @@ do
CFLAGS="-Wall -fPIC"
LDFLAGS="-shared"
fi
- if [ "$OSTYPE" = "freebsd" ]
+ if [ "$OSTYPE" = "freebsd" -o "$OSTYPE" = "openbsd" ]
then LUA_INCDIR="/usr/local/include/lua51"
LUA_INCDIR_SET=yes
CFLAGS="-Wall -fPIC -I/usr/local/include"
@@ -122,6 +122,9 @@ do
LUA_DIR=/usr/local
LUA_DIR_SET=yes
fi
+ if [ "$OSTYPE" = "openbsd" ]
+ then LUA_INCDIR="/usr/local/include";
+ fi
;;
--datadir=*)
DATADIR="$value"
diff --git a/core/certmanager.lua b/core/certmanager.lua
index 8b82ac47..cccf3098 100644
--- a/core/certmanager.lua
+++ b/core/certmanager.lua
@@ -35,7 +35,7 @@ function create_context(host, mode, user_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;
+ password = user_ssl_config.password or function() log("error", "Encrypted certificate for %s requires 'ssl' 'password' to be set in config", host); end;
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);
diff --git a/core/configmanager.lua b/core/configmanager.lua
index 85919492..e2253171 100644
--- a/core/configmanager.lua
+++ b/core/configmanager.lua
@@ -41,6 +41,9 @@ function getconfig()
end
function get(host, section, key)
+ if not key then
+ section, key = "core", section;
+ end
local sec = config[host][section];
if sec then
return sec[key];
diff --git a/core/hostmanager.lua b/core/hostmanager.lua
index 330a0d03..a9db1a92 100644
--- a/core/hostmanager.lua
+++ b/core/hostmanager.lua
@@ -24,7 +24,7 @@ if not _G.prosody.incoming_s2s then
end
local incoming_s2s = _G.prosody.incoming_s2s;
-local pairs, setmetatable, select = pairs, setmetatable, select;
+local pairs, select = pairs, select;
local tostring, type = tostring, type;
module "hostmanager"
@@ -94,7 +94,7 @@ function activate(host, host_config)
end
log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
- prosody_events.fire_event("host-activated", host, host_config);
+ prosody_events.fire_event("host-activated", host);
return true;
end
@@ -102,13 +102,14 @@ 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);
- prosody_events.fire_event("host-deactivating", host, host_session);
+ prosody_events.fire_event("host-deactivating", { host = host, host_session = host_session, reason = reason });
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
+ -- TODO: These should move to mod_c2s and mod_s2s (how do they know they're being unloaded and not reloaded?)
if host_session.sessions then
for username, user in pairs(host_session.sessions) do
for resource, session in pairs(user.sessions) do
@@ -133,6 +134,7 @@ function deactivate(host, reason)
end
end
+ -- TODO: This should be done in modulemanager
if host_session.modules then
for module in pairs(host_session.modules) do
modulemanager.unload(host, module);
diff --git a/core/loggingmanager.lua b/core/loggingmanager.lua
index 88f2bbbf..56a3ee2c 100644
--- a/core/loggingmanager.lua
+++ b/core/loggingmanager.lua
@@ -41,41 +41,19 @@ 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; });
local get_levels;
-local logging_levels = { "debug", "info", "warn", "error", "critical" }
+local logging_levels = { "debug", "info", "warn", "error" }
-- Put a rule into action. Requires that the sink type has already been registered.
-- This function is called automatically when a new sink type is added [see apply_sink_rules()]
local function add_rule(sink_config)
local sink_maker = log_sink_types[sink_config.to];
if sink_maker then
- if sink_config.levels and not sink_config.source then
- -- Create sink
- local sink = sink_maker(sink_config);
-
- -- Set sink for all chosen levels
- for level in pairs(get_levels(sink_config.levels)) do
- logger.add_level_sink(level, sink);
- end
- elseif sink_config.source and not sink_config.levels then
- logger.add_name_sink(sink_config.source, sink_maker(sink_config));
- elseif sink_config.source and sink_config.levels then
- local levels = get_levels(sink_config.levels);
- local sink = sink_maker(sink_config);
- logger.add_name_sink(sink_config.source,
- function (name, level, ...)
- if levels[level] then
- return sink(name, level, ...);
- end
- end);
- else
- -- All sources
- -- Create sink
- local sink = sink_maker(sink_config);
-
- -- Set sink for all levels
- for _, level in pairs(logging_levels) do
- logger.add_level_sink(level, sink);
- end
+ -- Create sink
+ local sink = sink_maker(sink_config);
+
+ -- Set sink for all chosen levels
+ for level in pairs(get_levels(sink_config.levels or logging_levels)) do
+ logger.add_level_sink(level, sink);
end
else
-- No such sink type
diff --git a/core/moduleapi.lua b/core/moduleapi.lua
new file mode 100644
index 00000000..57367255
--- /dev/null
+++ b/core/moduleapi.lua
@@ -0,0 +1,342 @@
+-- Prosody IM
+-- Copyright (C) 2008-2012 Matthew Wild
+-- Copyright (C) 2008-2012 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local config = require "core.configmanager";
+local modulemanager = require "modulemanager";
+local array = require "util.array";
+local set = require "util.set";
+local logger = require "util.logger";
+local pluginloader = require "util.pluginloader";
+local timer = require "util.timer";
+
+local multitable_new = require "util.multitable".new;
+
+local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
+local error, setmetatable, setfenv, type = error, setmetatable, setfenv, type;
+local ipairs, pairs, select, unpack = ipairs, pairs, select, unpack;
+local tonumber, tostring = tonumber, tostring;
+
+local prosody = prosody;
+local hosts = prosody.hosts;
+local core_post_stanza = prosody.core_post_stanza;
+
+-- Registry of shared module data
+local shared_data = setmetatable({}, { __mode = "v" });
+
+local NULL = {};
+
+local api = {};
+
+-- Returns the name of the current module
+function api:get_name()
+ return self.name;
+end
+
+-- Returns the host that the current module is serving
+function api:get_host()
+ return self.host;
+end
+
+function api:get_host_type()
+ return self.host ~= "*" and hosts[self.host].type or nil;
+end
+
+function api:set_global()
+ self.host = "*";
+ -- Update the logger
+ local _log = logger.init("mod_"..self.name);
+ self.log = function (self, ...) return _log(...); end;
+ self._log = _log;
+ self.global = true;
+end
+
+function api:add_feature(xmlns)
+ self:add_item("feature", xmlns);
+end
+function api:add_identity(category, type, name)
+ self:add_item("identity", {category = category, type = type, name = name});
+end
+function api:add_extension(data)
+ self:add_item("extension", data);
+end
+
+function api:fire_event(...)
+ return (hosts[self.host] or prosody).events.fire_event(...);
+end
+
+function api:hook_object_event(object, event, handler, priority)
+ self.event_handlers[handler] = { name = event, priority = priority, object = object };
+ return object.add_handler(event, handler, priority);
+end
+
+function api:unhook_object_event(object, event, handler)
+ return object.remove_handler(event, handler);
+end
+
+function api:hook(event, handler, priority)
+ return self:hook_object_event((hosts[self.host] or prosody).events, event, handler, priority);
+end
+
+function api:hook_global(event, handler, priority)
+ return self:hook_object_event(prosody.events, event, handler, priority);
+end
+
+function api:hook_tag(xmlns, name, handler, priority)
+ if not handler and type(name) == "function" then
+ -- If only 2 options then they specified no xmlns
+ xmlns, name, handler, priority = nil, xmlns, name, handler;
+ elseif not (handler and name) then
+ self:log("warn", "Error: Insufficient parameters to module:hook_stanza()");
+ return;
+ end
+ return self:hook("stanza/"..(xmlns and (xmlns..":") or "")..name, function (data) return handler(data.origin, data.stanza, data); end, priority);
+end
+api.hook_stanza = api.hook_tag; -- COMPAT w/pre-0.9
+
+function api:require(lib)
+ local f, n = pluginloader.load_code(self.name, lib..".lib.lua");
+ if not f then
+ 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, self.environment);
+ return f();
+end
+
+function api:depends(name)
+ if not self.dependencies then
+ self.dependencies = {};
+ self:hook("module-reloaded", function (event)
+ if self.dependencies[event.module] then
+ self:log("info", "Auto-reloading due to reload of %s:%s", event.host, event.module);
+ modulemanager.reload(self.host, self.name);
+ return;
+ end
+ end);
+ self:hook("module-unloaded", function (event)
+ if self.dependencies[event.module] then
+ self:log("info", "Auto-unloading due to unload of %s:%s", event.host, event.module);
+ modulemanager.unload(self.host, self.name);
+ end
+ end);
+ end
+ local mod = modulemanager.get_module(self.host, name) or modulemanager.get_module("*", name);
+ if mod and mod.module.host == "*" and self.host ~= "*"
+ and modulemanager.module_has_method(mod, "add_host") then
+ mod = nil; -- This is a shared module, so we still want to load it on our host
+ end
+ if not mod then
+ local err;
+ mod, err = modulemanager.load(self.host, name);
+ if not mod then
+ return error(("Unable to load required module, mod_%s: %s"):format(name, ((err or "unknown error"):gsub("%-", " ")) ));
+ end
+ end
+ self.dependencies[name] = true;
+ return mod;
+end
+
+-- Returns one or more shared tables at the specified virtual paths
+-- Intentionally does not allow the table at a path to be _set_, it
+-- is auto-created if it does not exist.
+function api:shared(...)
+ if not self.shared_data then self.shared_data = {}; end
+ local paths = { n = select("#", ...), ... };
+ local data_array = {};
+ local default_path_components = { self.host, self.name };
+ for i = 1, paths.n do
+ local path = paths[i];
+ if path:sub(1,1) ~= "/" then -- Prepend default components
+ local n_components = select(2, path:gsub("/", "%1"));
+ path = (n_components<#default_path_components and "/" or "")..t_concat(default_path_components, "/", 1, #default_path_components-n_components).."/"..path;
+ end
+ local shared = shared_data[path];
+ if not shared then
+ shared = {};
+ shared_data[path] = shared;
+ end
+ t_insert(data_array, shared);
+ self.shared_data[path] = shared;
+ end
+ return unpack(data_array);
+end
+
+function api:get_option(name, default_value)
+ local value = config.get(self.host, self.name, name);
+ if value == nil then
+ value = config.get(self.host, "core", name);
+ if value == nil then
+ value = default_value;
+ end
+ end
+ return value;
+end
+
+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
+
+function api:add_item(key, value)
+ self.items = self.items or {};
+ self.items[key] = self.items[key] or {};
+ t_insert(self.items[key], value);
+ self:fire_event("item-added/"..key, {source = self, item = value});
+end
+function api:remove_item(key, value)
+ local t = self.items and self.items[key] or NULL;
+ for i = #t,1,-1 do
+ if t[i] == value then
+ t_remove(self.items[key], i);
+ self:fire_event("item-removed/"..key, {source = self, item = value});
+ return value;
+ end
+ end
+end
+
+function api:get_host_items(key)
+ local result = {};
+ for mod_name, module in pairs(modulemanager.get_modules(self.host)) do
+ module = module.module;
+ if module.items then
+ for _, item in ipairs(module.items[key] or NULL) do
+ t_insert(result, item);
+ end
+ end
+ end
+ for mod_name, module in pairs(modulemanager.get_modules("*")) do
+ module = module.module;
+ if module.items then
+ for _, item in ipairs(module.items[key] or NULL) do
+ t_insert(result, item);
+ end
+ end
+ end
+ return result;
+end
+
+function api:handle_items(type, added_cb, removed_cb, existing)
+ self:hook("item-added/"..type, added_cb);
+ self:hook("item-removed/"..type, removed_cb);
+ if existing ~= false then
+ for _, item in ipairs(self:get_host_items(type)) do
+ added_cb({ item = item });
+ end
+ end
+end
+
+function api:provides(name, item)
+ if not item then item = self.environment; end
+ if not item.name then
+ local item_name = self.name;
+ -- Strip a provider prefix to find the item name
+ -- (e.g. "auth_foo" -> "foo" for an auth provider)
+ if item_name:find(name.."_", 1, true) == 1 then
+ item_name = item_name:sub(#name+2);
+ end
+ item.name = item_name;
+ end
+ self:add_item(name.."-provider", item);
+end
+
+function api:send(stanza)
+ return core_post_stanza(hosts[self.host], stanza);
+end
+
+function api:add_timer(delay, callback)
+ return timer.add_task(delay, function (t)
+ if self.loaded == false then return; end
+ return callback(t);
+ end);
+end
+
+local path_sep = package.config:sub(1,1);
+function api:get_directory()
+ return self.path and (self.path:gsub("%"..path_sep.."[^"..path_sep.."]*$", "")) or nil;
+end
+
+function api:load_resource(path, mode)
+ path = config.resolve_relative_path(self:get_directory(), path);
+ return io.open(path, mode);
+end
+
+return api;
diff --git a/core/modulemanager.lua b/core/modulemanager.lua
index 2d1eeb77..90116166 100644
--- a/core/modulemanager.lua
+++ b/core/modulemanager.lua
@@ -9,23 +9,14 @@
local logger = require "util.logger";
local log = logger.init("modulemanager");
local config = require "core.configmanager";
-local multitable_new = require "util.multitable".new;
-local st = require "util.stanza";
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;
-local pairs, ipairs = pairs, ipairs;
-local t_insert, t_concat = table.insert, table.concat;
-local type = type;
-local next = next;
-local rawget = rawget;
-local error = error;
-local tostring, tonumber = tostring, tonumber;
+
+local pcall, xpcall = pcall, xpcall;
+local setmetatable, rawget, setfenv = setmetatable, rawget, setfenv;
+local pairs, type, tostring = pairs, type, tostring;
local debug_traceback = debug.traceback;
local unpack, select = unpack, select;
@@ -35,9 +26,9 @@ pcall = function(f, ...)
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 set = require "util.set";
-local autoload_modules = {"presence", "message", "iq", "offline"};
+local autoload_modules = {"presence", "message", "iq", "offline", "c2s", "s2s"};
local component_inheritable_modules = {"tls", "dialback", "iq"};
-- We need this to let modules access the real global namespace
@@ -45,17 +36,11 @@ local _G = _G;
module "modulemanager"
-api = {};
-local api = api; -- Module API container
+local api = _G.require "core.moduleapi"; -- Module API container
+-- [host] = { [module] = module_env }
local modulemap = { ["*"] = {} };
-local modulehelpers = setmetatable({}, { __index = _G });
-
-local hooks = multitable_new();
-
-local NULL = {};
-
-- Load modules when a host is activated
function load_modules_for_host(host)
local component = config.get(host, "core", "component_module");
@@ -88,24 +73,80 @@ function load_modules_for_host(host)
load(host, module);
end
end
-prosody_events.add_handler("host-activated", load_modules_for_host);
---
+prosody.events.add_handler("host-activated", load_modules_for_host);
+prosody.events.add_handler("host-deactivated", function (host)
+ modulemap[host] = nil;
+end);
-function load(host, module_name, config)
+--- Private helpers ---
+
+local function do_unload_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
+ local ok, err = call_module_method(mod, "unload");
+ if (not ok) and err then
+ log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err);
+ end
+ end
+
+ for handler, event in pairs(mod.module.event_handlers) do
+ event.object.remove_handler(event.name, handler);
+ end
+
+ if mod.module.items then -- remove items
+ local events = (host == "*" and prosody.events) or hosts[host].events;
+ for key,t in pairs(mod.module.items) do
+ for i = #t,1,-1 do
+ local value = t[i];
+ t[i] = nil;
+ events.fire_event("item-removed/"..key, {source = mod.module, item = value});
+ end
+ end
+ end
+ mod.module.loaded = false;
+ modulemap[host][name] = nil;
+ return true;
+end
+
+local function do_load_module(host, module_name)
if not (host and module_name) then
return nil, "insufficient-parameters";
- elseif not hosts[host] then
+ elseif not hosts[host] and host ~= "*"then
return nil, "unknown-host";
end
if not modulemap[host] then
modulemap[host] = {};
+ if host ~= "*" then
+ hosts[host].modules = modulemap[host];
+ end
end
if modulemap[host][module_name] then
log("warn", "%s is already loaded for %s, so not loading again", module_name, host);
return nil, "module-already-loaded";
elseif modulemap["*"][module_name] then
+ local mod = modulemap["*"][module_name];
+ if module_has_method(mod, "add_host") then
+ local _log = logger.init(host..":"..module_name);
+ local host_module_api = setmetatable({
+ host = host, event_handlers = {}, items = {};
+ _log = _log, log = function (self, ...) return _log(...); end;
+ },{
+ __index = modulemap["*"][module_name].module;
+ });
+ local host_module = setmetatable({ module = host_module_api }, { __index = mod });
+ host_module_api.environment = host_module;
+ modulemap[host][module_name] = host_module;
+ local ok, result, module_err = call_module_method(mod, "add_host", host_module_api);
+ if not ok or result == false then
+ modulemap[host][module_name] = nil;
+ return nil, ok and module_err or result;
+ end
+ return host_module;
+ end
return nil, "global-module-already-loaded";
end
@@ -117,88 +158,47 @@ function load(host, module_name, config)
end
local _log = logger.init(host..":"..module_name);
- local api_instance = setmetatable({ name = module_name, host = host, path = err, config = config, _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
+ local api_instance = setmetatable({ name = module_name, host = host, path = err,
+ _log = _log, log = function (self, ...) return _log(...); end, event_handlers = {} }
+ , { __index = api });
local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
api_instance.environment = pluginenv;
setfenv(mod, pluginenv);
- hosts[host].modules = modulemap[host];
- modulemap[host][module_name] = pluginenv;
- local success, err = pcall(mod);
- if success then
+ modulemap[host][module_name] = pluginenv;
+ local ok, err = pcall(mod);
+ if ok then
+ -- Call module's "load"
if module_has_method(pluginenv, "load") then
- success, err = call_module_method(pluginenv, "load");
- if not success then
+ ok, err = call_module_method(pluginenv, "load");
+ if not ok then
log("warn", "Error loading module '%s' on '%s': %s", module_name, host, err or "nil");
end
end
- -- Use modified host, if the module set one
- if api_instance.host == "*" and host ~= "*" then
+ if api_instance.host == "*" then
+ if not api_instance.global then -- COMPAT w/pre-0.9
+ log("warn", "mod_%s: Setting module.host = '*' deprecated, call module:set_global() instead", module_name);
+ api_instance:set_global();
+ end
modulemap[host][module_name] = nil;
- modulemap["*"][module_name] = pluginenv;
- api_instance:set_global();
- end
- else
- log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil");
- end
- if success then
- (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);
- return nil, err;
- end
-end
-
-function get_module(host, name)
- return modulemap[host] and modulemap[host][name];
-end
-
-function is_loaded(host, name)
- return modulemap[host] and modulemap[host][name] and true;
-end
-
-function unload(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
- local ok, err = call_module_method(mod, "unload");
- if (not ok) and err then
- log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err);
- end
- end
- -- 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
- (hosts[host] or prosody).events.remove_handler(event, handler);
- end
- end
- -- unhook event handlers hooked by module:hook_global
- for event, handlers in pairs(hooks:get("*", name) or NULL) do
- for handler in pairs(handlers or NULL) do
- prosody.events.remove_handler(event, handler);
- end
- end
- hooks:remove(host, name);
- if mod.module.items then -- remove items
- for key,t in pairs(mod.module.items) do
- for i = #t,1,-1 do
- local value = t[i];
- t[i] = nil;
- hosts[host].events.fire_event("item-removed/"..key, {source = mod.module, item = value});
+ modulemap[api_instance.host][module_name] = pluginenv;
+ if host ~= api_instance.host and module_has_method(pluginenv, "add_host") then
+ -- Now load the module again onto the host it was originally being loaded on
+ ok, err = do_load_module(host, module_name);
end
end
end
- modulemap[host][name] = nil;
- (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host });
- return true;
+ if not ok then
+ modulemap[api_instance.host][module_name] = nil;
+ log("error", "Error initializing module '%s' on '%s': %s", module_name, host, err or "nil");
+ end
+ return ok and pluginenv, err;
end
-function reload(host, name, ...)
+local function do_reload_module(host, name)
local mod = get_module(host, name);
if not mod then return nil, "module-not-loaded"; end
@@ -209,7 +209,6 @@ function reload(host, name, ...)
end
local saved;
-
if module_has_method(mod, "save") then
local ok, ret, err = call_module_method(mod, "save");
if ok then
@@ -225,8 +224,8 @@ function reload(host, name, ...)
end
end
- unload(host, name, ...);
- local ok, err = load(host, name, ...);
+ do_unload_module(host, name);
+ local ok, err = do_load_module(host, name);
if ok then
mod = get_module(host, name);
if module_has_method(mod, "restore") then
@@ -235,231 +234,62 @@ function reload(host, name, ...)
log("warn", "Error restoring module '%s' from '%s': %s", name, host, err);
end
end
- return true;
- end
- return ok, err;
-end
-
-function module_has_method(module, method)
- return type(module.module[method]) == "function";
-end
-
-function call_module_method(module, method, ...)
- if module_has_method(module, method) then
- local f = module.module[method];
- return pcall(f, ...);
- else
- return false, "no-such-method";
end
+ return ok and mod, err;
end
------ API functions exposed to modules -----------
--- Must all be in api.*
-
--- Returns the name of the current module
-function api:get_name()
- return self.name;
-end
-
--- Returns the host that the current module is serving
-function api:get_host()
- return self.host;
-end
-
-function api:get_host_type()
- return hosts[self.host].type;
-end
-
-function api:set_global()
- self.host = "*";
- -- Update the logger
- local _log = logger.init("mod_"..self.name);
- self.log = function (self, ...) return _log(...); end;
- self._log = _log;
-end
+--- Public API ---
-function api:add_feature(xmlns)
- self:add_item("feature", xmlns);
-end
-function api:add_identity(category, type, name)
- self:add_item("identity", {category = category, type = type, name = name});
-end
-function api:add_extension(data)
- self:add_item("extension", data);
-end
-
-function api:fire_event(...)
- return (hosts[self.host] or prosody).events.fire_event(...);
-end
-
-function api:hook(event, handler, priority)
- hooks:set(self.host, self.name, event, handler, true);
- (hosts[self.host] or prosody).events.add_handler(event, handler, priority);
-end
-
-function api:hook_global(event, handler, priority)
- hooks:set("*", self.name, event, handler, true);
- prosody.events.add_handler(event, handler, priority);
-end
-
-function api:hook_stanza(xmlns, name, handler, priority)
- if not handler and type(name) == "function" then
- -- If only 2 options then they specified no xmlns
- xmlns, name, handler, priority = nil, xmlns, name, handler;
- elseif not (handler and name) then
- self:log("warn", "Error: Insufficient parameters to module:hook_stanza()");
- return;
- end
- return api.hook(self, "stanza/"..(xmlns and (xmlns..":") or "")..name, function (data) return handler(data.origin, data.stanza, data); end, priority);
-end
-
-function api:require(lib)
- local f, n = pluginloader.load_code(self.name, lib..".lib.lua");
- if not f then
- 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, self.environment);
- return f();
-end
-
-function api:get_option(name, default_value)
- local value = config.get(self.host, self.name, name);
- if value == nil then
- value = config.get(self.host, "core", name);
- if value == nil then
- value = default_value;
- end
- end
- return value;
-end
-
-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;
+-- Load a module and fire module-loaded event
+function load(host, name)
+ local mod, err = do_load_module(host, name);
+ if mod then
+ (hosts[mod.module.host] or prosody).events.fire_event("module-loaded", { module = name, host = host });
end
- return tostring(value);
+ return mod, err;
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);
+-- Unload a module and fire module-unloaded
+function unload(host, name)
+ local ok, err = do_unload_module(host, name);
+ if ok then
+ (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host });
end
- return ret;
+ return ok, err;
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);
+function reload(host, name)
+ local ok, err = do_reload_module(host, name);
+ if ok then
+ (hosts[host] or prosody).events.fire_event("module-reloaded", { module = name, host = host });
+ elseif not is_loaded(host, name) then
+ (hosts[host] or prosody).events.fire_event("module-unloaded", { module = name, host = host });
end
- return ret;
+ return ok, err;
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
+function get_module(host, name)
+ return modulemap[host] and modulemap[host][name];
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);
+function get_modules(host)
+ return modulemap[host];
end
-local t_remove = _G.table.remove;
-local module_items = multitable_new();
-function api:add_item(key, value)
- self.items = self.items or {};
- self.items[key] = self.items[key] or {};
- t_insert(self.items[key], value);
- self:fire_event("item-added/"..key, {source = self, item = value});
-end
-function api:remove_item(key, value)
- local t = self.items and self.items[key] or NULL;
- for i = #t,1,-1 do
- if t[i] == value then
- t_remove(self.items[key], i);
- self:fire_event("item-removed/"..key, {source = self, item = value});
- return value;
- end
- end
+function is_loaded(host, name)
+ return modulemap[host] and modulemap[host][name] and true;
end
-function api:get_host_items(key)
- local result = {};
- for mod_name, module in pairs(modulemap[self.host]) do
- module = module.module;
- if module.items then
- for _, item in ipairs(module.items[key] or NULL) do
- t_insert(result, item);
- end
- end
- end
- for mod_name, module in pairs(modulemap["*"]) do
- module = module.module;
- if module.items then
- for _, item in ipairs(module.items[key] or NULL) do
- t_insert(result, item);
- end
- end
- end
- return result;
+function module_has_method(module, method)
+ return type(rawget(module.module, method)) == "function";
end
-function api:handle_items(type, added_cb, removed_cb, existing)
- self:hook("item-added/"..type, added_cb);
- self:hook("item-removed/"..type, removed_cb);
- if existing ~= false then
- for _, item in ipairs(self:get_host_items(type)) do
- added_cb({ item = item });
- end
+function call_module_method(module, method, ...)
+ local f = rawget(module.module, method);
+ if type(f) == "function" then
+ return pcall(f, ...);
+ else
+ return false, "no-such-method";
end
end
diff --git a/core/portmanager.lua b/core/portmanager.lua
new file mode 100644
index 00000000..fed35987
--- /dev/null
+++ b/core/portmanager.lua
@@ -0,0 +1,206 @@
+local config = require "core.configmanager";
+local server = require "net.server";
+
+local log = require "util.logger".init("portmanager");
+local multitable = require "util.multitable";
+local set = require "util.set";
+
+local table, package = table, package;
+local setmetatable, rawset, rawget = setmetatable, rawset, rawget;
+local type, tonumber = type, tonumber;
+
+local prosody = prosody;
+local fire_event = prosody.events.fire_event;
+
+module "portmanager";
+
+--- Config
+
+local default_interfaces = { "*" };
+local default_local_interfaces = { "127.0.0.1" };
+if config.get("*", "use_ipv6") then
+ table.insert(default_interfaces, "::");
+ table.insert(default_local_interfaces, "::1");
+end
+
+--- Private state
+
+-- service_name -> { service_info, ... }
+local services = setmetatable({}, { __index = function (t, k) rawset(t, k, {}); return rawget(t, k); end });
+
+-- service_name, interface (string), port (number)
+local active_services = multitable.new();
+
+--- Private helpers
+
+local function error_to_friendly_message(service_name, port, err)
+ local friendly_message = err;
+ if err:match(" in use") then
+ -- FIXME: Use service_name here
+ if port == 5222 or port == 5223 or port == 5269 then
+ friendly_message = "check that Prosody or another XMPP server is "
+ .."not already running and using this port";
+ elseif port == 80 or port == 81 then
+ friendly_message = "check that a HTTP server is not already using "
+ .."this port";
+ elseif port == 5280 then
+ friendly_message = "check that Prosody or a BOSH connection manager "
+ .."is not already running";
+ else
+ friendly_message = "this port is in use by another application";
+ end
+ elseif err:match("permission") then
+ friendly_message = "Prosody does not have sufficient privileges to use this port";
+ elseif err == "no ssl context" then
+ if not config.get("*", "core", "ssl") then
+ friendly_message = "there is no 'ssl' config under Host \"*\" which is "
+ .."require for legacy SSL ports";
+ else
+ friendly_message = "initializing SSL support failed, see previous log entries";
+ end
+ end
+ return friendly_message;
+end
+
+prosody.events.add_handler("item-added/net-provider", function (event)
+ local item = event.item;
+ register_service(item.name, item);
+end);
+prosody.events.add_handler("item-removed/net-provider", function (event)
+ local item = event.item;
+ unregister_service(item.name, item);
+end);
+
+--- Public API
+
+function activate(service_name)
+ local service_info = services[service_name][1];
+ if not service_info then
+ return nil, "Unknown service: "..service_name;
+ end
+
+ local listener = service_info.listener;
+
+ local config_prefix = (service_info.config_prefix or service_name).."_";
+ if config_prefix == "_" then
+ config_prefix = "";
+ end
+
+ local bind_interfaces = config.get("*", config_prefix.."interfaces")
+ or config.get("*", config_prefix.."interface") -- COMPAT w/pre-0.9
+ or (service_info.private and default_local_interfaces)
+ or config.get("*", "interfaces")
+ or config.get("*", "interface") -- COMPAT w/pre-0.9
+ or listener.default_interface -- COMPAT w/pre0.9
+ or default_interfaces
+ bind_interfaces = set.new(type(bind_interfaces)~="table" and {bind_interfaces} or bind_interfaces);
+
+ local bind_ports = set.new(config.get("*", config_prefix.."ports")
+ or service_info.default_ports
+ or {service_info.default_port
+ or listener.default_port -- COMPAT w/pre-0.9
+ });
+
+ local mode = listener.default_mode or "*a";
+ local ssl;
+ if service_info.encryption == "ssl" then
+ ssl = prosody.global_ssl_ctx;
+ if not ssl then
+ return nil, "global-ssl-context-required";
+ end
+ end
+
+ for interface in bind_interfaces do
+ for port in bind_ports do
+ port = tonumber(port);
+ if #active_services:search(nil, interface, port) > 0 then
+ log("error", "Multiple services configured to listen on the same port ([%s]:%d): %s, %s", interface, port, active_services:search(nil, interface, port)[1][1].service.name or "<unnamed>", service_name or "<unnamed>");
+ else
+ local handler, err = server.addserver(interface, port, listener, mode, ssl);
+ if not handler then
+ log("error", "Failed to open server port %d on %s, %s", port, interface, error_to_friendly_message(service_name, port, err));
+ else
+ log("debug", "Added listening service %s to [%s]:%d", service_name, interface, port);
+ active_services:add(service_name, interface, port, {
+ server = handler;
+ service = service_info;
+ });
+ end
+ end
+ end
+ end
+ log("info", "Activated service '%s'", service_name);
+ return true;
+end
+
+function deactivate(service_name)
+ local active = active_services:search(service_name)[1];
+ if not active then return; end
+ for interface, ports in pairs(active) do
+ for port, active_service in pairs(ports) do
+ close(interface, port);
+ end
+ end
+ log("info", "Deactivated service '%s'", service_name);
+end
+
+function register_service(service_name, service_info)
+ table.insert(services[service_name], service_info);
+
+ if not active_services:get(service_name) then
+ log("debug", "No active service for %s, activating...", service_name);
+ local ok, err = activate(service_name);
+ if not ok then
+ log("error", "Failed to activate service '%s': %s", service_name, err or "unknown error");
+ end
+ end
+
+ fire_event("service-added", { name = service_name, service = service_info });
+ return true;
+end
+
+function unregister_service(service_name, service_info)
+ local service_info_list = services[service_name];
+ for i, service in ipairs(service_info_list) do
+ if service == service_info then
+ table.remove(service_info_list, i);
+ end
+ end
+ if active_services[service_name] == service_info then
+ deactivate(service_name);
+ if #service_info_list > 0 then -- Other services registered with this name
+ activate(service_name); -- Re-activate with the next available one
+ end
+ end
+ fire_event("service-removed", { name = service_name, service = service_info });
+end
+
+function close(interface, port)
+ local service, server = get_service_at(interface, port);
+ if not service then
+ return false, "port-not-open";
+ end
+ server:close();
+ active_services:remove(service.name, interface, port);
+ log("debug", "Removed listening service %s from [%s]:%d", service.name, interface, port);
+ return true;
+end
+
+function get_service_at(interface, port)
+ local data = active_services:search(nil, interface, port)[1][1];
+ return data.service, data.server;
+end
+
+function get_service(service_name)
+ return services[service_name];
+end
+
+function get_active_services(...)
+ return active_services;
+end
+
+function get_registered_services()
+ return services;
+end
+
+return _M;
diff --git a/core/s2smanager.lua b/core/s2smanager.lua
index e3c536f4..5907ff2f 100644
--- a/core/s2smanager.lua
+++ b/core/s2smanager.lua
@@ -9,25 +9,21 @@
local hosts = hosts;
-local sessions = sessions;
local core_process_stanza = function(a, b) core_process_stanza(a, b); end
-local add_task = require "util.timer".add_task;
-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, type, error, tonumber, setmetatable
+ = tostring, pairs, ipairs, getmetatable, newproxy, type, 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 cert_verify_identity = require "util.x509".verify_identity;
+local new_ip = require "util.ip".new_ip;
+local rfc3484_dest = require "util.rfc3484".destination;
local fire_event = prosody.events.fire_event;
local uuid_gen = require "util.uuid".generate;
@@ -40,10 +36,12 @@ 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 15;
-local max_dns_depth = config.get("*", "core", "dns_max_depth") or 3;
+local cfg_sources = config.get("*", "core", "s2s_interface")
+ or config.get("*", "core", "interface");
+local sources;
+--FIXME: s2sout should create its own resolver w/ timeout
dns.settimeout(dns_timeout);
local prosody = _G.prosody;
@@ -53,89 +51,6 @@ local incoming_s2s = incoming_s2s;
module "s2smanager"
-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
- session.log("info", "sending error replies for "..#sendq.." queued stanzas because of failed outgoing connection to "..tostring(session.to_host));
- local dummy = {
- type = "s2sin";
- send = function(s)
- (session.log or log)("error", "Replying to to an s2s error reply, please report this! Traceback: %s", get_traceback());
- end;
- dummy = true;
- };
- for i, data in ipairs(sendq) do
- local reply = data[2];
- 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("Server-to-server connection failed: "..reason):up();
- end
- core_process_stanza(dummy, reply);
- end
- sendq[i] = nil;
- end
- session.sendq = nil;
- end
-end
-
-function send_to_host(from_host, to_host, data)
- if not hosts[from_host] then
- log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host);
- return false;
- end
- local host = hosts[from_host].s2sout[to_host];
- if host then
- -- We have a connection to this host already
- if host.type == "s2sout_unauthed" and (data.name ~= "db:verify" or not host.dialback_key) then
- (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), 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
- if host.from_host ~= from_host then
- log("error", "WARNING! This might, possibly, be a bug, but it might not...");
- log("error", "We are going to send from %s instead of %s", tostring(host.from_host), tostring(from_host));
- end
- host.sends2s(data);
- host.log("debug", "stanza sent over "..host.type);
- end
- else
- log("debug", "opening a new outgoing connection for this stanza");
- local host_session = new_outgoing(from_host, to_host);
-
- -- Store in buffer
- host_session.sendq = { {tostring(data), 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);
- 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;
function new_incoming(conn)
@@ -145,396 +60,18 @@ function new_incoming(conn)
getmetatable(session.trace).__gc = function () open_sessions = open_sessions - 1; end;
end
open_sessions = open_sessions + 1;
- local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
- session.log = log;
- 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
+ session.log = logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
incoming_s2s[session] = true;
- add_task(connect_timeout, function ()
- if session.conn ~= conn or
- session.type == "s2sin" then
- return; -- Ok, we're connect[ed|ing]
- end
- -- Not connected, need to close session and clean up
- (session.log or log)("debug", "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, 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;
-
- 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]*$");
- log = logger_init(conn_name);
- host_session.log = log;
- end
-
- initialize_filters(host_session);
-
- if connect ~= false then
- -- Kick the connection attempting machine into life
- 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
- -- 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
- local buffer;
- function host_session.sends2s(data)
- if not buffer then
- buffer = {};
- host_session.send_buffer = buffer;
- end
- log("debug", "Buffering data on unconnected s2sout to %s", to_host);
- buffer[#buffer+1] = data;
- log("debug", "Buffered item %d: %s", #buffer, tostring(data));
- end
- end
-
- return host_session;
-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 = to_host and idna_to_ascii(to_host), 5269;
-
- if not connect_host then
- return false;
- end
-
- if not err then -- This is our first attempt
- log("debug", "First attempt to connect to %s, starting with SRV lookup...", to_host);
- host_session.connecting = true;
- local handle;
- handle = adns.lookup(function (answer)
- handle = nil;
- host_session.connecting = nil;
- if answer then
- log("debug", to_host.." has SRV records, handling...");
- local srv_hosts = {};
- host_session.srv_hosts = srv_hosts;
- for _, record in ipairs(answer) do
- t_insert(srv_hosts, record.srv);
- end
- t_sort(srv_hosts, compare_srv_priorities);
-
- local srv_choice = srv_hosts[1];
- host_session.srv_choice = 1;
- if srv_choice then
- connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port;
- log("debug", "Best record found, will connect to %s:%d", connect_host, connect_port);
- end
- else
- log("debug", to_host.." has no SRV records, falling back to A");
- end
- -- Try with SRV, or just the plain hostname if no SRV
- local ok, err = try_connect(host_session, connect_host, connect_port);
- if not ok then
- if not attempt_connection(host_session, err) then
- -- No more attempts will be made
- destroy_session(host_session, err);
- end
- end
- end, "_xmpp-server._tcp."..connect_host..".", "SRV");
-
- 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;
- local srv_choice = host_session.srv_hosts[host_session.srv_choice];
- connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port;
- host_session.log("info", "Connection failed (%s). Attempt #%d: This time to %s:%d", tostring(err), host_session.srv_choice, connect_host, connect_port);
- else
- host_session.log("info", "Out of connection options, can't connect to %s", tostring(host_session.to_host));
- -- We're out of options
- return false;
- end
-
- if not (connect_host and connect_port) then
- -- Likely we couldn't resolve DNS
- log("warn", "Hmm, we're without a host (%s) and port (%s) to connect to for %s, giving up :(", tostring(connect_host), tostring(connect_port), tostring(to_host));
- return false;
- end
-
- return try_connect(host_session, connect_host, connect_port);
-end
-
-function try_connect(host_session, connect_host, connect_port)
- host_session.connecting = true;
- local handle;
- handle = adns.lookup(function (reply, err)
- handle = nil;
- host_session.connecting = nil;
-
- -- COMPAT: This is a compromise for all you CNAME-(ab)users :)
- if not (reply and reply[#reply] and reply[#reply].a) then
- local count = max_dns_depth;
- reply = dns.peek(connect_host, "CNAME", "IN");
- while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do
- log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count);
- reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN");
- count = count - 1;
- end
- end
- -- end of CNAME resolving
-
- if reply and reply[#reply] and reply[#reply].a then
- log("debug", "DNS reply for %s gives us %s", connect_host, reply[#reply].a);
- 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);
- 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");
-
- return true;
-end
-
-function make_connect(host_session, connect_host, connect_port)
- (host_session.log or log)("info", "Beginning new connection attempt to %s (%s:%d)", host_session.to_host, connect_host, connect_port);
- -- Ok, we're going to try to connect
-
- local from_host, to_host = host_session.from_host, host_session.to_host;
-
- local conn, handler = socket.tcp();
-
- if not conn then
- log("warn", "Failed to create outgoing connection, system error: %s", handler);
- return false, handler;
- end
-
- conn:settimeout(0);
- local success, err = conn:connect(connect_host, connect_port);
- if not success and err ~= "timeout" then
- log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host, connect_port, err);
- return false, err;
- end
-
- local cl = connlisteners_get("xmppserver");
- 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);
-
- host_session:open_stream(from_host, to_host);
-
- log("debug", "Connection attempt in progress...");
- add_task(connect_timeout, function ()
- if host_session.conn ~= conn or
- host_session.type == "s2sout" or
- host_session.connecting then
- return; -- Ok, we're connect[ed|ing]
- end
- -- Not connected, need to close session and clean up
- (host_session.log or log)("warn", "Destroying incomplete session %s->%s due to inactivity",
- host_session.from_host or "(unknown)", host_session.to_host or "(unknown)");
- host_session:close("connection-timeout");
- end);
- return true;
-end
-
-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
-
-local function check_cert_status(session)
- local conn = session.conn:socket()
- local cert
- if conn.getpeercertificate then
- cert = conn:getpeercertificate()
- end
-
- if cert then
- local chain_valid, errors = conn:getpeerverification()
- -- Is there any interest in printing out all/the number of errors here?
- if not chain_valid then
- (session.log or log)("debug", "certificate chain validation result: invalid");
- session.cert_chain_status = "invalid";
- else
- (session.log or log)("debug", "certificate chain validation result: valid");
- session.cert_chain_status = "valid";
-
- local host;
- if session.direction == "incoming" then
- host = session.from_host;
- else
- host = session.to_host;
- end
-
- -- We'll go ahead and verify the asserted identity if the
- -- connecting server specified one.
- if host then
- if cert_verify_identity(host, "xmpp-server", cert) then
- session.cert_identity_status = "valid"
- else
- session.cert_identity_status = "invalid"
- end
- end
- end
- end
-end
-
-function streamopened(session, attr)
- local send = session.sends2s;
-
- -- TODO: #29: SASL/TLS on s2s streams
- session.version = tonumber(attr.version) or 0;
-
- -- TODO: Rename session.secure to session.encrypted
- if session.secure == false then
- session.secure = true;
- end
-
- if session.direction == "incoming" then
- -- Send a reply stream header
- session.to_host = attr.to and nameprep(attr.to);
- session.from_host = attr.from and nameprep(attr.from);
-
- session.streamid = uuid_gen();
- (session.log or log)("debug", "Incoming s2s received %s", st.stanza("stream:stream", attr):top_tag());
- 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
-
- if session.secure and not session.cert_chain_status then check_cert_status(session); end
-
- send("<?xml version='1.0'?>");
- send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
- ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, to=session.from_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
- if session.version >= 1.0 then
- local features = st.stanza("stream:features");
-
- if session.to_host then
- hosts[session.to_host].events.fire_event("s2s-stream-features", { 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
-
- log("debug", "Sending stream features: %s", tostring(features));
- send(features);
- end
- elseif session.direction == "outgoing" then
- -- If we are just using the connection for verifying dialback keys, we won't try and auth it
- if not attr.id then error("stream response did not give us a streamid!!!"); end
- session.streamid = attr.id;
-
- if session.secure and not session.cert_chain_status then check_cert_status(session); end
-
- -- Send unauthed buffer
- -- (stanzas which are fine to send before dialback)
- -- 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(tostring(data));
- send_buffer[i] = nil;
- end
- end
- session.send_buffer = nil;
-
- -- If server is pre-1.0, don't wait for features, just do dialback
- if session.version < 1.0 then
- if not session.dialback_verifying then
- log("debug", "Initiating dialback...");
- initiate_dialback(session);
- else
- mark_connected(session);
- end
- end
- end
- session.notopen = nil;
-end
-
-function streamclosed(session)
- (session.log or log)("debug", "Received </stream:stream>");
- session:close();
-end
-
-function initiate_dialback(session)
- -- generate dialback key
- session.dialback_key = generate_dialback(session.streamid, session.to_host, session.from_host);
- session.sends2s(format("<db:result from='%s' to='%s'>%s</db:result>", session.from_host, session.to_host, session.dialback_key));
- session.log("info", "sent dialback key on outgoing s2s stream");
-end
-
-function generate_dialback(id, to, from)
- return sha256_hash(id..to..from..hosts[from].dialback_secret, true);
-end
-
-function verify_dialback(id, to, from, key)
- return key == generate_dialback(id, to, from);
+ local host_session = { to_host = to_host, from_host = from_host, host = from_host,
+ notopen = true, type = "s2sout_unauthed", direction = "outgoing" };
+ hosts[from_host].s2sout[to_host] = host_session;
+ local conn_name = "s2sout"..tostring(host_session):match("[a-f0-9]*$");
+ host_session.log = logger_init(conn_name);
+ return host_session;
end
function make_authenticated(session, host)
@@ -576,10 +113,7 @@ function mark_connected(session)
local from, to = session.from_host, session.to_host;
session.log("info", session.direction.." s2s connection "..from.."->"..to.." complete");
-
- local send_to_host = send_to_host;
- 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);
@@ -599,6 +133,7 @@ function mark_connected(session)
session.sendq = nil;
end
+ session.ip_hosts = nil;
session.srv_hosts = nil;
end
end
@@ -636,7 +171,7 @@ function destroy_session(session, reason)
if session.direction == "outgoing" then
hosts[session.from_host].s2sout[session.to_host] = nil;
- bounce_sendq(session, reason);
+ session:bounce_sendq(reason);
elseif session.direction == "incoming" then
incoming_s2s[session] = nil;
end
diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua
index 1de6c41a..c101bf4e 100644
--- a/core/sessionmanager.lua
+++ b/core/sessionmanager.lua
@@ -6,26 +6,21 @@
-- COPYING file in the source package for more information.
--
-
-
local tonumber, tostring, setmetatable = tonumber, tostring, setmetatable;
local ipairs, pairs, print, next= ipairs, pairs, print, next;
-local format = string.format;
local hosts = hosts;
local full_sessions = full_sessions;
local bare_sessions = bare_sessions;
-local modulemanager = require "core.modulemanager";
local logger = require "util.logger";
local log = logger.init("sessionmanager");
local error = error;
-local uuid_generate = require "util.uuid".generate;
local rm_load_roster = require "core.rostermanager".load_roster;
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 uuid_generate = require "util.uuid".generate;
local initialize_filters = require "util.filters".initialize;
local fire_event = prosody.events.fire_event;
@@ -34,8 +29,6 @@ local gettime = require "socket".gettime;
local st = require "util.stanza";
-local c2s_timeout = config_get("*", "core", "c2s_timeout");
-
local newproxy = newproxy;
local getmetatable = getmetatable;
@@ -68,14 +61,6 @@ function new_session(conn)
session.ip = conn:ip();
local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
session.log = logger.init(conn_name);
-
- if c2s_timeout then
- add_task(c2s_timeout, function ()
- if session.type == "c2s_unauthed" then
- session:close("connection-timeout");
- end
- end);
- end
return session;
end
@@ -217,50 +202,6 @@ function bind_resource(session, resource)
return true;
end
-function streamopened(session, attr)
- local send = session.send;
- 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();
- (session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
-
- if not hosts[session.host] then
- -- We don't serve this host...
- session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
- return;
- end
-
- send("<?xml version='1.0'?>");
- send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host));
-
- (session.log or log)("debug", "Sent reply <stream:stream> to client");
- session.notopen = nil;
-
- -- If session.secure is *false* (not nil) then it means we /were/ encrypting
- -- since we now have a new stream header, session is secured
- if session.secure == false then
- session.secure = true;
- end
-
- local features = st.stanza("stream:features");
- hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
- fire_event("stream-features", session, features);
-
- send(features);
-
-end
-
-function streamclosed(session)
- session.log("debug", "Received </stream:stream>");
- session:close();
-end
-
function send_to_available_resources(user, host, stanza)
local jid = user.."@"..host;
local count = 0;
diff --git a/core/stanza_router.lua b/core/stanza_router.lua
index 406ad2f0..54c5a1a6 100644
--- a/core/stanza_router.lua
+++ b/core/stanza_router.lua
@@ -11,7 +11,6 @@ local log = require "util.logger".init("stanzarouter")
local hosts = _G.prosody.hosts;
local tostring = tostring;
local st = require "util.stanza";
-local send_s2s = require "core.s2smanager".send_to_host;
local jid_split = require "util.jid".split;
local jid_prepped_split = require "util.jid".prepped_split;
@@ -105,11 +104,6 @@ function core_process_stanza(origin, stanza)
stanza.attr.from = from;
end
- --[[if to and not(hosts[to]) and not(hosts[to_bare]) and (hosts[host] and hosts[host].type ~= "local") then -- not for us?
- log("warn", "stanza recieved for a non-local server");
- return; -- FIXME what should we do here?
- end]] -- FIXME
-
if (origin.type == "s2sin" or origin.type == "c2s" or origin.type == "component") and xmlns == nil then
if origin.type == "s2sin" and not origin.dummy then
local host_status = origin.hosts[from_host];
@@ -189,26 +183,18 @@ function core_route_stanza(origin, stanza)
if hosts[host] then
-- old stanza routing code removed
core_post_stanza(origin, stanza);
- elseif origin.type == "c2s" then
- -- Remote host
+ else
+ log("debug", "Routing to remote...");
if not hosts[from_host] then
log("error", "No hosts[from_host] (please report): %s", tostring(stanza));
- end
- if (not hosts[from_host]) or (not hosts[from_host].disallow_s2s) then
+ else
local xmlns = stanza.attr.xmlns;
- --stanza.attr.xmlns = "jabber:server";
stanza.attr.xmlns = nil;
- 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
+ local routed = prosody.events.fire_event("route/remote", { origin = origin, stanza = stanza, from_host = from_host, to_host = host }); --FIXME: Should be per-host (shared modules!)
stanza.attr.xmlns = xmlns; -- reset
- else
- core_route_stanza(hosts[from_host], st.error_reply(stanza, "cancel", "not-allowed", "Communication with remote servers is not allowed"));
+ if routed == nil then
+ core_route_stanza(hosts[from_host], st.error_reply(stanza, "cancel", "not-allowed", "Communication with remote domains is not enabled"));
+ end
end
- elseif origin.type == "component" or origin.type == "local" then
- -- Route via s2s for components and modules
- log("debug", "Routing outgoing stanza for %s to %s", from_host, host);
- send_s2s(from_host, host, stanza);
- else
- log("warn", "received %s stanza from unhandled connection type: %s", tostring(stanza.name), tostring(origin.type));
end
end
diff --git a/core/storagemanager.lua b/core/storagemanager.lua
index c96ef3ec..71e79271 100644
--- a/core/storagemanager.lua
+++ b/core/storagemanager.lua
@@ -47,7 +47,7 @@ prosody.events.add_handler("host-activated", initialize_host, 101);
function load_driver(host, driver_name)
if driver_name == "null" then
- return null_storage_provider;
+ return null_storage_driver;
end
local driver = stores_available:get(host, driver_name);
if driver then return driver; end
diff --git a/core/usermanager.lua b/core/usermanager.lua
index 0152afd7..50aee701 100644
--- a/core/usermanager.lua
+++ b/core/usermanager.lua
@@ -11,6 +11,7 @@ local log = require "util.logger".init("usermanager");
local type = type;
local ipairs = ipairs;
local jid_bare = require "util.jid".bare;
+local jid_prep = require "util.jid".prep;
local config = require "core.configmanager";
local hosts = hosts;
local sasl_new = require "util.sasl".new;
@@ -40,7 +41,10 @@ function initialize_host(host)
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 config.get(host, "core", "anonymous_login") then
+ log("error", "Deprecated config option 'anonymous_login'. Use authentication = 'anonymous' instead.");
+ auth_provider = "anonymous";
+ end -- COMPAT 0.7
if provider.name == auth_provider then
host_session.users = setmetatable(provider, provider_mt);
end
@@ -97,6 +101,7 @@ end
function is_admin(jid, host)
if host and not hosts[host] then return false; end
+ if type(jid) ~= "string" then return false; end
local is_admin;
jid = jid_bare(jid);
@@ -108,7 +113,7 @@ function is_admin(jid, host)
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
+ if jid_prep(admin) == jid then
is_admin = true;
break;
end
@@ -121,7 +126,7 @@ function is_admin(jid, host)
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
+ if jid_prep(admin) == jid then
is_admin = true;
break;
end
diff --git a/net/connlisteners.lua b/net/connlisteners.lua
index 6a227c9d..99ddc720 100644
--- a/net/connlisteners.lua
+++ b/net/connlisteners.lua
@@ -1,81 +1,15 @@
--- 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.
---
+-- COMPAT w/pre-0.9
+local log = require "util.logger".init("net.connlisteners");
+local traceback = debug.traceback;
+module "httpserver"
-
-local listeners_dir = (CFG_SOURCEDIR or ".").."/net/";
-local server = require "net.server";
-local log = require "util.logger".init("connlisteners");
-local tostring = tostring;
-local type = type
-local ipairs = ipairs
-
-local dofile, xpcall, error =
- dofile, xpcall, error
-
-local debug_traceback = debug.traceback;
-
-module "connlisteners"
-
-local listeners = {};
-
-function register(name, listener)
- if listeners[name] and listeners[name] ~= listener then
- log("debug", "Listener %s is already registered, not registering any more", name);
- return false;
- end
- listeners[name] = listener;
- log("debug", "Registered connection listener %s", name);
- return true;
+function fail()
+ log("error", "Attempt to use legacy connlisteners API. For more info see http://prosody.im/doc/developers/network");
+ log("error", "Legacy connlisteners API usage, %s", traceback("", 2));
end
-function deregister(name)
- listeners[name] = nil;
-end
-
-function get(name)
- local h = listeners[name];
- if not h then
- local ok, ret = xpcall(function() dofile(listeners_dir..name:gsub("[^%w%-]", "_").."_listener.lua") end, debug_traceback);
- if not ok then
- log("error", "Error while loading listener '%s': %s", tostring(name), tostring(ret));
- return nil, ret;
- end
- h = listeners[name];
- end
- return h;
-end
-
-function start(name, udata)
- local h, err = get(name);
- if not h then
- error("No such connection module: "..name.. (err and (" ("..err..")") or ""), 0);
- end
-
- local interfaces = (udata and udata.interface) or h.default_interface or "*";
- if type(interfaces) == "string" then interfaces = {interfaces}; end
- local port = (udata and udata.port) or h.default_port or error("Can't start listener "..name.." because no port was specified, and it has no default port", 0);
- local mode = (udata and udata.mode) or h.default_mode or 1;
- local ssl = (udata and udata.ssl) or nil;
- local autossl = udata and udata.type == "ssl";
-
- if autossl and not ssl then
- return nil, "no ssl context";
- end
-
- ok, err = true, {};
- for _, interface in ipairs(interfaces) do
- local handler
- handler, err[interface] = server.addserver(interface, port, h, mode, autossl and ssl or nil);
- ok = ok and handler;
- end
-
- return ok, err;
-end
+register, deregister = fail, fail;
+get, start = fail, fail, epic_fail;
return _M;
diff --git a/net/dns.lua b/net/dns.lua
index 8f428476..5b32a56c 100644
--- a/net/dns.lua
+++ b/net/dns.lua
@@ -358,6 +358,7 @@ function resolver:name() -- - - - - - - - - - - - - - - - - - - - - - name
local remember, pointers = nil, 0;
local len = self:byte();
local n = {};
+ if len == 0 then return "." end -- Root label
while len > 0 do
if len >= 0xc0 then -- name is "compressed"
pointers = pointers + 1;
diff --git a/net/http.lua b/net/http.lua
index 6287f408..59f2c080 100644
--- a/net/http.lua
+++ b/net/http.lua
@@ -13,9 +13,6 @@ local httpstream_new = require "util.httpstream".new;
local server = require "net.server"
-local connlisteners_get = require "net.connlisteners".get;
-local listener = connlisteners_get("httpclient") or error("No httpclient listener!");
-
local t_insert, t_concat = table.insert, table.concat;
local pairs, ipairs = pairs, ipairs;
local tonumber, tostring, xpcall, select, debug_traceback, char, format =
@@ -25,6 +22,52 @@ local log = require "util.logger".init("http");
module "http"
+local requests = {}; -- Open requests
+
+local listener = { default_port = 80, default_mode = "*a" };
+
+function listener.onconnect(conn)
+ local req = requests[conn];
+ -- Send the request
+ local request_line = { req.method or "GET", " ", req.path, " HTTP/1.1\r\n" };
+ if req.query then
+ t_insert(request_line, 4, "?"..req.query);
+ end
+
+ conn:write(t_concat(request_line));
+ local t = { [2] = ": ", [4] = "\r\n" };
+ for k, v in pairs(req.headers) do
+ t[1], t[3] = k, v;
+ conn:write(t_concat(t));
+ end
+ conn:write("\r\n");
+
+ if req.body then
+ conn:write(req.body);
+ end
+end
+
+function listener.onincoming(conn, data)
+ local request = requests[conn];
+
+ if not request then
+ log("warn", "Received response from connection %s with no request attached!", tostring(conn));
+ return;
+ end
+
+ if data and request.reader then
+ request:reader(data);
+ end
+end
+
+function listener.ondisconnect(conn, err)
+ local request = requests[conn];
+ if request and request.conn then
+ request:reader(nil);
+ end
+ requests[conn] = nil;
+end
+
function urlencode(s) return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end)); end
function urldecode(s) return s and (s:gsub("%%(%x%x)", function (c) return char(tonumber(c,16)); end)); end
@@ -152,8 +195,7 @@ function request(u, ex, callback)
req.reader = request_reader;
req.state = "status";
- listener.register_request(req.handler, req);
-
+ requests[req.handler] = req;
return req;
end
diff --git a/net/http/codes.lua b/net/http/codes.lua
new file mode 100644
index 00000000..0cadd079
--- /dev/null
+++ b/net/http/codes.lua
@@ -0,0 +1,67 @@
+
+local response_codes = {
+ -- Source: http://www.iana.org/assignments/http-status-codes
+ -- s/^\(\d*\)\s*\(.*\S\)\s*\[RFC.*\]\s*$/^I["\1"] = "\2";
+ [100] = "Continue";
+ [101] = "Switching Protocols";
+ [102] = "Processing";
+
+ [200] = "OK";
+ [201] = "Created";
+ [202] = "Accepted";
+ [203] = "Non-Authoritative Information";
+ [204] = "No Content";
+ [205] = "Reset Content";
+ [206] = "Partial Content";
+ [207] = "Multi-Status";
+ [208] = "Already Reported";
+ [226] = "IM Used";
+
+ [300] = "Multiple Choices";
+ [301] = "Moved Permanently";
+ [302] = "Found";
+ [303] = "See Other";
+ [304] = "Not Modified";
+ [305] = "Use Proxy";
+ -- The 306 status code was used in a previous version of [RFC2616], is no longer used, and the code is reserved.
+ [307] = "Temporary Redirect";
+
+ [400] = "Bad Request";
+ [401] = "Unauthorized";
+ [402] = "Payment Required";
+ [403] = "Forbidden";
+ [404] = "Not Found";
+ [405] = "Method Not Allowed";
+ [406] = "Not Acceptable";
+ [407] = "Proxy Authentication Required";
+ [408] = "Request Timeout";
+ [409] = "Conflict";
+ [410] = "Gone";
+ [411] = "Length Required";
+ [412] = "Precondition Failed";
+ [413] = "Request Entity Too Large";
+ [414] = "Request-URI Too Long";
+ [415] = "Unsupported Media Type";
+ [416] = "Requested Range Not Satisfiable";
+ [417] = "Expectation Failed";
+ [418] = "I'm a teapot";
+ [422] = "Unprocessable Entity";
+ [423] = "Locked";
+ [424] = "Failed Dependency";
+ -- The 425 status code is reserved for the WebDAV advanced collections expired proposal [RFC2817]
+ [426] = "Upgrade Required";
+
+ [500] = "Internal Server Error";
+ [501] = "Not Implemented";
+ [502] = "Bad Gateway";
+ [503] = "Service Unavailable";
+ [504] = "Gateway Timeout";
+ [505] = "HTTP Version Not Supported";
+ [506] = "Variant Also Negotiates"; -- Experimental
+ [507] = "Insufficient Storage";
+ [508] = "Loop Detected";
+ [510] = "Not Extended";
+};
+
+for k,v in pairs(response_codes) do response_codes[k] = k.." "..v; end
+return setmetatable(response_codes, { __index = function(t, k) return k.." Unassigned"; end })
diff --git a/net/http/parser.lua b/net/http/parser.lua
new file mode 100644
index 00000000..3d9d1a87
--- /dev/null
+++ b/net/http/parser.lua
@@ -0,0 +1,139 @@
+
+local tonumber = tonumber;
+local assert = assert;
+
+local function preprocess_path(path)
+ if path:sub(1,1) ~= "/" then
+ path = "/"..path;
+ end
+ local level = 0;
+ for component in path:gmatch("([^/]+)/") do
+ if component == ".." then
+ level = level - 1;
+ elseif component ~= "." then
+ level = level + 1;
+ end
+ if level < 0 then
+ return nil;
+ end
+ end
+ return path;
+end
+
+local httpstream = {};
+
+function httpstream.new(success_cb, error_cb, parser_type, options_cb)
+ local client = true;
+ if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end
+ local buf = "";
+ local chunked;
+ local state = nil;
+ local packet;
+ local len;
+ local have_body;
+ local error;
+ return {
+ feed = function(self, data)
+ if error then return nil, "parse has failed"; end
+ if not data then -- EOF
+ if state and client and not len then -- reading client body until EOF
+ packet.body = buf;
+ success_cb(packet);
+ elseif buf ~= "" then -- unexpected EOF
+ error = true; return error_cb();
+ end
+ return;
+ end
+ buf = buf..data;
+ while #buf > 0 do
+ if state == nil then -- read request
+ local index = buf:find("\r\n\r\n", nil, true);
+ if not index then return; end -- not enough data
+ local method, path, httpversion, status_code, reason_phrase;
+ local first_line;
+ local headers = {};
+ for line in buf:sub(1,index+1):gmatch("([^\r\n]+)\r\n") do -- parse request
+ if first_line then
+ local key, val = line:match("^([^%s:]+): *(.*)$");
+ if not key then error = true; return error_cb("invalid-header-line"); end -- TODO handle multi-line and invalid headers
+ key = key:lower();
+ headers[key] = headers[key] and headers[key]..","..val or val;
+ else
+ first_line = line;
+ if client then
+ httpversion, status_code, reason_phrase = line:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$");
+ if not status_code then error = true; return error_cb("invalid-status-line"); end
+ have_body = not
+ ( (options_cb and options_cb().method == "HEAD")
+ or (status_code == 204 or status_code == 304 or status_code == 301)
+ or (status_code >= 100 and status_code < 200) );
+ chunked = have_body and headers["transfer-encoding"] == "chunked";
+ else
+ method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$");
+ if not method then error = true; return error_cb("invalid-status-line"); end
+ end
+ end
+ end
+ len = tonumber(headers["content-length"]); -- TODO check for invalid len
+ if client then
+ -- FIXME handle '100 Continue' response (by skipping it)
+ if not have_body then len = 0; end
+ packet = {
+ code = status_code;
+ httpversion = httpversion;
+ headers = headers;
+ body = have_body and "" or nil;
+ -- COMPAT the properties below are deprecated
+ responseversion = httpversion;
+ responseheaders = headers;
+ };
+ else
+ -- path normalization
+ if path:match("^https?://") then
+ headers.host, path = path:match("^https?://([^/]*)(.*)");
+ end
+ path = preprocess_path(path);
+
+ len = len or 0;
+ packet = {
+ method = method;
+ path = path;
+ httpversion = httpversion;
+ headers = headers;
+ body = nil;
+ };
+ end
+ buf = buf:sub(index + 4);
+ state = true;
+ end
+ if state then -- read body
+ if client then
+ if chunked then
+ local index = buf:find("\r\n", nil, true);
+ if not index then return; end -- not enough data
+ local chunk_size = buf:match("^%x+");
+ if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end
+ chunk_size = tonumber(chunk_size, 16);
+ index = index + 2;
+ if chunk_size == 0 then
+ state = nil; success_cb(packet);
+ elseif #buf - index + 1 >= chunk_size then -- we have a chunk
+ packet.body = packet.body..buf:sub(index, index + chunk_size - 1);
+ buf = buf:sub(index + chunk_size);
+ end
+ error("trailers"); -- FIXME MUST read trailers
+ elseif len and #buf >= len then
+ packet.body, buf = buf:sub(1, len), buf:sub(len + 1);
+ state = nil; success_cb(packet);
+ end
+ elseif #buf >= len then
+ packet.body, buf = buf:sub(1, len), buf:sub(len + 1);
+ state = nil; success_cb(packet);
+ end
+ end
+ end
+ end;
+ };
+end
+
+return httpstream;
diff --git a/net/http/server.lua b/net/http/server.lua
new file mode 100644
index 00000000..69908e4e
--- /dev/null
+++ b/net/http/server.lua
@@ -0,0 +1,273 @@
+
+local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
+local parser_new = require "net.http.parser".new;
+local events = require "util.events".new();
+local addserver = require "net.server".addserver;
+local log = require "util.logger".init("http.server");
+local os_date = os.date;
+local pairs = pairs;
+local s_upper = string.upper;
+local setmetatable = setmetatable;
+local xpcall = xpcall;
+local debug = debug;
+local tostring = tostring;
+local codes = require "net.http.codes";
+
+local _M = {};
+
+local sessions = {};
+local listener = {};
+local hosts = {};
+local default_host;
+
+local function is_wildcard_event(event)
+ return event:sub(-2, -1) == "/*";
+end
+local function is_wildcard_match(wildcard_event, event)
+ return wildcard_event:sub(1, -2) == event:sub(1, #wildcard_event-1);
+end
+
+local event_map = events._event_map;
+setmetatable(events._handlers, {
+ __index = function (handlers, curr_event)
+ if is_wildcard_event(curr_event) then return; end -- Wildcard events cannot be fired
+ -- Find all handlers that could match this event, sort them
+ -- and then put the array into handlers[curr_event] (and return it)
+ local matching_handlers_set = {};
+ local handlers_array = {};
+ for event, handlers_set in pairs(event_map) do
+ if event == curr_event or
+ is_wildcard_event(event) and is_wildcard_match(event, curr_event) then
+ for handler, priority in pairs(handlers_set) do
+ matching_handlers_set[handler] = { (select(2, event:gsub("/", "%1"))), is_wildcard_event(event) and 0 or 1, priority };
+ table.insert(handlers_array, handler);
+ end
+ end
+ end
+ if #handlers_array > 0 then
+ table.sort(handlers_array, function(b, a)
+ local a_score, b_score = matching_handlers_set[a], matching_handlers_set[b];
+ for i = 1, #a_score do
+ if a_score[i] ~= b_score[i] then -- If equal, compare next score value
+ return a_score[i] < b_score[i];
+ end
+ end
+ return false;
+ end);
+ else
+ handlers_array = false;
+ end
+ rawset(handlers, curr_event, handlers_array);
+ return handlers_array;
+ end;
+ __newindex = function (handlers, curr_event, handlers_array)
+ if handlers_array == nil
+ and is_wildcard_event(curr_event) then
+ -- Invalidate the indexes of all matching events
+ for event in pairs(handlers) do
+ if is_wildcard_match(curr_event, event) then
+ handlers[event] = nil;
+ end
+ end
+ end
+ rawset(handlers, curr_event, handlers_array);
+ end;
+});
+
+local handle_request;
+local _1, _2, _3;
+local function _handle_request() return handle_request(_1, _2, _3); end
+
+local last_err;
+local function _traceback_handler(err) last_err = err; log("error", "Traceback[http]: %s: %s", tostring(err), debug.traceback()); end
+events.add_handler("http-error", function (error)
+ return "Error processing request: "..codes[error.code]..". Check your error log for more information.";
+end, -1);
+
+function listener.onconnect(conn)
+ local secure = conn:ssl() and true or nil;
+ local pending = {};
+ local waiting = false;
+ local function process_next()
+ --if waiting then log("debug", "can't process_next, waiting"); return; end
+ if sessions[conn] and #pending > 0 then
+ local request = t_remove(pending);
+ --log("debug", "process_next: %s", request.path);
+ waiting = true;
+ --handle_request(conn, request, process_next);
+ _1, _2, _3 = conn, request, process_next;
+ if not xpcall(_handle_request, _traceback_handler) then
+ conn:write("HTTP/1.0 500 Internal Server Error\r\n\r\n"..events.fire_event("http-error", { code = 500, private_message = last_err }));
+ conn:close();
+ end
+ else
+ --log("debug", "ready for more");
+ waiting = false;
+ end
+ end
+ local function success_cb(request)
+ --log("debug", "success_cb: %s", request.path);
+ request.secure = secure;
+ t_insert(pending, request);
+ if not waiting then
+ process_next();
+ end
+ end
+ local function error_cb(err)
+ log("debug", "error_cb: %s", err or "<nil>");
+ -- FIXME don't close immediately, wait until we process current stuff
+ -- FIXME if err, send off a bad-request response
+ sessions[conn] = nil;
+ conn:close();
+ end
+ sessions[conn] = parser_new(success_cb, error_cb);
+end
+
+function listener.ondisconnect(conn)
+ local open_response = conn._http_open_response;
+ if open_response and open_response.on_destroy then
+ open_response.finished = true;
+ open_response:on_destroy();
+ end
+ sessions[conn] = nil;
+end
+
+function listener.onincoming(conn, data)
+ sessions[conn]:feed(data);
+end
+
+local headerfix = setmetatable({}, {
+ __index = function(t, k)
+ local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": ";
+ t[k] = v;
+ return v;
+ end
+});
+
+function _M.hijack_response(response, listener)
+ error("TODO");
+end
+function handle_request(conn, request, finish_cb)
+ --log("debug", "handler: %s", request.path);
+ local headers = {};
+ for k,v in pairs(request.headers) do headers[k:gsub("-", "_")] = v; end
+ request.headers = headers;
+ request.conn = conn;
+
+ local date_header = os_date('!%a, %d %b %Y %H:%M:%S GMT'); -- FIXME use
+ local conn_header = request.headers.connection;
+ local keep_alive = conn_header == "Keep-Alive" or (request.httpversion == "1.1" and conn_header ~= "close");
+
+ local response = {
+ request = request;
+ status_code = 200;
+ headers = { date = date_header, connection = (keep_alive and "Keep-Alive" or "close") };
+ conn = conn;
+ send = _M.send_response;
+ finish_cb = finish_cb;
+ };
+ conn._http_open_response = response;
+
+ local host = (request.headers.host or ""):match("[^:]+");
+
+ -- Some sanity checking
+ local err_code, err;
+ if not request.path then
+ err_code, err = 400, "Invalid path";
+ elseif not hosts[host] then
+ if hosts[default_host] then
+ host = default_host;
+ elseif host then
+ err_code, err = 404, "Unknown host: "..host;
+ else
+ err_code, err = 400, "Missing or invalid 'Host' header";
+ end
+ end
+
+ if err then
+ response.status_code = err_code;
+ response:send(events.fire_event("http-error", { code = err_code, message = err }));
+ return;
+ end
+
+ local event = request.method.." "..host..request.path:match("[^?]*");
+ local payload = { request = request, response = response };
+ --log("debug", "Firing event: %s", event);
+ local result = events.fire_event(event, payload);
+ if result ~= nil then
+ if result ~= true then
+ local body;
+ local result_type = type(result);
+ if result_type == "number" then
+ response.status_code = result;
+ if result >= 400 then
+ body = events.fire_event("http-error", { code = result });
+ end
+ elseif result_type == "string" then
+ body = result;
+ elseif result_type == "table" then
+ for k, v in pairs(result) do
+ response[k] = v;
+ end
+ end
+ response:send(body);
+ end
+ return;
+ end
+
+ -- if handler not called, return 404
+ response.status_code = 404;
+ response:send(events.fire_event("http-error", { code = 404 }));
+end
+function _M.send_response(response, body)
+ if response.finished then return; end
+ response.finished = true;
+ response.conn._http_open_response = nil;
+
+ local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
+ local headers = response.headers;
+ body = body or response.body or "";
+ headers.content_length = #body;
+
+ local output = { status_line };
+ for k,v in pairs(headers) do
+ t_insert(output, headerfix[k]..v);
+ end
+ t_insert(output, "\r\n\r\n");
+ t_insert(output, body);
+
+ response.conn:write(t_concat(output));
+ if response.on_destroy then
+ response:on_destroy();
+ response.on_destroy = nil;
+ end
+ if headers.connection == "Keep-Alive" then
+ response:finish_cb();
+ else
+ response.conn:close();
+ end
+end
+function _M.add_handler(event, handler, priority)
+ events.add_handler(event, handler, priority);
+end
+function _M.remove_handler(event, handler)
+ events.remove_handler(event, handler);
+end
+
+function _M.listen_on(port, interface, ssl)
+ addserver(interface or "*", port, listener, "*a", ssl);
+end
+function _M.add_host(host)
+ hosts[host] = true;
+end
+function _M.remove_host(host)
+ hosts[host] = nil;
+end
+function _M.set_default_host(host)
+ default_host = host;
+end
+
+_M.listener = listener;
+_M.codes = codes;
+_M._events = events;
+return _M;
diff --git a/net/httpclient_listener.lua b/net/httpclient_listener.lua
deleted file mode 100644
index c4e3c153..00000000
--- a/net/httpclient_listener.lua
+++ /dev/null
@@ -1,66 +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 log = require "util.logger".init("httpclient_listener");
-local t_concat, t_insert = table.concat, table.insert;
-
-local connlisteners_register = require "net.connlisteners".register;
-
-local requests = {}; -- Open requests
-local buffers = {}; -- Buffers of partial lines
-
-local httpclient = { default_port = 80, default_mode = "*a" };
-
-function httpclient.onconnect(conn)
- local req = requests[conn];
- -- Send the request
- local request_line = { req.method or "GET", " ", req.path, " HTTP/1.1\r\n" };
- if req.query then
- t_insert(request_line, 4, "?"..req.query);
- end
-
- conn:write(t_concat(request_line));
- local t = { [2] = ": ", [4] = "\r\n" };
- for k, v in pairs(req.headers) do
- t[1], t[3] = k, v;
- conn:write(t_concat(t));
- end
- conn:write("\r\n");
-
- if req.body then
- conn:write(req.body);
- end
-end
-
-function httpclient.onincoming(conn, data)
- local request = requests[conn];
-
- if not request then
- log("warn", "Received response from connection %s with no request attached!", tostring(conn));
- return;
- end
-
- if data and request.reader then
- request:reader(data);
- end
-end
-
-function httpclient.ondisconnect(conn, err)
- local request = requests[conn];
- if request and request.conn then
- request:reader(nil);
- end
- requests[conn] = nil;
-end
-
-function httpclient.register_request(conn, req)
- log("debug", "Attaching request %s to connection %s", tostring(req.id or req), tostring(conn));
- requests[conn] = req;
-end
-
-connlisteners_register("httpclient", httpclient);
diff --git a/net/httpserver.lua b/net/httpserver.lua
index 74f61c56..7d574788 100644
--- a/net/httpserver.lua
+++ b/net/httpserver.lua
@@ -1,239 +1,15 @@
--- 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 server = require "net.server"
-local url_parse = require "socket.url".parse;
-local httpstream_new = require "util.httpstream".new;
-
-local connlisteners_start = require "net.connlisteners".start;
-local connlisteners_get = require "net.connlisteners".get;
-local listener;
-
-local t_insert, t_concat = table.insert, table.concat;
-local tonumber, tostring, pairs, ipairs, type = tonumber, tostring, pairs, ipairs, type;
-local xpcall = xpcall;
-local debug_traceback = debug.traceback;
-
-local urlencode = function (s) return s and (s:gsub("%W", function (c) return ("%%%02x"):format(c:byte()); end)); end
-
-local log = require "util.logger".init("httpserver");
-
-local http_servers = {};
+-- COMPAT w/pre-0.9
+local log = require "util.logger".init("net.httpserver");
+local traceback = debug.traceback;
module "httpserver"
-local default_handler;
-
-local function send_response(request, response)
- -- Write status line
- local resp;
- if response.body or response.headers then
- local body = response.body and tostring(response.body);
- log("debug", "Sending response to %s", request.id);
- resp = { "HTTP/1.0 "..(response.status or "200 OK").."\r\n" };
- local h = response.headers;
- if h then
- for k, v in pairs(h) do
- t_insert(resp, k..": "..v.."\r\n");
- end
- end
- if body and not (h and h["Content-Length"]) then
- t_insert(resp, "Content-Length: "..#body.."\r\n");
- end
- t_insert(resp, "\r\n");
-
- if body and request.method ~= "HEAD" then
- t_insert(resp, body);
- end
- request.write(t_concat(resp));
- else
- -- Response we have is just a string (the body)
- log("debug", "Sending 200 response to %s", request.id or "<none>");
-
- local resp = "HTTP/1.0 200 OK\r\n"
- .. "Connection: close\r\n"
- .. "Content-Type: text/html\r\n"
- .. "Content-Length: "..#response.."\r\n"
- .. "\r\n"
- .. response;
-
- request.write(resp);
- end
- if not request.stayopen then
- request:destroy();
- end
-end
-
-local function call_callback(request, err)
- if request.handled then return; end
- request.handled = true;
- local callback = request.callback;
- if not callback and request.path then
- local path = request.url.path;
- local base = path:match("^/([^/?]+)");
- if not base then
- base = path:match("^http://[^/?]+/([^/?]+)");
- end
-
- callback = (request.server and request.server.handlers[base]) or default_handler;
- end
- if callback then
- local _callback = callback;
- function callback(method, body, request)
- local ok, result = xpcall(function() return _callback(method, body, request) end, debug_traceback);
- if ok then return result; end
- log("error", "Error in HTTP server handler: %s", result);
- -- TODO: When we support pipelining, request.destroyed
- -- won't be the right flag - we just want to see if there
- -- has been a response to this request yet.
- if not request.destroyed then
- return {
- status = "500 Internal Server Error";
- headers = { ["Content-Type"] = "text/plain" };
- body = "There was an error processing your request. See the error log for more details.";
- };
- end
- end
- if err then
- log("debug", "Request error: "..err);
- if not callback(nil, err, request) then
- destroy_request(request);
- end
- return;
- end
-
- local response = callback(request.method, request.body and t_concat(request.body), request);
- if response then
- if response == true and not request.destroyed then
- -- Keep connection open, we will reply later
- log("debug", "Request %s left open, on_destroy is %s", request.id, tostring(request.on_destroy));
- elseif response ~= true then
- -- Assume response
- send_response(request, response);
- destroy_request(request);
- end
- else
- log("debug", "Request handler provided no response, destroying request...");
- -- No response, close connection
- destroy_request(request);
- end
- end
-end
-
-local function request_reader(request, data, startpos)
- if not request.parser then
- local function success_cb(r)
- for k,v in pairs(r) do request[k] = v; end
- request.url = url_parse(request.path);
- request.url.path = request.url.path and request.url.path:gsub("%%(%x%x)", function(x) return x.char(tonumber(x, 16)) end);
- request.body = { request.body };
- call_callback(request);
- end
- local function error_cb(r)
- call_callback(request, r or "connection-closed");
- destroy_request(request);
- end
- request.parser = httpstream_new(success_cb, error_cb);
- end
- request.parser:feed(data);
-end
-
--- The default handler for requests
-default_handler = function (method, body, request)
- log("debug", method.." request for "..tostring(request.path) .. " on port "..request.handler:serverport());
- return { status = "404 Not Found",
- headers = { ["Content-Type"] = "text/html" },
- body = "<html><head><title>Page Not Found</title></head><body>Not here :(</body></html>" };
-end
-
-
-function new_request(handler)
- return { handler = handler, conn = handler,
- write = function (...) return handler:write(...); end, state = "request",
- server = http_servers[handler:serverport()],
- send = send_response,
- destroy = destroy_request,
- id = tostring{}:match("%x+$")
- };
-end
-
-function destroy_request(request)
- log("debug", "Destroying request %s", request.id);
- listener = listener or connlisteners_get("httpserver");
- if not request.destroyed then
- request.destroyed = true;
- if request.on_destroy then
- log("debug", "Request has destroy callback");
- request.on_destroy(request);
- else
- log("debug", "Request has no destroy callback");
- end
- request.handler:close()
- if request.conn then
- listener.ondisconnect(request.conn, "closed");
- end
- end
-end
-
-function new(params)
- local http_server = http_servers[params.port];
- if not http_server then
- http_server = { handlers = {} };
- http_servers[params.port] = http_server;
- -- We weren't already listening on this port, so start now
- connlisteners_start("httpserver", params);
- end
- if params.base then
- http_server.handlers[params.base] = params.handler;
- end
-end
-
-function set_default_handler(handler)
- default_handler = handler;
-end
-
-function new_from_config(ports, handle_request, default_options)
- if type(handle_request) == "string" then -- COMPAT with old plugins
- log("warn", "Old syntax of httpserver.new_from_config being used to register %s", handle_request);
- handle_request, default_options = default_options, { base = handle_request };
- end
- ports = ports or {5280};
- for _, options in ipairs(ports) do
- local port = default_options.port or 5280;
- local base = default_options.base;
- local ssl = default_options.ssl or false;
- local interface = default_options.interface;
- if type(options) == "number" then
- port = options;
- elseif type(options) == "table" then
- port = options.port or port;
- base = options.path or base;
- ssl = options.ssl or ssl;
- interface = options.interface or interface;
- elseif type(options) == "string" then
- base = options;
- end
-
- if ssl then
- ssl.mode = "server";
- ssl.protocol = "sslv23";
- ssl.options = "no_sslv2";
- end
-
- new{ port = port, interface = interface,
- base = base, handler = handle_request,
- ssl = ssl, type = (ssl and "ssl") or "tcp" };
- end
+function fail()
+ log("error", "Attempt to use legacy HTTP API. For more info see http://prosody.im/doc/developers/legacy_http");
+ log("error", "Legacy HTTP API usage, %s", traceback("", 2));
end
-_M.request_reader = request_reader;
-_M.send_response = send_response;
-_M.urlencode = urlencode;
+new, new_from_config = fail, fail;
+set_default_handler = fail;
return _M;
diff --git a/net/httpserver_listener.lua b/net/httpserver_listener.lua
deleted file mode 100644
index dd14b43c..00000000
--- a/net/httpserver_listener.lua
+++ /dev/null
@@ -1,46 +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 connlisteners_register = require "net.connlisteners".register;
-local new_request = require "net.httpserver".new_request;
-local request_reader = require "net.httpserver".request_reader;
-
-local requests = {}; -- Open requests
-
-local httpserver = { default_port = 80, default_mode = "*a" };
-
-function httpserver.onincoming(conn, data)
- local request = requests[conn];
-
- if not request then
- request = new_request(conn);
- requests[conn] = request;
-
- -- If using HTTPS, request is secure
- if conn:ssl() then
- request.secure = true;
- end
- end
-
- if data and data ~= "" then
- request_reader(request, data);
- end
-end
-
-function httpserver.ondisconnect(conn, err)
- local request = requests[conn];
- if request and not request.destroyed then
- request.conn = nil;
- request_reader(request, nil);
- end
- requests[conn] = nil;
-end
-
-connlisteners_register("httpserver", httpserver);
diff --git a/net/multiplex_listener.lua b/net/multiplex_listener.lua
deleted file mode 100644
index b515ccce..00000000
--- a/net/multiplex_listener.lua
+++ /dev/null
@@ -1,50 +0,0 @@
-
-local connlisteners_register = require "net.connlisteners".register;
-local connlisteners_get = require "net.connlisteners".get;
-
-local httpserver_listener = connlisteners_get("httpserver");
-local xmppserver_listener = connlisteners_get("xmppserver");
-local xmppclient_listener = connlisteners_get("xmppclient");
-local xmppcomponent_listener = connlisteners_get("xmppcomponent");
-
-local server = { default_mode = "*a" };
-
-local buffer = {};
-
-function server.onincoming(conn, data)
- if not data then return; end
- local buf = buffer[conn];
- buffer[conn] = nil;
- buf = buf and buf..data or data;
- if buf:match("^[a-zA-Z]") then
- local listener = httpserver_listener;
- conn:setlistener(listener);
- local onconnect = listener.onconnect;
- if onconnect then onconnect(conn) end
- listener.onincoming(conn, buf);
- elseif buf:match(">") then
- local listener;
- local xmlns = buf:match("%sxmlns%s*=%s*['\"]([^'\"]*)");
- if xmlns == "jabber:server" then
- listener = xmppserver_listener;
- elseif xmlns == "jabber:component:accept" then
- listener = xmppcomponent_listener;
- else
- listener = xmppclient_listener;
- end
- conn:setlistener(listener);
- local onconnect = listener.onconnect;
- if onconnect then onconnect(conn) end
- listener.onincoming(conn, buf);
- elseif #buf > 1024 then
- conn:close();
- else
- buffer[conn] = buf;
- end
-end
-
-function server.ondisconnect(conn, err)
- buffer[conn] = nil; -- warn if no buffer?
-end
-
-connlisteners_register("multiplex", server);
diff --git a/net/server_event.lua b/net/server_event.lua
index f4745c34..8d6f5597 100644
--- a/net/server_event.lua
+++ b/net/server_event.lua
@@ -295,7 +295,10 @@ do
end
function interface_mt:resume()
- return self:_lock(self.nointerface, false, self.nowriting);
+ self:_lock(self.nointerface, false, self.nowriting);
+ if not self.eventread then
+ self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT ); -- register callback
+ end
end
function interface_mt:counter(c)
@@ -340,24 +343,11 @@ do
return nil, "writebuffer not empty, waiting"
end
else
- debug( "try to close server with id:", self.id, "args:", now )
+ debug( "try to close server with id:", tostring(self.id), "args:", tostring(now) )
self.fatalerror = "server to close"
self:_lock( true )
- local count = 0
- for _, item in ipairs( interfacelist( ) ) do
- if ( item.type ~= "server" ) and ( item._server == self ) then -- client/server match
- if item:close( now ) then -- writebuffer was empty
- count = count + 1
- end
- end
- end
- local timeout = 0 -- dont wait for unfinished writebuffers of clients...
- if not now then
- timeout = cfg.WRITE_TIMEOUT -- ...or wait for it
- end
- self:_close( timeout ) -- add new event to remove the server interface
- debug( "seconds remained until server is closed:", timeout )
- return count -- returns finished clients with empty writebuffer
+ self:_close( 0 ) -- add new event to remove the server interface
+ return true
end
end
@@ -642,6 +632,10 @@ do
return -1
end
end
+ if interface.noreading then
+ interface.eventread = nil;
+ return -1;
+ end
return EV_READ, cfg.READ_TIMEOUT
end
end
diff --git a/net/server_select.lua b/net/server_select.lua
index 412d03c0..d6cfc1f8 100644
--- a/net/server_select.lua
+++ b/net/server_select.lua
@@ -202,6 +202,7 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxco
socket:close( )
_sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
_readlistlen = removesocket( _readlist, socket, _readlistlen )
+ _server[ip..":"..serverport] = nil;
_socketlist[ socket ] = nil
handler = nil
socket = nil
@@ -596,25 +597,23 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
handler.sendbuffer = handshake
handshake( socket ) -- do handshake
end
- handler.readbuffer = _readbuffer
- handler.sendbuffer = _sendbuffer
-
- if sslctx then
- out_put "server.lua: auto-starting ssl negotiation..."
- handler.autostart_ssl = true;
- handler:starttls(sslctx);
- end
-
- else
- handler.readbuffer = _readbuffer
- handler.sendbuffer = _sendbuffer
end
+
+ handler.readbuffer = _readbuffer
+ handler.sendbuffer = _sendbuffer
send = socket.send
receive = socket.receive
shutdown = ( ssl and id ) or socket.shutdown
_socketlist[ socket ] = handler
_readlistlen = addsocket(_readlist, socket, _readlistlen)
+
+ if sslctx and luasec then
+ out_put "server.lua: auto-starting ssl negotiation..."
+ handler.autostart_ssl = true;
+ handler:starttls(sslctx);
+ end
+
return handler, socket
end
diff --git a/net/xmppcomponent_listener.lua b/net/xmppcomponent_listener.lua
deleted file mode 100644
index 90293559..00000000
--- a/net/xmppcomponent_listener.lua
+++ /dev/null
@@ -1,220 +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 hosts = _G.hosts;
-
-local t_concat = table.concat;
-local tostring = tostring;
-local type = type;
-local pairs = pairs;
-
-local lxp = require "lxp";
-local logger = require "util.logger";
-local config = require "core.configmanager";
-local connlisteners = require "net.connlisteners";
-local uuid_gen = require "util.uuid".generate;
-local jid_split = require "util.jid".split;
-local sha1 = require "util.hashes".sha1;
-local st = require "util.stanza";
-local new_xmpp_stream = require "util.xmppstream".new;
-
-local sessions = {};
-
-local log = logger.init("componentlistener");
-
-local component_listener = { default_port = 5347; default_mode = "*a"; default_interface = config.get("*", "core", "component_interface") or "127.0.0.1" };
-
-local xmlns_component = 'jabber:component:accept';
-
---- Callbacks/data for xmppstream to handle streams for us ---
-
-local stream_callbacks = { default_ns = xmlns_component };
-
-local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
-
-function stream_callbacks.error(session, error, data, data2)
- if session.destroyed then return; end
- log("warn", "Error processing component stream: "..tostring(error));
- if error == "no-stream" then
- session:close("invalid-namespace");
- elseif error == "parse-error" then
- session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(data));
- session:close("not-well-formed");
- elseif error == "stream-error" then
- local condition, text = "undefined-condition";
- for child in data:children() do
- if child.attr.xmlns == xmlns_xmpp_streams then
- if child.name ~= "text" then
- condition = child.name;
- else
- text = child:get_text();
- end
- if condition ~= "undefined-condition" and text then
- break;
- end
- end
- end
- text = condition .. (text and (" ("..text..")") or "");
- session.log("info", "Session closed by remote with error: %s", text);
- session:close(nil, text);
- end
-end
-
-function stream_callbacks.streamopened(session, attr)
- if config.get(attr.to, "core", "component_module") ~= "component" then
- -- Trying to act as a component domain which
- -- hasn't been configured
- session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" };
- return;
- end
-
- -- Note that we don't create the internal component
- -- until after the external component auths successfully
-
- session.host = attr.to;
- session.streamid = uuid_gen();
- session.notopen = nil;
-
- session.send(st.stanza("stream:stream", { xmlns=xmlns_component,
- ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag());
-
-end
-
-function stream_callbacks.streamclosed(session)
- session.log("debug", "Received </stream:stream>");
- session:close();
-end
-
-local core_process_stanza = core_process_stanza;
-
-function stream_callbacks.handlestanza(session, stanza)
- -- Namespaces are icky.
- if not stanza.attr.xmlns and stanza.name == "handshake" then
- stanza.attr.xmlns = xmlns_component;
- end
- if not stanza.attr.xmlns or stanza.attr.xmlns == "jabber:client" then
- local from = stanza.attr.from;
- if from then
- if session.component_validate_from then
- local _, domain = jid_split(stanza.attr.from);
- if domain ~= session.host then
- -- Return error
- session.log("warn", "Component sent stanza with missing or invalid 'from' address");
- session:close{
- condition = "invalid-from";
- text = "Component tried to send from address <"..tostring(from)
- .."> which is not in domain <"..tostring(session.host)..">";
- };
- return;
- end
- end
- else
- stanza.attr.from = session.host;
- end
- if not stanza.attr.to then
- session.log("warn", "Rejecting stanza with no 'to' address");
- session.send(st.error_reply(stanza, "modify", "bad-request", "Components MUST specify a 'to' address on stanzas"));
- return;
- end
- end
- return core_process_stanza(session, stanza);
-end
-
---- Closing a component connection
-local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
-local function session_close(session, reason)
- if session.destroyed then return; end
- local log = session.log or log;
- if session.conn then
- if session.notopen then
- session.send("<?xml version='1.0'?>");
- session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
- end
- if reason then
- if type(reason) == "string" then -- assume stream error
- log("info", "Disconnecting component, <stream:error> is: %s", reason);
- session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
- elseif type(reason) == "table" then
- if reason.condition then
- local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
- if reason.text then
- stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
- end
- if reason.extra then
- stanza:add_child(reason.extra);
- end
- log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza));
- session.send(stanza);
- elseif reason.name then -- a stanza
- log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason));
- session.send(reason);
- end
- end
- end
- session.send("</stream:stream>");
- session.conn:close();
- component_listener.ondisconnect(session.conn, "stream error");
- end
-end
-
---- Component connlistener
-function component_listener.onconnect(conn)
- local _send = conn.write;
- local session = { type = "component", conn = conn, send = function (data) return _send(conn, tostring(data)); end };
-
- -- Logging functions --
- local conn_name = "jcp"..tostring(conn):match("[a-f0-9]+$");
- session.log = logger.init(conn_name);
- session.close = session_close;
-
- session.log("info", "Incoming Jabber component connection");
-
- local stream = new_xmpp_stream(session, stream_callbacks);
- session.stream = stream;
-
- session.notopen = true;
-
- function session.reset_stream()
- session.notopen = true;
- session.stream:reset();
- end
-
- function session.data(conn, data)
- local ok, err = stream:feed(data);
- if ok then return; end
- log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
- session:close("not-well-formed");
- end
-
- session.dispatch_stanza = stream_callbacks.handlestanza;
-
- sessions[conn] = session;
-end
-function component_listener.onincoming(conn, data)
- local session = sessions[conn];
- session.data(conn, data);
-end
-function component_listener.ondisconnect(conn, err)
- local session = sessions[conn];
- if session then
- (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
- if session.on_destroy then session:on_destroy(err); end
- sessions[conn] = nil;
- for k in pairs(session) do
- if k ~= "log" and k ~= "close" then
- session[k] = nil;
- end
- end
- session.destroyed = true;
- session = nil;
- end
-end
-
-connlisteners.register('xmppcomponent', component_listener);
diff --git a/net/xmppserver_listener.lua b/net/xmppserver_listener.lua
deleted file mode 100644
index 3af0b962..00000000
--- a/net/xmppserver_listener.lua
+++ /dev/null
@@ -1,209 +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 tostring = tostring;
-local type = type;
-local xpcall = xpcall;
-local s_format = string.format;
-local traceback = debug.traceback;
-
-local logger = require "logger";
-local log = logger.init("xmppserver_listener");
-local st = require "util.stanza";
-local connlisteners_register = require "net.connlisteners".register;
-local new_xmpp_stream = require "util.xmppstream".new;
-local s2s_new_incoming = require "core.s2smanager".new_incoming;
-local s2s_streamopened = require "core.s2smanager".streamopened;
-local s2s_streamclosed = require "core.s2smanager".streamclosed;
-local s2s_destroy_session = require "core.s2smanager".destroy_session;
-local s2s_attempt_connect = require "core.s2smanager".attempt_connection;
-local stream_callbacks = { default_ns = "jabber:server",
- streamopened = s2s_streamopened, streamclosed = s2s_streamclosed, handlestanza = core_process_stanza };
-
-local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
-
-function stream_callbacks.error(session, error, data)
- if error == "no-stream" then
- session:close("invalid-namespace");
- elseif error == "parse-error" then
- session.log("debug", "Server-to-server XML parse error: %s", tostring(error));
- session:close("not-well-formed");
- elseif error == "stream-error" then
- local condition, text = "undefined-condition";
- for child in data:children() do
- if child.attr.xmlns == xmlns_xmpp_streams then
- if child.name ~= "text" then
- condition = child.name;
- else
- text = child:get_text();
- end
- if condition ~= "undefined-condition" and text then
- break;
- end
- end
- end
- text = condition .. (text and (" ("..text..")") or "");
- session.log("info", "Session closed by remote with error: %s", text);
- session:close(nil, text);
- end
-end
-
-local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end
-function stream_callbacks.handlestanza(session, stanza)
- if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
- stanza.attr.xmlns = nil;
- end
- stanza = session.filter("stanzas/in", stanza);
- if stanza then
- return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
- end
-end
-
-local sessions = {};
-local xmppserver = { default_port = 5269, default_mode = "*a" };
-
--- These are session methods --
-
-local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
-local function session_close(session, reason, remote_reason)
- local log = session.log or log;
- if session.conn then
- if session.notopen then
- session.sends2s("<?xml version='1.0'?>");
- session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
- end
- if reason then
- if type(reason) == "string" then -- assume stream error
- log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, reason);
- session.sends2s(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
- elseif type(reason) == "table" then
- if reason.condition then
- local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
- if reason.text then
- stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
- end
- if reason.extra then
- stanza:add_child(reason.extra);
- end
- log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, tostring(stanza));
- session.sends2s(stanza);
- elseif reason.name then -- a stanza
- log("info", "Disconnecting %s->%s[%s], <stream:error> is: %s", session.from_host or "(unknown host)", session.to_host or "(unknown host)", session.type, tostring(reason));
- session.sends2s(reason);
- end
- end
- end
- session.sends2s("</stream:stream>");
- if session.notopen or not session.conn:close() then
- session.conn:close(true); -- Force FIXME: timer?
- end
- session.conn:close();
- xmppserver.ondisconnect(session.conn, remote_reason or (reason and (reason.text or reason.condition)) or reason or "stream closed");
- end
-end
-
-
--- End of session methods --
-
-local function initialize_session(session)
- local stream = new_xmpp_stream(session, stream_callbacks);
- session.stream = stream;
-
- session.notopen = true;
-
- function session.reset_stream()
- session.notopen = true;
- session.stream:reset();
- end
-
- local filter = session.filter;
- function session.data(data)
- data = filter("bytes/in", data);
- if data then
- local ok, err = stream:feed(data);
- if ok then return; end
- (session.log or log)("warn", "Received invalid XML: %s", data);
- (session.log or log)("warn", "Problem was: %s", err);
- session:close("not-well-formed");
- end
- end
-
- session.close = session_close;
- local handlestanza = stream_callbacks.handlestanza;
- function session.dispatch_stanza(session, stanza)
- return handlestanza(session, stanza);
- end
-end
-
-function xmppserver.onconnect(conn)
- if not sessions[conn] then -- May be an existing outgoing session
- local session = s2s_new_incoming(conn);
- sessions[conn] = session;
-
- -- Logging functions --
- local conn_name = "s2sin"..tostring(conn):match("[a-f0-9]+$");
- session.log = logger.init(conn_name);
-
- session.log("info", "Incoming s2s connection");
-
- initialize_session(session);
- end
-end
-
-function xmppserver.onincoming(conn, data)
- local session = sessions[conn];
- if session then
- session.data(data);
- end
-end
-
-function xmppserver.onstatus(conn, status)
- if status == "ssl-handshake-complete" then
- local session = sessions[conn];
- if session and session.direction == "outgoing" then
- local to_host, from_host = session.to_host, session.from_host;
- session.log("debug", "Sending stream header...");
- session.sends2s(s_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'>]], from_host, to_host));
- end
- end
-end
-
-function xmppserver.ondisconnect(conn, err)
- local session = sessions[conn];
- if session then
- if err and err ~= "closed" and session.srv_hosts then
- (session.log or log)("debug", "s2s connection attempt failed: %s", err);
- if s2s_attempt_connect(session, err) then
- (session.log or log)("debug", "...so we're going to try another target");
- return; -- Session lives for now
- end
- end
- (session.log or log)("info", "s2s disconnected: %s->%s (%s)", tostring(session.from_host), tostring(session.to_host), tostring(err or "closed"));
- s2s_destroy_session(session, err);
- sessions[conn] = nil;
- session = nil;
- end
-end
-
-function xmppserver.register_outgoing(conn, session)
- session.direction = "outgoing";
- sessions[conn] = session;
-
- initialize_session(session);
-end
-
-connlisteners_register("xmppserver", xmppserver);
-
-
--- We need to perform some initialisation when a connection is created
--- We also need to perform that same initialisation at other points (SASL, TLS, ...)
-
--- ...and we need to handle data
--- ...and record all sessions associated with connections
diff --git a/plugins/adhoc/adhoc.lib.lua b/plugins/adhoc/adhoc.lib.lua
index 0cb4efe1..57059b3f 100644
--- a/plugins/adhoc/adhoc.lib.lua
+++ b/plugins/adhoc/adhoc.lib.lua
@@ -25,6 +25,7 @@ function _M.new(name, node, handler, permission)
end
function _M.handle_cmd(command, origin, stanza)
+ local cmdtag, actions;
local sessionid = stanza.tags[1].attr.sessionid or uuid.generate();
local dataIn = {};
dataIn.to = stanza.attr.to;
@@ -58,7 +59,7 @@ function _M.handle_cmd(command, origin, stanza)
elseif name == "error" then
cmdtag:tag("note", {type="error"}):text(content.message):up();
elseif name =="actions" then
- local actions = st.stanza("actions");
+ actions = st.stanza("actions");
for _, action in ipairs(content) do
if (action == "prev") or (action == "next") or (action == "complete") then
actions:tag(action):up();
@@ -67,7 +68,6 @@ function _M.handle_cmd(command, origin, stanza)
'" at node "'..command.node..'" provided an invalid action "'..action..'"');
end
end
- cmdtag:add_child(actions);
elseif name == "form" then
cmdtag:add_child((content.layout or content):form(content.values));
elseif name == "result" then
@@ -76,6 +76,13 @@ function _M.handle_cmd(command, origin, stanza)
cmdtag:add_child(content);
end
end
+
+ if not actions then
+ actions = st.stanza("actions");
+ actions:tag("complete"):up();
+ end
+ cmdtag:add_child(actions);
+
stanza:add_child(cmdtag);
origin.send(stanza);
diff --git a/plugins/adhoc/mod_adhoc.lua b/plugins/adhoc/mod_adhoc.lua
index 6d826338..49d07103 100644
--- a/plugins/adhoc/mod_adhoc.lua
+++ b/plugins/adhoc/mod_adhoc.lua
@@ -90,19 +90,13 @@ module:hook("iq/host/"..xmlns_cmd..":command", function (event)
end
end, 500);
-local function handle_item_added(item)
+local function adhoc_added(event)
+ local item = event.item;
commands[item.node] = item;
end
-module:hook("item-added/adhoc", function (event)
- return handle_item_added(event.item);
-end, 500);
-
-module:hook("item-removed/adhoc", function (event)
+local function adhoc_removed(event)
commands[event.item.node] = nil;
-end, 500);
-
--- Pick up any items that are already added
-for _, item in ipairs(module:get_host_items("adhoc")) do
- handle_item_added(item);
end
+
+module:handle_items("adhoc", adhoc_added, adhoc_removed);
diff --git a/plugins/mod_admin_adhoc.lua b/plugins/mod_admin_adhoc.lua
index d78c1aee..4d2c60d7 100644
--- a/plugins/mod_admin_adhoc.lua
+++ b/plugins/mod_admin_adhoc.lua
@@ -10,7 +10,8 @@ local prosody = _G.prosody;
local hosts = prosody.hosts;
local t_concat = table.concat;
-require "util.iterators";
+local iterators = require "util.iterators";
+local keys, values = iterators.keys, iterators.values;
local usermanager_user_exists = require "core.usermanager".user_exists;
local usermanager_create_user = require "core.usermanager".create_user;
local usermanager_get_password = require "core.usermanager".get_password;
@@ -23,6 +24,7 @@ local dataforms_new = require "util.dataforms".new;
local array = require "util.array";
local modulemanager = require "modulemanager";
+module:depends("adhoc");
local adhoc_new = module:require "adhoc".new;
function add_user_command_handler(self, data, state)
diff --git a/plugins/mod_admin_telnet.lua b/plugins/mod_admin_telnet.lua
index 0dfdc827..544ea161 100644
--- a/plugins/mod_admin_telnet.lua
+++ b/plugins/mod_admin_telnet.lua
@@ -6,27 +6,25 @@
-- COPYING file in the source package for more information.
--
-module.host = "*";
+module:set_global();
local _G = _G;
local prosody = _G.prosody;
local hosts = prosody.hosts;
-local connlisteners_register = require "net.connlisteners".register;
-local console_listener = { default_port = 5582; default_mode = "*l"; default_interface = "127.0.0.1" };
+local console_listener = { default_port = 5582; default_mode = "*l"; interface = "127.0.0.1" };
-require "util.iterators";
+local iterators = require "util.iterators";
+local keys, values = iterators.keys, iterators.values;
local jid_bare = require "util.jid".bare;
local set, array = require "util.set", require "util.array";
local cert_verify_identity = require "util.x509".verify_identity;
-local commands = {};
-local def_env = {};
+local commands = module:shared("commands")
+local def_env = module:shared("env");
local default_env_mt = { __index = def_env };
-prosody.console = { commands = commands, env = def_env };
-
local function redirect_output(_G, session)
local env = setmetatable({ print = session.print }, { __index = function (t, k) return rawget(_G, k); end });
env.dofile = function(name)
@@ -149,8 +147,6 @@ function console_listener.ondisconnect(conn, err)
end
end
-connlisteners_register('console', console_listener);
-
-- Console commands --
-- These are simple commands, not valid standalone in Lua
@@ -281,8 +277,12 @@ local function get_hosts_set(hosts, module)
return set.new { hosts };
elseif hosts == nil then
local mm = require "modulemanager";
- return set.new(array.collect(keys(prosody.hosts)))
+ local hosts_set = set.new(array.collect(keys(prosody.hosts)))
/ function (host) return prosody.hosts[host].type == "local" or module and mm.is_loaded(host, module); end;
+ if module and mm.get_module("*", module) then
+ hosts_set:add("*");
+ end
+ return hosts_set;
end
end
@@ -292,16 +292,22 @@ function def_env.module:load(name, hosts, config)
hosts = get_hosts_set(hosts);
-- Load the module for each host
- local ok, err, count = true, nil, 0;
+ local ok, err, count, mod = true, nil, 0, nil;
for host in hosts do
if (not mm.is_loaded(host, name)) then
- ok, err = mm.load(host, name, config);
- if not ok then
+ mod, err = mm.load(host, name, config);
+ if not mod then
ok = false;
+ if err == "global-module-already-loaded" then
+ if count > 0 then
+ ok, err, count = true, nil, 1;
+ end
+ break;
+ end
self.session.print(err or "Unknown error loading module");
else
count = count + 1;
- self.session.print("Loaded for "..host);
+ self.session.print("Loaded for "..mod.module.host);
end
end
end
@@ -334,11 +340,15 @@ end
function def_env.module:reload(name, hosts)
local mm = require "modulemanager";
- hosts = get_hosts_set(hosts, name);
-
+ hosts = array.collect(get_hosts_set(hosts, name)):sort(function (a, b)
+ if a == "*" then return true
+ elseif b == "*" then return false
+ else return a < b; end
+ end);
+
-- Reload the module for each host
local ok, err, count = true, nil, 0;
- for host in hosts do
+ for _, host in ipairs(hosts) do
if mm.is_loaded(host, name) then
ok, err = mm.reload(host, name);
if not ok then
@@ -359,6 +369,7 @@ end
function def_env.module:list(hosts)
if hosts == nil then
hosts = array.collect(keys(prosody.hosts));
+ table.insert(hosts, 1, "*");
end
if type(hosts) == "string" then
hosts = { hosts };
@@ -369,8 +380,8 @@ function def_env.module:list(hosts)
local print = self.session.print;
for _, host in ipairs(hosts) do
- print(host..":");
- local modules = array.collect(keys(prosody.hosts[host] and prosody.hosts[host].modules or {})):sort();
+ print((host == "*" and "Global" or host)..":");
+ local modules = array.collect(keys(modulemanager.get_modules(host) or {})):sort();
if #modules == 0 then
if prosody.hosts[host] then
print(" No modules loaded");
@@ -429,6 +440,16 @@ local function show_c2s(callback)
end
end
+function def_env.c2s:count(match_jid)
+ local count = 0;
+ show_c2s(function (jid, session)
+ if (not match_jid) or jid:match(match_jid) then
+ count = count + 1;
+ end
+ end);
+ return true, "Total: "..count.." clients";
+end
+
function def_env.c2s:show(match_jid)
local print, count = self.session.print, 0;
local curr_host;
@@ -766,6 +787,51 @@ function def_env.host:list()
return true, i.." hosts";
end
+def_env.port = {};
+
+function def_env.port:list()
+ local print = self.session.print;
+ local services = portmanager.get_active_services().data;
+ local ordered_services, n_ports = {}, 0;
+ for service, interfaces in pairs(services) do
+ table.insert(ordered_services, service);
+ end
+ table.sort(ordered_services);
+ for _, service in ipairs(ordered_services) do
+ local ports_list = {};
+ for interface, ports in pairs(services[service]) do
+ for port in pairs(ports) do
+ table.insert(ports_list, "["..interface.."]:"..port);
+ end
+ end
+ n_ports = n_ports + #ports_list;
+ print(service..": "..table.concat(ports_list, ", "));
+ end
+ return true, #ordered_services.." services listening on "..n_ports.." ports";
+end
+
+function def_env.port:close(close_port, close_interface)
+ close_port = assert(tonumber(close_port), "Invalid port number");
+ local n_closed = 0;
+ local services = portmanager.get_active_services().data;
+ for service, interfaces in pairs(services) do
+ for interface, ports in pairs(interfaces) do
+ if not close_interface or close_interface == interface then
+ if ports[close_port] then
+ self.session.print("Closing ["..interface.."]:"..close_port.."...");
+ local ok, err = portmanager.close(interface, close_port)
+ if not ok then
+ self.session.print("Failed to close "..interface.." "..port..": "..err);
+ else
+ n_closed = n_closed + 1;
+ end
+ end
+ end
+ end
+ end
+ return true, "Closed "..n_closed.." ports";
+end
+
-------------
function printbanner(session)
@@ -796,4 +862,9 @@ if option and option ~= "short" and option ~= "full" and option ~= "graphic" the
end
end
-prosody.net_activate_ports("console", "console", {5582}, "tcp");
+module:add_item("net-provider", {
+ name = "console";
+ listener = console_listener;
+ default_port = 5582;
+ private = true;
+});
diff --git a/plugins/mod_auth_anonymous.lua b/plugins/mod_auth_anonymous.lua
index 8d790508..056d7e6b 100644
--- a/plugins/mod_auth_anonymous.lua
+++ b/plugins/mod_auth_anonymous.lua
@@ -6,7 +6,6 @@
-- COPYING file in the source package for more information.
--
-local log = require "util.logger".init("auth_anonymous");
local new_sasl = require "util.sasl".new;
local datamanager = require "util.datamanager";
diff --git a/plugins/mod_auth_internal_hashed.lua b/plugins/mod_auth_internal_hashed.lua
index ee810426..607ecab4 100644
--- a/plugins/mod_auth_internal_hashed.lua
+++ b/plugins/mod_auth_internal_hashed.lua
@@ -9,22 +9,11 @@
local datamanager = require "util.datamanager";
local log = require "util.logger".init("auth_internal_hashed");
-local type = type;
-local error = error;
-local ipairs = ipairs;
-local hashes = require "util.hashes";
-local jid_bare = require "util.jid".bare;
local getAuthenticationDatabaseSHA1 = require "util.sasl.scram".getAuthenticationDatabaseSHA1;
-local config = require "core.configmanager";
local usermanager = require "core.usermanager";
local generate_uuid = require "util.uuid".generate;
local new_sasl = require "util.sasl".new;
local nodeprep = require "util.encodings".stringprep.nodeprep;
-local hosts = hosts;
-
--- COMPAT w/old trunk: remove these two lines before 0.8 release
-local hmac_sha1 = require "util.hmac".sha1;
-local sha1 = require "util.hashes".sha1;
local to_hex;
do
@@ -47,14 +36,12 @@ do
end
-local prosody = _G.prosody;
-
-- Default; can be set per-user
local iteration_count = 4096;
function new_hashpass_provider(host)
local provider = { name = "internal_hashed" };
- log("debug", "initializing hashpass authentication provider for host '%s'", host);
+ log("debug", "initializing internal_hashed authentication provider for host '%s'", host);
function provider.test_password(username, password)
local credentials = datamanager.load(username, host, "accounts") or {};
@@ -75,16 +62,6 @@ function new_hashpass_provider(host)
return nil, "Auth failed. Stored salt and iteration count information is not complete.";
end
- -- convert hexpass to stored_key and server_key
- -- COMPAT w/old trunk: remove before 0.8 release
- if credentials.hashpass then
- local salted_password = from_hex(credentials.hashpass);
- credentials.stored_key = sha1(hmac_sha1(salted_password, "Client Key"), true);
- credentials.server_key = to_hex(hmac_sha1(salted_password, "Server Key"));
- credentials.hashpass = nil
- datamanager.store(username, host, "accounts", credentials);
- end
-
local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, credentials.salt, credentials.iteration_count);
local stored_key_hex = to_hex(stored_key);
@@ -158,16 +135,6 @@ function new_hashpass_provider(host)
if not credentials then return; end
end
- -- convert hexpass to stored_key and server_key
- -- COMPAT w/old trunk: remove before 0.8 release
- if credentials.hashpass then
- local salted_password = from_hex(credentials.hashpass);
- credentials.stored_key = sha1(hmac_sha1(salted_password, "Client Key"), true);
- credentials.server_key = to_hex(hmac_sha1(salted_password, "Server Key"));
- credentials.hashpass = nil
- datamanager.store(username, host, "accounts", credentials);
- end
-
local stored_key, server_key, iteration_count, salt = credentials.stored_key, credentials.server_key, credentials.iteration_count, credentials.salt;
stored_key = stored_key and from_hex(stored_key);
server_key = server_key and from_hex(server_key);
diff --git a/plugins/mod_auth_internal_plain.lua b/plugins/mod_auth_internal_plain.lua
index 784553ea..89dd7f1b 100644
--- a/plugins/mod_auth_internal_plain.lua
+++ b/plugins/mod_auth_internal_plain.lua
@@ -7,23 +7,15 @@
--
local datamanager = require "util.datamanager";
-local log = require "util.logger".init("auth_internal_plain");
-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 usermanager = require "core.usermanager";
local new_sasl = require "util.sasl".new;
local nodeprep = require "util.encodings".stringprep.nodeprep;
-local hosts = hosts;
-local prosody = _G.prosody;
+local log = module._log;
function new_default_provider(host)
local provider = { name = "internal_plain" };
- log("debug", "initializing default authentication provider for host '%s'", host);
+ log("debug", "initializing internal_plain authentication provider for host '%s'", host);
function provider.test_password(username, password)
log("debug", "test password '%s' for user %s at host %s", password, username, module.host);
diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua
index 7b1d8800..24dc3755 100644
--- a/plugins/mod_bosh.lua
+++ b/plugins/mod_bosh.lua
@@ -6,12 +6,10 @@
-- COPYING file in the source package for more information.
--
-module.host = "*" -- Global module
+module:set_global(); -- Global module
local hosts = _G.hosts;
-local lxp = require "lxp";
local new_xmpp_stream = require "util.xmppstream".new;
-local httpserver = require "net.httpserver";
local sm = require "core.sessionmanager";
local sm_destroy_session = sm.destroy_session;
local new_uuid = require "util.uuid".generate;
@@ -20,7 +18,6 @@ local core_process_stanza = core_process_stanza;
local st = require "util.stanza";
local logger = require "util.logger";
local log = logger.init("mod_bosh");
-local timer = require "util.timer";
local xmlns_streams = "http://etherx.jabber.org/streams";
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
@@ -35,6 +32,7 @@ local BOSH_DEFAULT_POLLING = module:get_option_number("bosh_max_polling", 5);
local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2);
local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure");
+local auto_cork = module:get_option_boolean("bosh_auto_cork", false);
local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" };
@@ -57,7 +55,7 @@ end
local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items;
local function get_ip_from_request(request)
- local ip = request.handler:ip();
+ local ip = request.conn:ip();
local forwarded_for = request.headers["x-forwarded-for"];
if forwarded_for then
forwarded_for = forwarded_for..", "..ip;
@@ -79,11 +77,12 @@ local inactive_sessions = {}; -- Sessions which have no open requests
-- Used to respond to idle sessions (those with waiting requests)
local waiting_requests = {};
function on_destroy_request(request)
+ log("debug", "Request destroyed: %s", tostring(request));
waiting_requests[request] = nil;
- local session = sessions[request.sid];
+ local session = sessions[request.context.sid];
if session then
local requests = session.requests;
- for i,r in ipairs(requests) do
+ for i, r in ipairs(requests) do
if r == request then
t_remove(requests, i);
break;
@@ -91,42 +90,50 @@ function on_destroy_request(request)
end
-- If this session now has no requests open, mark it as inactive
- if #requests == 0 and session.bosh_max_inactive and not inactive_sessions[session] then
- inactive_sessions[session] = os_time();
- (session.log or log)("debug", "BOSH session marked as inactive at %d", inactive_sessions[session]);
+ local max_inactive = session.bosh_max_inactive;
+ if max_inactive and #requests == 0 then
+ inactive_sessions[session] = os_time() + max_inactive;
+ (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive);
end
end
end
-function handle_request(method, body, request)
- if (not body) or request.method ~= "POST" then
- if request.method == "OPTIONS" then
- local headers = {};
- for k,v in pairs(default_headers) do headers[k] = v; end
- headers["Content-Type"] = nil;
- return { headers = headers, body = "" };
- else
- return "<html><body>You really don't look like a BOSH client to me... what do you want?</body></html>";
- end
- end
- if not method then
- log("debug", "Request %s suffered error %s", tostring(request.id), body);
- return;
- end
- --log("debug", "Handling new request %s: %s\n----------", request.id, tostring(body));
- request.notopen = true;
- request.log = log;
- request.on_destroy = on_destroy_request;
+local function handle_GET(request)
+ return [[<html><body>
+ <p>It works! Now point your BOSH client to this URL to connect to Prosody.</p>
+ <p>For more information see <a href="http://prosody.im/doc/setting_up_bosh">Prosody: Setting up BOSH</a>.</p>
+</body></html>]];
+end
+
+function handle_OPTIONS(request)
+ local headers = {};
+ for k,v in pairs(default_headers) do headers[k] = v; end
+ headers["Content-Type"] = nil;
+ return { headers = headers, body = "" };
+end
+
+function handle_POST(event)
+ log("debug", "Handling new request %s: %s\n----------", tostring(event.request), tostring(event.request.body));
+
+ local request, response = event.request, event.response;
+ response.on_destroy = on_destroy_request;
+ local body = request.body;
+
+ local context = { request = request, response = response, notopen = true };
+ local stream = new_xmpp_stream(context, stream_callbacks);
+ response.context = context;
- local stream = new_xmpp_stream(request, stream_callbacks);
-- stream:feed() calls the stream_callbacks, so all stanzas in
-- the body are processed in this next line before it returns.
- local ok, err = stream:feed(body);
- if not ok then
- log("error", "Failed to parse BOSH payload: %s", err);
- end
+ -- In particular, the streamopened() stream callback is where
+ -- much of the session logic happens, because it's where we first
+ -- get to see the 'sid' of this request.
+ stream:feed(body);
- local session = sessions[request.sid];
+ -- Stanzas (if any) in the request have now been processed, and
+ -- we take care of the high-level BOSH logic here, including
+ -- giving a response or putting the request "on hold".
+ local session = sessions[context.sid];
if session then
-- Session was marked as inactive, since we have
-- a request open now, unmark it
@@ -135,8 +142,11 @@ function handle_request(method, body, request)
end
local r = session.requests;
- log("debug", "Session %s has %d out of %d requests open", request.sid, #r, session.bosh_hold);
- log("debug", "and there are %d things in the send_buffer", #session.send_buffer);
+ log("debug", "Session %s has %d out of %d requests open", context.sid, #r, session.bosh_hold);
+ log("debug", "and there are %d things in the send_buffer:", #session.send_buffer);
+ for i, thing in ipairs(session.send_buffer) do
+ log("debug", " %s", tostring(thing));
+ end
if #r > session.bosh_hold then
-- We are holding too many requests, send what's in the buffer,
log("debug", "We are holding too many requests, so...");
@@ -156,12 +166,11 @@ function handle_request(method, body, request)
session.send(resp);
end
- if not request.destroyed then
+ if not response.finished then
-- We're keeping this request open, to respond later
log("debug", "Have nothing to say, so leaving request unanswered for now");
if session.bosh_wait then
- request.reply_before = os_time() + session.bosh_wait;
- waiting_requests[request] = true;
+ waiting_requests[response] = os_time() + session.bosh_wait;
end
end
@@ -170,7 +179,7 @@ function handle_request(method, body, request)
session:close();
return nil;
else
- return true; -- Inform httpserver we shall reply later
+ return true; -- Inform http server we shall reply later
end
end
end
@@ -209,22 +218,24 @@ local function bosh_close_stream(session, reason)
log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply));
end
- local session_close_response = { headers = default_headers, body = tostring(close_reply) };
-
+ local response_body = tostring(close_reply);
for _, held_request in ipairs(session.requests) do
- held_request:send(session_close_response);
- held_request:destroy();
+ held_request.headers = default_headers;
+ held_request:send(response_body);
end
sessions[session.sid] = nil;
+ inactive_sessions[session] = nil;
sm_destroy_session(session);
end
-function stream_callbacks.streamopened(request, attr)
+-- Handle the <body> tag in the request payload.
+function stream_callbacks.streamopened(context, attr)
+ local request, response = context.request, context.response;
local sid = attr.sid;
log("debug", "BOSH body open (sid: %s)", sid or "<none>");
if not sid then
-- New session request
- request.notopen = nil; -- Signals that we accept this opening tag
+ context.notopen = nil; -- Signals that we accept this opening tag
-- TODO: Sanity checks here (rid, to, known host, etc.)
if not hosts[attr.to] then
@@ -232,7 +243,7 @@ function stream_callbacks.streamopened(request, attr)
log("debug", "BOSH client tried to connect to unknown host: %s", tostring(attr.to));
local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
["xmlns:stream"] = xmlns_streams, condition = "host-unknown" });
- request:send(tostring(close_reply));
+ response:send(tostring(close_reply));
return;
end
@@ -251,8 +262,7 @@ function stream_callbacks.streamopened(request, attr)
session.log("debug", "BOSH session created for request from %s", session.ip);
log("info", "New BOSH session, assigned it sid '%s'", sid);
- local r, send_buffer = session.requests, session.send_buffer;
- local response = { headers = default_headers }
+ local r = session.requests;
function session.send(s)
-- We need to ensure that outgoing stanzas have the jabber:client xmlns
if s.attr and not s.attr.xmlns then
@@ -261,27 +271,16 @@ function stream_callbacks.streamopened(request, attr)
end
--log("debug", "Sending BOSH data: %s", tostring(s));
local oldest_request = r[1];
- if oldest_request then
+ if oldest_request and (not(auto_cork) or waiting_requests[oldest_request]) then
log("debug", "We have an open request, so sending on that");
- response.body = t_concat({
+ oldest_request.headers = default_headers;
+ oldest_request:send(t_concat({
"<body xmlns='http://jabber.org/protocol/httpbind' ",
session.bosh_terminate and "type='terminate' " or "",
"sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>",
tostring(s),
"</body>"
- });
- oldest_request:send(response);
- --log("debug", "Sent");
- if oldest_request.stayopen then
- if #r>1 then
- -- Move front request to back
- t_insert(r, oldest_request);
- t_remove(r, 1);
- end
- else
- log("debug", "Destroying the request now...");
- oldest_request:destroy();
- end
+ }));
elseif s ~= "" then
log("debug", "Saved to send buffer because there are %d open requests", #r);
-- Hmm, no requests are open :(
@@ -297,7 +296,7 @@ function stream_callbacks.streamopened(request, attr)
hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
fire_event("stream-features", session, features);
--xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh'
- local response = st.stanza("body", { xmlns = xmlns_bosh,
+ local body = st.stanza("body", { xmlns = xmlns_bosh,
wait = attr.wait,
inactivity = tostring(BOSH_DEFAULT_INACTIVITY),
polling = tostring(BOSH_DEFAULT_POLLING),
@@ -309,7 +308,8 @@ function stream_callbacks.streamopened(request, attr)
["xmlns:xmpp"] = "urn:xmpp:xbosh",
["xmlns:stream"] = "http://etherx.jabber.org/streams"
}):add_child(features);
- request:send{ headers = default_headers, body = tostring(response) };
+ response.headers = default_headers;
+ response:send(tostring(body));
request.sid = sid;
return;
@@ -319,8 +319,9 @@ function stream_callbacks.streamopened(request, attr)
if not session then
-- Unknown sid
log("info", "Client tried to use sid '%s' which we don't know about", sid);
- request:send{ headers = default_headers, body = tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" })) };
- request.notopen = nil;
+ response.headers = default_headers;
+ response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" })));
+ context.notopen = nil;
return;
end
@@ -332,15 +333,25 @@ function stream_callbacks.streamopened(request, attr)
elseif diff <= 0 then
-- Repeated, ignore
session.log("debug", "rid repeated (on request %s), ignoring: %s (diff %d)", request.id, session.rid, diff);
- request.notopen = nil;
- request.ignore = true;
- request.sid = sid;
- t_insert(session.requests, request);
+ context.notopen = nil;
+ context.ignore = true;
+ context.sid = sid;
+ t_insert(session.requests, response);
return;
end
session.rid = rid;
end
+ if attr.type == "terminate" then
+ -- Client wants to end this session, which we'll do
+ -- after processing any stanzas in this request
+ session.bosh_terminate = true;
+ end
+
+ context.notopen = nil; -- Signals that we accept this opening tag
+ t_insert(session.requests, response);
+ context.sid = sid;
+
if session.notopen then
local features = st.stanza("stream:features");
hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
@@ -348,22 +359,12 @@ function stream_callbacks.streamopened(request, attr)
session.send(features);
session.notopen = nil;
end
-
- if attr.type == "terminate" then
- -- Client wants to end this session, which we'll do
- -- after processing any stanzas in this request
- session.bosh_terminate = true;
- end
-
- request.notopen = nil; -- Signals that we accept this opening tag
- t_insert(session.requests, request);
- request.sid = sid;
end
-function stream_callbacks.handlestanza(request, stanza)
- if request.ignore then return; end
+function stream_callbacks.handlestanza(context, stanza)
+ if context.ignore then return; end
log("debug", "BOSH stanza received: %s\n", stanza:top_tag());
- local session = sessions[request.sid];
+ local session = sessions[context.sid];
if session then
if stanza.attr.xmlns == xmlns_bosh then
stanza.attr.xmlns = nil;
@@ -372,14 +373,17 @@ function stream_callbacks.handlestanza(request, stanza)
end
end
-function stream_callbacks.error(request, error)
+function stream_callbacks.error(context, error)
log("debug", "Error parsing BOSH request payload; %s", error);
- if not request.sid then
- request:send({ headers = default_headers, status = "400 Bad Request" });
+ if not context.sid then
+ local response = context.response;
+ response.headers = default_headers;
+ response.status_code = 400;
+ response:send();
return;
end
- local session = sessions[request.sid];
+ local session = sessions[context.sid];
if error == "stream-error" then -- Remote stream error, we close normally
session:close();
else
@@ -392,30 +396,26 @@ function on_timer()
-- log("debug", "Checking for requests soon to timeout...");
-- Identify requests timing out within the next few seconds
local now = os_time() + 3;
- for request in pairs(waiting_requests) do
- if request.reply_before <= now then
- log("debug", "%s was soon to timeout, sending empty response", request.id);
+ for request, reply_before in pairs(waiting_requests) do
+ if reply_before <= now then
+ log("debug", "%s was soon to timeout (at %d, now %d), sending empty response", tostring(request), reply_before, now);
-- Send empty response to let the
-- client know we're still here
if request.conn then
- sessions[request.sid].send("");
+ sessions[request.context.sid].send("");
end
end
end
now = now - 3;
local n_dead_sessions = 0;
- for session, inactive_since in pairs(inactive_sessions) do
- if session.bosh_max_inactive then
- if now - inactive_since > session.bosh_max_inactive then
- (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now);
- sessions[session.sid] = nil;
- inactive_sessions[session] = nil;
- n_dead_sessions = n_dead_sessions + 1;
- dead_sessions[n_dead_sessions] = session;
- end
- else
+ for session, close_after in pairs(inactive_sessions) do
+ if close_after < now then
+ (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now);
+ sessions[session.sid] = nil;
inactive_sessions[session] = nil;
+ n_dead_sessions = n_dead_sessions + 1;
+ dead_sessions[n_dead_sessions] = session;
end
end
@@ -426,15 +426,19 @@ function on_timer()
end
return 1;
end
+module:add_timer(1, on_timer);
-
-local function setup()
- local ports = module:get_option_array("bosh_ports") or { 5280 };
- httpserver.new_from_config(ports, handle_request, { base = "http-bind" });
- timer.add_task(1, on_timer);
-end
-if prosody.start_time then -- already started
- setup();
-else
- prosody.events.add_handler("server-started", setup);
+function module.add_host(module)
+ module:depends("http");
+ module:provides("http", {
+ default_path = "/http-bind";
+ route = {
+ ["GET"] = handle_GET;
+ ["GET /"] = handle_GET;
+ ["OPTIONS"] = handle_OPTIONS;
+ ["OPTIONS /"] = handle_OPTIONS;
+ ["POST"] = handle_POST;
+ ["POST /"] = handle_POST;
+ };
+ });
end
diff --git a/net/xmppclient_listener.lua b/plugins/mod_c2s.lua
index 4cc90cbf..69f2298f 100644
--- a/net/xmppclient_listener.lua
+++ b/plugins/mod_c2s.lua
@@ -6,31 +6,77 @@
-- COPYING file in the source package for more information.
--
+module:set_global();
-
-local logger = require "logger";
-local log = logger.init("xmppclient_listener");
+local add_task = require "util.timer".add_task;
local new_xmpp_stream = require "util.xmppstream".new;
-
-local connlisteners_register = require "net.connlisteners".register;
-
+local nameprep = require "util.encodings".stringprep.nameprep;
local sessionmanager = require "core.sessionmanager";
-local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
-local sm_streamopened = sessionmanager.streamopened;
-local sm_streamclosed = sessionmanager.streamclosed;
local st = require "util.stanza";
-local xpcall = xpcall;
-local tostring = tostring;
-local type = type;
+local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
+local uuid_generate = require "util.uuid".generate;
+
+local xpcall, tostring, type = xpcall, tostring, type;
+local format = string.format;
local traceback = debug.traceback;
-local config = require "core.configmanager";
-local opt_keepalives = config.get("*", "core", "tcp_keepalives");
+local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
-local stream_callbacks = { default_ns = "jabber:client",
- streamopened = sm_streamopened, streamclosed = sm_streamclosed, handlestanza = core_process_stanza };
+local log = module._log;
-local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
+local c2s_timeout = module:get_option_number("c2s_timeout");
+local opt_keepalives = module:get_option_boolean("tcp_keepalives", false);
+
+local sessions = module:shared("sessions");
+
+local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza };
+local listener = {};
+
+--- Stream events handlers
+local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
+local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+
+function stream_callbacks.streamopened(session, attr)
+ local send = session.send;
+ session.host = nameprep(attr.to);
+ if not session.host then
+ session:close{ condition = "improper-addressing",
+ text = "A valid 'to' attribute is required on stream headers" };
+ return;
+ end
+ session.version = tonumber(attr.version) or 0;
+ session.streamid = uuid_generate();
+ (session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
+
+ if not hosts[session.host] then
+ -- We don't serve this host...
+ session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
+ return;
+ end
+
+ send("<?xml version='1.0'?>");
+ send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host));
+
+ (session.log or log)("debug", "Sent reply <stream:stream> to client");
+ session.notopen = nil;
+
+ -- If session.secure is *false* (not nil) then it means we /were/ encrypting
+ -- since we now have a new stream header, session is secured
+ if session.secure == false then
+ session.secure = true;
+ end
+
+ local features = st.stanza("stream:features");
+ hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
+ module:fire_event("stream-features", session, features);
+
+ send(features);
+end
+
+function stream_callbacks.streamclosed(session)
+ session.log("debug", "Received </stream:stream>");
+ session:close();
+end
function stream_callbacks.error(session, error, data)
if error == "no-stream" then
@@ -67,13 +113,7 @@ function stream_callbacks.handlestanza(session, stanza)
end
end
-local sessions = {};
-local xmppclient = { default_port = 5222, default_mode = "*a" };
-
--- These are session methods --
-
-local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+--- Session methods
local function session_close(session, reason)
local log = session.log or log;
if session.conn then
@@ -104,14 +144,12 @@ local function session_close(session, reason)
end
session.send("</stream:stream>");
session.conn:close();
- xmppclient.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
+ listener.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
end
end
-
--- End of session methods --
-
-function xmppclient.onconnect(conn)
+--- Port listener
+function listener.onconnect(conn)
local session = sm_new_session(conn);
sessions[conn] = session;
@@ -122,7 +160,7 @@ function xmppclient.onconnect(conn)
session.secure = true;
end
- if opt_keepalives ~= nil then
+ if opt_keepalives then
conn:setoption("keepalive", opt_keepalives);
end
@@ -130,7 +168,6 @@ function xmppclient.onconnect(conn)
local stream = new_xmpp_stream(session, stream_callbacks);
session.stream = stream;
-
session.notopen = true;
function session.reset_stream()
@@ -148,21 +185,27 @@ function xmppclient.onconnect(conn)
session:close("not-well-formed");
end
end
+
- local handlestanza = stream_callbacks.handlestanza;
- function session.dispatch_stanza(session, stanza)
- return handlestanza(session, stanza);
+ if c2s_timeout then
+ add_task(c2s_timeout, function ()
+ if session.type == "c2s_unauthed" then
+ session:close("connection-timeout");
+ end
+ end);
end
+
+ session.dispatch_stanza = stream_callbacks.handlestanza;
end
-function xmppclient.onincoming(conn, data)
+function listener.onincoming(conn, data)
local session = sessions[conn];
if session then
session.data(data);
end
end
-
-function xmppclient.ondisconnect(conn, err)
+
+function listener.ondisconnect(conn, err)
local session = sessions[conn];
if session then
(session.log or log)("info", "Client disconnected: %s", err);
@@ -172,8 +215,27 @@ function xmppclient.ondisconnect(conn, err)
end
end
-function xmppclient.associate_session(conn, session)
+function listener.associate_session(conn, session)
sessions[conn] = session;
end
-connlisteners_register("xmppclient", xmppclient);
+module:add_item("net-provider", {
+ name = "c2s";
+ listener = listener;
+ default_port = 5222;
+ encryption = "starttls";
+ multiplex = {
+ pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:client%1.*>";
+ };
+});
+
+module:add_item("net-provider", {
+ name = "legacy_ssl";
+ listener = listener;
+ encryption = "ssl";
+ multiplex = {
+ pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:client%1.*>";
+ };
+});
+
+
diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua
index 97c46a8c..6be68e39 100644
--- a/plugins/mod_component.lua
+++ b/plugins/mod_component.lua
@@ -6,95 +6,298 @@
-- COPYING file in the source package for more information.
--
-if module:get_host_type() ~= "component" then
- error("Don't load mod_component manually, it should be for a component, please see http://prosody.im/doc/components", 0);
-end
-
-local hosts = _G.hosts;
+module:set_global();
local t_concat = table.concat;
+local logger = require "util.logger";
local sha1 = require "util.hashes".sha1;
local st = require "util.stanza";
+local jid_split = require "util.jid".split;
+local new_xmpp_stream = require "util.xmppstream".new;
+local uuid_gen = require "util.uuid".generate;
+
+
local log = module._log;
-local main_session, send;
+local sessions = module:shared("sessions");
-local function on_destroy(session, err)
- if main_session == session then
- main_session = nil;
+function module.add_host(module)
+ if module:get_host_type() ~= "component" then
+ error("Don't load mod_component manually, it should be for a component, please see http://prosody.im/doc/components", 0);
+ end
+
+ local env = module.environment;
+ env.connected = false;
+
+ local send;
+
+ local function on_destroy(session, err)
+ env.connected = false;
send = nil;
session.on_destroy = nil;
end
+
+ -- Handle authentication attempts by component
+ local function handle_component_auth(event)
+ local session, stanza = event.origin, event.stanza;
+
+ if session.type ~= "component" then return; end
+
+ if (not session.host) or #stanza.tags > 0 then
+ (session.log or log)("warn", "Invalid component handshake for host: %s", session.host);
+ session:close("not-authorized");
+ return true;
+ end
+
+ local secret = module:get_option("component_secret");
+ if not secret then
+ (session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.host);
+ session:close("not-authorized");
+ return true;
+ end
+
+ local supplied_token = t_concat(stanza);
+ local calculated_token = sha1(session.streamid..secret, true);
+ if supplied_token:lower() ~= calculated_token:lower() then
+ module:log("info", "Component authentication failed for %s", session.host);
+ session:close{ condition = "not-authorized", text = "Given token does not match calculated token" };
+ return true;
+ end
+
+ if env.connected then
+ module:log("error", "Second component attempted to connect, denying connection");
+ session:close{ condition = "conflict", text = "Component already connected" };
+ return true;
+ end
+
+ env.connected = true;
+ send = session.send;
+ session.on_destroy = on_destroy;
+ session.component_validate_from = module:get_option_boolean("validate_from_addresses", true);
+ module:log("info", "External component successfully authenticated");
+ session.send(st.stanza("handshake"));
+
+ return true;
+ end
+ module:hook("stanza/jabber:component:accept:handshake", handle_component_auth);
+
+ -- Handle stanzas addressed to this component
+ local function handle_stanza(event)
+ local stanza = event.stanza;
+ if send then
+ stanza.attr.xmlns = nil;
+ send(stanza);
+ else
+ module:log("warn", "Component not connected, bouncing error for: %s", stanza:top_tag());
+ if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
+ event.origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
+ end
+ end
+ return true;
+ end
+
+ module:hook("iq/bare", handle_stanza, -1);
+ module:hook("message/bare", handle_stanza, -1);
+ module:hook("presence/bare", handle_stanza, -1);
+ module:hook("iq/full", handle_stanza, -1);
+ module:hook("message/full", handle_stanza, -1);
+ module:hook("presence/full", handle_stanza, -1);
+ module:hook("iq/host", handle_stanza, -1);
+ module:hook("message/host", handle_stanza, -1);
+ module:hook("presence/host", handle_stanza, -1);
end
-local function handle_stanza(event)
- local stanza = event.stanza;
- if send then
- stanza.attr.xmlns = nil;
- send(stanza);
- else
- log("warn", "Component not connected, bouncing error for: %s", stanza:top_tag());
- if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
- event.origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
+--- Network and stream part ---
+
+local xmlns_component = 'jabber:component:accept';
+
+local listener = {};
+
+--- Callbacks/data for xmppstream to handle streams for us ---
+
+local stream_callbacks = { default_ns = xmlns_component };
+
+local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
+
+function stream_callbacks.error(session, error, data, data2)
+ if session.destroyed then return; end
+ module:log("warn", "Error processing component stream: "..tostring(error));
+ if error == "no-stream" then
+ session:close("invalid-namespace");
+ elseif error == "parse-error" then
+ session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(data));
+ session:close("not-well-formed");
+ elseif error == "stream-error" then
+ local condition, text = "undefined-condition";
+ for child in data:children() do
+ if child.attr.xmlns == xmlns_xmpp_streams then
+ if child.name ~= "text" then
+ condition = child.name;
+ else
+ text = child:get_text();
+ end
+ if condition ~= "undefined-condition" and text then
+ break;
+ end
+ end
end
+ text = condition .. (text and (" ("..text..")") or "");
+ session.log("info", "Session closed by remote with error: %s", text);
+ session:close(nil, text);
end
- return true;
end
-module:hook("iq/bare", handle_stanza, -1);
-module:hook("message/bare", handle_stanza, -1);
-module:hook("presence/bare", handle_stanza, -1);
-module:hook("iq/full", handle_stanza, -1);
-module:hook("message/full", handle_stanza, -1);
-module:hook("presence/full", handle_stanza, -1);
-module:hook("iq/host", handle_stanza, -1);
-module:hook("message/host", handle_stanza, -1);
-module:hook("presence/host", handle_stanza, -1);
-
---- Handle authentication attempts by components
-function handle_component_auth(event)
- local session, stanza = event.origin, event.stanza;
-
- if session.type ~= "component" then return; end
- if main_session == session then return; end
+function stream_callbacks.streamopened(session, attr)
+ if not hosts[attr.to] or not hosts[attr.to].modules.component then
+ session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" };
+ return;
+ end
+ session.host = attr.to;
+ session.streamid = uuid_gen();
+ session.notopen = nil;
+ -- Return stream header
+ session.send(st.stanza("stream:stream", { xmlns=xmlns_component,
+ ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag());
+end
- if (not session.host) or #stanza.tags > 0 then
- (session.log or log)("warn", "Invalid component handshake for host: %s", session.host);
- session:close("not-authorized");
- return true;
+function stream_callbacks.streamclosed(session)
+ session.log("debug", "Received </stream:stream>");
+ session:close();
+end
+
+local core_process_stanza = core_process_stanza;
+
+function stream_callbacks.handlestanza(session, stanza)
+ -- Namespaces are icky.
+ if not stanza.attr.xmlns and stanza.name == "handshake" then
+ stanza.attr.xmlns = xmlns_component;
end
-
- local secret = module:get_option("component_secret");
- if not secret then
- (session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.host);
- session:close("not-authorized");
- return true;
+ if not stanza.attr.xmlns or stanza.attr.xmlns == "jabber:client" then
+ local from = stanza.attr.from;
+ if from then
+ if session.component_validate_from then
+ local _, domain = jid_split(stanza.attr.from);
+ if domain ~= session.host then
+ -- Return error
+ session.log("warn", "Component sent stanza with missing or invalid 'from' address");
+ session:close{
+ condition = "invalid-from";
+ text = "Component tried to send from address <"..tostring(from)
+ .."> which is not in domain <"..tostring(session.host)..">";
+ };
+ return;
+ end
+ end
+ else
+ stanza.attr.from = session.host; -- COMPAT: Strictly we shouldn't allow this
+ end
+ if not stanza.attr.to then
+ session.log("warn", "Rejecting stanza with no 'to' address");
+ session.send(st.error_reply(stanza, "modify", "bad-request", "Components MUST specify a 'to' address on stanzas"));
+ return;
+ end
end
-
- local supplied_token = t_concat(stanza);
- local calculated_token = sha1(session.streamid..secret, true);
- if supplied_token:lower() ~= calculated_token:lower() then
- log("info", "Component authentication failed for %s", session.host);
- session:close{ condition = "not-authorized", text = "Given token does not match calculated token" };
- return true;
+ return core_process_stanza(session, stanza);
+end
+
+--- Closing a component connection
+local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
+local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+local function session_close(session, reason)
+ if session.destroyed then return; end
+ if session.conn then
+ if session.notopen then
+ session.send("<?xml version='1.0'?>");
+ session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
+ end
+ if reason then
+ if type(reason) == "string" then -- assume stream error
+ module:log("info", "Disconnecting component, <stream:error> is: %s", reason);
+ session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
+ elseif type(reason) == "table" then
+ if reason.condition then
+ local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
+ if reason.text then
+ stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
+ end
+ if reason.extra then
+ stanza:add_child(reason.extra);
+ end
+ module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza));
+ session.send(stanza);
+ elseif reason.name then -- a stanza
+ module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason));
+ session.send(reason);
+ end
+ end
+ end
+ session.send("</stream:stream>");
+ session.conn:close();
+ listener.ondisconnect(session.conn, "stream error");
end
+end
+
+--- Component connlistener
+
+function listener.onconnect(conn)
+ local _send = conn.write;
+ local session = { type = "component", conn = conn, send = function (data) return _send(conn, tostring(data)); end };
+
+ -- Logging functions --
+ local conn_name = "jcp"..tostring(conn):match("[a-f0-9]+$");
+ session.log = logger.init(conn_name);
+ session.close = session_close;
- -- If component not already created for this host, create one now
- if not main_session then
- send = session.send;
- main_session = session;
- session.on_destroy = on_destroy;
- session.component_validate_from = module:get_option_boolean("validate_from_addresses", true);
- log("info", "Component successfully authenticated: %s", session.host);
- session.send(st.stanza("handshake"));
- else -- TODO: Implement stanza distribution
- log("error", "Multiple components bound to the same address, first one wins: %s", session.host);
- session:close{ condition = "conflict", text = "Component already connected" };
+ session.log("info", "Incoming Jabber component connection");
+
+ local stream = new_xmpp_stream(session, stream_callbacks);
+ session.stream = stream;
+
+ session.notopen = true;
+
+ function session.reset_stream()
+ session.notopen = true;
+ session.stream:reset();
+ end
+
+ function session.data(conn, data)
+ local ok, err = stream:feed(data);
+ if ok then return; end
+ module:log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
+ session:close("not-well-formed");
end
- return true;
+ session.dispatch_stanza = stream_callbacks.handlestanza;
+
+ sessions[conn] = session;
+end
+function listener.onincoming(conn, data)
+ local session = sessions[conn];
+ session.data(conn, data);
+end
+function listener.ondisconnect(conn, err)
+ local session = sessions[conn];
+ if session then
+ (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
+ if session.on_destroy then session:on_destroy(err); end
+ sessions[conn] = nil;
+ for k in pairs(session) do
+ if k ~= "log" and k ~= "close" then
+ session[k] = nil;
+ end
+ end
+ session.destroyed = true;
+ session = nil;
+ end
end
-module:hook("stanza/jabber:component:accept:handshake", handle_component_auth);
+module:add_item("net-provider", {
+ name = "component";
+ listener = listener;
+ default_port = 5347;
+ multiplex = {
+ pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:component%1.*>";
+ };
+});
diff --git a/plugins/mod_compression.lua b/plugins/mod_compression.lua
index 82403016..62c0fa2e 100644
--- a/plugins/mod_compression.lua
+++ b/plugins/mod_compression.lua
@@ -16,12 +16,8 @@ local xmlns_stream = "http://etherx.jabber.org/streams";
local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
local add_filter = require "util.filters".add_filter;
-local compression_level = module:get_option("compression_level");
--- if not defined assume admin wants best compression
-if compression_level == nil then compression_level = 9 end;
+local compression_level = module:get_option_number("compression_level", 7);
-
-compression_level = tonumber(compression_level);
if not compression_level or compression_level < 1 or compression_level > 9 then
module:log("warn", "Invalid compression level in config: %s", tostring(compression_level));
module:log("warn", "Module loading aborted. Compression won't be available.");
diff --git a/plugins/mod_dialback.lua b/plugins/mod_dialback.lua
index e27f8657..35186c5e 100644
--- a/plugins/mod_dialback.lua
+++ b/plugins/mod_dialback.lua
@@ -6,22 +6,35 @@
-- COPYING file in the source package for more information.
--
+local format = string.format;
local hosts = _G.hosts;
-local send_s2s = require "core.s2smanager".send_to_host;
local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
-local s2s_initiate_dialback = require "core.s2smanager".initiate_dialback;
-local s2s_verify_dialback = require "core.s2smanager".verify_dialback;
local log = module._log;
local st = require "util.stanza";
+local sha256_hash = require "util.hashes".sha256;
local xmlns_stream = "http://etherx.jabber.org/streams";
-local xmlns_dialback = "jabber:server:dialback";
local dialback_requests = setmetatable({}, { __mode = 'v' });
+function generate_dialback(id, to, from)
+ return sha256_hash(id..to..from..hosts[from].dialback_secret, true);
+end
+
+function initiate_dialback(session)
+ -- generate dialback key
+ session.dialback_key = generate_dialback(session.streamid, session.to_host, session.from_host);
+ session.sends2s(format("<db:result from='%s' to='%s'>%s</db:result>", session.from_host, session.to_host, session.dialback_key));
+ session.log("info", "sent dialback key on outgoing s2s stream");
+end
+
+function verify_dialback(id, to, from, key)
+ return key == generate_dialback(id, to, from);
+end
+
module:hook("stanza/jabber:server:dialback:verify", function(event)
local origin, stanza = event.origin, event.stanza;
@@ -32,7 +45,7 @@ module:hook("stanza/jabber:server:dialback:verify", function(event)
-- COMPAT: Grr, ejabberd breaks this one too?? it is black and white in XEP-220 example 34
--if attr.from ~= origin.to_host then error("invalid-from"); end
local type;
- if s2s_verify_dialback(attr.id, attr.from, attr.to, stanza[1]) then
+ if verify_dialback(attr.id, attr.from, attr.to, stanza[1]) then
type = "valid"
else
type = "invalid"
@@ -72,8 +85,7 @@ module:hook("stanza/jabber:server:dialback:result", function(event)
end
origin.log("debug", "asking %s if key %s belongs to them", attr.from, stanza[1]);
- send_s2s(attr.to, attr.from,
- st.stanza("db:verify", { from = attr.to, to = attr.from, id = origin.streamid }):text(stanza[1]));
+ origin.send(st.stanza("db:verify", { from = attr.to, to = attr.from, id = origin.streamid }):text(stanza[1]));
return true;
end
end);
@@ -84,6 +96,7 @@ module:hook("stanza/jabber:server:dialback:verify", function(event)
if origin.type == "s2sout_unauthed" or origin.type == "s2sout" then
local attr = stanza.attr;
local dialback_verifying = dialback_requests[attr.from.."/"..(attr.id or "")];
+ module:log("debug", tostring(dialback_verifying).." "..attr.from.." "..origin.to_host);
if dialback_verifying and attr.from == origin.to_host then
local valid;
if attr.type == "valid" then
@@ -134,18 +147,25 @@ end);
module:hook_stanza("urn:ietf:params:xml:ns:xmpp-sasl", "failure", function (origin, stanza)
if origin.external_auth == "failed" then
module:log("debug", "SASL EXTERNAL failed, falling back to dialback");
- s2s_initiate_dialback(origin);
+ initiate_dialback(origin);
return true;
end
end, 100);
module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
if not origin.external_auth or origin.external_auth == "failed" then
- s2s_initiate_dialback(origin);
+ module:log("debug", "Initiating dialback...");
+ initiate_dialback(origin);
return true;
end
end, 100);
+module:hook("s2s-authenticate-legacy", function (event)
+ module:log("debug", "Initiating dialback...");
+ initiate_dialback(event.origin);
+ return true;
+end, 100);
+
-- Offer dialback to incoming hosts
module:hook("s2s-stream-features", function (data)
data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):up();
diff --git a/plugins/mod_http.lua b/plugins/mod_http.lua
new file mode 100644
index 00000000..b7376831
--- /dev/null
+++ b/plugins/mod_http.lua
@@ -0,0 +1,113 @@
+-- Prosody IM
+-- Copyright (C) 2008-2012 Matthew Wild
+-- Copyright (C) 2008-2012 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+module:set_global();
+module:depends("http_errors");
+
+local server = require "net.http.server";
+
+server.set_default_host(module:get_option_string("http_default_host"));
+
+local function normalize_path(path)
+ if path:sub(1,1) ~= "/" then path = "/"..path; end
+ if path:sub(-1,-1) == "/" then path = path:sub(1, -2); end
+ return path;
+end
+
+local function get_http_event(host, app_path, key)
+ local method, path = key:match("^(%S+)%s+(.+)$");
+ if not method then -- No path specified, default to "" (base path)
+ method, path = key, "";
+ end
+ if method:sub(1,1) == "/" then
+ return nil;
+ end
+ return method:upper().." "..host..app_path..path;
+end
+
+local function get_base_path(host_module, app_name, default_app_path)
+ return host_module:get_option("http_paths", {})[app_name] -- Host
+ or module:get_option("http_paths", {})[app_name] -- Global
+ or default_app_path; -- Default
+end
+
+function module.add_host(module)
+ local host = module.host;
+ local apps = {};
+ module.environment.apps = apps;
+ local function http_app_added(event)
+ local app_name = event.item.name;
+ local default_app_path = event.item.default_path or "/"..app_name;
+ local app_path = normalize_path(get_base_path(module, app_name, default_app_path));
+ if not app_name then
+ -- TODO: Link to docs
+ module:log("error", "HTTP app has no 'name', add one or use module:provides('http', app)");
+ return;
+ end
+ apps[app_name] = apps[app_name] or {};
+ local app_handlers = apps[app_name];
+ for key, handler in pairs(event.item.route or {}) do
+ local event_name = get_http_event(host, app_path, key);
+ if event_name then
+ if type(handler) ~= "function" then
+ local data = handler;
+ handler = function () return data; end
+ elseif event_name:sub(-2, -1) == "/*" then
+ local base_path = event_name:match("/(.+)/*$");
+ local _handler = handler;
+ handler = function (event)
+ local path = event.request.path:sub(#base_path+1);
+ return _handler(event, path);
+ end;
+ end
+ if not app_handlers[event_name] then
+ app_handlers[event_name] = handler;
+ module:hook_object_event(server, event_name, handler);
+ else
+ module:log("warn", "App %s added handler twice for '%s', ignoring", app_name, event_name);
+ end
+ else
+ module:log("error", "Invalid route in %s, %q. See http://prosody.im/doc/developers/http#routes", app_name, key);
+ end
+ end
+ end
+
+ local function http_app_removed(event)
+ local app_handlers = apps[event.item.name];
+ apps[event.item.name] = nil;
+ for event, handler in pairs(app_handlers) do
+ module:unhook_object_event(server, event, handler);
+ end
+ end
+
+ module:handle_items("http-provider", http_app_added, http_app_removed);
+
+ server.add_host(host);
+ function module.unload()
+ server.remove_host(host);
+ end
+end
+
+module:add_item("net-provider", {
+ name = "http";
+ listener = server.listener;
+ default_port = 5280;
+ multiplex = {
+ pattern = "^[A-Z]";
+ };
+});
+
+module:add_item("net-provider", {
+ name = "https";
+ listener = server.listener;
+ default_port = 5281;
+ encryption = "ssl";
+ multiplex = {
+ pattern = "^[A-Z]";
+ };
+});
diff --git a/plugins/mod_http_errors.lua b/plugins/mod_http_errors.lua
new file mode 100644
index 00000000..c7bcbbc1
--- /dev/null
+++ b/plugins/mod_http_errors.lua
@@ -0,0 +1,77 @@
+module:set_global();
+module:depends("http");
+
+local server = require "net.http.server";
+local codes = require "net.http.codes";
+local termcolours = require "util.termcolours";
+
+local show_private = module:get_option_boolean("http_errors_detailed", false);
+local always_serve = module:get_option_boolean("http_errors_always_show", true);
+local default_message = { module:get_option_string("http_errors_default_message", "That's all I know.") };
+local default_messages = {
+ [400] = { "What kind of request do you call that??" };
+ [403] = { "You're not allowed to do that." };
+ [404] = { "Whatever you were looking for is not here. %";
+ "Where did you put it?", "It's behind you.", "Keep looking." };
+ [500] = { "% Check your error log for more info.";
+ "Gremlins.", "It broke.", "Don't look at me." };
+};
+
+local messages = setmetatable(module:get_option("http_errors_messages", {}), { __index = default_messages });
+
+local html = [[
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <style>
+ body{
+ margin-top:14%;
+ text-align:center;
+ background-color:#F8F8F8;
+ font-family:sans-serif;
+ }
+ h1{
+ font-size:xx-large;
+ }
+ p{
+ font-size:x-large;
+ }
+ p+p { font-size: large; font-family: courier }
+ </style>
+</head>
+<body>
+ <h1>$title</h1>
+ <p>$message</p>
+ <p>$extra</p>
+</body>
+</html>]];
+html = html:gsub("%s%s+", "");
+
+local entities = {
+ ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;",
+ ["'"] = "&apos;", ["\""] = "&quot;", ["\n"] = "<br/>",
+};
+
+local function tohtml(plain)
+ return (plain:gsub("[<>&'\"\n]", entities));
+
+end
+
+local function get_page(code, extra)
+ local message = messages[code];
+ if always_serve or message then
+ message = message or default_message;
+ return (html:gsub("$(%a+)", {
+ title = rawget(codes, code) or ("Code "..tostring(code));
+ message = message[1]:gsub("%%", function ()
+ return message[math.random(2, math.max(#message,2))];
+ end);
+ extra = tohtml(extra or "");
+ }));
+ end
+end
+
+module:hook_object_event(server, "http-error", function (event)
+ return get_page(event.code, (show_private and event.private_message) or event.message);
+end);
diff --git a/plugins/mod_http_files.lua b/plugins/mod_http_files.lua
new file mode 100644
index 00000000..dc58ff5d
--- /dev/null
+++ b/plugins/mod_http_files.lua
@@ -0,0 +1,57 @@
+-- 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.
+--
+
+module:depends("http");
+local lfs = require "lfs";
+
+local open = io.open;
+local stat = lfs.attributes;
+
+local http_base = module:get_option_string("http_files_dir", module:get_option_string("http_path", "www_files"));
+
+-- TODO: Should we read this from /etc/mime.types if it exists? (startup time...?)
+local mime_map = {
+ html = "text/html";
+ htm = "text/html";
+ xml = "text/xml";
+ xsl = "text/xml";
+ txt = "text/plain; charset=utf-8";
+ js = "text/javascript";
+ css = "text/css";
+};
+
+function serve_file(event, path)
+ local response = event.response;
+ local full_path = http_base.."/"..path;
+ if stat(full_path, "mode") == "directory" then
+ if stat(full_path.."/index.html", "mode") == "file" then
+ return serve_file(event, path.."/index.html");
+ end
+ return 403;
+ end
+ local f, err = open(full_path, "rb");
+ if not f then
+ module:log("warn", "Failed to open file: %s", err);
+ return 404;
+ end
+ local data = f:read("*a");
+ f:close();
+ if not data then
+ return 403;
+ end
+ local ext = path:match("%.([^.]*)$");
+ response.headers.content_type = mime_map[ext]; -- Content-Type should be nil when not known
+ return response:send(data);
+end
+
+module:provides("http", {
+ route = {
+ ["GET /*"] = serve_file;
+ };
+});
+
diff --git a/plugins/mod_httpserver.lua b/plugins/mod_httpserver.lua
deleted file mode 100644
index 654aff06..00000000
--- a/plugins/mod_httpserver.lua
+++ /dev/null
@@ -1,97 +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 httpserver = require "net.httpserver";
-local lfs = require "lfs";
-
-local open = io.open;
-local t_concat = table.concat;
-local stat = lfs.attributes;
-
-local http_base = config.get("*", "core", "http_path") or "www_files";
-
-local response_400 = { status = "400 Bad Request", body = "<h1>Bad Request</h1>Sorry, we didn't understand your request :(" };
-local response_403 = { status = "403 Forbidden", body = "<h1>Forbidden</h1>You don't have permission to view the contents of this directory :(" };
-local response_404 = { status = "404 Not Found", body = "<h1>Page Not Found</h1>Sorry, we couldn't find what you were looking for :(" };
-
--- TODO: Should we read this from /etc/mime.types if it exists? (startup time...?)
-local mime_map = {
- html = "text/html";
- htm = "text/html";
- xml = "text/xml";
- xsl = "text/xml";
- txt = "text/plain; charset=utf-8";
- js = "text/javascript";
- css = "text/css";
-};
-
-local function preprocess_path(path)
- if path:sub(1,1) ~= "/" then
- path = "/"..path;
- end
- local level = 0;
- for component in path:gmatch("([^/]+)/") do
- if component == ".." then
- level = level - 1;
- elseif component ~= "." then
- level = level + 1;
- end
- if level < 0 then
- return nil;
- end
- end
- return path;
-end
-
-function serve_file(path)
- local full_path = http_base..path;
- if stat(full_path, "mode") == "directory" then
- if stat(full_path.."/index.html", "mode") == "file" then
- return serve_file(path.."/index.html");
- end
- return response_403;
- end
- local f, err = open(full_path, "rb");
- if not f then return response_404; end
- local data = f:read("*a");
- f:close();
- if not data then
- return response_403;
- end
- local ext = path:match("%.([^.]*)$");
- local mime = mime_map[ext]; -- Content-Type should be nil when not known
- return {
- headers = { ["Content-Type"] = mime; };
- body = data;
- };
-end
-
-local function handle_file_request(method, body, request)
- local path = preprocess_path(request.url.path);
- if not path then return response_400; end
- path = path:gsub("^/[^/]+", ""); -- Strip /files/
- return serve_file(path);
-end
-
-local function handle_default_request(method, body, request)
- local path = preprocess_path(request.url.path);
- if not path then return response_400; end
- return serve_file(path);
-end
-
-local function setup()
- local ports = config.get(module.host, "core", "http_ports") or { 5280 };
- httpserver.set_default_handler(handle_default_request);
- httpserver.new_from_config(ports, handle_file_request, { base = "files" });
-end
-if prosody.start_time then -- already started
- setup();
-else
- prosody.events.add_handler("server-started", setup);
-end
diff --git a/plugins/mod_iq.lua b/plugins/mod_iq.lua
index 484a1f8f..55bf59c3 100644
--- a/plugins/mod_iq.lua
+++ b/plugins/mod_iq.lua
@@ -8,10 +8,8 @@
local st = require "util.stanza";
-local jid_split = require "util.jid".split;
local full_sessions = full_sessions;
-local bare_sessions = bare_sessions;
if module:get_host_type() == "local" then
module:hook("iq/full", function(data)
@@ -33,7 +31,7 @@ end
module:hook("iq/bare", function(data)
-- IQ to bare JID recieved
- local origin, stanza = data.origin, data.stanza;
+ local stanza = data.stanza;
local type = stanza.attr.type;
-- TODO fire post processing events
@@ -49,7 +47,7 @@ end);
module:hook("iq/self", function(data)
-- IQ to self JID recieved
- local origin, stanza = data.origin, data.stanza;
+ local stanza = data.stanza;
local type = stanza.attr.type;
if type == "get" or type == "set" then
@@ -64,7 +62,7 @@ end);
module:hook("iq/host", function(data)
-- IQ to a local host recieved
- local origin, stanza = data.origin, data.stanza;
+ local stanza = data.stanza;
local type = stanza.attr.type;
if type == "get" or type == "set" then
diff --git a/plugins/mod_message.lua b/plugins/mod_message.lua
index df317532..ebff2fe7 100644
--- a/plugins/mod_message.lua
+++ b/plugins/mod_message.lua
@@ -14,7 +14,6 @@ local st = require "util.stanza";
local jid_bare = require "util.jid".bare;
local jid_split = require "util.jid".split;
local user_exists = require "core.usermanager".user_exists;
-local t_insert = table.insert;
local function process_to_bare(bare, origin, stanza)
local user = bare_sessions[bare];
diff --git a/plugins/mod_motd.lua b/plugins/mod_motd.lua
index d567288b..39b74de9 100644
--- a/plugins/mod_motd.lua
+++ b/plugins/mod_motd.lua
@@ -13,17 +13,18 @@ local motd_jid = module:get_option_string("motd_jid", host);
if not motd_text then return; end
+local jid_join = require "util.jid".join;
local st = require "util.stanza";
motd_text = motd_text:gsub("^%s*(.-)%s*$", "%1"):gsub("\n%s+", "\n"); -- Strip indentation from the config
-module:hook("resource-bind",
- function (event)
- local session = event.session;
- local motd_stanza =
- st.message({ to = session.username..'@'..session.host, from = motd_jid })
- :tag("body"):text(motd_text);
- core_route_stanza(hosts[host], motd_stanza);
- module:log("debug", "MOTD send to user %s@%s", session.username, session.host);
-
-end);
+module:hook("presence/bare", function (event)
+ local session, stanza = event.origin, event.stanza;
+ if not session.presence and not stanza.attr.type then
+ local motd_stanza =
+ st.message({ to = session.full_jid, from = motd_jid })
+ :tag("body"):text(motd_text);
+ core_route_stanza(hosts[host], motd_stanza);
+ module:log("debug", "MOTD send to user %s", session.full_jid);
+ end
+end, 1);
diff --git a/plugins/mod_net_multiplex.lua b/plugins/mod_net_multiplex.lua
new file mode 100644
index 00000000..44e1c1ee
--- /dev/null
+++ b/plugins/mod_net_multiplex.lua
@@ -0,0 +1,70 @@
+module:set_global();
+
+local max_buffer_len = module:get_option_number("multiplex_buffer_size", 1024);
+
+local portmanager = require "core.portmanager";
+
+local available_services = {};
+
+local function add_service(service)
+ local multiplex_pattern = service.multiplex and service.multiplex.pattern;
+ if multiplex_pattern then
+ module:log("debug", "Adding multiplex service %q with pattern %q", service.name, multiplex_pattern);
+ available_services[service] = multiplex_pattern;
+ else
+ module:log("debug", "Service %q is not multiplex-capable", service.name);
+ end
+end
+module:hook("service-added", function (event) add_service(event.service); end);
+module:hook("service-removed", function (event) available_services[event.service] = nil; end);
+
+for service_name, services in pairs(portmanager.get_registered_services()) do
+ for i, service in ipairs(services) do
+ add_service(service);
+ end
+end
+
+local buffers = {};
+
+local listener = { default_mode = "*a" };
+
+function listener.onconnect()
+end
+
+function listener.onincoming(conn, data)
+ if not data then return; end
+ local buf = buffers[conn];
+ buffers[conn] = nil;
+ buf = buf and buf..data or data;
+ for service, multiplex_pattern in pairs(available_services) do
+ if buf:match(multiplex_pattern) then
+ module:log("debug", "Routing incoming connection to %s", service.name);
+ local listener = service.listener;
+ conn:setlistener(listener);
+ local onconnect = listener.onconnect;
+ if onconnect then onconnect(conn) end
+ return listener.onincoming(conn, buf);
+ end
+ end
+ if #buf > max_buffer_len then -- Give up
+ conn:close();
+ else
+ buffers[conn] = buf;
+ end
+end
+
+function listener.ondisconnect(conn, err)
+ buffers[conn] = nil; -- warn if no buffer?
+end
+
+module:add_item("net-provider", {
+ name = "multiplex";
+ config_prefix = "";
+ listener = listener;
+});
+
+module:provides("net", {
+ name = "multiplex_ssl";
+ config_prefix = "ssl";
+ listener = listener;
+});
diff --git a/plugins/mod_posix.lua b/plugins/mod_posix.lua
index 11985885..1670ac95 100644
--- a/plugins/mod_posix.lua
+++ b/plugins/mod_posix.lua
@@ -22,7 +22,7 @@ local stat = lfs.attributes;
local prosody = _G.prosody;
-module.host = "*"; -- we're a global module
+module:set_global(); -- we're a global module
local umask = module:get_option("umask") or "027";
pposix.umask(umask);
diff --git a/plugins/mod_proxy65.lua b/plugins/mod_proxy65.lua
index d02f3b58..b2f6f703 100644
--- a/plugins/mod_proxy65.lua
+++ b/plugins/mod_proxy65.lua
@@ -6,35 +6,21 @@
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
---[[
-* to restart the proxy in the console: e.g.
-module:unload("proxy65");
-> server.removeserver(<proxy65_port>);
-module:load("proxy65", <proxy65_jid>);
-]]--
+module:set_global();
-local module = module;
-local tostring = tostring;
local jid_compare, jid_prep = require "util.jid".compare, require "util.jid".prep;
local st = require "util.stanza";
-local connlisteners = require "net.connlisteners";
local sha1 = require "util.hashes".sha1;
-local server = require "net.server";
local b64 = require "util.encodings".base64.encode;
+local server = require "net.server";
-local host, name = module:get_host(), "SOCKS5 Bytestreams Service";
-local sessions, transfers = {}, {};
-
-local proxy_port = module:get_option("proxy65_port") or 5000;
-local proxy_interface = module:get_option("proxy65_interface") or "*";
-local proxy_address = module:get_option("proxy65_address") or (proxy_interface ~= "*" and proxy_interface) or host;
-local proxy_acl = module:get_option("proxy65_acl");
+local sessions, transfers = module:shared("sessions", "transfers");
local max_buffer_size = 4096;
-local connlistener = { default_port = proxy_port, default_interface = proxy_interface, default_mode = "*a" };
+local listener = {};
-function connlistener.onincoming(conn, data)
+function listener.onincoming(conn, data)
local session = sessions[conn] or {};
local transfer = transfers[session.sha];
@@ -84,7 +70,7 @@ function connlistener.onincoming(conn, data)
end
end
-function connlistener.ondisconnect(conn, err)
+function listener.ondisconnect(conn, err)
local session = sessions[conn];
if session then
if transfers[session.sha] then
@@ -101,88 +87,93 @@ function connlistener.ondisconnect(conn, err)
end
end
-module:add_identity("proxy", "bytestreams", name);
-module:add_feature("http://jabber.org/protocol/bytestreams");
-
-module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
- local origin, stanza = event.origin, event.stanza;
- origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#info")
- :tag("identity", {category='proxy', type='bytestreams', name=name}):up()
- :tag("feature", {var="http://jabber.org/protocol/bytestreams"}) );
- return true;
-end, -1);
-
-module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event)
- local origin, stanza = event.origin, event.stanza;
- origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#items"));
- return true;
-end, -1);
+function module.add_host(module)
+ local host, name = module:get_host(), module:get_option_string("name", "SOCKS5 Bytestreams Service");
+
+ local proxy_address = module:get_option("proxy65_address", host);
+ local proxy_port = module:get_option_number("proxy65_port", next(portmanager.get_active_services():search("proxy65", nil)[1] or {}));
+ local proxy_acl = module:get_option("proxy65_acl");
-module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event)
- local origin, stanza = event.origin, event.stanza;
+ module:add_identity("proxy", "bytestreams", name);
+ module:add_feature("http://jabber.org/protocol/bytestreams");
+
+ module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
+ local origin, stanza = event.origin, event.stanza;
+ origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#info")
+ :tag("identity", {category='proxy', type='bytestreams', name=name}):up()
+ :tag("feature", {var="http://jabber.org/protocol/bytestreams"}) );
+ return true;
+ end, -1);
+
+ module:hook("iq-get/host/http://jabber.org/protocol/disco#items:query", function(event)
+ local origin, stanza = event.origin, event.stanza;
+ origin.send(st.reply(stanza):query("http://jabber.org/protocol/disco#items"));
+ return true;
+ end, -1);
- -- check ACL
- while proxy_acl and #proxy_acl > 0 do -- using 'while' instead of 'if' so we can break out of it
- local jid = stanza.attr.from;
- for _, acl in ipairs(proxy_acl) do
- if jid_compare(jid, acl) then break; end
+ module:hook("iq-get/host/http://jabber.org/protocol/bytestreams:query", function(event)
+ local origin, stanza = event.origin, event.stanza;
+
+ -- check ACL
+ while proxy_acl and #proxy_acl > 0 do -- using 'while' instead of 'if' so we can break out of it
+ local jid = stanza.attr.from;
+ for _, acl in ipairs(proxy_acl) do
+ if jid_compare(jid, acl) then break; end
+ end
+ module:log("warn", "Denying use of proxy for %s", tostring(stanza.attr.from));
+ origin.send(st.error_reply(stanza, "auth", "forbidden"));
+ return true;
end
- module:log("warn", "Denying use of proxy for %s", tostring(stanza.attr.from));
- origin.send(st.error_reply(stanza, "auth", "forbidden"));
+
+ local sid = stanza.tags[1].attr.sid;
+ origin.send(st.reply(stanza):tag("query", {xmlns="http://jabber.org/protocol/bytestreams", sid=sid})
+ :tag("streamhost", {jid=host, host=proxy_address, port=proxy_port}));
return true;
- end
-
- local sid = stanza.tags[1].attr.sid;
- origin.send(st.reply(stanza):tag("query", {xmlns="http://jabber.org/protocol/bytestreams", sid=sid})
- :tag("streamhost", {jid=host, host=proxy_address, port=proxy_port}));
- return true;
-end);
-
-module.unload = function()
- connlisteners.deregister(module.host .. ':proxy65');
-end
-
-module:hook("iq-set/host/http://jabber.org/protocol/bytestreams:query", function(event)
- local origin, stanza = event.origin, event.stanza;
-
- local query = stanza.tags[1];
- local sid = query.attr.sid;
- local from = stanza.attr.from;
- local to = query:get_child_text("activate");
- local prepped_to = jid_prep(to);
-
- local info = "sid: "..tostring(sid)..", initiator: "..tostring(from)..", target: "..tostring(prepped_to or to);
- if prepped_to and sid then
- local sha = sha1(sid .. from .. prepped_to, true);
- if not transfers[sha] then
- module:log("debug", "Activation request has unknown session id; activation failed (%s)", info);
- origin.send(st.error_reply(stanza, "modify", "item-not-found"));
- elseif not transfers[sha].initiator then
- module:log("debug", "The sender was not connected to the proxy; activation failed (%s)", info);
- origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The sender (you) is not connected to the proxy"));
- --elseif not transfers[sha].target then -- can't happen, as target is set when a transfer object is created
- -- module:log("debug", "The recipient was not connected to the proxy; activation failed (%s)", info);
- -- origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The recipient is not connected to the proxy"));
- else -- if transfers[sha].initiator ~= nil and transfers[sha].target ~= nil then
- module:log("debug", "Transfer activated (%s)", info);
- transfers[sha].activated = true;
- transfers[sha].target:resume();
- transfers[sha].initiator:resume();
- origin.send(st.reply(stanza));
+ end);
+
+ module:hook("iq-set/host/http://jabber.org/protocol/bytestreams:query", function(event)
+ local origin, stanza = event.origin, event.stanza;
+
+ local query = stanza.tags[1];
+ local sid = query.attr.sid;
+ local from = stanza.attr.from;
+ local to = query:get_child_text("activate");
+ local prepped_to = jid_prep(to);
+
+ local info = "sid: "..tostring(sid)..", initiator: "..tostring(from)..", target: "..tostring(prepped_to or to);
+ if prepped_to and sid then
+ local sha = sha1(sid .. from .. prepped_to, true);
+ if not transfers[sha] then
+ module:log("debug", "Activation request has unknown session id; activation failed (%s)", info);
+ origin.send(st.error_reply(stanza, "modify", "item-not-found"));
+ elseif not transfers[sha].initiator then
+ module:log("debug", "The sender was not connected to the proxy; activation failed (%s)", info);
+ origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The sender (you) is not connected to the proxy"));
+ --elseif not transfers[sha].target then -- can't happen, as target is set when a transfer object is created
+ -- module:log("debug", "The recipient was not connected to the proxy; activation failed (%s)", info);
+ -- origin.send(st.error_reply(stanza, "cancel", "not-allowed", "The recipient is not connected to the proxy"));
+ else -- if transfers[sha].initiator ~= nil and transfers[sha].target ~= nil then
+ module:log("debug", "Transfer activated (%s)", info);
+ transfers[sha].activated = true;
+ transfers[sha].target:resume();
+ transfers[sha].initiator:resume();
+ origin.send(st.reply(stanza));
+ end
+ elseif to and sid then
+ module:log("debug", "Malformed activation jid; activation failed (%s)", info);
+ origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
+ else
+ module:log("debug", "Bad request; activation failed (%s)", info);
+ origin.send(st.error_reply(stanza, "modify", "bad-request"));
end
- elseif to and sid then
- module:log("debug", "Malformed activation jid; activation failed (%s)", info);
- origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
- else
- module:log("debug", "Bad request; activation failed (%s)", info);
- origin.send(st.error_reply(stanza, "modify", "bad-request"));
- end
- return true;
-end);
-
-if not connlisteners.register(module.host .. ':proxy65', connlistener) then
- module:log("error", "mod_proxy65: Could not establish a connection listener. Check your configuration please.");
- module:log("error", "Possibly two proxy65 components are configured to share the same port.");
+ return true;
+ end);
end
-connlisteners.start(module.host .. ':proxy65');
+module:provides("net", {
+ default_port = 5000;
+ listener = listener;
+ multiplex = {
+ pattern = "^\5";
+ };
+});
diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua
index 7708572a..804db5f9 100644
--- a/plugins/mod_saslauth.lua
+++ b/plugins/mod_saslauth.lua
@@ -26,7 +26,6 @@ local log = module._log;
local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl';
local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind';
-local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas';
local function build_reply(status, ret, err_msg)
local reply = st.stanza(status, {xmlns = xmlns_sasl});
diff --git a/plugins/mod_tls.lua b/plugins/mod_tls.lua
index cace2d69..707ae8f5 100644
--- a/plugins/mod_tls.lua
+++ b/plugins/mod_tls.lua
@@ -75,7 +75,7 @@ end);
module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
module:log("debug", "Received features element");
if can_do_tls(session) and stanza:child_with_ns(xmlns_starttls) then
- module:log("%s is offering TLS, taking up the offer...", session.to_host);
+ module:log("debug", "%s is offering TLS, taking up the offer...", session.to_host);
session.sends2s("<starttls xmlns='"..xmlns_starttls.."'/>");
return true;
end
diff --git a/plugins/muc/mod_muc.lua b/plugins/muc/mod_muc.lua
index 8ef7a5b3..43b8423d 100644
--- a/plugins/muc/mod_muc.lua
+++ b/plugins/muc/mod_muc.lua
@@ -69,10 +69,10 @@ for jid in pairs(persistent_rooms) do
local node = jid_split(jid);
local data = datamanager.load(node, muc_host, "config") or {};
local room = muc_new_room(jid, {
- history_length = max_history_messages;
+ max_history_length = max_history_messages;
});
room._data = data._data;
- room._data.history_length = max_history_messages; --TODO: Need to allow per-room with a global limit
+ room._data.max_history_length = max_history_messages; -- Overwrite old max_history_length in data with current settings
room._affiliations = data._affiliations;
room.route_stanza = room_route_stanza;
room.save = room_save;
@@ -80,7 +80,7 @@ for jid in pairs(persistent_rooms) do
end
local host_room = muc_new_room(muc_host, {
- history_length = max_history_messages;
+ max_history_length = max_history_messages;
});
host_room.route_stanza = room_route_stanza;
host_room.save = room_save;
@@ -131,7 +131,7 @@ function stanza_handler(event)
(restrict_room_creation == "admin" and is_admin(stanza.attr.from)) or
(restrict_room_creation == "local" and select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "")) then
room = muc_new_room(bare, {
- history_length = max_history_messages;
+ max_history_length = max_history_messages;
});
room.route_stanza = room_route_stanza;
room.save = room_save;
diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua
index 731f9e37..9be1736f 100644
--- a/plugins/muc/muc.lib.lua
+++ b/plugins/muc/muc.lib.lua
@@ -9,7 +9,6 @@
local select = select;
local pairs, ipairs = pairs, ipairs;
-local datamanager = require "util.datamanager";
local datetime = require "util.datetime";
local dataform = require "util.dataforms";
@@ -19,7 +18,6 @@ local jid_bare = require "util.jid".bare;
local jid_prep = require "util.jid".prep;
local st = require "util.stanza";
local log = require "util.logger".init("mod_muc");
-local multitable_new = require "util.multitable".new;
local t_insert, t_remove = table.insert, table.remove;
local setmetatable = setmetatable;
local base64 = require "util.encodings".base64;
@@ -133,12 +131,11 @@ function room_mt:broadcast_message(stanza, historic)
stanza = st.clone(stanza);
stanza.attr.to = "";
local stamp = datetime.datetime();
- local chars = #tostring(stanza);
stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
local entry = { stanza = stanza, stamp = stamp };
t_insert(history, entry);
- while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
+ while #history > self._data.history_length do t_remove(history, 1) end
end
end
function room_mt:broadcast_except_nick(stanza, nick)
@@ -185,7 +182,6 @@ function room_mt:send_history(to, stanza)
local n = 0;
local charcount = 0;
- local stanzacount = 0;
for i=#history,1,-1 do
local entry = history[i];
@@ -339,6 +335,21 @@ end
function room_mt:get_changesubject()
return self._data.changesubject;
end
+function room_mt:get_historylength()
+ return self._data.history_length or default_history_length;
+end
+function room_mt:set_historylength(length)
+ if tonumber(length) == nil then
+ return
+ end
+ length = tonumber(length);
+ log("debug", "max_history_length %s", self._data.max_history_length or "nil");
+ if self._data.max_history_length and length > self._data.max_history_length then
+ length = self._data.max_history_length
+ end
+ self._data.history_length = length;
+end
+
function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
local from, to = stanza.attr.from, stanza.attr.to;
@@ -608,6 +619,12 @@ function room_mt:get_form_layout()
type = 'boolean',
label = 'Make Room Members-Only?',
value = self:is_members_only()
+ },
+ {
+ name = 'muc#roomconfig_historylength',
+ type = 'text-single',
+ label = 'Maximum Number of History Messages Returned by Room',
+ value = tostring(self:get_historylength())
}
});
end
@@ -659,6 +676,11 @@ function room_mt:process_form(origin, stanza)
dirty = dirty or (self:get_changesubject() ~= (not changesubject and true or nil))
module:log('debug', 'changesubject=%s', changesubject and "true" or "false")
+ local historylength = fields['muc#roomconfig_historylength'];
+ dirty = dirty or (self:get_historylength() ~= (historylength and true or nil))
+ module:log('debug', 'historylength=%s', historylength)
+
+
local whois = fields['muc#roomconfig_whois'];
if not valid_whois[whois] then
origin.send(st.error_reply(stanza, 'cancel', 'bad-request', "Invalid value for 'whois'"));
@@ -677,6 +699,7 @@ function room_mt:process_form(origin, stanza)
self:set_persistent(persistent);
self:set_hidden(not public);
self:set_changesubject(changesubject);
+ self:set_historylength(historylength);
if self.save then self:save(true); end
origin.send(st.reply(stanza));
@@ -828,7 +851,6 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
end
elseif stanza.name == "message" and type == "groupchat" then
local from, to = stanza.attr.from, stanza.attr.to;
- local room = jid_bare(to);
local current_nick = self._jid_nick[from];
local occupant = self._occupants[current_nick];
if not occupant then -- not in room
@@ -848,7 +870,7 @@ function room_mt:handle_to_room(origin, stanza) -- presence changes and groupcha
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
end
else
- self:broadcast_message(stanza, true);
+ self:broadcast_message(stanza, self:get_historylength() > 0);
end
stanza.attr.from = from;
end
@@ -1102,7 +1124,8 @@ function _M.new_room(jid, config)
_occupants = {};
_data = {
whois = 'moderators';
- history_length = (config and config.history_length);
+ history_length = (config and config.max_history_length) or default_history_length;
+ max_history_length = (config and config.max_history_length) or default_history_length;
};
_affiliations = {};
}, room_mt);
diff --git a/plugins/s2s/mod_s2s.lua b/plugins/s2s/mod_s2s.lua
new file mode 100644
index 00000000..cee7d6f6
--- /dev/null
+++ b/plugins/s2s/mod_s2s.lua
@@ -0,0 +1,486 @@
+-- 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.
+--
+
+module:set_global();
+
+local prosody = prosody;
+local hosts = prosody.hosts;
+local core_process_stanza = core_process_stanza;
+
+local tostring, type = tostring, type;
+local t_insert = table.insert;
+local xpcall, traceback = xpcall, debug.traceback;
+
+local add_task = require "util.timer".add_task;
+local st = require "util.stanza";
+local initialize_filters = require "util.filters".initialize;
+local nameprep = require "util.encodings".stringprep.nameprep;
+local new_xmpp_stream = require "util.xmppstream".new;
+local s2s_new_incoming = require "core.s2smanager".new_incoming;
+local s2s_new_outgoing = require "core.s2smanager".new_outgoing;
+local s2s_destroy_session = require "core.s2smanager".destroy_session;
+local s2s_mark_connected = require "core.s2smanager".mark_connected;
+local uuid_gen = require "util.uuid".generate;
+local cert_verify_identity = require "util.x509".verify_identity;
+
+local s2sout = module:require("s2sout");
+
+local connect_timeout = module:get_option_number("s2s_timeout", 60);
+
+local sessions = module:shared("sessions");
+
+local log = module._log;
+
+--- Handle stanzas to remote domains
+
+local bouncy_stanzas = { message = true, presence = true, iq = true };
+local function bounce_sendq(session, reason)
+ local sendq = session.sendq;
+ if not sendq then return; end
+ session.log("info", "sending error replies for "..#sendq.." queued stanzas because of failed outgoing connection to "..tostring(session.to_host));
+ local dummy = {
+ type = "s2sin";
+ send = function(s)
+ (session.log or log)("error", "Replying to to an s2s error reply, please report this! Traceback: %s", traceback());
+ end;
+ dummy = true;
+ };
+ for i, data in ipairs(sendq) do
+ local reply = data[2];
+ 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("Server-to-server connection failed: "..reason):up();
+ end
+ core_process_stanza(dummy, reply);
+ end
+ sendq[i] = nil;
+ end
+ session.sendq = nil;
+end
+
+module:hook("route/remote", function (event)
+ local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza;
+ if not hosts[from_host] then
+ log("warn", "Attempt to send stanza from %s - a host we don't serve", from_host);
+ return false;
+ end
+ local host = hosts[from_host].s2sout[to_host];
+ if host then
+ -- We have a connection to this host already
+ if host.type == "s2sout_unauthed" and (stanza.name ~= "db:verify" or not host.dialback_key) then
+ (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(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)});
+ else host.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} }; end
+ host.log("debug", "stanza [%s] queued ", stanza.name);
+ return true;
+ elseif host.type == "local" or host.type == "component" then
+ log("error", "Trying to send a stanza to ourselves??")
+ log("error", "Traceback: %s", traceback());
+ log("error", "Stanza: %s", tostring(stanza));
+ return false;
+ else
+ (host.log or log)("debug", "going to send stanza to "..to_host.." from "..from_host);
+ -- FIXME
+ if host.from_host ~= from_host then
+ log("error", "WARNING! This might, possibly, be a bug, but it might not...");
+ log("error", "We are going to send from %s instead of %s", tostring(host.from_host), tostring(from_host));
+ end
+ host.sends2s(stanza);
+ host.log("debug", "stanza sent over "..host.type);
+ return true;
+ end
+ end
+end, 200);
+
+module:hook("route/remote", function (event)
+ local from_host, to_host, stanza = event.from_host, event.to_host, event.stanza;
+ log("debug", "opening a new outgoing connection for this stanza");
+ local host_session = s2s_new_outgoing(from_host, to_host);
+
+ -- Store in buffer
+ host_session.bounce_sendq = bounce_sendq;
+ host_session.sendq = { {tostring(stanza), stanza.attr.type ~= "error" and stanza.attr.type ~= "result" and st.reply(stanza)} };
+ log("debug", "stanza [%s] queued until connection complete", tostring(stanza.name));
+ s2sout.initiate_connection(host_session);
+ if (not host_session.connecting) and (not host_session.conn) then
+ log("warn", "Connection to %s failed already, destroying session...", to_host);
+ s2s_destroy_session(host_session, "Connection failed");
+ return false;
+ end
+ return true;
+end, 100);
+
+--- Helper to check that a session peer's certificate is valid
+local function check_cert_status(session)
+ local conn = session.conn:socket()
+ local cert
+ if conn.getpeercertificate then
+ cert = conn:getpeercertificate()
+ end
+
+ if cert then
+ local chain_valid, errors = conn:getpeerverification()
+ -- Is there any interest in printing out all/the number of errors here?
+ if not chain_valid then
+ (session.log or log)("debug", "certificate chain validation result: invalid");
+ for depth, t in ipairs(errors) do
+ (session.log or log)("debug", "certificate error(s) at depth %d: %s", depth-1, table.concat(t, ", "))
+ end
+ session.cert_chain_status = "invalid";
+ else
+ (session.log or log)("debug", "certificate chain validation result: valid");
+ session.cert_chain_status = "valid";
+
+ local host = session.direction == "incoming" and session.from_host or session.to_host
+
+ -- We'll go ahead and verify the asserted identity if the
+ -- connecting server specified one.
+ if host then
+ if cert_verify_identity(host, "xmpp-server", cert) then
+ session.cert_identity_status = "valid"
+ else
+ session.cert_identity_status = "invalid"
+ end
+ end
+ end
+ end
+end
+
+--- XMPP stream event handlers
+
+local stream_callbacks = { default_ns = "jabber:server", handlestanza = core_process_stanza };
+
+local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
+
+function stream_callbacks.streamopened(session, attr)
+ local send = session.sends2s;
+
+ -- TODO: #29: SASL/TLS on s2s streams
+ session.version = tonumber(attr.version) or 0;
+
+ -- TODO: Rename session.secure to session.encrypted
+ if session.secure == false then
+ session.secure = true;
+ end
+
+ if session.direction == "incoming" then
+ -- Send a reply stream header
+
+ -- Validate to/from
+ local to, from = nameprep(attr.to), nameprep(attr.from);
+ if not to and attr.to then -- COMPAT: Some servers do not reliably set 'to' (especially on stream restarts)
+ session:close({ condition = "improper-addressing", text = "Invalid 'to' address" });
+ return;
+ end
+ if not from and attr.from then -- COMPAT: Some servers do not reliably set 'from' (especially on stream restarts)
+ session:close({ condition = "improper-addressing", text = "Invalid 'from' address" });
+ return;
+ end
+
+ -- Set session.[from/to]_host if they have not been set already and if
+ -- this session isn't already authenticated
+ if session.type == "s2sin_unauthed" and from and not session.from_host then
+ session.from_host = from;
+ elseif from ~= session.from_host then
+ session:close({ condition = "improper-addressing", text = "New stream 'from' attribute does not match original" });
+ return;
+ end
+ if session.type == "s2sin_unauthed" and to and not session.to_host then
+ session.to_host = to;
+ elseif to ~= session.to_host then
+ session:close({ condition = "improper-addressing", text = "New stream 'to' attribute does not match original" });
+ return;
+ end
+
+ session.streamid = uuid_gen();
+ (session.log or log)("debug", "Incoming s2s received %s", st.stanza("stream:stream", attr):top_tag());
+ 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
+
+ if session.secure and not session.cert_chain_status then check_cert_status(session); end
+
+ send("<?xml version='1.0'?>");
+ send(st.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", { 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
+
+ log("debug", "Sending stream features: %s", tostring(features));
+ send(features);
+ end
+ elseif session.direction == "outgoing" then
+ -- If we are just using the connection for verifying dialback keys, we won't try and auth it
+ if not attr.id then error("stream response did not give us a streamid!!!"); end
+ session.streamid = attr.id;
+
+ if session.secure and not session.cert_chain_status then check_cert_status(session); end
+
+ -- Send unauthed buffer
+ -- (stanzas which are fine to send before dialback)
+ -- 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(tostring(data));
+ send_buffer[i] = nil;
+ end
+ end
+ session.send_buffer = nil;
+
+ -- If server is pre-1.0, don't wait for features, just do dialback
+ if session.version < 1.0 then
+ if not session.dialback_verifying then
+ hosts[session.from_host].events.fire_event("s2s-authenticate-legacy", { origin = session });
+ else
+ s2s_mark_connected(session);
+ end
+ end
+ end
+ session.notopen = nil;
+ session.send = function(stanza) prosody.events.fire_event("route/remote", { from_host = session.to_host, to_host = session.from_host, stanza = stanza}) end;
+end
+
+function stream_callbacks.streamclosed(session)
+ (session.log or log)("debug", "Received </stream:stream>");
+ session:close();
+end
+
+function stream_callbacks.streamdisconnected(session, err)
+ if err and err ~= "closed" then
+ (session.log or log)("debug", "s2s connection attempt failed: %s", err);
+ if s2sout.attempt_connection(session, err) then
+ (session.log or log)("debug", "...so we're going to try another target");
+ return true; -- Session lives for now
+ end
+ end
+ (session.log or log)("info", "s2s disconnected: %s->%s (%s)", tostring(session.from_host), tostring(session.to_host), tostring(err or "closed"));
+ s2s_destroy_session(session, err);
+end
+
+function stream_callbacks.error(session, error, data)
+ if error == "no-stream" then
+ session:close("invalid-namespace");
+ elseif error == "parse-error" then
+ session.log("debug", "Server-to-server XML parse error: %s", tostring(error));
+ session:close("not-well-formed");
+ elseif error == "stream-error" then
+ local condition, text = "undefined-condition";
+ for child in data:children() do
+ if child.attr.xmlns == xmlns_xmpp_streams then
+ if child.name ~= "text" then
+ condition = child.name;
+ else
+ text = child:get_text();
+ end
+ if condition ~= "undefined-condition" and text then
+ break;
+ end
+ end
+ end
+ text = condition .. (text and (" ("..text..")") or "");
+ session.log("info", "Session closed by remote with error: %s", text);
+ session:close(nil, text);
+ end
+end
+
+local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end
+function stream_callbacks.handlestanza(session, stanza)
+ if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
+ stanza.attr.xmlns = nil;
+ end
+ stanza = session.filter("stanzas/in", stanza);
+ if stanza then
+ return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+ end
+end
+
+local listener = {};
+
+--- Session methods
+local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
+local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+local function session_close(session, reason, remote_reason)
+ local log = session.log or log;
+ if session.conn then
+ if session.notopen then
+ session.sends2s("<?xml version='1.0'?>");
+ session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
+ end
+ if reason then
+ if type(reason) == "string" then -- assume stream error
+ log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, reason);
+ session.sends2s(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
+ elseif type(reason) == "table" then
+ if reason.condition then
+ local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
+ if reason.text then
+ stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
+ end
+ if reason.extra then
+ stanza:add_child(reason.extra);
+ end
+ log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, tostring(stanza));
+ session.sends2s(stanza);
+ elseif reason.name then -- a stanza
+ log("info", "Disconnecting %s->%s[%s], <stream:error> is: %s", session.from_host or "(unknown host)", session.to_host or "(unknown host)", session.type, tostring(reason));
+ session.sends2s(reason);
+ end
+ end
+ end
+ session.sends2s("</stream:stream>");
+ if session.notopen or not session.conn:close() then
+ session.conn:close(true); -- Force FIXME: timer?
+ end
+ session.conn:close();
+ listener.ondisconnect(session.conn, remote_reason or (reason and (reason.text or reason.condition)) or reason or "stream closed");
+ end
+end
+
+-- Session initialization logic shared by incoming and outgoing
+local function initialize_session(session)
+ local stream = new_xmpp_stream(session, stream_callbacks);
+ session.stream = stream;
+
+ session.notopen = true;
+
+ function session.reset_stream()
+ session.notopen = true;
+ session.stream:reset();
+ end
+
+ local filter = session.filter;
+ function session.data(data)
+ data = filter("bytes/in", data);
+ if data then
+ local ok, err = stream:feed(data);
+ if ok then return; end
+ (session.log or log)("warn", "Received invalid XML: %s", data);
+ (session.log or log)("warn", "Problem was: %s", err);
+ session:close("not-well-formed");
+ end
+ end
+
+ session.close = session_close;
+
+ local handlestanza = stream_callbacks.handlestanza;
+ function session.dispatch_stanza(session, stanza)
+ return handlestanza(session, stanza);
+ end
+
+ local conn = session.conn;
+ add_task(connect_timeout, function ()
+ if session.conn ~= conn or session.connecting
+ or session.type == "s2sin" or session.type == "s2sout" then
+ return; -- Ok, we're connect[ed|ing]
+ end
+ -- Not connected, need to close session and clean up
+ (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);
+end
+
+function listener.onconnect(conn)
+ if not sessions[conn] then -- May be an existing outgoing session
+ local session = s2s_new_incoming(conn);
+ sessions[conn] = session;
+ session.log("debug", "Incoming s2s connection");
+
+ local filter = initialize_filters(session);
+ local w = conn.write;
+ 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
+
+ initialize_session(session);
+ end
+end
+
+function listener.onincoming(conn, data)
+ local session = sessions[conn];
+ if session then
+ session.data(data);
+ end
+end
+
+function listener.onstatus(conn, status)
+ if status == "ssl-handshake-complete" then
+ local session = sessions[conn];
+ if session and session.direction == "outgoing" then
+ session.log("debug", "Sending stream header...");
+ session:open_stream(session.from_host, session.to_host);
+ end
+ end
+end
+
+function listener.ondisconnect(conn, err)
+ local session = sessions[conn];
+ if session then
+ if stream_callbacks.streamdisconnected(session, err) then
+ return; -- Connection lives, for now
+ end
+ end
+ sessions[conn] = nil;
+end
+
+function listener.register_outgoing(conn, session)
+ session.direction = "outgoing";
+ sessions[conn] = session;
+ initialize_session(session);
+end
+
+s2sout.set_listener(listener);
+
+module:add_item("net-provider", {
+ name = "s2s";
+ listener = listener;
+ default_port = 5269;
+ encryption = "starttls";
+ multiplex = {
+ pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])jabber:server%1.*>";
+ };
+});
+
diff --git a/plugins/s2s/s2sout.lib.lua b/plugins/s2s/s2sout.lib.lua
new file mode 100644
index 00000000..f3496597
--- /dev/null
+++ b/plugins/s2s/s2sout.lib.lua
@@ -0,0 +1,352 @@
+-- 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.
+--
+
+--- Module containing all the logic for connecting to a remote server
+
+local portmanager = require "core.portmanager";
+local wrapclient = require "net.server".wrapclient;
+local initialize_filters = require "util.filters".initialize;
+local idna_to_ascii = require "util.encodings".idna.to_ascii;
+local new_ip = require "util.ip".new_ip;
+local rfc3484_dest = require "util.rfc3484".destination;
+local socket = require "socket";
+local adns = require "net.adns";
+local dns = require "net.dns";
+local t_insert, t_sort, ipairs = table.insert, table.sort, ipairs;
+local st = require "util.stanza";
+
+local s2s_destroy_session = require "core.s2smanager".destroy_session;
+
+local log = module._log;
+
+local sources = {};
+
+local max_dns_depth = module:get_option_number("dns_max_depth", 3);
+
+local s2sout = {};
+
+local s2s_listener;
+
+
+function s2sout.set_listener(listener)
+ s2s_listener = listener;
+end
+
+local function compare_srv_priorities(a,b)
+ return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight);
+end
+
+local 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 s2sout.initiate_connection(host_session)
+ initialize_filters(host_session);
+ host_session.open_stream = session_open_stream;
+
+ -- Kick the connection attempting machine into life
+ if not s2sout.attempt_connection(host_session) then
+ -- Intentionally not returning here, the
+ -- session is needed, connected or not
+ s2s_destroy_session(host_session);
+ end
+
+ 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
+ local buffer;
+ function host_session.sends2s(data)
+ if not buffer then
+ buffer = {};
+ host_session.send_buffer = buffer;
+ end
+ log("debug", "Buffering data on unconnected s2sout to %s", tostring(host_session.to_host));
+ buffer[#buffer+1] = data;
+ log("debug", "Buffered item %d: %s", #buffer, tostring(data));
+ end
+ end
+end
+
+function s2sout.attempt_connection(host_session, err)
+ local from_host, to_host = host_session.from_host, host_session.to_host;
+ local connect_host, connect_port = to_host and idna_to_ascii(to_host), 5269;
+
+ if not connect_host then
+ return false;
+ end
+
+ if not err then -- This is our first attempt
+ log("debug", "First attempt to connect to %s, starting with SRV lookup...", to_host);
+ host_session.connecting = true;
+ local handle;
+ handle = adns.lookup(function (answer)
+ handle = nil;
+ host_session.connecting = nil;
+ if answer then
+ log("debug", to_host.." has SRV records, handling...");
+ local srv_hosts = {};
+ host_session.srv_hosts = srv_hosts;
+ for _, record in ipairs(answer) do
+ t_insert(srv_hosts, record.srv);
+ end
+ if #srv_hosts == 1 and srv_hosts[1].target == "." then
+ log("debug", to_host.." does not provide a XMPP service");
+ s2s_destroy_session(host_session, err); -- Nothing to see here
+ return;
+ end
+ t_sort(srv_hosts, compare_srv_priorities);
+
+ local srv_choice = srv_hosts[1];
+ host_session.srv_choice = 1;
+ if srv_choice then
+ connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port;
+ log("debug", "Best record found, will connect to %s:%d", connect_host, connect_port);
+ end
+ else
+ log("debug", to_host.." has no SRV records, falling back to A");
+ end
+ -- Try with SRV, or just the plain hostname if no SRV
+ local ok, err = s2sout.try_connect(host_session, connect_host, connect_port);
+ if not ok then
+ if not s2sout.attempt_connection(host_session, err) then
+ -- No more attempts will be made
+ s2s_destroy_session(host_session, err);
+ end
+ end
+ end, "_xmpp-server._tcp."..connect_host..".", "SRV");
+
+ return true; -- Attempt in progress
+ elseif host_session.ip_hosts then
+ return s2sout.try_connect(host_session, connect_host, connect_port, err);
+ 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;
+ local srv_choice = host_session.srv_hosts[host_session.srv_choice];
+ connect_host, connect_port = srv_choice.target or to_host, srv_choice.port or connect_port;
+ host_session.log("info", "Connection failed (%s). Attempt #%d: This time to %s:%d", tostring(err), host_session.srv_choice, connect_host, connect_port);
+ else
+ host_session.log("info", "Out of connection options, can't connect to %s", tostring(host_session.to_host));
+ -- We're out of options
+ return false;
+ end
+
+ if not (connect_host and connect_port) then
+ -- Likely we couldn't resolve DNS
+ log("warn", "Hmm, we're without a host (%s) and port (%s) to connect to for %s, giving up :(", tostring(connect_host), tostring(connect_port), tostring(to_host));
+ return false;
+ end
+
+ return s2sout.try_connect(host_session, connect_host, connect_port);
+end
+
+function s2sout.try_next_ip(host_session)
+ host_session.connecting = nil;
+ host_session.ip_choice = host_session.ip_choice + 1;
+ local ip = host_session.ip_hosts[host_session.ip_choice];
+ local ok, err= s2sout.make_connect(host_session, ip.ip, ip.port);
+ if not ok then
+ if not s2sout.attempt_connection(host_session, err or "closed") then
+ err = err and (": "..err) or "";
+ s2s_destroy_session(host_session, "Connection failed"..err);
+ end
+ end
+end
+
+function s2sout.try_connect(host_session, connect_host, connect_port, err)
+ host_session.connecting = true;
+
+ if not err then
+ local IPs = {};
+ host_session.ip_hosts = IPs;
+ local handle4, handle6;
+ local has_other = false;
+
+ handle4 = adns.lookup(function (reply, err)
+ handle4 = nil;
+
+ -- COMPAT: This is a compromise for all you CNAME-(ab)users :)
+ if not (reply and reply[#reply] and reply[#reply].a) then
+ local count = max_dns_depth;
+ reply = dns.peek(connect_host, "CNAME", "IN");
+ while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do
+ log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count);
+ reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN");
+ count = count - 1;
+ end
+ end
+ -- end of CNAME resolving
+
+ if reply and reply[#reply] and reply[#reply].a then
+ for _, ip in ipairs(reply) do
+ log("debug", "DNS reply for %s gives us %s", connect_host, ip.a);
+ IPs[#IPs+1] = new_ip(ip.a, "IPv4");
+ end
+ end
+
+ if has_other then
+ if #IPs > 0 then
+ rfc3484_dest(host_session.ip_hosts, sources);
+ for i = 1, #IPs do
+ IPs[i] = {ip = IPs[i], port = connect_port};
+ end
+ host_session.ip_choice = 0;
+ s2sout.try_next_ip(host_session);
+ else
+ log("debug", "DNS lookup failed to get a response for %s", connect_host);
+ host_session.ip_hosts = nil;
+ if not s2sout.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);
+ err = err and (": "..err) or "";
+ s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+ end
+ end
+ else
+ has_other = true;
+ end
+ end, connect_host, "A", "IN");
+
+ handle6 = adns.lookup(function (reply, err)
+ handle6 = nil;
+
+ if reply and reply[#reply] and reply[#reply].aaaa then
+ for _, ip in ipairs(reply) do
+ log("debug", "DNS reply for %s gives us %s", connect_host, ip.aaaa);
+ IPs[#IPs+1] = new_ip(ip.aaaa, "IPv6");
+ end
+ end
+
+ if has_other then
+ if #IPs > 0 then
+ rfc3484_dest(host_session.ip_hosts, sources);
+ for i = 1, #IPs do
+ IPs[i] = {ip = IPs[i], port = connect_port};
+ end
+ host_session.ip_choice = 0;
+ s2sout.try_next_ip(host_session);
+ else
+ log("debug", "DNS lookup failed to get a response for %s", connect_host);
+ host_session.ip_hosts = nil;
+ if not s2sout.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);
+ err = err and (": "..err) or "";
+ s2s_destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+ end
+ end
+ else
+ has_other = true;
+ end
+ end, connect_host, "AAAA", "IN");
+
+ return true;
+ elseif host_session.ip_hosts and #host_session.ip_hosts > host_session.ip_choice then -- Not our first attempt, and we also have IPs left to try
+ s2sout.try_next_ip(host_session);
+ else
+ host_session.ip_hosts = nil;
+ if not s2sout.attempt_connection(host_session, "out of IP addresses") then -- Retry if we can
+ log("debug", "No other records to try for %s - destroying", host_session.to_host);
+ err = err and (": "..err) or "";
+ s2s_destroy_session(host_session, "Connecting failed"..err); -- End of the line, we can't
+ return false;
+ end
+ end
+
+ return true;
+end
+
+function s2sout.make_connect(host_session, connect_host, connect_port)
+ (host_session.log or log)("info", "Beginning new connection attempt to %s ([%s]:%d)", host_session.to_host, connect_host.addr, connect_port);
+ -- Ok, we're going to try to connect
+
+ local from_host, to_host = host_session.from_host, host_session.to_host;
+
+ local conn, handler;
+ if connect_host.proto == "IPv4" then
+ conn, handler = socket.tcp();
+ else
+ if not socket.tcp6 then
+ log("warn", "Could not connect to "..to_host..". Your version of lua-socket does not support IPv6");
+ return false, "no-ipv6";
+ end
+ conn, handler = socket.tcp6();
+ end
+
+ if not conn then
+ log("warn", "Failed to create outgoing connection, system error: %s", handler);
+ return false, handler;
+ end
+
+ conn:settimeout(0);
+ local success, err = conn:connect(connect_host.addr, connect_port);
+ if not success and err ~= "timeout" then
+ log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host.addr, connect_port, err);
+ return false, err;
+ end
+
+ conn = wrapclient(conn, connect_host.addr, connect_port, s2s_listener, "*a");
+ 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
+ s2s_listener.register_outgoing(conn, host_session);
+
+ host_session:open_stream(from_host, to_host);
+
+ log("debug", "Connection attempt in progress...");
+ return true;
+end
+
+module:hook_global("service-added", function (event)
+ if event.name ~= "s2s" then return end
+
+ local s2s_sources = portmanager.get_active_services():get("s2s");
+ if not s2s_sources then
+ module:log("warn", "s2s not listening on any ports, outgoing connections may fail");
+ return;
+ end
+ for source, _ in pairs(s2s_sources) do
+ if source == "*" or source == "0.0.0.0" then
+ if not socket.local_addresses then
+ sources[#sources + 1] = new_ip("0.0.0.0", "IPv4");
+ else
+ for _, addr in ipairs(socket.local_addresses("ipv4", true)) do
+ sources[#sources + 1] = new_ip(addr, "IPv4");
+ end
+ end
+ elseif source == "::" then
+ if not socket.local_addresses then
+ sources[#sources + 1] = new_ip("::", "IPv6");
+ else
+ for _, addr in ipairs(socket.local_addresses("ipv6", true)) do
+ sources[#sources + 1] = new_ip(addr, "IPv6");
+ end
+ end
+ else
+ sources[#sources + 1] = new_ip(source, (source:find(":") and "IPv6") or "IPv4");
+ end
+ end
+end);
+
+return s2sout;
diff --git a/prosody b/prosody
index e18b4e61..64021fd3 100755
--- a/prosody
+++ b/prosody
@@ -150,10 +150,14 @@ function sandbox_require()
local curr_env_mt = getmetatable(getfenv(2));
local _realG_mt = getmetatable(_realG);
if curr_env_mt and curr_env_mt.__index and not curr_env_mt.__newindex and _realG_mt then
- local old_newindex
+ local old_newindex, old_index;
old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
+ old_index, _realG_mt.__index = _realG_mt.__index, function (_G, k)
+ return rawget(curr_env, k);
+ end;
local ret = _real_require(...);
_realG_mt.__newindex = old_newindex;
+ _realG_mt.__index = old_index;
return ret;
end
return _real_require(...);
@@ -254,63 +258,6 @@ function init_global_state()
local global_ssl_ctx = certmanager.create_context("*", "server");
prosody.global_ssl_ctx = global_ssl_ctx;
- local cl = require "net.connlisteners";
- function prosody.net_activate_ports(option, listener, default, conntype)
- conntype = conntype or (global_ssl_ctx and "tls") or "tcp";
- local ports_option = option and option.."_ports" or "ports";
- if not cl.get(listener) then return; end
- local ports = config.get("*", "core", ports_option) or default;
- if type(ports) == "number" then ports = {ports} end;
-
- if type(ports) ~= "table" then
- log("error", "core."..ports_option.." is not a table");
- else
- for _, port in ipairs(ports) do
- port = tonumber(port);
- if type(port) ~= "number" then
- log("error", "Non-numeric "..ports_option..": "..tostring(port));
- else
- local ok, errors = cl.start(listener, {
- ssl = conntype == "ssl" and global_ssl_ctx,
- port = port,
- interface = (option and config.get("*", "core", option.."_interface"))
- or cl.get(listener).default_interface
- or config.get("*", "core", "interface"),
- type = conntype
- });
- if not ok then
- for addr, err in pairs(errors) do
- local friendly_message = err;
- if err:match(" in use") then
- if port == 5222 or port == 5223 or port == 5269 then
- friendly_message = "check that Prosody or another XMPP server is "
- .."not already running and using this port";
- elseif port == 80 or port == 81 then
- friendly_message = "check that a HTTP server is not already using "
- .."this port";
- elseif port == 5280 then
- friendly_message = "check that Prosody or a BOSH connection manager "
- .."is not already running";
- else
- friendly_message = "this port is in use by another application";
- end
- elseif err:match("permission") then
- friendly_message = "Prosody does not have sufficient privileges to use this port";
- elseif err == "no ssl context" then
- if not config.get("*", "core", "ssl") then
- friendly_message = "there is no 'ssl' config under Host \"*\" which is "
- .."require for legacy SSL ports";
- else
- friendly_message = "initializing SSL support failed, see previous log entries";
- end
- end
- log("error", "Failed to open server port %d on %s, %s", port, addr, friendly_message);
- end
- end
- end
- end
- end
- end
end
function read_version()
@@ -333,6 +280,7 @@ function load_secondary_libraries()
require "util.xmppstream"
require "core.rostermanager"
require "core.hostmanager"
+ require "core.portmanager"
require "core.modulemanager"
require "core.usermanager"
require "core.sessionmanager"
@@ -359,9 +307,6 @@ function load_secondary_libraries()
if remdebug then remdebug.engine.start() end
]]
- require "net.connlisteners";
- require "net.httpserver";
-
require "util.stanza"
require "util.jid"
end
@@ -374,20 +319,6 @@ function prepare_to_start()
log("info", "Prosody is using the %s backend for connection handling", server.get_backend());
-- Signal to modules that we are ready to start
prosody.events.fire_event("server-starting");
-
- -- start listening on sockets
- if config.get("*", "core", "ports") then
- prosody.net_activate_ports(nil, "multiplex", {5222, 5269});
- if config.get("*", "core", "ssl_ports") then
- prosody.net_activate_ports("ssl", "multiplex", {5223}, "ssl");
- end
- else
- prosody.net_activate_ports("c2s", "xmppclient", {5222});
- prosody.net_activate_ports("s2s", "xmppserver", {5269});
- prosody.net_activate_ports("component", "xmppcomponent", {5347}, "tcp");
- prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
- end
-
prosody.start_time = os.time();
end
diff --git a/prosody.cfg.lua.dist b/prosody.cfg.lua.dist
index db85a70b..c0c729a9 100644
--- a/prosody.cfg.lua.dist
+++ b/prosody.cfg.lua.dist
@@ -56,11 +56,13 @@ modules_enabled = {
-- Admin interfaces
"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
--"admin_telnet"; -- Opens telnet console interface on localhost port 5582
+
+ -- HTTP modules
+ --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
+ --"http_files"; -- Serve static files from a directory over HTTP
-- Other specific functionality
--"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
- --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
- --"httpserver"; -- Serve static files from a directory over HTTP
--"groups"; -- Shared roster support
--"announce"; -- Send announcement to all online users
--"welcome"; -- Welcome users who register accounts
diff --git a/prosodyctl b/prosodyctl
index f0ba68ce..cdeaaf26 100755
--- a/prosodyctl
+++ b/prosodyctl
@@ -236,6 +236,7 @@ local show_message, show_warning = prosodyctl.show_message, prosodyctl.show_warn
local show_usage = prosodyctl.show_usage;
local getchar, getpass = prosodyctl.getchar, prosodyctl.getpass;
local show_yesno = prosodyctl.show_yesno;
+local show_prompt = prosodyctl.show_prompt;
local read_password = prosodyctl.read_password;
local prosodyctl_timeout = (config.get("*", "core", "prosodyctl_timeout") or 5) * 2;
@@ -612,6 +613,106 @@ function commands.unregister(arg)
return 1;
end
+local x509 = require "util.x509";
+local genx509san = x509.genx509san;
+local opensslbaseconf = x509.baseconf;
+local seralizeopensslbaseconf = x509.serialize_conf;
+
+local cert_commands = {};
+
+-- TODO Should this be moved to util.prosodyctl or x509?
+function cert_commands.config(arg)
+ if #arg >= 1 and arg[1] ~= "--help" then
+ local conf_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".cnf";
+ if os.execute("test -f "..conf_filename) == 0
+ and not show_yesno("Overwrite "..conf_filename .. "?") then
+ return nil, conf_filename;
+ end
+ local conf = opensslbaseconf();
+ conf.subject_alternative_name = genx509san(hosts, config, arg, true)
+ for k, v in pairs(conf.distinguished_name) do
+ local nv;
+ if k == "commonName" then
+ v = arg[1]
+ elseif k == "emailAddress" then
+ v = "xmpp@" .. arg[1];
+ end
+ nv = show_prompt(("%s (%s):"):format(k, nv or v));
+ nv = (not nv or nv == "") and v or nv;
+ conf.distinguished_name[k] = nv ~= "." and nv or nil;
+ end
+ local conf_file = io.open(conf_filename, "w");
+ conf_file:write(seralizeopensslbaseconf(conf));
+ conf_file:close();
+ print("");
+ show_message("Config written to " .. conf_filename);
+ return nil, conf_filename;
+ else
+ show_usage("cert config HOSTNAME", "generates config for OpenSSL")
+ end
+end
+
+function cert_commands.key(arg)
+ if #arg >= 1 and arg[1] ~= "--help" then
+ local key_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".key";
+ if os.execute("test -f "..key_filename) == 0
+ and not show_yesno("Overwrite "..key_filename .. "?") then
+ return nil, key_filename;
+ end
+ local key_size = tonumber(arg[2] or show_prompt("Choose key size (2048):") or 2048);
+ os.execute(("openssl genrsa -out %s %d"):format(key_filename, tonumber(key_size)));
+ os.execute(("chmod 400 %s"):format(key_filename));
+ show_message("Key written to ".. key_filename);
+ return nil, key_filename;
+ else
+ show_usage("cert key HOSTNAME <bits>", "Generates a RSA key")
+ end
+end
+
+function cert_commands.request(arg)
+ if #arg >= 1 and arg[1] ~= "--help" then
+ local req_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".req";
+ if os.execute("test -f "..req_filename) == 0
+ and not show_yesno("Overwrite "..req_filename .. "?") then
+ return nil, req_filename;
+ end
+ local _, key_filename = cert_commands.key({arg[1]});
+ local _, conf_filename = cert_commands.config({arg[1]});
+ os.execute(("openssl req -new -key %s -utf8 -config %s -out %s")
+ :format(key_filename, conf_filename, req_filename));
+ show_message("Certificate request written to ".. req_filename);
+ else
+ show_usage("cert request HOSTNAME", "Generates a certificate request")
+ end
+end
+
+function cert_commands.generate(arg)
+ if #arg >= 1 and arg[1] ~= "--help" then
+ local cert_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".cert";
+ if os.execute("test -f "..cert_filename) == 0
+ and not show_yesno("Overwrite "..cert_filename .. "?") then
+ return nil, cert_filename;
+ end
+ local _, key_filename = cert_commands.key({arg[1]});
+ local _, conf_filename = cert_commands.config({arg[1]});
+ os.execute(("openssl req -new -x509 -nodes -key %s -days 365 -sha1 -utf8 -config %s -out %s")
+ :format(key_filename, conf_filename, cert_filename));
+ show_message("Certificate written to ".. cert_filename);
+ else
+ show_usage("cert generate HOSTNAME", "Generates a self-signed certificate")
+ end
+end
+
+function commands.cert(arg)
+ if #arg >= 1 and arg[1] ~= "--help" then
+ local subcmd = table.remove(arg, 1);
+ if type(cert_commands[subcmd]) == "function" then
+ return cert_commands[subcmd](arg);
+ end
+ end
+ show_usage("cert config|request|generate|key", "Helpers for X.509 certificates.")
+end
+
---------------------
if command and command:match("^mod_") then -- Is a command in a module
diff --git a/tests/test.lua b/tests/test.lua
index 000c3ee9..db727ce1 100644
--- a/tests/test.lua
+++ b/tests/test.lua
@@ -12,6 +12,7 @@ function run_all_tests()
package.loaded["net.connlisteners"] = { get = function () return {} end };
dotest "util.jid"
dotest "util.multitable"
+ dotest "util.rfc3484"
dotest "net.http"
dotest "core.modulemanager"
dotest "core.stanza_router"
diff --git a/tests/test_util_rfc3484.lua b/tests/test_util_rfc3484.lua
new file mode 100644
index 00000000..18ae310e
--- /dev/null
+++ b/tests/test_util_rfc3484.lua
@@ -0,0 +1,51 @@
+-- Prosody IM
+-- Copyright (C) 2011 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+function source(source)
+ local new_ip = require"util.ip".new_ip;
+ assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("3ffe::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr, "3ffe::1", "prefer appropriate scope");
+ assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope");
+ assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "2001::1", "prefer appropriate scope");
+ assert_equal(source(new_ip("ff05::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope");
+ assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::1", "IPv6"), new_ip("2002::1", "IPv6")}).addr, "2001::1", "prefer same address");
+ assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fec0::2", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::2", "prefer appropriate scope");
+ assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::2", "IPv6"), new_ip("3ffe::2", "IPv6")}).addr, "2001::2", "longest matching prefix");
+ assert_equal(source(new_ip("2002:836b:2179::1", "IPv6"), {new_ip("2002:836b:2179::d5e3:7953:13eb:22e8", "IPv6"), new_ip("2001::2", "IPv6")}).addr, "2002:836b:2179::d5e3:7953:13eb:22e8", "prefer matching label");
+end
+
+function destination(dest)
+ local order;
+ local new_ip = require"util.ip".new_ip;
+ order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("169.254.13.78", "IPv4")})
+ assert_equal(order[1].addr, "2001::1", "prefer matching scope");
+ assert_equal(order[2].addr, "131.107.65.121", "prefer matching scope")
+
+ order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("fe80::1", "IPv6"), new_ip("131.107.65.117", "IPv4")})
+ assert_equal(order[1].addr, "131.107.65.121", "prefer matching scope")
+ assert_equal(order[2].addr, "2001::1", "prefer matching scope")
+
+ order = dest({new_ip("2001::1", "IPv6"), new_ip("10.1.2.3", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("10.1.2.4", "IPv4")})
+ assert_equal(order[1].addr, "2001::1", "prefer higher precedence");
+ assert_equal(order[2].addr, "10.1.2.3", "prefer higher precedence");
+
+ order = dest({new_ip("2001::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "fe80::1", "prefer smaller scope");
+ assert_equal(order[2].addr, "fec0::1", "prefer smaller scope");
+ assert_equal(order[3].addr, "2001::1", "prefer smaller scope");
+
+ order = dest({new_ip("2001::1", "IPv6"), new_ip("3ffe::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("3f44::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "2001::1", "longest matching prefix");
+ assert_equal(order[2].addr, "3ffe::1", "longest matching prefix");
+
+ order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "2002:836b:4179::1", "prefer matching label");
+ assert_equal(order[2].addr, "2001::1", "prefer matching label");
+
+ order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("2001::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+ assert_equal(order[1].addr, "2001::1", "prefer higher precedence");
+ assert_equal(order[2].addr, "2002:836b:4179::1", "prefer higher precedence");
+end
diff --git a/tools/ejabberdsql2prosody.lua b/tools/ejabberdsql2prosody.lua
index 576f4436..c64faee0 100644
--- a/tools/ejabberdsql2prosody.lua
+++ b/tools/ejabberdsql2prosody.lua
@@ -129,7 +129,12 @@ local function readInsert()
end
end
local tname = readTableName();
- for ch in ("` VALUES "):gmatch(".") do read(ch); end -- expect this
+ read("`"); read(" ") -- expect this
+ if peek() == "(" then -- skip column list
+ repeat until read() == ")";
+ read(" ");
+ end
+ for ch in ("VALUES "):gmatch(".") do read(ch); end -- expect this
local tuples = readTuples();
read(";"); read("\n");
return tname, tuples;
diff --git a/tools/migration/migrator/jabberd14.lua b/tools/migration/migrator/jabberd14.lua
index 47004a52..54c45d94 100644
--- a/tools/migration/migrator/jabberd14.lua
+++ b/tools/migration/migrator/jabberd14.lua
@@ -67,15 +67,12 @@ local parse_xml = (function()
end)();
local function load_xml(path)
- if path then
- local f, err = io_open(path);
- if not f then return f, err; end
- local data = f:read("*a");
- f:close();
- if data then
- return parse_xml(data);
- end
- end
+ local f, err = io_open(path);
+ if not f then return f, err; end
+ local data = f:read("*a");
+ f:close();
+ if not data then return; end
+ return parse_xml(data);
end
local function load_spool_file(host, filename, path)
diff --git a/util/array.lua b/util/array.lua
index 5dbd3037..5dc604ba 100644
--- a/util/array.lua
+++ b/util/array.lua
@@ -9,6 +9,11 @@
local t_insert, t_sort, t_remove, t_concat
= table.insert, table.sort, table.remove, table.concat;
+local setmetatable = setmetatable;
+local math_random = math.random;
+local pairs, ipairs = pairs, ipairs;
+local tostring = tostring;
+
local array = {};
local array_base = {};
local array_methods = {};
@@ -25,6 +30,15 @@ end
setmetatable(array, { __call = new_array });
+-- Read-only methods
+function array_methods:random()
+ return self[math_random(1,#self)];
+end
+
+-- These methods can be called two ways:
+-- array.method(existing_array, [params [, ...]]) -- Create new array for result
+-- existing_array:method([params, ...]) -- Transform existing array into result
+--
function array_base.map(outa, ina, func)
for k,v in ipairs(ina) do
outa[k] = func(v);
@@ -60,15 +74,18 @@ function array_base.sort(outa, ina, ...)
return outa;
end
---- These methods only mutate
-function array_methods:random()
- return self[math.random(1,#self)];
+function array_base.pluck(outa, ina, key)
+ for i=1,#ina do
+ outa[i] = ina[i][key];
+ end
+ return outa;
end
+--- These methods only mutate the array
function array_methods:shuffle(outa, ina)
local len = #self;
for i=1,#self do
- local r = math.random(i,len);
+ local r = math_random(i,len);
self[i], self[r] = self[r], self[i];
end
return self;
@@ -91,10 +108,24 @@ function array_methods:append(array)
return self;
end
-array_methods.push = table.insert;
-array_methods.pop = table.remove;
-array_methods.concat = table.concat;
-array_methods.length = function (t) return #t; end
+function array_methods:push(x)
+ t_insert(self, x);
+ return self;
+end
+
+function array_methods:pop(x)
+ local v = self[x];
+ t_remove(self, x);
+ return v;
+end
+
+function array_methods:concat(sep)
+ return t_concat(array.map(self, tostring), sep);
+end
+
+function array_methods:length()
+ return #self;
+end
--- These methods always create a new array
function array.collect(f, s, var)
@@ -102,7 +133,7 @@ function array.collect(f, s, var)
while true do
var = f(s, var);
if var == nil then break; end
- table.insert(t, var);
+ t_insert(t, var);
end
return setmetatable(t, array_mt);
end
diff --git a/util/datamanager.lua b/util/datamanager.lua
index d5e9c88c..a5d676cc 100644
--- a/util/datamanager.lua
+++ b/util/datamanager.lua
@@ -1,7 +1,7 @@
-- 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.
--
@@ -20,7 +20,7 @@ local error = error;
local next = next;
local t_insert = table.insert;
local append = require "util.serialization".append;
-local path_separator = "/"; if os.getenv("WINDIR") then path_separator = "\\" end
+local path_separator = assert ( package.config:match ( "^([^\n]+)" ) , "package.config not in standard form" ) -- Extract directory seperator from package.config (an undocumented string that comes with lua)
local lfs = require "lfs";
local prosody = prosody;
local raw_mkdir;
@@ -72,7 +72,7 @@ local function callback(username, host, datastore, data)
username, host, datastore, data = f(username, host, datastore, data);
if username == false then break; end
end
-
+
return username, host, datastore, data;
end
function add_callback(func)
diff --git a/util/debug.lua b/util/debug.lua
index 22d02bf2..bff0e347 100644
--- a/util/debug.lua
+++ b/util/debug.lua
@@ -7,8 +7,25 @@ local censored_names = {
pass = true;
pwd = true;
};
+local optimal_line_length = 65;
-local function get_locals_table(level)
+local termcolours = require "util.termcolours";
+local getstring = termcolours.getstring;
+local styles;
+do
+ _ = termcolours.getstyle;
+ styles = {
+ boundary_padding = _("bright");
+ filename = _("bright", "blue");
+ level_num = _("green");
+ funcname = _("yellow");
+ location = _("yellow");
+ };
+end
+module("debugx", package.seeall);
+
+function get_locals_table(level)
+ level = level + 1; -- Skip this function itself
local locals = {};
for local_num = 1, math.huge do
local name, value = debug.getlocal(level, local_num);
@@ -18,7 +35,7 @@ local function get_locals_table(level)
return locals;
end
-local function get_upvalues_table(func)
+function get_upvalues_table(func)
local upvalues = {};
if func then
for upvalue_num = 1, math.huge do
@@ -30,7 +47,7 @@ local function get_upvalues_table(func)
return upvalues;
end
-local function string_from_var_table(var_table, max_line_len, indent_str)
+function string_from_var_table(var_table, max_line_len, indent_str)
local var_string = {};
local col_pos = 0;
max_line_len = max_line_len or math.huge;
@@ -71,46 +88,72 @@ function get_traceback_table(thread, start_level)
for level = start_level, math.huge do
local info;
if thread then
- info = debug.getinfo(thread, level);
+ info = debug.getinfo(thread, level+1);
else
- info = debug.getinfo(level);
+ info = debug.getinfo(level+1);
end
if not info then break; end
levels[(level-start_level)+1] = {
level = level;
info = info;
- locals = get_locals_table(level);
+ locals = get_locals_table(level+1);
upvalues = get_upvalues_table(info.func);
};
end
return levels;
end
-function debug.traceback(thread, message, level)
- if type(thread) ~= "thread" then
- thread, message, level = coroutine.running(), thread, message;
+function traceback(...)
+ local ok, ret = pcall(_traceback, ...);
+ if not ok then
+ return "Error in error handling: "..ret;
end
- if level and type(message) ~= "string" then
- return nil, "invalid message";
- elseif not level then
- level = message or 2;
+ return ret;
+end
+
+local function build_source_boundary_marker(last_source_desc)
+ local padding = string.rep("-", math.floor(((optimal_line_length - 6) - #last_source_desc)/2));
+ return getstring(styles.boundary_padding, "v"..padding).." "..getstring(styles.filename, last_source_desc).." "..getstring(styles.boundary_padding, padding..(#last_source_desc%2==0 and "-v" or "v "));
+end
+
+function _traceback(thread, message, level)
+
+ -- Lua manual says: debug.traceback ([thread,] [message [, level]])
+ -- I fathom this to mean one of:
+ -- ()
+ -- (thread)
+ -- (message, level)
+ -- (thread, message, level)
+
+ if thread == nil then -- Defaults
+ thread, message, level = coroutine.running(), message, level;
+ elseif type(thread) == "string" then
+ thread, message, level = coroutine.running(), thread, message;
+ elseif type(thread) ~= "thread" then
+ return nil; -- debug.traceback() does this
end
-
+
+ level = level or 1;
+
message = message and (message.."\n") or "";
- local levels = get_traceback_table(thread, level+2);
+ -- +3 counts for this function, and the pcall() and wrapper above us
+ local levels = get_traceback_table(thread, level+3);
+
+ local last_source_desc;
local lines = {};
for nlevel, level in ipairs(levels) do
local info = level.info;
local line = "...";
local func_type = info.namewhat.." ";
+ local source_desc = (info.short_src == "[C]" and "C code") or info.short_src or "Unknown";
if func_type == " " then func_type = ""; end;
if info.short_src == "[C]" then
- line = "[ C ] "..func_type.."C function "..(info.name and ("%q"):format(info.name) or "(unknown name)")
+ line = "[ C ] "..func_type.."C function "..getstring(styles.location, (info.name and ("%q"):format(info.name) or "(unknown name)"));
elseif info.what == "main" then
- line = "[Lua] "..info.short_src.." line "..info.currentline;
+ line = "[Lua] "..getstring(styles.location, info.short_src.." line "..info.currentline);
else
local name = info.name or " ";
if name ~= " " then
@@ -119,19 +162,32 @@ function debug.traceback(thread, message, level)
if func_type == "global " or func_type == "local " then
func_type = func_type.."function ";
end
- line = "[Lua] "..info.short_src.." line "..info.currentline.." in "..func_type..name.." defined on line "..info.linedefined;
+ line = "[Lua] "..getstring(styles.location, info.short_src.." line "..info.currentline).." in "..func_type..getstring(styles.funcname, name).." (defined on line "..info.linedefined..")";
+ end
+ if source_desc ~= last_source_desc then -- Venturing into a new source, add marker for previous
+ last_source_desc = source_desc;
+ table.insert(lines, "\t "..build_source_boundary_marker(last_source_desc));
end
nlevel = nlevel-1;
- table.insert(lines, "\t"..(nlevel==0 and ">" or " ").."("..nlevel..") "..line);
+ table.insert(lines, "\t"..(nlevel==0 and ">" or " ")..getstring(styles.level_num, "("..nlevel..") ")..line);
local npadding = (" "):rep(#tostring(nlevel));
- local locals_str = string_from_var_table(level.locals, 65, "\t "..npadding);
+ local locals_str = string_from_var_table(level.locals, optimal_line_length, "\t "..npadding);
if locals_str then
table.insert(lines, "\t "..npadding.."Locals: "..locals_str);
end
- local upvalues_str = string_from_var_table(level.upvalues, 65, "\t "..npadding);
+ local upvalues_str = string_from_var_table(level.upvalues, optimal_line_length, "\t "..npadding);
if upvalues_str then
table.insert(lines, "\t "..npadding.."Upvals: "..upvalues_str);
end
end
+
+-- table.insert(lines, "\t "..build_source_boundary_marker(last_source_desc));
+
return message.."stack traceback:\n"..table.concat(lines, "\n");
end
+
+function use()
+ debug.traceback = traceback;
+end
+
+return _M;
diff --git a/util/dependencies.lua b/util/dependencies.lua
index 5baea942..53d2719d 100644
--- a/util/dependencies.lua
+++ b/util/dependencies.lua
@@ -136,6 +136,14 @@ function log_warnings()
log("error", "This version of LuaSec contains a known bug that causes disconnects, see http://prosody.im/doc/depends");
end
end
+ if lxp then
+ if not pcall(lxp.new, { StartDoctypeDecl = false }) then
+ log("error", "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
+ end
end
return _M;
diff --git a/util/helpers.lua b/util/helpers.lua
index 11356176..6103a319 100644
--- a/util/helpers.lua
+++ b/util/helpers.lua
@@ -6,6 +6,8 @@
-- COPYING file in the source package for more information.
--
+local debug = require "util.debug";
+
module("helpers", package.seeall);
-- Helper functions for debugging
@@ -28,7 +30,36 @@ function log_events(events, name, logger)
end
function revert_log_events(events)
- events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :)
+ events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :))
+end
+
+function show_events(events, specific_event)
+ local event_handlers = events._handlers;
+ local events_array = {};
+ local event_handler_arrays = {};
+ for event in pairs(events._event_map) do
+ local handlers = event_handlers[event];
+ if handlers and (event == specific_event or not specific_event) then
+ table.insert(events_array, event);
+ local handler_strings = {};
+ for i, handler in ipairs(handlers) do
+ local upvals = debug.string_from_var_table(debug.get_upvalues_table(handler));
+ handler_strings[i] = " "..i..": "..tostring(handler)..(upvals and ("\n "..upvals) or "");
+ end
+ event_handler_arrays[event] = handler_strings;
+ end
+ end
+ table.sort(events_array);
+ local i = 1;
+ while i <= #events_array do
+ local handlers = event_handler_arrays[events_array[i]];
+ for j=#handlers, 1, -1 do
+ table.insert(events_array, i+1, handlers[j]);
+ end
+ if i > 1 then events_array[i] = "\n"..events_array[i]; end
+ i = i + #handlers + 1
+ end
+ return table.concat(events_array, "\n");
end
function get_upvalue(f, get_name)
diff --git a/util/httpstream.lua b/util/httpstream.lua
index bdc3fce7..190b3ed6 100644
--- a/util/httpstream.lua
+++ b/util/httpstream.lua
@@ -107,9 +107,6 @@ local function parser(success_cb, parser_type, options_cb)
httpversion = httpversion;
headers = headers;
body = body;
- -- COMPAT the properties below are deprecated
- responseversion = httpversion;
- responseheaders = headers;
});
end
else coroutine.yield("unknown-parser-type"); end
diff --git a/util/ip.lua b/util/ip.lua
new file mode 100644
index 00000000..2f09c034
--- /dev/null
+++ b/util/ip.lua
@@ -0,0 +1,176 @@
+-- Prosody IM
+-- Copyright (C) 2008-2011 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local ip_methods = {};
+local ip_mt = { __index = function (ip, key) return (ip_methods[key])(ip); end,
+ __tostring = function (ip) return ip.addr; end,
+ __eq = function (ipA, ipB) return ipA.addr == ipB.addr; end};
+local hex2bits = { ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011", ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111", ["8"] = "1000", ["9"] = "1001", ["A"] = "1010", ["B"] = "1011", ["C"] = "1100", ["D"] = "1101", ["E"] = "1110", ["F"] = "1111" };
+
+local function new_ip(ipStr, proto)
+ if proto ~= "IPv4" and proto ~= "IPv6" then
+ return nil, "invalid protocol";
+ end
+
+ return setmetatable({ addr = ipStr, proto = proto }, ip_mt);
+end
+
+local function toBits(ip)
+ local result = "";
+ local fields = {};
+ if ip.proto == "IPv4" then
+ ip = ip.toV4mapped;
+ end
+ ip = (ip.addr):upper();
+ ip:gsub("([^:]*):?", function (c) fields[#fields + 1] = c end);
+ if not ip:match(":$") then fields[#fields] = nil; end
+ for i, field in ipairs(fields) do
+ if field:len() == 0 and i ~= 1 and i ~= #fields then
+ for i = 1, 16 * (9 - #fields) do
+ result = result .. "0";
+ end
+ else
+ for i = 1, 4 - field:len() do
+ result = result .. "0000";
+ end
+ for i = 1, field:len() do
+ result = result .. hex2bits[field:sub(i,i)];
+ end
+ end
+ end
+ return result;
+end
+
+local function commonPrefixLength(ipA, ipB)
+ ipA, ipB = toBits(ipA), toBits(ipB);
+ for i = 1, 128 do
+ if ipA:sub(i,i) ~= ipB:sub(i,i) then
+ return i-1;
+ end
+ end
+ return 128;
+end
+
+local function v4scope(ip)
+ local fields = {};
+ ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
+ -- Loopback:
+ if fields[1] == 127 then
+ return 0x2;
+ -- Link-local unicast:
+ elseif fields[1] == 169 and fields[2] == 254 then
+ return 0x2;
+ -- Site-local unicast:
+ elseif (fields[1] == 10) or (fields[1] == 192 and fields[2] == 168) or (fields[1] == 172 and (fields[2] >= 16 and fields[2] < 32)) then
+ return 0x5;
+ -- Global unicast:
+ else
+ return 0xE;
+ end
+end
+
+local function v6scope(ip)
+ -- Loopback:
+ if ip:match("^[0:]*1$") then
+ return 0x2;
+ -- Link-local unicast:
+ elseif ip:match("^[Ff][Ee][89ABab]") then
+ return 0x2;
+ -- Site-local unicast:
+ elseif ip:match("^[Ff][Ee][CcDdEeFf]") then
+ return 0x5;
+ -- Multicast:
+ elseif ip:match("^[Ff][Ff]") then
+ return tonumber("0x"..ip:sub(4,4));
+ -- Global unicast:
+ else
+ return 0xE;
+ end
+end
+
+local function label(ip)
+ if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
+ return 0;
+ elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
+ return 2;
+ elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
+ return 3;
+ elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
+ return 4;
+ else
+ return 1;
+ end
+end
+
+local function precedence(ip)
+ if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
+ return 50;
+ elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
+ return 30;
+ elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
+ return 20;
+ elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
+ return 10;
+ else
+ return 40;
+ end
+end
+
+local function toV4mapped(ip)
+ local fields = {};
+ local ret = "::ffff:";
+ ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
+ ret = ret .. ("%02x"):format(fields[1]);
+ ret = ret .. ("%02x"):format(fields[2]);
+ ret = ret .. ":"
+ ret = ret .. ("%02x"):format(fields[3]);
+ ret = ret .. ("%02x"):format(fields[4]);
+ return new_ip(ret, "IPv6");
+end
+
+function ip_methods:toV4mapped()
+ if self.proto ~= "IPv4" then return nil, "No IPv4 address" end
+ local value = toV4mapped(self.addr);
+ self.toV4mapped = value;
+ return value;
+end
+
+function ip_methods:label()
+ local value;
+ if self.proto == "IPv4" then
+ value = label(self.toV4mapped);
+ else
+ value = label(self);
+ end
+ self.label = value;
+ return value;
+end
+
+function ip_methods:precedence()
+ local value;
+ if self.proto == "IPv4" then
+ value = precedence(self.toV4mapped);
+ else
+ value = precedence(self);
+ end
+ self.precedence = value;
+ return value;
+end
+
+function ip_methods:scope()
+ local value;
+ if self.proto == "IPv4" then
+ value = v4scope(self.addr);
+ else
+ value = v6scope(self.addr);
+ end
+ self.scope = value;
+ return value;
+end
+
+return {new_ip = new_ip,
+ commonPrefixLength = commonPrefixLength};
diff --git a/util/iterators.lua b/util/iterators.lua
index 2a87e97a..fb89f4a5 100644
--- a/util/iterators.lua
+++ b/util/iterators.lua
@@ -8,8 +8,10 @@
--[[ Iterators ]]--
+local it = {};
+
-- Reverse an iterator
-function reverse(f, s, var)
+function it.reverse(f, s, var)
local results = {};
-- First call the normal iterator
@@ -34,12 +36,12 @@ end
local function _keys_it(t, key)
return (next(t, key));
end
-function keys(t)
+function it.keys(t)
return _keys_it, t;
end
-- Iterate only over values in a table
-function values(t)
+function it.values(t)
local key, val;
return function (t)
key, val = next(t, key);
@@ -48,7 +50,7 @@ function values(t)
end
-- Given an iterator, iterate only over unique items
-function unique(f, s, var)
+function it.unique(f, s, var)
local set = {};
return function ()
@@ -65,7 +67,7 @@ function unique(f, s, var)
end
--[[ Return the number of items an iterator returns ]]--
-function count(f, s, var)
+function it.count(f, s, var)
local x = 0;
while true do
@@ -79,7 +81,7 @@ function count(f, s, var)
end
-- Return the first n items an iterator returns
-function head(n, f, s, var)
+function it.head(n, f, s, var)
local c = 0;
return function (s, var)
if c >= n then
@@ -91,7 +93,7 @@ function head(n, f, s, var)
end
-- Skip the first n items an iterator returns
-function skip(n, f, s, var)
+function it.skip(n, f, s, var)
for i=1,n do
var = f(s, var);
end
@@ -99,7 +101,7 @@ function skip(n, f, s, var)
end
-- Return the last n items an iterator returns
-function tail(n, f, s, var)
+function it.tail(n, f, s, var)
local results, count = {}, 0;
while true do
local ret = { f(s, var) };
@@ -121,13 +123,13 @@ function tail(n, f, s, var)
end
local function _range_iter(max, curr) if curr < max then return curr + 1; end end
-function range(x, y)
+function it.range(x, y)
if not y then x, y = 1, x; end -- Default to 1..x if y not given
return _range_iter, y, x-1;
end
-- Convert the values returned by an iterator to an array
-function it2array(f, s, var)
+function it.to_array(f, s, var)
local t, var = {};
while true do
var = f(s, var);
@@ -139,8 +141,8 @@ end
-- Treat the return of an iterator as key,value pairs,
-- and build a table
-function it2table(f, s, var)
- local t, var = {};
+function it.to_table(f, s, var)
+ local t, var2 = {};
while true do
var, var2 = f(s, var);
if var == nil then break; end
@@ -149,3 +151,4 @@ function it2table(f, s, var)
return t;
end
+return it;
diff --git a/util/logger.lua b/util/logger.lua
index c3bf3992..4fadb4b9 100644
--- a/util/logger.lua
+++ b/util/logger.lua
@@ -13,8 +13,7 @@ local ipairs, pairs, setmetatable = ipairs, pairs, setmetatable;
module "logger"
-local name_sinks, level_sinks = {}, {};
-local name_patterns = {};
+local level_sinks = {};
local make_logger;
@@ -46,17 +45,7 @@ function make_logger(source_name, level)
level_sinks[level] = level_handlers;
end
- local source_handlers = name_sinks[source_name];
-
local logger = function (message, ...)
- if source_handlers then
- for i = 1,#source_handlers do
- if source_handlers[i](source_name, level, message, ...) == false then
- return;
- end
- end
- end
-
for i = 1,#level_handlers do
level_handlers[i](source_name, level, message, ...);
end
@@ -66,14 +55,12 @@ function make_logger(source_name, level)
end
function reset()
- for k in pairs(name_sinks) do name_sinks[k] = nil; end
for level, handler_list in pairs(level_sinks) do
-- Clear all handlers for this level
for i = 1, #handler_list do
handler_list[i] = nil;
end
end
- for k in pairs(name_patterns) do name_patterns[k] = nil; end
end
function add_level_sink(level, sink_function)
@@ -84,22 +71,6 @@ function add_level_sink(level, sink_function)
end
end
-function add_name_sink(name, sink_function, exclusive)
- if not name_sinks[name] then
- name_sinks[name] = { sink_function };
- else
- name_sinks[name][#name_sinks[name] + 1] = sink_function;
- end
-end
-
-function add_name_pattern_sink(name_pattern, sink_function, exclusive)
- if not name_patterns[name_pattern] then
- name_patterns[name_pattern] = { sink_function };
- else
- name_patterns[name_pattern][#name_patterns[name_pattern] + 1] = sink_function;
- end
-end
-
_M.new = make_logger;
return _M;
diff --git a/util/prosodyctl.lua b/util/prosodyctl.lua
index 2da39cb2..439de551 100644
--- a/util/prosodyctl.lua
+++ b/util/prosodyctl.lua
@@ -16,6 +16,7 @@ local signal = require "util.signal";
local set = require "util.set";
local lfs = require "lfs";
local pcall = pcall;
+local type = type;
local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep;
@@ -63,6 +64,13 @@ function getchar(n)
end
end
+function getline()
+ local ok, line = pcall(io.read, "*l");
+ if ok then
+ return line;
+ end
+end
+
function getpass()
local stty_ret = os.execute("stty -echo 2>/dev/null");
if stty_ret ~= 0 then
@@ -112,6 +120,13 @@ function read_password()
return password;
end
+function show_prompt(prompt)
+ io.write(prompt, " ");
+ local line = getline();
+ line = line and line:gsub("\n$","");
+ return (line and #line > 0) and line or nil;
+end
+
-- Server control
function adduser(params)
local user, host, password = nodeprep(params.user), nameprep(params.host), params.password;
diff --git a/util/rfc3484.lua b/util/rfc3484.lua
new file mode 100644
index 00000000..dd855a84
--- /dev/null
+++ b/util/rfc3484.lua
@@ -0,0 +1,133 @@
+-- Prosody IM
+-- Copyright (C) 2008-2011 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local commonPrefixLength = require"util.ip".commonPrefixLength
+local new_ip = require"util.ip".new_ip;
+
+local function t_sort(t, comp)
+ for i = 1, (#t - 1) do
+ for j = (i + 1), #t do
+ local a, b = t[i], t[j];
+ if not comp(a,b) then
+ t[i], t[j] = b, a;
+ end
+ end
+ end
+end
+
+function source(dest, candidates)
+ local function comp(ipA, ipB)
+ -- Rule 1: Prefer same address
+ if dest == ipA then
+ return true;
+ elseif dest == ipB then
+ return false;
+ end
+
+ -- Rule 2: Prefer appropriate scope
+ if ipA.scope < ipB.scope then
+ if ipA.scope < dest.scope then
+ return false;
+ else
+ return true;
+ end
+ elseif ipA.scope > ipB.scope then
+ if ipB.scope < dest.scope then
+ return true;
+ else
+ return false;
+ end
+ end
+
+ -- Rule 3: Avoid deprecated addresses
+ -- XXX: No way to determine this
+ -- Rule 4: Prefer home addresses
+ -- XXX: Mobility Address related, no way to determine this
+ -- Rule 5: Prefer outgoing interface
+ -- XXX: Interface to address relation. No way to determine this
+ -- Rule 6: Prefer matching label
+ if ipA.label == dest.label and ipB.label ~= dest.label then
+ return true;
+ elseif ipB.label == dest.label and ipA.label ~= dest.label then
+ return false;
+ end
+
+ -- Rule 7: Prefer public addresses (over temporary ones)
+ -- XXX: No way to determine this
+ -- Rule 8: Use longest matching prefix
+ if commonPrefixLength(ipA, dest) > commonPrefixLength(ipB, dest) then
+ return true;
+ else
+ return false;
+ end
+ end
+
+ t_sort(candidates, comp);
+ return candidates[1];
+end
+
+function destination(candidates, sources)
+ local sourceAddrs = {};
+ local function comp(ipA, ipB)
+ local ipAsource = sourceAddrs[ipA];
+ local ipBsource = sourceAddrs[ipB];
+ -- Rule 1: Avoid unusable destinations
+ -- XXX: No such information
+ -- Rule 2: Prefer matching scope
+ if ipA.scope == ipAsource.scope and ipB.scope ~= ipBsource.scope then
+ return true;
+ elseif ipA.scope ~= ipAsource.scope and ipB.scope == ipBsource.scope then
+ return false;
+ end
+
+ -- Rule 3: Avoid deprecated addresses
+ -- XXX: No way to determine this
+ -- Rule 4: Prefer home addresses
+ -- XXX: Mobility Address related, no way to determine this
+ -- Rule 5: Prefer matching label
+ if ipAsource.label == ipA.label and ipBsource.label ~= ipB.label then
+ return true;
+ elseif ipBsource.label == ipB.label and ipAsource.label ~= ipA.label then
+ return false;
+ end
+
+ -- Rule 6: Prefer higher precedence
+ if ipA.precedence > ipB.precedence then
+ return true;
+ elseif ipA.precedence < ipB.precedence then
+ return false;
+ end
+
+ -- Rule 7: Prefer native transport
+ -- XXX: No way to determine this
+ -- Rule 8: Prefer smaller scope
+ if ipA.scope < ipB.scope then
+ return true;
+ elseif ipA.scope > ipB.scope then
+ return false;
+ end
+
+ -- Rule 9: Use longest matching prefix
+ if commonPrefixLength(ipA, ipAsource) > commonPrefixLength(ipB, ipBsource) then
+ return true;
+ elseif commonPrefixLength(ipA, ipAsource) < commonPrefixLength(ipB, ipBsource) then
+ return false;
+ end
+
+ -- Rule 10: Otherwise, leave order unchanged
+ return true;
+ end
+ for _, ip in ipairs(candidates) do
+ sourceAddrs[ip] = source(ip, sources);
+ end
+
+ t_sort(candidates, comp);
+ return candidates;
+end
+
+return {source = source,
+ destination = destination};
diff --git a/util/set.lua b/util/set.lua
index e4cc2dff..050446ec 100644
--- a/util/set.lua
+++ b/util/set.lua
@@ -82,8 +82,10 @@ function new(list)
end
function set:add_list(list)
- for _, item in ipairs(list) do
- items[item] = true;
+ if list then
+ for _, item in ipairs(list) do
+ items[item] = true;
+ end
end
end
diff --git a/util/stanza.lua b/util/stanza.lua
index de83977f..1449f707 100644
--- a/util/stanza.lua
+++ b/util/stanza.lua
@@ -8,22 +8,16 @@
local t_insert = table.insert;
-local t_concat = table.concat;
local t_remove = table.remove;
local t_concat = table.concat;
local s_format = string.format;
local s_match = string.match;
local tostring = tostring;
local setmetatable = setmetatable;
-local getmetatable = getmetatable;
local pairs = pairs;
local ipairs = ipairs;
local type = type;
-local next = next;
-local print = print;
-local unpack = unpack;
local s_gsub = string.gsub;
-local s_char = string.char;
local s_find = string.find;
local os = os;
@@ -258,11 +252,6 @@ function stanza_mt.get_error(stanza)
return type, condition or "undefined-condition", text;
end
-function stanza_mt.__add(s1, s2)
- return s1:add_direct_child(s2);
-end
-
-
do
local id = 0;
function new_id()
diff --git a/util/template.lua b/util/template.lua
index ebd8be14..5e9b479e 100644
--- a/util/template.lua
+++ b/util/template.lua
@@ -7,6 +7,7 @@ local ipairs = ipairs;
local error = error;
local loadstring = loadstring;
local debug = debug;
+local t_remove = table.remove;
module("template")
@@ -42,7 +43,6 @@ local parse_xml = (function()
stanza:tag(name, attr);
end
function handler:CharacterData(data)
- data = data:gsub("^%s*", ""):gsub("%s*$", "");
stanza:text(data);
end
function handler:EndElement(tagname)
@@ -60,6 +60,19 @@ local parse_xml = (function()
end;
end)();
+local function trim_xml(stanza)
+ for i=#stanza,1,-1 do
+ local child = stanza[i];
+ if child.name then
+ trim_xml(child);
+ else
+ child = child:gsub("^%s*", ""):gsub("%s*$", "");
+ stanza[i] = child;
+ if child == "" then t_remove(stanza, i); end
+ end
+ end
+end
+
local function create_string_string(str)
str = ("%q"):format(str);
str = str:gsub("{([^}]*)}", function(s)
@@ -118,6 +131,7 @@ local template_mt = { __tostring = function(t) return t.name end };
local function create_template(templates, text)
local stanza, err = parse_xml(text);
if not stanza then error(err); end
+ trim_xml(stanza);
local info = debug.getinfo(3, "Sl");
info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)";
diff --git a/util/termcolours.lua b/util/termcolours.lua
index df204688..6ef3b689 100644
--- a/util/termcolours.lua
+++ b/util/termcolours.lua
@@ -9,6 +9,7 @@
local t_concat, t_insert = table.concat, table.insert;
local char, format = string.char, string.format;
+local tonumber = tonumber;
local ipairs = ipairs;
local io_write = io.write;
@@ -34,6 +35,15 @@ local winstylemap = {
["1;31"] = 4+8 -- bold red
}
+local cssmap = {
+ [1] = "font-weight: bold", [2] = "opacity: 0.5", [4] = "text-decoration: underline", [8] = "visibility: hidden",
+ [30] = "color:black", [31] = "color:red", [32]="color:green", [33]="color:#FFD700",
+ [34] = "color:blue", [35] = "color: magenta", [36] = "color:cyan", [37] = "color: white",
+ [40] = "background-color:black", [41] = "background-color:red", [42]="background-color:green",
+ [43]="background-color:yellow", [44] = "background-color:blue", [45] = "background-color: magenta",
+ [46] = "background-color:cyan", [47] = "background-color: white";
+};
+
local fmt_string = char(0x1B).."[%sm%s"..char(0x1B).."[0m";
function getstring(style, text)
if style then
@@ -76,4 +86,17 @@ if windows then
end
end
+local function ansi2css(ansi_codes)
+ if ansi_codes == "0" then return "</span>"; end
+ local css = {};
+ for code in ansi_codes:gmatch("[^;]+") do
+ t_insert(css, cssmap[tonumber(code)]);
+ end
+ return "</span><span style='"..t_concat(css, ";").."'>";
+end
+
+function tohtml(input)
+ return input:gsub("\027%[(.-)m", ansi2css);
+end
+
return _M;
diff --git a/util/timer.lua b/util/timer.lua
index d5b66473..d36fb8c4 100644
--- a/util/timer.lua
+++ b/util/timer.lua
@@ -15,8 +15,7 @@ local math_min = math.min
local math_huge = math.huge
local get_time = require "socket".gettime;
local t_insert = table.insert;
-local t_remove = table.remove;
-local ipairs, pairs = ipairs, pairs;
+local pairs = pairs;
local type = type;
local data = {};
diff --git a/util/x509.lua b/util/x509.lua
index d3c55bb4..f106e6fa 100644
--- a/util/x509.lua
+++ b/util/x509.lua
@@ -21,6 +21,10 @@
local nameprep = require "util.encodings".stringprep.nameprep;
local idna_to_ascii = require "util.encodings".idna.to_ascii;
local log = require "util.logger".init("x509");
+local pairs, ipairs = pairs, ipairs;
+local s_format = string.format;
+local t_insert = table.insert;
+local t_concat = table.concat;
module "x509"
@@ -208,4 +212,109 @@ function verify_identity(host, service, cert)
return false
end
+-- TODO Rename? Split out subroutines?
+-- Also, this is probably openssl specific, what TODO about that?
+function genx509san(hosts, config, certhosts, raw) -- recive config through that or some better way?
+ local function utf8string(s)
+ -- This is how we tell openssl not to encode UTF-8 strings as Latin1
+ return s_format("FORMAT:UTF8,UTF8:%s", s);
+ end
+
+ local function ia5string(s)
+ return s_format("IA5STRING:%s", s);
+ end
+
+ local function dnsname(t, host)
+ t_insert(t.DNS, idna_to_ascii(host));
+ end
+
+ local function srvname(t, host, service)
+ t_insert(t.otherName, s_format("%s;%s", oid_dnssrv, ia5string("_" .. service .."." .. idna_to_ascii(host))));
+ end
+
+ local function xmppAddr(t, host)
+ t_insert(t.otherName, s_format("%s;%s", oid_xmppaddr, utf8string(host)));
+ end
+
+ -----------------------------
+
+ local san = {
+ DNS = {};
+ otherName = {};
+ };
+
+ local sslsanconf = { };
+
+ for i = 1,#certhosts do
+ local certhost = certhosts[i];
+ for name, host in pairs(hosts) do
+ if name == certhost or name:sub(-1-#certhost) == "."..certhost then
+ dnsname(san, name);
+ --print(name .. "#component_module: " .. (config.get(name, "core", "component_module") or "nil"));
+ if config.get(name, "core", "component_module") == nil then
+ srvname(san, name, "xmpp-client");
+ end
+ --print(name .. "#anonymous_login: " .. tostring(config.get(name, "core", "anonymous_login")));
+ if not (config.get(name, "core", "anonymous_login") or
+ config.get(name, "core", "authentication") == "anonymous") then
+ srvname(san, name, "xmpp-server");
+ end
+ xmppAddr(san, name);
+ end
+ end
+ end
+
+ for t, n in pairs(san) do
+ for i = 1,#n do
+ t_insert(sslsanconf, s_format("%s.%d = %s", t, i -1, n[i]));
+ end
+ end
+
+ return raw and sslsanconf or t_concat(sslsanconf, "\n");
+end
+
+function baseconf()
+ return {
+ req = {
+ distinguished_name = "distinguished_name",
+ req_extensions = "v3_extensions",
+ x509_extensions = "v3_extensions",
+ prompt = "no",
+ },
+ distinguished_name = {
+ commonName = "example.com",
+ countryName = "GB",
+ localityName = "The Internet",
+ organizationName = "Your Organisation",
+ organizationalUnitName = "XMPP Department",
+ emailAddress = "xmpp@example.com",
+ },
+ v3_extensions = {
+ basicConstraints = "CA:FALSE",
+ keyUsage = "digitalSignature,keyEncipherment",
+ extendedKeyUsage = "serverAuth,clientAuth",
+ subjectAltName = "@subject_alternative_name",
+ },
+ subject_alternative_name = { },
+ }
+end
+
+function serialize_conf(conf)
+ local s = "";
+ for k, t in pairs(conf) do
+ s = s .. ("[%s]\n"):format(k);
+ if t[1] then
+ for i, v in ipairs(t) do
+ s = s .. ("%s\n"):format(v);
+ end
+ else
+ for k, v in pairs(t) do
+ s = s .. ("%s = %s\n"):format(k, v);
+ end
+ end
+ s = s .. "\n";
+ end
+ return s;
+end
+
return _M;
diff --git a/util/xmppstream.lua b/util/xmppstream.lua
index e5271b72..f1793b4f 100644
--- a/util/xmppstream.lua
+++ b/util/xmppstream.lua
@@ -11,32 +11,25 @@ local lxp = require "lxp";
local st = require "util.stanza";
local stanza_mt = st.stanza_mt;
+local error = error;
local tostring = tostring;
local t_insert = table.insert;
local t_concat = table.concat;
local t_remove = table.remove;
local setmetatable = setmetatable;
-local default_log = require "util.logger".init("xmppstream");
-
-- 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 "xmppstream"
local new_parser = lxp.new;
-local ns_prefixes = {
- ["http://www.w3.org/XML/1998/namespace"] = "xml";
+local xml_namespace = {
+ ["http://www.w3.org/XML/1998/namespace\1lang"] = "xml:lang";
+ ["http://www.w3.org/XML/1998/namespace\1space"] = "xml:space";
+ ["http://www.w3.org/XML/1998/namespace\1base"] = "xml:base";
+ ["http://www.w3.org/XML/1998/namespace\1id"] = "xml:id";
};
local xmlns_streams = "http://etherx.jabber.org/streams";
@@ -50,8 +43,6 @@ _M.ns_pattern = ns_pattern;
function new_sax_handlers(session, stream_callbacks)
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;
@@ -85,17 +76,13 @@ function new_sax_handlers(session, stream_callbacks)
non_streamns_depth = non_streamns_depth + 1;
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
+ local xmlk = xml_namespace[k];
+ if xmlk then
+ attr[xmlk] = attr[k];
+ attr[k] = nil;
end
end
@@ -152,19 +139,9 @@ function new_sax_handlers(session, stream_callbacks)
stanza = t_remove(stack);
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);
+ if cb_streamclosed then
+ cb_streamclosed(session);
end
- stanza, chardata = nil, {};
- stack = {};
end
end
@@ -188,7 +165,6 @@ function new_sax_handlers(session, stream_callbacks)
local function set_session(stream, new_session)
session = new_session;
- log = new_session.log or default_log;
end
return xml_handlers, { reset = reset, set_session = set_session };