Merge with 0.5

Sun, 27 Sep 2009 12:26:51 +0100

author
Matthew Wild <mwild1@gmail.com>
date
Sun, 27 Sep 2009 12:26:51 +0100
changeset 1833
f4c88dd32724
parent 1823
7c3ec7ac6316 (diff)
parent 1832
5ae3209fefa2 (current diff)
child 1837
c07f8f3e93ea

Merge with 0.5

core/s2smanager.lua file | annotate | diff | comparison | revisions
net/dns.lua file | annotate | diff | comparison | revisions
plugins/mod_legacyauth.lua file | annotate | diff | comparison | revisions
--- a/Makefile	Sun Sep 27 12:10:50 2009 +0100
+++ b/Makefile	Sun Sep 27 12:26:51 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
--- a/core/componentmanager.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/core/componentmanager.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -6,18 +6,14 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
 local prosody = 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 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,27 +21,16 @@
 
 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"));
+	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();
 		
@@ -76,7 +61,7 @@
 		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));
 	end
 end
 
@@ -138,4 +123,8 @@
 	components[host] = handler;
 end
 
+function get_children(host)
+	return disco_items:get(host) or NULL;
+end
+
 return _M;
--- a/core/configmanager.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/core/configmanager.lua	Sun Sep 27 12:26:51 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	Sun Sep 27 12:10:50 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/modulemanager.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/core/modulemanager.lua	Sun Sep 27 12:26:51 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();
@@ -171,8 +166,6 @@
 		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]);
@@ -328,50 +321,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, ...)
@@ -418,6 +372,42 @@
 	return f();
 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;
+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
+	return result;
+end
+
 --------------------------------------------------------------------
 
 local actions = {};
--- a/core/s2smanager.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/core/s2smanager.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -128,6 +128,7 @@
 	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 ()
--- a/core/sessionmanager.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/core/sessionmanager.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -19,7 +19,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;
@@ -50,6 +51,9 @@
 	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);
+		
 	return session;
 end
 
--- a/core/usermanager.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/core/usermanager.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -1,7 +1,7 @@
 -- 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.
 --
@@ -23,6 +23,7 @@
 function validate_credentials(host, username, password, method)
 	log("debug", "User '%s' is being validated", username);
 	local credentials = datamanager.load(username, host, "accounts") or {};
+
 	if method == nil then method = "PLAIN"; end
 	if method == "PLAIN" and credentials.password then -- PLAIN, do directly
 		if password == credentials.password then
@@ -30,7 +31,7 @@
 		else
 			return nil, "Auth failed. Invalid username or password.";
 		end
-	end
+  end
 	-- must do md5
 	-- make credentials md5
 	local pwd = credentials.password;
@@ -49,6 +50,10 @@
 	end
 end
 
+function get_password(username, host)
+  return (datamanager.load(username, host, "accounts") or {}).password
+end
+
 function user_exists(username, host)
 	return datamanager.load(username, host, "accounts") ~= nil; -- FIXME also check for empty credentials
 end
@@ -58,13 +63,11 @@
 end
 
 function get_supported_methods(host)
-	local methods = {["PLAIN"] = true}; -- TODO this should be taken from the config
-	methods["DIGEST-MD5"] = true;
-	return methods;
+	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") or {};
+	local admins = config.get("*", "core", "admins");
 	if type(admins) == "table" then
 		jid = jid_bare(jid);
 		for _,admin in ipairs(admins) do
--- a/net/httpserver.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/net/httpserver.lua	Sun Sep 27 12:26:51 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
@@ -251,6 +248,10 @@
 	end
 end
 
+function set_default_handler(handler)
+	default_handler = handler;
+end
+
 function new_from_config(ports, default_base, handle_request)
 	for _, options in ipairs(ports) do
 		local port, base, ssl, interface = 5280, default_base, false, nil;
--- a/net/server.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/net/server.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -246,7 +246,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( )
@@ -374,7 +374,7 @@
             handler = nil
         end
         socket = nil
-        mem_free( )
+        --mem_free( )
 	if server then
 		server.remove( )
 	end
@@ -566,7 +566,7 @@
             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 )
@@ -671,7 +671,7 @@
     _readlistlen = removesocket( _readlist, socket, _readlistlen )
     _socketlist[ socket ] = nil
     socket:close( )
-    mem_free( )
+    --mem_free( )
 end
 
 ----------------------------------// PUBLIC //--
@@ -740,7 +740,7 @@
     _sendlist = { }
     _timerlist = { }
     _socketlist = { }
-    mem_free( )
+    --mem_free( )
 end
 
 getsettings = function( )
--- a/net/xmppclient_listener.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/net/xmppclient_listener.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -61,6 +61,7 @@
 		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:close("xml-not-well-formed");
 		end
 		
@@ -100,7 +101,7 @@
 		end
 		session.send("</stream:stream>");
 		session.conn.close();
-		xmppclient.disconnect(session.conn, (reason and reason.condition) or reason or "session closed");
+		xmppclient.disconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
 	end
 end
 
@@ -113,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/xmppserver_listener.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/net/xmppserver_listener.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -61,6 +61,7 @@
 		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:close("xml-not-well-formed");
 		end
 		
@@ -113,12 +114,6 @@
 		session = s2s_new_incoming(conn);
 		sessions[conn] = session;
 
-		-- Logging functions --
-
-		
-		local conn_name = "s2sin"..tostring(conn):match("[a-f0-9]+$");
-		session.log = logger.init(conn_name);
-		
 		session.log("info", "Incoming s2s connection");
 		
 		session.reset_stream = session_reset_stream;
--- a/plugins/mod_bosh.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_bosh.lua	Sun Sep 27 12:26:51 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|body", 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;
@@ -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();
@@ -275,7 +288,7 @@
 	end
 end
 
-local ports = config.get(module.host, "core", "bosh_ports") or { 5280 };
+local ports = module:get_option("bosh_ports") or { 5280 };
 httpserver.new_from_config(ports, "http-bind", handle_request);
 
 server.addtimer(on_timer);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_compression.lua	Sun Sep 27 12:26:51 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	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_console.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -70,6 +70,9 @@
 			if data:match("^>") then
 				data = data:gsub("^>", "");
 				useglobalenv = true;
+			elseif data == "\004" then
+				commands["bye"](session, data);
+				return;
 			else
 				local command = data:lower();
 				command = data:match("^%w+") or data:match("%p");
@@ -205,7 +208,8 @@
 -- Anything in def_env will be accessible within the session as a global variable
 
 def_env.server = {};
-function def_env.server:reload()
+
+function def_env.server:insane_reload()
 	prosody.unlock_globals();
 	dofile "prosody"
 	prosody = _G.prosody;
@@ -230,6 +234,11 @@
 		minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time));
 end
 
+function def_env.server:shutdown(reason)
+	prosody.shutdown(reason);
+	return true, "Shutdown initiated";
+end
+
 def_env.module = {};
 
 local function get_hosts_set(hosts, module)
@@ -333,6 +342,11 @@
 	return true, tostring(config_get(host, section, key));
 end
 
+function def_env.config:reload()
+	local ok, err = prosody.reload_config();
+	return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err);
+end
+
 def_env.hosts = {};
 function def_env.hosts:list()
 	for host, host_session in pairs(hosts) do
@@ -359,7 +373,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 "-");
@@ -371,7 +390,7 @@
 					status = "available";
 				end
 			end
-			print(jid.." - "..status.."("..priority..")");
+			print("   "..jid.." - "..status.."("..priority..")");
 		end		
 	end);
 	return true, "Total: "..count.." clients";
--- a/plugins/mod_disco.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_disco.lua	Sun Sep 27 12:26:51 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	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_httpserver.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -11,45 +11,51 @@
 
 local open = io.open;
 local t_concat = table.concat;
-local check_http_path;
 
 local http_base = config.get("*", "core", "http_path") or "www_files";
 
-local response_403 = { status = "403 Forbidden", body = "<h1>Invalid URL</h1>Sorry, we couldn't find what you were looking for :(" };
+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 = check_http_path(request.url.path:gsub("^/[^/]+%.*", ""));
-	if not path then
-		return response_403;
+local function preprocess_path(path)
+	if path:sub(1,1) ~= "/" then
+		path = "/"..path;
 	end
-	http_path[2] = path;
-	local f, err = open(t_concat(http_path), "r");
+	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 ports = config.get(module.host, "core", "http_ports") or { 5280 };
-httpserver.new_from_config(ports, "files", handle_request);
+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
 
-function check_http_path(url)
-	if url:sub(1,1) ~= "/" then
-		url = "/"..url;
-	end
-	
-	local level = 0;
-	for part in url:gmatch("%/([^/]+)") do
-		if part == ".." then
-			level = level - 1;
-		elseif part ~= "." then
-			level = level + 1;
-		end
-		if level < 0 then
-			return nil;
-		end
-	end
-	return url;
+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.set_default_handler(handle_default_request);
+httpserver.new_from_config(ports, "files", handle_file_request);
--- a/plugins/mod_legacyauth.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_legacyauth.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -11,8 +11,7 @@
 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("require_encryption");
 
 local sessionmanager = require "core.sessionmanager";
 local usermanager = require "core.usermanager";
@@ -45,14 +44,12 @@
 					: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 username and resource and usermanager.validate_credentials(session.host, username, password) then
+				if usermanager.validate_credentials(session.host, username, password) then
 					-- Authentication successful!
 					local success, err = sessionmanager.make_authenticated(session, username);
 					if success then
@@ -69,15 +66,9 @@
 						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	Sun Sep 27 12:10:50 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +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);
-
-module.unload = function()
-	deregister_component(muc_host);
-end
-module.save = function()
-	return {rooms = rooms};
-end
-module.restore = function(data)
-	rooms = data.rooms or {};
-end
--- a/plugins/mod_offline.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_offline.lua	Sun Sep 27 12:26:51 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	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_pep.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -25,7 +25,7 @@
 local recipients = {};
 local hash_map = {};
 
-module:add_identity("pubsub", "pep");
+module:add_identity("pubsub", "pep", "Prosody");
 module:add_feature("http://jabber.org/protocol/pubsub#publish");
 
 local function publish(session, node, item)
--- a/plugins/mod_posix.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_posix.lua	Sun Sep 27 12:26:51 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,7 @@
 end
 require "core.loggingmanager".register_sink_type("syslog", syslog_sink_maker);
 
-if not config_get("*", "core", "no_daemonize") then
+if not module:get_option("no_daemonize") then
 	local function daemonize_server()
 		local ok, ret = pposix.daemonize();
 		if not ok then
--- a/plugins/mod_register.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_register.lua	Sun Sep 27 12:26:51 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];
--- a/plugins/mod_roster.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_roster.lua	Sun Sep 27 12:26:51 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	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_saslauth.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -12,9 +12,13 @@
 local sm_bind_resource = require "core.sessionmanager".bind_resource;
 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;
+local usermanager_user_exists = require "core.usermanager".user_exists;
+local usermanager_get_password = require "core.usermanager".get_password;
 local t_concat, t_insert = table.concat, table.insert;
 local tostring = tostring;
 local jid_split = require "util.jid".split
@@ -57,29 +61,43 @@
 			session.sasl_handler = nil;
 			session:reset_stream();
 			return;
-		end 
+		end
 		sm_make_authenticated(session, session.sasl_handler.username);
 		session.sasl_handler = nil;
 		session:reset_stream();
 	end
 end
 
-local function password_callback(node, hostname, realm, mechanism, decoder)
-	local func = function(x) return x; end;
-	local node = nodeprep(node);
-	if not node then
-		return func, nil;
-	end
-	local password = (datamanager_load(node, hostname, "accounts") or {}).password; -- FIXME handle hashed passwords
-	if password then
-		if mechanism == "PLAIN" then
-			return func, password;
-		elseif mechanism == "DIGEST-MD5" then
-			if decoder then node, realm, password = decoder(node), decoder(realm), decoder(password); end
+local function credentials_callback(mechanism, ...)
+	if mechanism == "PLAIN" then
+		local username, hostname, password = ...;
+		username = nodeprep(username);
+		if not username then
+			return false;
+		end
+		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 prepped_node = nodeprep(node);
+		if not prepped_node then
+			return func, nil;
+		end
+		local password = usermanager_get_password(prepped_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
-	return func, nil;
 end
 
 local function sasl_handler(session, stanza)
@@ -92,7 +110,7 @@
 		elseif stanza.attr.mechanism == "ANONYMOUS" then
 			return session.send(build_reply("failure", "mechanism-too-weak"));
 		end
-		session.sasl_handler = new_sasl(stanza.attr.mechanism, session.host, password_callback);
+		session.sasl_handler = new_sasl(stanza.attr.mechanism, session.host, credentials_callback);
 		if not session.sasl_handler then
 			return session.send(build_reply("failure", "invalid-mechanism"));
 		end
@@ -111,7 +129,7 @@
 	end
 	local status, ret, err_msg = session.sasl_handler:feed(text);
 	handle_status(session, status);
-	local s = build_reply(status, ret, err_msg); 
+	local s = build_reply(status, ret, err_msg);
 	log("debug", "sasl reply: %s", tostring(s));
 	session.send(s);
 end
@@ -123,8 +141,8 @@
 local mechanisms_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' };
 local bind_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-bind' };
 local xmpp_session_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-session' };
-module:add_event_hook("stream-features", 
-		function (session, features)												
+module:add_event_hook("stream-features",
+		function (session, features)
 			if not session.username then
 				if secure_auth_only and not session.secure then
 					return;
@@ -134,8 +152,10 @@
 					if config.get(session.host or "*", "core", "anonymous_login") then
 						features:tag("mechanism"):text("ANONYMOUS"):up();
 					else
-						features:tag("mechanism"):text("DIGEST-MD5"):up();
-						features:tag("mechanism"):text("PLAIN"):up();
+						mechanisms = usermanager_get_supported_methods(session.host or "*");
+						for k, v in pairs(mechanisms) do
+							features:tag("mechanism"):text(k):up();
+						end
 					end
 				features:up();
 			else
@@ -143,8 +163,8 @@
 				features:tag("session", xmpp_session_attr):up();
 			end
 		end);
-					
-module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind", 
+
+module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind",
 		function (session, stanza)
 			log("debug", "Client requesting a resource bind");
 			local resource;
@@ -166,8 +186,8 @@
 					:tag("jid"):text(session.full_jid));
 			end
 		end);
-		
-module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-session", 
+
+module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-session",
 		function (session, stanza)
 			log("debug", "Client requesting a session");
 			session.send(st.reply(stanza));
--- a/plugins/mod_selftests.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_selftests.lua	Sun Sep 27 12:26:51 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_tls.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_tls.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -6,14 +6,11 @@
 -- 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 config = require "core.configmanager";
-local secure_auth_only = config.get("*", "core", "require_encryption");
+local secure_auth_only = module:get_option("require_encryption");
 
 module:add_handler("c2s_unauthed", "starttls", xmlns_starttls,
 		function (session, stanza)
@@ -31,7 +28,7 @@
 		
 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
--- a/plugins/mod_version.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_version.lua	Sun Sep 27 12:26:51 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	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_watchregistrations.lua	Sun Sep 27 12:26:51 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	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_welcome.lua	Sun Sep 27 12:26:51 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 $username, welcome to the $host IM server!";
+local welcome_text = module:get_option("welcome_message") or "Hello $user, welcome to the $host IM server!";
 
 local st = require "util.stanza";
 
--- a/plugins/mod_xmlrpc.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/plugins/mod_xmlrpc.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -16,6 +16,7 @@
 local tostring = tostring;
 local is_admin = require "core.usermanager".is_admin;
 local jid_split = require "util.jid".split;
+local jid_bare = require "util.jid".bare;
 local b64_decode = require "util.encodings".base64.decode;
 local get_method = require "core.objectmanager".get_object;
 local validate_credentials = require "core.usermanager".validate_credentials;
@@ -65,10 +66,15 @@
 	return stanza.tags[1];
 end
 
-local function handle_xmlrpc_request(method, args)
+local function handle_xmlrpc_request(jid, method, args)
+	local is_secure_call = (method:sub(1,7) == "secure/");
+	if not is_admin(jid) and not is_secure_call then
+		return create_error_response(401, "not authorized");
+	end
 	method = get_method(method);
 	if not method then return create_error_response(404, "method not found"); end
 	args = args or {};
+	if is_secure_call then table.insert(args, 1, jid); end
 	local success, result = pcall(method, unpack(args));
 	if success then
 		success, result = pcall(create_response, result or "nil");
@@ -77,22 +83,20 @@
 		end
 		return create_error_response(500, "Error in creating response: "..result);
 	end
-	return create_error_response(0, result or "nil");
+	return create_error_response(0, tostring(result):gsub("^[^:]+:%d+: ", ""));
 end
 
 local function handle_xmpp_request(origin, stanza)
 	local query = stanza.tags[1];
 	if query.name == "query" then
 		if #query.tags == 1 then
-			if is_admin(stanza.attr.from) then
-				local success, method, args = pcall(translate_request, query.tags[1]);
-				if success then
-					local result = handle_xmlrpc_request(method, args);
-					origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:rpc'}):add_child(result));
-				else
-					origin.send(st.error_reply(stanza, "modify", "bad-request", method));
-				end
-			else origin.send(st.error_reply(stanza, "auth", "forbidden", "No content in XML-RPC request")); end
+			local success, method, args = pcall(translate_request, query.tags[1]);
+			if success then
+				local result = handle_xmlrpc_request(jid_bare(stanza.attr.from), method, args);
+				origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:rpc'}):add_child(result));
+			else
+				origin.send(st.error_reply(stanza, "modify", "bad-request", method));
+			end
 		else origin.send(st.error_reply(stanza, "modify", "bad-request", "No content in XML-RPC request")); end
 	else origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end
 end
@@ -106,7 +110,7 @@
 	-- authenticate user
 	local username, password = b64_decode(request['authorization'] or ''):gmatch('([^:]*):(.*)')(); -- TODO digest auth
 	local node, host = jid_split(username);
-	if not validate_credentials(host, node, password) and is_admin(username) then
+	if not validate_credentials(host, node, password) then
 		return unauthorized_response;
 	end
 	-- parse request
@@ -117,7 +121,7 @@
 	-- execute request
 	local success, method, args = pcall(translate_request, stanza);
 	if success then
-		return { headers = default_headers; body = tostring(handle_xmlrpc_request(method, args)) };
+		return { headers = default_headers; body = tostring(handle_xmlrpc_request(node.."@"..host, method, args)) };
 	end
 	return "<html><body>Error parsing XML-RPC request: "..tostring(method).."</body></html>";
 end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/muc/mod_muc.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -0,0 +1,149 @@
+-- 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 = 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 st = require "util.stanza";
+local uuid_gen = require "util.uuid".generate;
+local datamanager = require "util.datamanager";
+
+local rooms = {};
+local persistent_rooms = datamanager.load(nil, muc_host, "persistent") or {};
+local component;
+
+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
+				room = muc_new_room(bare);
+				room.route_stanza = room_route_stanza;
+				room.save = room_save;
+				rooms[bare] = room;
+			end
+			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 --[[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	Sun Sep 27 12:26:51 2009 +0100
@@ -0,0 +1,625 @@
+-- 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 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), 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 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, code, nick)
+	stanza = get_filtered_presence(stanza);
+	local data = self._occupants[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(self._occupants) do
+		if occupant ~= stanza.attr.from then
+			for jid in pairs(o_data.sessions) do
+				stanza.attr.to = jid;
+				self: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;
+			self:route_stanza(stanza);
+		end
+	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, role=o_data.role}):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)
+	return st.reply(stanza):query("http://jabber.org/protocol/disco#items");
+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('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._occupants[current_nick];
+				data.role = 'none';
+				self:broadcast_presence(pr);
+				self._occupants[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._occupants[current_nick].sessions[from] = pr;
+						self:broadcast_presence(pr);
+					else -- change nick
+						if self._occupants[to] 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, '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);
+							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;
+				if self._occupants[to] then
+					new_nick = nil;
+				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
+						self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}};
+						self._jid_nick[from] = to;
+						self:send_occupant_list(from);
+						pr.attr.from = to;
+						self:broadcast_presence(pr);
+						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
+			origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error?
+		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"));
+	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 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
+					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);
+						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);
+						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 get_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('This participant is kicked from the room because he sent an error message to another occupant')); -- 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" 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
+
+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)
+	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"}):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)
+	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"}):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
+
+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	Sun Sep 27 12:10:50 2009 +0100
+++ b/prosody	Sun Sep 27 12:26:51 2009 +0100
@@ -36,7 +36,7 @@
 
 config = require "core.configmanager"
 
-do
+function read_config()
 	-- TODO: Check for other formats when we add support for them
 	-- Use lfs? Make a new conf/ dir?
 	local ok, level, err = config.load((CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
@@ -62,236 +62,280 @@
 	end
 end
 
---- Initialize logging
-require "core.loggingmanager"
-
---- Check runtime dependencies
-require "util.dependencies"
+function load_libraries()
+	--- Initialize logging
+	require "core.loggingmanager"
+	
+	--- Check runtime dependencies
+	require "util.dependencies"
+	
+	--- Load socket framework
+	server = require "net.server"
+end	
 
---- Load socket framework
-local server = require "net.server"
+function init_global_state()
+	bare_sessions = {};
+	full_sessions = {};
+	hosts = {};
 
-bare_sessions = {};
-full_sessions = {};
-hosts = {};
-
--- Global 'prosody' object
-prosody = {};
-local prosody = prosody;
+	-- Global 'prosody' object
+	prosody = {};
+	local prosody = prosody;
+	
+	prosody.bare_sessions = bare_sessions;
+	prosody.full_sessions = full_sessions;
+	prosody.hosts = hosts;
+	
+	prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
+	                  plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
+	
+	prosody.arg = _G.arg;
 
-prosody.bare_sessions = bare_sessions;
-prosody.full_sessions = full_sessions;
-prosody.hosts = hosts;
-
-prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
-                  plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
-
-prosody.arg = arg;
-
-prosody.events = require "util.events".new();
+	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()
+		log("info", "Reloading configuration file");
+		prosody.events.fire_event("reloading-config");
+		local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
+		if not ok then
+			if level == "parser" then
+				log("error", "There was an error parsing the configuration file: %s", tostring(err));
+			elseif level == "file" then
+				log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
+			end
+		end
+		return ok, (err and tostring(level)..": "..tostring(err)) or nil;
+	end
 
--- Try to determine version
-local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
-if version_file then
-	prosody.version = version_file:read("*a"):gsub("%s*$", "");
-	version_file:close();
-	if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
-		prosody.version = "hg:"..prosody.version;
+	-- Function to reopen logfiles
+	function prosody.reopen_logfiles()
+		log("info", "Re-opening log files");
+		eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
+		prosody.events.fire_event("reopen-log-files");
 	end
-else
-	prosody.version = "unknown";
+
+	-- Function to initiate prosody shutdown
+	function prosody.shutdown(reason)
+		log("info", "Shutting down: %s", reason or "unknown reason");
+		prosody.shutdown_reason = reason;
+		prosody.events.fire_event("server-stopping", {reason = reason});
+		server.setquitting(true);
+	end
 end
 
-log("info", "Hello and welcome to Prosody version %s", prosody.version);
-
---- Load and initialise core modules
-require "util.import"
-require "core.xmlhandlers"
-require "core.rostermanager"
-require "core.eventmanager"
-require "core.hostmanager"
-require "core.modulemanager"
-require "core.usermanager"
-require "core.sessionmanager"
-require "core.stanza_router"
-
-require "util.array"
-require "util.iterators"
-require "util.timer"
-
--- Commented to protect us from 
--- the second kind of people
---[[ 
-pcall(require, "remdebug.engine");
-if remdebug then remdebug.engine.start() end
-]]
-
-local cl = require "net.connlisteners";
-
-require "util.stanza"
-require "util.jid"
-
-------------------------------------------------------------------------
-
-
-------------- Begin code without a home ---------------------
-
-local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
-require "util.datamanager".set_data_path(data_path);
-require "util.datamanager".add_callback(function(username, host, datastore, data)
-	if config.get(host, "core", "anonymous_login") then
-		return false;
-	end
-	return username, host, datastore, data;
-end);
-
------------ End of out-of-place code --------------
-
--- Function to reload the config file
-function prosody.reload_config()
-	log("info", "Reloading configuration file");
-	prosody.events.fire_event("reloading-config");
-	local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
-	if not ok then
-		if level == "parser" then
-			log("error", "There was an error parsing the configuration file: %s", tostring(err));
-		elseif level == "file" then
-			log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
+function read_version()
+	-- Try to determine version
+	local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
+	if version_file then
+		prosody.version = version_file:read("*a"):gsub("%s*$", "");
+		version_file:close();
+		if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
+			prosody.version = "hg:"..prosody.version;
 		end
+	else
+		prosody.version = "unknown";
 	end
 end
 
--- Function to reopen logfiles
-function prosody.reopen_logfiles()
-	log("info", "Re-opening log files");
-	eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
-	prosody.events.fire_event("reopen-log-files");
+function load_secondary_libraries()
+	--- Load and initialise core modules
+	require "util.import"
+	require "core.xmlhandlers"
+	require "core.rostermanager"
+	require "core.eventmanager"
+	require "core.hostmanager"
+	require "core.modulemanager"
+	require "core.usermanager"
+	require "core.sessionmanager"
+	require "core.stanza_router"
+
+	require "util.array"
+	require "util.iterators"
+	require "util.timer"
+	require "util.helpers"
+	
+	-- Commented to protect us from 
+	-- the second kind of people
+	--[[ 
+	pcall(require, "remdebug.engine");
+	if remdebug then remdebug.engine.start() end
+	]]
+
+	require "net.connlisteners";
+	
+	require "util.stanza"
+	require "util.jid"
+end
+
+function init_data_store()
+	local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
+	require "util.datamanager".set_data_path(data_path);
+	require "util.datamanager".add_callback(function(username, host, datastore, data)
+		if config.get(host, "core", "anonymous_login") then
+			return false;
+		end
+		return username, host, datastore, data;
+	end);
 end
 
--- Function to initiate prosody shutdown
-function prosody.shutdown(reason)
-	log("info", "Shutting down: %s", reason or "unknown reason");
-	prosody.events.fire_event("server-stopping", {reason = reason});
+function prepare_to_start()
+	-- Signal to modules that we are ready to start
+	eventmanager.fire_event("server-starting");
+	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");
+	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 });
+	end
+
+	local cl = require "net.connlisteners";
+	-- start listening on sockets
+	function 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;
+		
+		if type(ports) ~= "table" then
+			log("error", "core."..option.." is not a table");
+		else
+			for _, port in ipairs(ports) do
+				if type(port) ~= "number" then
+					log("error", "Non-numeric "..option.."_ports: "..tostring(port));
+				else
+					cl.start(listener, { 
+						ssl = conntype ~= "tcp" and global_ssl_ctx,
+						port = port,
+						interface = config.get("*", "core", option.."_interface") 
+							or cl.get(listener).default_interface 
+							or config.get("*", "core", "interface"),
+						type = conntype
+					});
+				end
+			end
+		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.start_time = os.time();
+end	
+
+function init_global_protection()
+	-- Catch global accesses --
+	local locked_globals_mt = { __index = function (t, k) error("Attempt to read a non-existent global '"..k.."'", 2); end, __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end }
+		
+	function prosody.unlock_globals()
+		setmetatable(_G, nil);
+	end
+	
+	function prosody.lock_globals()
+		setmetatable(_G, locked_globals_mt);
+	end
+
+	-- And lock now...
+	prosody.lock_globals();
+end
+
+function loop()
+	-- Error handler for errors that make it this far
+	local function catch_uncaught_error(err)
+		if err:match("%d*: interrupted!$") then
+			return "quitting";
+		end
+		
+		log("error", "Top-level error, please report:\n%s", tostring(err));
+		local traceback = debug.traceback("", 2);
+		if traceback then
+			log("error", "%s", traceback);
+		end
+		
+		prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
+	end
+	
+	while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
+		socket.sleep(0.2);
+	end
+end
+
+function cleanup()
+	log("info", "Shutdown status: Cleaning up");
+	prosody.events.fire_event("server-cleanup");
+	
+	-- Ok, we're quitting I know, but we
+	-- need to do some tidying before we go :)
+	server.setquitting(false);
+	
+	log("info", "Shutdown status: Closing all active sessions");
+	for hostname, host in pairs(hosts) do
+		log("debug", "Shutdown status: Closing client connections for %s", hostname)
+		if host.sessions then
+			local reason = { condition = "system-shutdown", text = "Server is shutting down" };
+			if prosody.shutdown_reason then
+				reason.text = reason.text..": "..prosody.shutdown_reason;
+			end
+			for username, user in pairs(host.sessions) do
+				for resource, session in pairs(user.sessions) do
+					log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
+					session:close(reason);
+				end
+			end
+		end
+	
+		log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
+		if host.s2sout then
+			for remotehost, session in pairs(host.s2sout) do
+				if session.close then
+					session:close("system-shutdown");
+				else
+					log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
+				end
+			end
+		end
+	end
+
+	log("info", "Shutdown status: Closing all server connections");
+	server.closeall();
+	
 	server.setquitting(true);
 end
 
--- Signal to modules that we are ready to start
-eventmanager.fire_event("server-starting");
-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");
-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 });
-end
-
--- start listening on sockets
-function 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;
-	
-	if type(ports) ~= "table" then
-		log("error", "core."..option.." is not a table");
-	else
-		for _, port in ipairs(ports) do
-			if type(port) ~= "number" then
-				log("error", "Non-numeric "..option.."_ports: "..tostring(port));
-			else
-				cl.start(listener, { 
-					ssl = conntype ~= "tcp" and global_ssl_ctx,
-					port = port,
-					interface = config.get("*", "core", option.."_interface") 
-						or cl.get(listener).default_interface 
-						or config.get("*", "core", "interface"),
-					type = conntype
-				});
-			end
-		end
-	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");
-
--- Catch global accesses --
-local locked_globals_mt = { __index = function (t, k) error("Attempt to read a non-existent global '"..k.."'", 2); end, __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end }
-
-function prosody.unlock_globals()
-	setmetatable(_G, nil);
-end
-
-function prosody.lock_globals()
-	setmetatable(_G, locked_globals_mt);
-end
-
--- And lock now...
-prosody.lock_globals();
-
-prosody.start_time = os.time();
+read_config();
+load_libraries();
+init_global_state();
+read_version();
+log("info", "Hello and welcome to Prosody version %s", prosody.version);
+load_secondary_libraries();
+init_data_store();
+prepare_to_start();
+init_global_protection();
 
 eventmanager.fire_event("server-started");
 prosody.events.fire_event("server-started");
 
--- Error handler for errors that make it this far
-local function catch_uncaught_error(err)
-	if err:match("%d*: interrupted!$") then
-		return "quitting";
-	end
-	
-	log("error", "Top-level error, please report:\n%s", tostring(err));
-	local traceback = debug.traceback("", 2);
-	if traceback then
-		log("error", "%s", traceback);
-	end
-	
-	prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
-end
-
-while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
-	socket.sleep(0.2);
-end
-
-log("info", "Shutdown status: Cleaning up");
-prosody.events.fire_event("server-cleanup");
-
--- Ok, we're quitting I know, but we
--- need to do some tidying before we go :)
-server.setquitting(false);
+loop();
 
-log("info", "Shutdown status: Closing all active sessions");
-for hostname, host in pairs(hosts) do
-	log("debug", "Shutdown status: Closing client connections for %s", hostname)
-	if host.sessions then
-		for username, user in pairs(host.sessions) do
-			for resource, session in pairs(user.sessions) do
-				log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
-				session:close("system-shutdown");
-			end
-		end
-	end
-	
-	log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
-	if host.s2sout then
-		for remotehost, session in pairs(host.s2sout) do
-			if session.close then
-				session:close("system-shutdown");
-			else
-				log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
-			end
-		end
-	end
-end
-
-log("info", "Shutdown status: Closing all server connections");
-server.closeall();
-
-server.setquitting(true);
-
+log("info", "Shutting down...");
+cleanup();
 eventmanager.fire_event("server-stopped");
 prosody.events.fire_event("server-stopped");
-log("info", "Shutdown status: Complete!");
+log("info", "Shutdown complete");
+
--- a/tools/ejabberd2prosody.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/tools/ejabberd2prosody.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -91,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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/ejabberdsql2prosody.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -0,0 +1,346 @@
+#!/usr/bin/env lua
+-- 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.
+--
+
+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}
+local dm = require "util.datamanager"
+dm.set_data_path("data");
+
+function parseFile(filename)
+------
+
+local file = nil;
+local last = nil;
+local function read(expected)
+	local ch;
+	if last then
+		ch = last; last = nil;
+	else ch = file:read(1); end
+	if expected and ch ~= expected then error("expected: "..expected.."; got: "..(ch or "nil")); end
+	return ch;
+end
+local function pushback(ch)
+	if last then error(); end
+	last = ch;
+end
+local function peek()
+	if not last then last = read(); end
+	return last;
+end
+
+local escapes = {
+	["\\0"] = "\0";
+	["\\'"] = "'";
+	["\\\""] = "\"";
+	["\\b"] = "\b";
+	["\\n"] = "\n";
+	["\\r"] = "\r";
+	["\\t"] = "\t";
+	["\\Z"] = "\26";
+	["\\\\"] = "\\";
+	["\\%"] = "%";
+	["\\_"] = "_";
+}
+local function unescape(s)
+	return escapes[s] or error("Unknown escape sequence: "..s);
+end
+local function readString()
+	read("'");
+	local s = "";
+	while true do
+		local ch = peek();
+		if ch == "\\" then
+			s = s..unescape(read()..read());
+		elseif ch == "'" then
+			break;
+		else
+			s = s..read();
+		end
+	end
+	read("'");
+	return s;
+end
+local function readNonString()
+	local s = "";
+	while true do
+		if peek() == "," or peek() == ")" then
+			break;
+		else
+			s = s..read();
+		end
+	end
+	return tonumber(s);
+end
+local function readItem()
+	if peek() == "'" then
+		return readString();
+	else
+		return readNonString();
+	end
+end
+local function readTuple()
+	local items = {}
+	read("(");
+	while peek() ~= ")" do
+		table.insert(items, readItem());
+		if peek() == ")" then break; end
+		read(",");
+	end
+	read(")");
+	return items;
+end
+local function readTuples()
+	if peek() ~= "(" then read("("); end
+	local tuples = {};
+	while true do
+		table.insert(tuples, readTuple());
+		if peek() == "," then read() end
+		if peek() == ";" then break; end
+	end
+	return tuples;
+end
+local function readTableName()
+	local tname = "";
+	while peek() ~= "`" do tname = tname..read(); end
+	return tname;
+end
+local function readInsert()
+	if peek() == nil then return nil; end
+	for ch in ("INSERT INTO `"):gmatch(".") do -- find line starting with this
+		if peek() == ch then
+			read(); -- found
+		else -- match failed, skip line
+			while peek() and read() ~= "\n" do end
+			return nil;
+		end
+	end
+	local tname = readTableName();
+	for ch in ("` VALUES "):gmatch(".") do read(ch); end -- expect this
+	local tuples = readTuples();
+	read(";"); read("\n");
+	return tname, tuples;
+end
+
+local function readFile(filename)
+	file = io.open(filename);
+	if not file then error("File not found: "..filename); os.exit(0); end
+	local t = {};
+	while true do
+		local tname, tuples = readInsert();
+		if tname then
+			if t[tname] then
+				local t_name = t[tname];
+				for i=1,#tuples do
+					table.insert(t_name, tuples[i]);
+				end
+			else
+				t[tname] = tuples;
+			end
+		elseif peek() == nil then
+			break;
+		end
+	end
+	return t;
+end
+
+return readFile(filename);
+
+------
+end
+
+-- XML parser
+local parse_xml = (function()
+	local entity_map = setmetatable({
+		["amp"] = "&";
+		["gt"] = ">";
+		["lt"] = "<";
+		["apos"] = "'";
+		["quot"] = "\"";
+	}, {__index = function(_, s)
+			if s:sub(1,1) == "#" then
+				if s:sub(2,2) == "x" then
+					return string.char(tonumber(s:sub(3), 16));
+				else
+					return string.char(tonumber(s:sub(2)));
+				end
+			end
+		end
+	});
+	local function xml_unescape(str)
+		return (str:gsub("&(.-);", entity_map));
+	end
+	local function parse_tag(s)
+		local name,sattr=(s):gmatch("([^%s]+)(.*)")();
+		local attr = {};
+		for a,b in (sattr):gmatch("([^=%s]+)=['\"]([^'\"]*)['\"]") do attr[a] = xml_unescape(b); end
+		return name, attr;
+	end
+	return function(xml)
+		local stanza = st.stanza("root");
+		local regexp = "<([^>]*)>([^<]*)";
+		for elem, text in xml:gmatch(regexp) do
+			if elem:sub(1,1) == "!" or elem:sub(1,1) == "?" then -- neglect comments and processing-instructions
+			elseif elem:sub(1,1) == "/" then -- end tag
+				elem = elem:sub(2);
+				stanza:up(); -- TODO check for start-end tag name match
+			elseif elem:sub(-1,-1) == "/" then -- empty tag
+				elem = elem:sub(1,-2);
+				local name,attr = parse_tag(elem);
+				stanza:tag(name, attr):up();
+			else -- start tag
+				local name,attr = parse_tag(elem);
+				stanza:tag(name, attr);
+			end
+			if #text ~= 0 then -- text
+				stanza:text(xml_unescape(text));
+			end
+		end
+		return stanza.tags[1];
+	end
+end)();
+-- end of XML parser
+
+local arg, host = ...;
+local help = "/? -? ? /h -h /help -help --help";
+if not(arg and host) or help:find(arg, 1, true) then
+	print([[ejabberd SQL DB dump importer for Prosody
+
+  Usage: ejabberdsql2prosody.lua filename.txt hostname
+
+The file can be generated using mysqldump:
+  mysqldump db_name > filename.txt]]);
+	os.exit(1);
+end
+local map = {
+	["last"] = {"username", "seconds", "state"};
+	["privacy_default_list"] = {"username", "name"};
+	["privacy_list"] = {"username", "name", "id"};
+	["privacy_list_data"] = {"id", "t", "value", "action", "ord", "match_all", "match_iq", "match_message", "match_presence_in", "match_presence_out"};
+	["private_storage"] = {"username", "namespace", "data"};
+	["rostergroups"] = {"username", "jid", "grp"};
+	["rosterusers"] = {"username", "jid", "nick", "subscription", "ask", "askmessage", "server", "subscribe", "type"};
+	["spool"] = {"username", "xml", "seq"};
+	["users"] = {"username", "password"};
+	["vcard"] = {"username", "vcard"};
+	--["vcard_search"] = {};
+}
+local NULL = {};
+local t = parseFile(arg);
+for name, data in pairs(t) do
+	local m = map[name];
+	if m then
+		if #data > 0 and #data[1] ~= #m then
+			print("[warning] expected "..#m.." columns for table `"..name.."`, found "..#data[1]);
+		end
+		for i=1,#data do
+			local row = data[i];
+			for j=1,#m do
+				row[m[j]] = row[j];
+				row[j] = nil;
+			end
+		end
+	end
+end
+--print(serialize(t));
+
+for i, row in ipairs(t["users"] or NULL) do
+	local node, password = row.username, row.password;
+	local ret, err = dm.store(node, host, "accounts", {password = password});
+	print("["..(err or "success").."] accounts: "..node.."@"..host.." = "..password);
+end
+
+function roster(node, host, jid, item)
+	local roster = dm.load(node, host, "roster") or {};
+	roster[jid] = item;
+	local ret, err = dm.store(node, host, "roster", roster);
+	print("["..(err or "success").."] roster: " ..node.."@"..host.." - "..jid);
+end
+function roster_pending(node, host, jid)
+	local roster = dm.load(node, host, "roster") or {};
+	roster.pending = roster.pending or {};
+	roster.pending[jid] = true;
+	local ret, err = dm.store(node, host, "roster", roster);
+	print("["..(err or "success").."] roster-pending: " ..node.."@"..host.." - "..jid);
+end
+function roster_group(node, host, jid, group)
+	local roster = dm.load(node, host, "roster") or {};
+	local item = roster[jid];
+	if not item then print("Warning: No roster item "..jid.." for user "..node..", can't put in group "..group); return; end
+	item.groups[group] = true;
+	local ret, err = dm.store(node, host, "roster", roster);
+	print("["..(err or "success").."] roster-group: " ..node.."@"..host.." - "..jid.." - "..group);
+end
+function private_storage(node, host, xmlns, stanza)
+	local private = dm.load(node, host, "private") or {};
+	private[stanza.name..":"..xmlns] = st.preserialize(stanza);
+	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;
+	if name == "" then name = nil; end
+	local subscription = row.subscription;
+	if subscription == "N" then
+		subscription = "none"
+	elseif subscription == "B" then
+		subscription = "both"
+	elseif subscription == "F" then
+		subscription = "from"
+	elseif subscription == "T" then
+		subscription = "to"
+	else error("Unknown subscription type: "..subscription) end;
+	local ask = row.ask;
+	if ask == "N" then
+		ask = nil;
+	elseif ask == "O" then
+		ask = "subscribe";
+	elseif ask == "I" then
+		roster_pending(node, host, contact);
+		ask = nil;
+	elseif ask == "B" then
+		roster_pending(node, host, contact);
+		ask = "subscribe";
+	else error("Unknown ask type: "..ask); end
+	local item = {name = name, ask = ask, subscription = subscription, groups = {}};
+	roster(node, host, contact, item);
+end
+for i, row in ipairs(t["rostergroups"] or NULL) do
+	roster_group(row.username, host, row.jid, row.grp);
+end
+for i, row in ipairs(t["vcard"] or NULL) do
+	local ret, err = dm.store(row.username, host, "vcard", st.preserialize(parse_xml(row.vcard)));
+	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, 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	Sun Sep 27 12:10:50 2009 +0100
+++ b/tools/erlparse.lua	Sun Sep 27 12:26:51 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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/broadcast.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -0,0 +1,68 @@
+-- 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 ipairs, pairs, setmetatable, type = 
+        ipairs, pairs, setmetatable, type;
+
+module "pubsub"
+
+local pubsub_node_mt = { __index = _M };
+
+function new_node(name)
+	return setmetatable({ name = name, subscribers = {} }, pubsub_node_mt);
+end
+
+function set_subscribers(node, subscribers_list, list_type)
+	local subscribers = node.subscribers;
+	
+	if list_type == "array" then
+		for _, jid in ipairs(subscribers_list) do
+			if not subscribers[jid] then
+				node:add_subscriber(jid);
+			end
+		end
+	elseif (not list_type) or list_type == "set" then
+		for jid in pairs(subscribers_list) do
+			if type(jid) == "string" then
+				node:add_subscriber(jid);
+			end
+		end
+	end
+end
+
+function get_subscribers(node)
+	return node.subscribers;
+end
+
+function publish(node, item, dispatcher, data)
+	local subscribers = node.subscribers;
+	for i = 1,#subscribers do
+		item.attr.to = subscribers[i];
+		dispatcher(data, item);
+	end
+end
+
+function add_subscriber(node, jid)
+	local subscribers = node.subscribers;
+	if not subscribers[jid] then
+		local space = #subscribers;
+		subscribers[space] = jid;
+		subscribers[jid] = space;
+	end
+end
+
+function remove_subscriber(node, jid)
+	local subscribers = node.subscribers;
+	if subscribers[jid] then
+		subscribers[subscribers[jid]] = nil;
+		subscribers[jid] = nil;
+	end
+end
+
+return _M;
--- a/util/discohelper.lua	Sun Sep 27 12:10:50 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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/helpers.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -0,0 +1,27 @@
+
+module("helpers", package.seeall);
+
+-- Helper functions for debugging
+
+local log = require "util.logger".init("util.debug");
+
+function log_events(events, name, logger)
+	local f = events.fire_event;
+	if not f then
+		error("Object does not appear to be a util.events object");
+	end
+	logger = logger or log;
+	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;
+end
+
+function revert_log_events(events)
+	events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :)
+end
+
+return _M;
--- a/util/iterators.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/util/iterators.lua	Sun Sep 27 12:26:51 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	Sun Sep 27 12:10:50 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/pubsub.lua	Sun Sep 27 12:10:50 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +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 ipairs, pairs, setmetatable, type = 
-        ipairs, pairs, setmetatable, type;
-
-module "pubsub"
-
-local pubsub_node_mt = { __index = _M };
-
-function new_node(name)
-	return setmetatable({ name = name, subscribers = {} }, pubsub_node_mt);
-end
-
-function set_subscribers(node, subscribers_list, list_type)
-	local subscribers = node.subscribers;
-	
-	if list_type == "array" then
-		for _, jid in ipairs(subscribers_list) do
-			if not subscribers[jid] then
-				node:add_subscriber(jid);
-			end
-		end
-	elseif (not list_type) or list_type == "set" then
-		for jid in pairs(subscribers_list) do
-			if type(jid) == "string" then
-				node:add_subscriber(jid);
-			end
-		end
-	end
-end
-
-function get_subscribers(node)
-	return node.subscribers;
-end
-
-function publish(node, item, dispatcher, data)
-	local subscribers = node.subscribers;
-	for i = 1,#subscribers do
-		item.attr.to = subscribers[i];
-		dispatcher(data, item);
-	end
-end
-
-function add_subscriber(node, jid)
-	local subscribers = node.subscribers;
-	if not subscribers[jid] then
-		local space = #subscribers;
-		subscribers[space] = jid;
-		subscribers[jid] = space;
-	end
-end
-
-function remove_subscriber(node, jid)
-	local subscribers = node.subscribers;
-	if subscribers[jid] then
-		subscribers[subscribers[jid]] = nil;
-		subscribers[jid] = nil;
-	end
-end
-
-return _M;
--- a/util/sasl.lua	Sun Sep 27 12:10:50 2009 +0100
+++ b/util/sasl.lua	Sun Sep 27 12:26:51 2009 +0100
@@ -1,14 +1,14 @@
 -- 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.
 
 
@@ -20,7 +20,6 @@
 local t_insert, t_concat = table.insert, table.concat;
 local to_byte, to_char = string.byte, string.char;
 local to_unicode = require "util.encodings".idna.to_unicode;
-local saslprep = require "util.encodings".stringprep.saslprep;
 local s_match = string.match;
 local gmatch = string.gmatch
 local string = string
@@ -31,60 +30,56 @@
 
 module "sasl"
 
-local function new_plain(realm, password_handler)
-	local object = { mechanism = "PLAIN", realm = realm, password_handler = password_handler}
+-- Credentials handler:
+--   Arguments: ("PLAIN", user, host, password)
+--   Returns: true (success) | false (fail) | nil (user unknown)
+local function new_plain(realm, credentials_handler)
+	local object = { mechanism = "PLAIN", realm = realm, credentials_handler = credentials_handler}
 	function object.feed(self, message)
-	
 		if message == "" or message == nil then return "failure", "malformed-request" end
 		local response = message
 		local authorization = s_match(response, "([^%z]+)")
 		local authentication = s_match(response, "%z([^%z]+)%z")
 		local password = s_match(response, "%z[^%z]+%z([^%z]+)")
-		authorization, authentication, password = saslprep(authorization), saslprep(authentication), saslprep(password);
-		
-		if authentication == nil or password == nil then return "failure", "malformed-request" end
-		
-		local password_encoding, correct_password = self.password_handler(authentication, self.realm, self.realm, "PLAIN")
-		
-		if correct_password == nil then return "failure", "not-authorized"
-		elseif correct_password == false then return "failure", "account-disabled" end
-		
-		local claimed_password = ""
-		if password_encoding == nil then claimed_password = password
-		else claimed_password = password_encoding(password) end
-		claimed_password = saslprep(claimed_password);
-		
-		self.username = authentication
-		if claimed_password == correct_password then
-			return "success"
-		else
-			return "failure", "not-authorized"
-		end
-	end
-	return object
+
+    if authentication == nil or password == nil then return "failure", "malformed-request" end
+    self.username = authentication
+    local auth_success = self.credentials_handler("PLAIN", self.username, self.realm, password)
+
+    if auth_success then
+      return "success"
+    elseif auth_success == nil then
+      return "failure", "account-disabled"
+    else
+      return "failure", "not-authorized"
+    end
+  end
+  return object
 end
 
-
+-- credentials_handler:
+--   Arguments: (mechanism, node, domain, realm, decoder)
+--   Returns: Password encoding, (plaintext) password
 -- implementing RFC 2831
-local function new_digest_md5(realm, password_handler)
+local function new_digest_md5(realm, credentials_handler)
 	--TODO complete support for authzid
 
 	local function serialize(message)
 		local data = ""
-		
+
 		if type(message) ~= "table" then error("serialize needs an argument of type table.") end
-		
+
 		-- testing all possible values
+		if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
 		if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end
 		if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end
 		if message["charset"] then data = data..[[charset=]]..message.charset.."," end
 		if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end
-		if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
 		if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end
 		data = data:gsub(",$", "")
 		return data
 	end
-	
+
 	local function utf8tolatin1ifpossible(passwd)
 		local i = 1;
 		while i <= #passwd do
@@ -140,16 +135,16 @@
 		return message;
 	end
 
-	local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler};
-	
+	local object = { mechanism = "DIGEST-MD5", realm = realm, credentials_handler = credentials_handler};
+
 	object.nonce = generate_uuid();
 	object.step = 0;
 	object.nonce_count = {};
-												
+
 	function object.feed(self, message)
 		self.step = self.step + 1;
 		if (self.step == 1) then
-			local challenge = serialize({	nonce = object.nonce, 
+			local challenge = serialize({	nonce = object.nonce,
 											qop = "auth",
 											charset = "utf-8",
 											algorithm = "md5-sess",
@@ -161,13 +156,13 @@
 			if response["nc"] then
 				if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end
 			end
-			
+
 			-- check for username, it's REQUIRED by RFC 2831
 			if not response["username"] then
 				return "failure", "malformed-request";
 			end
 			self["username"] = response["username"];
-			
+
 			-- check for nonce, ...
 			if not response["nonce"] then
 				return "failure", "malformed-request";
@@ -175,23 +170,23 @@
 				-- check if it's the right nonce
 				if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end
 			end
-			
+
 			if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end
 			if not response["qop"] then response["qop"] = "auth" end
-			
+
 			if response["realm"] == nil or response["realm"] == "" then
 				response["realm"] = "";
 			elseif response["realm"] ~= self.realm then
 				return "failure", "not-authorized", "Incorrect realm value";
 			end
-			
+
 			local decoder;
 			if response["charset"] == nil then
 				decoder = utf8tolatin1ifpossible;
 			elseif response["charset"] ~= "utf-8" then
 				return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8.";
 			end
-			
+
 			local domain = "";
 			local protocol = "";
 			if response["digest-uri"] then
@@ -200,10 +195,10 @@
 			else
 				return "failure", "malformed-request", "Missing entry for digest-uri in SASL message."
 			end
-			
+
 			--TODO maybe realm support
 			self.username = response["username"];
-			local password_encoding, Y = self.password_handler(response["username"], to_unicode(domain), response["realm"], "DIGEST-MD5", decoder);
+			local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder);
 			if Y == nil then return "failure", "not-authorized"
 			elseif Y == false then return "failure", "account-disabled" end
 			local A1 = "";
@@ -219,27 +214,27 @@
 				A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
 			end
 			local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
-			
+
 			local HA1 = md5(A1, true);
 			local HA2 = md5(A2, true);
-			
+
 			local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2;
 			local response_value = md5(KD, true);
-			
+
 			if response_value == response["response"] then
 				-- calculate rspauth
 				A2 = ":"..protocol.."/"..domain;
-				
+
 				HA1 = md5(A1, true);
 				HA2 = md5(A2, true);
-				
+
 				KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
 				local rspauth = md5(KD, true);
 				self.authenticated = true;
 				return "challenge", serialize({rspauth = rspauth});
 			else
 				return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
-			end							
+			end
 		elseif self.step == 3 then
 			if self.authenticated ~= nil then return "success"
 			else return "failure", "malformed-request" end
@@ -248,8 +243,10 @@
 	return object;
 end
 
-local function new_anonymous(realm, password_handler)
-	local object = { mechanism = "ANONYMOUS", realm = realm, password_handler = password_handler}
+-- Credentials handler: Can be nil. If specified, should take the mechanism as
+-- the only argument, and return true for OK, or false for not-OK (TODO)
+local function new_anonymous(realm, credentials_handler)
+	local object = { mechanism = "ANONYMOUS", realm = realm, credentials_handler = credentials_handler}
 		function object.feed(self, message)
 			return "success"
 		end
@@ -258,11 +255,11 @@
 end
 
 
-function new(mechanism, realm, password_handler)
+function new(mechanism, realm, credentials_handler)
 	local object
-	if mechanism == "PLAIN" then object = new_plain(realm, password_handler)
-	elseif mechanism == "DIGEST-MD5" then object = new_digest_md5(realm, password_handler)
-	elseif mechanism == "ANONYMOUS" then object = new_anonymous(realm, password_handler)
+	if mechanism == "PLAIN" then object = new_plain(realm, credentials_handler)
+	elseif mechanism == "DIGEST-MD5" then object = new_digest_md5(realm, credentials_handler)
+	elseif mechanism == "ANONYMOUS" then object = new_anonymous(realm, credentials_handler)
 	else
 		log("debug", "Unsupported SASL mechanism: "..tostring(mechanism));
 		return nil

mercurial