Changeset

4684:dc70c4ffb66d

Merge timber->trunk - thanks everyone!
author Matthew Wild <mwild1@gmail.com>
date Tue, 24 Apr 2012 21:59:20 +0100
parents 4529:12621337471f (current diff) 4683:c1374e083c97 (diff)
children 4685:3d90264c7b3d
files core/s2smanager.lua net/connlisteners.lua net/httpclient_listener.lua net/multiplex_listener.lua net/xmppclient_listener.lua net/xmppcomponent_listener.lua net/xmppserver_listener.lua plugins/mod_admin_telnet.lua plugins/mod_bosh.lua plugins/mod_httpserver.lua util/debug.lua
diffstat 47 files changed, 3082 insertions(+), 2254 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Tue Apr 24 03:23:44 2012 +0100
+++ b/Makefile	Tue Apr 24 21:59:20 2012 +0100
@@ -32,12 +32,8 @@
 	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
--- a/core/certmanager.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/core/certmanager.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -35,7 +35,7 @@
 		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);
--- a/core/configmanager.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/core/configmanager.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -41,6 +41,9 @@
 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];
--- a/core/loggingmanager.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/core/loggingmanager.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -48,34 +48,12 @@
 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/moduleapi.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -0,0 +1,326 @@
+-- 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 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 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;
+	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: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_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 self:hook("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: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 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
+
+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
+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
+
+return api;
--- a/core/modulemanager.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/core/modulemanager.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -9,13 +9,10 @@
 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;
@@ -37,7 +34,7 @@
 
 local array, set = require "util.array", 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,15 +42,11 @@
 
 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
@@ -88,24 +81,74 @@
 		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);
+
+--- Private helpers ---
 
-function load(host, module_name, config)
+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 ok, result, module_err = call_module_method(mod, "add_host", host_module_api);
+			if not ok or result == false then return nil, ok and module_err or result; end
+			local host_module = setmetatable({ module = host_module_api }, { __index = mod });
+			host_module.module.environment = host_module;
+			modulemap[host][module_name] = host_module;
+			return host_module;
+		end
 		return nil, "global-module-already-loaded";
 	end
 	
@@ -117,88 +160,44 @@
 	end
 
 	local _log = logger.init(host..":"..module_name);
-	local api_instance = setmetatable({ name = module_name, host = host, path = err, _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
+	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
-			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 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
+			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
+		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 +208,6 @@
 	end
 
 	local saved;
-
 	if module_has_method(mod, "save") then
 		local ok, ret, err = call_module_method(mod, "save");
 		if ok then
@@ -225,8 +223,8 @@
 		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,232 +233,63 @@
 				log("warn", "Error restoring module '%s' from '%s': %s", name, host, err);
 			end
 		end
-		return true;
+	end
+	return ok and mod, err;
+end
+
+--- Public API ---
+
+-- 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 mod, err;
+end
+
+-- 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 ok, err;
 end
 
+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 ok, err;
+end
+
+function get_module(host, name)
+	return modulemap[host] and modulemap[host][name];
+end
+
+function get_modules(host)
+	return modulemap[host];
+end
+
+function is_loaded(host, name)
+	return modulemap[host] and modulemap[host][name] and true;
+end
+
 function module_has_method(module, method)
-	return type(module.module[method]) == "function";
+	return type(rawget(module.module, method)) == "function";
 end
 
 function call_module_method(module, method, ...)
-	if module_has_method(module, method) then
-		local f = module.module[method];
+	local f = rawget(module.module, method);
+	if type(f) == "function" then
 		return pcall(f, ...);
 	else
 		return false, "no-such-method";
 	end
 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
-
-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;
-	end
-	return tostring(value);
-end
-
-function api:get_option_number(name, ...)
-	local value = self:get_option(name, ...);
-	if type(value) == "table" then
-		if #value > 1 then
-			self:log("error", "Config option '%s' does not take a list, using just the first item", name);
-		end
-		value = value[1];
-	end
-	local ret = tonumber(value);
-	if value ~= nil and ret == nil then
-		self:log("error", "Config option '%s' not understood, expecting a number", name);
-	end
-	return ret;
-end
-
-function api:get_option_boolean(name, ...)
-	local value = self:get_option(name, ...);
-	if type(value) == "table" then
-		if #value > 1 then
-			self:log("error", "Config option '%s' does not take a list, using just the first item", name);
-		end
-		value = value[1];
-	end
-	if value == nil then
-		return nil;
-	end
-	local ret = value == true or value == "true" or value == 1 or nil;
-	if ret == nil then
-		ret = (value == false or value == "false" or value == 0);
-		if ret then
-			ret = false;
-		else
-			ret = nil;
-		end
-	end
-	if ret == nil then
-		self:log("error", "Config option '%s' not understood, expecting true/false", name);
-	end
-	return ret;
-end
-
-function api:get_option_array(name, ...)
-	local value = self:get_option(name, ...);
-
-	if value == nil then
-		return nil;
-	end
-	
-	if type(value) ~= "table" then
-		return array{ value }; -- Assume any non-list is a single-item list
-	end
-	
-	return array():append(value); -- Clone
-end
-
-function api:get_option_set(name, ...)
-	local value = self:get_option_array(name, ...);
-	
-	if value == nil then
-		return nil;
-	end
-	
-	return set.new(value);
-end
-
-local t_remove = _G.table.remove;
-local module_items = multitable_new();
-function api:add_item(key, value)
-	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(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;
-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
-
 return _M;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/portmanager.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -0,0 +1,194 @@
+
+local multitable = require "util.multitable";
+local fire_event = prosody.events.fire_event;
+
+--- 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
+
+module("portmanager", package.seeall);
+
+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(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 = set.new(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);
+	
+	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
+			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(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;
--- a/core/s2smanager.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/core/s2smanager.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -9,18 +9,13 @@
 
 
 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, 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 st = require "stanza";
@@ -41,11 +36,12 @@
 
 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;
@@ -55,89 +51,6 @@
 
 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)
@@ -147,496 +60,18 @@
 		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
-				if #srv_hosts == 1 and srv_hosts[1].target == "." then
-					log("debug", to_host.." does not provide a XMPP service");
-					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 = 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.ip_hosts then
-		return 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 try_connect(host_session, connect_host, connect_port);
-end
-
-function 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= make_connect(host_session, ip.ip, ip.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
-end
-
-function 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;
-
-		if not sources then
-			sources =  {};
-			local cfg_sources = config.get("*", "core", "interface") or connlisteners_get("xmppserver").default_interface;
-			if type(cfg_sources) == "string" then
-				cfg_sources = { cfg_sources };
-			end
-			for i, source in ipairs(cfg_sources) do
-				if source == "*" then
-					sources[i] = new_ip("0.0.0.0", "IPv4");
-				else
-					sources[i] = new_ip(source, (source:find(":") and "IPv6") or "IPv4");
-				end
-			end
-		end
-
-		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;
-					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 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
-			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;
-					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 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
-			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
-		try_next_ip(host_session);
-	else
-		host_session.ip_hosts = nil;
-		if not 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 "";
-			destroy_session(host_session, "Connecting failed"..err); -- End of the line, we can't
-			return false;
-		end
-	end
-
-	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.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();
-	elseif socket.tcp6 then
-		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
-	
-	local cl = connlisteners_get("xmppserver");
-	conn = wrapclient(conn, connect_host.addr, 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)
@@ -678,10 +113,7 @@
 	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);
@@ -701,6 +133,7 @@
 			session.sendq = nil;
 		end
 		
+		session.ip_hosts = nil;
 		session.srv_hosts = nil;
 	end
 end
@@ -738,7 +171,7 @@
 	
 	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
--- a/core/sessionmanager.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/core/sessionmanager.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -6,11 +6,8 @@
 -- 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;
@@ -19,12 +16,11 @@
 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;
@@ -33,8 +29,6 @@
 
 local st = require "util.stanza";
 
-local c2s_timeout = config_get("*", "core", "c2s_timeout");
-
 local newproxy = newproxy;
 local getmetatable = getmetatable;
 
@@ -67,14 +61,6 @@
 	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
@@ -216,50 +202,6 @@
 	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;
--- a/core/stanza_router.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/core/stanza_router.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -11,7 +11,6 @@
 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 @@
 		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 @@
 	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
--- a/net/connlisteners.lua	Tue Apr 24 03:23:44 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +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 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;
-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
-
-return _M;
--- a/net/http.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/net/http.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -13,9 +13,6 @@
 
 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 @@
 
 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 @@
 	req.reader = request_reader;
 	req.state = "status";
 
-	listener.register_request(req.handler, req);
-
+	requests[req.handler] = req;
 	return req;
 end
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/net/http/codes.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -0,0 +1,66 @@
+
+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";
+	[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 })
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/net/http/parser.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -0,0 +1,116 @@
+
+local tonumber = tonumber;
+local assert = assert;
+
+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
+								path = path:gsub("^//+", "/"); -- TODO parse url more
+							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
+						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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/net/http/server.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -0,0 +1,283 @@
+
+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 _G = _G;
+local legacy_httpserver = require "net.httpserver";
+
+local _M = {};
+
+local sessions = {};
+local handlers = {};
+
+local listener = {};
+
+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[event]
+		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"))), priority };
+					table.insert(handlers_array, handler);
+				end
+			end
+		end
+		if #handlers_array == 0 then return; end
+		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 ~= b then -- If equal, compare next score value
+					return a_score[i] < b_score[i];
+				end
+			end
+			return false;
+		end);
+		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 all matching
+			for event in pairs(handlers) do
+				if is_wildcard_match(curr_event, event) then
+					handlers[event] = nil;
+				end
+			end
+		end
+	end;
+});
+
+local handle_request;
+local _1, _2, _3;
+local function _handle_request() return handle_request(_1, _2, _3); end
+local function _traceback_handler(err) log("error", "Traceback[http]: %s: %s", tostring(err), debug.traceback()); end
+
+function listener.onconnect(conn)
+	local secure = conn:ssl() and true or nil;
+	local pending = {};
+	local waiting = false;
+	local function process_next(last_response)
+		--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 503 Internal Server Error\r\n\r\nAn error occured during the processing of this request.");
+				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)
+	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;
+	};
+
+	if not request.headers.host then
+		response.status_code = 400;
+		response.headers.content_type = "text/html";
+		response:send("<html><head>400 Bad Request</head><body>400 Bad Request: No Host header.</body></html>");
+	else
+		-- TODO call handler
+		--response.headers.content_type = "text/plain";
+		--response:send("host="..(request.headers.host or "").."\npath="..request.path.."\n"..(request.body or ""));
+		local host = request.headers.host;
+		if host then
+			host = host:match("[^:]*"):lower();
+			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 code, body = 200, "";
+					local result_type = type(result);
+					if result_type == "number" then
+						response.status_code = result;
+					elseif result_type == "string" then
+						body = result;
+					elseif result_type == "table" then
+						body = result.body;
+						result.body = nil;
+						for k, v in pairs(result) do
+							response[k] = v;
+						end
+					end
+					response:send(body);
+				end
+				return;
+			end
+		end
+
+		-- if handler not called, fallback to legacy httpserver handlers
+		_M.legacy_handler(request, response);
+	end
+end
+function _M.send_response(response, body)
+	local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
+	local headers = response.headers;
+	body = 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 headers.connection == "Keep-Alive" then
+		response:finish_cb();
+	else
+		response.conn:close();
+	end
+end
+function _M.legacy_handler(request, response)
+	log("debug", "Invoking legacy handler");
+	local base = request.path:match("^/([^/?]+)");
+	local legacy_server = legacy_httpserver and legacy_httpserver.new.http_servers[5280];
+	local handler = legacy_server and legacy_server.handlers[base];
+	if not handler then handler = legacy_httpserver and legacy_httpserver.set_default_handler.default_handler; end
+	if handler then
+		-- add legacy properties to request object
+		request.url = { path = request.path };
+		request.handler = response.conn;
+		request.id = tostring{}:match("%x+$");
+		local headers = {};
+		for k,v in pairs(request.headers) do
+			headers[k:gsub("_", "-")] = v;
+		end
+		request.headers = headers;
+		function request:send(resp)
+			if self.destroyed then return; end
+			if resp.body or resp.headers then
+				if resp.headers then
+					for k,v in pairs(resp.headers) do response.headers[k] = v; end
+				end
+				response:send(resp.body)
+			else
+				response:send(resp)
+			end
+			self.sent = true;
+			self:destroy();
+		end
+		function request:destroy()
+			if self.destroyed then return; end
+			if not self.sent then return self:send(""); end
+			self.destroyed = true;
+			if self.on_destroy then
+				log("debug", "Request has destroy callback");
+				self:on_destroy();
+			else
+				log("debug", "Request has no destroy callback");
+			end
+		end
+		local r = handler(request.method, request.body, request);
+		if r ~= true then
+			request:send(r);
+		end
+	else
+		log("debug", "No handler found");
+		response.status_code = 404;
+		response.headers.content_type = "text/html";
+		response:send("<html><head><title>404 Not Found</title></head><body>404 Not Found: No such page.</body></html>");
+	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
+
+_M.listener = listener;
+_M.codes = codes;
+return _M;
--- a/net/httpclient_listener.lua	Tue Apr 24 03:23:44 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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);
--- a/net/httpserver.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/net/httpserver.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -10,8 +10,8 @@
 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 connlisteners_start = require "net.connlisteners".start;
+--local connlisteners_get = require "net.connlisteners".get;
 local listener;
 
 local t_insert, t_concat = table.insert, table.concat;
@@ -164,7 +164,7 @@
 
 function destroy_request(request)
 	log("debug", "Destroying request %s", request.id);
-	listener = listener or connlisteners_get("httpserver");
+	--listener = listener or connlisteners_get("httpserver");
 	if not request.destroyed then
 		request.destroyed = true;
 		if request.on_destroy then
@@ -186,7 +186,7 @@
 		http_server = { handlers = {} };
 		http_servers[params.port] = http_server;
 		-- We weren't already listening on this port, so start now
-		connlisteners_start("httpserver", params);
+		--connlisteners_start("httpserver", params);
 	end
 	if params.base then
 		http_server.handlers[params.base] = params.handler;
--- a/net/multiplex_listener.lua	Tue Apr 24 03:23:44 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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);
--- a/net/server_event.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/net/server_event.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -343,24 +343,11 @@
 				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
 	
--- a/net/server_select.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/net/server_select.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -202,6 +202,7 @@
 		socket:close( )
 		_sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
 		_readlistlen = removesocket( _readlist, socket, _readlistlen )
+		_server[ip..":"..serverport] = nil;
 		_socketlist[ socket ] = nil
 		handler = nil
 		socket = nil
--- a/net/xmppclient_listener.lua	Tue Apr 24 03:23:44 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,179 +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 logger = require "logger";
-local log = logger.init("xmppclient_listener");
-local new_xmpp_stream = require "util.xmppstream".new;
-
-local connlisteners_register = require "net.connlisteners".register;
-
-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 traceback = debug.traceback;
-
-local config = require "core.configmanager";
-local opt_keepalives = config.get("*", "core", "tcp_keepalives");
-
-local stream_callbacks = { default_ns = "jabber:client",
-		streamopened = sm_streamopened, streamclosed = sm_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.log("debug", "Invalid opening stream header");
-		session:close("invalid-namespace");
-	elseif error == "parse-error" then
-		(session.log or log)("debug", "Client XML parse error: %s", 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
-
-local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end
-function stream_callbacks.handlestanza(session, stanza)
-	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 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 = "" };
-local function session_close(session, reason)
-	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 client, <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 client, <stream:error> is: %s", tostring(stanza));
-					session.send(stanza);
-				elseif reason.name then -- a stanza
-					log("info", "Disconnecting client, <stream:error> is: %s", tostring(reason));
-					session.send(reason);
-				end
-			end
-		end
-		session.send("</stream:stream>");
-		session.conn:close();
-		xmppclient.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)
-	local session = sm_new_session(conn);
-	sessions[conn] = session;
-	
-	session.log("info", "Client connected");
-	
-	-- Client is using legacy SSL (otherwise mod_tls sets this flag)
-	if conn:ssl() then
-		session.secure = true;
-	end
-	
-	if opt_keepalives ~= nil then
-		conn:setoption("keepalive", opt_keepalives);
-	end
-	
-	session.close = session_close;
-	
-	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
-			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
-	end
-	
-	local handlestanza = stream_callbacks.handlestanza;
-	function session.dispatch_stanza(session, stanza)
-		return handlestanza(session, stanza);
-	end
-end
-
-function xmppclient.onincoming(conn, data)
-	local session = sessions[conn];
-	if session then
-		session.data(data);
-	end
-end
-	
-function xmppclient.ondisconnect(conn, err)
-	local session = sessions[conn];
-	if session then
-		(session.log or log)("info", "Client disconnected: %s", err);
-		sm_destroy_session(session, err);
-		sessions[conn]  = nil;
-		session = nil;
-	end
-end
-
-function xmppclient.associate_session(conn, session)
-	sessions[conn] = session;
-end
-
-connlisteners_register("xmppclient", xmppclient);
--- a/net/xmppcomponent_listener.lua	Tue Apr 24 03:23:44 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,218 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-local t_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);
--- a/net/xmppserver_listener.lua	Tue Apr 24 03:23:44 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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", default_interface = "*" };
-
--- 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.type == "s2sout_unauthed" 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
--- a/plugins/adhoc/adhoc.lib.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/plugins/adhoc/adhoc.lib.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -25,6 +25,7 @@
 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 @@
 		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 @@
 						'" 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 @@
 			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);
 
--- a/plugins/mod_admin_adhoc.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/plugins/mod_admin_adhoc.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -10,7 +10,8 @@
 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 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)
--- a/plugins/mod_admin_telnet.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/plugins/mod_admin_telnet.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -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"; interface = "127.0.0.1" };
 
-local console_listener = { default_port = 5582; default_mode = "*l"; default_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 @@
 	end
 end
 
-connlisteners_register('console', console_listener);
-
 -- Console commands --
 -- These are simple commands, not valid standalone in Lua
 
@@ -281,8 +277,12 @@
 		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 @@
 	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 @@
 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 @@
 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 @@
 	
 	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");
@@ -766,6 +777,51 @@
 	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 +852,9 @@
 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;
+});
--- a/plugins/mod_auth_internal_hashed.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/plugins/mod_auth_internal_hashed.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -54,7 +54,7 @@
 
 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 {};
--- a/plugins/mod_auth_internal_plain.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/plugins/mod_auth_internal_plain.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -23,7 +23,7 @@
 
 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);
--- a/plugins/mod_bosh.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/plugins/mod_bosh.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -6,7 +6,7 @@
 -- COPYING file in the source package for more information.
 --
 
-module.host = "*" -- Global module
+module:set_global(); -- Global module
 
 local hosts = _G.hosts;
 local new_xmpp_stream = require "util.xmppstream".new;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_c2s.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -0,0 +1,242 @@
+-- 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 add_task = require "util.timer".add_task;
+local new_xmpp_stream = require "util.xmppstream".new;
+local nameprep = require "util.encodings".stringprep.nameprep;
+local portmanager = require "core.portmanager";
+local sessionmanager = require "core.sessionmanager";
+local st = require "util.stanza";
+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 xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
+
+local log = module._log;
+
+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
+		session.log("debug", "Invalid opening stream header");
+		session:close("invalid-namespace");
+	elseif error == "parse-error" then
+		(session.log or log)("debug", "Client XML parse error: %s", 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
+
+local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end
+function stream_callbacks.handlestanza(session, stanza)
+	stanza = session.filter("stanzas/in", stanza);
+	if stanza then
+		return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+	end
+end
+
+--- Session methods
+local function session_close(session, reason)
+	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 client, <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 client, <stream:error> is: %s", tostring(stanza));
+					session.send(stanza);
+				elseif reason.name then -- a stanza
+					log("info", "Disconnecting client, <stream:error> is: %s", tostring(reason));
+					session.send(reason);
+				end
+			end
+		end
+		session.send("</stream:stream>");
+		session.conn:close();
+		listener.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
+	end
+end
+
+--- Port listener
+function listener.onconnect(conn)
+	local session = sm_new_session(conn);
+	sessions[conn] = session;
+	
+	session.log("info", "Client connected");
+	
+	-- Client is using legacy SSL (otherwise mod_tls sets this flag)
+	if conn:ssl() then
+		session.secure = true;
+	end
+	
+	if opt_keepalives then
+		conn:setoption("keepalive", opt_keepalives);
+	end
+	
+	session.close = session_close;
+	
+	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
+			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
+	end
+
+	
+	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 listener.onincoming(conn, data)
+	local session = sessions[conn];
+	if session then
+		session.data(data);
+	end
+end
+
+function listener.ondisconnect(conn, err)
+	local session = sessions[conn];
+	if session then
+		(session.log or log)("info", "Client disconnected: %s", err);
+		sm_destroy_session(session, err);
+		sessions[conn]  = nil;
+		session = nil;
+	end
+end
+
+function listener.associate_session(conn, session)
+	sessions[conn] = session;
+end
+
+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.*>";
+	};
+});
+
+
--- a/plugins/mod_component.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/plugins/mod_component.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -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
+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
-		connected = false;
-		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" };
+		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
+
+--- 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
 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"));
+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
+
+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; -- 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
-	return true;
+	return core_process_stanza(session, stanza);
 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);
+--- 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
+				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
 
---- Handle authentication attempts by components
-function handle_component_auth(event)
-	local session, stanza = event.origin, event.stanza;
+--- 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;
+	
+	session.log("info", "Incoming Jabber component connection");
 	
-	if session.type ~= "component" then return; end
-	if main_session == session then return; end
+	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
 
-	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 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
 	
-	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
-		log("info", "Component authentication failed for %s", session.host);
-		session:close{ condition = "not-authorized", text = "Given token does not match calculated token" };
-		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
-	
-	-- If component not already created for this host, create one now
-	if not main_session then
-		connected = true;
-		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" };
-	end
-	
-	return true;
 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.*>";
+	};
+});
--- a/plugins/mod_dialback.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/plugins/mod_dialback.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -6,22 +6,36 @@
 -- 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 +46,7 @@
 		-- 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 +86,7 @@
 		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 +97,7 @@
 	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 +148,25 @@
 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();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_http.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -0,0 +1,98 @@
+-- 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();
+
+local parse_url = require "socket.url".parse;
+local server = require "net.http.server";
+
+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
+		if key:sub(1,1) ~= "/" then
+			return nil;
+		end
+		method, path = "GET", key;
+	end
+	path = normalize_path(path);
+	return method:upper().." "..host..app_path..path;
+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(module:get_option_string(app_name.."_http_path", 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 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;
+					server.add_handler(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", 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
+			server.remove_handler(event, handler);
+		end
+	end
+	
+	module:handle_items("http-provider", http_app_added, http_app_removed);
+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]";
+	};
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_http_files.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -0,0 +1,86 @@
+-- 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_path", "www_files");
+
+local response_400 = "<h1>Bad Request</h1>Sorry, we didn't understand your request :(";
+local response_403 = "<h1>Forbidden</h1>You don't have permission to view the contents of this directory :(";
+local response_404 = "<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(event, path)
+	local response = event.response;
+	path = path and preprocess_path(path);
+	if not path then
+		response.status = 400;
+		return response:send(response_400);
+	end
+	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
+		response.status = 403;
+		return response:send(response_403);
+	end
+	local f, err = open(full_path, "rb");
+	if not f then
+		response.status = 404;
+		return response:send(response_404.."<br/>"..tostring(err));
+	end
+	local data = f:read("*a");
+	f:close();
+	if not data then
+		response.status = 403;
+		return response:send(response_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 = {
+		["/*"] = serve_file;
+	};
+});
+
--- a/plugins/mod_httpserver.lua	Tue Apr 24 03:23:44 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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
--- a/plugins/mod_motd.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/plugins/mod_motd.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -13,17 +13,18 @@
 
 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);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_net_multiplex.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -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;
+});
--- a/plugins/mod_posix.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/plugins/mod_posix.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -22,7 +22,7 @@
 
 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);
--- a/plugins/mod_proxy65.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/plugins/mod_proxy65.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -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 b64 = require "util.encodings".base64.encode;
 local server = require "net.server";
-local b64 = require "util.encodings".base64.encode;
 
-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 @@
 	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,90 @@
 	end
 end
 
-module:add_identity("proxy", "bytestreams", name);
-module:add_feature("http://jabber.org/protocol/bytestreams");
+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]));
+	local proxy_acl = module:get_option("proxy65_acl");
 
-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);
-
-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);
+	
+	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
 	
-	-- 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"));
+		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
+		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);
 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
-	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.");
-end
-
-connlisteners.start(module.host .. ':proxy65');
+module:provides("net", {
+	default_port = 5000;
+	listener = listener;
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/s2s/mod_s2s.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -0,0 +1,477 @@
+-- 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 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 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");
+
+--- 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", 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
+
+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", get_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");
+			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
+			local to_host, from_host = session.to_host, session.from_host;
+			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.*>";
+	};
+});
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/s2s/s2sout.lib.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -0,0 +1,346 @@
+-- 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 add_task = require "util.timer".add_task;
+local new_ip = require "util.ip".new_ip;
+local rfc3484_dest = require "util.rfc3484".destination;
+local socket = require "socket";
+local t_insert, t_sort, ipairs = table.insert, table.sort, ipairs;
+local st = require "util.stanza";
+
+local s2s_new_outgoing = require "core.s2smanager".new_outgoing;
+local s2s_destroy_session = require "core.s2smanager".destroy_session;
+
+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", 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
+		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;
--- a/prosody	Tue Apr 24 03:23:44 2012 +0100
+++ b/prosody	Tue Apr 24 21:59:20 2012 +0100
@@ -150,10 +150,14 @@
 		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 @@
 	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 @@
 	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 @@
 	if remdebug then remdebug.engine.start() end
 	]]
 
-	require "net.connlisteners";
-	require "net.httpserver";
-	
 	require "util.stanza"
 	require "util.jid"
 end
@@ -374,20 +319,6 @@
 	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	
 
--- a/util/debug.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/util/debug.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -22,8 +22,9 @@
 		location         = _("yellow");
 	};
 end
+module("debugx", package.seeall);
 
-local function get_locals_table(level)
+function get_locals_table(level)
 	level = level + 1; -- Skip this function itself
 	local locals = {};
 	for local_num = 1, math.huge do
@@ -34,7 +35,7 @@
 	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
@@ -46,7 +47,7 @@
 	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;
@@ -103,8 +104,8 @@
 	return levels;
 end
 
-function debug.traceback(...)
-	local ok, ret = pcall(debug._traceback, ...);
+function traceback(...)
+	local ok, ret = pcall(_traceback, ...);
 	if not ok then
 		return "Error in error handling: "..ret;
 	end
@@ -116,7 +117,8 @@
 	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 debug._traceback(thread, message, level)
+function _traceback(thread, message, level)
+
 	if type(thread) ~= "thread" then
 		thread, message, level = coroutine.running(), thread, message;
 	end
@@ -178,3 +180,9 @@
 
 	return message.."stack traceback:\n"..table.concat(lines, "\n");
 end
+
+function use()
+	debug.traceback = traceback;
+end
+
+return _M;
--- a/util/helpers.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/util/helpers.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -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,34 @@
 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)
+	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];
+		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
+	table.sort(events_array);
+	local i = 1;
+	repeat
+		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
+	until i == #events_array;
+	return table.concat(events_array, "\n");
 end
 
 function get_upvalue(f, get_name)
--- a/util/iterators.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/util/iterators.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -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 @@
 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 @@
 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 @@
 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 @@
 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 @@
 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 @@
 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 @@
 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,7 +141,7 @@
 
 -- Treat the return of an iterator as key,value pairs,
 -- and build a table
-function it2table(f, s, var)
+function it.to_table(f, s, var)
 	local t, var2 = {};
 	while true do
 		var, var2 = f(s, var);
@@ -149,3 +151,4 @@
 	return t;
 end
 
+return it;
--- a/util/logger.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/util/logger.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -13,8 +13,7 @@
 
 module "logger"
 
-local name_sinks, level_sinks = {}, {};
-local name_patterns = {};
+local level_sinks = {};
 
 local make_logger;
 
@@ -46,17 +45,7 @@
 		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 @@
 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 @@
 	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;
--- a/util/set.lua	Tue Apr 24 03:23:44 2012 +0100
+++ b/util/set.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -82,8 +82,10 @@
 	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