Merge with sasl branch.

Mon, 16 Nov 2009 21:43:57 +0100

author
Tobias Markmann <tm@ayena.de>
date
Mon, 16 Nov 2009 21:43:57 +0100
changeset 2189
8fbbdb11a520
parent 2080
ca419b92a8c7 (diff)
parent 2188
fa46d158c95f (current diff)
child 2190
41d42d253a1d

Merge with sasl branch.

plugins/mod_saslauth.lua file | annotate | diff | comparison | revisions
util/sasl.lua file | annotate | diff | comparison | revisions
util/sasl/anonymous.lua file | annotate | diff | comparison | revisions
util/sasl/digest-md5.lua file | annotate | diff | comparison | revisions
--- a/.hgtags	Fri Nov 13 14:31:03 2009 +0100
+++ b/.hgtags	Mon Nov 16 21:43:57 2009 +0100
@@ -27,3 +27,10 @@
 6b91a2b39680f0f7195fec561a3ce3d660ea0c20 0.4.2
 06030af44faddd7d3c20e7d380bb55b92b079c5f 0.5.0
 a63ff2fbba8d0a29503e7552a29fc7c831faa23c 0.5.1
+cea841708dd462c7634df68e84429402f6cdf71d 0.5.2
+cea841708dd462c7634df68e84429402f6cdf71d 0.5.2
+7c45ae42923a1ef03406e481af2a4a6f356361b8 0.5.2
+7c45ae42923a1ef03406e481af2a4a6f356361b8 0.5.2
+e32593074602a785d152f9e153825f29db4d0973 0.5.2
+e32593074602a785d152f9e153825f29db4d0973 0.5.2
+5ae3209fefa2c8dc1c53d08c2c1caa340b8ec542 0.5.2
--- a/Makefile	Fri Nov 13 14:31:03 2009 +0100
+++ b/Makefile	Mon Nov 16 21:43:57 2009 +0100
@@ -28,9 +28,11 @@
 	install -m644 net/* $(SOURCE)/net
 	install -m644 util/* $(SOURCE)/util
 	install -m644 fallbacks/* $(SOURCE)/fallbacks
-	install -m644 plugins/* $(MODULES)
+	install -m644 plugins/*.lua $(MODULES)
+	install -d $(MODULES)/muc
+	install -m644 plugins/muc/* $(MODULES)/muc
 	install -m644 certs/* $(CONFIG)/certs
-	install -m644 plugins/* $(MODULES)
+	install -m644 plugins/*.lua $(MODULES)
 	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
@@ -71,7 +73,7 @@
 	sed 's|certs/|$(INSTALLEDCONFIG)/certs/|' prosody.cfg.lua.dist > prosody.cfg.lua.install
 
 prosody.release:
-	test -e .hg/dirstate && hexdump -n6 -e'6/1 "%01x"' .hg/dirstate \
+	test -e .hg/dirstate && hexdump -n6 -e'6/1 "%02x"' .hg/dirstate \
 	    > prosody.version || true
 
 prosody.version: prosody.release
--- a/certs/localhost.cert	Fri Nov 13 14:31:03 2009 +0100
+++ b/certs/localhost.cert	Mon Nov 16 21:43:57 2009 +0100
@@ -1,22 +1,22 @@
 -----BEGIN CERTIFICATE-----
-MIIDkDCCAvmgAwIBAgIJAO6CeZTVrfDwMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYD
-VQQGEwJHQjETMBEGA1UECBMKU29tZS1TdGF0ZTETMBEGA1UEBxMKSmFiYmVybGFu
-ZDETMBEGA1UEChMKUHJvc29keSBJTTEcMBoGA1UEAxMTRXhhbXBsZSBjZXJ0aWZp
-Y2F0ZTEhMB8GCSqGSIb3DQEJARYScHJvc29keUBwcm9zb2R5LmltMB4XDTA4MTEy
-OTE3MTQyNFoXDTA5MTEyOTE3MTQyNFowgY0xCzAJBgNVBAYTAkdCMRMwEQYDVQQI
-EwpTb21lLVN0YXRlMRMwEQYDVQQHEwpKYWJiZXJsYW5kMRMwEQYDVQQKEwpQcm9z
-b2R5IElNMRwwGgYDVQQDExNFeGFtcGxlIGNlcnRpZmljYXRlMSEwHwYJKoZIhvcN
-AQkBFhJwcm9zb2R5QHByb3NvZHkuaW0wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
-AoGBALVAPZ/hONuU5P1okNPNfE/bSDj3AsOrRb+Kj4a7MPyRzVCARAm5KvCkPwI3
-zfDoemp6PpjVk+K8buYTKD+FT3ZxHu8mVHOnnDid/Z3KjxXOh0q1fnzKCCWH49Lu
-hKz7AtAXxvyGvTqTrfquxYVu3U4jxNIVdy//8K0+qPt69aJTAgMBAAGjgfUwgfIw
-HQYDVR0OBBYEFA7Ehhe9zSpASafg6MXFXjAA5jTcMIHCBgNVHSMEgbowgbeAFA7E
-hhe9zSpASafg6MXFXjAA5jTcoYGTpIGQMIGNMQswCQYDVQQGEwJHQjETMBEGA1UE
-CBMKU29tZS1TdGF0ZTETMBEGA1UEBxMKSmFiYmVybGFuZDETMBEGA1UEChMKUHJv
-c29keSBJTTEcMBoGA1UEAxMTRXhhbXBsZSBjZXJ0aWZpY2F0ZTEhMB8GCSqGSIb3
-DQEJARYScHJvc29keUBwcm9zb2R5LmltggkA7oJ5lNWt8PAwDAYDVR0TBAUwAwEB
-/zANBgkqhkiG9w0BAQUFAAOBgQBCYiXpGULtMCsIi/yo3NxdeC7SjgsY8KKxxkB9
-VynZpC+R6+BMtEloOgl0uvjnGy1cu7l2ddQBN4NxpZjezo9KQjRjJxXSBgMKglXH
-ybsPjB5b61zmCnr/uvjuthRCVuHfcVD0wptoHkb1VDd+lQT1/+QQCm1hlDbgb8NI
-nfxA7A==
+MIIDojCCAwugAwIBAgIJAPO1OI+vmUi8MA0GCSqGSIb3DQEBBQUAMIGTMQswCQYD
+VQQGEwJHQjETMBEGA1UECBMKSmFiYmVybGFuZDETMBEGA1UEChMKUHJvc29keSBJ
+TTE8MDoGA1UECxQzaHR0cDovL3Byb3NvZHkuaW0vZG9jL2FkdmFuY2VkX3NzbF90
+bHMjY2VydGlmaWNhdGVzMRwwGgYDVQQDExNFeGFtcGxlIGNlcnRpZmljYXRlMB4X
+DTA5MTAxNzE3MDc1NloXDTEwMTAxNzE3MDc1NlowgZMxCzAJBgNVBAYTAkdCMRMw
+EQYDVQQIEwpKYWJiZXJsYW5kMRMwEQYDVQQKEwpQcm9zb2R5IElNMTwwOgYDVQQL
+FDNodHRwOi8vcHJvc29keS5pbS9kb2MvYWR2YW5jZWRfc3NsX3RscyNjZXJ0aWZp
+Y2F0ZXMxHDAaBgNVBAMTE0V4YW1wbGUgY2VydGlmaWNhdGUwgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBAN5n5y7+A7V6WZ5n/+n4eqjHiQ+p0XD1BYA2435AgzKE
+R+ilmrCFv59aWVIi3jS0YB3goMmuSk8PLv8pi/rjEKYhzDoiuoW/LvzjK5pVzbFM
+NlkW5I0t4Lrjb2lMkxbQr/B/k07RDlJJJRTmr2j4N7vMoznVFbjQY6dRAv3svYZF
+AgMBAAGjgfswgfgwHQYDVR0OBBYEFJhMTxNc3LEYA1vm3v4sCdHzRnUDMIHIBgNV
+HSMEgcAwgb2AFJhMTxNc3LEYA1vm3v4sCdHzRnUDoYGZpIGWMIGTMQswCQYDVQQG
+EwJHQjETMBEGA1UECBMKSmFiYmVybGFuZDETMBEGA1UEChMKUHJvc29keSBJTTE8
+MDoGA1UECxQzaHR0cDovL3Byb3NvZHkuaW0vZG9jL2FkdmFuY2VkX3NzbF90bHMj
+Y2VydGlmaWNhdGVzMRwwGgYDVQQDExNFeGFtcGxlIGNlcnRpZmljYXRlggkA87U4
+j6+ZSLwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCtLrTOSpQn+j+/
+5zoiP5wAGLpdZE+Iatzd26QwVsL61zd5399nEb1yFs3Hl9jo4W3idyNoofa67atX
+2/+3juA0Q/oN/ZT16bWihmcrzv+Qd/CsQfMOZ5ApYV4SEw40L6GITtrZuBDjO4mU
+TavhtScoGRzrZavhJG+PyhDH0Scglg==
 -----END CERTIFICATE-----
--- a/certs/localhost.key	Fri Nov 13 14:31:03 2009 +0100
+++ b/certs/localhost.key	Mon Nov 16 21:43:57 2009 +0100
@@ -1,15 +1,15 @@
 -----BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQC1QD2f4TjblOT9aJDTzXxP20g49wLDq0W/io+GuzD8kc1QgEQJ
-uSrwpD8CN83w6Hpqej6Y1ZPivG7mEyg/hU92cR7vJlRzp5w4nf2dyo8VzodKtX58
-ygglh+PS7oSs+wLQF8b8hr06k636rsWFbt1OI8TSFXcv//CtPqj7evWiUwIDAQAB
-AoGAbk0w83oxite630hiUrMLguGUuy3/Xap+YMlm/PwwHJRyWRolzbEFI7sgqS3i
-w0gHL4NDUuku/V3lM1jXNojfSNOq2T+M8L7G8q5e+Ch89RKiJvqKPqBsxu5bEL4m
-lyJi+Vt0SXUqJkxBHWLRJb8W6++aM2ByZ7CKDyjomg5fplkCQQDhnMMIyVSKM7a1
-VTbUbeqfcJmyDRaCkbA5X7NsEtatrEWusulFtPExCUUdpgFACJYj35PhCqLzmCpJ
-MxKL8zGdAkEAzanffEouT1eDlqdfLc/LVcKj3QTMmLck9KP0AhRy0vaiCqkYE/tE
-M+l9HTwxGmveLngfuw8p0HdztUFO6lAYrwJBAJhpHzRjVfIa51XuoCC3tGVLWvj2
-cHt6UhMgPIRI4a/njhdrk7zcdIeM3J0f1P5eDpdjZXIEjnqDFCXpE6Fpg90CQC1l
-a8FBlotI4/DjLO0tytI5TnZA0vB6rJubfQbggJ/0dLwpqvjuI5XZ2hYT7TrJyJc1
-SLu/kxlC5LWDnum1mF0CQDHt9x7DnGLquBhRUzcKmFcmaYsVl37A9tAfQSnrGqq+
-GBc3K1k0bhYc1/I1Ym1PfVCfLENXhhA0hHmaYviHF6U=
+MIICWwIBAAKBgQDeZ+cu/gO1elmeZ//p+Hqox4kPqdFw9QWANuN+QIMyhEfopZqw
+hb+fWllSIt40tGAd4KDJrkpPDy7/KYv64xCmIcw6IrqFvy784yuaVc2xTDZZFuSN
+LeC6429pTJMW0K/wf5NO0Q5SSSUU5q9o+De7zKM51RW40GOnUQL97L2GRQIDAQAB
+AoGAYaWw5Pr12en8CwaSX8GO6SeiT9Q5dqS9Y4u12iqs77MQd16uSi6O8YITkXJp
+qS5AvR1wutvhGFEMS0+Me/zRw62OFc2VVrKmX6eqgRMR8d/+SZjqzUxb4pNIAPQU
+dHbQzqGXermf6UWm6Cbi7vN0diohd8Qoj98PeWfRQrXju0kCQQD3OXD2SEevEhNe
+g4YTREsyUkZV1etkldhAeDAJzlitCQdQF5zE9Wt/Ahv0BKlLTaz3mvSDwrI+lXYQ
+1iDzOrXrAkEA5kzu1A3Y2gclyRupTg7crgp+afh1fLKCIVUaFdOYgwQDX90YnnIq
+TaY4uQ8Eutoixha4ZM4/bJq17YjjY1O4jwJAZMEHNYftlv7h3/HwMWfy0XZQbej5
+vwuGj3er9EMhRpvYXB7TaD2w6pkcdU11BViJtntzTUOKyxC0hlYOJbJ2swJAOL3N
+vhtnSVine6RAE4Zf4tWdDdj0gXOt0i6YjbYjhmwvtKfR0AAK4jTJFvdXT/48wReJ
++PRD9issFck7VRakiwJAPTgFUTsFCR1ZPcuCPHSCK/wz2NFma/O5Eqm0qTIbNUfw
+3qDRyUuKbyr3bAc+K+asN5ok2PAnhiRUIpu146M17w==
 -----END RSA PRIVATE KEY-----
--- a/core/componentmanager.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/core/componentmanager.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,18 +6,15 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
-local prosody = prosody;
+local prosody = _G.prosody;
 local log = require "util.logger".init("componentmanager");
 local configmanager = require "core.configmanager";
 local modulemanager = require "core.modulemanager";
-local core_route_stanza = core_route_stanza;
 local jid_split = require "util.jid".split;
+local fire_event = require "core.eventmanager".fire_event;
 local events_new = require "util.events".new;
 local st = require "util.stanza";
 local hosts = hosts;
-local serialize = require "util.serialization".serialize
 
 local pairs, type, tostring = pairs, type, tostring;
 
@@ -25,45 +22,38 @@
 
 local disco_items = require "util.multitable".new();
 local NULL = {};
-require "core.discomanager".addDiscoItemsHandler("*host", function(reply, to, from, node)
-	if #node == 0 and hosts[to] then
-		for jid in pairs(disco_items:get(to) or NULL) do
-			reply:tag("item", {jid = jid}):up();
-		end
-		return true;
-	end
-end);
-
-prosody.events.add_handler("server-starting", function () core_route_stanza = _G.core_route_stanza; end);
 
 module "componentmanager"
 
 local function default_component_handler(origin, stanza)
-	log("warn", "Stanza being handled by default component, bouncing error");
-	if stanza.attr.type ~= "error" then
-		core_route_stanza(nil, st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
+	log("warn", "Stanza being handled by default component; bouncing error for: %s", stanza:top_tag());
+	if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
+		origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
 	end
 end
 
-local components_loaded_once;
 function load_enabled_components(config)
 	local defined_hosts = config or configmanager.getconfig();
 		
 	for host, host_config in pairs(defined_hosts) do
 		if host ~= "*" and ((host_config.core.enabled == nil or host_config.core.enabled) and type(host_config.core.component_module) == "string") then
-			hosts[host] = { type = "component", host = host, connected = false, s2sout = {}, events = events_new() };
+			hosts[host] = create_component(host);
+			hosts[host].connected = false;
 			components[host] = default_component_handler;
 			local ok, err = modulemanager.load(host, host_config.core.component_module);
 			if not ok then
 				log("error", "Error loading %s component %s: %s", tostring(host_config.core.component_module), tostring(host), tostring(err));
 			else
+				fire_event("component-activated", host, host_config);
 				log("debug", "Activated %s component: %s", host_config.core.component_module, host);
 			end
 		end
 	end
 end
 
-prosody.events.add_handler("server-starting", load_enabled_components);
+if prosody and prosody.events then
+	prosody.events.add_handler("server-starting", load_enabled_components);
+end
 
 function handle_stanza(origin, stanza)
 	local node, host = jid_split(stanza.attr.to);
@@ -76,13 +66,25 @@
 		log("debug", "%s stanza being handled by component: %s", stanza.name, host);
 		component(origin, stanza, hosts[host]);
 	else
-		log("error", "Component manager recieved a stanza for a non-existing component: " .. (stanza.attr.to or serialize(stanza)));
+		log("error", "Component manager recieved a stanza for a non-existing component: "..tostring(stanza));
+		default_component_handler(origin, stanza);
 	end
 end
 
-function create_component(host, component)
+function create_component(host, component, events)
 	-- TODO check for host well-formedness
-	return { type = "component", host = host, connected = true, s2sout = {}, events = events_new() };
+	local ssl_ctx;
+	if host then
+		-- We need to find SSL context to use...
+		-- Discussion in prosody@ concluded that
+		-- 1 level back is usually enough by default
+		local base_host = host:gsub("^[^%.]+%.", "");
+		if hosts[base_host] then
+			ssl_ctx = hosts[base_host].ssl_ctx;
+		end
+	end
+	return { type = "component", host = host, connected = true, s2sout = {}, 
+			ssl_ctx = ssl_ctx, events = events or events_new() };
 end
 
 function register_component(host, component, session)
@@ -90,7 +92,7 @@
 		local old_events = hosts[host] and hosts[host].events;
 
 		components[host] = component;
-		hosts[host] = session or create_component(host, component);
+		hosts[host] = session or create_component(host, component, old_events);
 		
 		-- Add events object if not already one
 		if not hosts[host].events then
@@ -101,8 +103,8 @@
 		if not(host:find("@", 1, true) or host:find("/", 1, true)) and host:find(".", 1, true) then
 			disco_items:set(host:sub(host:find(".", 1, true)+1), host, true);
 		end
-		-- FIXME only load for a.b.c if b.c has dialback, and/or check in config
 		modulemanager.load(host, "dialback");
+		modulemanager.load(host, "tls");
 		log("debug", "component added: "..host);
 		return session or hosts[host];
 	else
@@ -112,6 +114,7 @@
 
 function deregister_component(host)
 	if components[host] then
+		modulemanager.unload(host, "tls");
 		modulemanager.unload(host, "dialback");
 		hosts[host].connected = nil;
 		local host_config = configmanager.getconfig()[host];
@@ -120,7 +123,7 @@
 			components[host] = default_component_handler;
 		else
 			-- Component not in config, or disabled, remove
-			hosts[host] = nil;
+			hosts[host] = nil; -- FIXME do proper unload of all modules and other cleanup before removing
 			components[host] = nil;
 		end
 		-- remove from disco_items
@@ -138,4 +141,8 @@
 	components[host] = handler;
 end
 
+function get_children(host)
+	return disco_items:get(host) or NULL;
+end
+
 return _M;
--- a/core/configmanager.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/core/configmanager.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -68,7 +68,7 @@
 	if parsers[format] and parsers[format].load then
 		local f, err = io.open(filename);
 		if f then 
-			local ok, err = parsers[format].load(f:read("*a"));
+			local ok, err = parsers[format].load(f:read("*a"), filename);
 			f:close();
 			if ok then
 				eventmanager.fire_event("config-reloaded", { filename = filename, format = format });
@@ -99,7 +99,7 @@
 	local loadstring, pcall, setmetatable = _G.loadstring, _G.pcall, _G.setmetatable;
 	local setfenv, rawget, tostring = _G.setfenv, _G.rawget, _G.tostring;
 	parsers.lua = {};
-	function parsers.lua.load(data)
+	function parsers.lua.load(data, filename)
 		local env;
 		-- The ' = true' are needed so as not to set off __newindex when we assign the functions below
 		env = setmetatable({ Host = true; host = true; Component = true, component = true,
@@ -139,7 +139,7 @@
 			local f, err = io.open(file);
 			if f then
 				local data = f:read("*a");
-				local ok, err = parsers.lua.load(data);
+				local ok, err = parsers.lua.load(data, file);
 				if not ok then error(err:gsub("%[string.-%]", file), 0); end
 			end
 			if not f then error("Error loading included "..file..": "..err, 0); end
@@ -147,7 +147,7 @@
 		end
 		env.include = env.Include;
 		
-		local chunk, err = loadstring(data);
+		local chunk, err = loadstring(data, "@"..filename);
 		
 		if not chunk then
 			return nil, err;
--- a/core/discomanager.lua	Fri Nov 13 14:31:03 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-
-local helper = require "util.discohelper".new();
-local hosts = hosts;
-local jid_split = require "util.jid".split;
-local jid_bare = require "util.jid".bare;
-local usermanager_user_exists = require "core.usermanager".user_exists;
-local rostermanager_is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
-local print = print;
-
-do
-	helper:addDiscoInfoHandler("*host", function(reply, to, from, node)
-		if hosts[to] then
-			reply:tag("identity", {category="server", type="im", name="Prosody"}):up();
-			return true;
-		end
-	end);
-	helper:addDiscoInfoHandler("*node", function(reply, to, from, node)
-		local node, host = jid_split(to);
-		if hosts[host] and rostermanager_is_contact_subscribed(node, host, jid_bare(from)) then
-			reply:tag("identity", {category="account", type="registered"}):up();
-			return true;
-		end
-	end);
-	helper:addDiscoItemsHandler("*host", function(reply, to, from, node)
-		if hosts[to] and hosts[to].type == "local" then
-			return true;
-		end
-	end);
-end
-
-module "discomanager"
-
-function handle(stanza)
-	return helper:handle(stanza);
-end
-
-function addDiscoItemsHandler(jid, func)
-	return helper:addDiscoItemsHandler(jid, func);
-end
-
-function addDiscoInfoHandler(jid, func)
-	return helper:addDiscoInfoHandler(jid, func);
-end
-
-function set(plugin, var, origin)
-	-- TODO handle origin and host based on plugin.
-	local handler = function(reply, to, from, node) -- service discovery
-		if #node == 0 then
-			reply:tag("feature", {var = var}):up();
-			return true;
-		end
-	end
-	addDiscoInfoHandler("*node", handler);
-	addDiscoInfoHandler("*host", handler);
-end
-
-return _M;
--- a/core/hostmanager.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/core/hostmanager.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,15 +6,26 @@
 -- COPYING file in the source package for more information.
 --
 
+local ssl = ssl
 
 local hosts = hosts;
 local configmanager = require "core.configmanager";
 local eventmanager = require "core.eventmanager";
+local modulemanager = require "core.modulemanager";
 local events_new = require "util.events".new;
 
+if not _G.prosody.incoming_s2s then
+	require "core.s2smanager";
+end
+local incoming_s2s = _G.prosody.incoming_s2s;
+
+-- These are the defaults if not overridden in the config
+local default_ssl_ctx = { mode = "client", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
+local default_ssl_ctx_in = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
+
 local log = require "util.logger".init("hostmanager");
 
-local pairs = pairs;
+local pairs, setmetatable = pairs, setmetatable;
 
 module "hostmanager"
 
@@ -24,7 +35,7 @@
 	local defined_hosts = config or configmanager.getconfig();
 	
 	for host, host_config in pairs(defined_hosts) do
-		if host ~= "*" and (host_config.core.enabled == nil or host_config.core.enabled) then
+		if host ~= "*" and (host_config.core.enabled == nil or host_config.core.enabled) and not host_config.core.component_module then
 			activate(host, host_config);
 		end
 	end
@@ -46,23 +57,57 @@
 			log("warn", "%s: Option '%s' has no effect for virtual hosts - put it in global Host \"*\" instead", host, option_name);
 		end
 	end
+	
+	if ssl then
+		local ssl_config = host_config.core.ssl or configmanager.get("*", "core", "ssl");
+		if ssl_config then
+        		hosts[host].ssl_ctx = ssl.newcontext(setmetatable(ssl_config, { __index = default_ssl_ctx }));
+        		hosts[host].ssl_ctx_in = ssl.newcontext(setmetatable(ssl_config, { __index = default_ssl_ctx_in }));
+        	end
+        end
+
 	log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
 	eventmanager.fire_event("host-activated", host, host_config);
 end
 
-function deactivate(host)
+function deactivate(host, reason)
 	local host_session = hosts[host];
 	log("info", "Deactivating host: %s", host);
 	eventmanager.fire_event("host-deactivating", host, host_session);
 	
+	reason = reason or { condition = "host-gone", text = "This server has stopped serving "..host };
+	
 	-- Disconnect local users, s2s connections
-	for user, session_list in pairs(host_session.sessions) do
-		for resource, session in pairs(session_list) do
-			session:close("host-gone");
+	if host_session.sessions then
+		for username, user in pairs(host_session.sessions) do
+			for resource, session in pairs(user.sessions) do
+				log("debug", "Closing connection for %s@%s/%s", username, host, resource);
+				session:close(reason);
+			end
 		end
 	end
-	-- Components?
-	
+	if host_session.s2sout then
+		for remotehost, session in pairs(host_session.s2sout) do
+			if session.close then
+				log("debug", "Closing outgoing connection to %s", remotehost);
+				if session.srv_hosts then session.srv_hosts = nil; end
+				session:close(reason);
+			end
+		end
+	end
+	for remote_session in pairs(incoming_s2s) do
+		if remote_session.to_host == host then
+			log("debug", "Closing incoming connection from %s", remote_session.from_host or "<unknown>");
+			remote_session:close(reason);
+		end
+	end
+
+	if host_session.modules then
+		for module in pairs(host_session.modules) do
+			modulemanager.unload(host, module);
+		end
+	end
+
 	hosts[host] = nil;
 	eventmanager.fire_event("host-deactivated", host);
 	log("info", "Deactivated host: %s", host);
@@ -71,3 +116,4 @@
 function getconfig(name)
 end
 
+return _M;
--- a/core/loggingmanager.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/core/loggingmanager.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -187,6 +187,7 @@
 		return function (name, level, message, ...)
 			sourcewidth = math_max(#name+2, sourcewidth);
 			local namelen = #name;
+			
 			if timestamps then
 				io_write(os_date(timestamps), " ");
 			end
--- a/core/modulemanager.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/core/modulemanager.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,13 +6,10 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
 local plugin_dir = CFG_PLUGINDIR or "./plugins/";
 
 local logger = require "util.logger";
 local log = logger.init("modulemanager");
-local addDiscoInfoHandler = require "core.discomanager".addDiscoInfoHandler;
 local eventmanager = require "core.eventmanager";
 local config = require "core.configmanager";
 local multitable_new = require "util.multitable".new;
@@ -50,8 +47,6 @@
 
 local modulehelpers = setmetatable({}, { __index = _G });
 
-local features_table = multitable_new();
-local identities_table = multitable_new();
 local handler_table = multitable_new();
 local hooked = multitable_new();
 local hooks = multitable_new();
@@ -61,22 +56,27 @@
 
 -- Load modules when a host is activated
 function load_modules_for_host(host)
+	local disabled_set = {};
+	local modules_disabled = config.get(host, "core", "modules_disabled");
+	if modules_disabled then
+		for _, module in ipairs(modules_disabled) do
+			disabled_set[module] = true;
+		end
+	end
+
+	-- Load auto-loaded modules for this host
+	if hosts[host].type == "local" then
+		for _, module in ipairs(autoload_modules) do
+			if not disabled_set[module] then
+				load(host, module);
+			end
+		end
+	end
+
+	-- Load modules from global section
 	if config.get(host, "core", "load_global_modules") ~= false then
-		-- Load modules from global section
 		local modules_enabled = config.get("*", "core", "modules_enabled");
-		local modules_disabled = config.get(host, "core", "modules_disabled");
-		local disabled_set = {};
 		if modules_enabled then
-			if modules_disabled then
-				for _, module in ipairs(modules_disabled) do
-					disabled_set[module] = true;
-				end
-			end
-			for _, module in ipairs(autoload_modules) do
-				if not disabled_set[module] then
-					load(host, module);
-				end
-			end
 			for _, module in ipairs(modules_enabled) do
 				if not disabled_set[module] and not is_loaded(host, module) then
 					load(host, module);
@@ -96,6 +96,7 @@
 	end
 end
 eventmanager.add_event_hook("host-activated", load_modules_for_host);
+eventmanager.add_event_hook("component-activated", load_modules_for_host);
 --
 
 function load(host, module_name, config)
@@ -127,29 +128,39 @@
 	local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
 	
 	setfenv(mod, pluginenv);
-	if not hosts[host] then hosts[host] = { type = "component", host = host, connected = false, s2sout = {} }; end
-	
-	local success, ret = pcall(mod);
-	if not success then
-		log("error", "Error initialising module '%s': %s", module_name or "nil", ret or "nil");
-		return nil, ret;
+	if not hosts[host] then
+		local create_component = _G.require "core.componentmanager".create_component;
+		hosts[host] = create_component(host);
+		hosts[host].connected = false;
+		log("debug", "Created new component: %s", host);
 	end
+	hosts[host].modules = modulemap[host];
+	modulemap[host][module_name] = pluginenv;
 	
-	if module_has_method(pluginenv, "load") then
-		local ok, err = call_module_method(pluginenv, "load");
-		if (not ok) and err then
-			log("warn", "Error loading module '%s' on '%s': %s", module_name, host, err);
+	local success, err = pcall(mod);
+	if success then
+		if module_has_method(pluginenv, "load") then
+			success, err = call_module_method(pluginenv, "load");
+			if not success then
+				log("warn", "Error loading module '%s' on '%s': %s", module_name, host, err or "nil");
+			end
 		end
-	end
 
-	-- Use modified host, if the module set one
-	modulemap[api_instance.host][module_name] = pluginenv;
-	
-	if api_instance.host == "*" and host ~= "*" then
-		api_instance:set_global();
+		-- 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
-		
-	return true;
+	if success then
+		return true;
+	else -- load failed, unloading
+		unload(api_instance.host, module_name);
+		return nil, err;
+	end
 end
 
 function get_module(host, name)
@@ -170,9 +181,6 @@
 			log("warn", "Non-fatal error unloading module '%s' on '%s': %s", name, host, err);
 		end
 	end
-	modulemap[host][name] = nil;
-	features_table:remove(host, name);
-	identities_table:remove(host, name);
 	local params = handler_table:get(host, name); -- , {module.host, origin_type, tag, xmlns}
 	for _, param in pairs(params or NULL) do
 		local handlers = stanza_handlers:get(param[1], param[2], param[3], param[4]);
@@ -189,6 +197,7 @@
 		end
 	end
 	hooks:remove(host, name);
+	modulemap[host][name] = nil;
 	return true;
 end
 
@@ -235,7 +244,7 @@
 end
 
 function handle_stanza(host, origin, stanza)
-	local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns, origin.type;
+	local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type;
 	if name == "iq" and xmlns == "jabber:client" then
 		if stanza.attr.type == "get" or stanza.attr.type == "set" then
 			xmlns = stanza.tags[1].attr.xmlns or "jabber:client";
@@ -252,12 +261,13 @@
 		(handlers[1])(origin, stanza);
 		return true;
 	else
-		log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
 		if stanza.attr.xmlns == "jabber:client" then
+			log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
 			if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
 				origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
 			end
 		elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
+			log("warn", "Unhandled %s stream element: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it
 			origin:close("unsupported-stanza-type");
 		end
 	end
@@ -328,50 +338,11 @@
 	self:add_handler(origin_type, "iq", xmlns, handler);
 end
 
-addDiscoInfoHandler("*host", function(reply, to, from, node)
-	if #node == 0 then
-		local done = {};
-		for module, identities in pairs(identities_table:get(to) or NULL) do -- for each module
-			for identity, attr in pairs(identities) do
-				if not done[identity] then
-					reply:tag("identity", attr):up(); -- TODO cache
-					done[identity] = true;
-				end
-			end
-		end
-		for module, identities in pairs(identities_table:get("*") or NULL) do -- for each module
-			for identity, attr in pairs(identities) do
-				if not done[identity] then
-					reply:tag("identity", attr):up(); -- TODO cache
-					done[identity] = true;
-				end
-			end
-		end
-		for module, features in pairs(features_table:get(to) or NULL) do -- for each module
-			for feature in pairs(features) do
-				if not done[feature] then
-					reply:tag("feature", {var = feature}):up(); -- TODO cache
-					done[feature] = true;
-				end
-			end
-		end
-		for module, features in pairs(features_table:get("*") or NULL) do -- for each module
-			for feature in pairs(features) do
-				if not done[feature] then
-					reply:tag("feature", {var = feature}):up(); -- TODO cache
-					done[feature] = true;
-				end
-			end
-		end
-		return next(done) ~= nil;
-	end
-end);
-
 function api:add_feature(xmlns)
-	features_table:set(self.host, self.name, xmlns, true);
+	self:add_item("feature", xmlns);
 end
-function api:add_identity(category, type)
-	identities_table:set(self.host, self.name, category.."\0"..type, {category = category, type = type});
+function api:add_identity(category, type, name)
+	self:add_item("identity", {category = category, type = type, name = name});
 end
 
 local event_hook = function(host, mod_name, event_name, ...)
@@ -419,7 +390,54 @@
 end
 
 function api:get_option(name, default_value)
-	return config.get(self.host, self.name, name) or config.get(self.host, "core", name) or 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
+
+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
 
 --------------------------------------------------------------------
--- a/core/rostermanager.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/core/rostermanager.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -18,6 +18,7 @@
 local tostring = tostring;
 
 local hosts = hosts;
+local bare_sessions = bare_sessions;
 
 local datamanager = require "util.datamanager"
 local st = require "util.stanza";
@@ -81,33 +82,41 @@
 end
 
 function load_roster(username, host)
-	log("debug", "load_roster: asked for: "..username.."@"..host);
+	local jid = username.."@"..host;
+	log("debug", "load_roster: asked for: "..jid);
+	local user = bare_sessions[jid];
 	local roster;
-	if hosts[host] and hosts[host].sessions[username] then
-		roster = hosts[host].sessions[username].roster;
-		if not roster then
-			log("debug", "load_roster: loading for new user: "..username.."@"..host);
-			roster = datamanager.load(username, host, "roster") or {};
-			if not roster[false] then roster[false] = { }; end
-			hosts[host].sessions[username].roster = roster;
-			hosts[host].events.fire_event("roster-load", username, host, roster);
-		end
-		return roster;
+	if user then
+		roster = user.roster;
+		if roster then return roster; end
+		log("debug", "load_roster: loading for new user: "..username.."@"..host);
+	else -- Attempt to load roster for non-loaded user
+		log("debug", "load_roster: loading for offline user: "..username.."@"..host);
 	end
-	
-	-- Attempt to load roster for non-loaded user
-	log("debug", "load_roster: loading for offline user: "..username.."@"..host);
 	roster = datamanager.load(username, host, "roster") or {};
+	if user then user.roster = roster; end
+	if not roster[false] then roster[false] = { }; end
+	if roster[jid] then
+		roster[jid] = nil;
+		log("warn", "roster for "..jid.." has a self-contact");
+	end
 	hosts[host].events.fire_event("roster-load", username, host, roster);
 	return roster;
 end
 
-function save_roster(username, host)
+function save_roster(username, host, roster)
 	log("debug", "save_roster: saving roster for "..username.."@"..host);
-	if hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster then
-		local roster = hosts[host].sessions[username].roster;
-		roster[false].version = (roster[false].version or 1) + 1;
-		return datamanager.store(username, host, "roster", hosts[host].sessions[username].roster);
+	if not roster then
+		roster = hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster;
+		--if not roster then
+		--	--roster = load_roster(username, host);
+		--	return true; -- roster unchanged, no reason to save
+		--end
+	end
+	if roster then
+		if not roster[false] then roster[false] = {}; end
+		roster[false].version = (roster[false].version or 0) + 1;
+		return datamanager.store(username, host, "roster", roster);
 	end
 	log("warn", "save_roster: user had no roster to save");
 	return nil;
@@ -123,7 +132,7 @@
 			item.subscription = "both";
 		end
 		item.ask = nil;
-		return datamanager.store(username, host, "roster", roster);
+		return save_roster(username, host, roster);
 	end
 end
 
@@ -145,7 +154,7 @@
 		end
 	end
 	if changed then
-		return datamanager.store(username, host, "roster", roster);
+		return save_roster(username, host, roster);
 	end
 end
 
@@ -167,7 +176,7 @@
 		end
 	end
 	if changed then
-		return datamanager.store(username, host, "roster", roster);
+		return save_roster(username, host, roster);
 	end
 end
 
@@ -189,7 +198,7 @@
 	end
 	if not roster.pending then roster.pending = {}; end
 	roster.pending[jid] = true;
-	return datamanager.store(username, host, "roster", roster);
+	return save_roster(username, host, roster);
 end
 function is_contact_pending_out(username, host, jid)
 	local roster = load_roster(username, host);
@@ -208,7 +217,7 @@
 	end
 	item.ask = "subscribe";
 	log("debug", "set_contact_pending_out: saving roster; set "..username.."@"..host..".roster["..jid.."].ask=subscribe");
-	return datamanager.store(username, host, "roster", roster);
+	return save_roster(username, host, roster);
 end
 function unsubscribe(username, host, jid)
 	local roster = load_roster(username, host);
@@ -223,7 +232,7 @@
 	elseif item.subscription == "to" then
 		item.subscription = "none";
 	end
-	return datamanager.store(username, host, "roster", roster);
+	return save_roster(username, host, roster);
 end
 function subscribed(username, host, jid)
 	if is_contact_pending_in(username, host, jid) then
@@ -240,7 +249,7 @@
 		end
 		roster.pending[jid] = nil;
 		-- TODO maybe remove roster.pending if empty
-		return datamanager.store(username, host, "roster", roster);
+		return save_roster(username, host, roster);
 	end -- TODO else implement optional feature pre-approval (ask = subscribed)
 end
 function unsubscribed(username, host, jid)
@@ -262,7 +271,7 @@
 		end
 	end
 	if changed then
-		return datamanager.store(username, host, "roster", roster);
+		return save_roster(username, host, roster);
 	end
 end
 
@@ -271,7 +280,7 @@
 	local item = roster[jid];
 	if item and (item.subscription == "none" or item.subscription == "from") then
 		item.ask = "subscribe";
-		return datamanager.store(username, host, "roster", roster);
+		return save_roster(username, host, roster);
 	end
 end
 
@@ -280,7 +289,7 @@
 	local item = roster[jid];
 	if item and (item.subscription == "none" or item.subscription == "from" then
 		item.ask = "subscribe";
-		return datamanager.store(username, host, "roster", roster);
+		return save_roster(username, host, roster);
 	end
 end]]
 
--- a/core/s2smanager.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/core/s2smanager.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -27,6 +27,7 @@
 local stanza = st.stanza;
 local nameprep = require "util.encodings".stringprep.nameprep;
 
+local fire_event = require "core.eventmanager".fire_event;
 local uuid_gen = require "util.uuid".generate;
 
 local logger_init = require "util.logger".init;
@@ -37,11 +38,14 @@
 
 local dialback_secret = uuid_gen();
 
-local adns = require "net.adns";
-
+local adns, dns = require "net.adns", require "net.dns";
+local config = require "core.configmanager";
+local connect_timeout = config.get("*", "core", "s2s_timeout") or 60;
 local dns_timeout = config.get("*", "core", "dns_timeout") or 60;
+local max_dns_depth = config.get("*", "core", "dns_max_depth") or 3;
 
 incoming_s2s = {};
+_G.prosody.incoming_s2s = incoming_s2s;
 local incoming_s2s = incoming_s2s;
 
 module "s2smanager"
@@ -126,13 +130,26 @@
 	end
 	open_sessions = open_sessions + 1;
 	local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
+	session.log = log;
 	session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end
 	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)("warn", "Destroying incomplete session %s->%s due to inactivity", 
+		    session.from_host or "(unknown)", session.to_host or "(unknown)");
+		session:close("connection-timeout");
+	end);
 	return session;
 end
 
 function new_outgoing(from_host, to_host)
-		local host_session = { to_host = to_host, from_host = from_host, notopen = true, type = "s2sout_unauthed", direction = "outgoing" };
+		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 log;
@@ -173,7 +190,7 @@
 	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 answer, handle;
+		local handle;
 		handle = adns.lookup(function (answer)
 			handle = nil;
 			host_session.connecting = nil;
@@ -235,6 +252,47 @@
 end
 
 function try_connect(host_session, connect_host, connect_port)
+	host_session.connecting = true;
+	local handle;
+	handle = adns.lookup(function (reply)
+		handle = nil;
+		host_session.connecting = nil;
+		
+		-- COMPAT: This is a compromise for all you CNAME-(ab)users :)
+		if not (reply and reply[#reply] and reply[#reply].a) then
+			local count = max_dns_depth;
+			reply = dns.peek(connect_host, "CNAME", "IN");
+			while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do
+				log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count);
+				reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN");
+				count = count - 1;
+			end
+		end
+		-- end of CNAME resolving
+		
+		if reply and reply[#reply] and reply[#reply].a then
+			log("debug", "DNS reply for %s gives us %s", connect_host, reply[#reply].a);
+			return make_connect(host_session, reply[#reply].a, connect_port);
+		else
+			log("debug", "DNS lookup failed to get a response for %s", connect_host);
+			if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can
+				log("debug", "No other records to try for %s - destroying", host_session.to_host);
+				destroy_session(host_session); -- End of the line, we can't
+			end
+		end
+	end, connect_host, "A", "IN");
+
+	-- Set handler for DNS timeout
+	add_task(dns_timeout, function ()
+		if handle then
+			adns.cancel(handle, true);
+		end
+	end);
+		
+	return true;
+end
+
+function make_connect(host_session, connect_host, connect_port)
 	host_session.log("info", "Beginning new connection attempt to %s (%s:%d)", host_session.to_host, connect_host, connect_port);
 	-- Ok, we're going to try to connect
 	
@@ -257,11 +315,22 @@
 	-- otherwise it will assume it is a new incoming connection
 	cl.register_outgoing(conn, host_session);
 	
-	local w = conn.write;
+	local w, log = conn.write, host_session.log;
 	host_session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end
 	
 	conn.write(format([[<stream:stream xmlns='jabber:server' xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' from='%s' to='%s' version='1.0' xml:lang='en'>]], from_host, to_host));
 	log("debug", "Connection attempt in progress...");
+	add_task(connect_timeout, function ()
+		if host_session.conn ~= conn or
+		   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
 
@@ -269,10 +338,16 @@
 	local send = session.sends2s;
 	
 	-- TODO: #29: SASL/TLS on s2s streams
-	session.version = 0; --tonumber(attr.version) or 0;
+	session.version = tonumber(attr.version) or 0;
+	
+	if session.secure == false then
+		session.secure = true;
+	end
 	
 	if session.version >= 1.0 and not (attr.to and attr.from) then
-		log("warn", (session.to_host or "(unknown)").." failed to specify 'to' or 'from' hostname as per RFC");
+		
+		(session.log or log)("warn", "Remote of stream "..(session.from_host or "(unknown)").."->"..(session.to_host or "(unknown)")
+			.." failed to specify to (%s) and/or from (%s) hostname as per RFC", tostring(attr.to), tostring(attr.from));
 	end
 	
 	if session.direction == "incoming" then
@@ -284,15 +359,23 @@
 		(session.log or log)("debug", "incoming s2s received <stream:stream>");
 		send("<?xml version='1.0'?>");
 		send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback', 
-				["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host }):top_tag());
+				["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
 		if session.to_host and not hosts[session.to_host] then
 			-- Attempting to connect to a host we don't serve
 			session:close({ condition = "host-unknown"; text = "This host does not serve "..session.to_host });
 			return;
 		end
 		if session.version >= 1.0 then
-			send(st.stanza("stream:features")
-					:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):tag("optional"):up():up());
+			local features = st.stanza("stream:features");
+							
+			if session.to_host then
+				hosts[session.to_host].events.fire_event("s2s-stream-features", { session = session, features = features });
+			else
+				(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
@@ -313,10 +396,14 @@
 		end
 		session.send_buffer = nil;
 	
-		if not session.dialback_verifying then
-			initiate_dialback(session);
-		else
-			mark_connected(session);
+		-- 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
 
@@ -366,6 +453,7 @@
 	return true;
 end
 
+-- Stream is authorised, and ready for normal stanzas
 function mark_connected(session)
 	local sendq, send = session.sendq, session.sends2s;
 	
--- a/core/sessionmanager.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/core/sessionmanager.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -11,7 +11,6 @@
 local tonumber, tostring = tonumber, tostring;
 local ipairs, pairs, print, next= ipairs, pairs, print, next;
 local collectgarbage = collectgarbage;
-local m_random = import("math", "random");
 local format = import("string", "format");
 
 local hosts = hosts;
@@ -19,7 +18,8 @@
 local bare_sessions = bare_sessions;
 
 local modulemanager = require "core.modulemanager";
-local log = require "util.logger".init("sessionmanager");
+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;
@@ -27,11 +27,13 @@
 local nameprep = require "util.encodings".stringprep.nameprep;
 
 local fire_event = require "core.eventmanager".fire_event;
-
+local add_task = require "util.timer".add_task;
 local gettime = require "socket".gettime;
 
 local st = require "util.stanza";
 
+local c2s_timeout = config_get("*", "core", "c2s_timeout");
+
 local newproxy = newproxy;
 local getmetatable = getmetatable;
 
@@ -50,6 +52,17 @@
 	local w = conn.write;
 	session.send = function (t) w(tostring(t)); end
 	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
 
@@ -154,31 +167,32 @@
 	session.host = attr.to or error("Client failed to specify destination hostname");
 	session.host = nameprep(session.host);
 	session.version = tonumber(attr.version) or 0;
-	session.streamid = m_random(1000000, 99999999);
+	session.streamid = uuid_generate();
 	(session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
-	
-	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));
 
 	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");
 	fire_event("stream-features", session, features);
-	
+
 	send(features);
-	
-	(session.log or log)("debug", "Sent reply <stream:stream> to client");
-	session.notopen = nil;
+
 end
 
 function streamclosed(session)
--- a/core/stanza_router.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/core/stanza_router.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -8,7 +8,7 @@
 
 local log = require "util.logger".init("stanzarouter")
 
-local hosts = _G.hosts;
+local hosts = _G.prosody.hosts;
 local tostring = tostring;
 local st = require "util.stanza";
 local send_s2s = require "core.s2smanager".send_to_host;
@@ -17,6 +17,9 @@
 local jid_split = require "util.jid".split;
 local jid_prepped_split = require "util.jid".prepped_split;
 
+local full_sessions = _G.prosody.full_sessions;
+local bare_sessions = _G.prosody.bare_sessions;
+
 function core_process_stanza(origin, stanza)
 	(origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:top_tag())
 
@@ -26,7 +29,8 @@
 	-- TODO verify validity of stanza (as well as JID validity)
 	if stanza.attr.type == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log
 	if stanza.name == "iq" then
-		if (stanza.attr.type == "set" or stanza.attr.type == "get") and #stanza.tags ~= 1 then
+		if not stanza.attr.id then stanza.attr.id = ""; end -- COMPAT Jabiru doesn't send the id attribute on roster requests
+		if (stanza.attr.type == "set" or stanza.attr.type == "get") and (#stanza.tags ~= 1) then
 			origin.send(st.error_reply(stanza, "modify", "bad-request"));
 			return;
 		end
@@ -110,7 +114,7 @@
 			end
 			if h.events.fire_event(event, {origin = origin, stanza = stanza}) then return; end
 		end
-		if host and not hosts[host] then host = nil; end -- workaround for a Pidgin bug which sets 'to' to the SRV result
+		if host and not hosts[host] then host = nil; end -- COMPAT: workaround for a Pidgin bug which sets 'to' to the SRV result
 		modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
 	end
 end
--- a/core/usermanager.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/core/usermanager.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,10 +6,7 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
-require "util.datamanager"
-local datamanager = datamanager;
+local datamanager = require "util.datamanager";
 local log = require "util.logger".init("usermanager");
 local type = type;
 local error = error;
@@ -66,14 +63,18 @@
 	return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
 end
 
-function is_admin(jid)
-	local admins = config.get("*", "core", "admins");
+function is_admin(jid, host)
+	host = host or "*";
+	local admins = config.get(host, "core", "admins");
+	if host ~= "*" and admins == config.get("*", "core", "admins") then
+		return nil;
+	end
 	if type(admins) == "table" then
 		jid = jid_bare(jid);
 		for _,admin in ipairs(admins) do
 			if admin == jid then return true; end
 		end
-	else log("debug", "Option core.admins is not a table"); end
+	elseif admins then log("warn", "Option 'admins' for host '%s' is not a table", host); end
 	return nil;
 end
 
--- a/core/xmlhandlers.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/core/xmlhandlers.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -29,7 +29,6 @@
 
 function init_xmlhandlers(session, stream_callbacks)
 		local ns_stack = { "" };
-		local curr_ns, name = "";
 		local curr_tag;
 		local chardata = {};
 		local xml_handlers = {};
@@ -50,7 +49,7 @@
 				stanza:text(t_concat(chardata));
 				chardata = {};
 			end
-			local curr_ns,name = tagname:match("^(.-)|?([^%|]-)$");
+			local curr_ns,name = tagname:match("^([^\1]*)\1?(.*)$");
 			if not name then
 				curr_ns, name = "", curr_ns;
 			end
@@ -63,7 +62,7 @@
 			for i=1,#attr do
 				local k = attr[i];
 				attr[i] = nil;
-				local ns, nm = k:match("^([^|]+)|?([^|]-)$")
+				local ns, nm = k:match("^([^\1]*)\1?(.*)$");
 				if ns and nm then
 					ns = ns_prefixes[ns]; 
 					if ns then 
@@ -105,7 +104,7 @@
 			end
 		end
 		function xml_handlers:EndElement(tagname)
-			curr_ns,name = tagname:match("^(.-)|?([^%|]-)$");
+			local curr_ns,name = tagname:match("^([^\1]*)\1?(.*)$");
 			if not name then
 				curr_ns, name = "", curr_ns;
 			end
@@ -114,12 +113,13 @@
 					if cb_streamclosed then
 						cb_streamclosed(session);
 					end
-					return;
 				elseif name == "error" then
 					cb_error(session, "stream-error", stanza);
 				else
 					cb_error(session, "parse-error", "unexpected-element-close", name);
 				end
+				stanza, chardata = nil, {};
+				return;
 			end
 			if #chardata > 0 then
 				-- We have some character data in the buffer
--- a/net/adns.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/net/adns.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -11,6 +11,7 @@
 
 local log = require "util.logger".init("adns");
 
+local t_insert, t_remove = table.insert, table.remove;
 local coroutine, tostring, pcall = coroutine, tostring, pcall;
 
 module "adns"
@@ -28,7 +29,7 @@
 				log("debug", "Reply for %s (%s)", qname, tostring(coroutine.running()));
 				local ok, err = pcall(handler, dns.peek(qname, qtype, qclass));
 				if not ok then
-					log("debug", "Error in DNS response handler: %s", tostring(err));
+					log("error", "Error in DNS response handler: %s", tostring(err));
 				end
 			end)(dns.peek(qname, qtype, qclass));
 end
@@ -41,18 +42,31 @@
 	end
 end
 
-function new_async_socket(sock)
-	local newconn = {};
+function new_async_socket(sock, resolver)
+	local newconn, peername = {}, "<unknown>";
 	local listener = {};
 	function listener.incoming(conn, data)
 		dns.feed(sock, data);
 	end
-	function listener.disconnect()
+	function listener.disconnect(conn, err)
+		log("warn", "DNS socket for %s disconnected: %s", peername, err);
+		local servers = resolver.server;
+		if resolver.socketset[newconn.handler] == resolver.best_server and resolver.best_server == #servers then
+			log("error", "Exhausted all %d configured DNS servers, next lookup will try %s again", #servers, servers[1]);
+		end
+		
+		resolver:servfail(conn); -- Let the magic commence
 	end
 	newconn.handler, newconn._socket = server.wrapclient(sock, "dns", 53, listener);
+	if not newconn.handler then
+		log("warn", "handler is nil");
+	end
+	if not newconn._socket then
+		log("warn", "socket is nil");
+	end
 	newconn.handler.settimeout = function () end
 	newconn.handler.setsockname = function (_, ...) return sock:setsockname(...); end
-	newconn.handler.setpeername = function (_, ...) local ret = sock:setpeername(...); _.setsend(sock.send); return ret; end
+	newconn.handler.setpeername = function (_, ...) peername = (...); local ret = sock:setpeername(...); _.setsend(sock.send); return ret; end
 	newconn.handler.connect = function (_, ...) return sock:connect(...) end	
 	newconn.handler.send = function (_, data) _.write(data); return _.sendbuffer(); end	
 	return newconn.handler;
--- a/net/connlisteners.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/net/connlisteners.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -11,6 +11,7 @@
 local listeners_dir = (CFG_SOURCEDIR or ".").."/net/";
 local server = require "net.server";
 local log = require "util.logger".init("connlisteners");
+local tostring = tostring;
 
 local dofile, pcall, error = 
 	dofile, pcall, error
@@ -37,7 +38,10 @@
 	local h = listeners[name];
 	if not h then
 		local ok, ret = pcall(dofile, listeners_dir..name:gsub("[^%w%-]", "_").."_listener.lua");
-		if not ok then return nil, ret; end
+		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;
--- a/net/dns.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/net/dns.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -14,21 +14,22 @@
 -- reference: http://tools.ietf.org/html/rfc1876 (LOC)
 
 
-require 'socket'
-local ztact = require 'util.ztact'
-local require = require
+local socket = require "socket";
+local ztact = require "util.ztact";
+local _, windows = pcall(require, "util.windows");
+local is_windows = (_ and windows) or os.getenv("WINDIR");
 
-local coroutine, io, math, socket, string, table =
-      coroutine, io, math, socket, string, table
+local coroutine, io, math, string, table =
+      coroutine, io, math, string, table;
 
 local ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack =
-      ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack
+      ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack;
 
-local get, set = ztact.get, ztact.set
+local get, set = ztact.get, ztact.set;
 
 
 -------------------------------------------------- module dns
-module ('dns')
+module('dns')
 local dns = _M;
 
 
@@ -38,826 +39,928 @@
 local append = table.insert
 
 
-local function highbyte (i)    -- - - - - - - - - - - - - - - - - - -  highbyte
-  return (i-(i%0x100))/0x100
-  end
+local function highbyte(i)    -- - - - - - - - - - - - - - - - - - -  highbyte
+	return (i-(i%0x100))/0x100;
+end
 
 
 local function augment (t)    -- - - - - - - - - - - - - - - - - - - -  augment
-  local a = {}
-  for i,s in pairs (t) do  a[i] = s  a[s] = s  a[string.lower (s)] = s  end
-  return a
-  end
+	local a = {};
+	for i,s in pairs(t) do
+		a[i] = s;
+		a[s] = s;
+		a[string.lower(s)] = s;
+	end
+	return a;
+end
 
 
 local function encode (t)    -- - - - - - - - - - - - - - - - - - - - -  encode
-  local code = {}
-  for i,s in pairs (t) do
-    local word = string.char (highbyte (i), i %0x100)
-    code[i] = word
-    code[s] = word
-    code[string.lower (s)] = word
-    end
-  return code
-  end
+	local code = {};
+	for i,s in pairs(t) do
+		local word = string.char(highbyte(i), i%0x100);
+		code[i] = word;
+		code[s] = word;
+		code[string.lower(s)] = word;
+	end
+	return code;
+end
 
 
 dns.types = {
-  'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR', 'NULL', 'WKS',
-  'PTR', 'HINFO', 'MINFO', 'MX', 'TXT',
-  [ 28] = 'AAAA', [ 29] = 'LOC',   [ 33] = 'SRV',
-  [252] = 'AXFR', [253] = 'MAILB', [254] = 'MAILA', [255] = '*' }
+	'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR', 'NULL', 'WKS',
+	'PTR', 'HINFO', 'MINFO', 'MX', 'TXT',
+	[ 28] = 'AAAA', [ 29] = 'LOC',   [ 33] = 'SRV',
+	[252] = 'AXFR', [253] = 'MAILB', [254] = 'MAILA', [255] = '*' };
 
 
-dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' }
+dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' };
 
 
-dns.type      = augment (dns.types)
-dns.class     = augment (dns.classes)
-dns.typecode  = encode  (dns.types)
-dns.classcode = encode  (dns.classes)
+dns.type      = augment (dns.types);
+dns.class     = augment (dns.classes);
+dns.typecode  = encode  (dns.types);
+dns.classcode = encode  (dns.classes);
 
 
 
-local function standardize (qname, qtype, qclass)    -- - - - - - - standardize
-  if string.byte (qname, -1) ~= 0x2E then  qname = qname..'.'  end
-  qname = string.lower (qname)
-  return qname, dns.type[qtype or 'A'], dns.class[qclass or 'IN']
-  end
+local function standardize(qname, qtype, qclass)    -- - - - - - - standardize
+	if string.byte(qname, -1) ~= 0x2E then qname = qname..'.';  end
+	qname = string.lower(qname);
+	return qname, dns.type[qtype or 'A'], dns.class[qclass or 'IN'];
+end
 
 
-local function prune (rrs, time, soft)    -- - - - - - - - - - - - - - -  prune
-
-  time = time or socket.gettime ()
-  for i,rr in pairs (rrs) do
-
-    if rr.tod then
-      -- rr.tod = rr.tod - 50    -- accelerated decripitude
-      rr.ttl = math.floor (rr.tod - time)
-      if rr.ttl <= 0 then  rrs[i] = nil  end
-
-    elseif soft == 'soft' then    -- What is this?  I forget!
-      assert (rr.ttl == 0)
-      rrs[i] = nil
-      end  end  end
+local function prune(rrs, time, soft)    -- - - - - - - - - - - - - - -  prune
+	time = time or socket.gettime();
+	for i,rr in pairs(rrs) do
+		if rr.tod then
+			-- rr.tod = rr.tod - 50    -- accelerated decripitude
+			rr.ttl = math.floor(rr.tod - time);
+			if rr.ttl <= 0 then
+				table.remove(rrs, i);
+				return prune(rrs, time, soft); -- Re-iterate
+			end
+		elseif soft == 'soft' then    -- What is this?  I forget!
+			assert(rr.ttl == 0);
+			rrs[i] = nil;
+		end
+	end
+end
 
 
 -- metatables & co. ------------------------------------------ metatables & co.
 
 
-local resolver = {}
-resolver.__index = resolver
+local resolver = {};
+resolver.__index = resolver;
 
 
-local SRV_tostring
+local SRV_tostring;
 
 
-local rr_metatable = {}    -- - - - - - - - - - - - - - - - - - -  rr_metatable
-function rr_metatable.__tostring (rr)
-  local s0 = string.format (
-    '%2s %-5s %6i %-28s', rr.class, rr.type, rr.ttl, rr.name )
-  local s1 = ''
-  if rr.type == 'A' then  s1 = ' '..rr.a
-  elseif rr.type == 'MX' then
-    s1 = string.format (' %2i %s', rr.pref, rr.mx)
-  elseif rr.type == 'CNAME' then  s1 = ' '..rr.cname
-  elseif rr.type == 'LOC'   then  s1 = ' '..resolver.LOC_tostring (rr)
-  elseif rr.type == 'NS'    then  s1 = ' '..rr.ns
-  elseif rr.type == 'SRV'   then  s1 = ' '..SRV_tostring (rr)
-  elseif rr.type == 'TXT'   then  s1 = ' '..rr.txt
-  else  s1 = ' <UNKNOWN RDATA TYPE>'  end
-  return s0..s1
-  end
+local rr_metatable = {};   -- - - - - - - - - - - - - - - - - - -  rr_metatable
+function rr_metatable.__tostring(rr)
+	local s0 = string.format('%2s %-5s %6i %-28s', rr.class, rr.type, rr.ttl, rr.name);
+	local s1 = '';
+	if rr.type == 'A' then
+		s1 = ' '..rr.a;
+	elseif rr.type == 'MX' then
+		s1 = string.format(' %2i %s', rr.pref, rr.mx);
+	elseif rr.type == 'CNAME' then
+		s1 = ' '..rr.cname;
+	elseif rr.type == 'LOC' then
+		s1 = ' '..resolver.LOC_tostring(rr);
+	elseif rr.type == 'NS' then
+		s1 = ' '..rr.ns;
+	elseif rr.type == 'SRV' then
+		s1 = ' '..SRV_tostring(rr);
+	elseif rr.type == 'TXT' then
+		s1 = ' '..rr.txt;
+	else
+		s1 = ' <UNKNOWN RDATA TYPE>';
+	end
+	return s0..s1;
+end
 
 
-local rrs_metatable = {}    -- - - - - - - - - - - - - - - - - -  rrs_metatable
-function rrs_metatable.__tostring (rrs)
-  local t = {}
-  for i,rr in pairs (rrs) do  append (t, tostring (rr)..'\n')  end
-  return table.concat (t)
-  end
+local rrs_metatable = {};    -- - - - - - - - - - - - - - - - - -  rrs_metatable
+function rrs_metatable.__tostring(rrs)
+	local t = {};
+	for i,rr in pairs(rrs) do
+		append(t, tostring(rr)..'\n');
+	end
+	return table.concat(t);
+end
 
 
-local cache_metatable = {}    -- - - - - - - - - - - - - - - -  cache_metatable
-function cache_metatable.__tostring (cache)
-  local time = socket.gettime ()
-  local t = {}
-  for class,types in pairs (cache) do
-    for type,names in pairs (types) do
-      for name,rrs in pairs (names) do
-        prune (rrs, time)
-        append (t, tostring (rrs))  end  end  end
-  return table.concat (t)
-  end
+local cache_metatable = {};    -- - - - - - - - - - - - - - - -  cache_metatable
+function cache_metatable.__tostring(cache)
+	local time = socket.gettime();
+	local t = {};
+	for class,types in pairs(cache) do
+		for type,names in pairs(types) do
+			for name,rrs in pairs(names) do
+				prune(rrs, time);
+				append(t, tostring(rrs));
+			end
+		end
+	end
+	return table.concat(t);
+end
 
 
-function resolver:new ()    -- - - - - - - - - - - - - - - - - - - - - resolver
-  local r = { active = {}, cache = {}, unsorted = {} }
-  setmetatable (r, resolver)
-  setmetatable (r.cache, cache_metatable)
-  setmetatable (r.unsorted, { __mode = 'kv' })
-  return r
-  end
+function resolver:new()    -- - - - - - - - - - - - - - - - - - - - - resolver
+	local r = { active = {}, cache = {}, unsorted = {} };
+	setmetatable(r, resolver);
+	setmetatable(r.cache, cache_metatable);
+	setmetatable(r.unsorted, { __mode = 'kv' });
+	return r;
+end
 
 
 -- packet layer -------------------------------------------------- packet layer
 
 
-function dns.random (...)    -- - - - - - - - - - - - - - - - - - -  dns.random
-  math.randomseed (10000*socket.gettime ())
-  dns.random = math.random
-  return dns.random (...)
-  end
+function dns.random(...)    -- - - - - - - - - - - - - - - - - - -  dns.random
+	math.randomseed(10000*socket.gettime());
+	dns.random = math.random;
+	return dns.random(...);
+end
 
 
-local function encodeHeader (o)    -- - - - - - - - - - - - - - -  encodeHeader
-
-  o = o or {}
+local function encodeHeader(o)    -- - - - - - - - - - - - - - -  encodeHeader
+	o = o or {};
+	o.id = o.id or dns.random(0, 0xffff); -- 16b	(random) id
 
-  o.id = o.id or		-- 16b	(random) id
-    dns.random (0, 0xffff)
-
-  o.rd = o.rd or 1		--  1b  1 recursion desired
-  o.tc = o.tc or 0		--  1b	1 truncated response
-  o.aa = o.aa or 0		--  1b	1 authoritative response
-  o.opcode = o.opcode or 0	--  4b	0 query
-				--      1 inverse query
+	o.rd = o.rd or 1;		--  1b  1 recursion desired
+	o.tc = o.tc or 0;		--  1b	1 truncated response
+	o.aa = o.aa or 0;		--  1b	1 authoritative response
+	o.opcode = o.opcode or 0;	--  4b	0 query
+				--  1 inverse query
 				--	2 server status request
 				--	3-15 reserved
-  o.qr = o.qr or 0		--  1b	0 query, 1 response
+	o.qr = o.qr or 0;		--  1b	0 query, 1 response
 
-  o.rcode = o.rcode or 0	--  4b  0 no error
+	o.rcode = o.rcode or 0;	--  4b  0 no error
 				--	1 format error
 				--	2 server failure
 				--	3 name error
 				--	4 not implemented
 				--	5 refused
 				--	6-15 reserved
-  o.z  = o.z  or 0		--  3b  0 resvered
-  o.ra = o.ra or 0		--  1b  1 recursion available
+	o.z = o.z  or 0;		--  3b  0 resvered
+	o.ra = o.ra or 0;		--  1b  1 recursion available
 
-  o.qdcount = o.qdcount or 1	-- 16b	number of question RRs
-  o.ancount = o.ancount or 0	-- 16b	number of answers RRs
-  o.nscount = o.nscount or 0	-- 16b	number of nameservers RRs
-  o.arcount = o.arcount or 0	-- 16b  number of additional RRs
+	o.qdcount = o.qdcount or 1;	-- 16b	number of question RRs
+	o.ancount = o.ancount or 0;	-- 16b	number of answers RRs
+	o.nscount = o.nscount or 0;	-- 16b	number of nameservers RRs
+	o.arcount = o.arcount or 0;	-- 16b  number of additional RRs
 
-  -- string.char() rounds, so prevent roundup with -0.4999
-  local header = string.char (
-    highbyte (o.id),  o.id %0x100,
-    o.rd + 2*o.tc + 4*o.aa + 8*o.opcode + 128*o.qr,
-    o.rcode + 16*o.z + 128*o.ra,
-    highbyte (o.qdcount),  o.qdcount %0x100,
-    highbyte (o.ancount),  o.ancount %0x100,
-    highbyte (o.nscount),  o.nscount %0x100,
-    highbyte (o.arcount),  o.arcount %0x100 )
+	-- string.char() rounds, so prevent roundup with -0.4999
+	local header = string.char(
+		highbyte(o.id), o.id %0x100,
+		o.rd + 2*o.tc + 4*o.aa + 8*o.opcode + 128*o.qr,
+		o.rcode + 16*o.z + 128*o.ra,
+		highbyte(o.qdcount),  o.qdcount %0x100,
+		highbyte(o.ancount),  o.ancount %0x100,
+		highbyte(o.nscount),  o.nscount %0x100,
+		highbyte(o.arcount),  o.arcount %0x100
+	);
 
-  return header, o.id
-  end
+	return header, o.id;
+end
 
 
-local function encodeName (name)    -- - - - - - - - - - - - - - - - encodeName
-  local t = {}
-  for part in string.gmatch (name, '[^.]+') do
-    append (t, string.char (string.len (part)))
-    append (t, part)
-    end
-  append (t, string.char (0))
-  return table.concat (t)
-  end
+local function encodeName(name)    -- - - - - - - - - - - - - - - - encodeName
+	local t = {};
+	for part in string.gmatch(name, '[^.]+') do
+		append(t, string.char(string.len(part)));
+		append(t, part);
+	end
+	append(t, string.char(0));
+	return table.concat(t);
+end
 
 
-local function encodeQuestion (qname, qtype, qclass)    -- - - - encodeQuestion
-  qname  = encodeName (qname)
-  qtype  = dns.typecode[qtype or 'a']
-  qclass = dns.classcode[qclass or 'in']
-  return qname..qtype..qclass;
-  end
+local function encodeQuestion(qname, qtype, qclass)    -- - - - encodeQuestion
+	qname  = encodeName(qname);
+	qtype  = dns.typecode[qtype or 'a'];
+	qclass = dns.classcode[qclass or 'in'];
+	return qname..qtype..qclass;
+end
 
 
-function resolver:byte (len)    -- - - - - - - - - - - - - - - - - - - - - byte
-  len = len or 1
-  local offset = self.offset
-  local last = offset + len - 1
-  if last > #self.packet then
-    error (string.format ('out of bounds: %i>%i', last, #self.packet))  end
-  self.offset = offset + len
-  return string.byte (self.packet, offset, last)
-  end
+function resolver:byte(len)    -- - - - - - - - - - - - - - - - - - - - - byte
+	len = len or 1;
+	local offset = self.offset;
+	local last = offset + len - 1;
+	if last > #self.packet then
+		error(string.format('out of bounds: %i>%i', last, #self.packet));
+	end
+	self.offset = offset + len;
+	return string.byte(self.packet, offset, last);
+end
 
 
-function resolver:word ()    -- - - - - - - - - - - - - - - - - - - - - -  word
-  local b1, b2 = self:byte (2)
-  return 0x100*b1 + b2
-  end
+function resolver:word()    -- - - - - - - - - - - - - - - - - - - - - -  word
+	local b1, b2 = self:byte(2);
+	return 0x100*b1 + b2;
+end
 
 
 function resolver:dword ()    -- - - - - - - - - - - - - - - - - - - - -  dword
-  local b1, b2, b3, b4 = self:byte (4)
-  --print ('dword', b1, b2, b3, b4)
-  return 0x1000000*b1 + 0x10000*b2 + 0x100*b3 + b4
-  end
+	local b1, b2, b3, b4 = self:byte(4);
+	--print('dword', b1, b2, b3, b4);
+	return 0x1000000*b1 + 0x10000*b2 + 0x100*b3 + b4;
+end
 
 
-function resolver:sub (len)    -- - - - - - - - - - - - - - - - - - - - - - sub
-  len = len or 1
-  local s = string.sub (self.packet, self.offset, self.offset + len - 1)
-  self.offset = self.offset + len
-  return s
-  end
+function resolver:sub(len)    -- - - - - - - - - - - - - - - - - - - - - - sub
+	len = len or 1;
+	local s = string.sub(self.packet, self.offset, self.offset + len - 1);
+	self.offset = self.offset + len;
+	return s;
+end
 
 
-function resolver:header (force)    -- - - - - - - - - - - - - - - - - - header
+function resolver:header(force)    -- - - - - - - - - - - - - - - - - - header
+	local id = self:word();
+	--print(string.format(':header  id  %x', id));
+	if not self.active[id] and not force then return nil; end
 
-  local id = self:word ()
-  --print (string.format (':header  id  %x', id))
-  if not self.active[id] and not force then  return nil  end
+	local h = { id = id };
 
-  local h = { id = id }
-
-  local b1, b2 = self:byte (2)
+	local b1, b2 = self:byte(2);
 
-  h.rd      = b1 %2
-  h.tc      = b1 /2%2
-  h.aa      = b1 /4%2
-  h.opcode  = b1 /8%16
-  h.qr      = b1 /128
+	h.rd      = b1 %2;
+	h.tc      = b1 /2%2;
+	h.aa      = b1 /4%2;
+	h.opcode  = b1 /8%16;
+	h.qr      = b1 /128;
 
-  h.rcode   = b2 %16
-  h.z       = b2 /16%8
-  h.ra      = b2 /128
+	h.rcode   = b2 %16;
+	h.z       = b2 /16%8;
+	h.ra      = b2 /128;
 
-  h.qdcount = self:word ()
-  h.ancount = self:word ()
-  h.nscount = self:word ()
-  h.arcount = self:word ()
+	h.qdcount = self:word();
+	h.ancount = self:word();
+	h.nscount = self:word();
+	h.arcount = self:word();
 
-  for k,v in pairs (h) do  h[k] = v-v%1  end
+	for k,v in pairs(h) do h[k] = v-v%1; end
 
-  return h
-  end
+	return h;
+end
 
 
-function resolver:name ()    -- - - - - - - - - - - - - - - - - - - - - -  name
-  local remember, pointers = nil, 0
-  local len = self:byte ()
-  local n = {}
-  while len > 0 do
-    if len >= 0xc0 then    -- name is "compressed"
-      pointers = pointers + 1
-      if pointers >= 20 then  error ('dns error: 20 pointers')  end
-      local offset = ((len-0xc0)*0x100) + self:byte ()
-      remember = remember or self.offset
-      self.offset = offset + 1    -- +1 for lua
-    else    -- name is not compressed
-      append (n, self:sub (len)..'.')
-      end
-    len = self:byte ()
-    end
-  self.offset = remember or self.offset
-  return table.concat (n)
-  end
+function resolver:name()    -- - - - - - - - - - - - - - - - - - - - - -  name
+	local remember, pointers = nil, 0;
+	local len = self:byte();
+	local n = {};
+	while len > 0 do
+		if len >= 0xc0 then    -- name is "compressed"
+			pointers = pointers + 1;
+			if pointers >= 20 then error('dns error: 20 pointers'); end;
+			local offset = ((len-0xc0)*0x100) + self:byte();
+			remember = remember or self.offset;
+			self.offset = offset + 1;    -- +1 for lua
+		else    -- name is not compressed
+			append(n, self:sub(len)..'.');
+		end
+		len = self:byte();
+	end
+	self.offset = remember or self.offset;
+	return table.concat(n);
+end
 
 
-function resolver:question ()    -- - - - - - - - - - - - - - - - - -  question
-  local q = {}
-  q.name  = self:name ()
-  q.type  = dns.type[self:word ()]
-  q.class = dns.class[self:word ()]
-  return q
-  end
+function resolver:question()    -- - - - - - - - - - - - - - - - - -  question
+	local q = {};
+	q.name  = self:name();
+	q.type  = dns.type[self:word()];
+	q.class = dns.class[self:word()];
+	return q;
+end
 
 
-function resolver:A (rr)    -- - - - - - - - - - - - - - - - - - - - - - - -  A
-  local b1, b2, b3, b4 = self:byte (4)
-  rr.a = string.format ('%i.%i.%i.%i', b1, b2, b3, b4)
-  end
+function resolver:A(rr)    -- - - - - - - - - - - - - - - - - - - - - - - -  A
+	local b1, b2, b3, b4 = self:byte(4);
+	rr.a = string.format('%i.%i.%i.%i', b1, b2, b3, b4);
+end
 
 
-function resolver:CNAME (rr)    -- - - - - - - - - - - - - - - - - - - -  CNAME
-  rr.cname = self:name ()
-  end
+function resolver:CNAME(rr)    -- - - - - - - - - - - - - - - - - - - -  CNAME
+	rr.cname = self:name();
+end
 
 
-function resolver:MX (rr)    -- - - - - - - - - - - - - - - - - - - - - - -  MX
-  rr.pref = self:word ()
-  rr.mx   = self:name ()
-  end
+function resolver:MX(rr)    -- - - - - - - - - - - - - - - - - - - - - - -  MX
+	rr.pref = self:word();
+	rr.mx   = self:name();
+end
 
 
-function resolver:LOC_nibble_power ()    -- - - - - - - - - -  LOC_nibble_power
-  local b = self:byte ()
-  --print ('nibbles', ((b-(b%0x10))/0x10), (b%0x10))
-  return ((b-(b%0x10))/0x10) * (10^(b%0x10))
-  end
+function resolver:LOC_nibble_power()    -- - - - - - - - - -  LOC_nibble_power
+	local b = self:byte();
+	--print('nibbles', ((b-(b%0x10))/0x10), (b%0x10));
+	return ((b-(b%0x10))/0x10) * (10^(b%0x10));
+end
 
 
-function resolver:LOC (rr)    -- - - - - - - - - - - - - - - - - - - - - -  LOC
-  rr.version = self:byte ()
-  if rr.version == 0 then
-    rr.loc           = rr.loc or {}
-    rr.loc.size      = self:LOC_nibble_power ()
-    rr.loc.horiz_pre = self:LOC_nibble_power ()
-    rr.loc.vert_pre  = self:LOC_nibble_power ()
-    rr.loc.latitude  = self:dword ()
-    rr.loc.longitude = self:dword ()
-    rr.loc.altitude  = self:dword ()
-    end  end
+function resolver:LOC(rr)    -- - - - - - - - - - - - - - - - - - - - - -  LOC
+	rr.version = self:byte();
+	if rr.version == 0 then
+		rr.loc           = rr.loc or {};
+		rr.loc.size      = self:LOC_nibble_power();
+		rr.loc.horiz_pre = self:LOC_nibble_power();
+		rr.loc.vert_pre  = self:LOC_nibble_power();
+		rr.loc.latitude  = self:dword();
+		rr.loc.longitude = self:dword();
+		rr.loc.altitude  = self:dword();
+	end
+end
 
 
-local function LOC_tostring_degrees (f, pos, neg)    -- - - - - - - - - - - - -
-  f = f - 0x80000000
-  if f < 0 then  pos = neg  f = -f  end
-  local deg, min, msec
-  msec = f%60000
-  f    = (f-msec)/60000
-  min  = f%60
-  deg = (f-min)/60
-  return string.format ('%3d %2d %2.3f %s', deg, min, msec/1000, pos)
-  end
+local function LOC_tostring_degrees(f, pos, neg)    -- - - - - - - - - - - - -
+	f = f - 0x80000000;
+	if f < 0 then pos = neg; f = -f; end
+	local deg, min, msec;
+	msec = f%60000;
+	f    = (f-msec)/60000;
+	min  = f%60;
+	deg = (f-min)/60;
+	return string.format('%3d %2d %2.3f %s', deg, min, msec/1000, pos);
+end
 
 
-function resolver.LOC_tostring (rr)    -- - - - - - - - - - - - -  LOC_tostring
-
-  local t = {}
+function resolver.LOC_tostring(rr)    -- - - - - - - - - - - - -  LOC_tostring
+	local t = {};
 
-  --[[
-  for k,name in pairs { 'size', 'horiz_pre', 'vert_pre',
-			'latitude', 'longitude', 'altitude' } do
-    append (t, string.format ('%4s%-10s: %12.0f\n', '', name, rr.loc[name]))
-    end
-  --]]
+	--[[
+	for k,name in pairs { 'size', 'horiz_pre', 'vert_pre', 'latitude', 'longitude', 'altitude' } do
+		append(t, string.format('%4s%-10s: %12.0f\n', '', name, rr.loc[name]));
+	end
+	--]]
 
-  append ( t, string.format (
-    '%s    %s    %.2fm %.2fm %.2fm %.2fm',
-    LOC_tostring_degrees (rr.loc.latitude, 'N', 'S'),
-    LOC_tostring_degrees (rr.loc.longitude, 'E', 'W'),
-    (rr.loc.altitude - 10000000) / 100,
-    rr.loc.size / 100,
-    rr.loc.horiz_pre / 100,
-    rr.loc.vert_pre / 100 ) )
+	append(t, string.format(
+		'%s    %s    %.2fm %.2fm %.2fm %.2fm',
+		LOC_tostring_degrees (rr.loc.latitude, 'N', 'S'),
+		LOC_tostring_degrees (rr.loc.longitude, 'E', 'W'),
+		(rr.loc.altitude - 10000000) / 100,
+		rr.loc.size / 100,
+		rr.loc.horiz_pre / 100,
+		rr.loc.vert_pre / 100
+	));
 
-  return table.concat (t)
-  end
+	return table.concat(t);
+end
 
 
-function resolver:NS (rr)    -- - - - - - - - - - - - - - - - - - - - - - -  NS
-  rr.ns = self:name ()
-  end
+function resolver:NS(rr)    -- - - - - - - - - - - - - - - - - - - - - - -  NS
+	rr.ns = self:name();
+end
 
 
-function resolver:SOA (rr)    -- - - - - - - - - - - - - - - - - - - - - -  SOA
-  end
+function resolver:SOA(rr)    -- - - - - - - - - - - - - - - - - - - - - -  SOA
+end
 
 
-function resolver:SRV (rr)    -- - - - - - - - - - - - - - - - - - - - - -  SRV
-  rr.srv = {}
-  rr.srv.priority = self:word ()
-  rr.srv.weight   = self:word ()
-  rr.srv.port     = self:word ()
-  rr.srv.target   = self:name ()
-  end
+function resolver:SRV(rr)    -- - - - - - - - - - - - - - - - - - - - - -  SRV
+	  rr.srv = {};
+	  rr.srv.priority = self:word();
+	  rr.srv.weight   = self:word();
+	  rr.srv.port     = self:word();
+	  rr.srv.target   = self:name();
+end
 
 
-function SRV_tostring (rr)    -- - - - - - - - - - - - - - - - - - SRV_tostring
-  local s = rr.srv
-  return string.format ( '%5d %5d %5d %s',
-                         s.priority, s.weight, s.port, s.target )
-  end
+function SRV_tostring(rr)    -- - - - - - - - - - - - - - - - - - SRV_tostring
+	local s = rr.srv;
+	return string.format( '%5d %5d %5d %s', s.priority, s.weight, s.port, s.target );
+end
 
 
-function resolver:TXT (rr)    -- - - - - - - - - - - - - - - - - - - - - -  TXT
-  rr.txt = self:sub (rr.rdlength)
-  end
+function resolver:TXT(rr)    -- - - - - - - - - - - - - - - - - - - - - -  TXT
+	rr.txt = self:sub (rr.rdlength);
+end
 
 
-function resolver:rr ()    -- - - - - - - - - - - - - - - - - - - - - - - -  rr
-  local rr = {}
-  setmetatable (rr, rr_metatable)
-  rr.name     = self:name (self)
-  rr.type     = dns.type[self:word ()] or rr.type
-  rr.class    = dns.class[self:word ()] or rr.class
-  rr.ttl      = 0x10000*self:word () + self:word ()
-  rr.rdlength = self:word ()
+function resolver:rr()    -- - - - - - - - - - - - - - - - - - - - - - - -  rr
+	local rr = {};
+	setmetatable(rr, rr_metatable);
+	rr.name     = self:name(self);
+	rr.type     = dns.type[self:word()] or rr.type;
+	rr.class    = dns.class[self:word()] or rr.class;
+	rr.ttl      = 0x10000*self:word() + self:word();
+	rr.rdlength = self:word();
 
-  if rr.ttl == 0 then  -- pass
-  else  rr.tod = self.time + rr.ttl  end
+	if rr.ttl <= 0 then
+		rr.tod = self.time + 30;
+	else
+		rr.tod = self.time + rr.ttl;
+	end
 
-  local remember = self.offset
-  local rr_parser = self[dns.type[rr.type]]
-  if rr_parser then  rr_parser (self, rr)  end
-  self.offset = remember
-  rr.rdata = self:sub (rr.rdlength)
-  return rr
-  end
+	local remember = self.offset;
+	local rr_parser = self[dns.type[rr.type]];
+	if rr_parser then rr_parser(self, rr); end
+	self.offset = remember;
+	rr.rdata = self:sub(rr.rdlength);
+	return rr;
+end
 
 
 function resolver:rrs (count)    -- - - - - - - - - - - - - - - - - - - - - rrs
-  local rrs = {}
-  for i = 1,count do  append (rrs, self:rr ())  end
-  return rrs
-  end
+	local rrs = {};
+	for i = 1,count do append(rrs, self:rr()); end
+	return rrs;
+end
 
 
-function resolver:decode (packet, force)    -- - - - - - - - - - - - - - decode
+function resolver:decode(packet, force)    -- - - - - - - - - - - - - - decode
+	self.packet, self.offset = packet, 1;
+	local header = self:header(force);
+	if not header then return nil; end
+	local response = { header = header };
 
-  self.packet, self.offset = packet, 1
-  local header = self:header (force)
-  if not header then  return nil  end
-  local response = { header = header }
+	response.question = {};
+	local offset = self.offset;
+	for i = 1,response.header.qdcount do
+		append(response.question, self:question());
+	end
+	response.question.raw = string.sub(self.packet, offset, self.offset - 1);
 
-  response.question = {}
-  local offset = self.offset
-  for i = 1,response.header.qdcount do
-    append (response.question, self:question ())  end
-  response.question.raw = string.sub (self.packet, offset, self.offset - 1)
+	if not force then
+		if not self.active[response.header.id] or not self.active[response.header.id][response.question.raw] then
+			return nil;
+		end
+	end
 
-  if not force then
-    if not self.active[response.header.id] or
-       not self.active[response.header.id][response.question.raw] then
-      return nil  end  end
+	response.answer     = self:rrs(response.header.ancount);
+	response.authority  = self:rrs(response.header.nscount);
+	response.additional = self:rrs(response.header.arcount);
 
-  response.answer     = self:rrs (response.header.ancount)
-  response.authority  = self:rrs (response.header.nscount)
-  response.additional = self:rrs (response.header.arcount)
-
-  return response
-  end
+	return response;
+end
 
 
 -- socket layer -------------------------------------------------- socket layer
 
 
-resolver.delays = { 1, 3, 11, 45 }
+resolver.delays = { 1, 3 };
 
 
-function resolver:addnameserver (address)    -- - - - - - - - - - addnameserver
-  self.server = self.server or {}
-  append (self.server, address)
-  end
+function resolver:addnameserver(address)    -- - - - - - - - - - addnameserver
+	self.server = self.server or {};
+	append(self.server, address);
+end
+
+
+function resolver:setnameserver(address)    -- - - - - - - - - - setnameserver
+	self.server = {};
+	self:addnameserver(address);
+end
 
 
-function resolver:setnameserver (address)    -- - - - - - - - - - setnameserver
-  self.server = {}
-  self:addnameserver (address)
-  end
-
-
-function resolver:adddefaultnameservers ()    -- - - - -  adddefaultnameservers
-  local resolv_conf = io.open("/etc/resolv.conf");
-  if resolv_conf then
-	  for line in resolv_conf:lines() do
-		local address = string.match (line, 'nameserver%s+(%d+%.%d+%.%d+%.%d+)')
-		if address then  self:addnameserver (address)  end
-	  end
-  else -- FIXME correct for windows, using opendns nameservers for now
-	self:addnameserver ("208.67.222.222")
-	self:addnameserver ("208.67.220.220")
-  end
+function resolver:adddefaultnameservers()    -- - - - -  adddefaultnameservers
+	if is_windows then
+		if windows then
+			for _, server in ipairs(windows.get_nameservers()) do
+				self:addnameserver(server);
+			end
+		end
+		if not self.server or #self.server == 0 then
+			-- TODO log warning about no nameservers, adding opendns servers as fallback
+			self:addnameserver("208.67.222.222");
+			self:addnameserver("208.67.220.220") ; 	
+		end
+	else -- posix
+		local resolv_conf = io.open("/etc/resolv.conf");
+		if resolv_conf then
+			for line in resolv_conf:lines() do
+				local address = line:gsub("#.*$", ""):match('^%s*nameserver%s+(%d+%.%d+%.%d+%.%d+)%s*$');
+				if address then self:addnameserver(address) end
+			end
+		end
+		if not self.server or #self.server == 0 then
+			-- TODO log warning about no nameservers, adding localhost as the default nameserver
+			self:addnameserver("127.0.0.1");
+		end
+	end
 end
 
 
-function resolver:getsocket (servernum)    -- - - - - - - - - - - - - getsocket
+function resolver:getsocket(servernum)    -- - - - - - - - - - - - - getsocket
+	self.socket = self.socket or {};
+	self.socketset = self.socketset or {};
 
-  self.socket = self.socket or {}
-  self.socketset = self.socketset or {}
-
-  local sock = self.socket[servernum]
-  if sock then  return sock  end
+	local sock = self.socket[servernum];
+	if sock then return sock; end
 
-  sock = socket.udp ()
-  if self.socket_wrapper then  sock = self.socket_wrapper (sock)  end
-  sock:settimeout (0)
-  -- todo: attempt to use a random port, fallback to 0
-  sock:setsockname ('*', 0)
-  sock:setpeername (self.server[servernum], 53)
-  self.socket[servernum] = sock
-  self.socketset[sock] = sock
-  return sock
-  end
+	sock = socket.udp();
+	if self.socket_wrapper then sock = self.socket_wrapper(sock, self); end
+	sock:settimeout(0);
+	-- todo: attempt to use a random port, fallback to 0
+	sock:setsockname('*', 0);
+	sock:setpeername(self.server[servernum], 53);
+	self.socket[servernum] = sock;
+	self.socketset[sock] = servernum;
+	return sock;
+end
 
+function resolver:voidsocket(sock)
+	if self.socket[sock] then
+		self.socketset[self.socket[sock]] = nil;
+		self.socket[sock] = nil;
+	elseif self.socketset[sock] then
+		self.socket[self.socketset[sock]] = nil;
+		self.socketset[sock] = nil;
+	end
+end
 
-function resolver:socket_wrapper_set (func)  -- - - - - - - socket_wrapper_set
-  self.socket_wrapper = func
-  end
+function resolver:socket_wrapper_set(func)  -- - - - - - - socket_wrapper_set
+	self.socket_wrapper = func;
+end
 
 
 function resolver:closeall ()    -- - - - - - - - - - - - - - - - - -  closeall
-  for i,sock in ipairs (self.socket) do  self.socket[i]:close ()  end
-  self.socket = {}
-  end
+	for i,sock in ipairs(self.socket) do
+		self.socket[i] = nil;
+		self.socketset[sock] = nil;
+		sock:close();
+	end
+end
 
 
-function resolver:remember (rr, type)    -- - - - - - - - - - - - - -  remember
-
-  --print ('remember', type, rr.class, rr.type, rr.name)
+function resolver:remember(rr, type)    -- - - - - - - - - - - - - -  remember
+	--print ('remember', type, rr.class, rr.type, rr.name)
 
-  if type ~= '*' then
-    type = rr.type
-    local all = get (self.cache, rr.class, '*', rr.name)
-    --print ('remember all', all)
-    if all then  append (all, rr)  end
-    end
+	if type ~= '*' then
+		type = rr.type;
+		local all = get(self.cache, rr.class, '*', rr.name);
+		--print('remember all', all);
+		if all then append(all, rr); end
+	end
 
-  self.cache = self.cache or setmetatable ({}, cache_metatable)
-  local rrs = get (self.cache, rr.class, type, rr.name) or
-    set (self.cache, rr.class, type, rr.name, setmetatable ({}, rrs_metatable))
-  append (rrs, rr)
+	self.cache = self.cache or setmetatable({}, cache_metatable);
+	local rrs = get(self.cache, rr.class, type, rr.name) or
+		set(self.cache, rr.class, type, rr.name, setmetatable({}, rrs_metatable));
+	append(rrs, rr);
 
-  if type == 'MX' then  self.unsorted[rrs] = true  end
-  end
+	if type == 'MX' then self.unsorted[rrs] = true; end
+end
 
 
-local function comp_mx (a, b)    -- - - - - - - - - - - - - - - - - - - comp_mx
-  return (a.pref == b.pref) and (a.mx < b.mx) or (a.pref < b.pref)
-  end
+local function comp_mx(a, b)    -- - - - - - - - - - - - - - - - - - - comp_mx
+	return (a.pref == b.pref) and (a.mx < b.mx) or (a.pref < b.pref);
+end
 
 
 function resolver:peek (qname, qtype, qclass)    -- - - - - - - - - - - -  peek
-  qname, qtype, qclass = standardize (qname, qtype, qclass)
-  local rrs = get (self.cache, qclass, qtype, qname)
-  if not rrs then  return nil  end
-  if prune (rrs, socket.gettime ()) and qtype == '*' or not next (rrs) then
-    set (self.cache, qclass, qtype, qname, nil)  return nil  end
-  if self.unsorted[rrs] then  table.sort (rrs, comp_mx)  end
-  return rrs
-  end
-
-
-function resolver:purge (soft)    -- - - - - - - - - - - - - - - - - - -  purge
-  if soft == 'soft' then
-    self.time = socket.gettime ()
-    for class,types in pairs (self.cache or {}) do
-      for type,names in pairs (types) do
-        for name,rrs in pairs (names) do
-          prune (rrs, self.time, 'soft')
-          end  end  end
-  else  self.cache = {}  end
-  end
+	qname, qtype, qclass = standardize(qname, qtype, qclass);
+	local rrs = get(self.cache, qclass, qtype, qname);
+	if not rrs then return nil; end
+	if prune(rrs, socket.gettime()) and qtype == '*' or not next(rrs) then
+		set(self.cache, qclass, qtype, qname, nil);
+		return nil;
+	end
+	if self.unsorted[rrs] then table.sort (rrs, comp_mx); end
+	return rrs;
+end
 
 
-function resolver:query (qname, qtype, qclass)    -- - - - - - - - - - -- query
-
-  qname, qtype, qclass = standardize (qname, qtype, qclass)
-
-  if not self.server then self:adddefaultnameservers ()  end
-
-  local question = encodeQuestion (qname, qtype, qclass)
-  local peek = self:peek (qname, qtype, qclass)
-  if peek then  return peek  end
-
-  local header, id = encodeHeader ()
-  --print ('query  id', id, qclass, qtype, qname)
-  local o = { packet = header..question,
-              server = 1,
-              delay  = 1,
-              retry  = socket.gettime () + self.delays[1] }
-  self:getsocket (o.server):send (o.packet)
-
-  -- remember the query
-  self.active[id] = self.active[id] or {}
-  self.active[id][question] = o
-
-  -- remember which coroutine wants the answer
-  local co = coroutine.running ()
-  if co then
-    set (self.wanted, qclass, qtype, qname, co, true)
-    --set (self.yielded, co, qclass, qtype, qname, true)
-  end
+function resolver:purge(soft)    -- - - - - - - - - - - - - - - - - - -  purge
+	if soft == 'soft' then
+		self.time = socket.gettime();
+		for class,types in pairs(self.cache or {}) do
+			for type,names in pairs(types) do
+				for name,rrs in pairs(names) do
+					prune(rrs, self.time, 'soft')
+				end
+			end
+		end
+	else self.cache = {}; end
 end
 
 
+function resolver:query(qname, qtype, qclass)    -- - - - - - - - - - -- query
+	qname, qtype, qclass = standardize(qname, qtype, qclass)
 
-function resolver:receive (rset)    -- - - - - - - - - - - - - - - - -  receive
+	if not self.server then self:adddefaultnameservers(); end
+
+	local question = encodeQuestion(qname, qtype, qclass);
+	local peek = self:peek (qname, qtype, qclass);
+	if peek then return peek; end
 
-  --print 'receive'  print (self.socket)
-  self.time = socket.gettime ()
-  rset = rset or self.socket
+	local header, id = encodeHeader();
+	--print ('query  id', id, qclass, qtype, qname)
+	local o = {
+		packet = header..question,
+		server = self.best_server,
+		delay  = 1,
+		retry  = socket.gettime() + self.delays[1]
+	};
+
+  -- remember the query
+	self.active[id] = self.active[id] or {};
+	self.active[id][question] = o;
 
-  local response
-  for i,sock in pairs (rset) do
+  -- remember which coroutine wants the answer
+	local co = coroutine.running();
+	if co then
+		set(self.wanted, qclass, qtype, qname, co, true);
+		--set(self.yielded, co, qclass, qtype, qname, true);
+	end
+
+	self:getsocket (o.server):send (o.packet)
+end
+
+function resolver:servfail(sock)
+	-- Resend all queries for this server
 
-    if self.socketset[sock] then
-    local packet = sock:receive ()
-    if packet then
+	local num = self.socketset[sock]
+
+	-- Socket is dead now
+	self:voidsocket(sock);
 
-    response = self:decode (packet)
-    if response then
-    --print 'received response'
-    --self.print (response)
+	-- Find all requests to the down server, and retry on the next server
+	self.time = socket.gettime();
+	for id,queries in pairs(self.active) do
+		for question,o in pairs(queries) do
+			if o.server == num then -- This request was to the broken server
+				o.server = o.server + 1 -- Use next server
+				if o.server > #self.server then
+					o.server = 1;
+				end
 
-    for i,section in pairs { 'answer', 'authority', 'additional' } do
-      for j,rr in pairs (response[section]) do
-        self:remember (rr, response.question[1].type)  end  end
+				o.retries = (o.retries or 0) + 1;
+				if o.retries >= #self.server then
+					--print('timeout');
+					queries[question] = nil;
+				else
+					local _a = self:getsocket(o.server);
+					if _a then _a:send(o.packet); end
+				end
+			end
+		end
+	end
+   
+	if num == self.best_server then
+		self.best_server = self.best_server + 1;
+		if self.best_server > #self.server then
+			-- Exhausted all servers, try first again
+			self.best_server = 1;
+		end
+	end
+end
 
-    -- retire the query
-    local queries = self.active[response.header.id]
-    if queries[response.question.raw] then
-      queries[response.question.raw] = nil  end
-    if not next (queries) then  self.active[response.header.id] = nil  end
-    if not next (self.active) then  self:closeall ()  end
+function resolver:receive(rset)    -- - - - - - - - - - - - - - - - -  receive
+	--print('receive');  print(self.socket);
+	self.time = socket.gettime();
+	rset = rset or self.socket;
+
+	local response;
+	for i,sock in pairs(rset) do
 
-    -- was the query on the wanted list?
-    local q = response.question
-    local cos = get (self.wanted, q.class, q.type, q.name)
-    if cos then
-      for co in pairs (cos) do
-        set (self.yielded, co, q.class, q.type, q.name, nil)
-	if coroutine.status(co) == "suspended" then  coroutine.resume (co)  end
-        end
-      set (self.wanted, q.class, q.type, q.name, nil)
-      end  end  end  end  end
+		if self.socketset[sock] then
+			local packet = sock:receive();
+			if packet then
+				response = self:decode(packet);
+				if response then
+					--print('received response');
+					--self.print(response);
+
+					for i,section in pairs({ 'answer', 'authority', 'additional' }) do
+						for j,rr in pairs(response[section]) do
+							self:remember(rr, response.question[1].type)
+						end
+					end
 
-  return response
-  end
+					-- retire the query
+					local queries = self.active[response.header.id];
+					if queries[response.question.raw] then
+						queries[response.question.raw] = nil;
+					end
+					if not next(queries) then self.active[response.header.id] = nil; end
+					if not next(self.active) then self:closeall(); end
+
+					-- was the query on the wanted list?
+					local q = response.question;
+					local cos = get(self.wanted, q.class, q.type, q.name);
+					if cos then
+						for co in pairs(cos) do
+							set(self.yielded, co, q.class, q.type, q.name, nil);
+							if coroutine.status(co) == "suspended" then coroutine.resume(co); end
+						end
+						set(self.wanted, q.class, q.type, q.name, nil);
+					end
+				end
+			end
+		end
+	end
+
+	return response;
+end
 
 
 function resolver:feed(sock, packet)
-  --print 'receive'  print (self.socket)
-  self.time = socket.gettime ()
+	--print('receive'); print(self.socket);
+	self.time = socket.gettime();
 
-  local response = self:decode (packet)
-  if response then
-    --print 'received response'
-    --self.print (response)
+	local response = self:decode(packet);
+	if response then
+		--print('received response');
+		--self.print(response);
 
-    for i,section in pairs { 'answer', 'authority', 'additional' } do
-      for j,rr in pairs (response[section]) do
-        self:remember (rr, response.question[1].type)
-      end
-    end
+		for i,section in pairs({ 'answer', 'authority', 'additional' }) do
+			for j,rr in pairs(response[section]) do
+				self:remember(rr, response.question[1].type);
+			end
+		end
 
-    -- retire the query
-    local queries = self.active[response.header.id]
-    if queries[response.question.raw] then
-      queries[response.question.raw] = nil
-    end
-    if not next (queries) then  self.active[response.header.id] = nil  end
-    if not next (self.active) then  self:closeall ()  end
+		-- retire the query
+		local queries = self.active[response.header.id];
+		if queries[response.question.raw] then
+			queries[response.question.raw] = nil;
+		end
+		if not next(queries) then self.active[response.header.id] = nil; end
+		if not next(self.active) then self:closeall(); end
 
-    -- was the query on the wanted list?
-    local q = response.question[1]
-    if q then
-      local cos = get (self.wanted, q.class, q.type, q.name)
-      if cos then
-        for co in pairs (cos) do
-          set (self.yielded, co, q.class, q.type, q.name, nil)
-          if coroutine.status(co) == "suspended" then coroutine.resume (co)  end
-        end
-        set (self.wanted, q.class, q.type, q.name, nil)
-      end
-    end
-  end 
+		-- was the query on the wanted list?
+		local q = response.question[1];
+		if q then
+			local cos = get(self.wanted, q.class, q.type, q.name);
+			if cos then
+				for co in pairs(cos) do
+					set(self.yielded, co, q.class, q.type, q.name, nil);
+					if coroutine.status(co) == "suspended" then coroutine.resume(co); end
+				end
+				set(self.wanted, q.class, q.type, q.name, nil);
+			end
+		end
+	end 
 
-  return response
+	return response;
 end
 
 function resolver:cancel(data)
-	local cos = get (self.wanted, unpack(data, 1, 3))
+	local cos = get(self.wanted, unpack(data, 1, 3));
 	if cos then
 		cos[data[4]] = nil;
 	end
 end
 
-function resolver:pulse ()    -- - - - - - - - - - - - - - - - - - - - -  pulse
+function resolver:pulse()    -- - - - - - - - - - - - - - - - - - - - -  pulse
+	--print(':pulse');
+	while self:receive() do end
+	if not next(self.active) then return nil; end
 
-  --print ':pulse'
-  while self:receive() do end
-  if not next (self.active) then  return nil  end
+	self.time = socket.gettime();
+	for id,queries in pairs(self.active) do
+		for question,o in pairs(queries) do
+			if self.time >= o.retry then
 
-  self.time = socket.gettime ()
-  for id,queries in pairs (self.active) do
-    for question,o in pairs (queries) do
-      if self.time >= o.retry then
+				o.server = o.server + 1;
+				if o.server > #self.server then
+					o.server = 1;
+					o.delay = o.delay + 1;
+				end
 
-        o.server = o.server + 1
-        if o.server > #self.server then
-          o.server = 1
-          o.delay = o.delay + 1
-          end
+				if o.delay > #self.delays then
+					--print('timeout');
+					queries[question] = nil;
+					if not next(queries) then self.active[id] = nil; end
+					if not next(self.active) then return nil; end
+				else
+					--print('retry', o.server, o.delay);
+					local _a = self.socket[o.server];
+					if _a then _a:send(o.packet); end
+					o.retry = self.time + self.delays[o.delay];
+				end
+			end
+		end
+	end
 
-        if o.delay > #self.delays then
-          --print ('timeout')
-          queries[question] = nil
-          if not next (queries) then  self.active[id] = nil  end
-          if not next (self.active) then  return nil  end
-        else
-          --print ('retry', o.server, o.delay)
-          local _a = self.socket[o.server];
-          if _a then _a:send (o.packet) end
-          o.retry = self.time + self.delays[o.delay]
-          end  end  end  end
-
-  if next (self.active) then  return true  end
-  return nil
-  end
+	if next(self.active) then return true; end
+	return nil;
+end
 
 
-function resolver:lookup (qname, qtype, qclass)    -- - - - - - - - - -  lookup
-  self:query (qname, qtype, qclass)
-  while self:pulse () do  socket.select (self.socket, nil, 4)  end
-  --print (self.cache)
-  return self:peek (qname, qtype, qclass)
-  end
+function resolver:lookup(qname, qtype, qclass)    -- - - - - - - - - -  lookup
+	self:query (qname, qtype, qclass)
+	while self:pulse() do socket.select(self.socket, nil, 4); end
+	--print(self.cache);
+	return self:peek(qname, qtype, qclass);
+end
 
-function resolver:lookupex (handler, qname, qtype, qclass)    -- - - - - - - - - -  lookup
-  return self:peek (qname, qtype, qclass) or self:query (qname, qtype, qclass)
-  end
+function resolver:lookupex(handler, qname, qtype, qclass)    -- - - - - - - - - -  lookup
+	return self:peek(qname, qtype, qclass) or self:query(qname, qtype, qclass);
+end
 
 
 --print ---------------------------------------------------------------- print
 
 
 local hints = {    -- - - - - - - - - - - - - - - - - - - - - - - - - - - hints
-  qr = { [0]='query', 'response' },
-  opcode = { [0]='query', 'inverse query', 'server status request' },
-  aa = { [0]='non-authoritative', 'authoritative' },
-  tc = { [0]='complete', 'truncated' },
-  rd = { [0]='recursion not desired', 'recursion desired' },
-  ra = { [0]='recursion not available', 'recursion available' },
-  z  = { [0]='(reserved)' },
-  rcode = { [0]='no error', 'format error', 'server failure', 'name error',
-            'not implemented' },
+	qr = { [0]='query', 'response' },
+	opcode = { [0]='query', 'inverse query', 'server status request' },
+	aa = { [0]='non-authoritative', 'authoritative' },
+	tc = { [0]='complete', 'truncated' },
+	rd = { [0]='recursion not desired', 'recursion desired' },
+	ra = { [0]='recursion not available', 'recursion available' },
+	z  = { [0]='(reserved)' },
+	rcode = { [0]='no error', 'format error', 'server failure', 'name error', 'not implemented' },
 
-  type = dns.type,
-  class = dns.class, }
+	type = dns.type,
+	class = dns.class
+};
 
 
-local function hint (p, s)    -- - - - - - - - - - - - - - - - - - - - - - hint
-  return (hints[s] and hints[s][p[s]]) or ''  end
+local function hint(p, s)    -- - - - - - - - - - - - - - - - - - - - - - hint
+	return (hints[s] and hints[s][p[s]]) or '';
+end
 
 
-function resolver.print (response)    -- - - - - - - - - - - - - resolver.print
+function resolver.print(response)    -- - - - - - - - - - - - - resolver.print
+	for s,s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z',
+						'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do
+		print( string.format('%-30s', 'header.'..s), response.header[s], hint(response.header, s) );
+	end
 
-  for s,s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z',
-		     'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do
-    print ( string.format ('%-30s', 'header.'..s),
-            response.header[s], hint (response.header, s) )
-    end
-
-  for i,question in ipairs (response.question) do
-    print (string.format ('question[%i].name         ', i), question.name)
-    print (string.format ('question[%i].type         ', i), question.type)
-    print (string.format ('question[%i].class        ', i), question.class)
-    end
+	for i,question in ipairs(response.question) do
+		print(string.format ('question[%i].name         ', i), question.name);
+		print(string.format ('question[%i].type         ', i), question.type);
+		print(string.format ('question[%i].class        ', i), question.class);
+	end
 
-  local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }
-  local tmp
-  for s,s in pairs {'answer', 'authority', 'additional'} do
-    for i,rr in pairs (response[s]) do
-      for j,t in pairs { 'name', 'type', 'class', 'ttl', 'rdlength' } do
-        tmp = string.format ('%s[%i].%s', s, i, t)
-        print (string.format ('%-30s', tmp), rr[t], hint (rr, t))
-        end
-      for j,t in pairs (rr) do
-        if not common[j] then
-          tmp = string.format ('%s[%i].%s', s, i, j)
-          print (string.format ('%-30s  %s', tostring(tmp), tostring(t)))
-          end  end  end  end  end
+	local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 };
+	local tmp;
+	for s,s in pairs({'answer', 'authority', 'additional'}) do
+		for i,rr in pairs(response[s]) do
+			for j,t in pairs({ 'name', 'type', 'class', 'ttl', 'rdlength' }) do
+				tmp = string.format('%s[%i].%s', s, i, t);
+				print(string.format('%-30s', tmp), rr[t], hint(rr, t));
+			end
+			for j,t in pairs(rr) do
+				if not common[j] then
+					tmp = string.format('%s[%i].%s', s, i, j);
+					print(string.format('%-30s  %s', tostring(tmp), tostring(t)));
+				end
+			end
+		end
+	end
+end
 
 
 -- module api ------------------------------------------------------ module api
 
 
-local function resolve (func, ...)    -- - - - - - - - - - - - - - resolver_get
-  dns._resolver = dns._resolver or dns.resolver ()
-  return func (dns._resolver, ...)
-  end
+local function resolve(func, ...)    -- - - - - - - - - - - - - - resolver_get
+	return func(dns._resolver, ...);
+end
 
 
 function dns.resolver ()    -- - - - - - - - - - - - - - - - - - - - - resolver
-
-  -- this function seems to be redundant with resolver.new ()
+	-- this function seems to be redundant with resolver.new ()
 
-  local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {} }
-  setmetatable (r, resolver)
-  setmetatable (r.cache, cache_metatable)
-  setmetatable (r.unsorted, { __mode = 'kv' })
-  return r
-  end
+	local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {}, best_server = 1 };
+	setmetatable (r, resolver);
+	setmetatable (r.cache, cache_metatable);
+	setmetatable (r.unsorted, { __mode = 'kv' });
+	return r;
+end
 
 
-function dns.lookup (...)    -- - - - - - - - - - - - - - - - - - - - -  lookup
-  return resolve (resolver.lookup, ...)  end
+function dns.lookup(...)    -- - - - - - - - - - - - - - - - - - - - -  lookup
+	return resolve(resolver.lookup, ...);
+end
 
 
-function dns.purge (...)    -- - - - - - - - - - - - - - - - - - - - - -  purge
-  return resolve (resolver.purge, ...)  end
+function dns.purge(...)    -- - - - - - - - - - - - - - - - - - - - - -  purge
+	return resolve(resolver.purge, ...);
+end
 
-function dns.peek (...)    -- - - - - - - - - - - - - - - - - - - - - - -  peek
-  return resolve (resolver.peek, ...)  end
+function dns.peek(...)    -- - - - - - - - - - - - - - - - - - - - - - -  peek
+	return resolve(resolver.peek, ...);
+end
 
 
-function dns.query (...)    -- - - - - - - - - - - - - - - - - - - - - -  query
-  return resolve (resolver.query, ...)  end
+function dns.query(...)    -- - - - - - - - - - - - - - - - - - - - - -  query
+	return resolve(resolver.query, ...);
+end
 
-function dns.feed (...)    -- - - - - - - - - - - - - - - - - - - - - -  feed
-  return resolve (resolver.feed, ...)  end
+function dns.feed(...)    -- - - - - - - - - - - - - - - - - - - - - -  feed
+	return resolve(resolver.feed, ...);
+end
 
 function dns.cancel(...)   -- - - - - - - - - - - - - - - - - - - - - -  cancel
-  return resolve(resolver.cancel, ...) end
+	return resolve(resolver.cancel, ...);
+end
 
-function dns:socket_wrapper_set (...)    -- - - - - - - - -  socket_wrapper_set
-  return resolve (resolver.socket_wrapper_set, ...)  end
+function dns:socket_wrapper_set(...)    -- - - - - - - - -  socket_wrapper_set
+	return resolve(resolver.socket_wrapper_set, ...);
+end
 
+dns._resolver = dns.resolver();
 
-return dns
+return dns;
--- a/net/httpserver.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/net/httpserver.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -61,7 +61,7 @@
 		end
 	else
 		-- Response we have is just a string (the body)
-		log("debug", "Sending response to %s: %s", request.id or "<none>", response or "<none>");
+		log("debug", "Sending 200 response to %s", request.id or "<none>");
 		
 		resp = { "HTTP/1.0 200 OK\r\n" };
 		t_insert(resp, "Connection: close\r\n");
@@ -89,9 +89,6 @@
 		end
 		
 		callback = (request.server and request.server.handlers[base]) or default_handler;
-		if callback == default_handler then
-			log("debug", "Default callback for this request (base: "..tostring(base)..")")
-		end
 	end
 	if callback then
 		if err then
@@ -233,7 +230,7 @@
 		end
 		request.handler.close()
 		if request.conn then
-			listener.disconnect(request.conn, "closed");
+			listener.disconnect(request.handler, "closed");
 		end
 	end
 end
@@ -251,13 +248,27 @@
 	end
 end
 
-function new_from_config(ports, default_base, handle_request)
+function set_default_handler(handler)
+	default_handler = handler;
+end
+
+function new_from_config(ports, handle_request, default_options)
+	if type(handle_request) == "string" then -- COMPAT with old plugins
+		log("warn", "Old syntax of httpserver.new_from_config being used to register %s", handle_request);
+		handle_request, default_options = default_options, { base = handle_request };
+	end
 	for _, options in ipairs(ports) do
-		local port, base, ssl, interface = 5280, default_base, false, nil;
+		local port = default_options.port or 5280;
+		local base = default_options.base;
+		local ssl = default_options.ssl or false;
+		local interface = default_options.interface;
 		if type(options) == "number" then
 			port = options;
 		elseif type(options) == "table" then
-			port, base, ssl, interface = options.port or 5280, options.path or default_base, options.ssl or false, options.interface;
+			port = options.port or port;
+			base = options.path or base;
+			ssl = options.ssl or ssl;
+			interface = options.interface or interface;
 		elseif type(options) == "string" then
 			base = options;
 		end
@@ -267,7 +278,9 @@
 			ssl.protocol = "sslv23";
 		end
 		
-		new{ port = port, base = base, handler = handle_request, ssl = ssl, type = (ssl and "ssl") or "tcp" }
+		new{ port = port, interface = interface, 
+			base = base, handler = handle_request, 
+			ssl = ssl, type = (ssl and "ssl") or "tcp" };
 	end
 end
 
--- a/net/server.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/net/server.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -157,6 +157,7 @@
 
 _maxclientsperserver = 1000
 
+_maxsslhandshake = 30 -- max handshake round-trips
 ----------------------------------// PRIVATE //--
 
 wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxconnections, startssl )    -- this function wraps a server
@@ -230,6 +231,9 @@
     handler.ssl = function( )
         return ssl
     end
+    handler.sslctx = function( )
+        return sslctx
+    end
     handler.remove = function( )
         connections = connections - 1
     end
@@ -246,7 +250,7 @@
         _socketlist[ socket ] = nil
         handler = nil
         socket = nil
-        mem_free( )
+        --mem_free( )
         out_put "server.lua: closed server handler and removed sockets from list"
     end
     handler.ip = function( )
@@ -297,6 +301,7 @@
     local ssl
 
     local dispatch = listeners.incoming or listeners.listener
+    local status = listeners.status
     local disconnect = listeners.disconnect
 
     local bufferqueue = { }    -- buffer array
@@ -336,6 +341,9 @@
     handler.ssl = function( )
         return ssl
     end
+    handler.sslctx = function ( )
+        return sslctx
+    end
     handler.send = function( _, data, i, j )
         return send( socket, data, i, j )
     end
@@ -363,17 +371,20 @@
                 send( socket, table_concat( bufferqueue, "", 1, bufferqueuelen ), 1, bufferlen )    -- forced send
             end
         end
-        _ = shutdown and shutdown( socket )
-        socket:close( )
-        _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
-        _socketlist[ socket ] = nil
+        if socket then
+          _ = shutdown and shutdown( socket )
+          socket:close( )
+          _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
+          _socketlist[ socket ] = nil
+          socket = nil
+        else
+          out_put "server.lua: socket already closed"
+        end
         if handler then
             _writetimes[ handler ] = nil
             _closelist[ handler ] = nil
             handler = nil
         end
-        socket = nil
-        mem_free( )
 	if server then
 		server.remove( )
 	end
@@ -396,9 +407,7 @@
             handler.write = idfalse    -- dont write anymore
             return false
         elseif socket and not _sendlist[ socket ] then
-            _sendlistlen = _sendlistlen + 1
-            _sendlist[ _sendlistlen ] = socket
-            _sendlist[ socket ] = _sendlistlen
+            _sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
         end
         bufferqueuelen = bufferqueuelen + 1
         bufferqueue[ bufferqueuelen ] = data
@@ -446,9 +455,7 @@
             handler.write = write
             if noread then
                 noread = false
-                _readlistlen = _readlistlen + 1
-                _readlist[ socket ] = _readlistlen
-                _readlist[ _readlistlen ] = socket
+                _readlistlen = addsocket(_readlist, socket, _readlistlen)
                 _readtimes[ handler ] = _currenttime
             end
             if nosend then
@@ -472,10 +479,10 @@
             readtraffic = readtraffic + count
             _readtraffic = _readtraffic + count
             _readtimes[ handler ] = _currenttime
-            --out_put( "server.lua: read data '", buffer, "', error: ", err )
+            --out_put( "server.lua: read data '", buffer:gsub("[^%w%p ]", "."), "', error: ", err )
             return dispatch( handler, buffer, err )
         else    -- connections was closed or fatal error
-            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " error: ", tostring(err) )
+            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " read error: ", tostring(err) )
             fatalerror = true
             disconnect( handler, err )
 	    _ = handler and handler.close( )
@@ -483,13 +490,19 @@
         end
     end
     local _sendbuffer = function( )    -- this function sends data
-        local buffer = table_concat( bufferqueue, "", 1, bufferqueuelen )
-        local succ, err, byte = send( socket, buffer, 1, bufferlen )
-        local count = ( succ or byte or 0 ) * STAT_UNIT
-        sendtraffic = sendtraffic + count
-        _sendtraffic = _sendtraffic + count
-        _ = _cleanqueue and clean( bufferqueue )
-        --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) )
+    	local succ, err, byte, buffer, count;
+    	local count;
+    	if socket then
+            buffer = table_concat( bufferqueue, "", 1, bufferqueuelen )
+            succ, err, byte = send( socket, buffer, 1, bufferlen )
+            count = ( succ or byte or 0 ) * STAT_UNIT
+            sendtraffic = sendtraffic + count
+            _sendtraffic = _sendtraffic + count
+            _ = _cleanqueue and clean( bufferqueue )
+            --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) )
+        else
+            succ, err, count = false, "closed", 0;
+        end
         if succ then    -- sending succesful
             bufferqueuelen = 0
             bufferlen = 0
@@ -506,7 +519,7 @@
             _writetimes[ handler ] = _currenttime
             return true
         else    -- connection was closed during sending or fatal error
-            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " error: ", tostring(err) )
+            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " write error: ", tostring(err) )
             fatalerror = true
             disconnect( handler, err )
             _ = handler and handler.close( )
@@ -514,38 +527,40 @@
         end
     end
 
-    if sslctx then    -- ssl?
+    -- Set the sslctx
+    local handshake;
+    function handler.set_sslctx(new_sslctx)
         ssl = true
+        sslctx = new_sslctx;
         local wrote
         local read
-        local handshake = coroutine_wrap( function( client )    -- create handshake coroutine
+        handshake = coroutine_wrap( function( client )    -- create handshake coroutine
                 local err
-                for i = 1, 10 do    -- 10 handshake attemps
-                    _sendlistlen = ( wrote and removesocket( _sendlist, socket, _sendlistlen ) ) or _sendlistlen
-                    _readlistlen = ( read and removesocket( _readlist, socket, _readlistlen ) ) or _readlistlen
+                for i = 1, _maxsslhandshake do
+                    _sendlistlen = ( wrote and removesocket( _sendlist, client, _sendlistlen ) ) or _sendlistlen
+                    _readlistlen = ( read and removesocket( _readlist, client, _readlistlen ) ) or _readlistlen
                     read, wrote = nil, nil
                     _, err = client:dohandshake( )
                     if not err then
                         out_put( "server.lua: ssl handshake done" )
                         handler.readbuffer = _readbuffer    -- when handshake is done, replace the handshake function with regular functions
                         handler.sendbuffer = _sendbuffer
-                        -- return dispatch( handler )
+                        _ = status and status( handler, "ssl-handshake-complete" )
+                        _readlistlen = addsocket(_readlist, client, _readlistlen)
                         return true
                     else
-                        out_put( "server.lua: error during ssl handshake: ", tostring(err) )
-                        if err == "wantwrite" and not wrote then
-                            _sendlistlen = _sendlistlen + 1
-                            _sendlist[ _sendlistlen ] = client
-                            wrote = true
-                        elseif err == "wantread" and not read then
-                                _readlistlen = _readlistlen + 1
-                                _readlist [ _readlistlen ] = client
-                                read = true
-                        else
-                        	break;
-                        end
-                        --coroutine_yield( handler, nil, err )    -- handshake not finished
-                        coroutine_yield( )
+                       out_put( "server.lua: error during ssl handshake: ", tostring(err) )
+                       if err == "wantwrite" and not wrote then
+                           _sendlistlen = addsocket(_sendlist, client, _sendlistlen)
+                           wrote = true
+                       elseif err == "wantread" and not read then
+                           _readlistlen = addsocket(_readlist, client, _readlistlen)
+                           read = true
+                       else
+                           break;
+                       end
+                       --coroutine_yield( handler, nil, err )    -- handshake not finished
+                       coroutine_yield( )
                     end
                 end
                 disconnect( handler, "ssl handshake failed" )
@@ -553,13 +568,16 @@
                 return false    -- handshake failed
             end
         )
+    end
+    if sslctx then    -- ssl?
+    	handler.set_sslctx(sslctx);
         if startssl then    -- ssl now?
             --out_put("server.lua: ", "starting ssl handshake")
 	    local err
             socket, err = ssl_wrap( socket, sslctx )    -- wrap socket
             if err then
                 out_put( "server.lua: ssl error: ", tostring(err) )
-                mem_free( )
+                --mem_free( )
                 return nil, nil, err    -- fatal error
             end
             socket:settimeout( 0 )
@@ -596,9 +614,7 @@
                 shutdown = id
 
                 _socketlist[ socket ] = handler
-                _readlistlen = _readlistlen + 1
-                _readlist[ _readlistlen ] = socket
-                _readlist[ socket ] = _readlistlen
+                _readlistlen = addsocket(_readlist, socket, _readlistlen)
 
                 -- remove traces of the old socket
 
@@ -630,9 +646,7 @@
     shutdown = ( ssl and id ) or socket.shutdown
 
     _socketlist[ socket ] = handler
-    _readlistlen = _readlistlen + 1
-    _readlist[ _readlistlen ] = socket
-    _readlist[ socket ] = _readlistlen
+    _readlistlen = addsocket(_readlist, socket, _readlistlen)
 
     return handler, socket
 end
@@ -644,6 +658,15 @@
     return false
 end
 
+addsocket = function( list, socket, len )
+    if not list[ socket ] then
+      len = len + 1
+      list[ len ] = socket
+      list[ socket ] = len
+    end
+    return len;
+end
+
 removesocket = function( list, socket, len )    -- this function removes sockets from a list ( copied from copas )
     local pos = list[ socket ]
     if pos then
@@ -664,7 +687,7 @@
     _readlistlen = removesocket( _readlist, socket, _readlistlen )
     _socketlist[ socket ] = nil
     socket:close( )
-    mem_free( )
+    --mem_free( )
 end
 
 ----------------------------------// PUBLIC //--
@@ -698,8 +721,7 @@
         return nil, err
     end
     server:settimeout( 0 )
-    _readlistlen = _readlistlen + 1
-    _readlist[ _readlistlen ] = server
+    _readlistlen = addsocket(_readlist, server, _readlistlen)
     _server[ port ] = handler
     _socketlist[ server ] = handler
     out_put( "server.lua: new server listener on '", addr, ":", port, "'" )
@@ -713,7 +735,7 @@
 removeserver = function( port )
     local handler = _server[ port ]
     if not handler then
-        return nil, "no server found on port '" .. tostring( port ) "'"
+        return nil, "no server found on port '" .. tostring( port ) .. "'"
     end
     handler.close( )
     _server[ port ] = nil
@@ -733,11 +755,11 @@
     _sendlist = { }
     _timerlist = { }
     _socketlist = { }
-    mem_free( )
+    --mem_free( )
 end
 
 getsettings = function( )
-    return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver
+    return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver, _maxsslhandshake
 end
 
 changesettings = function( new )
@@ -753,6 +775,7 @@
     _readtimeout = tonumber( new.readtimeout ) or _readtimeout
     _cleanqueue = new.cleanqueue
     _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver
+    _maxsslhandshake = new._maxsslhandshake or _maxsslhandshake
     return true
 end
 
@@ -805,7 +828,7 @@
         _currenttime = os_time( )
         if os_difftime( _currenttime - _timer ) >= 1 then
             for i = 1, _timerlistlen do
-                _timerlist[ i ]( )    -- fire timers
+                _timerlist[ i ]( _currenttime )    -- fire timers
             end
             _timer = _currenttime
         end
@@ -820,9 +843,7 @@
 local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx, startssl )
     local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx, startssl )
     _socketlist[ socket ] = handler
-    _sendlistlen = _sendlistlen + 1
-    _sendlist[ _sendlistlen ] = socket
-    _sendlist[ socket ] = _sendlistlen
+    _sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
     return handler, socket
 end
 
--- a/net/xmppclient_listener.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/net/xmppclient_listener.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -27,7 +27,7 @@
 local sm_streamclosed = sessionmanager.streamclosed;
 local st = require "util.stanza";
 
-local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams|stream", 
+local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams\1stream", 
 		default_ns = "jabber:client",
 		streamopened = sm_streamopened, streamclosed = sm_streamclosed, handlestanza = core_process_stanza };
 
@@ -53,7 +53,7 @@
 
 local function session_reset_stream(session)
 	-- Reset stream
-		local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "|");
+		local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1");
 		session.parser = parser;
 		
 		session.notopen = true;
@@ -70,7 +70,7 @@
 
 
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = stream_callbacks.stream_tag:gsub("%|[^|]+$", ""), xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+local default_stream_attr = { ["xmlns:stream"] = stream_callbacks.stream_tag:match("[^\1]*"), 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
@@ -114,11 +114,6 @@
 		session = sm_new_session(conn);
 		sessions[conn] = session;
 
-		-- Logging functions --
-
-		local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
-		session.log = logger.init(conn_name);
-		
 		session.log("info", "Client connected");
 		
 		-- Client is using legacy SSL (otherwise mod_tls sets this flag)
--- a/net/xmppcomponent_listener.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/net/xmppcomponent_listener.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -32,7 +32,7 @@
 
 --- Callbacks/data for xmlhandlers to handle streams for us ---
 
-local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams|stream", default_ns = xmlns_component };
+local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams\1stream", default_ns = xmlns_component };
 
 function stream_callbacks.error(session, error, data, data2)
 	log("warn", "Error processing component stream: "..tostring(error));
@@ -87,7 +87,7 @@
 
 --- Closing a component connection
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = stream_callbacks.stream_tag:gsub("%|[^|]+$", ""), xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+local default_stream_attr = { ["xmlns:stream"] = stream_callbacks.stream_tag:match("[^\1]*"), 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
@@ -138,7 +138,7 @@
 		
 		session.log("info", "Incoming Jabber component connection");
 		
-		local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "|");
+		local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1");
 		session.parser = parser;
 		
 		session.notopen = true;
--- a/net/xmppserver_listener.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/net/xmppserver_listener.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -17,7 +17,7 @@
 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 = { stream_tag = "http://etherx.jabber.org/streams|stream", 
+local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams\1stream", 
 		default_ns = "jabber:server",
 		streamopened = s2s_streamopened, streamclosed = s2s_streamclosed, handlestanza =  core_process_stanza };
 
@@ -53,7 +53,7 @@
 
 local function session_reset_stream(session)
 	-- Reset stream
-		local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "|");
+		local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1");
 		session.parser = parser;
 		
 		session.notopen = true;
@@ -61,16 +61,16 @@
 		function session.data(conn, data)
 			local ok, err = parser:parse(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]+", " "));
+			(session.log or log)("warn", "Received invalid XML: %s", data);
+			(session.log or log)("warn", "Problem was: %s", err);
 			session:close("xml-not-well-formed");
 		end
 		
 		return true;
 end
 
-
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = stream_callbacks.stream_tag:gsub("%|[^|]+$", ""), xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
+local default_stream_attr = { ["xmlns:stream"] = stream_callbacks.stream_tag:match("[^\1]*"), 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
@@ -100,6 +100,9 @@
 			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.disconnect(session.conn, "stream error");
 	end
@@ -134,6 +137,17 @@
 	end
 end
 	
+function xmppserver.status(conn, status)
+	if status == "ssl-handshake-complete" then
+		local session = sessions[conn];
+		if session and session.direction == "outgoing" then
+			local format, to_host, from_host = string.format, session.to_host, session.from_host;
+			session.log("debug", "Sending stream header...");
+			session.sends2s(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.disconnect(conn, err)
 	local session = sessions[conn];
 	if session then
--- a/plugins/mod_bosh.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_bosh.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,7 +6,6 @@
 -- COPYING file in the source package for more information.
 --
 
-
 module.host = "*" -- Global module
 
 local hosts = _G.hosts;
@@ -22,17 +21,18 @@
 local st = require "util.stanza";
 local logger = require "util.logger";
 local log = logger.init("mod_bosh");
-local stream_callbacks = { stream_tag = "http://jabber.org/protocol/httpbind|body" };
-local config = require "core.configmanager";
+
 local xmlns_bosh = "http://jabber.org/protocol/httpbind"; -- (hard-coded into a literal in session.send)
+local stream_callbacks = { stream_tag = "http://jabber.org/protocol/httpbind\1body", default_ns = xmlns_bosh };
 
-local BOSH_DEFAULT_HOLD = tonumber(config.get("*", "core", "bosh_default_hold")) or 1;
-local BOSH_DEFAULT_INACTIVITY = tonumber(config.get("*", "core", "bosh_max_inactivity")) or 60;
-local BOSH_DEFAULT_POLLING = tonumber(config.get("*", "core", "bosh_max_polling")) or 5;
-local BOSH_DEFAULT_REQUESTS = tonumber(config.get("*", "core", "bosh_max_requests")) or 2;
-local BOSH_DEFAULT_MAXPAUSE = tonumber(config.get("*", "core", "bosh_max_pause")) or 300;
+local BOSH_DEFAULT_HOLD = tonumber(module:get_option("bosh_default_hold")) or 1;
+local BOSH_DEFAULT_INACTIVITY = tonumber(module:get_option("bosh_max_inactivity")) or 60;
+local BOSH_DEFAULT_POLLING = tonumber(module:get_option("bosh_max_polling")) or 5;
+local BOSH_DEFAULT_REQUESTS = tonumber(module:get_option("bosh_max_requests")) or 2;
+local BOSH_DEFAULT_MAXPAUSE = tonumber(module:get_option("bosh_max_pause")) or 300;
 
 local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" };
+local session_close_reply = { headers = default_headers, body = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate" }), attr = {} };
 
 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
 local os_time = os.time;
@@ -70,7 +70,7 @@
 	--log("debug", "Handling new request %s: %s\n----------", request.id, tostring(body));
 	request.notopen = true;
 	request.log = log;
-	local parser = lxp.new(init_xmlhandlers(request, stream_callbacks), "|");
+	local parser = lxp.new(init_xmlhandlers(request, stream_callbacks), "\1");
 	
 	parser:parse(body);
 	
@@ -112,11 +112,9 @@
 
 local function bosh_reset_stream(session) session.notopen = true; end
 
-local session_close_reply = { headers = default_headers, body = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate" }), attr = {} };
 local function bosh_close_stream(session, reason)
 	(session.log or log)("info", "BOSH client disconnected");
 	session_close_reply.attr.condition = reason;
-	local session_close_reply = tostring(session_close_reply);
 	for _, held_request in ipairs(session.requests) do
 		held_request:send(session_close_reply);
 		held_request:destroy();
@@ -144,7 +142,7 @@
 		
 		-- New session
 		sid = new_uuid();
-		local session = { type = "c2s_unauthed", conn = {}, sid = sid, rid = attr.rid, host = attr.to, bosh_version = attr.ver, bosh_wait = attr.wait, streamid = sid, 
+		local session = { type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid), host = attr.to, bosh_version = attr.ver, bosh_wait = attr.wait, streamid = sid, 
 						bosh_hold = BOSH_DEFAULT_HOLD, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY,
 						requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream, close = bosh_close_stream, 
 						dispatch_stanza = core_process_stanza, log = logger.init("bosh"..sid), secure = request.secure };
@@ -209,6 +207,21 @@
 		return;
 	end
 	
+	if session.rid then
+		local rid = tonumber(attr.rid);
+		local diff = rid - session.rid;
+		if diff > 1 then
+			session.log("warn", "rid too large (means a request was lost). Last rid: %d New rid: %s", session.rid, attr.rid);
+		elseif diff <= 0 then
+			-- Repeated, ignore
+			session.log("debug", "rid repeated (on request %s), ignoring: %d", request.id, session.rid);
+			request.notopen = nil;
+			t_insert(session.requests, request);
+			return;
+		end
+		session.rid = rid;
+	end
+	
 	if attr.type == "terminate" then
 		-- Client wants to end this session
 		session:close();
@@ -245,6 +258,7 @@
 	end
 end
 
+local dead_sessions = {};
 function on_timer()
 	-- log("debug", "Checking for requests soon to timeout...");
 	-- Identify requests timing out within the next few seconds
@@ -261,21 +275,29 @@
 	end
 	
 	now = now - 3;
+	local n_dead_sessions = 0;
 	for session, inactive_since in pairs(inactive_sessions) do
 		if session.bosh_max_inactive then
 			if now - inactive_since > session.bosh_max_inactive then
 				(session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now);
 				sessions[session.sid]  = nil;
 				inactive_sessions[session] = nil;
-				sm_destroy_session(session, "BOSH client silent for over "..session.bosh_max_inactive.." seconds");
+				n_dead_sessions = n_dead_sessions + 1;
+				dead_sessions[n_dead_sessions] = session;
 			end
 		else
 			inactive_sessions[session] = nil;
 		end
 	end
+
+	for i=1,n_dead_sessions do
+		local session = dead_sessions[i];
+		dead_sessions[i] = nil;
+		sm_destroy_session(session, "BOSH client silent for over "..session.bosh_max_inactive.." seconds");
+	end
 end
 
-local ports = config.get(module.host, "core", "bosh_ports") or { 5280 };
-httpserver.new_from_config(ports, "http-bind", handle_request);
+local ports = module:get_option("bosh_ports") or { 5280 };
+httpserver.new_from_config(ports, handle_request, { base = "http-bind" });
 
 server.addtimer(on_timer);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_compression.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -0,0 +1,122 @@
+-- Prosody IM
+-- Copyright (C) 2009 Tobias Markmann
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local st = require "util.stanza";
+local zlib = require "zlib";
+local pcall = pcall;
+
+local xmlns_compression_feature = "http://jabber.org/features/compress"
+local xmlns_compression_protocol = "http://jabber.org/protocol/compress"
+local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
+
+local compression_level = module:get_option("compression_level");
+
+-- if not defined assume admin wants best compression
+if compression_level == nil then compression_level = 9 end;
+
+compression_level = tonumber(compression_level);
+if not compression_level or compression_level < 1 or compression_level > 9 then
+	module:log("warn", "Invalid compression level in config: %s", tostring(compression_level));
+	module:log("warn", "Module loading aborted. Compression won't be available.");
+	return;
+end
+
+module:add_event_hook("stream-features",
+		function (session, features)
+			if not session.compressed then
+				-- FIXME only advertise compression support when TLS layer has no compression enabled
+				features:add_child(compression_stream_feature);
+			end
+		end
+);
+
+-- TODO Support compression on S2S level too.
+module:add_handler({"c2s_unauthed", "c2s"}, "compress", xmlns_compression_protocol,
+		function(session, stanza)
+			-- fail if we are already compressed
+			if session.compressed then
+				local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
+				session.send(error_st);
+				session:log("warn", "Tried to establish another compression layer.");
+			end
+			
+			-- checking if the compression method is supported
+			local method = stanza:child_with_name("method")[1];
+			if method == "zlib" then
+				session.log("info", method.." compression selected.");
+				session.send(st.stanza("compressed", {xmlns=xmlns_compression_protocol}));
+				session:reset_stream();
+				
+				-- create deflate and inflate streams
+				local status, deflate_stream = pcall(zlib.deflate, compression_level);
+				if status == false then
+					local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
+					session.send(error_st);
+					session:log("error", "Failed to create zlib.deflate filter.");
+					module:log("error", deflate_stream);
+					return
+				end
+				
+				local status, inflate_stream = pcall(zlib.inflate);
+				if status == false then
+					local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
+					session.send(error_st);
+					session:log("error", "Failed to create zlib.deflate filter.");
+					module:log("error", inflate_stream);
+					return
+				end
+				
+				-- setup compression for session.w
+				local old_send = session.send;
+				
+				session.send = function(t)
+						local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
+						if status == false then
+							session:close({
+								condition = "undefined-condition";
+								text = compressed;
+								extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+							});
+							module:log("warn", compressed);
+							return;
+						end
+						old_send(compressed);
+					end;
+					
+				-- setup decompression for session.data
+				local function setup_decompression(session)
+					local old_data = session.data
+					session.data = function(conn, data)
+							local status, decompressed, eof = pcall(inflate_stream, data);
+							if status == false then
+								session:close({
+									condition = "undefined-condition";
+									text = decompressed;
+									extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+								});
+								module:log("warn", decompressed);
+								return;
+							end
+							old_data(conn, decompressed);
+						end;
+				end
+				setup_decompression(session);
+				
+				local session_reset_stream = session.reset_stream;
+				session.reset_stream = function(session)
+						session_reset_stream(session);
+						setup_decompression(session);
+						return true;
+					end;
+				session.compressed = true;
+			else
+				session.log("info", method.." compression selected. But we don't support it.");
+				local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
+				session.send(error_st);
+			end
+		end
+);
--- a/plugins/mod_console.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_console.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -127,7 +127,11 @@
 end
 
 function console_listener.disconnect(conn, err)
-	
+	local session = sessions[conn];
+	if session then
+		session.disconnect();
+		sessions[conn] = nil;
+	end
 end
 
 connlisteners_register('console', console_listener);
@@ -170,6 +174,7 @@
 		print [[s2s - Commands to manage sessions between this server and others]]
 		print [[module - Commands to load/reload/unload modules/plugins]]
 		print [[server - Uptime, version, shutting down, etc.]]
+		print [[config - Reloading the configuration, etc.]]
 		print [[console - Help regarding the console itself]]
 	elseif section == "c2s" then
 		print [[c2s:show(jid) - Show all client sessions with the specified JID (or all if no JID given)]]
@@ -183,10 +188,13 @@
 		print [[module:load(module, host) - Load the specified module on the specified host (or all hosts if none given)]]
 		print [[module:reload(module, host) - The same, but unloads and loads the module (saving state if the module supports it)]]
 		print [[module:unload(module, host) - The same, but just unloads the module from memory]]
+		print [[module:list(host) - List the modules loaded on the specified host]]
 	elseif section == "server" then
 		print [[server:version() - Show the server's version number]]
 		print [[server:uptime() - Show how long the server has been running]]
 		--print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]]
+	elseif section == "config" then
+		print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
 	elseif section == "console" then
 		print [[Hey! Welcome to Prosody's admin console.]]
 		print [[First thing, if you're ever wondering how to get out, simply type 'quit'.]]
@@ -327,6 +335,35 @@
 	return ok, (ok and "Module reloaded on "..count.." host"..(count ~= 1 and "s" or "")) or ("Last error: "..tostring(err));
 end
 
+function def_env.module:list(hosts)
+	if hosts == nil then
+		hosts = array.collect(keys(prosody.hosts));
+	end
+	if type(hosts) == "string" then
+		hosts = { hosts };
+	end
+	if type(hosts) ~= "table" then
+		return false, "Please supply a host or a list of hosts you would like to see";
+	end
+	
+	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();
+		if #modules == 0 then
+			if prosody.hosts[host] then
+				print("    No modules loaded");
+			else
+				print("    Host not found");
+			end
+		else
+			for _, name in ipairs(modules) do
+				print("    "..name);
+			end
+		end
+	end
+end
+
 def_env.config = {};
 function def_env.config:load(filename, format)
 	local config_load = require "core.configmanager".load;
@@ -373,7 +410,12 @@
 
 function def_env.c2s:show(match_jid)
 	local print, count = self.session.print, 0;
+	local curr_host;
 	show_c2s(function (jid, session)
+		if curr_host ~= session.host then
+			curr_host = session.host;
+			print(curr_host);
+		end
 		if (not match_jid) or jid:match(match_jid) then
 			count = count + 1;
 			local status, priority = "unavailable", tostring(session.priority or "-");
@@ -385,7 +427,7 @@
 					status = "available";
 				end
 			end
-			print(jid.." - "..status.."("..priority..")");
+			print("   "..jid.." - "..status.."("..priority..")");
 		end		
 	end);
 	return true, "Total: "..count.." clients";
@@ -436,7 +478,7 @@
 		for remotehost, session in pairs(host_session.s2sout) do
 			if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
 				count_out = count_out + 1;
-				print("    "..host.." -> "..remotehost);
+				print("    "..host.." -> "..remotehost..(session.secure and " (encrypted)" or ""));
 				if session.sendq then
 					print("        There are "..#session.sendq.." queued outgoing stanzas for this connection");
 				end
@@ -464,12 +506,16 @@
 				end
 			end
 		end	
-		
+		local subhost_filter = function (h) 
+				return (match_jid and h:match(match_jid));
+			end
 		for session in pairs(incoming_s2s) do
 			if session.to_host == host and ((not match_jid) or host:match(match_jid) 
-				or (session.from_host and session.from_host:match(match_jid))) then
+				or (session.from_host and session.from_host:match(match_jid))
+				-- Pft! is what I say to list comprehensions
+				or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
 				count_in = count_in + 1;
-				print("    "..host.." <- "..(session.from_host or "(unknown)"));
+				print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or ""));
 				if session.type == "s2sin_unauthed" then
 						print("        Connection not yet authenticated");
 				end
@@ -510,7 +556,7 @@
 		if not session then 
 			print("No outgoing connection from "..from.." to "..to)
 		else
-			s2smanager.destroy_session(session);
+			(session.close or s2smanager.destroy_session)(session);
 			count = count + 1;
 			print("Closed outgoing session from "..from.." to "..to);
 		end
@@ -518,7 +564,7 @@
 		-- Is an incoming connection
 		for session in pairs(incoming_s2s) do
 			if session.to_host == to and session.from_host == from then
-				s2smanager.destroy_session(session);
+				(session.close or s2smanager.destroy_session)(session);
 				count = count + 1;
 			end
 		end
@@ -537,6 +583,44 @@
 	return true, "Closed "..count.." s2s session"..((count == 1 and "") or "s");
 end
 
+def_env.host = {}; def_env.hosts = def_env.host;
+function def_env.host:activate(hostname, config)
+	local hostmanager_activate = require "core.hostmanager".activate;
+	if hosts[hostname] then
+		return false, "The host "..tostring(hostname).." is already activated";
+	end
+	
+	local defined_hosts = config or configmanager.getconfig();
+	if not config and not defined_hosts[hostname] then
+		return false, "Couldn't find "..tostring(hostname).." defined in the config, perhaps you need to config:reload()?";
+	end
+	hostmanager_activate(hostname, config or defined_hosts[hostname]);
+	return true, "Host "..tostring(hostname).." activated";
+end
+
+function def_env.host:deactivate(hostname, reason)
+	local hostmanager_deactivate = require "core.hostmanager".deactivate;
+	local host = hosts[hostname];
+	if not host then
+		return false, "The host "..tostring(hostname).." is not activated";
+	end
+	if reason then
+		reason = { condition = "host-gone", text = reason };
+	end
+	hostmanager_deactivate(hostname, reason);
+	return true, "Host "..tostring(hostname).." deactivated";
+end
+
+function def_env.host:list()
+	local print = self.session.print;
+	local i = 0;
+	for host in values(array.collect(keys(prosody.hosts)):sort()) do
+		i = i + 1;
+		print(host);
+	end
+	return true, i.." hosts";
+end
+
 -------------
 
 function printbanner(session)
--- a/plugins/mod_dialback.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_dialback.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -10,6 +10,7 @@
 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 s2s_destroy_session = require "core.s2smanager".destroy_session;
 
@@ -17,6 +18,7 @@
 
 local st = require "util.stanza";
 
+local xmlns_stream = "http://etherx.jabber.org/streams";
 local xmlns_dialback = "jabber:server:dialback";
 
 local dialback_requests = setmetatable({}, { __mode = 'v' });
@@ -113,3 +115,13 @@
 			s2s_destroy_session(origin)
 		end
 	end);
+
+module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
+		s2s_initiate_dialback(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' }):tag("optional"):up():up();
+	end);
--- a/plugins/mod_disco.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_disco.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,16 +6,47 @@
 -- COPYING file in the source package for more information.
 --
 
-
+local componentmanager_get_children = require "core.componentmanager".get_children;
+local st = require "util.stanza"
 
-local discomanager_handle = require "core.discomanager".handle;
-
+module:add_identity("server", "im", "Prosody"); -- FIXME should be in the non-existing mod_router
 module:add_feature("http://jabber.org/protocol/disco#info");
 module:add_feature("http://jabber.org/protocol/disco#items");
 
-module:add_iq_handler({"c2s", "s2sin"}, "http://jabber.org/protocol/disco#info", function (session, stanza)
-	session.send(discomanager_handle(stanza));
+module:hook("iq/host/http://jabber.org/protocol/disco#info:query", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	if stanza.attr.type ~= "get" then return; end
+	local node = stanza.tags[1].attr.node;
+	if node and node ~= "" then return; end -- TODO fire event?
+
+	local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#info");
+	local done = {};
+	for _,identity in ipairs(module:get_host_items("identity")) do
+		local identity_s = identity.category.."\0"..identity.type;
+		if not done[identity_s] then
+			reply:tag("identity", identity):up();
+			done[identity_s] = true;
+		end
+	end
+	for _,feature in ipairs(module:get_host_items("feature")) do
+		if not done[feature] then
+			reply:tag("feature", {var=feature}):up();
+			done[feature] = true;
+		end
+	end
+	origin.send(reply);
+	return true;
 end);
-module:add_iq_handler({"c2s", "s2sin"}, "http://jabber.org/protocol/disco#items", function (session, stanza)
-	session.send(discomanager_handle(stanza));
+module:hook("iq/host/http://jabber.org/protocol/disco#items:query", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	if stanza.attr.type ~= "get" then return; end
+	local node = stanza.tags[1].attr.node;
+	if node and node ~= "" then return; end -- TODO fire event?
+
+	local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
+	for jid in pairs(componentmanager_get_children(module.host)) do
+		reply:tag("item", {jid = jid}):up();
+	end
+	origin.send(reply);
+	return true;
 end);
--- a/plugins/mod_httpserver.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_httpserver.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -12,20 +12,50 @@
 local open = io.open;
 local t_concat = table.concat;
 
-local http_base = "www_files";
+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_404 = { status = "404 Not Found", body = "<h1>Page Not Found</h1>Sorry, we couldn't find what you were looking for :(" };
 
-local http_path = { http_base };
-local function handle_request(method, body, request)
-	local path = request.url.path:gsub("%.%.%/", ""):gsub("^/[^/]+", "");
-	http_path[2] = path;
-	local f, err = open(t_concat(http_path), "r");
+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 f, err = open(http_base..path, "r");
 	if not f then return response_404; end
 	local data = f:read("*a");
 	f:close();
 	return 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 ports = config.get(module.host, "core", "http_ports") or { 5280 };
-httpserver.new_from_config(ports, "files", handle_request);
+httpserver.set_default_handler(handle_default_request);
+httpserver.new_from_config(ports, handle_file_request, { base = "files" });
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_lastactivity.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -0,0 +1,52 @@
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local st = require "util.stanza";
+local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
+local jid_bare = require "util.jid".bare;
+local jid_split = require "util.jid".split;
+
+module:add_feature("jabber:iq:last");
+
+local map = {};
+
+module:hook("pre-presence/bare", function(event)
+	local stanza = event.stanza;
+	if not(stanza.attr.to) and stanza.attr.type == "unavailable" then
+		local t = os.time();
+		local s = stanza:child_with_name("status");
+		s = s and #s.tags == 0 and s[1] or "";
+		map[event.origin.username] = {s = s, t = t};
+	end
+end, 10);
+
+module:hook("iq/bare/jabber:iq:last:query", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	if stanza.attr.type == "get" then
+		local username = jid_split(stanza.attr.to) or origin.username;
+		if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
+			local seconds, text = "0", "";
+			if map[username] then
+				seconds = tostring(os.difftime(os.time(), map[username].t));
+				text = map[username].s;
+			end
+			origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:last', seconds=seconds}):text(text));
+		else
+			origin.send(st.error_reply(stanza, 'auth', 'forbidden'));
+		end
+		return true;
+	end
+end);
+
+module.save = function()
+	return {map = map};
+end
+module.restore = function(data)
+	map = data.map or {};
+end
+
--- a/plugins/mod_legacyauth.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_legacyauth.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -11,11 +11,12 @@
 local st = require "util.stanza";
 local t_concat = table.concat;
 
-local config = require "core.configmanager";
-local secure_auth_only = config.get(module:get_host(), "core", "require_encryption");
+local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
 
 local sessionmanager = require "core.sessionmanager";
 local usermanager = require "core.usermanager";
+local nodeprep = require "util.encodings".stringprep.nodeprep;
+local resourceprep = require "util.encodings".stringprep.resourceprep;
 
 module:add_feature("jabber:iq:auth");
 module:add_event_hook("stream-features", function (session, features)
@@ -43,11 +44,11 @@
 					:tag("username"):up()
 					:tag("password"):up()
 					:tag("resource"):up());
-				return true;			
 			else
 				username, password, resource = t_concat(username), t_concat(password), t_concat(resource);
+				username = nodeprep(username);
+				resource = resourceprep(resource)
 				local reply = st.reply(stanza);
-				require "core.usermanager"
 				if usermanager.validate_credentials(session.host, username, password) then
 					-- Authentication successful!
 					local success, err = sessionmanager.make_authenticated(session, username);
@@ -56,19 +57,18 @@
 						success, err_type, err, err_msg = sessionmanager.bind_resource(session, resource);
 						if not success then
 							session.send(st.error_reply(stanza, err_type, err, err_msg));
+							session.username, session.type = nil, "c2s_unauthed"; -- FIXME should this be placed in sessionmanager?
+							return true;
+						elseif resource ~= session.resource then -- server changed resource, not supported by legacy auth
+							session.send(st.error_reply(stanza, "cancel", "conflict", "The requested resource could not be assigned to this session."));
+							session:close(); -- FIXME undo resource bind and auth instead of closing the session?
 							return true;
 						end
 					end
 					session.send(st.reply(stanza));
-					return true;
 				else
-					local reply = st.reply(stanza);
-					reply.attr.type = "error";
-					reply:tag("error", { code = "401", type = "auth" })
-						:tag("not-authorized", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" });
-					session.send(reply);
-					return true;
+					session.send(st.error_reply(stanza, "auth", "not-authorized"));
 				end
 			end
-			
+			return true;
 		end);
--- a/plugins/mod_muc.lua	Fri Nov 13 14:31:03 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-if module:get_host_type() ~= "component" then
-	error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0);
-end
-
-local muc_host = module:get_host();
-local muc_name = "Chatrooms";
-local history_length = 20;
-
-local muc_new_room = require "util.muc".new_room;
-local register_component = require "core.componentmanager".register_component;
-local deregister_component = require "core.componentmanager".deregister_component;
-local jid_split = require "util.jid".split;
-local st = require "util.stanza";
-
-local rooms = {};
-local component;
-local host_room = muc_new_room(muc_host);
-host_room.route_stanza = function(room, stanza) core_post_stanza(component, stanza); end;
-
-local function get_disco_info(stanza)
-	return st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
-		:tag("identity", {category='conference', type='text', name=muc_name}):up()
-		:tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
-end
-local function get_disco_items(stanza)
-	local reply = st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
-	for jid, room in pairs(rooms) do
-		reply:tag("item", {jid=jid, name=jid}):up();
-	end
-	return reply; -- TODO cache disco reply
-end
-
-local function handle_to_domain(origin, stanza)
-	local type = stanza.attr.type;
-	if type == "error" or type == "result" then return; end
-	if stanza.name == "iq" and type == "get" then
-		local xmlns = stanza.tags[1].attr.xmlns;
-		if xmlns == "http://jabber.org/protocol/disco#info" then
-			origin.send(get_disco_info(stanza));
-		elseif xmlns == "http://jabber.org/protocol/disco#items" then
-			origin.send(get_disco_items(stanza));
-		else
-			origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc
-		end
-	else
-		host_room:handle_stanza(origin, stanza);
-		--origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it"));
-	end
-end
-
-component = register_component(muc_host, function(origin, stanza)
-	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
-	if to_node then
-		local bare = to_node.."@"..to_host;
-		if to_host == muc_host or bare == muc_host then
-			local room = rooms[bare];
-			if not room then
-				room = muc_new_room(bare);
-				room.route_stanza = function(room, stanza) core_post_stanza(component, stanza); end;
-				rooms[bare] = room;
-			end
-			room:handle_stanza(origin, stanza);
-		else --[[not for us?]] end
-		return;
-	end
-	-- to the main muc domain
-	handle_to_domain(origin, stanza);
-end);
-
-prosody.hosts[module:get_host()].muc = { rooms = rooms };
-
-module.unload = function()
-	deregister_component(muc_host);
-end
-module.save = function()
-	return {rooms = rooms};
-end
-module.restore = function(data)
-	rooms = data.rooms or {};
-	prosody.hosts[module:get_host()].muc = { rooms = rooms };
-end
--- a/plugins/mod_offline.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_offline.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -10,7 +10,8 @@
 local datamanager = require "util.datamanager";
 local st = require "util.stanza";
 local datetime = require "util.datetime";
-local ipairs = ipairs;
+local ipairs = ipairs;
+local jid_split = require "util.jid".split;
 
 module:add_feature("msgoffline");
 
--- a/plugins/mod_pep.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_pep.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -25,10 +25,20 @@
 local recipients = {};
 local hash_map = {};
 
-module:add_identity("pubsub", "pep");
+module.save = function()
+	return { data = data, recipients = recipients, hash_map = hash_map };
+end
+module.restore = function(state)
+	data = state.data or {};
+	recipients = state.recipients or {};
+	hash_map = state.hash_map or {};
+end
+
+module:add_identity("pubsub", "pep", "Prosody");
 module:add_feature("http://jabber.org/protocol/pubsub#publish");
 
 local function publish(session, node, item)
+	item.attr.xmlns = nil;
 	local disable = #item.tags ~= 1 or #item.tags[1].tags == 0;
 	if #item.tags == 0 then item.name = "retract"; end
 	local bare = session.username..'@'..session.host;
@@ -132,9 +142,9 @@
 			if payload and (payload.name == 'publish' or payload.name == 'retract') and payload.attr.node then -- <publish node='http://jabber.org/protocol/tune'>
 				local node = payload.attr.node;
 				payload = payload.tags[1];
-				if payload then -- <item>
-					publish(session, node, payload);
+				if payload and payload.name == "item" then -- <item>
 					session.send(st.reply(stanza));
+					publish(session, node, st.clone(payload));
 					return true;
 				end
 			end
--- a/plugins/mod_ping.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_ping.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,15 +6,16 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
 local st = require "util.stanza";
 
 module:add_feature("urn:xmpp:ping");
 
-module:add_iq_handler({"c2s", "s2sin"}, "urn:xmpp:ping",
-	function(session, stanza)
-		if stanza.attr.type == "get" then
-			session.send(st.reply(stanza));
-		end
-	end);
+local function ping_handler(event)
+	if event.stanza.attr.type == "get" then
+		event.origin.send(st.reply(event.stanza));
+		return true;
+	end
+end
+
+module:hook("iq/bare/urn:xmpp:ping:ping", ping_handler);
+module:hook("iq/host/urn:xmpp:ping:ping", ping_handler);
--- a/plugins/mod_posix.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_posix.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -17,19 +17,45 @@
 	module:log("warn", "Couldn't load signal library, won't respond to SIGTERM");
 end
 
-local config_get = require "core.configmanager".get;
 local logger_set = require "util.logger".setwriter;
 
 local prosody = _G.prosody;
 
 module.host = "*"; -- we're a global module
 
+-- Allow switching away from root, some people like strange ports.
+module:add_event_hook("server-started", function ()
+		local uid = module:get_option("setuid");
+		local gid = module:get_option("setgid");
+		if gid then
+			local success, msg = pposix.setgid(gid);
+			if success then
+				module:log("debug", "Changed group to "..gid.." successfully.");
+			else
+				module:log("error", "Failed to change group to "..gid..". Error: "..msg);
+				prosody.shutdown("Failed to change group to "..gid);
+			end
+		end
+		if uid then
+			local success, msg = pposix.setuid(uid);
+			if success then
+				module:log("debug", "Changed user to "..uid.." successfully.");
+			else
+				module:log("error", "Failed to change user to "..uid..". Error: "..msg);
+				prosody.shutdown("Failed to change user to "..uid);
+			end
+		end
+	end);
+
 -- Don't even think about it!
 module:add_event_hook("server-starting", function ()
-		if pposix.getuid() == 0 and not config_get("*", "core", "run_as_root") then
-			module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
-			module:log("error", "For more information on running Prosody as root, see http://prosody.im/doc/root");
-			prosody.shutdown("Refusing to run as root");
+		local suid = module:get_option("setuid");
+		if not suid or suid == 0 or suid == "root" then
+			if pposix.getuid() == 0 and not module:get_option("run_as_root") then
+				module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
+				module:log("error", "For more information on running Prosody as root, see http://prosody.im/doc/root");
+				prosody.shutdown("Refusing to run as root");
+			end
 		end
 	end);
 
@@ -46,7 +72,7 @@
 	if pidfile_written then
 		remove_pidfile();
 	end
-	local pidfile = config_get("*", "core", "pidfile");
+	local pidfile = module:get_option("pidfile");
 	if pidfile then
 		local pf, err = io.open(pidfile, "w+");
 		if not pf then
@@ -76,7 +102,17 @@
 end
 require "core.loggingmanager".register_sink_type("syslog", syslog_sink_maker);
 
-if not config_get("*", "core", "no_daemonize") then
+local daemonize = module:get_option("daemonize");
+if daemonize == nil then
+	local no_daemonize = module:get_option("no_daemonize"); --COMPAT w/ 0.5
+	daemonize = not no_daemonize;
+	if no_daemonize ~= nil then
+		module:log("warn", "The 'no_daemonize' option is now replaced by 'daemonize'");
+		module:log("warn", "Update your config from 'no_daemonize = %s' to 'daemonize = %s'", tostring(no_daemonize), tostring(daemonize));
+	end
+end
+
+if daemonize then
 	local function daemonize_server()
 		local ok, ret = pposix.daemonize();
 		if not ok then
--- a/plugins/mod_presence.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_presence.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -29,7 +29,7 @@
 	if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
 		local node, host = jid_split(stanza.attr.to);
 		host = hosts[host];
-		if host and host.type == "local" then
+		if node and host and host.type == "local" then
 			handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
 			return;
 		end
@@ -142,7 +142,7 @@
 	stanza.attr.to = nil; -- reset it
 end
 
-function send_presence_of_available_resources(user, host, jid, recipient_session, core_route_stanza)
+function send_presence_of_available_resources(user, host, jid, recipient_session, core_route_stanza, stanza)
 	local h = hosts[host];
 	local count = 0;
 	if h and h.type == "local" then
@@ -151,6 +151,7 @@
 			for k, session in pairs(u.sessions) do
 				local pres = session.presence;
 				if pres then
+					if stanza then pres = stanza; pres.attr.from = session.full_jid; end
 					pres.attr.to = jid;
 					core_route_stanza(session, pres);
 					pres.attr.to = nil;
@@ -165,7 +166,7 @@
 
 function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)
 	local node, host = jid_split(from_bare);
-	if node == origin.username and host == origin.host then return; end -- No self contacts
+	if to_bare == origin.username.."@"..origin.host then return; end -- No self contacts
 	local st_from, st_to = stanza.attr.from, stanza.attr.to;
 	stanza.attr.from, stanza.attr.to = from_bare, to_bare;
 	log("debug", "outbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);
@@ -199,6 +200,9 @@
 			rostermanager.roster_push(node, host, to_bare);
 		end
 		core_route_stanza(origin, stanza);
+		-- COMPAT: Some legacy clients keep displaying unsubscribed contacts as online unless an unavailable presence is sent:
+		send_presence_of_available_resources(node, host, to_bare, origin, core_route_stanza,
+			st.presence({ type="unavailable", from=from_bare, to=to_bare, id=stanza.attr.id }));
 	end
 	stanza.attr.from, stanza.attr.to = st_from, st_to;
 end
--- a/plugins/mod_register.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_register.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -9,7 +9,6 @@
 
 local hosts = _G.hosts;
 local st = require "util.stanza";
-local config = require "core.configmanager";
 local datamanager = require "util.datamanager";
 local usermanager_user_exists = require "core.usermanager".user_exists;
 local usermanager_create_user = require "core.usermanager".create_user;
@@ -90,16 +89,16 @@
 end);
 
 local recent_ips = {};
-local min_seconds_between_registrations = config.get(module.host, "core", "min_seconds_between_registrations");
-local whitelist_only = config.get(module.host, "core", "whitelist_registration_only");
-local whitelisted_ips = config.get(module.host, "core", "registration_whitelist") or { "127.0.0.1" };
-local blacklisted_ips = config.get(module.host, "core", "registration_blacklist") or {};
+local min_seconds_between_registrations = module:get_option("min_seconds_between_registrations");
+local whitelist_only = module:get_option("whitelist_registration_only");
+local whitelisted_ips = module:get_option("registration_whitelist") or { "127.0.0.1" };
+local blacklisted_ips = module:get_option("registration_blacklist") or {};
 
 for _, ip in ipairs(whitelisted_ips) do whitelisted_ips[ip] = true; end
 for _, ip in ipairs(blacklisted_ips) do blacklisted_ips[ip] = true; end
 
 module:add_iq_handler("c2s_unauthed", "jabber:iq:register", function (session, stanza)
-	if config.get(module.host, "core", "allow_registration") == false then
+	if module:get_option("allow_registration") == false then
 		session.send(st.error_reply(stanza, "cancel", "service-unavailable"));
 	elseif stanza.tags[1].name == "query" then
 		local query = stanza.tags[1];
@@ -119,19 +118,18 @@
 				if username and password then
 					-- Check that the user is not blacklisted or registering too often
 					if blacklisted_ips[session.ip] or (whitelist_only and not whitelisted_ips[session.ip]) then
-							session.send(st.error_reply(stanza, "cancel", "not-acceptable"));
-							return;
+						session.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not allowed to register an account."));
+						return;
 					elseif min_seconds_between_registrations and not whitelisted_ips[session.ip] then
 						if not recent_ips[session.ip] then
 							recent_ips[session.ip] = { time = os_time(), count = 1 };
 						else
-						
 							local ip = recent_ips[session.ip];
 							ip.count = ip.count + 1;
 							
 							if os_time() - ip.time < min_seconds_between_registrations then
 								ip.time = os_time();
-								session.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+								session.send(st.error_reply(stanza, "wait", "not-acceptable"));
 								return;
 							end
 							ip.time = os_time();
@@ -140,18 +138,21 @@
 					-- FIXME shouldn't use table.concat
 					username = nodeprep(table.concat(username));
 					password = table.concat(password);
-					if usermanager_user_exists(username, session.host) then
-						session.send(st.error_reply(stanza, "cancel", "conflict"));
+					local host = module.host;
+					if not username then
+						session.send(st.error_reply(stanza, "modify", "not-acceptable", "The requested username is invalid."));
+					elseif usermanager_user_exists(username, host) then
+						session.send(st.error_reply(stanza, "cancel", "conflict", "The requested username already exists."));
 					else
-						if usermanager_create_user(username, password, session.host) then
+						if usermanager_create_user(username, password, host) then
 							session.send(st.reply(stanza)); -- user created!
-							module:log("info", "User account created: %s@%s", username, session.host);
+							module:log("info", "User account created: %s@%s", username, host);
 							module:fire_event("user-registered", { 
-								username = username, host = session.host, source = "mod_register",
+								username = username, host = host, source = "mod_register",
 								session = session });
 						else
 							-- TODO unable to write file, file may be locked, etc, what's the correct error?
-							session.send(st.error_reply(stanza, "wait", "internal-server-error"));
+							session.send(st.error_reply(stanza, "wait", "internal-server-error", "Failed to write data to disk."));
 						end
 					end
 				else
--- a/plugins/mod_roster.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_roster.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -24,7 +24,7 @@
 
 local rosterver_stream_feature = st.stanza("ver", {xmlns="urn:xmpp:features:rosterver"}):tag("optional"):up();
 module:add_event_hook("stream-features", 
-		function (session, features)												
+		function (session, features)
 			if session.username then
 				features:add_child(rosterver_stream_feature);
 			end
--- a/plugins/mod_saslauth.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_saslauth.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -13,6 +13,7 @@
 local sm_make_authenticated = require "core.sessionmanager".make_authenticated;
 local base64 = require "util.encodings".base64;
 
+local nodeprep = require "util.encodings".stringprep.nodeprep;
 local datamanager_load = require "util.datamanager".load;
 local usermanager_validate_credentials = require "core.usermanager".validate_credentials;
 local usermanager_get_supported_methods = require "core.usermanager".get_supported_methods;
@@ -24,7 +25,7 @@
 local md5 = require "util.hashes".md5;
 local config = require "core.configmanager";
 
-local secure_auth_only = config.get(module:get_host(), "core", "require_encryption");
+local secure_auth_only = config.get(module:get_host(), "core", "c2s_require_encryption") or config.get(module:get_host(), "core", "require_encryption");
 
 local log = module._log;
 
@@ -36,10 +37,25 @@
 
 default_authentication_profile = {
 	plain = function(username, realm)
-			return usermanager_get_password(username, realm), true;
+			local prepped_username = nodeprep(username);
+			if not prepped_username then
+				log("debug", "NODEprep failed on username: %s", username);
+				return "", nil;
+			end
+			local password = usermanager_get_password(prepped_username, realm);
+			if not password then
+				return "", nil;
+			end
+			return password, true;
 		end
 };
 
+anonymous_authentication_profile = {
+	anonymous = function(username, realm)
+			return true; -- for normal usage you should always return true here
+		end
+}
+
 local function build_reply(status, ret, err_msg)
 	local reply = st.stanza(status, {xmlns = xmlns_sasl});
 	if status == "challenge" then
@@ -61,7 +77,8 @@
 	if status == "failure" then
 		session.sasl_handler = nil;
 	elseif status == "success" then
-		if not session.sasl_handler.username then -- TODO move this to sessionmanager
+		local username = nodeprep(session.sasl_handler.username);
+		if not username then -- TODO move this to sessionmanager
 			module:log("warn", "SASL succeeded but we didn't get a username!");
 			session.sasl_handler = nil;
 			session:reset_stream();
@@ -73,30 +90,6 @@
 	end
 end
 
-local function credentials_callback(mechanism, ...)
-	if mechanism == "PLAIN" then
-		local username, hostname, password = ...;
-		local response = usermanager_validate_credentials(hostname, username, password, mechanism);
-		if response == nil then
-			return false;
-		else
-			return response;
-		end
-	elseif mechanism == "DIGEST-MD5" then
-		function func(x) return x; end
-		local node, domain, realm, decoder = ...;
-		local password = usermanager_get_password(node, domain);
-		if password then
-			if decoder then
-				node, realm, password = decoder(node), decoder(realm), decoder(password);
-			end
-			return func, md5(node..":"..realm..":"..password);
-		else
-			return func, nil;
-		end
-	end
-end
-
 local function sasl_handler(session, stanza)
 	if stanza.name == "auth" then
 		-- FIXME ignoring duplicates because ejabberd does
@@ -144,20 +137,20 @@
 				if secure_auth_only and not session.secure then
 					return;
 				end
-				session.sasl_handler = new_sasl(session.host, default_authentication_profile);
+				if config.get(session.host or "*", "core", "anonymous_login") then
+					session.sasl_handler = new_sasl(session.host, anonymous_authentication_profile);
+				else
+					session.sasl_handler = new_sasl(session.host, default_authentication_profile);
+				end
 				features:tag("mechanisms", mechanisms_attr);
 				-- TODO: Provide PLAIN only if TLS is active, this is a SHOULD from the introduction of RFC 4616. This behavior could be overridden via configuration but will issuing a warning or so.
-					if config.get(session.host or "*", "core", "anonymous_login") then
-						features:tag("mechanism"):text("ANONYMOUS"):up();
-					else
-						for k, v in pairs(session.sasl_handler:mechanisms()) do
-							features:tag("mechanism"):text(v):up();
-						end
-					end
+				for k, v in pairs(session.sasl_handler:mechanisms()) do
+					features:tag("mechanism"):text(v):up();
+				end
 				features:up();
 			else
 				features:tag("bind", bind_attr):tag("required"):up():up();
-				features:tag("session", xmpp_session_attr):up();
+				features:tag("session", xmpp_session_attr):tag("optional"):up():up();
 			end
 		end);
 
--- a/plugins/mod_selftests.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_selftests.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,14 +6,13 @@
 -- COPYING file in the source package for more information.
 --
 
-
+module.host = "*" -- Global module
 
 local st = require "util.stanza";
 local register_component = require "core.componentmanager".register_component;
 local core_route_stanza = core_route_stanza;
 local socket = require "socket";
-local config = require "core.configmanager";
-local ping_hosts = config.get("*", "mod_selftests", "ping_hosts") or { "coversant.interop.xmpp.org", "djabberd.interop.xmpp.org", "djabberd-trunk.interop.xmpp.org", "ejabberd.interop.xmpp.org", "openfire.interop.xmpp.org" };
+local ping_hosts = module:get_option("ping_hosts") or { "coversant.interop.xmpp.org", "djabberd.interop.xmpp.org", "djabberd-trunk.interop.xmpp.org", "ejabberd.interop.xmpp.org", "openfire.interop.xmpp.org" };
 
 local open_pings = {};
 
--- a/plugins/mod_time.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_time.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,8 +6,6 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
 local st = require "util.stanza";
 local datetime = require "util.datetime".datetime;
 local legacy = require "util.datetime".legacy;
@@ -16,23 +14,31 @@
 
 module:add_feature("urn:xmpp:time");
 
-module:add_iq_handler({"c2s", "s2sin"}, "urn:xmpp:time",
-	function(session, stanza)
-		if stanza.attr.type == "get" then
-			session.send(st.reply(stanza):tag("time", {xmlns="urn:xmpp:time"})
-				:tag("tzo"):text("+00:00"):up() -- FIXME get the timezone in a platform independent fashion
-				:tag("utc"):text(datetime()));
-		end
-	end);
+local function time_handler(event)
+	local origin, stanza = event.origin, event.stanza;
+	if stanza.attr.type == "get" then
+		origin.send(st.reply(stanza):tag("time", {xmlns="urn:xmpp:time"})
+			:tag("tzo"):text("+00:00"):up() -- TODO get the timezone in a platform independent fashion
+			:tag("utc"):text(datetime()));
+		return true;
+	end
+end
+
+module:hook("iq/bare/urn:xmpp:time:time", time_handler);
+module:hook("iq/host/urn:xmpp:time:time", time_handler);
 
 -- XEP-0090: Entity Time (deprecated)
 
 module:add_feature("jabber:iq:time");
 
-module:add_iq_handler({"c2s", "s2sin"}, "jabber:iq:time",
-	function(session, stanza)
-		if stanza.attr.type == "get" then
-			session.send(st.reply(stanza):tag("query", {xmlns="jabber:iq:time"})
-				:tag("utc"):text(legacy()));
-		end
-	end);
+local function legacy_time_handler(event)
+	local origin, stanza = event.origin, event.stanza;
+	if stanza.attr.type == "get" then
+		origin.send(st.reply(stanza):tag("query", {xmlns="jabber:iq:time"})
+			:tag("utc"):text(legacy()));
+		return true;
+	end
+end
+
+module:hook("iq/bare/jabber:iq:time:query", legacy_time_handler);
+module:hook("iq/host/jabber:iq:time:query", legacy_time_handler);
--- a/plugins/mod_tls.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_tls.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,20 +6,22 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
 local st = require "util.stanza";
 
-local xmlns_starttls ='urn:ietf:params:xml:ns:xmpp-tls';
+local xmlns_stream = 'http://etherx.jabber.org/streams';
+local xmlns_starttls = 'urn:ietf:params:xml:ns:xmpp-tls';
 
-local config = require "core.configmanager";
-local secure_auth_only = config.get("*", "core", "require_encryption");
+local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
+local secure_s2s_only = module:get_option("s2s_require_encryption");
 
 module:add_handler("c2s_unauthed", "starttls", xmlns_starttls,
 		function (session, stanza)
 			if session.conn.starttls then
 				session.send(st.stanza("proceed", { xmlns = xmlns_starttls }));
 				session:reset_stream();
+				if session.host and hosts[session.host].ssl_ctx_in then
+					session.conn.set_sslctx(hosts[session.host].ssl_ctx_in);
+				end
 				session.conn.starttls();
 				session.log("info", "TLS negotiation started...");
 				session.secure = false;
@@ -29,9 +31,27 @@
 			end
 		end);
 		
+module:add_handler("s2sin_unauthed", "starttls", xmlns_starttls,
+		function (session, stanza)
+			if session.conn.starttls then
+				session.sends2s(st.stanza("proceed", { xmlns = xmlns_starttls }));
+				session:reset_stream();
+				if session.to_host and hosts[session.to_host].ssl_ctx_in then
+					session.conn.set_sslctx(hosts[session.to_host].ssl_ctx_in);
+				end
+				session.conn.starttls();
+				session.log("info", "TLS negotiation started for incoming s2s...");
+				session.secure = false;
+			else
+				-- FIXME: What reply?
+				session.log("warn", "Attempt to start TLS, but TLS is not available on this s2s connection");
+			end
+		end);
+
+
 local starttls_attr = { xmlns = xmlns_starttls };
 module:add_event_hook("stream-features", 
-		function (session, features)												
+		function (session, features)
 			if session.conn.starttls then
 				features:tag("starttls", starttls_attr);
 				if secure_auth_only then
@@ -41,3 +61,37 @@
 				end
 			end
 		end);
+
+module:hook("s2s-stream-features", 
+		function (data)
+			local session, features = data.session, data.features;
+			if session.to_host and session.conn.starttls then
+				features:tag("starttls", starttls_attr):up();
+				if secure_s2s_only then
+					features:tag("required"):up():up();
+				else
+					features:up();
+				end
+			end
+		end);
+
+-- For s2sout connections, start TLS if we can
+module:hook_stanza(xmlns_stream, "features",
+		function (session, stanza)
+			module:log("debug", "Received features element");
+			if session.conn.starttls and stanza:child_with_ns(xmlns_starttls) then
+				module:log("%s is offering TLS, taking up the offer...", session.to_host);
+				session.sends2s("<starttls xmlns='"..xmlns_starttls.."'/>");
+				return true;
+			end
+		end, 500);
+
+module:hook_stanza(xmlns_starttls, "proceed",
+		function (session, stanza)
+			module:log("debug", "Proceeding with TLS on s2sout...");
+			local format, to_host, from_host = string.format, session.to_host, session.from_host;
+			session:reset_stream();
+			session.conn.starttls(true);
+			session.secure = false;
+			return true;
+		end);
--- a/plugins/mod_uptime.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_uptime.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,30 +6,17 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
-local st = require "util.stanza"
-
-local jid_split = require "util.jid".split;
-local t_concat = table.concat;
+local st = require "util.stanza";
 
 local start_time = prosody.start_time;
-
-prosody.events.add_handler("server-started", function () start_time = prosody.start_time end);
+prosody.events.add_handler("server-started", function() start_time = prosody.start_time end);
 
 module:add_feature("jabber:iq:last");
 
-module:add_iq_handler({"c2s", "s2sin"}, "jabber:iq:last", 
-	function (origin, stanza)
-		if stanza.tags[1].name == "query" then
-			if stanza.attr.type == "get" then
-				local node, host, resource = jid_split(stanza.attr.to);
-				if node or resource then
-					-- TODO
-				else
-					origin.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:last", seconds = tostring(os.difftime(os.time(), start_time))}));
-					return true;
-				end
-			end
-		end
-	end);
+module:hook("iq/host/jabber:iq:last:query", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	if stanza.attr.type == "get" then
+		origin.send(st.reply(stanza):tag("query", {xmlns = "jabber:iq:last", seconds = tostring(os.difftime(os.time(), start_time))}));
+		return true;
+	end
+end);
--- a/plugins/mod_vcard.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_vcard.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,58 +6,53 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
-local hosts = _G.hosts;
+local st = require "util.stanza"
+local jid_split = require "util.jid".split;
 local datamanager = require "util.datamanager"
 
-local st = require "util.stanza"
-local t_concat, t_insert = table.concat, table.insert;
-
-local jid = require "util.jid"
-local jid_split = jid.split;
-
 module:add_feature("vcard-temp");
 
-module:add_iq_handler({"c2s", "s2sin"}, "vcard-temp", 
-		function (session, stanza)
-			if stanza.tags[1].name == "vCard" then
-				local to = stanza.attr.to;
-				if stanza.attr.type == "get" then
-					local vCard;
-					if to then
-						local node, host = jid_split(to);
-						if hosts[host] and hosts[host].type == "local" then
-							vCard = st.deserialize(datamanager.load(node, host, "vcard")); -- load vCard for user or server
-						end
-					else
-						vCard = st.deserialize(datamanager.load(session.username, session.host, "vcard"));-- load user's own vCard
-					end
-					if vCard then
-						session.send(st.reply(stanza):add_child(vCard)); -- send vCard!
-					else
-						session.send(st.error_reply(stanza, "cancel", "item-not-found"));
-					end
-				elseif stanza.attr.type == "set" then
-					if not to or to == session.username.."@"..session.host then
-						if datamanager.store(session.username, session.host, "vcard", st.preserialize(stanza.tags[1])) then
-							session.send(st.reply(stanza));
-						else
-							-- TODO unable to write file, file may be locked, etc, what's the correct error?
-							session.send(st.error_reply(stanza, "wait", "internal-server-error"));
-						end
-					else
-						session.send(st.error_reply(stanza, "auth", "forbidden"));
-					end
-				end
-				return true;
+local function handle_vcard(event)
+	local session, stanza = event.origin, event.stanza;
+	local to = stanza.attr.to;
+	if stanza.attr.type == "get" then
+		local vCard;
+		if to then
+			local node, host = jid_split(to);
+			vCard = st.deserialize(datamanager.load(node, host, "vcard")); -- load vCard for user or server
+		else
+			vCard = st.deserialize(datamanager.load(session.username, session.host, "vcard"));-- load user's own vCard
+		end
+		if vCard then
+			session.send(st.reply(stanza):add_child(vCard)); -- send vCard!
+		else
+			session.send(st.error_reply(stanza, "cancel", "item-not-found"));
+		end
+	else
+		if not to then
+			if datamanager.store(session.username, session.host, "vcard", st.preserialize(stanza.tags[1])) then
+				session.send(st.reply(stanza));
+			else
+				-- TODO unable to write file, file may be locked, etc, what's the correct error?
+				session.send(st.error_reply(stanza, "wait", "internal-server-error"));
 			end
-		end);
+		else
+			session.send(st.error_reply(stanza, "auth", "forbidden"));
+		end
+	end
+	return true;
+end
+
+module:hook("iq/bare/vcard-temp:vCard", handle_vcard);
+module:hook("iq/host/vcard-temp:vCard", handle_vcard);
 
-local feature_vcard_attr = { var='vcard-temp' };
-module:add_event_hook("stream-features",
-					function (session, features)
-						if session.type == "c2s" then
-							features:tag("feature", feature_vcard_attr):up();
-						end
-					end);
+-- COMPAT: https://support.process-one.net/browse/EJAB-1045
+if module:get_option("vcard_compatibility") then
+	module:hook("iq/full", function(data)
+		local stanza = data.stanza;
+		local payload = stanza.tags[1];
+		if stanza.attr.type == "get" or stanza.attr.type == "set" and payload.name == "vCard" and payload.attr.xmlns == "vcard-temp" then
+			return handle_vcard(data);
+		end
+	end, 1);
+end
--- a/plugins/mod_version.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_version.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,17 +6,13 @@
 -- COPYING file in the source package for more information.
 --
 
-
-local prosody = prosody;
 local st = require "util.stanza";
 
-local xmlns_version = "jabber:iq:version"
-
-module:add_feature(xmlns_version);
+module:add_feature("jabber:iq:version");
 
 local version = "the best operating system ever!";
 
-if not require "core.configmanager".get("*", "core", "hide_os_type") then
+if not module:get_option("hide_os_type") then
 	if os.getenv("WINDIR") then
 		version = "Windows";
 	else
@@ -31,11 +27,15 @@
 
 version = version:match("^%s*(.-)%s*$") or version;
 
-module:add_iq_handler({"c2s", "s2sin"}, xmlns_version, function(session, stanza)
-	if stanza.attr.type == "get" then
-		session.send(st.reply(stanza):query(xmlns_version)
-			:tag("name"):text("Prosody"):up()
-			:tag("version"):text(prosody.version):up()
-			:tag("os"):text(version));
+local query = st.stanza("query", {xmlns = "jabber:iq:version"})
+	:tag("name"):text("Prosody"):up()
+	:tag("version"):text(prosody.version):up()
+	:tag("os"):text(version);
+
+module:hook("iq/host/jabber:iq:version:query", function(event)
+	local stanza = event.stanza;
+	if stanza.attr.type == "get" and stanza.attr.to == module.host then
+		event.origin.send(st.reply(stanza):add_child(query));
+		return true;
 	end
 end);
--- a/plugins/mod_watchregistrations.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_watchregistrations.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -9,12 +9,10 @@
 
 local host = module:get_host();
 
-local config = require "core.configmanager";
+local registration_watchers = module:get_option("registration_watchers") 
+	or module:get_option("admins") or {};
 
-local registration_watchers = config.get(host, "core", "registration_watchers") 
-	or config.get(host, "core", "admins") or {};
-
-local registration_alert = config.get(host, "core", "registration_notification") or "User $username just registered on $host from $ip";
+local registration_alert = module:get_option("registration_notification") or "User $username just registered on $host from $ip";
 
 local st = require "util.stanza";
 
--- a/plugins/mod_welcome.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_welcome.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,10 +6,8 @@
 -- COPYING file in the source package for more information.
 --
 
-local config = require "core.configmanager";
-
 local host = module:get_host();
-local welcome_text = config.get("*", "core", "welcome_message") or "Hello $user, welcome to the $host IM server!";
+local welcome_text = module:get_option("welcome_message") or "Hello $username, welcome to the $host IM server!";
 
 local st = require "util.stanza";
 
--- a/plugins/mod_xmlrpc.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/plugins/mod_xmlrpc.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -83,7 +83,7 @@
 		end
 		return create_error_response(500, "Error in creating response: "..result);
 	end
-	return create_error_response(0, (result and result:gmatch("[^:]*:[^:]*: (.*)")()) or "nil");
+	return create_error_response(0, tostring(result):gsub("^[^:]+:%d+: ", ""));
 end
 
 local function handle_xmpp_request(origin, stanza)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/muc/mod_muc.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -0,0 +1,164 @@
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+
+if module:get_host_type() ~= "component" then
+	error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0);
+end
+
+local muc_host = module:get_host();
+local muc_name = module:get_option("name");
+if type(muc_name) ~= "string" then muc_name = "Prosody Chatrooms"; end
+local restrict_room_creation = module:get_option("restrict_room_creation");
+if restrict_room_creation and restrict_room_creation ~= true then restrict_room_creation = nil; end
+local history_length = 20;
+
+local muc_new_room = module:require "muc".new_room;
+local register_component = require "core.componentmanager".register_component;
+local deregister_component = require "core.componentmanager".deregister_component;
+local jid_split = require "util.jid".split;
+local jid_bare = require "util.jid".bare;
+local st = require "util.stanza";
+local uuid_gen = require "util.uuid".generate;
+local datamanager = require "util.datamanager";
+local um_is_admin = require "core.usermanager".is_admin;
+
+local rooms = {};
+local persistent_rooms = datamanager.load(nil, muc_host, "persistent") or {};
+local component;
+
+local function is_admin(jid)
+	return um_is_admin(jid) or um_is_admin(jid, module.host);
+end
+
+local function room_route_stanza(room, stanza) core_post_stanza(component, stanza); end
+local function room_save(room, forced)
+	local node = jid_split(room.jid);
+	persistent_rooms[room.jid] = room._data.persistent;
+	if room._data.persistent then
+		local history = room._data.history;
+		room._data.history = nil;
+		local data = {
+			jid = room.jid;
+			_data = room._data;
+			_affiliations = room._affiliations;
+		};
+		datamanager.store(node, muc_host, "config", data);
+		room._data.history = history;
+	elseif forced then
+		datamanager.store(node, muc_host, "config", nil);
+	end
+	if forced then datamanager.store(nil, muc_host, "persistent", persistent_rooms); end
+end
+
+for jid in pairs(persistent_rooms) do
+	local node = jid_split(jid);
+	local data = datamanager.load(node, muc_host, "config") or {};
+	local room = muc_new_room(jid);
+	room._data = data._data;
+	room._affiliations = data._affiliations;
+	room.route_stanza = room_route_stanza;
+	room.save = room_save;
+	rooms[jid] = room;
+end
+
+local host_room = muc_new_room(muc_host);
+host_room.route_stanza = room_route_stanza;
+host_room.save = room_save;
+
+local function get_disco_info(stanza)
+	return st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
+		:tag("identity", {category='conference', type='text', name=muc_name}):up()
+		:tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
+end
+local function get_disco_items(stanza)
+	local reply = st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
+	for jid, room in pairs(rooms) do
+		if not room._data.hidden then
+			reply:tag("item", {jid=jid, name=jid}):up();
+		end
+	end
+	return reply; -- TODO cache disco reply
+end
+
+local function handle_to_domain(origin, stanza)
+	local type = stanza.attr.type;
+	if type == "error" or type == "result" then return; end
+	if stanza.name == "iq" and type == "get" then
+		local xmlns = stanza.tags[1].attr.xmlns;
+		if xmlns == "http://jabber.org/protocol/disco#info" then
+			origin.send(get_disco_info(stanza));
+		elseif xmlns == "http://jabber.org/protocol/disco#items" then
+			origin.send(get_disco_items(stanza));
+		elseif xmlns == "http://jabber.org/protocol/muc#unique" then
+			origin.send(st.reply(stanza):tag("unique", {xmlns = xmlns}):text(uuid_gen())); -- FIXME Random UUIDs can theoretically have collisions
+		else
+			origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc
+		end
+	else
+		host_room:handle_stanza(origin, stanza);
+		--origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it"));
+	end
+end
+
+component = register_component(muc_host, function(origin, stanza)
+	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
+	if to_node then
+		local bare = to_node.."@"..to_host;
+		if to_host == muc_host or bare == muc_host then
+			local room = rooms[bare];
+			if not room then
+				if not(restrict_room_creation) or is_admin(stanza.attr.from) then
+					room = muc_new_room(bare);
+					room.route_stanza = room_route_stanza;
+					room.save = room_save;
+					rooms[bare] = room;
+				end
+			end
+			if room then
+				room:handle_stanza(origin, stanza);
+				if not next(room._occupants) and not persistent_rooms[room.jid] then -- empty, non-persistent room
+					rooms[bare] = nil; -- discard room
+				end
+			else
+				origin.send(st.error_reply(stanza, "cancel", "not-allowed"));
+			end
+		else --[[not for us?]] end
+		return;
+	end
+	-- to the main muc domain
+	handle_to_domain(origin, stanza);
+end);
+function component.send(stanza) -- FIXME do a generic fix
+	if stanza.attr.type == "result" or stanza.attr.type == "error" then
+		core_post_stanza(component, stanza);
+	else error("component.send only supports result and error stanzas at the moment"); end
+end
+
+prosody.hosts[module:get_host()].muc = { rooms = rooms };
+
+module.unload = function()
+	deregister_component(muc_host);
+end
+module.save = function()
+	return {rooms = rooms};
+end
+module.restore = function(data)
+	rooms = {};
+	for jid, oldroom in pairs(data.rooms or {}) do
+		local room = muc_new_room(jid);
+		room._jid_nick = oldroom._jid_nick;
+		room._occupants = oldroom._occupants;
+		room._data = oldroom._data;
+		room._affiliations = oldroom._affiliations;
+		room.route_stanza = room_route_stanza;
+		room.save = room_save;
+		rooms[jid] = room;
+	end
+	prosody.hosts[module:get_host()].muc = { rooms = rooms };
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/muc/muc.lib.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -0,0 +1,735 @@
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local datamanager = require "util.datamanager";
+local datetime = require "util.datetime";
+
+local jid_split = require "util.jid".split;
+local jid_bare = require "util.jid".bare;
+local jid_prep = require "util.jid".prep;
+local st = require "util.stanza";
+local log = require "util.logger".init("mod_muc");
+local multitable_new = require "util.multitable".new;
+local t_insert, t_remove = table.insert, table.remove;
+local setmetatable = setmetatable;
+local base64 = require "util.encodings".base64;
+local md5 = require "util.hashes".md5;
+
+local muc_domain = nil; --module:get_host();
+local history_length = 20;
+
+------------
+local function filter_xmlns_from_array(array, filters)
+	local count = 0;
+	for i=#array,1,-1 do
+		local attr = array[i].attr;
+		if filters[attr and attr.xmlns] then
+			t_remove(array, i);
+			count = count + 1;
+		end
+	end
+	return count;
+end
+local function filter_xmlns_from_stanza(stanza, filters)
+	if filters then
+		if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then
+			return stanza, filter_xmlns_from_array(stanza, filters);
+		end
+	end
+	return stanza, 0;
+end
+local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
+local function get_filtered_presence(stanza)
+	return filter_xmlns_from_stanza(st.clone(stanza):reset(), presence_filters);
+end
+local kickable_error_conditions = {
+	["gone"] = true;
+	["internal-server-error"] = true;
+	["item-not-found"] = true;
+	["jid-malformed"] = true;
+	["recipient-unavailable"] = true;
+	["redirect"] = true;
+	["remote-server-not-found"] = true;
+	["remote-server-timeout"] = true;
+	["service-unavailable"] = true;
+	["malformed error"] = true;
+};
+local function get_error_condition(stanza)
+	for _, tag in ipairs(stanza.tags) do
+		if tag.name == "error" and (not(tag.attr.xmlns) or tag.attr.xmlns == "jabber:client") then
+			for _, cond in ipairs(tag.tags) do
+				if cond.attr.xmlns == "urn:ietf:params:xml:ns:xmpp-stanzas" then
+					return cond.name;
+				end
+			end
+			return "malformed error";
+		end
+	end
+	return "malformed error";
+end
+local function is_kickable_error(stanza)
+	local cond = get_error_condition(stanza);
+	return kickable_error_conditions[cond] and cond;
+end
+local function getUsingPath(stanza, path, getText)
+	local tag = stanza;
+	for _, name in ipairs(path) do
+		if type(tag) ~= 'table' then return; end
+		tag = tag:child_with_name(name);
+	end
+	if tag and getText then tag = table.concat(tag); end
+	return tag;
+end
+local function getTag(stanza, path) return getUsingPath(stanza, path); end
+local function getText(stanza, path) return getUsingPath(stanza, path, true); end
+-----------
+
+--[[function get_room_disco_info(room, stanza)
+	return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
+		:tag("identity", {category='conference', type='text', name=room._data["name"]):up()
+		:tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
+end
+function get_room_disco_items(room, stanza)
+	return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
+end -- TODO allow non-private rooms]]
+
+--
+
+local room_mt = {};
+room_mt.__index = room_mt;
+
+function room_mt:get_default_role(affiliation)
+	if affiliation == "owner" or affiliation == "admin" then
+		return "moderator";
+	elseif affiliation == "member" or not affiliation then
+		return "participant";
+	end
+end
+
+function room_mt:broadcast_presence(stanza, sid, code, nick)
+	stanza = get_filtered_presence(stanza);
+	local occupant = self._occupants[stanza.attr.from];
+	stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+		:tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none", nick=nick}):up();
+	if code then
+		stanza:tag("status", {code=code}):up();
+	end
+	self:broadcast_except_nick(stanza, stanza.attr.from);
+	local me = self._occupants[stanza.attr.from];
+	if me then
+		stanza:tag("status", {code='110'});
+		stanza.attr.to = sid;
+		self:_route_stanza(stanza);
+	end
+end
+function room_mt:broadcast_message(stanza, historic)
+	for occupant, o_data in pairs(self._occupants) do
+		for jid in pairs(o_data.sessions) do
+			stanza.attr.to = jid;
+			self:_route_stanza(stanza);
+		end
+	end
+	if historic then -- add to history
+		local history = self._data['history'];
+		if not history then history = {}; self._data['history'] = history; end
+		-- stanza = st.clone(stanza);
+		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
+		stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
+		t_insert(history, st.clone(st.preserialize(stanza)));
+		while #history > history_length do t_remove(history, 1) end
+	end
+end
+function room_mt:broadcast_except_nick(stanza, nick)
+	for rnick, occupant in pairs(self._occupants) do
+		if rnick ~= nick then
+			for jid in pairs(occupant.sessions) do
+				stanza.attr.to = jid;
+				self:_route_stanza(stanza);
+			end
+		end
+	end
+end
+
+function room_mt:send_occupant_list(to)
+	local current_nick = self._jid_nick[to];
+	for occupant, o_data in pairs(self._occupants) do
+		if occupant ~= current_nick then
+			local pres = get_filtered_presence(o_data.sessions[o_data.jid]);
+			pres.attr.to, pres.attr.from = to, occupant;
+			pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+				:tag("item", {affiliation=o_data.affiliation or "none", role=o_data.role or "none"}):up();
+			self:_route_stanza(pres);
+		end
+	end
+end
+function room_mt:send_history(to)
+	local history = self._data['history']; -- send discussion history
+	if history then
+		for _, msg in ipairs(history) do
+			msg = st.deserialize(msg);
+			msg.attr.to=to;
+			self:_route_stanza(msg);
+		end
+	end
+	if self._data['subject'] then
+		self:_route_stanza(st.message({type='groupchat', from=self.jid, to=to}):tag("subject"):text(self._data['subject']));
+	end
+end
+
+local function room_get_disco_info(self, stanza)
+	return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
+		:tag("identity", {category="conference", type="text"}):up()
+		:tag("feature", {var="http://jabber.org/protocol/muc"});
+end
+local function room_get_disco_items(self, stanza)
+	local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
+	for room_jid in pairs(self._occupants) do
+		reply:tag("item", {jid = room_jid, name = room_jid:match("/(.*)")}):up();
+	end
+	return reply;
+end
+function room_mt:set_subject(current_nick, subject)
+	-- TODO check nick's authority
+	if subject == "" then subject = nil; end
+	self._data['subject'] = subject;
+	if self.save then self:save(); end
+	local msg = st.message({type='groupchat', from=current_nick})
+		:tag('subject'):text(subject):up();
+	self:broadcast_message(msg, false);
+	return true;
+end
+
+function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
+	local from, to = stanza.attr.from, stanza.attr.to;
+	local room = jid_bare(to);
+	local current_nick = self._jid_nick[from];
+	local type = stanza.attr.type;
+	log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag());
+	if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end
+	if stanza.name == "presence" then
+		local pr = get_filtered_presence(stanza);
+		pr.attr.from = current_nick;
+		if type == "error" then -- error, kick em out!
+			if current_nick then
+				log("debug", "kicking %s from %s", current_nick, room);
+				self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to})
+					:tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable
+			end
+		elseif type == "unavailable" then -- unavailable
+			if current_nick then
+				log("debug", "%s leaving %s", current_nick, room);
+				local occupant = self._occupants[current_nick];
+				local new_jid = next(occupant.sessions);
+				if new_jid == from then new_jid = next(occupant.sessions, new_jid); end
+				if new_jid then
+					local jid = occupant.jid;
+					occupant.jid = new_jid;
+					occupant.sessions[from] = nil;
+					pr.attr.to = from;
+					pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+						:tag("item", {affiliation=occupant.affiliation or "none", role='none'}):up()
+						:tag("status", {code='110'});
+					self:_route_stanza(pr);
+					if jid ~= new_jid then
+						pr = st.clone(occupant.sessions[new_jid])
+							:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+							:tag("item", {affiliation=occupant.affiliation or "none", role=occupant.role or "none"});
+						pr.attr.from = current_nick;
+						self:broadcast_except_nick(pr, current_nick);
+					end
+				else
+					occupant.role = 'none';
+					self:broadcast_presence(pr, from);
+					self._occupants[current_nick] = nil;
+				end
+				self._jid_nick[from] = nil;
+			end
+		elseif not type then -- available
+			if current_nick then
+				--if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence
+					if current_nick == to then -- simple presence
+						log("debug", "%s broadcasted presence", current_nick);
+						self._occupants[current_nick].sessions[from] = pr;
+						self:broadcast_presence(pr, from);
+					else -- change nick
+						local occupant = self._occupants[current_nick];
+						local is_multisession = next(occupant.sessions, next(occupant.sessions));
+						if self._occupants[to] or is_multisession then
+							log("debug", "%s couldn't change nick", current_nick);
+							local reply = st.error_reply(stanza, "cancel", "conflict"):up();
+							reply.tags[1].attr.code = "409";
+							origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
+						else
+							local data = self._occupants[current_nick];
+							local to_nick = select(3, jid_split(to));
+							if to_nick then
+								log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to);
+								local p = st.presence({type='unavailable', from=current_nick});
+								self:broadcast_presence(p, from, '303', to_nick);
+								self._occupants[current_nick] = nil;
+								self._occupants[to] = data;
+								self._jid_nick[from] = to;
+								pr.attr.from = to;
+								self._occupants[to].sessions[from] = pr;
+								self:broadcast_presence(pr, from);
+							else
+								--TODO malformed-jid
+							end
+						end
+					end
+				--else -- possible rejoin
+				--	log("debug", "%s had connection replaced", current_nick);
+				--	self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to})
+				--		:tag('status'):text('Replaced by new connection'):up()); -- send unavailable
+				--	self:handle_to_occupant(origin, stanza); -- resend available
+				--end
+			else -- enter room
+				local new_nick = to;
+				local is_merge;
+				if self._occupants[to] then
+					if jid_bare(from) ~= jid_bare(self._occupants[to].jid) then
+						new_nick = nil;
+					end
+					is_merge = true;
+				end
+				if not new_nick then
+					log("debug", "%s couldn't join due to nick conflict: %s", from, to);
+					local reply = st.error_reply(stanza, "cancel", "conflict"):up();
+					reply.tags[1].attr.code = "409";
+					origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
+				else
+					log("debug", "%s joining as %s", from, to);
+					if not next(self._affiliations) then -- new room, no owners
+						self._affiliations[jid_bare(from)] = "owner";
+					end
+					local affiliation = self:get_affiliation(from);
+					local role = self:get_default_role(affiliation)
+					if role then -- new occupant
+						if not is_merge then
+							self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}};
+						else
+							self._occupants[to].sessions[from] = get_filtered_presence(stanza);
+						end
+						self._jid_nick[from] = to;
+						self:send_occupant_list(from);
+						pr.attr.from = to;
+						if not is_merge then
+							self:broadcast_presence(pr, from);
+						else
+							pr.attr.to = from;
+							self:_route_stanza(pr:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+								:tag("item", {affiliation=affiliation or "none", role=role or "none"}):up()
+								:tag("status", {code='110'}));
+						end
+						self:send_history(from);
+					else -- banned
+						local reply = st.error_reply(stanza, "auth", "forbidden"):up();
+						reply.tags[1].attr.code = "403";
+						origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
+					end
+				end
+			end
+		elseif type ~= 'result' then -- bad type
+			if type ~= 'visible' and type ~= 'invisible' then -- COMPAT ejabberd can broadcast or forward XEP-0018 presences
+				origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error?
+			end
+		end
+	elseif not current_nick then -- not in room
+		if type == "error" or type == "result" then
+			local id = stanza.name == "iq" and stanza.attr.id and base64.decode(stanza.attr.id);
+			local _nick, _id, _hash = (id or ""):match("^(.+)%z(.*)%z(.+)$");
+			local occupant = self._occupants[stanza.attr.to];
+			if occupant and _nick and self._jid_nick[_nick] and _id and _hash then
+				local id, _to = stanza.attr.id;
+				for jid in pairs(occupant.sessions) do
+					if md5(jid) == _hash then
+						_to = jid;
+						break;
+					end
+				end
+				if _to then
+					stanza.attr.to, stanza.attr.from, stanza.attr.id = _to, self._jid_nick[_nick], _id;
+					self:_route_stanza(stanza);
+					stanza.attr.to, stanza.attr.from, stanza.attr.id = to, from, id;
+				end
+			end
+		else
+			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+		end
+	elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM
+		origin.send(st.error_reply(stanza, "modify", "bad-request"));
+	elseif current_nick and stanza.name == "message" and type == "error" and is_kickable_error(stanza) then
+		log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
+		self:handle_to_occupant(origin, st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to})
+			:tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable
+	else -- private stanza
+		local o_data = self._occupants[to];
+		if o_data then
+			log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid);
+			local jid = o_data.jid;
+			local bare = jid_bare(jid);
+			stanza.attr.to, stanza.attr.from = jid, current_nick;
+			local id = stanza.attr.id;
+			if stanza.name=='iq' and type=='get' and stanza.tags[1].attr.xmlns == 'vcard-temp' and bare ~= jid then
+				stanza.attr.to = bare;
+				stanza.attr.id = base64.encode(jid.."\0"..id.."\0"..md5(from));
+			end
+			self:_route_stanza(stanza);
+			stanza.attr.to, stanza.attr.from, stanza.attr.id = to, from, id;
+		elseif type ~= "error" and type ~= "result" then -- recipient not in room
+			origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room"));
+		end
+	end
+end
+
+function room_mt:handle_form(origin, stanza)
+	if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return; end
+	if stanza.attr.type == "get" then
+		local title = "Configuration for "..self.jid;
+		origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
+			:tag("x", {xmlns='jabber:x:data', type='form'})
+				:tag("title"):text(title):up()
+				:tag("instructions"):text(title):up()
+				:tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
+				:tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
+					:tag("value"):text(self._data.persistent and "1" or "0"):up()
+				:up()
+				:tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
+					:tag("value"):text(self._data.hidden and "0" or "1"):up()
+				:up()
+		);
+	elseif stanza.attr.type == "set" then
+		local query = stanza.tags[1];
+		local form;
+		for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
+		if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
+		if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
+		if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+		local fields = {};
+		for _, field in pairs(form.tags) do
+			if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
+				fields[field.attr.var] = field.tags[1][1] or "";
+			end
+		end
+		if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+
+		local persistent = fields['muc#roomconfig_persistentroom'];
+		if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
+		else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+		self._data.persistent = persistent;
+		module:log("debug", "persistent=%s", tostring(persistent));
+
+		local public = fields['muc#roomconfig_publicroom'];
+		if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
+		else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+		self._data.hidden = not public and true or nil;
+
+		if self.save then self:save(true); end
+		origin.send(st.reply(stanza));
+	end
+end
+
+function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc
+	local type = stanza.attr.type;
+	local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
+	if stanza.name == "iq" then
+		if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" then
+			origin.send(room_get_disco_info(self, stanza));
+		elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" then
+			origin.send(room_get_disco_items(self, stanza));
+		elseif xmlns == "http://jabber.org/protocol/muc#admin" then
+			local actor = stanza.attr.from;
+			local affiliation = self:get_affiliation(actor);
+			local current_nick = self._jid_nick[actor];
+			local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation);
+			local item = stanza.tags[1].tags[1];
+			if item and item.name == "item" then
+				if type == "set" then
+					local callback = function() origin.send(st.reply(stanza)); end
+					if item.attr.jid then -- Validate provided JID
+						item.attr.jid = jid_prep(item.attr.jid);
+						if not item.attr.jid then
+							origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
+							return;
+						end
+					end
+					if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
+						local occupant = self._occupants[self.jid.."/"..item.attr.nick];
+						if occupant then item.attr.jid = occupant.jid; end
+					end
+					local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1];
+					if item.attr.affiliation and item.attr.jid and not item.attr.role then
+						local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback, reason);
+						if not success then origin.send(st.error_reply(stanza, errtype, err)); end
+					elseif item.attr.role and item.attr.nick and not item.attr.affiliation then
+						local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback, reason);
+						if not success then origin.send(st.error_reply(stanza, errtype, err)); end
+					else
+						origin.send(st.error_reply(stanza, "cancel", "bad-request"));
+					end
+				elseif type == "get" then
+					local _aff = item.attr.affiliation;
+					local _rol = item.attr.role;
+					if _aff and not _rol then
+						if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then
+							local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
+							for jid, affiliation in pairs(self._affiliations) do
+								if affiliation == _aff then
+									reply:tag("item", {affiliation = _aff, jid = jid}):up();
+								end
+							end
+							origin.send(reply);
+						else
+							origin.send(st.error_reply(stanza, "auth", "forbidden"));
+						end
+					elseif _rol and not _aff then
+						if role == "moderator" then
+							-- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway?
+							if _rol == "none" then _rol = nil; end
+							local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
+							for nick, occupant in pairs(self._occupants) do
+								if occupant.role == _rol then
+									reply:tag("item", {nick = nick, role = _rol or "none", affiliation = occupant.affiliation or "none", jid = occupant.jid}):up();
+								end
+							end
+							origin.send(reply);
+						else
+							origin.send(st.error_reply(stanza, "auth", "forbidden"));
+						end
+					else
+						origin.send(st.error_reply(stanza, "cancel", "bad-request"));
+					end
+				end
+			elseif type == "set" or type == "get" then
+				origin.send(st.error_reply(stanza, "cancel", "bad-request"));
+			end
+		elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then
+			self:handle_form(origin, stanza);
+		elseif type == "set" or type == "get" then
+			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+		end
+	elseif stanza.name == "message" and type == "groupchat" then
+		local from, to = stanza.attr.from, stanza.attr.to;
+		local room = jid_bare(to);
+		local current_nick = self._jid_nick[from];
+		if not current_nick then -- not in room
+			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+		else
+			local from = stanza.attr.from;
+			stanza.attr.from = current_nick;
+			local subject = getText(stanza, {"subject"});
+			if subject then
+				self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
+			else
+				self:broadcast_message(stanza, true);
+			end
+		end
+	elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then
+		local current_nick = self._jid_nick[stanza.attr.from];
+		log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
+		self:handle_to_occupant(origin, st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to})
+			:tag('status'):text('Kicked: '..get_error_condition(stanza))); -- send unavailable
+	elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick
+		local to = stanza.attr.to;
+		local current_nick = self._jid_nick[stanza.attr.from];
+		if current_nick then
+			stanza.attr.to = current_nick;
+			self:handle_to_occupant(origin, stanza);
+			stanza.attr.to = to;
+		elseif type ~= "error" and type ~= "result" then
+			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+		end
+	elseif stanza.name == "message" and not stanza.attr.type and #stanza.tags == 1 and self._jid_nick[stanza.attr.from]
+		and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" then
+		local x = stanza.tags[1];
+		local payload = (#x.tags == 1 and x.tags[1]);
+		if payload and payload.name == "invite" and payload.attr.to then
+			local _from, _to = stanza.attr.from, stanza.attr.to;
+			local _invitee = jid_prep(payload.attr.to);
+			if _invitee then
+				local _reason = payload.tags[1] and payload.tags[1].name == 'reason' and #payload.tags[1].tags == 0 and payload.tags[1][1];
+				local invite = st.message({from = _to, to = _invitee, id = stanza.attr.id})
+					:tag('x', {xmlns='http://jabber.org/protocol/muc#user'})
+						:tag('invite', {from=_from})
+							:tag('reason'):text(_reason or ""):up()
+						:up()
+					:up()
+					:tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this
+						:text(_reason or "")
+					:up()
+					:tag('body') -- Add a plain message for clients which don't support invites
+						:text(_from..' invited you to the room '.._to..(_reason and (' ('.._reason..')') or ""))
+					:up();
+				self:_route_stanza(invite);
+			else
+				origin.send(st.error_reply(stanza, "cancel", "jid-malformed"));
+			end
+		else
+			origin.send(st.error_reply(stanza, "cancel", "bad-request"));
+		end
+	else
+		if type == "error" or type == "result" then return; end
+		origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+	end
+end
+
+function room_mt:handle_stanza(origin, stanza)
+	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
+	if to_resource then
+		self:handle_to_occupant(origin, stanza);
+	else
+		self:handle_to_room(origin, stanza);
+	end
+end
+
+function room_mt:route_stanza(stanza) end -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end
+
+function room_mt:get_affiliation(jid)
+	local node, host, resource = jid_split(jid);
+	local bare = node and node.."@"..host or host;
+	local result = self._affiliations[bare]; -- Affiliations are granted, revoked, and maintained based on the user's bare JID.
+	if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned
+	return result;
+end
+function room_mt:set_affiliation(actor, jid, affiliation, callback, reason)
+	jid = jid_bare(jid);
+	if affiliation == "none" then affiliation = nil; end
+	if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then
+		return nil, "modify", "not-acceptable";
+	end
+	if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end
+	if jid_bare(actor) == jid then return nil, "cancel", "not-allowed"; end
+	self._affiliations[jid] = affiliation;
+	local role = self:get_default_role(affiliation);
+	local p = st.presence()
+		:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
+			:tag("item", {affiliation=affiliation or "none", role=role or "none"})
+				:tag("reason"):text(reason or ""):up()
+			:up();
+	local x = p.tags[1];
+	local item = x.tags[1];
+	if not role then -- getting kicked
+		p.attr.type = "unavailable";
+		if affiliation == "outcast" then
+			x:tag("status", {code="301"}):up(); -- banned
+		else
+			x:tag("status", {code="321"}):up(); -- affiliation change
+		end
+	end
+	local modified_nicks = {};
+	for nick, occupant in pairs(self._occupants) do
+		if jid_bare(occupant.jid) == jid then
+			if not role then -- getting kicked
+				self._occupants[nick] = nil;
+			else
+				t_insert(modified_nicks, nick);
+				occupant.affiliation, occupant.role = affiliation, role;
+			end
+			p.attr.from = nick;
+			for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick
+				if not role then self._jid_nick[jid] = nil; end
+				p.attr.to = jid;
+				self:_route_stanza(p);
+			end
+		end
+	end
+	if self.save then self:save(); end
+	if callback then callback(); end
+	for _, nick in ipairs(modified_nicks) do
+		p.attr.from = nick;
+		self:broadcast_except_nick(p, nick);
+	end
+	return true;
+end
+
+function room_mt:get_role(nick)
+	local session = self._occupants[nick];
+	return session and session.role or nil;
+end
+function room_mt:set_role(actor, nick, role, callback, reason)
+	if role == "none" then role = nil; end
+	if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end
+	if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end
+	local occupant = self._occupants[nick];
+	if not occupant then return nil, "modify", "not-acceptable"; end
+	if occupant.affiliation == "owner" or occupant.affiliation == "admin" then return nil, "cancel", "not-allowed"; end
+	local p = st.presence({from = nick})
+		:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
+			:tag("item", {affiliation=occupant.affiliation or "none", nick=nick, role=role or "none"})
+				:tag("reason"):text(reason or ""):up()
+			:up();
+	if not role then -- kick
+		p.attr.type = "unavailable";
+		self._occupants[nick] = nil;
+		for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick
+			self._jid_nick[jid] = nil;
+		end
+		p:tag("status", {code = "307"}):up();
+	else
+		occupant.role = role;
+	end
+	for jid in pairs(occupant.sessions) do -- send to all sessions of the nick
+		p.attr.to = jid;
+		self:_route_stanza(p);
+	end
+	if callback then callback(); end
+	self:broadcast_except_nick(p, nick);
+	return true;
+end
+
+function room_mt:_route_stanza(stanza)
+	local muc_child;
+	local to_occupant = self._occupants[self._jid_nick[stanza.attr.to]];
+	local from_occupant = self._occupants[stanza.attr.from];
+	if stanza.name == "presence" then
+		if to_occupant and from_occupant then
+			if to_occupant.role == "moderator" or jid_bare(to_occupant.jid) == jid_bare(from_occupant.jid) then
+				for i=#stanza.tags,1,-1 do
+					local tag = stanza.tags[i];
+					if tag.name == "x" and tag.attr.xmlns == "http://jabber.org/protocol/muc#user" then
+						muc_child = tag;
+						break;
+					end
+				end
+			end
+		end
+	end
+	if muc_child then
+		for _, item in pairs(muc_child.tags) do
+			if item.name == "item" then
+				if from_occupant == to_occupant then
+					item.attr.jid = stanza.attr.to;
+				else
+					item.attr.jid = from_occupant.jid;
+				end
+			end
+		end
+	end
+	self:route_stanza(stanza);
+	if muc_child then
+		for _, item in pairs(muc_child.tags) do
+			if item.name == "item" then
+				item.attr.jid = nil;
+			end
+		end
+	end
+end
+
+local _M = {}; -- module "muc"
+
+function _M.new_room(jid)
+	return setmetatable({
+		jid = jid;
+		_jid_nick = {};
+		_occupants = {};
+		_data = {};
+		_affiliations = {};
+	}, room_mt);
+end
+
+return _M;
--- a/prosody	Fri Nov 13 14:31:03 2009 +0100
+++ b/prosody	Mon Nov 16 21:43:57 2009 +0100
@@ -33,6 +33,25 @@
 -- Required to be able to find packages installed with luarocks
 pcall(require, "luarocks.require")
 
+-- Replace require with one that doesn't pollute _G
+do
+	local _realG = _G;
+	local _real_require = require;
+	function require(...)
+		local curr_env = getfenv(2);
+		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
+			old_newindex, _realG_mt.__newindex = _realG_mt.__newindex, curr_env;
+			local ret = _real_require(...);
+			_realG_mt.__newindex = old_newindex;
+			return ret;
+		end
+		return _real_require(...);
+	end
+end
+
 
 config = require "core.configmanager"
 
@@ -93,6 +112,17 @@
 
 	prosody.events = require "util.events".new();
 	
+	prosody.platform = "unknown";
+	if os.getenv("WINDIR") then
+		prosody.platform = "windows";
+	elseif package.config:sub(1,1) == "/" then
+		prosody.platform = "posix";
+	end
+	
+	prosody.installed = nil;
+	if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
+		prosody.installed = true;
+	end
 	
 	-- Function to reload the config file
 	function prosody.reload_config()
@@ -151,11 +181,16 @@
 	require "core.sessionmanager"
 	require "core.stanza_router"
 
+	require "net.http"
+	
 	require "util.array"
+	require "util.datetime"
 	require "util.iterators"
 	require "util.timer"
 	require "util.helpers"
 	
+	pcall(require, "util.signal") -- Not on Windows
+	
 	-- Commented to protect us from 
 	-- the second kind of people
 	--[[ 
@@ -186,7 +221,7 @@
 	prosody.events.fire_event("server-starting");
 
 	-- Load SSL settings from config, and create a ctx table
-	local global_ssl_ctx = ssl and config.get("*", "core", "ssl");
+	local global_ssl_ctx = rawget(_G, "ssl") and config.get("*", "core", "ssl");
 	if global_ssl_ctx then
 		local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
 		setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
@@ -194,7 +229,7 @@
 
 	local cl = require "net.connlisteners";
 	-- start listening on sockets
-	function net_activate_ports(option, listener, default, conntype)
+	function prosody.net_activate_ports(option, listener, default, conntype)
 		if not cl.get(listener) then return; end
 		local ports = config.get("*", "core", option.."_ports") or default;
 		if type(ports) == "number" then ports = {ports} end;
@@ -219,11 +254,11 @@
 		end
 	end
 
-	net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
-	net_activate_ports("s2s", "xmppserver", {5269}, "tcp");
-	net_activate_ports("component", "xmppcomponent", {}, "tcp");
-	net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
-	net_activate_ports("console", "console", {5582}, "tcp");
+	prosody.net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
+	prosody.net_activate_ports("s2s", "xmppserver", {5269}, (global_ssl_ctx and "tls") or "tcp");
+	prosody.net_activate_ports("component", "xmppcomponent", {}, "tcp");
+	prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
+	prosody.net_activate_ports("console", "console", {5582}, "tcp");
 
 	prosody.start_time = os.time();
 end	
@@ -247,7 +282,7 @@
 function loop()
 	-- Error handler for errors that make it this far
 	local function catch_uncaught_error(err)
-		if err:match("%d*: interrupted!$") then
+		if type(err) == "string" and err:match("%d*: interrupted!$") then
 			return "quitting";
 		end
 		
@@ -314,8 +349,8 @@
 log("info", "Hello and welcome to Prosody version %s", prosody.version);
 load_secondary_libraries();
 init_data_store();
+init_global_protection();
 prepare_to_start();
-init_global_protection();
 
 eventmanager.fire_event("server-started");
 prosody.events.fire_event("server-started");
--- a/prosody.cfg.lua.dist	Fri Nov 13 14:31:03 2009 +0100
+++ b/prosody.cfg.lua.dist	Mon Nov 16 21:43:57 2009 +0100
@@ -1,104 +1,110 @@
--- Prosody Example Configuration File 
--- 
--- If it wasn't already obvious, -- starts a comment, and all 
--- text after it on a line is ignored by Prosody.
---
--- The config is split into sections, a global section, and one 
--- for each defined host that we serve. You can add as many host 
--- sections as you like.
---
--- Lists are written { "like", "this", "one" } 
--- Lists can also be of { 1, 2, 3 } numbers, and other things. 
--- Either commas, or semi-colons; may be used
--- as seperators.
---
--- A table is a list of values, except each value has a name. An 
--- example table would be:
---
--- ssl = { key = "keyfile.key", certificate = "certificate.cert" }
---
--- Whitespace (that is tabs, spaces, line breaks) is mostly insignificant, so 
--- can 
--- be placed anywhere that 	you deem fitting.
---
--- Tip: You can check that the syntax of this file is correct when you have finished
--- by running: luac -p prosody.cfg.lua
--- If there are any errors, it will let you know what and where they are, otherwise it 
--- will keep quiet.
---
--- The only thing left to do is rename this file to remove the .dist ending, and fill in the 
--- blanks. Good luck, and happy Jabbering!
-
--- Server-wide settings go in this section
-Host "*"
-	
-	-- This is the list of modules Prosody will load on startup.
-	-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
-	modules_enabled = {
-			-- Generally required
-				"roster"; -- Allow users to have a roster. Recommended ;)
-				"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
-				"tls"; -- Add support for secure TLS on c2s/s2s connections
-				"dialback"; -- s2s dialback support
-			  	"disco"; -- Service discovery
-			
-			-- Not essential, but recommended
-				"private"; -- Private XML storage (for room bookmarks, etc.)
-				"vcard"; -- Allow users to set vCards
-			
-			-- Nice to have
-				"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
-				"version"; -- Replies to server version requests
-			  	"uptime"; -- Report how long server has been running
-			  	"time"; -- Let others know the time here on this server
-			  	"ping"; -- Replies to XMPP pings with pongs
-			  	"pep"; -- Enables users to publish their mood, activity, playing music and more
-				"register"; -- Allow users to register on this server using a client and change passwords
-
-			-- Other specific functionality
-				--"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
-			  	--"console"; -- telnet to port 5582 (needs console_enabled = true)
-				--"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
-				--"httpserver"; -- Serve static files from a directory over HTTP
-			  };
-	
-	-- These modules are auto-loaded, should you
-	-- for (for some mad reason) want to disable
-	-- them then uncomment them below
-	modules_disabled = {
-			-- "presence";
-			-- "message";
-			-- "iq";
-	};
-
-	-- Disable account creation by default, for security
-	-- For more information see http://prosody.im/doc/creating_accounts
-	allow_registration = false;
-	
-	-- These are the SSL/TLS-related settings. If you don't want
-	-- to use SSL/TLS, you may comment or remove this
-	ssl = { 
-		key = "certs/localhost.key";
-		certificate = "certs/localhost.cert";
-		}
-
--- This allows clients to connect to localhost. No harm in it.
-Host "localhost"
-
--- Section for example.com
--- (replace example.com with your domain name)
-Host "example.com"
-
-	enabled = false -- This will disable the host, preserving the config, but denying connections
-
-	-- Assign this host a certificate for TLS, otherwise it would use the one
-	-- set in the global section (if any).
-	-- Note that old-style SSL on port 5223 only supports one certificate, and will always
-	-- use the global one.
-	ssl = { 
-		key = "certs/example.com.key";
-		certificate = "certs/example.com.crt";
-		}
-
--- Set up a MUC (multi-user chat) room server on conference.example.com:
-Component "conference.example.com" "muc"
+-- Prosody Example Configuration File 
+-- 
+-- If it wasn't already obvious, -- starts a comment, and all 
+-- text after it on a line is ignored by Prosody.
+--
+-- The config is split into sections, a global section, and one 
+-- for each defined host that we serve. You can add as many host 
+-- sections as you like.
+--
+-- Lists are written { "like", "this", "one" } 
+-- Lists can also be of { 1, 2, 3 } numbers, and other things. 
+-- Either commas, or semi-colons; may be used
+-- as seperators.
+--
+-- A table is a list of values, except each value has a name. An 
+-- example table would be:
+--
+-- ssl = { key = "keyfile.key", certificate = "certificate.cert" }
+--
+-- Whitespace (that is tabs, spaces, line breaks) is mostly insignificant, so 
+-- can 
+-- be placed anywhere that 	you deem fitting.
+--
+-- Tip: You can check that the syntax of this file is correct when you have finished
+-- by running: luac -p prosody.cfg.lua
+-- If there are any errors, it will let you know what and where they are, otherwise it 
+-- will keep quiet.
+--
+-- The only thing left to do is rename this file to remove the .dist ending, and fill in the 
+-- blanks. Good luck, and happy Jabbering!
+
+-- Server-wide settings go in this section
+Host "*"
+	
+	-- This is a (by default, empty) list of accounts that are admins 
+	-- for the server. Note that you must create the accounts separately
+	-- (see http://prosody.im/doc/creating_accounts for info)
+	-- Example: admins = { "user1@example.com", "user2@example.net" }
+	admins = { }
+	
+	-- This is the list of modules Prosody will load on startup.
+	-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
+	modules_enabled = {
+			-- Generally required
+				"roster"; -- Allow users to have a roster. Recommended ;)
+				"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
+				"tls"; -- Add support for secure TLS on c2s/s2s connections
+				"dialback"; -- s2s dialback support
+			  	"disco"; -- Service discovery
+			
+			-- Not essential, but recommended
+				"private"; -- Private XML storage (for room bookmarks, etc.)
+				"vcard"; -- Allow users to set vCards
+			
+			-- Nice to have
+				"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
+				"version"; -- Replies to server version requests
+			  	"uptime"; -- Report how long server has been running
+			  	"time"; -- Let others know the time here on this server
+			  	"ping"; -- Replies to XMPP pings with pongs
+			  	"pep"; -- Enables users to publish their mood, activity, playing music and more
+				"register"; -- Allow users to register on this server using a client and change passwords
+
+			-- Other specific functionality
+				--"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
+			  	--"console"; -- telnet to port 5582 (needs console_enabled = true)
+				--"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
+				--"httpserver"; -- Serve static files from a directory over HTTP
+			  };
+	
+	-- These modules are auto-loaded, should you
+	-- for (for some mad reason) want to disable
+	-- them then uncomment them below
+	modules_disabled = {
+			-- "presence";
+			-- "message";
+			-- "iq";
+	};
+
+	-- Disable account creation by default, for security
+	-- For more information see http://prosody.im/doc/creating_accounts
+	allow_registration = false;
+	
+	-- These are the SSL/TLS-related settings. If you don't want
+	-- to use SSL/TLS, you may comment or remove this
+	ssl = { 
+		key = "certs/localhost.key";
+		certificate = "certs/localhost.cert";
+		}
+
+-- This allows clients to connect to localhost. No harm in it.
+Host "localhost"
+
+-- Section for example.com
+-- (replace example.com with your domain name)
+Host "example.com"
+
+	enabled = false -- This will disable the host, preserving the config, but denying connections
+
+	-- Assign this host a certificate for TLS, otherwise it would use the one
+	-- set in the global section (if any).
+	-- Note that old-style SSL on port 5223 only supports one certificate, and will always
+	-- use the global one.
+	ssl = { 
+		key = "certs/example.com.key";
+		certificate = "certs/example.com.crt";
+		}
+
+-- Set up a MUC (multi-user chat) room server on conference.example.com:
+Component "conference.example.com" "muc"
--- a/prosodyctl	Fri Nov 13 14:31:03 2009 +0100
+++ b/prosodyctl	Mon Nov 16 21:43:57 2009 +0100
@@ -32,7 +32,6 @@
 -- Required to be able to find packages installed with luarocks
 pcall(require, "luarocks.require")
 
-
 config = require "core.configmanager"
 
 do
@@ -102,15 +101,20 @@
 		["no-password"] = "No password was supplied";
 		["no-such-user"] = "The given user does not exist on the server";
 		["unable-to-save-data"] = "Unable to store, perhaps you don't have permission?";
-		["no-pidfile"] = "There is no pidfile option in the configuration file, see http://prosody.im/doc/prosodyctl#pidfile for help";
+		["no-pidfile"] = "There is no 'pidfile' option in the configuration file, see http://prosody.im/doc/prosodyctl#pidfile for help";
 		["no-such-method"] = "This module has no commands";
 		["not-running"] = "Prosody is not running";
 		}, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
 
+local events = require "util.events".new();
+
 hosts = {};
+prosody = { hosts = hosts, events = events };
 
-require "core.hostmanager"
-require "core.eventmanager".fire_event("server-starting");
+for hostname, config in pairs(config.getconfig()) do
+	hosts[hostname] = { events = events };
+end
+	
 require "core.modulemanager"
 
 require "util.prosodyctl"
--- a/tests/test.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/tests/test.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -11,6 +11,7 @@
 function run_all_tests()
 	dotest "util.jid"
 	dotest "util.multitable"
+	dotest "core.modulemanager"
 	dotest "core.stanza_router"
 	dotest "core.s2smanager"
 	dotest "core.configmanager"
@@ -29,9 +30,11 @@
 	package.cpath = package.cpath..";../?.so";
 end
 
+local _realG = _G;
+
 require "util.import"
 
-local env_mt = { __index = function (t,k) return rawget(_G, k) or print("WARNING: Attempt to access nil global '"..tostring(k).."'"); end };
+local env_mt = { __index = function (t,k) return rawget(_realG, k) or print("WARNING: Attempt to access nil global '"..tostring(k).."'"); end };
 function testlib_new_env(t)
 	return setmetatable(t or {}, env_mt);
 end
@@ -65,7 +68,7 @@
 
 
 function dosingletest(testname, fname)
-	local tests = setmetatable({}, { __index = _G });
+	local tests = setmetatable({}, { __index = _realG });
 	tests.__unit = testname;
 	tests.__test = fname;
 	local chunk, err = loadfile(testname);
@@ -103,7 +106,7 @@
 end
 
 function dotest(unitname)
-	local tests = setmetatable({}, { __index = _G });
+	local tests = setmetatable({}, { __index = _realG });
 	tests.__unit = unitname;
 	local chunk, err = loadfile("test_"..unitname:gsub("%.", "_")..".lua");
 	if not chunk then
@@ -118,8 +121,9 @@
 		return;
 	end
 	
-	local unit = setmetatable({}, { __index = setmetatable({ module = function () end }, { __index = _G }) });
-
+	if tests.env then setmetatable(tests.env, { __index = _realG }); end
+	local unit = setmetatable({}, { __index = setmetatable({ _G = tests.env or _G }, { __index = tests.env or _G }) });
+	unit._G = unit; _realG._G = unit;
 	local fn = "../"..unitname:gsub("%.", "/")..".lua";
 	local chunk, err = loadfile(fn);
 	if not chunk then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_core_modulemanager.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -0,0 +1,48 @@
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local config = require "core.configmanager";
+local helpers = require "util.helpers";
+local set = require "util.set";
+
+function load_modules_for_host(load_modules_for_host, mm)
+	local test_num = 0;
+	local function test_load(global_modules_enabled, global_modules_disabled, host_modules_enabled, host_modules_disabled, expected_modules)
+		test_num = test_num + 1;
+		-- Prepare
+		hosts = { ["example.com"] = {} };
+		config.set("*", "core", "modules_enabled", global_modules_enabled);
+		config.set("*", "core", "modules_disabled", global_modules_disabled);
+		config.set("example.com", "core", "modules_enabled", host_modules_enabled);
+		config.set("example.com", "core", "modules_disabled", host_modules_disabled);
+		
+		expected_modules = set.new(expected_modules);
+		expected_modules:add_list(helpers.get_upvalue(load_modules_for_host, "autoload_modules"));
+		
+		local loaded_modules = set.new();
+		function mm.load(host, module)
+			assert_equal(host, "example.com", test_num..": Host isn't example.com but "..tostring(host));
+			assert_equal(expected_modules:contains(module), true, test_num..": Loading unexpected module '"..tostring(module).."'");
+			loaded_modules:add(module);
+		end
+		load_modules_for_host("example.com");
+		assert_equal((expected_modules - loaded_modules):empty(), true, test_num..": Not all modules loaded: "..tostring(expected_modules - loaded_modules));
+	end
+	
+	test_load({ "one", "two", "three" }, nil, nil, nil, { "one", "two", "three" });
+	test_load({ "one", "two", "three" }, {}, nil, nil, { "one", "two", "three" });
+	test_load({ "one", "two", "three" }, { "two" }, nil, nil, { "one", "three" });
+	test_load({ "one", "two", "three" }, { "three" }, nil, nil, { "one", "two" });
+	test_load({ "one", "two", "three" }, nil, nil, { "three" }, { "one", "two" });
+	test_load({ "one", "two", "three" }, nil, { "three" }, { "three" }, { "one", "two", "three" });
+
+	test_load({ "one", "two" }, nil, { "three" }, nil, { "one", "two", "three" });
+	test_load({ "one", "two", "three" }, nil, { "three" }, nil, { "one", "two", "three" });
+	test_load({ "one", "two", "three" }, { "three" }, { "three" }, nil, { "one", "two", "three" });
+	test_load({ "one", "two" }, { "three" }, { "three" }, nil, { "one", "two", "three" });
+end
--- a/tests/test_core_stanza_router.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/tests/test_core_stanza_router.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,17 +6,19 @@
 -- COPYING file in the source package for more information.
 --
 
-
+_G.prosody = { full_sessions = {}; bare_sessions = {}; hosts = {}; };
 
-function core_process_stanza(core_process_stanza)
+function core_process_stanza(core_process_stanza, u)
+	local stanza = require "util.stanza";
 	local s2sout_session = { to_host = "remotehost", from_host = "localhost", type = "s2sout" }
 	local s2sin_session = { from_host = "remotehost", to_host = "localhost", type = "s2sin", hosts = { ["remotehost"] = { authed = true } } }
 	local local_host_session = { host = "localhost", type = "local", s2sout = { ["remotehost"] = s2sout_session } }
 	local local_user_session = { username = "user", host = "localhost", resource = "resource", full_jid = "user@localhost/resource", type = "c2s" }
-	local hosts = {
-			["localhost"] = local_host_session;
-			}
-				
+	
+	_G.prosody.hosts["localhost"] = local_host_session;
+	_G.prosody.full_sessions["user@localhost/resource"] = local_user_session;
+	_G.prosody.bare_sessions["user@localhost"] = { sessions = { resource = local_user_session } };
+
 	-- Test message routing
 	local function test_message_full_jid()
 		local env = testlib_new_env();
@@ -24,12 +26,14 @@
 		
 		local target_routed;
 		
-		function env.core_route_stanza(p_origin, p_stanza)
+		function env.core_post_stanza(p_origin, p_stanza)
 			assert_equal(p_origin, local_user_session, "origin of routed stanza is not correct");
 			assert_equal(p_stanza, msg, "routed stanza is not correct one: "..p_stanza:pretty_print());
 			target_routed = true;
 		end
+		
 		env.hosts = hosts;
+		env.prosody = { hosts = hosts };
 		setfenv(core_process_stanza, env);
 		assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value");
 		assert_equal(target_routed, true, "stanza was not routed successfully");
@@ -41,11 +45,12 @@
 		
 		local target_routed;
 		
-		function env.core_route_stanza(p_origin, p_stanza)
+		function env.core_post_stanza(p_origin, p_stanza)
 			assert_equal(p_origin, local_user_session, "origin of routed stanza is not correct");
 			assert_equal(p_stanza, msg, "routed stanza is not correct one: "..p_stanza:pretty_print());
 			target_routed = true;
 		end
+
 		env.hosts = hosts;
 		setfenv(core_process_stanza, env);
 		assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value");
@@ -58,14 +63,12 @@
 		
 		local target_handled;
 		
-		function env.core_route_stanza(p_origin, p_stanza)
-		end
-
-		function env.core_handle_stanza(p_origin, p_stanza)
+		function env.core_post_stanza(p_origin, p_stanza)
 			assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct");
 			assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print());
 			target_handled = true;		
 		end
+
 		env.hosts = hosts;
 		setfenv(core_process_stanza, env);
 		assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value");
@@ -84,6 +87,8 @@
 			target_routed = true;		
 		end
 
+		function env.core_post_stanza(...) env.core_route_stanza(...); end
+		
 		env.hosts = hosts;
 		setfenv(core_process_stanza, env);
 		assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value");
@@ -102,6 +107,10 @@
 			target_routed = true;		
 		end
 
+		function env.core_post_stanza(...)
+			env.core_route_stanza(...);
+		end
+
 		env.hosts = hosts;
 		setfenv(core_process_stanza, env);
 		assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value");
@@ -113,7 +122,7 @@
 
 	local function test_iq_to_remote_server()
 		local env = testlib_new_env();
-		local msg = stanza.stanza("iq", { to = "remotehost", type = "chat" }):tag("body"):text("Hello world");
+		local msg = stanza.stanza("iq", { to = "remotehost", type = "get", id = "id" }):tag("body"):text("Hello world");
 		
 		local target_routed;
 		
@@ -123,8 +132,8 @@
 			target_routed = true;		
 		end
 
-		function env.core_handle_stanza(p_origin, p_stanza)
-			
+		function env.core_post_stanza(...)
+			env.core_route_stanza(...);
 		end
 
 		env.hosts = hosts;
@@ -135,7 +144,7 @@
 
 	local function test_iq_error_to_local_user()
 		local env = testlib_new_env();
-		local msg = stanza.stanza("iq", { to = "user@localhost/resource", from = "user@remotehost", type = "error" }):tag("error", { type = 'cancel' }):tag("item-not-found", { xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' });
+		local msg = stanza.stanza("iq", { to = "user@localhost/resource", from = "user@remotehost", type = "error", id = "id" }):tag("error", { type = 'cancel' }):tag("item-not-found", { xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' });
 		
 		local target_routed;
 		
@@ -145,8 +154,8 @@
 			target_routed = true;		
 		end
 
-		function env.core_handle_stanza(p_origin, p_stanza)
-			
+		function env.core_post_stanza(...)
+			env.core_route_stanza(...);
 		end
 
 		env.hosts = hosts;
@@ -157,20 +166,16 @@
 
 	local function test_iq_to_local_bare()
 		local env = testlib_new_env();
-		local msg = stanza.stanza("iq", { to = "user@localhost", from = "user@localhost", type = "get" }):tag("ping", { xmlns = "urn:xmpp:ping:0" });
+		local msg = stanza.stanza("iq", { to = "user@localhost", from = "user@localhost", type = "get", id = "id" }):tag("ping", { xmlns = "urn:xmpp:ping:0" });
 		
 		local target_handled;
 		
-		function env.core_handle_stanza(p_origin, p_stanza)
+		function env.core_post_stanza(p_origin, p_stanza)
 			assert_equal(p_origin, local_user_session, "origin of handled stanza is not correct");
 			assert_equal(p_stanza, msg, "handled stanza is not correct one: "..p_stanza:pretty_print());
 			target_handled = true;		
 		end
 
-		function env.core_route_stanza(p_origin, p_stanza)
-			
-		end
-
 		env.hosts = hosts;
 		setfenv(core_process_stanza, env);
 		assert_equal(core_process_stanza(local_user_session, msg), nil, "core_process_stanza returned incorrect value");
@@ -189,6 +194,7 @@
 end
 
 function core_route_stanza(core_route_stanza)
+	local stanza = require "util.stanza";
 	local s2sout_session = { to_host = "remotehost", from_host = "localhost", type = "s2sout" }
 	local s2sin_session = { from_host = "remotehost", to_host = "localhost", type = "s2sin", hosts = { ["remotehost"] = { authed = true } } }
 	local local_host_session = { host = "localhost", type = "local", s2sout = { ["remotehost"] = s2sout_session }, sessions = {} }
@@ -204,7 +210,7 @@
 		--package.loaded["core.usermanager"] = { user_exists = function (user, host) print("RAR!") return true or user == "user" and host == "localhost" and true; end };
 		local target_handled, target_replied;
 		
-		function env.core_handle_stanza(p_origin, p_stanza)
+		function env.core_post_stanza(p_origin, p_stanza)
 			target_handled = true;
 		end
 		
@@ -222,5 +228,5 @@
 		package.loaded["core.usermanager"] = nil;
 	end
 
-	runtest(test_iq_result_to_offline_user, "iq type=result|error to an offline user are not replied to");
+	--runtest(test_iq_result_to_offline_user, "iq type=result|error to an offline user are not replied to");
 end
--- a/tools/ejabberd2prosody.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/tools/ejabberd2prosody.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -9,9 +9,14 @@
 
 
 
+package.path = package.path ..";../?.lua";
+
+if arg[0]:match("^./") then
+	package.path = package.path .. ";"..arg[0]:gsub("/ejabberd2prosody.lua$", "/?.lua");
+end
+
 require "erlparse";
 
-package.path = package.path ..";../?.lua";
 local serialize = require "util.serialization".serialize;
 local st = require "util.stanza";
 package.loaded["util.logger"] = {init = function() return function() end; end}
@@ -86,13 +91,24 @@
 		local name = tuple[5]; local subscription = tuple[6];
 		local ask = tuple[7]; local groups = tuple[8];
 		if type(name) ~= type("") then name = nil; end
-		if ask == "none" then ask = nil; elseif ask == "out" then ask = "subscribe" elseif ask == "in" then
+		if ask == "none" then
+			ask = nil;
+		elseif ask == "out" then
+			ask = "subscribe"
+		elseif ask == "in" then
 			roster_pending(node, host, contact);
-			return;
-		else error(ask) end
+			ask = nil;
+		elseif ask == "both" then
+			roster_pending(node, host, contact);
+			ask = "subscribe";
+		else error("Unknown ask type: "..ask); end
 		if subscription ~= "both" and subscription ~= "from" and subscription ~= "to" and subscription ~= "none" then error(subscription) end
 		local item = {name = name, ask = ask, subscription = subscription, groups = {}};
-		for _, g in ipairs(groups) do item.groups[g] = true; end
+		for _, g in ipairs(groups) do
+			if type(g) == "string" then
+				item.groups[g] = true;
+			end
+		end
 		roster(node, host, contact, item);
 	end;
 	private_storage = function(tuple)
--- a/tools/ejabberdsql2prosody.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/tools/ejabberdsql2prosody.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -136,8 +136,8 @@
 	while true do
 		local tname, tuples = readInsert();
 		if tname then
-			if t[name] then
-				local t_name = t[name];
+			if t[tname] then
+				local t_name = t[tname];
 				for i=1,#tuples do
 					table.insert(t_name, tuples[i]);
 				end
@@ -284,6 +284,12 @@
 	local ret, err = dm.store(node, host, "private", private);
 	print("["..(err or "success").."] private: " ..node.."@"..host.." - "..xmlns);
 end
+function offline_msg(node, host, t, stanza)
+	stanza.attr.stamp = os.date("!%Y-%m-%dT%H:%M:%SZ", t);
+	stanza.attr.stamp_legacy = os.date("!%Y%m%dT%H:%M:%S", t);
+	local ret, err = dm.list_append(node, host, "offline", st.preserialize(stanza));
+	print("["..(err or "success").."] offline: " ..node.."@"..host.." - "..os.date("!%Y-%m-%dT%H:%M:%SZ", t));
+end
 for i, row in ipairs(t["rosterusers"] or NULL) do
 	local node, contact = row.username, row.jid;
 	local name = row.nick;
@@ -321,5 +327,20 @@
 	print("["..(err or "success").."] vCard: "..row.username.."@"..host);
 end
 for i, row in ipairs(t["private_storage"] or NULL) do
-	private_storage(row.username, host, row.namespace, st.preserialize(parse_xml(row.data)));
+	private_storage(row.username, host, row.namespace, parse_xml(row.data));
+end
+table.sort(t["spool"] or NULL, function(a,b) return a.seq < b.seq; end); -- sort by sequence number, just in case
+local time_offset = os.difftime(os.time(os.date("!*t")), os.time(os.date("*t"))) -- to deal with timezones
+local date_parse = function(s)
+	local year, month, day, hour, min, sec = s:match("(....)-?(..)-?(..)T(..):(..):(..)");
+	return os.time({year=year, month=month, day=day, hour=hour, min=min, sec=sec-time_offset});
 end
+for i, row in ipairs(t["spool"] or NULL) do
+	local stanza = parse_xml(row.xml);
+	local last_child = stanza.tags[#stanza.tags];
+	if not last_child or last_child ~= stanza[#stanza] then error("Last child of offline message is not a tag"); end
+	if last_child.name ~= "x" and last_child.attr.xmlns ~= "jabber:x:delay" then error("Last child of offline message is not a timestamp"); end
+	stanza[#stanza], stanza.tags[#stanza.tags] = nil, nil;
+	local t = date_parse(last_child.attr.stamp);
+	offline_msg(row.username, host, t, stanza);
+end
--- a/tools/erlparse.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/tools/erlparse.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -45,16 +45,26 @@
 	return ch <= _space;
 end
 
+local escapes = {["\\b"]="\b", ["\\d"]="\d", ["\\e"]="\e", ["\\f"]="\f", ["\\n"]="\n", ["\\r"]="\r", ["\\s"]="\s", ["\\t"]="\t", ["\\v"]="\v", ["\\\""]="\"", ["\\'"]="'", ["\\\\"]="\\"};
 local function readString()
 	read("\""); -- skip quote
 	local slash = nil;
 	local str = "";
 	while true do
 		local ch = read();
-		if ch == "\"" and not slash then break; end
-		str = str..ch;
+		if slash then
+			slash = slash..ch;
+			if not escapes[slash] then error("Unknown escape sequence: "..slash); end
+			str = str..escapes[slash];
+			slash = nil;
+		elseif ch == "\"" then
+			break;
+		elseif ch == "\\" then
+			slash = ch;
+		else
+			str = str..ch;
+		end
 	end
-	str = str:gsub("\\.", {["\\b"]="\b", ["\\d"]="\d", ["\\e"]="\e", ["\\f"]="\f", ["\\n"]="\n", ["\\r"]="\r", ["\\s"]="\s", ["\\t"]="\t", ["\\v"]="\v", ["\\\""]="\"", ["\\'"]="'", ["\\\\"]="\\"});
 	return str;
 end
 local function readAtom1()
--- a/util-src/Makefile	Fri Nov 13 14:31:03 2009 +0100
+++ b/util-src/Makefile	Mon Nov 16 21:43:57 2009 +0100
@@ -24,21 +24,28 @@
 encodings.o: encodings.c
 	$(CC) $(CFLAGS) -I$(LUA_INCDIR) -c -o encodings.o encodings.c
 encodings.so: encodings.o
-	export MACOSX_DEPLOYMENT_TARGET="10.3"; $(LD) $(LFLAGS) -o encodings.so encodings.o -L$(LUA_LIBDIR) -llua$(LUA_SUFFIX) -lidn
+	MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET;
+	$(LD) $(LFLAGS) -o encodings.so encodings.o -L$(LUA_LIBDIR) -llua$(LUA_SUFFIX) -lidn
 	
 
 hashes.o: hashes.c
 	$(CC) $(CFLAGS) -I$(LUA_INCDIR) -c -o hashes.o hashes.c
 hashes.so: hashes.o
-	export MACOSX_DEPLOYMENT_TARGET="10.3"; $(LD) $(LFLAGS) -o hashes.so hashes.o -L$(LUA_LIBDIR) -llua$(LUA_SUFFIX) -lcrypto
+	MACOSX_DEPLOYMENT_TARGET="10.3";
+	export MACOSX_DEPLOYMENT_TARGET;
+	$(LD) $(LFLAGS) -o hashes.so hashes.o -L$(LUA_LIBDIR) -llua$(LUA_SUFFIX) -lcrypto
 
 pposix.o: pposix.c
 	$(CC) $(CFLAGS) -I$(LUA_INCDIR) -c -o pposix.o pposix.c
 pposix.so: pposix.o
-	export MACOSX_DEPLOYMENT_TARGET="10.3"; $(LD) $(LFLAGS) -o pposix.so pposix.o -L$(LUA_LIBDIR) -llua$(LUA_SUFFIX)
+	MACOSX_DEPLOYMENT_TARGET="10.3";
+	export MACOSX_DEPLOYMENT_TARGET;
+	$(LD) $(LFLAGS) -o pposix.so pposix.o -L$(LUA_LIBDIR) -llua$(LUA_SUFFIX)
 	
 lsignal.o: lsignal.c
 	$(CC) $(CFLAGS) -I$(LUA_INCDIR) -c -o lsignal.o lsignal.c
 signal.so: lsignal.o
-	export MACOSX_DEPLOYMENT_TARGET="10.3"; $(LD) $(LFLAGS) -o signal.so lsignal.o
+	MACOSX_DEPLOYMENT_TARGET="10.3";
+	export MACOSX_DEPLOYMENT_TARGET;
+	$(LD) $(LFLAGS) -o signal.so lsignal.o -L$(LUA_LIBDIR) -llua$(LUA_SUFFIX)
 	
--- a/util-src/Makefile.win	Fri Nov 13 14:31:03 2009 +0100
+++ b/util-src/Makefile.win	Mon Nov 16 21:43:57 2009 +0100
@@ -1,7 +1,7 @@
 
 LUA_PATH=$(LUA_DEV)
-IDN_PATH=.\libidn-1.9
-OPENSSL_PATH=.\openssl-0.9.8i
+IDN_PATH=..\..\libidn-1.15
+OPENSSL_PATH=..\..\openssl-0.9.8k
 
 LUA_INCLUDE=$(LUA_PATH)\include
 LUA_LIB=$(LUA_PATH)\lib\lua5.1.lib
@@ -12,18 +12,27 @@
 OPENSSL_LIB=$(OPENSSL_PATH)\out32dll\libeay32.lib
 OPENSSL_INCLUDE=$(OPENSSL_PATH)\include
 
-all: encodings.dll hashes.dll
+CL=cl /LD /MD /nologo
 
-install: encodings.dll hashes.dll
+all: encodings.dll hashes.dll windows.dll
+
+install: encodings.dll hashes.dll windows.dll
 	copy /Y *.dll ..\util\
 
 clean:
-	del encodings.dll encodings.exp encodings.lib encodings.obj
-	del hashes.dll hashes.exp hashes.lib hashes.obj
+	del encodings.dll encodings.exp encodings.lib encodings.obj encodings.dll.manifest
+	del hashes.dll hashes.exp hashes.lib hashes.obj hashes.dll.manifest
+	del windows.dll windows.exp windows.lib windows.obj windows.dll.manifest
 
 encodings.dll: encodings.c
-	cl /LD /nologo encodings.c /I"$(LUA_INCLUDE)" /I"$(IDN_INCLUDE1)" /I"$(IDN_INCLUDE2)" /link "$(LUA_LIB)" "$(IDN_LIB)" /export:luaopen_util_encodings
+	$(CL) encodings.c /I"$(LUA_INCLUDE)" /I"$(IDN_INCLUDE1)" /I"$(IDN_INCLUDE2)" /link "$(LUA_LIB)" "$(IDN_LIB)" /export:luaopen_util_encodings
+	del encodings.exp encodings.lib encodings.obj encodings.dll.manifest
 
 hashes.dll: hashes.c
-	cl /LD /nologo hashes.c /I"$(LUA_INCLUDE)" /I"$(OPENSSL_INCLUDE)" /link "$(LUA_LIB)" "$(OPENSSL_LIB)" /export:luaopen_util_hashes
+	$(CL) hashes.c /I"$(LUA_INCLUDE)" /I"$(OPENSSL_INCLUDE)" /link "$(LUA_LIB)" "$(OPENSSL_LIB)" /export:luaopen_util_hashes
+	del hashes.exp hashes.lib hashes.obj hashes.dll.manifest
 
+windows.dll: windows.c
+	$(CL) windows.c /I"$(LUA_INCLUDE)" /link "$(LUA_LIB)" dnsapi.lib /export:luaopen_util_windows
+	del windows.exp windows.lib windows.obj windows.dll.manifest
+
--- a/util-src/encodings.c	Fri Nov 13 14:31:03 2009 +0100
+++ b/util-src/encodings.c	Mon Nov 16 21:43:57 2009 +0100
@@ -108,7 +108,6 @@
 				break;
 		}
 	}
-	return 0;
 }
 
 static const luaL_Reg Reg_base64[] =
@@ -125,9 +124,14 @@
 static int stringprep_prep(lua_State *L, const Stringprep_profile *profile)
 {
 	size_t len;
-	const char *s = luaL_checklstring(L, 1, &len);
+	const char *s;
 	char string[1024];
 	int ret;
+	if(!lua_isstring(L, 1)) {
+		lua_pushnil(L);
+		return 1;
+	}
+	s = lua_tolstring(L, 1, &len);
 	if (len >= 1024) {
 		lua_pushnil(L);
 		return 1; // TODO return error message
@@ -163,6 +167,7 @@
 /***************** IDNA *****************/
 
 #include <idna.h>
+#include <idn-free.h>
 
 static int Lidna_to_ascii(lua_State *L)		/** idna.to_ascii(s) */
 {
@@ -172,11 +177,11 @@
 	int ret = idna_to_ascii_8z(s, &output, 0);
 	if (ret == IDNA_SUCCESS) {
 		lua_pushstring(L, output);
-		if (output) free(output);
+		idn_free(output);
 		return 1;
 	} else {
 		lua_pushnil(L);
-		if (output) free(output);
+		idn_free(output);
 		return 1; // TODO return error message
 	}
 }
@@ -189,11 +194,11 @@
 	int ret = idna_to_unicode_8z8z(s, &output, 0);
 	if (ret == IDNA_SUCCESS) {
 		lua_pushstring(L, output);
-		if (output) free(output);
+		idn_free(output);
 		return 1;
 	} else {
 		lua_pushnil(L);
-		if (output) free(output);
+		idn_free(output);
 		return 1; // TODO return error message
 	}
 }
--- a/util-src/lsignal.c	Fri Nov 13 14:31:03 2009 +0100
+++ b/util-src/lsignal.c	Mon Nov 16 21:43:57 2009 +0100
@@ -301,7 +301,7 @@
   return 1;
 }
 
-#ifdef _POSIX_SOURCE
+#if defined _POSIX_SOURCE || (defined(sun) || defined(__sun))
 
 /* define some posix only functions */
 
--- a/util-src/pposix.c	Fri Nov 13 14:31:03 2009 +0100
+++ b/util-src/pposix.c	Mon Nov 16 21:43:57 2009 +0100
@@ -91,10 +91,14 @@
 
 const char * const facility_strings[] = {
 					"auth",
+#if !(defined(sun) || defined(__sun))
 					"authpriv",
+#endif
 					"cron",
 					"daemon",
+#if !(defined(sun) || defined(__sun))
 					"ftp",
+#endif
 					"kern",
 					"local0",
 					"local1",
@@ -113,10 +117,14 @@
 				};
 int facility_constants[] =	{
 					LOG_AUTH,
+#if !(defined(sun) || defined(__sun))
 					LOG_AUTHPRIV,
+#endif
 					LOG_CRON,
 					LOG_DAEMON,
+#if !(defined(sun) || defined(__sun))
 					LOG_FTP,
+#endif
 					LOG_KERN,
 					LOG_LOCAL0,
 					LOG_LOCAL1,
@@ -365,11 +373,13 @@
 	if (!strcmp(s, "CPU")) return RLIMIT_CPU;
 	if (!strcmp(s, "DATA")) return RLIMIT_DATA;
 	if (!strcmp(s, "FSIZE")) return RLIMIT_FSIZE;
+	if (!strcmp(s, "NOFILE")) return RLIMIT_NOFILE;
+	if (!strcmp(s, "STACK")) return RLIMIT_STACK;
+#if !(defined(sun) || defined(__sun))
 	if (!strcmp(s, "MEMLOCK")) return RLIMIT_MEMLOCK;
-	if (!strcmp(s, "NOFILE")) return RLIMIT_NOFILE;
 	if (!strcmp(s, "NPROC")) return RLIMIT_NPROC;
 	if (!strcmp(s, "RSS")) return RLIMIT_RSS;
-	if (!strcmp(s, "STACK")) return RLIMIT_STACK;
+#endif
 	return -1;
 }
 
@@ -453,12 +463,20 @@
 	return 3;
 }
 
+void lc_abort(lua_State* L)
+{
+	abort();
+}
+
 /* Register functions */
 
 int luaopen_util_pposix(lua_State *L)
 {
 	lua_newtable(L);
 
+	lua_pushcfunction(L, lc_abort);
+	lua_setfield(L, -2, "abort");
+
 	lua_pushcfunction(L, lc_daemonize);
 	lua_setfield(L, -2, "daemonize");
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util-src/windows.c	Mon Nov 16 21:43:57 2009 +0100
@@ -0,0 +1,45 @@
+
+#include <stdio.h>
+#include <windows.h>
+#include <windns.h>
+
+#include "lua.h"
+#include "lauxlib.h"
+
+static int Lget_nameservers(lua_State *L) {
+	char stack_buffer[1024]; // stack allocated buffer
+	IP4_ARRAY* ips = (IP4_ARRAY*) stack_buffer;
+	DWORD len = sizeof(stack_buffer);
+	DNS_STATUS status;
+
+	status = DnsQueryConfig(DnsConfigDnsServerList, FALSE, NULL, NULL, ips, &len);
+	if (status == 0) {
+		DWORD i;
+		lua_createtable(L, ips->AddrCount, 0);
+		for (i = 0; i < ips->AddrCount; i++) {
+			DWORD ip = ips->AddrArray[i];
+			char ip_str[16] = "";
+			sprintf_s(ip_str, sizeof(ip_str), "%d.%d.%d.%d", (ip >> 0) & 255, (ip >> 8) & 255, (ip >> 16) & 255, (ip >> 24) & 255);
+			lua_pushstring(L, ip_str);
+			lua_rawseti(L, -2, i+1);
+		}
+		return 1;
+	} else {
+		luaL_error(L, "DnsQueryConfig returned %d", status);
+		return 0; // unreachable, but prevents a compiler warning
+	}
+}
+
+static const luaL_Reg Reg[] =
+{
+	{ "get_nameservers",	Lget_nameservers	},
+	{ NULL,		NULL	}
+};
+
+LUALIB_API int luaopen_util_windows(lua_State *L) {
+	luaL_register(L, "windows", Reg);
+	lua_pushliteral(L, "version");			/** version */
+	lua_pushliteral(L, "-3.14");
+	lua_settable(L,-3);
+	return 1;
+}
--- a/util/array.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/util/array.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -6,9 +6,14 @@
 -- COPYING file in the source package for more information.
 --
 
-local array = {};
+local t_insert, t_sort, t_remove, t_concat 
+	= table.insert, table.sort, table.remove, table.concat;
 
-local array_mt = { __index = array, __tostring = function (array) return array:concat(", "); end };
+local array = {};
+local array_base = {};
+local array_methods = {};
+local array_mt = { __index = array_methods, __tostring = function (array) return array:concat(", "); end };
+
 local function new_array(_, t)
 	return setmetatable(t or {}, array_mt);
 end
@@ -20,36 +25,47 @@
 
 setmetatable(array, { __call = new_array });
 
-function array:map(func, t2)
-	local t2 = t2 or array{};
-	for k,v in ipairs(self) do
-		t2[k] = func(v);
+function array_base.map(outa, ina, func)
+	for k,v in ipairs(ina) do
+		outa[k] = func(v);
 	end
-	return t2;
+	return outa;
 end
 
-function array:filter(func, t2)
-	local t2 = t2 or array{};
-	for k,v in ipairs(self) do
+function array_base.filter(outa, ina, func)
+	local inplace, start_length = ina == outa, #ina;
+	local write = 1;
+	for read=1,start_length do
+		local v = ina[read];
 		if func(v) then
-			t2:push(v);
+			outa[write] = v;
+			write = write + 1;
 		end
 	end
-	return t2;
+	
+	if inplace and write <= start_length then
+		for i=write,start_length do
+			outa[i] = nil;
+		end
+	end
+	
+	return outa;
 end
 
+function array_base.sort(outa, ina, ...)
+	if ina ~= outa then
+		outa:append(ina);
+	end
+	t_sort(outa, ...);
+	return outa;
+end
 
-array.push = table.insert;
-array.pop = table.remove;
-array.sort = table.sort;
-array.concat = table.concat;
-array.length = function (t) return #t; end
-
-function array:random()
+--- These methods only mutate
+function array_methods:random()
 	return self[math.random(1,#self)];
 end
 
-function array:shuffle()
+function array_methods:shuffle(outa, ina)
 	local len = #self;
 	for i=1,#self do
 		local r = math.random(i,len);
@@ -58,7 +74,7 @@
 	return self;
 end
 
-function array:reverse()
+function array_methods:reverse()
 	local len = #self-1;
 	for i=len,1,-1 do
 		self:push(self[i]);
@@ -67,7 +83,7 @@
 	return self;
 end
 
-function array:append(array)
+function array_methods:append(array)
 	local len,len2  = #self, #array;
 	for i=1,len2 do
 		self[len+i] = array[i];
@@ -75,6 +91,12 @@
 	return self;
 end
 
+array_methods.push = table.insert;
+array_methods.pop = table.remove;
+array_methods.concat = table.concat;
+array_methods.length = function (t) return #t; end
+
+--- These methods always create a new array
 function array.collect(f, s, var)
 	local t, var = {};
 	while true do
@@ -85,6 +107,22 @@
 	return setmetatable(t, array_mt);
 end
 
+---
+
+-- Setup methods from array_base
+for method, f in pairs(array_base) do
+	local base_method = f;
+	-- Setup global array method which makes new array
+	array[method] = function (old_a, ...)
+		local a = new_array();
+		return base_method(a, old_a, ...);
+	end
+	-- Setup per-array (mutating) method
+	array_methods[method] = function (self, ...)
+		return base_method(self, self, ...);
+	end
+end
+
 _G.array = array;
 module("array");
 
--- a/util/dataforms.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/util/dataforms.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -10,7 +10,6 @@
 local pairs, ipairs = pairs, ipairs;
 local tostring, type = tostring, type;
 local t_concat = table.concat;
-
 local st = require "util.stanza";
 
 module "dataforms"
@@ -37,34 +36,44 @@
 		-- Add field tag
 		form:tag("field", { type = field_type, var = field.name, label = field.label });
 
-		local value = data[field.name] or field.value;
+		local value = (data and data[field.name]) or field.value;
 		
-		-- Add value, depending on type
-		if field_type == "hidden" then
-			if type(value) == "table" then
-				-- Assume an XML snippet
-				form:tag("value")
-					:add_child(value)
-					:up();
-			elseif value then
-				form:tag("value"):text(tostring(value)):up();
-			end
-		elseif field_type == "boolean" then
-			form:tag("value"):text((value and "1") or "0"):up();
-		elseif field_type == "fixed" then
-			
-		elseif field_type == "jid-multi" then
-			for _, jid in ipairs(value) do
-				form:tag("value"):text(jid):up();
-			end
-		elseif field_type == "jid-single" then
-			form:tag("value"):text(value):up();
-		elseif field_type == "text-single" or field_type == "text-private" then
-			form:tag("value"):text(value):up();
-		elseif field_type == "text-multi" then
-			-- Split into multiple <value> tags, one for each line
-			for line in value:gmatch("([^\r\n]+)\r?\n*") do
-				form:tag("value"):text(line):up();
+		if value then
+			-- Add value, depending on type
+			if field_type == "hidden" then
+				if type(value) == "table" then
+					-- Assume an XML snippet
+					form:tag("value")
+						:add_child(value)
+						:up();
+				else
+					form:tag("value"):text(tostring(value)):up();
+				end
+			elseif field_type == "boolean" then
+				form:tag("value"):text((value and "1") or "0"):up();
+			elseif field_type == "fixed" then
+				
+			elseif field_type == "jid-multi" then
+				for _, jid in ipairs(value) do
+					form:tag("value"):text(jid):up();
+				end
+			elseif field_type == "jid-single" then
+				form:tag("value"):text(value):up();
+			elseif field_type == "text-single" or field_type == "text-private" then
+				form:tag("value"):text(value):up();
+			elseif field_type == "text-multi" then
+				-- Split into multiple <value> tags, one for each line
+				for line in value:gmatch("([^\r\n]+)\r?\n*") do
+					form:tag("value"):text(line):up();
+				end
+			elseif field_type == "list-single" then
+				for _, val in ipairs(value) do
+					if type(val) == "table" then
+						form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
+					else
+						form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
+					end
+				end
 			end
 		end
 		
@@ -106,6 +115,20 @@
 field_readers["text-private"] = 
 	field_readers["text-single"];
 
+field_readers["jid-single"] =
+	field_readers["text-single"];
+
+field_readers["jid-multi"] = 
+	function (field_tag)
+		local result = {};
+		for value_tag in field_tag:childtags() do
+			if value_tag.name == "value" then
+				result[#result+1] = value_tag[1];
+			end
+		end
+		return result;
+	end
+
 field_readers["text-multi"] = 
 	function (field_tag)
 		local result = {};
@@ -117,6 +140,9 @@
 		return t_concat(result, "\n");
 	end
 
+field_readers["list-single"] =
+	field_readers["text-single"];
+
 field_readers["boolean"] = 
 	function (field_tag)
 		local value = field_tag:child_with_name("value");
--- a/util/datamanager.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/util/datamanager.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -137,7 +137,7 @@
 	append(f, data);
 	f:close();
 	if next(data) == nil then -- try to delete empty datastore
-		log("debug", "Removing empty %s datastore for user %s@%s", datastore, username, host);
+		log("debug", "Removing empty %s datastore for user %s@%s", datastore, username or "nil", host or "nil");
 		os_remove(getpath(username, host, datastore));
 	end
 	-- we write data even when we are deleting because lua doesn't have a
@@ -179,7 +179,7 @@
 	end
 	f:close();
 	if next(data) == nil then -- try to delete empty datastore
-		log("debug", "Removing empty %s datastore for user %s@%s", datastore, username, host);
+		log("debug", "Removing empty %s datastore for user %s@%s", datastore, username or "nil", host or "nil");
 		os_remove(getpath(username, host, datastore, "list"));
 	end
 	-- we write data even when we are deleting because lua doesn't have a
--- a/util/discohelper.lua	Fri Nov 13 14:31:03 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-
-local t_insert = table.insert;
-local jid_split = require "util.jid".split;
-local ipairs = ipairs;
-local st = require "util.stanza";
-
-module "discohelper";
-
-local function addDiscoItemsHandler(self, jid, func)
-	if self.item_handlers[jid] then
-		t_insert(self.item_handlers[jid], func);
-	else
-		self.item_handlers[jid] = {func};
-	end
-end
-
-local function addDiscoInfoHandler(self, jid, func)
-	if self.info_handlers[jid] then
-		t_insert(self.info_handlers[jid], func);
-	else
-		self.info_handlers[jid] = {func};
-	end
-end
-
-local function handle(self, stanza)
-	if stanza.name == "iq" and stanza.tags[1].name == "query" then
-		local query = stanza.tags[1];
-		local to = stanza.attr.to;
-		local from = stanza.attr.from
-		local node = query.attr.node or "";
-		local to_node, to_host = jid_split(to);
-
-		local reply = st.reply(stanza):query(query.attr.xmlns);
-		local handlers;
-		if query.attr.xmlns == "http://jabber.org/protocol/disco#info" then -- select handler set
-			handlers = self.info_handlers;
-		elseif query.attr.xmlns == "http://jabber.org/protocol/disco#items" then
-			handlers = self.item_handlers;
-		end
-		local handler;
-		local found; -- to keep track of any handlers found
-		if to_node then -- handlers which get called always
-			handler = handlers["*node"];
-		else
-			handler = handlers["*host"];
-		end
-		if handler then -- call always called handler
-			for _, h in ipairs(handler) do
-				if h(reply, to, from, node) then found = true; end
-			end
-		end
-		handler = handlers[to]; -- get the handler
-		if not handler then -- if not found then use default handler
-			if to_node then
-				handler = handlers["*defaultnode"];
-			else
-				handler = handlers["*defaulthost"];
-			end
-		end
-		if handler then
-			for _, h in ipairs(handler) do
-				if h(reply, to, from, node) then found = true; end
-			end
-		end
-		if found then return reply; end -- return the reply if there was one
-		return st.error_reply(stanza, "cancel", "service-unavailable");
-	end
-end
-
-function new()
-	return {
-		item_handlers = {};
-		info_handlers = {};
-		addDiscoItemsHandler = addDiscoItemsHandler;
-		addDiscoInfoHandler = addDiscoInfoHandler;
-		handle = handle;
-	};
-end
-
-return _M;
--- a/util/helpers.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/util/helpers.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -1,3 +1,10 @@
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
 
 module("helpers", package.seeall);
 
@@ -14,6 +21,7 @@
 	name = name or tostring(events);
 	function events.fire_event(event, ...)
 		logger("debug", "%s firing event: %s", name, event);
+		return f(event, ...);
 	end
 	events[events.fire_event] = f;
 	return events;
@@ -23,4 +31,13 @@
 	events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :)
 end
 
+function get_upvalue(f, get_name)
+	local i, name, value = 0;
+	repeat
+		i = i + 1;
+		name, value = debug.getupvalue(f, i);
+	until name == get_name or name == nil;
+	return value;
+end
+
 return _M;
--- a/util/iterators.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/util/iterators.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -78,6 +78,39 @@
 	return x;
 end
 
+-- Return the first n items an iterator returns
+function head(n, f, s, var)
+	local c = 0;
+	return function (s, var)
+		if c >= n then
+			return nil;
+		end
+		c = c + 1;
+		return f(s, var);
+	end, s;
+end
+
+function tail(n, f, s, var)
+	local results, count = {}, 0;
+	while true do
+		local ret = { f(s, var) };
+		var = ret[1];
+	        if var == nil then break; end
+		results[(count%n)+1] = ret;
+		count = count + 1;
+	end
+
+	if n > count then n = count; end
+
+	local pos = 0;
+	return function ()
+		pos = pos + 1;
+		if pos > n then return nil; end
+		return unpack(results[((count-1+pos)%n)+1]);
+	end
+	--return reverse(head(n, reverse(f, s, var)));
+end
+
 -- Convert the values returned by an iterator to an array
 function it2array(f, s, var)
 	local t, var = {};
--- a/util/muc.lua	Fri Nov 13 14:31:03 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,418 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-local datamanager = require "util.datamanager";
-local datetime = require "util.datetime";
-
-local jid_split = require "util.jid".split;
-local jid_bare = require "util.jid".bare;
-local st = require "util.stanza";
-local log = require "util.logger".init("mod_muc");
-local multitable_new = require "util.multitable".new;
-local t_insert, t_remove = table.insert, table.remove;
-
-local muc_domain = nil; --module:get_host();
-local history_length = 20;
-
-------------
-local function filter_xmlns_from_array(array, filters)
-	local count = 0;
-	for i=#array,1,-1 do
-		local attr = array[i].attr;
-		if filters[attr and attr.xmlns] then
-			t_remove(array, i);
-			count = count + 1;
-		end
-	end
-	return count;
-end
-local function filter_xmlns_from_stanza(stanza, filters)
-	if filters then
-		if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then
-			return stanza, filter_xmlns_from_array(stanza, filters);
-		end
-	end
-	return stanza, 0;
-end
-local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
-local function get_filtered_presence(stanza)
-	return filter_xmlns_from_stanza(st.clone(stanza), presence_filters);
-end
-local kickable_error_conditions = {
-	["gone"] = true;
-	["internal-server-error"] = true;
-	["item-not-found"] = true;
-	["jid-malformed"] = true;
-	["recipient-unavailable"] = true;
-	["redirect"] = true;
-	["remote-server-not-found"] = true;
-	["remote-server-timeout"] = true;
-	["service-unavailable"] = true;
-};
-local function get_kickable_error(stanza)
-	for _, tag in ipairs(stanza.tags) do
-		if tag.name == "error" and tag.attr.xmlns == "jabber:client" then
-			for _, cond in ipairs(tag.tags) do
-				if cond.attr.xmlns == "urn:ietf:params:xml:ns:xmpp-stanzas" then
-					return kickable_error_conditions[cond.name] and cond.name;
-				end
-			end
-			return true; -- malformed error message
-		end
-	end
-	return true; -- malformed error message
-end
-local function getUsingPath(stanza, path, getText)
-	local tag = stanza;
-	for _, name in ipairs(path) do
-		if type(tag) ~= 'table' then return; end
-		tag = tag:child_with_name(name);
-	end
-	if tag and getText then tag = table.concat(tag); end
-	return tag;
-end
-local function getTag(stanza, path) return getUsingPath(stanza, path); end
-local function getText(stanza, path) return getUsingPath(stanza, path, true); end
------------
-
---[[function get_room_disco_info(room, stanza)
-	return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
-		:tag("identity", {category='conference', type='text', name=room._data["name"]):up()
-		:tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
-end
-function get_room_disco_items(room, stanza)
-	return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
-end -- TODO allow non-private rooms]]
-
---
-
-local function room_broadcast_presence(room, stanza, code, nick)
-	stanza = get_filtered_presence(stanza);
-	local data = room._participants[stanza.attr.from];
-	stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
-		:tag("item", {affiliation=data.affiliation, role=data.role, nick=nick}):up();
-	if code then
-		stanza:tag("status", {code=code}):up();
-	end
-	local me;
-	for occupant, o_data in pairs(room._participants) do
-		if occupant ~= stanza.attr.from then
-			for jid in pairs(o_data.sessions) do
-				stanza.attr.to = jid;
-				room:route_stanza(stanza);
-			end
-		else
-			me = o_data;
-		end
-	end
-	if me then
-		stanza:tag("status", {code='110'});
-		for jid in pairs(me.sessions) do
-			stanza.attr.to = jid;
-			room:route_stanza(stanza);
-		end
-	end
-end
-local function room_broadcast_message(room, stanza, historic)
-	for occupant, o_data in pairs(room._participants) do
-		for jid in pairs(o_data.sessions) do
-			stanza.attr.to = jid;
-			room:route_stanza(stanza);
-		end
-	end
-	if historic then -- add to history
-		local history = room._data['history'];
-		if not history then history = {}; room._data['history'] = history; end
-		-- stanza = st.clone(stanza);
-		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
-		stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
-		t_insert(history, st.clone(st.preserialize(stanza)));
-		while #history > history_length do t_remove(history, 1) end
-	end
-end
-
-
-local function room_send_occupant_list(room, to)
-	local current_nick = room._jid_nick[to];
-	for occupant, o_data in pairs(room._participants) do
-		if occupant ~= current_nick then
-			local pres = get_filtered_presence(o_data.sessions[o_data.jid]);
-			pres.attr.to, pres.attr.from = to, occupant;
-			pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
-				:tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up();
-			room:route_stanza(pres);
-		end
-	end
-end
-local function room_send_history(room, to)
-	local history = room._data['history']; -- send discussion history
-	if history then
-		for _, msg in ipairs(history) do
-			msg = st.deserialize(msg);
-			msg.attr.to=to;
-			room:route_stanza(msg);
-		end
-	end
-	if room._data['subject'] then
-		room:route_stanza(st.message({type='groupchat', from=room.jid, to=to}):tag("subject"):text(room._data['subject']));
-	end
-end
-
-local function room_get_disco_info(self, stanza) end
-local function room_get_disco_items(self, stanza) end
-local function room_set_subject(room, current_nick, subject)
-	-- TODO check nick's authority
-	if subject == "" then subject = nil; end
-	room._data['subject'] = subject;
-	local msg = st.message({type='groupchat', from=current_nick})
-		:tag('subject'):text(subject):up();
-	room_broadcast_message(room, msg, false);
-	return true;
-end
-
-local function room_handle_to_occupant(self, origin, stanza) -- PM, vCards, etc
-	local from, to = stanza.attr.from, stanza.attr.to;
-	local room = jid_bare(to);
-	local current_nick = self._jid_nick[from];
-	local type = stanza.attr.type;
-	log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag());
-	if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end
-	if stanza.name == "presence" then
-		local pr = get_filtered_presence(stanza);
-		pr.attr.from = current_nick;
-		if type == "error" then -- error, kick em out!
-			if current_nick then
-				log("debug", "kicking %s from %s", current_nick, room);
-				room_handle_to_occupant(self, origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error presence')); -- send unavailable
-			end
-		elseif type == "unavailable" then -- unavailable
-			if current_nick then
-				log("debug", "%s leaving %s", current_nick, room);
-				local data = self._participants[current_nick];
-				data.role = 'none';
-				room_broadcast_presence(self, pr);
-				self._participants[current_nick] = nil;
-				self._jid_nick[from] = nil;
-			end
-		elseif not type then -- available
-			if current_nick then
-				--if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence
-					if current_nick == to then -- simple presence
-						log("debug", "%s broadcasted presence", current_nick);
-						self._participants[current_nick].sessions[from] = pr;
-						room_broadcast_presence(self, pr);
-					else -- change nick
-						if self._participants[to] then
-							log("debug", "%s couldn't change nick", current_nick);
-							origin.send(st.error_reply(stanza, "cancel", "conflict"):tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
-						else
-							local data = self._participants[current_nick];
-							local to_nick = select(3, jid_split(to));
-							if to_nick then
-								log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to);
-								local p = st.presence({type='unavailable', from=current_nick});
-								room_broadcast_presence(self, p, '303', to_nick);
-								self._participants[current_nick] = nil;
-								self._participants[to] = data;
-								self._jid_nick[from] = to;
-								pr.attr.from = to;
-								self._participants[to].sessions[from] = pr;
-								room_broadcast_presence(self, pr);
-							else
-								--TODO malformed-jid
-							end
-						end
-					end
-				--else -- possible rejoin
-				--	log("debug", "%s had connection replaced", current_nick);
-				--	handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('Replaced by new connection'):up()); -- send unavailable
-				--	handle_to_occupant(origin, stanza); -- resend available
-				--end
-			else -- enter room
-				local new_nick = to;
-				if self._participants[to] then
-					new_nick = nil;
-				end
-				if not new_nick then
-					log("debug", "%s couldn't join due to nick conflict: %s", from, to);
-					origin.send(st.error_reply(stanza, "cancel", "conflict"):tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
-				else
-					log("debug", "%s joining as %s", from, to);
-					local data;
---					if not rooms:get(room) and not rooms_info:get(room) then -- new room
---						rooms_info:set(room, 'name', (jid_split(room)));
---						data = {affiliation='owner', role='moderator', jid=from, sessions={[from]=get_filtered_presence(stanza)}};
---					end
-					if not data then -- new occupant
-						data = {affiliation='none', role='participant', jid=from, sessions={[from]=get_filtered_presence(stanza)}};
-					end
-					self._participants[to] = data;
-					self._jid_nick[from] = to;
-					room_send_occupant_list(self, from);
-					pr.attr.from = to;
-					room_broadcast_presence(self, pr);
-					room_send_history(self, from);
-				end
-			end
-		elseif type ~= 'result' then -- bad type
-			origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error?
-		end
-	elseif not current_nick and type ~= "error" and type ~= "result" then -- not in room
-		origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
-	elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM
-		origin.send(st.error_reply(stanza, "modify", "bad-request"));
-	elseif stanza.name == "message" and type == "error" and get_kickable_error(stanza) then
-		log("debug", "%s kicked from %s for sending an error message", current_nick, room);
-		room_handle_to_occupant(self, origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error message to another occupant')); -- send unavailable
-	else -- private stanza
-		local o_data = self._participants[to];
-		if o_data then
-			log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid);
-			local jid = o_data.jid;
-			-- TODO if stanza.name=='iq' and type=='get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then jid = jid_bare(jid); end
-			stanza.attr.to, stanza.attr.from = jid, current_nick;
-			self:route_stanza(stanza);
-		elseif type ~= "error" and type ~= "result" then -- recipient not in room
-			origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room"));
-		end
-	end
-end
-
-local function room_handle_to_room(self, origin, stanza) -- presence changes and groupchat messages, along with disco/etc
-	local type = stanza.attr.type;
-	if stanza.name == "iq" and type == "get" then -- disco requests
-		local xmlns = stanza.tags[1].attr.xmlns;
-		if xmlns == "http://jabber.org/protocol/disco#info" then
-			origin.send(room_get_disco_info(self, stanza));
-		elseif xmlns == "http://jabber.org/protocol/disco#items" then
-			origin.send(room_get_disco_items(self, stanza));
-		else
-			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
-		end
-	elseif stanza.name == "message" and type == "groupchat" then
-		local from, to = stanza.attr.from, stanza.attr.to;
-		local room = jid_bare(to);
-		local current_nick = self._jid_nick[from];
-		if not current_nick then -- not in room
-			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
-		else
-			local from = stanza.attr.from;
-			stanza.attr.from = current_nick;
-			local subject = getText(stanza, {"subject"});
-			if subject then
-				self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
-			else
-				room_broadcast_message(self, stanza, true);
-			end
-		end
-	elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick
-		local to = stanza.attr.to;
-		local current_nick = self._jid_nick[stanza.attr.from];
-		if current_nick then
-			stanza.attr.to = current_nick;
-			room_handle_to_occupant(self, origin, stanza);
-			stanza.attr.to = to;
-		elseif type ~= "error" and type ~= "result" then
-			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
-		end
-	elseif stanza.name == "message" and not stanza.attr.type and #stanza.tags == 1 and self._jid_nick[stanza.attr.from]
-		and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" and #stanza.tags[1].tags == 1
-		and stanza.tags[1].tags[1].name == "invite" and stanza.tags[1].tags[1].attr.to then
-		local _from, _to = stanza.attr.from, stanza.attr.to;
-		local _invitee = stanza.tags[1].tags[1].attr.to;
-		stanza.attr.from, stanza.attr.to = _to, _invitee;
-		stanza.tags[1].tags[1].attr.from, stanza.tags[1].tags[1].attr.to = _from, nil;
-		self:route_stanza(stanza);
-		stanza.tags[1].tags[1].attr.from, stanza.tags[1].tags[1].attr.to = nil, _invitee;
-		stanza.attr.from, stanza.attr.to = _from, _to;
-	else
-		if type == "error" or type == "result" then return; end
-		origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
-	end
-end
-
-local function room_handle_stanza(self, origin, stanza)
-	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
-	if to_resource then
-		room_handle_to_occupant(self, origin, stanza);
-	else
-		room_handle_to_room(self, origin, stanza);
-	end
-end
-
-module "muc"
-
-function new_room(jid)
-	return {
-		jid = jid;
-		handle_stanza = room_handle_stanza;
-		set_subject = room_set_subject;
-		route_stanza = function(room, stanza) end; -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end
-		_jid_nick = {};
-		_participants = {};
-		_data = {};
-	}
-end
-
-return _M;
-
---[[function get_disco_info(stanza)
-	return st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
-		:tag("identity", {category='conference', type='text', name=muc_name}):up()
-		:tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
-end
-function get_disco_items(stanza)
-	local reply = st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
-	for room in pairs(rooms_info:get()) do
-		reply:tag("item", {jid=room, name=rooms_info:get(room, "name")}):up();
-	end
-	return reply; -- TODO cache disco reply
-end]]
-
---[[function handle_to_domain(origin, stanza)
-	local type = stanza.attr.type;
-	if type == "error" or type == "result" then return; end
-	if stanza.name == "iq" and type == "get" then
-		local xmlns = stanza.tags[1].attr.xmlns;
-		if xmlns == "http://jabber.org/protocol/disco#info" then
-			origin.send(get_disco_info(stanza));
-		elseif xmlns == "http://jabber.org/protocol/disco#items" then
-			origin.send(get_disco_items(stanza));
-		else
-			origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc
-		end
-	else
-		origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it"));
-	end
-end
-
-register_component(muc_domain, function(origin, stanza)
-	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
-	if to_resource and not to_node then
-		if type == "error" or type == "result" then return; end
-		origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- host/resource
-	elseif to_resource then
-		handle_to_occupant(origin, stanza);
-	elseif to_node then
-		handle_to_room(origin, stanza)
-	else -- to the main muc domain
-		if type == "error" or type == "result" then return; end
-		handle_to_domain(origin, stanza);
-	end
-end);]]
-
---[[module.unload = function()
-	deregister_component(muc_domain);
-end
-module.save = function()
-	return {rooms = rooms.data; jid_nick = jid_nick.data; rooms_info = rooms_info.data; persist_list = persist_list};
-end
-module.restore = function(data)
-	rooms.data, jid_nick.data, rooms_info.data, persist_list =
-	data.rooms or {}, data.jid_nick or {}, data.rooms_info or {}, data.persist_list or {};
-end]]
--- a/util/sasl.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/util/sasl.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -125,7 +125,7 @@
 end
 
 -- load the mechanisms
-load_mechs = {"plain", "digest-md5"}
+load_mechs = {"plain", "digest-md5", "anonymous"}
 for _, mech in ipairs(load_mechs) do
 	local name = "util.sasl."..mech;
 	local m = require(name);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/sasl/anonymous.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -0,0 +1,35 @@
+-- sasl.lua v0.4
+-- Copyright (C) 2008-2009 Tobias Markmann
+--
+--    All rights reserved.
+--
+--    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+--
+--        * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+--        * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+--        * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+--
+--    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+local s_match = string.match;
+
+local log = require "util.logger".init("sasl");
+
+module "anonymous"
+
+--=========================
+--SASL ANONYMOUS according to RFC 4505
+local function anonymous(self, message)
+	local username;
+	repeat
+		username = generate_uuid();
+	until self.profile.anonymous(username, self.realm);
+	self["username"] = username;
+	return "success"
+end
+
+function init(registerMechanism)
+	registerMechanism("ANONYMOUS", {"anonymous"}, anonymous);
+end
+
+return _M;
\ No newline at end of file
--- a/util/sasl/digest-md5.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/util/sasl/digest-md5.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -175,7 +175,7 @@
 			elseif state == false then return "failure", "account-disabled" end
 			Y = md5(response["username"]..":"..response["realm"]..":"..password);
 		elseif self.profile["digest-md5"] then
-			local Y, state = self.profile["digest-md5"](response["username"], self.realm, response["realm"] response["charset"])
+			local Y, state = self.profile["digest-md5"](response["username"], self.realm, response["realm"], response["charset"])
 			if state == nil then return "failure", "not-authorized"
 			elseif state == false then return "failure", "account-disabled" end
 		elseif self.profile["digest-md5-test"] then
@@ -186,12 +186,12 @@
 		--elseif Y == false then return "failure", "account-disabled" end
 		local A1 = "";
 		if response.authzid then
-			if response.authzid == self.username.."@"..self.realm then
+			if response.authzid == self.username or response.authzid == self.username.."@"..self.realm then
 				-- COMPAT
-				log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920.");
+				log("warn", "Client is violating RFC 3920 (section 6.1, point 7).");
 				A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
 			else
-				A1 = "?";
+				return "failure", "invalid-authzid";
 			end
 		else
 			A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
--- a/util/stanza.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/util/stanza.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -28,13 +28,19 @@
 local os            =            os;
 
 local do_pretty_printing = not os.getenv("WINDIR");
-local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
-
-local log = require "util.logger".init("stanza");
+local getstyle, getstring;
+if do_pretty_printing then
+	local ok, termcolours = pcall(require, "util.termcolours");
+	if ok then
+		getstyle, getstring = termcolours.getstyle, termcolours.getstring;
+	else
+		do_pretty_printing = nil;
+	end
+end
 
 module "stanza"
 
-stanza_mt = {};
+stanza_mt = { __type = "stanza" };
 stanza_mt.__index = stanza_mt;
 
 function stanza(name, attr)
@@ -118,20 +124,23 @@
 	                                    
 end
 
-local xml_escape = (function()
+local xml_escape
+do
 	local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
-	return function(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
-end)();
-local function _dostring(t, buf, self, xml_escape)
+	function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
+	_M.xml_escape = xml_escape;
+end
+
+local function _dostring(t, buf, self, xml_escape, parentns)
 	local nsid = 0;
 	local name = t.name
 	t_insert(buf, "<"..name);
 	for k, v in pairs(t.attr) do
-		if s_find(k, "|", 1, true) then
-			local ns, attrk = s_match(k, "^([^|]+)|(.+)$");
+		if s_find(k, "\1", 1, true) then
+			local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$");
 			nsid = nsid + 1;
 			t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'");
-		else
+		elseif not(k == "xmlns" and v == parentns) then
 			t_insert(buf, " "..k.."='"..xml_escape(v).."'");
 		end
 	end
@@ -143,7 +152,7 @@
 		for n=1,len do
 			local child = t[n];
 			if child.name then
-				self(child, buf, self, xml_escape);
+				self(child, buf, self, xml_escape, t.attr.xmlns);
 			else
 				t_insert(buf, xml_escape(child));
 			end
@@ -153,7 +162,7 @@
 end
 function stanza_mt.__tostring(t)
 	local buf = {};
-	_dostring(t, buf, _dostring, xml_escape);
+	_dostring(t, buf, _dostring, xml_escape, nil);
 	return t_concat(buf);
 end
 
@@ -201,6 +210,17 @@
 	if stanza then
 		local attr = stanza.attr;
 		for i=1,#attr do attr[i] = nil; end
+		local attrx = {};
+		for att in pairs(attr) do
+			if s_find(att, "|", 1, true) and not s_find(k, "\1", 1, true) then
+				local ns,na = s_match(k, "^([^|]+)|(.+)$");
+				attrx[ns.."\1"..na] = attr[att];
+				attr[att] = nil;
+			end
+		end
+		for a,v in pairs(attrx) do
+			attr[x] = v;
+		end
 		setmetatable(stanza, stanza_mt);
 		for _, child in ipairs(stanza) do
 			if type(child) == "table" then
--- a/util/timer.lua	Fri Nov 13 14:31:03 2009 +0100
+++ b/util/timer.lua	Mon Nov 16 21:43:57 2009 +0100
@@ -42,7 +42,7 @@
 		local t, func = d[1], d[2];
 		if t <= current_time then
 			data[i] = nil;
-			local r = func();
+			local r = func(current_time);
 			if type(r) == "number" then _add_task(r, func); end
 		end
 	end

mercurial