Merge 0.7->trunk

Sat, 12 Jun 2010 02:39:18 +0100

author
Matthew Wild <mwild1@gmail.com>
date
Sat, 12 Jun 2010 02:39:18 +0100
changeset 3240
9782a222e941
parent 3235
651139e831b1 (diff)
parent 3239
5ea90ee96022 (current diff)
child 3242
43f9478d8119

Merge 0.7->trunk

core/sessionmanager.lua file | annotate | diff | comparison | revisions
plugins/mod_saslauth.lua file | annotate | diff | comparison | revisions
--- a/core/configmanager.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/core/configmanager.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -30,10 +30,11 @@
 -- When key not found in section, check key in global's section
 function section_mt(section_name)
 	return { __index = 	function (t, k)
-									local section = rawget(global_config, section_name);
-									if not section then return nil; end
-									return section[k];
-							end };
+					local section = rawget(global_config, section_name);
+					if not section then return nil; end
+					return section[k];
+				end
+	};
 end
 
 function getconfig()
@@ -112,16 +113,20 @@
 	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, VirtualHost = true, Component = true, component = true,
-							Include = true, include = true, RunScript = dofile }, { __index = function (t, k)
-												return rawget(_G, k) or
-														function (settings_table)
-															config[__currenthost or "*"][k] = settings_table;
-														end;
-										end,
-								__newindex = function (t, k, v)
-											set(env.__currenthost or "*", "core", k, v);
-										end});
+		env = setmetatable({
+			Host = true, host = true, VirtualHost = true,
+			Component = true, component = true,
+			Include = true, include = true, RunScript = dofile }, {
+				__index = function (t, k)
+					return rawget(_G, k) or
+						function (settings_table)
+							config[__currenthost or "*"][k] = settings_table;
+						end;
+				end,
+				__newindex = function (t, k, v)
+					set(env.__currenthost or "*", "core", k, v);
+				end
+		});
 		
 		rawset(env, "__currenthost", "*") -- Default is global
 		function env.VirtualHost(name)
--- a/core/eventmanager.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/core/eventmanager.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -10,24 +10,18 @@
 local t_insert = table.insert;
 local ipairs = ipairs;
 
+local events = _G.prosody.events;
+
 module "eventmanager"
 
 local event_handlers = {};
 
 function add_event_hook(name, handler)
-	if not event_handlers[name] then
-		event_handlers[name] = {};
-	end
-	t_insert(event_handlers[name] , handler);
+	return events.add_handler(name, handler);
 end
 
 function fire_event(name, ...)
-	local event_handlers = event_handlers[name];
-	if event_handlers then
-		for name, handler in ipairs(event_handlers) do
-			handler(...);
-		end
-	end
+	return events.fire_event(name, ...);
 end
 
-return _M;
\ No newline at end of file
+return _M;
--- a/core/rostermanager.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/core/rostermanager.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -190,7 +190,19 @@
 	end
 end
 
+local function _get_online_roster_subscription(jidA, jidB)
+	local user = bare_sessions[jidA];
+	local item = user and (user.roster[jidB] or { subscription = "none" });
+	return item and item.subscription;
+end
 function is_contact_subscribed(username, host, jid)
+	do
+		local selfjid = username.."@"..host;
+		local subscription = _get_online_roster_subscription(selfjid, jid);
+		if subscription then return (subscription == "both" or subscription == "from"); end
+		local subscription = _get_online_roster_subscription(jid, selfjid);
+		if subscription then return (subscription == "both" or subscription == "to"); end
+	end
 	local roster, err = load_roster(username, host);
 	local item = roster[jid];
 	return item and (item.subscription == "from" or item.subscription == "both"), err;
--- a/core/s2smanager.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/core/s2smanager.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -23,6 +23,7 @@
 
 local idna_to_ascii = require "util.encodings".idna.to_ascii;
 local connlisteners_get = require "net.connlisteners".get;
+local initialize_filters = require "util.filters".initialize;
 local wrapclient = require "net.server".wrapclient;
 local modulemanager = require "core.modulemanager";
 local st = require "stanza";
@@ -137,7 +138,19 @@
 	open_sessions = open_sessions + 1;
 	local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
 	session.log = log;
-	session.sends2s = function (t) log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)")); w(conn, tostring(t)); end
+	local filter = initialize_filters(session);
+	session.sends2s = function (t)
+		log("debug", "sending: %s", t.top_tag and t:top_tag() or t:match("^([^>]*>?)"));
+		if t.name then
+			t = filter("stanzas/out", t);
+		end
+		if t then
+			t = filter("bytes/out", tostring(t));
+			if t then
+				return w(conn, t);
+			end
+		end
+	end
 	incoming_s2s[session] = true;
 	add_task(connect_timeout, function ()
 		if session.conn ~= conn or
@@ -166,6 +179,8 @@
 			host_session.log = log;
 		end
 		
+		initialize_filters(host_session);
+		
 		if connect ~= false then
 			-- Kick the connection attempting machine into life
 			attempt_connection(host_session);
@@ -331,8 +346,20 @@
 	-- otherwise it will assume it is a new incoming connection
 	cl.register_outgoing(conn, host_session);
 	
+	local filter = initialize_filters(host_session);
 	local w, log = conn.write, host_session.log;
-	host_session.sends2s = function (t) log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?")); w(conn, tostring(t)); end
+	host_session.sends2s = function (t)
+		if t.name then
+			t = filter("stanzas/out", t);
+		end
+		if t then
+			t = filter("bytes/out", tostring(t));
+			if t then
+				log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?"));
+				return w(conn, tostring(t));
+			end
+		end
+	end
 	
 	host_session:open_stream(from_host, to_host);
 	
--- a/core/sessionmanager.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/core/sessionmanager.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -27,6 +27,7 @@
 local resourceprep = require "util.encodings".stringprep.resourceprep;
 local nodeprep = require "util.encodings".stringprep.nodeprep;
 
+local initialize_filters = require "util.filters".initialize;
 local fire_event = require "core.eventmanager".fire_event;
 local add_task = require "util.timer".add_task;
 local gettime = require "socket".gettime;
@@ -50,8 +51,20 @@
 	end
 	open_sessions = open_sessions + 1;
 	log("debug", "open sessions now: ".. open_sessions);
+	
+	local filter = initialize_filters(session);
 	local w = conn.write;
-	session.send = function (t) w(conn, tostring(t)); end
+	session.send = function (t)
+		if t.name then
+			t = filter("stanzas/out", t);
+		end
+		if t then
+			t = filter("bytes/out", tostring(t));
+			if t then
+				return w(conn, t);
+			end
+		end
+	end
 	session.ip = conn:ip();
 	local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
 	session.log = logger.init(conn_name);
--- a/core/usermanager.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/core/usermanager.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -7,6 +7,7 @@
 --
 
 local datamanager = require "util.datamanager";
+local modulemanager = require "core.modulemanager";
 local log = require "util.logger".init("usermanager");
 local type = type;
 local error = error;
@@ -18,83 +19,96 @@
 
 local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false;
 
+local prosody = _G.prosody;
+
+local setmetatable = setmetatable;
+
+local default_provider = "internal";
+
 module "usermanager"
 
-local function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
-
-function validate_credentials(host, username, password, method)
-	log("debug", "User '%s' is being validated", username);
-	if is_cyrus(host) then return nil, "Legacy auth not supported with Cyrus SASL."; end
-	local credentials = datamanager.load(username, host, "accounts") or {};
+function new_null_provider()
+	local function dummy() end;
+	return setmetatable({name = "null"}, { __index = function() return dummy; end });
+end
 
-	if method == nil then method = "PLAIN"; end
-	if method == "PLAIN" and credentials.password then -- PLAIN, do directly
-		if password == credentials.password then
-			return true;
-		else
-			return nil, "Auth failed. Invalid username or password.";
+local function host_handler(host)
+	local host_session = hosts[host];
+	host_session.events.add_handler("item-added/auth-provider", function (event)
+		local provider = event.item;
+		local auth_provider = config.get(host, "core", "authentication") or default_provider;
+		if provider.name == auth_provider then
+			host_session.users = provider;
+		end
+		if host_session.users ~= nil and host_session.users.name ~= nil then
+			log("debug", "host '%s' now set to use user provider '%s'", host, host_session.users.name);
 		end
-  end
-	-- must do md5
-	-- make credentials md5
-	local pwd = credentials.password;
-	if not pwd then pwd = credentials.md5; else pwd = hashes.md5(pwd, true); end
-	-- make password md5
-	if method == "PLAIN" then
-		password = hashes.md5(password or "", true);
-	elseif method ~= "DIGEST-MD5" then
-		return nil, "Unsupported auth method";
-	end
-	-- compare
-	if password == pwd then
-		return true;
-	else
-		return nil, "Auth failed. Invalid username or password.";
-	end
+	end);
+	host_session.events.add_handler("item-removed/auth-provider", function (event)
+		local provider = event.item;
+		if host_session.users == provider then
+			host_session.users = new_null_provider();
+		end
+	end);
+   	host_session.users = new_null_provider(); -- Start with the default usermanager provider
+   	local auth_provider = config.get(host, "core", "authentication") or default_provider;
+   	if auth_provider ~= "null" then
+   		modulemanager.load(host, "auth_"..auth_provider);
+   	end
+end;
+prosody.events.add_handler("host-activated", host_handler, 100);
+prosody.events.add_handler("component-activated", host_handler, 100);
+
+function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
+
+function test_password(username, password, host)
+	return hosts[host].users.test_password(username, password);
 end
 
 function get_password(username, host)
-	if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
-	return (datamanager.load(username, host, "accounts") or {}).password
+	return hosts[host].users.get_password(username);
 end
-function set_password(username, host, password)
-	if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
-	local account = datamanager.load(username, host, "accounts");
-	if account then
-		account.password = password;
-		return datamanager.store(username, host, "accounts", account);
-	end
-	return nil, "Account not available.";
+
+function set_password(username, password, host)
+	return hosts[host].users.set_password(username, password);
 end
 
 function user_exists(username, host)
-	if not(require_provisioning) and is_cyrus(host) then return true; end
-	local account, err = datamanager.load(username, host, "accounts");
-	return (account or err) ~= nil; -- FIXME also check for empty credentials
+	return hosts[host].users.user_exists(username);
 end
 
 function create_user(username, password, host)
-	if not(require_provisioning) and is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
-	return datamanager.store(username, host, "accounts", {password = password});
+	return hosts[host].users.create_user(username, password);
 end
 
-function get_supported_methods(host)
-	return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
+function get_sasl_handler(host)
+	return hosts[host].users.get_sasl_handler();
+end
+
+function get_provider(host)
+	return hosts[host].users;
 end
 
 function is_admin(jid, host)
-	host = host or "*";
-	local admins = config.get(host, "core", "admins");
-	if host ~= "*" and admins == config.get("*", "core", "admins") then
-		return nil;
+	local is_admin;
+	if host and host ~= "*" then
+		is_admin = hosts[host].users.is_admin(jid);
 	end
-	if type(admins) == "table" then
-		jid = jid_bare(jid);
-		for _,admin in ipairs(admins) do
-			if admin == jid then return true; end
+	if not is_admin then -- Test only whether this JID is a global admin
+		local admins = config.get("*", "core", "admins");
+		if type(admins) == "table" then
+			jid = jid_bare(jid);
+			for _,admin in ipairs(admins) do
+				if admin == jid then
+					is_admin = true;
+					break;
+				end
+			end
+		elseif admins then
+			log("error", "Option 'admins' for host '%s' is not a table", host);
 		end
-	elseif admins then log("warn", "Option 'admins' for host '%s' is not a table", host); end
-	return nil;
+	end
+	return is_admin;
 end
 
 return _M;
--- a/net/multiplex_listener.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/net/multiplex_listener.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -19,6 +19,8 @@
 	if buf:match("^[a-zA-Z]") then
 		local listener = httpserver_listener;
 		conn:setlistener(listener);
+		local onconnect = listener.onconnect;
+		if onconnect then onconnect(conn) end
 		listener.onincoming(conn, buf);
 	elseif buf:match(">") then
 		local listener;
@@ -31,6 +33,8 @@
 			listener = xmppclient_listener;
 		end
 		conn:setlistener(listener);
+		local onconnect = listener.onconnect;
+		if onconnect then onconnect(conn) end
 		listener.onincoming(conn, buf);
 	elseif #buf > 1024 then
 		conn:close();
--- a/net/xmppclient_listener.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/net/xmppclient_listener.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -11,7 +11,7 @@
 local logger = require "logger";
 local log = logger.init("xmppclient_listener");
 local lxp = require "lxp"
-local init_xmlhandlers = require "core.xmlhandlers"
+local new_xmpp_stream = require "util.xmppstream".new;
 local sm_new_session = require "core.sessionmanager".new_session;
 
 local connlisteners_register = require "net.connlisteners".register;
@@ -72,23 +72,6 @@
 
 -- These are session methods --
 
-local function session_reset_stream(session)
-	-- Reset stream
-		local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1");
-		session.parser = parser;
-		
-		session.notopen = true;
-		
-		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]+", " "):gsub("[%z\1-\31]", "_"));
-			session:close("xml-not-well-formed");
-		end
-		
-		return true;
-end
-
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
 local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
 local function session_close(session, reason)
@@ -128,32 +111,57 @@
 
 -- End of session methods --
 
+function xmppclient.onconnect(conn)
+	local session = sm_new_session(conn);
+	sessions[conn] = session;
+	
+	session.log("info", "Client connected");
+	
+	-- Client is using legacy SSL (otherwise mod_tls sets this flag)
+	if conn:ssl() then
+		session.secure = true;
+	end
+	
+	if opt_keepalives ~= nil then
+		conn:setoption("keepalive", opt_keepalives);
+	end
+	
+	session.close = session_close;
+	
+	local stream = new_xmpp_stream(session, stream_callbacks);
+	session.stream = stream;
+	
+	session.notopen = true;
+	
+	function session.reset_stream()
+		session.notopen = true;
+		session.stream:reset();
+	end
+	
+	local filter = session.filter;
+	function session.data(data)
+		data = filter("bytes/in", data);
+		if data then
+			local ok, err = stream:feed(data);
+			if ok then return; end
+			log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
+			session:close("xml-not-well-formed");
+		end
+	end
+	
+	local handlestanza = stream_callbacks.handlestanza;
+	function session.dispatch_stanza(session, stanza)
+		stanza = filter("stanzas/in", stanza);
+		if stanza then
+			return handlestanza(session, stanza);
+		end
+	end
+end
+
 function xmppclient.onincoming(conn, data)
 	local session = sessions[conn];
-	if not session then
-		session = sm_new_session(conn);
-		sessions[conn] = session;
-
-		session.log("info", "Client connected");
-		
-		-- Client is using legacy SSL (otherwise mod_tls sets this flag)
-		if conn:ssl() then
-			session.secure = true;
-		end
-		
-		if opt_keepalives ~= nil then
-			conn:setoption("keepalive", opt_keepalives);
-		end
-		
-		session.reset_stream = session_reset_stream;
-		session.close = session_close;
-		
-		session_reset_stream(session); -- Initialise, ready for use
-		
-		session.dispatch_stanza = stream_callbacks.handlestanza;
-	end
-	if data then
-		session.data(conn, data);
+	if session then
+		session.data(data);
 	end
 end
 	
--- a/net/xmppserver_listener.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/net/xmppserver_listener.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -11,7 +11,7 @@
 local logger = require "logger";
 local log = logger.init("xmppserver_listener");
 local lxp = require "lxp"
-local init_xmlhandlers = require "core.xmlhandlers"
+local new_xmpp_stream = require "util.xmppstream".new;
 local s2s_new_incoming = require "core.s2smanager".new_incoming;
 local s2s_streamopened = require "core.s2smanager".streamopened;
 local s2s_streamclosed = require "core.s2smanager".streamclosed;
@@ -72,24 +72,6 @@
 
 -- These are session methods --
 
-local function session_reset_stream(session)
-	-- Reset stream
-		local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "\1");
-		session.parser = parser;
-		
-		session.notopen = true;
-		
-		function session.data(conn, data)
-			local ok, err = parser:parse(data);
-			if ok then return; end
-			(session.log or log)("warn", "Received invalid XML: %s", data);
-			(session.log or log)("warn", "Problem was: %s", err);
-			session:close("xml-not-well-formed");
-		end
-		
-		return true;
-end
-
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
 local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
 local function session_close(session, reason, remote_reason)
@@ -132,29 +114,58 @@
 
 -- End of session methods --
 
-function xmppserver.onincoming(conn, data)
-	local session = sessions[conn];
-	if not session then
-		session = s2s_new_incoming(conn);
+local function initialize_session(session)
+	local stream = new_xmpp_stream(session, stream_callbacks);
+	session.stream = stream;
+	
+	session.notopen = true;
+		
+	function session.reset_stream()
+		session.notopen = true;
+		session.stream:reset();
+	end
+	
+	local filter = session.filter;
+	function session.data(data)
+		data = filter("bytes/in", data);
+		if data then
+			local ok, err = stream:feed(data);
+			if ok then return; end
+			(session.log or log)("warn", "Received invalid XML: %s", data);
+			(session.log or log)("warn", "Problem was: %s", err);
+			session:close("xml-not-well-formed");
+		end
+	end
+
+	session.close = session_close;
+	local handlestanza = stream_callbacks.handlestanza;
+	function session.dispatch_stanza(session, stanza)
+		stanza = filters("stanzas/in", stanza);
+		if stanza then
+			return handlestanza(session, stanza);
+		end
+	end
+end
+
+function xmppserver.onconnect(conn)
+	if not sessions[conn] then -- May be an existing outgoing session
+		local session = s2s_new_incoming(conn);
 		sessions[conn] = session;
-
+	
 		-- Logging functions --
-
-		
 		local conn_name = "s2sin"..tostring(conn):match("[a-f0-9]+$");
 		session.log = logger.init(conn_name);
 		
 		session.log("info", "Incoming s2s connection");
 		
-		session.reset_stream = session_reset_stream;
-		session.close = session_close;
-		
-		session_reset_stream(session); -- Initialise, ready for use
-		
-		session.dispatch_stanza = stream_callbacks.handlestanza;
+		initialize_session(session);
 	end
-	if data then
-		session.data(conn, data);
+end
+
+function xmppserver.onincoming(conn, data)
+	local session = sessions[conn];
+	if session then
+		session.data(data);
 	end
 end
 	
@@ -190,12 +201,7 @@
 	session.direction = "outgoing";
 	sessions[conn] = session;
 	
-	session.reset_stream = session_reset_stream;
-	session.close = session_close;
-	session_reset_stream(session); -- Initialise, ready for use
-	
-	--local function handleerr(err) print("Traceback:", err, debug.traceback()); end
-	--session.stanza_dispatch = function (stanza) return select(2, xpcall(function () return core_process_stanza(session, stanza); end, handleerr));  end
+	initialize_session(session);
 end
 
 connlisteners_register("xmppserver", xmppserver);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/adhoc/adhoc.lib.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,85 @@
+-- Copyright (C) 2009-2010 Florian Zeitz
+--
+-- This file is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local st, uuid = require "util.stanza", require "util.uuid";
+
+local xmlns_cmd = "http://jabber.org/protocol/commands";
+
+local states = {}
+
+local _M = {};
+
+function _cmdtag(desc, status, sessionid, action)
+	local cmd = st.stanza("command", { xmlns = xmlns_cmd, node = desc.node, status = status });
+	if sessionid then cmd.attr.sessionid = sessionid; end
+	if action then cmd.attr.action = action; end
+
+	return cmd;
+end
+
+function _M.new(name, node, handler, permission)
+	return { name = name, node = node, handler = handler, cmdtag = _cmdtag, permission = (permission or "user") };
+end
+
+function _M.handle_cmd(command, origin, stanza)
+	local sessionid = stanza.tags[1].attr.sessionid or uuid.generate();
+	local dataIn = {};
+	dataIn.to = stanza.attr.to;
+	dataIn.from = stanza.attr.from;
+	dataIn.action = stanza.tags[1].attr.action or "execute";
+	dataIn.form = stanza.tags[1]:child_with_ns("jabber:x:data");
+
+	local data, state = command:handler(dataIn, states[sessionid]);
+	states[sessionid] = state;
+	local stanza = st.reply(stanza);
+	if data.status == "completed" then
+		states[sessionid] = nil;
+		cmdtag = command:cmdtag("completed", sessionid);
+	elseif data.status == "canceled" then
+		states[sessionid] = nil;
+		cmdtag = command:cmdtag("canceled", sessionid);
+	elseif data.status == "error" then
+		states[sessionid] = nil;
+		stanza = st.error_reply(stanza, data.error.type, data.error.condition, data.error.message);
+		origin.send(stanza);
+		return true;
+	else 
+		cmdtag = command:cmdtag("executing", sessionid);
+	end
+
+	for name, content in pairs(data) do
+		if name == "info" then
+			cmdtag:tag("note", {type="info"}):text(content):up();
+		elseif name == "warn" then
+			cmdtag:tag("note", {type="warn"}):text(content):up();
+		elseif name == "error" then
+			cmdtag:tag("note", {type="error"}):text(content.message):up();
+		elseif name =="actions" then
+			local actions = st.stanza("actions");
+			for _, action in ipairs(content) do
+				if (action == "prev") or (action == "next") or (action == "complete") then
+					actions:tag(action):up();
+				else
+					module:log("error", 'Command "'..command.name..
+						'" at node "'..command.node..'" provided an invalid action "'..action..'"');
+				end
+			end
+			cmdtag:add_child(actions);
+		elseif name == "form" then
+			cmdtag:add_child(content:form());
+		elseif name == "result" then
+			cmdtag:add_child(content.layout:form(content.data, "result"));
+		elseif name == "other" then
+			cmdtag:add_child(content);
+		end
+	end
+	stanza:add_child(cmdtag);
+	origin.send(stanza);
+
+	return true;
+end
+
+return _M;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/adhoc/mod_adhoc.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,76 @@
+-- Copyright (C) 2009 Thilo Cestonaro
+-- 
+-- This file is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local st = require "util.stanza";
+local is_admin = require "core.usermanager".is_admin;
+local adhoc_handle_cmd = module:require "adhoc".handle_cmd;
+local xmlns_cmd = "http://jabber.org/protocol/commands";
+local xmlns_disco = "http://jabber.org/protocol/disco";
+local commands = {};
+
+module:add_feature(xmlns_cmd);
+
+module:hook("iq/host/"..xmlns_disco.."#items:query", function (event)
+	local origin, stanza = event.origin, event.stanza;
+	-- TODO: Is this correct, or should is_admin be changed?
+	local privileged = is_admin(stanza.attr.from)
+	    or is_admin(stanza.attr.from, stanza.attr.to); 
+	if stanza.attr.type == "get" and stanza.tags[1].attr.node
+	    and stanza.tags[1].attr.node == xmlns_cmd then
+		reply = st.reply(stanza);
+		reply:tag("query", { xmlns = xmlns_disco.."#items",
+		    node = xmlns_cmd });
+		for node, command in pairs(commands) do
+			if (command.permission == "admin" and privileged)
+			    or (command.permission == "user") then
+				reply:tag("item", { name = command.name,
+				    node = node, jid = module:get_host() });
+				reply:up();
+			end
+		end
+		origin.send(reply);
+		return true;
+	end
+end, 500);
+
+module:hook("iq/host", function (event)
+	local origin, stanza = event.origin, event.stanza;
+	if stanza.attr.type == "set" and stanza.tags[1]
+	    and stanza.tags[1].name == "command" then 
+		local node = stanza.tags[1].attr.node
+		-- TODO: Is this correct, or should is_admin be changed?
+		local privileged = is_admin(event.stanza.attr.from)
+		    or is_admin(stanza.attr.from, stanza.attr.to);
+		if commands[node] then
+			if commands[node].permission == "admin"
+			    and not privileged then
+				origin.send(st.error_reply(stanza, "auth", "forbidden", "You don't have permission to execute this command"):up()
+				    :add_child(commands[node]:cmdtag("canceled")
+					:tag("note", {type="error"}):text("You don't have permission to execute this command")));
+				return true
+			end
+			-- User has permission now execute the command
+			return adhoc_handle_cmd(commands[node], origin, stanza);
+		end
+	end
+end, 500);
+
+local function handle_item_added(item)
+	commands[item.node] = item;
+end
+
+module:hook("item-added/adhoc", function (event)
+	return handle_item_added(event.item);
+end, 500);
+
+module:hook("item-removed/adhoc", function (event)
+	commands[event.item.node] = nil;
+end, 500);
+
+-- Pick up any items that are already added
+for _, item in ipairs(module:get_host_items("adhoc")) do
+	handle_item_added(item);
+end
--- a/plugins/mod_announce.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_announce.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -11,6 +11,30 @@
 local is_admin = require "core.usermanager".is_admin;
 local admins = set.new(config.get(module:get_host(), "core", "admins"));
 
+function send_to_online(message, server)
+	if server then
+		sessions = { [server] = hosts[server] };
+	else
+		sessions = hosts;
+	end
+
+	local c = 0;
+	for hostname, host_session in pairs(sessions) do
+		if host_session.sessions then
+			message.attr.from = hostname;
+			for username in pairs(host_session.sessions) do
+				c = c + 1;
+				message.attr.to = username.."@"..hostname;
+				core_post_stanza(host_session, message);
+			end
+		end
+	end
+
+	return c;
+end
+
+
+-- Old <message>-based jabberd-style announcement sending
 function handle_announcement(data)
 	local origin, stanza = data.origin, data.stanza;
 	local host, resource = select(2, jid.split(stanza.attr.to));
@@ -31,15 +55,47 @@
 	message.attr.type = "headline";
 	message.attr.from = host;
 	
-	local c = 0;
-	for user in pairs(host_session.sessions) do
-		c = c + 1;
-		message.attr.to = user.."@"..host;
-		core_post_stanza(host_session, message);
-	end
-	
+	local c = send_to_online(message, host);
 	module:log("info", "Announcement sent to %d online users", c);
 	return true;
 end
+module:hook("message/host", handle_announcement);
 
-module:hook("message/host", handle_announcement);
+-- Ad-hoc command (XEP-0133)
+local dataforms_new = require "util.dataforms".new;
+local announce_layout = dataforms_new{
+	title = "Making an Announcement";
+	instructions = "Fill out this form to make an announcement to all\nactive users of this service.";
+
+	{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/admin" };
+	{ name = "subject", type = "text-single", label = "Subject" };
+	{ name = "announcement", type = "text-multi", required = true, label = "Announcement" };
+};
+
+function announce_handler(self, data, state)
+	if state then
+		if data.action == "cancel" then
+			return { status = "canceled" };
+		end
+
+		local fields = announce_layout:data(data.form);
+
+		module:log("info", "Sending server announcement to all online users");
+		local message = st.message({type = "headline"}, fields.announcement):up()
+			:tag("subject"):text(fields.subject or "Announcement");
+		
+		local count = send_to_online(message, data.to);
+		
+		module:log("info", "Announcement sent to %d online users", count);
+		return { status = "completed", info = ("Announcement sent to %d online users"):format(count) };
+	else
+		return { status = "executing", form = announce_layout }, "executing";
+	end
+
+	return true;
+end
+
+local adhoc_new = module:require "adhoc".new;
+local announce_desc = adhoc_new("Send Announcement to Online Users", "http://jabber.org/protocol/admin#announce", announce_handler, "admin");
+module:add_item("adhoc", announce_desc);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_auth_anonymous.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,85 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- Copyright (C) 2010 Jeff Mitchell
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local log = require "util.logger".init("usermanager");
+local type = type;
+local ipairs = ipairs;
+local jid_bare = require "util.jid".bare;
+local config = require "core.configmanager";
+local new_sasl = require "util.sasl".new;
+local datamanager = require "util.datamanager";
+
+function new_default_provider(host)
+	local provider = { name = "anonymous" };
+
+	function provider.test_password(username, password)
+		return nil, "Password based auth not supported.";
+	end
+
+	function provider.get_password(username)
+		return nil, "Password not available.";
+	end
+	
+	function provider.set_password(username, password)
+		return nil, "Password based auth not supported.";
+	end
+
+	function provider.user_exists(username)
+		return nil, "Only anonymous users are supported."; -- FIXME check if anonymous user is connected?
+	end
+
+	function provider.create_user(username, password)
+		return nil, "Account creation/modification not supported.";
+	end
+
+	function provider.get_sasl_handler()
+		local realm = module:get_option("sasl_realm") or module.host;
+		local anonymous_authentication_profile = {
+			anonymous = function(username, realm)
+				return true; -- for normal usage you should always return true here
+			end
+		};
+		return new_sasl(realm, anonymous_authentication_profile);
+	end
+
+	function provider.is_admin(jid)
+		local admins = config.get(host, "core", "admins");
+		if admins ~= config.get("*", "core", "admins") and type(admins) == "table" then
+			jid = jid_bare(jid);
+			for _,admin in ipairs(admins) do
+				if admin == jid then return true; end
+			end
+		elseif admins then
+			log("error", "Option 'admins' for host '%s' is not a table", host);
+		end
+		return is_admin(jid); -- Test whether it's a global admin instead
+	end
+	return provider;
+end
+
+local function dm_callback(username, host, datastore, data)
+	if host == module.host then
+		return false;
+	end
+	return username, host, datastore, data;
+end
+local host = hosts[module.host];
+local _saved_disallow_s2s = host.disallow_s2s;
+function module.load()
+	_saved_disallow_s2s = host.disallow_s2s;
+	host.disallow_s2s = module:get_option("disallow_s2s") ~= false;
+	datamanager.add_callback(dm_callback);
+end
+function module.unload()
+	host.disallow_s2s = _saved_disallow_s2s;
+	datamanager.remove_callback(dm_callback);
+end
+
+module:add_item("auth-provider", new_default_provider(module.host));
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_auth_cyrus.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,77 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- Copyright (C) 2010 Jeff Mitchell
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local log = require "util.logger".init("usermanager");
+local type = type;
+local ipairs = ipairs;
+local jid_bare = require "util.jid".bare;
+local config = require "core.configmanager";
+
+local cyrus_service_realm = module:get_option("cyrus_service_realm");
+local cyrus_service_name = module:get_option("cyrus_service_name");
+local cyrus_application_name = module:get_option("cyrus_application_name");
+
+prosody.unlock_globals(); --FIXME: Figure out why this is needed and
+						  -- why cyrussasl isn't caught by the sandbox
+local cyrus_new = require "util.sasl_cyrus".new;
+prosody.lock_globals();
+local new_sasl = function(realm)
+	return cyrus_new(
+		cyrus_service_realm or realm,
+		cyrus_service_name or "xmpp",
+		cyrus_application_name or "prosody"
+	);
+end
+
+function new_default_provider(host)
+	local provider = { name = "cyrus" };
+	log("debug", "initializing default authentication provider for host '%s'", host);
+
+	function provider.test_password(username, password)
+		return nil, "Legacy auth not supported with Cyrus SASL.";
+	end
+
+	function provider.get_password(username)
+		return nil, "Passwords unavailable for Cyrus SASL.";
+	end
+	
+	function provider.set_password(username, password)
+		return nil, "Passwords unavailable for Cyrus SASL.";
+	end
+
+	function provider.user_exists(username)
+		return true;
+	end
+
+	function provider.create_user(username, password)
+		return nil, "Account creation/modification not available with Cyrus SASL.";
+	end
+
+	function provider.get_sasl_handler()
+		local realm = module:get_option("sasl_realm") or module.host;
+		return new_sasl(realm);
+	end
+
+	function provider.is_admin(jid)
+		local admins = config.get(host, "core", "admins");
+		if admins ~= config.get("*", "core", "admins") and type(admins) == "table" then
+			jid = jid_bare(jid);
+			for _,admin in ipairs(admins) do
+				if admin == jid then return true; end
+			end
+		elseif admins then
+			log("error", "Option 'admins' for host '%s' is not a table", host);
+		end
+		return is_admin(jid); -- Test whether it's a global admin instead
+	end
+	return provider;
+end
+
+module:add_item("auth-provider", new_default_provider(module.host));
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_auth_internal.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,110 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- Copyright (C) 2010 Jeff Mitchell
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local datamanager = require "util.datamanager";
+local log = require "util.logger".init("usermanager");
+local type = type;
+local error = error;
+local ipairs = ipairs;
+local hashes = require "util.hashes";
+local jid_bare = require "util.jid".bare;
+local config = require "core.configmanager";
+local usermanager = require "core.usermanager";
+local new_sasl = require "util.sasl".new;
+local nodeprep = require "util.encodings".stringprep.nodeprep;
+local hosts = hosts;
+
+local prosody = _G.prosody;
+
+local is_cyrus = usermanager.is_cyrus;
+
+function new_default_provider(host)
+	local provider = { name = "internal" };
+	log("debug", "initializing default authentication provider for host '%s'", host);
+
+	function provider.test_password(username, password)
+		log("debug", "test password '%s' for user %s at host %s", password, username, module.host);
+		if is_cyrus(host) then return nil, "Legacy auth not supported with Cyrus SASL."; end
+		local credentials = datamanager.load(username, host, "accounts") or {};
+	
+		if password == credentials.password then
+			return true;
+		else
+			return nil, "Auth failed. Invalid username or password.";
+		end
+	end
+
+	function provider.get_password(username)
+		log("debug", "get_password for username '%s' at host '%s'", username, module.host);
+		if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
+		return (datamanager.load(username, host, "accounts") or {}).password;
+	end
+	
+	function provider.set_password(username, password)
+		if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
+		local account = datamanager.load(username, host, "accounts");
+		if account then
+			account.password = password;
+			return datamanager.store(username, host, "accounts", account);
+		end
+		return nil, "Account not available.";
+	end
+
+	function provider.user_exists(username)
+		if is_cyrus(host) then return true; end
+		local account = datamanager.load(username, host, "accounts");
+		if not account then
+			log("debug", "account not found for username '%s' at host '%s'", username, module.host);
+			return nil, "Auth failed. Invalid username";
+		end
+		if account.password == nil or string.len(account.password) == 0 then
+			log("debug", "account password not set or zero-length for username '%s' at host '%s'", username, module.host);
+			return nil, "Auth failed. Password invalid.";
+		end
+		return true;
+	end
+
+	function provider.create_user(username, password)
+		if is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
+		return datamanager.store(username, host, "accounts", {password = password});
+	end
+
+	function provider.get_sasl_handler()
+		local realm = module:get_option("sasl_realm") or module.host;
+		local getpass_authentication_profile = {
+			plain = function(username, realm)
+				local prepped_username = nodeprep(username);
+				if not prepped_username then
+					log("debug", "NODEprep failed on username: %s", username);
+					return "", nil;
+				end
+				local password = usermanager.get_password(prepped_username, realm);
+				if not password then
+					return "", nil;
+				end
+				return password, true;
+			end
+		};
+		return new_sasl(realm, getpass_authentication_profile);
+	end
+
+	function provider.is_admin(jid)
+		local admins = module:get_option_array("admins");
+		if admins ~= config.get("*", "core", "admins") and type(admins) == "table" then
+			jid = jid_bare(jid);
+			for _,admin in ipairs(admins) do
+				if admin == jid then return true; end
+			end
+		end
+	end
+	return provider;
+end
+
+module:add_item("auth-provider", new_default_provider(module.host));
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_auth_internal_hashed.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,174 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- Copyright (C) 2010 Jeff Mitchell
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local datamanager = require "util.datamanager";
+local log = require "util.logger".init("usermanager");
+local type = type;
+local error = error;
+local ipairs = ipairs;
+local hashes = require "util.hashes";
+local jid_bare = require "util.jid".bare;
+local getAuthenticationDatabaseSHA1 = require "util.sasl.scram".getAuthenticationDatabaseSHA1;
+local config = require "core.configmanager";
+local usermanager = require "core.usermanager";
+local generate_uuid = require "util.uuid".generate;
+local new_sasl = require "util.sasl".new;
+local nodeprep = require "util.encodings".stringprep.nodeprep;
+local hosts = hosts;
+
+-- TODO: remove these two lines in near future
+local hmac_sha1 = require "util.hmac".sha1;
+local sha1 = require "util.hashes".sha1;
+
+local prosody = _G.prosody;
+
+local is_cyrus = usermanager.is_cyrus;
+
+-- Default; can be set per-user
+local iteration_count = 4096;
+
+function new_hashpass_provider(host)
+	local provider = { name = "internal_hashed" };
+	log("debug", "initializing hashpass authentication provider for host '%s'", host);
+
+	function provider.test_password(username, password)
+		if is_cyrus(host) then return nil, "Legacy auth not supported with Cyrus SASL."; end
+		local credentials = datamanager.load(username, host, "accounts") or {};
+	
+		if credentials.password ~= nil and string.len(credentials.password) ~= 0 then
+			if credentials.password ~= password then
+				return nil, "Auth failed. Provided password is incorrect.";
+			end
+
+			if provider.set_password(username, credentials.password) == nil then
+				return nil, "Auth failed. Could not set hashed password from plaintext.";
+			else
+				return true;
+			end
+		end
+
+		if credentials.iteration_count == nil or credentials.salt == nil or string.len(credentials.salt) == 0 then
+			return nil, "Auth failed. Stored salt and iteration count information is not complete.";
+		end
+		
+		-- convert hexpass to stored_key and server_key
+		-- TODO: remove this in near future
+		if credentials.hashpass then
+			local salted_password = credentials.hashpass:gsub("..", function(x) return string.char(tonumber(x, 16)); end);
+			credentials.stored_key = sha1(hmac_sha1(salted_password, "Client Key")):gsub(".", function (c) return ("%02x"):format(c:byte()); end);
+			credentials.server_key = hmac_sha1(salted_password, "Server Key"):gsub(".", function (c) return ("%02x"):format(c:byte()); end);
+			credentials.hashpass = nil
+			datamanager.store(username, host, "accounts", credentials);
+		end
+		
+		local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, credentials.salt, credentials.iteration_count);
+		
+		local stored_key_hex = stored_key:gsub(".", function (c) return ("%02x"):format(c:byte()); end);
+		local server_key_hex = server_key:gsub(".", function (c) return ("%02x"):format(c:byte()); end);
+		
+		if valid and stored_key_hex == credentials.stored_key and server_key_hex == credentials.server_key then
+			return true;
+		else
+			return nil, "Auth failed. Invalid username, password, or password hash information.";
+		end
+	end
+
+	function provider.set_password(username, password)
+		if is_cyrus(host) then return nil, "Passwords unavailable for Cyrus SASL."; end
+		local account = datamanager.load(username, host, "accounts");
+		if account then
+			account.salt = account.salt or generate_uuid();
+			account.iteration_count = account.iteration_count or iteration_count;
+			local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, account.salt, account.iteration_count);
+			local stored_key_hex = stored_key:gsub(".", function (c) return ("%02x"):format(c:byte()); end);
+			local server_key_hex = server_key:gsub(".", function (c) return ("%02x"):format(c:byte()); end);
+			
+			account.stored_key = stored_key_hex
+			account.server_key = server_key_hex
+
+			account.password = nil;
+			return datamanager.store(username, host, "accounts", account);
+		end
+		return nil, "Account not available.";
+	end
+
+	function provider.user_exists(username)
+		if is_cyrus(host) then return true; end
+		local account = datamanager.load(username, host, "accounts");
+		if not account then
+			log("debug", "account not found for username '%s' at host '%s'", username, module.host);
+			return nil, "Auth failed. Invalid username";
+		end
+		if (account.hashpass == nil or string.len(account.hashpass) == 0) and (account.password == nil or string.len(account.password) == 0) then
+			log("debug", "account password not set or zero-length for username '%s' at host '%s'", username, module.host);
+			return nil, "Auth failed. Password invalid.";
+		end
+		return true;
+	end
+
+	function provider.create_user(username, password)
+		if is_cyrus(host) then return nil, "Account creation/modification not available with Cyrus SASL."; end
+		local salt = generate_uuid();
+		local valid, stored_key, server_key = saltedPasswordSHA1(password, salt, iteration_count);
+		local stored_key_hex = stored_key:gsub(".", function (c) return ("%02x"):format(c:byte()); end);
+		local server_key_hex = server_key:gsub(".", function (c) return ("%02x"):format(c:byte()); end);
+		return datamanager.store(username, host, "accounts", {stored_key = stored_key_hex, server_key = server_key_hex, salt = salt, iteration_count = iteration_count});
+	end
+
+	function provider.get_sasl_handler()
+		local realm = module:get_option("sasl_realm") or module.host;
+		local testpass_authentication_profile = {
+			plain_test = function(username, password, realm)
+				local prepped_username = nodeprep(username);
+				if not prepped_username then
+					log("debug", "NODEprep failed on username: %s", username);
+					return "", nil;
+				end
+				return usermanager.test_password(prepped_username, password, realm), true;
+			end,
+			scram_sha_1 = function(username, realm)
+				local credentials = datamanager.load(username, host, "accounts") or {};
+				if credentials.password then
+					usermanager.set_password(username, credentials.password, host);
+					credentials = datamanager.load(username, host, "accounts") or {};
+				end
+				
+				-- convert hexpass to stored_key and server_key
+				-- TODO: remove this in near future
+				if credentials.hashpass then
+					local salted_password = credentials.hashpass:gsub("..", function(x) return string.char(tonumber(x, 16)); end);
+					credentials.stored_key = sha1(hmac_sha1(salted_password, "Client Key")):gsub(".", function (c) return ("%02x"):format(c:byte()); end);
+					credentials.server_key = hmac_sha1(salted_password, "Server Key"):gsub(".", function (c) return ("%02x"):format(c:byte()); end);
+					credentials.hashpass = nil
+					datamanager.store(username, host, "accounts", credentials);
+				end
+				
+				local stored_key, server_key, iteration_count, salt = credentials.stored_key, credentials.server_key, credentials.iteration_count, credentials.salt;
+				stored_key = stored_key and stored_key:gsub("..", function(x) return string.char(tonumber(x, 16)); end);
+				server_key = server_key and server_key:gsub("..", function(x) return string.char(tonumber(x, 16)); end);
+				return stored_key, server_key, iteration_count, salt, true;
+			end
+		};
+		return new_sasl(realm, testpass_authentication_profile);
+	end
+
+	function provider.is_admin(jid)
+		local admins = module:get_option_array("admins");
+		if admins ~= config.get("*", "core", "admins") and type(admins) == "table" then
+			jid = jid_bare(jid);
+			for _,admin in ipairs(admins) do
+				if admin == jid then return true; end
+			end
+		end
+	end
+	return provider;
+end
+
+module:add_item("auth-provider", new_hashpass_provider(module.host));
+
--- a/plugins/mod_compression.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_compression.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -14,6 +14,7 @@
 local xmlns_compression_protocol = "http://jabber.org/protocol/compress"
 local xmlns_stream = "http://etherx.jabber.org/streams";
 local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
+local add_filter = require "util.filters".add_filter;
 
 local compression_level = module:get_option("compression_level");
 -- if not defined assume admin wants best compression
@@ -94,43 +95,36 @@
 
 -- setup compression for a stream
 local function setup_compression(session, deflate_stream)
-	local old_send = (session.sends2s or session.send);
-	
-	local new_send = function(t)
-			--TODO: Better code injection in the sending process
-			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", "%s", tostring(compressed));
-				return;
-			end
-			session.conn:write(compressed);
-		end;
-	
-	if session.sends2s then session.sends2s = new_send
-	elseif session.send then session.send = new_send end
+	add_filter(session, "bytes/out", function(t)
+		local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
+		if status == false then
+			module:log("warn", "%s", tostring(compressed));
+			session:close({
+				condition = "undefined-condition";
+				text = compressed;
+				extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+			});
+			return;
+		end
+		return compressed;
+	end);	
 end
 
 -- setup decompression for a stream
 local function setup_decompression(session, inflate_stream)
-	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", "%s", tostring(decompressed));
-				return;
-			end
-			old_data(conn, decompressed);
-		end;
+	add_filter(session, "bytes/in", function(data)
+		local status, decompressed, eof = pcall(inflate_stream, data);
+		if status == false then
+			module:log("warn", "%s", tostring(decompressed));
+			session:close({
+				condition = "undefined-condition";
+				text = decompressed;
+				extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+			});
+			return;
+		end
+		return decompressed;
+	end);
 end
 
 module:add_handler({"s2sout_unauthed", "s2sout"}, "compressed", xmlns_compression_protocol, 
@@ -148,12 +142,6 @@
 				
 			-- setup decompression for session.data
 			setup_decompression(session, inflate_stream);
-			local session_reset_stream = session.reset_stream;
-			session.reset_stream = function(session)
-					session_reset_stream(session);
-					setup_decompression(session, inflate_stream);
-					return true;
-				end;
 			session:reset_stream();
 			local default_stream_attr = {xmlns = "jabber:server", ["xmlns:stream"] = "http://etherx.jabber.org/streams",
 										["xmlns:db"] = 'jabber:server:dialback', version = "1.0", to = session.to_host, from = session.from_host};
@@ -195,12 +183,6 @@
 				-- setup decompression for session.data
 				setup_decompression(session, inflate_stream);
 				
-				local session_reset_stream = session.reset_stream;
-				session.reset_stream = function(session)
-						session_reset_stream(session);
-						setup_decompression(session, inflate_stream);
-						return true;
-					end;
 				session.compressed = true;
 			elseif method then
 				session.log("debug", "%s compression selected, but we don't support it.", tostring(method));
--- a/plugins/mod_groups.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_groups.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -29,6 +29,9 @@
 			if jid ~= bare_jid then
 				if not roster[jid] then roster[jid] = {}; end
 				roster[jid].subscription = "both";
+				if groups[group_name][jid] then
+					roster[jid].name = groups[group_name][jid];
+				end
 				if not roster[jid].groups then
 					roster[jid].groups = { [group_name] = true };
 				end
@@ -100,10 +103,13 @@
 			groups[curr_group] = groups[curr_group] or {};
 		else
 			-- Add JID
-			local jid = jid_prep(line:match("%S+"));
+			local entryjid, name = line:match("([^=]*)=?(.*)");
+			module:log("debug", "entryjid = '%s', name = '%s'", entryjid, name);
+			local jid;
+			jid = jid_prep(entryjid:match("%S+"));
 			if jid then
 				module:log("debug", "New member of %s: %s", tostring(curr_group), tostring(jid));
-				groups[curr_group][jid] = true;
+				groups[curr_group][jid] = name or false;
 				members[jid] = members[jid] or {};
 				members[jid][#members[jid]+1] = curr_group;
 			end
--- a/plugins/mod_iq.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_iq.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -9,7 +9,6 @@
 
 local st = require "util.stanza";
 local jid_split = require "util.jid".split;
-local user_exists = require "core.usermanager".user_exists;
 
 local full_sessions = full_sessions;
 local bare_sessions = bare_sessions;
@@ -34,16 +33,6 @@
 	-- IQ to bare JID recieved
 	local origin, stanza = data.origin, data.stanza;
 
-	local to = stanza.attr.to;
-	if to and not bare_sessions[to] then -- quick check for account existance
-		local node, host = jid_split(to);
-		if not user_exists(node, host) then -- full check for account existance
-			if stanza.attr.type == "get" or stanza.attr.type == "set" then
-				origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
-			end
-			return true;
-		end
-	end
 	-- TODO fire post processing events
 	if stanza.attr.type == "get" or stanza.attr.type == "set" then
 		return module:fire_event("iq/bare/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name, data);
--- a/plugins/mod_legacyauth.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_legacyauth.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -50,7 +50,7 @@
 				username = nodeprep(username);
 				resource = resourceprep(resource)
 				local reply = st.reply(stanza);
-				if usermanager.validate_credentials(session.host, username, password) then
+				if usermanager.test_password(username, password, session.host) then
 					-- Authentication successful!
 					local success, err = sessionmanager.make_authenticated(session, username);
 					if success then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_motd.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,25 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- Copyright (C) 2010 Jeff Mitchell
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local host = module:get_host();
+local motd_text = module:get_option("motd_text") or "MOTD: (blank)";
+local motd_jid = module:get_option("motd_jid") or host;
+
+local st = require "util.stanza";
+
+module:hook("resource-bind",
+	function (event)
+		local session = event.session;
+		local motd_stanza =
+			st.message({ to = session.username..'@'..session.host, from = motd_jid })
+				:tag("body"):text(motd_text);
+		core_route_stanza(hosts[host], motd_stanza);
+		module:log("debug", "MOTD send to user %s@%s", session.username, session.host);
+
+end);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_offline.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,56 @@
+-- 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 st = require "util.stanza";
+local datetime = require "util.datetime";
+local ipairs = ipairs;
+local jid_split = require "util.jid".split;
+
+module:add_feature("msgoffline");
+
+module:hook("message/offline/store", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	local to = stanza.attr.to;
+	local node, host;
+	if to then
+		node, host = jid_split(to)
+	else
+		node, host = origin.username, origin.host;
+	end
+	
+	stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy();
+	local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza));
+	stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
+	
+	return true;
+end);
+
+module:hook("message/offline/broadcast", function(event)
+	local origin = event.origin;
+	local node, host = origin.username, origin.host;
+	
+	local data = datamanager.list_load(node, host, "offline");
+	if not data then return true; end
+	for _, stanza in ipairs(data) do
+		stanza = st.deserialize(stanza);
+		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203
+		stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated)
+		stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
+		origin.send(stanza);
+	end
+	return true;
+end);
+
+module:hook("message/offline/delete", function(event)
+	local origin = event.origin;
+	local node, host = origin.username, origin.host;
+
+	return datamanager.list_store(node, host, "offline", nil);
+end);
--- a/plugins/mod_pep.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_pep.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -16,7 +16,6 @@
 local pairs, ipairs = pairs, ipairs;
 local next = next;
 local type = type;
-local load_roster = require "core.rostermanager".load_roster;
 local sha1 = require "util.hashes".sha1;
 local base64 = require "util.encodings".base64.encode;
 
@@ -40,8 +39,8 @@
 local function subscription_presence(user_bare, recipient)
 	local recipient_bare = jid_bare(recipient);
 	if (recipient_bare == user_bare) then return true end
-	local item = load_roster(jid_split(user_bare))[recipient_bare];
-	return item and (item.subscription == 'from' or item.subscription == 'both');
+	local username, host = jid_split(user_bare);
+	return is_contact_subscribed(username, host, recipient_bare);
 end
 
 local function publish(session, node, id, item)
@@ -118,27 +117,32 @@
 	-- inbound presence to bare JID recieved
 	local origin, stanza = event.origin, event.stanza;
 	local user = stanza.attr.to or (origin.username..'@'..origin.host);
+	local t = stanza.attr.type;
 
-	if not stanza.attr.to or subscription_presence(user, stanza.attr.from) then
-		local recipient = stanza.attr.from;
-		local current = recipients[user] and recipients[user][recipient];
-		local hash = get_caps_hash_from_presence(stanza, current);
-		if current == hash then return; end
-		if not hash then
-			if recipients[user] then recipients[user][recipient] = nil; end
-		else
-			recipients[user] = recipients[user] or {};
-			if hash_map[hash] then
-				recipients[user][recipient] = hash_map[hash];
-				publish_all(user, recipient, origin);
+	if not t then -- available presence
+		if not stanza.attr.to or subscription_presence(user, stanza.attr.from) then
+			local recipient = stanza.attr.from;
+			local current = recipients[user] and recipients[user][recipient];
+			local hash = get_caps_hash_from_presence(stanza, current);
+			if current == hash then return; end
+			if not hash then
+				if recipients[user] then recipients[user][recipient] = nil; end
 			else
-				recipients[user][recipient] = hash;
-				origin.send(
-					st.stanza("iq", {from=stanza.attr.to, to=stanza.attr.from, id="disco", type="get"})
-						:query("http://jabber.org/protocol/disco#info")
-				);
+				recipients[user] = recipients[user] or {};
+				if hash_map[hash] then
+					recipients[user][recipient] = hash_map[hash];
+					publish_all(user, recipient, origin);
+				else
+					recipients[user][recipient] = hash;
+					origin.send(
+						st.stanza("iq", {from=stanza.attr.to, to=stanza.attr.from, id="disco", type="get"})
+							:query("http://jabber.org/protocol/disco#info")
+					);
+				end
 			end
 		end
+	elseif t == "unavailable" then
+		if recipients[user] then recipients[user][stanza.attr.from] = nil; end
 	end
 end, 10);
 
--- a/plugins/mod_posix.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_posix.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -54,16 +54,16 @@
 	end);
 
 -- Don't even think about it!
-module:add_event_hook("server-starting", function ()
-		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
+if not prosody.start_time then -- server-starting
+	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
+end
 
 local pidfile;
 local pidfile_handle;
@@ -141,7 +141,9 @@
 			write_pidfile();
 		end
 	end
-	module:add_event_hook("server-starting", daemonize_server);
+	if not prosody.start_time then -- server-starting
+		daemonize_server();
+	end
 else
 	-- Not going to daemonize, so write the pid of this process
 	write_pidfile();
--- a/plugins/mod_presence.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_presence.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -24,20 +24,6 @@
 local sessionmanager = require "core.sessionmanager";
 local offlinemanager = require "core.offlinemanager";
 
-local _core_route_stanza = core_route_stanza;
-local core_route_stanza;
-function core_route_stanza(origin, stanza)
-	if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
-		local node, host = jid_split(stanza.attr.to);
-		host = hosts[host];
-		if node and host and host.type == "local" then
-			handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
-			return;
-		end
-	end
-	_core_route_stanza(origin, stanza);
-end
-
 local function select_top_resources(user)
 	local priority = 0;
 	local recipients = {};
@@ -64,7 +50,7 @@
 
 local ignore_presence_priority = module:get_option("ignore_presence_priority");
 
-function handle_normal_presence(origin, stanza, core_route_stanza)
+function handle_normal_presence(origin, stanza)
 	if ignore_presence_priority then
 		local priority = stanza:child_with_name("priority");
 		if priority and priority[1] ~= "0" then
@@ -82,13 +68,13 @@
 	for _, res in pairs(user and user.sessions or NULL) do -- broadcast to all resources
 		if res ~= origin and res.presence then -- to resource
 			stanza.attr.to = res.full_jid;
-			core_route_stanza(origin, stanza);
+			core_post_stanza(origin, stanza, true);
 		end
 	end
 	for jid, item in pairs(roster) do -- broadcast to all interested contacts
 		if item.subscription == "both" or item.subscription == "from" then
 			stanza.attr.to = jid;
-			core_route_stanza(origin, stanza);
+			core_post_stanza(origin, stanza, true);
 		end
 	end
 	if stanza.attr.type == nil and not origin.presence then -- initial presence
@@ -97,13 +83,13 @@
 		for jid, item in pairs(roster) do -- probe all contacts we are subscribed to
 			if item.subscription == "both" or item.subscription == "to" then
 				probe.attr.to = jid;
-				core_route_stanza(origin, probe);
+				core_post_stanza(origin, probe, true);
 			end
 		end
 		for _, res in pairs(user and user.sessions or NULL) do -- broadcast from all available resources
 			if res ~= origin and res.presence then
 				res.presence.attr.to = origin.full_jid;
-				core_route_stanza(res, res.presence);
+				core_post_stanza(res, res.presence, true);
 				res.presence.attr.to = nil;
 			end
 		end
@@ -116,7 +102,7 @@
 		for jid, item in pairs(roster) do -- resend outgoing subscription requests
 			if item.ask then
 				request.attr.to = jid;
-				core_route_stanza(origin, request);
+				core_post_stanza(origin, request, true);
 			end
 		end
 		local offline = offlinemanager.load(node, host);
@@ -136,7 +122,7 @@
 		if origin.directed then
 			for jid in pairs(origin.directed) do
 				stanza.attr.to = jid;
-				core_route_stanza(origin, stanza);
+				core_post_stanza(origin, stanza, true);
 			end
 			origin.directed = nil;
 		end
@@ -159,7 +145,7 @@
 	stanza.attr.to = nil; -- reset it
 end
 
-function send_presence_of_available_resources(user, host, jid, recipient_session, core_route_stanza, stanza)
+function send_presence_of_available_resources(user, host, jid, recipient_session, stanza)
 	local h = hosts[host];
 	local count = 0;
 	if h and h.type == "local" then
@@ -170,7 +156,7 @@
 				if pres then
 					if stanza then pres = stanza; pres.attr.from = session.full_jid; end
 					pres.attr.to = jid;
-					core_route_stanza(session, pres);
+					core_post_stanza(session, pres, true);
 					pres.attr.to = nil;
 					count = count + 1;
 				end
@@ -181,26 +167,29 @@
 	return count;
 end
 
-function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)
+function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare)
 	local node, host = jid_split(from_bare);
-	if to_bare == origin.username.."@"..origin.host then return; end -- No self contacts
+	if to_bare == from_bare then return; end -- No self contacts
 	local st_from, st_to = stanza.attr.from, stanza.attr.to;
 	stanza.attr.from, stanza.attr.to = from_bare, to_bare;
 	log("debug", "outbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);
-	if stanza.attr.type == "subscribe" then
+	if stanza.attr.type == "probe" then
+		stanza.attr.from, stanza.attr.to = st_from, st_to;
+		return;
+	elseif stanza.attr.type == "subscribe" then
 		-- 1. route stanza
 		-- 2. roster push (subscription = none, ask = subscribe)
 		if rostermanager.set_contact_pending_out(node, host, to_bare) then
 			rostermanager.roster_push(node, host, to_bare);
 		end -- else file error
-		core_route_stanza(origin, stanza);
+		core_post_stanza(origin, stanza);
 	elseif stanza.attr.type == "unsubscribe" then
 		-- 1. route stanza
 		-- 2. roster push (subscription = none or from)
 		if rostermanager.unsubscribe(node, host, to_bare) then
 			rostermanager.roster_push(node, host, to_bare); -- FIXME do roster push when roster has in fact not changed?
 		end -- else file error
-		core_route_stanza(origin, stanza);
+		core_post_stanza(origin, stanza);
 	elseif stanza.attr.type == "subscribed" then
 		-- 1. route stanza
 		-- 2. roster_push ()
@@ -208,20 +197,21 @@
 		if rostermanager.subscribed(node, host, to_bare) then
 			rostermanager.roster_push(node, host, to_bare);
 		end
-		core_route_stanza(origin, stanza);
-		send_presence_of_available_resources(node, host, to_bare, origin, core_route_stanza);
+		core_post_stanza(origin, stanza);
+		send_presence_of_available_resources(node, host, to_bare, origin);
 	elseif stanza.attr.type == "unsubscribed" then
 		-- 1. route stanza
 		-- 2. roster push (subscription = none or to)
 		if rostermanager.unsubscribed(node, host, to_bare) then
 			rostermanager.roster_push(node, host, to_bare);
 		end
-		core_route_stanza(origin, stanza);
+		core_post_stanza(origin, stanza);
 	end
 	stanza.attr.from, stanza.attr.to = st_from, st_to;
+	return true;
 end
 
-function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)
+function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare)
 	local node, host = jid_split(to_bare);
 	local st_from, st_to = stanza.attr.from, stanza.attr.to;
 	stanza.attr.from, stanza.attr.to = from_bare, to_bare;
@@ -230,21 +220,21 @@
 	if stanza.attr.type == "probe" then
 		local result, err = rostermanager.is_contact_subscribed(node, host, from_bare);
 		if result then
-			if 0 == send_presence_of_available_resources(node, host, st_from, origin, core_route_stanza) then
-				core_route_stanza(hosts[host], st.presence({from=to_bare, to=st_from, type="unavailable"})); -- TODO send last activity
+			if 0 == send_presence_of_available_resources(node, host, st_from, origin) then
+				core_post_stanza(hosts[host], st.presence({from=to_bare, to=st_from, type="unavailable"}), true); -- TODO send last activity
 			end
 		elseif not err then
-			core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
+			core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}), true);
 		end
 	elseif stanza.attr.type == "subscribe" then
 		if rostermanager.is_contact_subscribed(node, host, from_bare) then
-			core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed
+			core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="subscribed"}), true); -- already subscribed
 			-- Sending presence is not clearly stated in the RFC, but it seems appropriate
-			if 0 == send_presence_of_available_resources(node, host, from_bare, origin, core_route_stanza) then
-				core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- TODO send last activity
+			if 0 == send_presence_of_available_resources(node, host, from_bare, origin) then
+				core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- TODO send last activity
 			end
 		else
-			core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- acknowledging receipt
+			core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- acknowledging receipt
 			if not rostermanager.is_contact_pending_in(node, host, from_bare) then
 				if rostermanager.set_contact_pending_in(node, host, from_bare) then
 					sessionmanager.send_to_available_resources(node, host, stanza);
@@ -268,6 +258,7 @@
 		end
 	end -- discard any other type
 	stanza.attr.from, stanza.attr.to = st_from, st_to;
+	return true;
 end
 
 local outbound_presence_handler = function(data)
@@ -278,12 +269,12 @@
 	if to then
 		local t = stanza.attr.type;
 		if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes
-			handle_outbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
-			return true;
+			return handle_outbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to));
 		end
 
 		local to_bare = jid_bare(to);
-		if not(origin.roster[to_bare] and (origin.roster[to_bare].subscription == "both" or origin.roster[to_bare].subscription == "from")) then -- directed presence
+		local roster = origin.roster;
+		if roster and not(roster[to_bare] and (roster[to_bare].subscription == "both" or roster[to_bare].subscription == "from")) then -- directed presence
 			origin.directed = origin.directed or {};
 			if t then -- removing from directed presence list on sending an error or unavailable
 				origin.directed[to] = nil; -- FIXME does it make more sense to add to_bare rather than to?
@@ -306,8 +297,7 @@
 	local t = stanza.attr.type;
 	if to then
 		if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes sent to bare JID
-			handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
-			return true;
+			return handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to));
 		end
 	
 		local user = bare_sessions[to];
@@ -319,7 +309,7 @@
 			end
 		end -- no resources not online, discard
 	elseif not t or t == "unavailable" then
-		handle_normal_presence(origin, stanza, core_route_stanza);
+		handle_normal_presence(origin, stanza);
 	end
 	return true;
 end);
@@ -329,8 +319,7 @@
 
 	local t = stanza.attr.type;
 	if t ~= nil and t ~= "unavailable" and t ~= "error" then -- check for subscriptions and probes sent to full JID
-		handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
-		return true;
+		return handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to));
 	end
 
 	local session = full_sessions[stanza.attr.to];
@@ -347,10 +336,10 @@
 	local from_bare = jid_bare(stanza.attr.from);
 	local t = stanza.attr.type;
 	if t == "probe" then
-		core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
+		core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
 	elseif t == "subscribe" then
-		core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id, type = "subscribed" }));
-		core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
+		core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id, type = "subscribed" }));
+		core_post_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
 	end
 	return true;
 end);
@@ -369,7 +358,7 @@
 		pres:tag("status"):text("Disconnected: "..err):up();
 		for jid in pairs(session.directed) do
 			pres.attr.to = jid;
-			core_route_stanza(session, pres);
+			core_post_stanza(session, pres, true);
 		end
 		session.directed = nil;
 	end
--- a/plugins/mod_privacy.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_privacy.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -438,7 +438,9 @@
 		 	e.stanza.attr.from = e.stanza.attr.from .. "/" .. session.resource;
 		end
 	end
-	return checkIfNeedToBeBlocked(e, session);
+	if session.username then -- FIXME do properly
+		return checkIfNeedToBeBlocked(e, session);
+	end
 end
 
 module:hook("pre-message/full", preCheckOutgoing, 500);
--- a/plugins/mod_register.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_register.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -35,7 +35,7 @@
 				local username, host = session.username, session.host;
 				--session.send(st.error_reply(stanza, "cancel", "not-allowed"));
 				--return;
-				usermanager_set_password(username, host, nil); -- Disable account
+				usermanager_set_password(username, nil, host); -- Disable account
 				-- FIXME the disabling currently allows a different user to recreate the account
 				-- we should add an in-memory account block mode when we have threading
 				session.send(st.reply(stanza));
@@ -70,7 +70,7 @@
 					username = nodeprep(table.concat(username));
 					password = table.concat(password);
 					if username == session.username then
-						if usermanager_set_password(username, session.host, password) then
+						if usermanager_set_password(username, password, session.host) then
 							session.send(st.reply(stanza));
 						else
 							-- TODO unable to write file, file may be locked, etc, what's the correct error?
--- a/plugins/mod_saslauth.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_saslauth.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -15,10 +15,11 @@
 
 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_get_provider = require "core.usermanager".get_provider;
+local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler;
 local usermanager_user_exists = require "core.usermanager".user_exists;
 local usermanager_get_password = require "core.usermanager".get_password;
+local usermanager_test_password = require "core.usermanager".test_password;
 local t_concat, t_insert = table.concat, table.insert;
 local tostring = tostring;
 local jid_split = require "util.jid".split;
@@ -66,21 +67,6 @@
 	error("Unknown SASL backend");
 end
 
-local default_authentication_profile = {
-	plain = function(username, realm)
-		local prepped_username = nodeprep(username);
-		if not prepped_username then
-			log("debug", "NODEprep failed on username: %s", username);
-			return "", nil;
-		end
-		local password = usermanager_get_password(prepped_username, realm);
-		if not password then
-			return "", nil;
-		end
-		return password, true;
-	end
-};
-
 local anonymous_authentication_profile = {
 	anonymous = function(username, realm)
 		return true; -- for normal usage you should always return true here
@@ -183,7 +169,7 @@
 		if module:get_option("anonymous_login") then
 			origin.sasl_handler = new_sasl(realm, anonymous_authentication_profile);
 		else
-			origin.sasl_handler = new_sasl(realm, default_authentication_profile);
+			origin.sasl_handler = usermanager_get_sasl_handler(module.host);
 			if not (module:get_option("allow_unencrypted_plain_auth")) and not origin.secure then
 				origin.sasl_handler:forbidden({"PLAIN"});
 			end
--- a/plugins/mod_uptime.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/mod_uptime.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -11,6 +11,7 @@
 local start_time = prosody.start_time;
 prosody.events.add_handler("server-started", function() start_time = prosody.start_time end);
 
+-- XEP-0012: Last activity
 module:add_feature("jabber:iq:last");
 
 module:hook("iq/host/jabber:iq:last:query", function(event)
@@ -20,3 +21,28 @@
 		return true;
 	end
 end);
+
+-- Ad-hoc command
+local adhoc_new = module:require "adhoc".new;
+
+function uptime_text()
+	local t = os.time()-prosody.start_time;
+	local seconds = t%60;
+	t = (t - seconds)/60;
+	local minutes = t%60;
+	t = (t - minutes)/60;
+	local hours = t%24;
+	t = (t - hours)/24;
+	local days = t;
+	return string.format("This server has been running for %d day%s, %d hour%s and %d minute%s (since %s)", 
+		days, (days ~= 1 and "s") or "", hours, (hours ~= 1 and "s") or "", 
+		minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time));
+end
+
+function uptime_command_handler (self, data, state)
+	return { info = uptime_text(), status = "completed" };
+end
+
+local descriptor = adhoc_new("Get uptime", "uptime", uptime_command_handler);
+
+module:add_item ("adhoc", descriptor);
--- a/plugins/muc/muc.lib.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/plugins/muc/muc.lib.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -122,9 +122,13 @@
 		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.attr.to = "";
+		local stamp = datetime.datetime();
+		local chars = #tostring(stanza);
+		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = stamp}):up(); -- XEP-0203
 		stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
-		t_insert(history, st.preserialize(stanza));
+		local entry = { stanza = stanza, stamp = stamp };
+		t_insert(history, entry);
 		while #history > history_length do t_remove(history, 1) end
 	end
 end
@@ -151,12 +155,46 @@
 		end
 	end
 end
-function room_mt:send_history(to)
+function room_mt:send_history(to, stanza)
 	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;
+		local x_tag = stanza and stanza:get_child("x", "http://jabber.org/protocol/muc");
+		local history_tag = x_tag and x_tag:get_child("history", "http://jabber.org/protocol/muc");
+		
+		local maxchars = history_tag and tonumber(history_tag.attr.maxchars);
+		if maxchars then maxchars = math.floor(maxchars); end
+		
+		local maxstanzas = math.floor(history_tag and tonumber(history_tag.attr.maxstanzas) or #history);
+		if not history_tag then maxstanzas = 20; end
+
+		local seconds = history_tag and tonumber(history_tag.attr.seconds);
+		if seconds then seconds = datetime.datetime(os.time() - math.floor(seconds)); end
+
+		local since = history_tag and history_tag.attr.since;
+		if since and not since:match("^%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d:%d%dZ$") then since = nil; end -- FIXME timezone support
+		if seconds and (not since or since < seconds) then since = seconds; end
+
+		local n = 0;
+		local charcount = 0;
+		local stanzacount = 0;
+		
+		for i=#history,1,-1 do
+			local entry = history[i];
+			if maxchars then
+				if not entry.chars then
+					entry.stanza.attr.to = "";
+					entry.chars = #tostring(entry.stanza);
+				end
+				charcount = charcount + entry.chars + #to;
+				if charcount > maxchars then break; end
+			end
+			if since and since > entry.stamp then break; end
+			if n + 1 > maxstanzas then break; end
+			n = n + 1;
+		end
+		for i=#history-n+1,#history do
+			local msg = history[i].stanza;
+			msg.attr.to = to;
 			self:_route_stanza(msg);
 		end
 	end
@@ -319,12 +357,7 @@
 								:tag("item", {affiliation=affiliation or "none", role=role or "none"}):up()
 								:tag("status", {code='110'}));
 						end
-						if self._data.whois == 'anyone' then -- non-anonymous?
-							self:_route_stanza(st.stanza("message", {from=to, to=from, type='groupchat'})
-								:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
-								:tag("status", {code='100'}));
-						end
-						self:send_history(from);
+						self:send_history(from, stanza);
 					else -- banned
 						local reply = st.error_reply(stanza, "auth", "forbidden"):up();
 						reply.tags[1].attr.code = "403";
@@ -751,7 +784,7 @@
 function room_mt:set_role(actor, occupant_jid, role, callback, reason)
 	if role == "none" then role = nil; end
 	if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end
-	if self:get_role(self._jid_nick[actor]) ~= "moderator" then return nil, "cancel", "not-allowed"; end
+	if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end
 	local occupant = self._occupants[occupant_jid];
 	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
@@ -804,6 +837,9 @@
 				end
 			end
 		end
+		if self._data.whois == 'anyone' then
+		    muc_child:tag('status', { code = '100' });
+		end
 	end
 	self:route_stanza(stanza);
 	if muc_child then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/storage/ejabberdstore.lib.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,190 @@
+
+local handlers = {};
+
+handlers.accounts = {
+	get = function(self, user)
+		local select = self:query("select password from users where username=?", user);
+		local row = select and select:fetch();
+		if row then return { password = row[1] }; end
+	end;
+	set = function(self, user, data)
+		if data and data.password then
+			return self:modify("update users set password=? where username=?", data.password, user)
+				or self:modify("insert into users (username, password) values (?, ?)", user, data.password);
+		else
+			return self:modify("delete from users where username=?", user);
+		end
+	end;
+};
+handlers.vcard = {
+	get = function(self, user)
+		local select = self:query("select vcard from vcard where username=?", user);
+		local row = select and select:fetch();
+		if row then return parse_xml(row[1]); end
+	end;
+	set = function(self, user, data)
+		if data then
+			data = unparse_xml(data);
+			return self:modify("update vcard set vcard=? where username=?", data, user)
+				or self:modify("insert into vcard (username, vcard) values (?, ?)", user, data);
+		else
+			return self:modify("delete from vcard where username=?", user);
+		end
+	end;
+};
+handlers.private = {
+	get = function(self, user)
+		local select = self:query("select namespace,data from private_storage where username=?", user);
+		if select then
+			local data = {};
+			for row in select:rows() do
+				data[row[1]] = parse_xml(row[2]);
+			end
+			return data;
+		end
+	end;
+	set = function(self, user, data)
+		if data then
+			self:modify("delete from private_storage where username=?", user);
+			for namespace,text in pairs(data) do
+				self:modify("insert into private_storage (username, namespace, data) values (?, ?, ?)", user, namespace, unparse_xml(text));
+			end
+			return true;
+		else
+			return self:modify("delete from private_storage where username=?", user);
+		end
+	end;
+	-- TODO map_set, map_get
+};
+local subscription_map = { N = "none", B = "both", F = "from", T = "to" };
+local subscription_map_reverse = { none = "N", both = "B", from = "F", to = "T" };
+handlers.roster = {
+	get = function(self, user)
+		local select = self:query("select jid,nick,subscription,ask,server,subscribe,type from rosterusers where username=?", user);
+		if select then
+			local roster = { pending = {} };
+			for row in select:rows() do
+				local jid,nick,subscription,ask,server,subscribe,typ = unpack(row);
+				local item = { groups = {} };
+				if nick == "" then nick = nil; end
+				item.nick = nick;
+				item.subscription = subscription_map[subscription];
+				if ask == "N" then ask = nil;
+				elseif ask == "O" then ask = "subscribe"
+				elseif ask == "I" then roster.pending[jid] = true; ask = nil;
+				elseif ask == "B" then roster.pending[jid] = true; ask = "subscribe";
+				else module:log("debug", "bad roster_item.ask: %s", ask); ask = nil; end
+				item.ask = ask;
+				roster[jid] = item;
+			end
+			
+			select = self:query("select jid,grp from rostergroups where username=?", user);
+			if select then
+				for row in select:rows() do
+					local jid,grp = unpack(rows);
+					if roster[jid] then roster[jid].groups[grp] = true; end
+				end
+			end
+			select = self:query("select version from roster_version where username=?", user);
+			local row = select and select:fetch();
+			if row then
+				roster[false] = { version = row[1]; };
+			end
+			return roster;
+		end
+	end;
+	set = function(self, user, data)
+		if data and next(data) ~= nil then
+			self:modify("delete from rosterusers where username=?", user);
+			self:modify("delete from rostergroups where username=?", user);
+			self:modify("delete from roster_version where username=?", user);
+			local done = {};
+			local pending = data.pending or {};
+			for jid,item in pairs(data) do
+				if jid and jid ~= "pending" then
+					local subscription = subscription_map_reverse[item.subscription];
+					local ask;
+					if pending[jid] then
+						if item.ask then ask = "B"; else ask = "I"; end
+					else
+						if item.ask then ask = "O"; else ask = "N"; end
+					end
+					local r = self:modify("insert into rosterusers (username,jid,nick,subscription,ask,askmessage,server,subscribe) values (?, ?, ?, ?, ?, '', '', '')", user, jid, item.nick or "", subscription, ask);
+					if not r then module:log("debug", "--- :( %s", tostring(r)); end
+					done[jid] = true;
+					for group in pairs(item.groups) do
+						self:modify("insert into rostergroups (username,jid,grp) values (?, ?, ?)", user, jid, group);
+					end
+				end
+			end
+			for jid in pairs(pending) do
+				if not done[jid] then
+					self:modify("insert into rosterusers (username,jid,nick,subscription,ask,askmessage,server,subscribe) values (?, ?, ?, ?, ?. ''. ''. '')", user, jid, "", "N", "I");
+				end
+			end
+			local version = data[false] and data[false].version;
+			if version then
+				self:modify("insert into roster_version (username,version) values (?, ?)", user, version);
+			end
+			return true;
+		else
+			self:modify("delete from rosterusers where username=?", user);
+			self:modify("delete from rostergroups where username=?", user);
+			self:modify("delete from roster_version where username=?", user);
+		end
+	end;
+};
+
+-----------------------------
+local driver = {};
+driver.__index = driver;
+
+function driver:prepare(sql)
+	module:log("debug", "query: %s", sql);
+	local err;
+	if not self.sqlcache then self.sqlcache = {}; end
+	local r = self.sqlcache[sql];
+	if r then return r; end
+	r, err = self.database:prepare(sql);
+	if not r then error("Unable to prepare SQL statement: "..err); end
+	self.sqlcache[sql] = r;
+	return r;
+end
+
+function driver:query(sql, ...)
+	local stmt = self:prepare(sql);
+	if stmt:execute(...) then return stmt; end
+end
+function driver:modify(sql, ...)
+	local stmt = self:query(sql, ...);
+	if stmt and stmt:affected() > 0 then return stmt; end
+end
+
+function driver:open(host, datastore, typ)
+	local cache_key = host.." "..datastore;
+	if self.ds_cache[cache_key] then return self.ds_cache[cache_key]; end
+	local instance = setmetatable({}, self);
+	instance.host = host;
+	instance.datastore = datastore;
+	local handler = handlers[datastore];
+	if not handler then return nil; end
+	for key,val in pairs(handler) do
+		instance[key] = val;
+	end
+	if instance.init then instance:init(); end
+	self.ds_cache[cache_key] = instance;
+	return instance;
+end
+
+-----------------------------
+local _M = {};
+
+function _M.new(dbtype, dbname, ...)
+	local instance = setmetatable({}, driver);
+	instance.__index = instance;
+	instance.database = get_database(dbtype, dbname, ...);
+	instance.ds_cache = {};
+	return instance;
+end
+
+return _M;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/storage/mod_storage.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,83 @@
+
+module:set_global();
+
+local cache = { data = {} };
+function cache:get(key) return self.data[key]; end
+function cache:set(key, val) self.data[key] = val; return val; end
+
+local DBI = require "DBI";
+function get_database(driver, db, ...)
+	local uri = "dbi:"..driver..":"..db;
+	return cache:get(uri) or cache:set(uri, (function(...)
+		module:log("debug", "Opening database: %s", uri);
+		prosody.unlock_globals();
+		local dbh = assert(DBI.Connect(...));
+		prosody.lock_globals();
+		dbh:autocommit(true)
+		return dbh;
+	end)(driver, db, ...));
+end
+
+local st = require "util.stanza";
+local _parse_xml = module:require("xmlparse");
+parse_xml_real = _parse_xml;
+function parse_xml(str)
+	local s = _parse_xml(str);
+	if s and not s.gsub then
+		return st.preserialize(s);
+	end
+end
+function unparse_xml(s)
+	return tostring(st.deserialize(s));
+end
+
+local drivers = {};
+
+--local driver = module:require("sqlbasic").new("SQLite3", "hello.sqlite");
+local option_datastore = module:get_option("datastore");
+local option_datastore_params = module:get_option("datastore_params") or {};
+if option_datastore then
+	local driver = module:require(option_datastore).new(unpack(option_datastore_params));
+	table.insert(drivers, driver);
+end
+
+local datamanager = require "util.datamanager";
+local olddm = {};
+local dm = {};
+for key,val in pairs(datamanager) do olddm[key] = val; end
+
+do -- driver based on old datamanager
+	local dmd = {};
+	dmd.__index = dmd;
+	function dmd:open(host, datastore)
+		return setmetatable({ host = host, datastore = datastore }, dmd);
+	end
+	function dmd:get(user) return olddm.load(user, self.host, self.datastore); end
+	function dmd:set(user, data) return olddm.store(user, self.host, self.datastore, data); end
+	table.insert(drivers, dmd);
+end
+
+local function open(...)
+	for _,driver in pairs(drivers) do
+		local ds = driver:open(...);
+		if ds then return ds; end
+	end
+end
+
+local _data_path;
+--function dm.set_data_path(path) _data_path = path; end
+--function dm.add_callback(...) end
+--function dm.remove_callback(...) end
+--function dm.getpath(...) end
+function dm.load(username, host, datastore)
+	local x = open(host, datastore);
+	return x:get(username);
+end
+function dm.store(username, host, datastore, data)
+	return open(host, datastore):set(username, data);
+end
+--function dm.list_append(...) return driver:list_append(...); end
+--function dm.list_store(...) return driver:list_store(...); end
+--function dm.list_load(...) return driver:list_load(...); end
+
+for key,val in pairs(dm) do datamanager[key] = val; end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/storage/sqlbasic.lib.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,97 @@
+
+-- Basic SQL driver
+-- This driver stores data as simple key-values
+
+local ser = require "util.serialization".serialize;
+local deser = function(data)
+	module:log("debug", "deser: %s", tostring(data));
+	if not data then return nil; end
+	local f = loadstring("return "..data);
+	if not f then return nil; end
+	setfenv(f, {});
+	local s, d = pcall(f);
+	if not s then return nil; end
+	return d;
+end;
+
+local driver = {};
+driver.__index = driver;
+
+driver.item_table = "item";
+driver.list_table = "list";
+
+function driver:prepare(sql)
+	module:log("debug", "query: %s", sql);
+	local err;
+	if not self.sqlcache then self.sqlcache = {}; end
+	local r = self.sqlcache[sql];
+	if r then return r; end
+	r, err = self.connection:prepare(sql);
+	if not r then error("Unable to prepare SQL statement: "..err); end
+	self.sqlcache[sql] = r;
+	return r;
+end
+
+function driver:load(username, host, datastore)
+	local select = self:prepare("select data from "..self.item_table.." where username=? and host=? and datastore=?");
+	select:execute(username, host, datastore);
+	local row = select:fetch();
+	return row and deser(row[1]) or nil;
+end
+
+function driver:store(username, host, datastore, data)
+	if not data or next(data) == nil then
+		local delete = self:prepare("delete from "..self.item_table.." where username=? and host=? and datastore=?");
+		delete:execute(username, host, datastore);
+		return true;
+	else
+		local d = self:load(username, host, datastore);
+		if d then -- update
+			local update = self:prepare("update "..self.item_table.." set data=? where username=? and host=? and datastore=?");
+			return update:execute(ser(data), username, host, datastore);
+		else -- insert
+			local insert = self:prepare("insert into "..self.item_table.." values (?, ?, ?, ?)");
+			return insert:execute(username, host, datastore, ser(data));
+		end
+	end
+end
+
+function driver:list_append(username, host, datastore, data)
+	if not data then return; end
+	local insert = self:prepare("insert into "..self.list_table.." values (?, ?, ?, ?)");
+	return insert:execute(username, host, datastore, ser(data));
+end
+
+function driver:list_store(username, host, datastore, data)
+	-- remove existing data
+	local delete = self:prepare("delete from "..self.list_table.." where username=? and host=? and datastore=?");
+	delete:execute(username, host, datastore);
+	if data and next(data) ~= nil then
+		-- add data
+		for _, d in ipairs(data) do
+			self:list_append(username, host, datastore, ser(d));
+		end
+	end
+	return true;
+end
+
+function driver:list_load(username, host, datastore)
+	local select = self:prepare("select data from "..self.list_table.." where username=? and host=? and datastore=?");
+	select:execute(username, host, datastore);
+	local r = {};
+	for row in select:rows() do
+		table.insert(r, deser(row[1]));
+	end
+	return r;
+end
+
+local _M = {};
+function _M.new(dbtype, dbname, ...)
+	local d = {};
+	setmetatable(d, driver);
+	local dbh = get_database(dbtype, dbname, ...);
+	--d:set_connection(dbh);
+	d.connection = dbh;
+	return d;
+end
+return _M;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/storage/xep227store.lib.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,168 @@
+
+local st = require "util.stanza";
+
+local function getXml(user, host)
+	local jid = user.."@"..host;
+	local path = "data/"..jid..".xml";
+	local f = io.open(path);
+	if not f then return; end
+	local s = f:read("*a");
+	return parse_xml_real(s);
+end
+local function setXml(user, host, xml)
+	local jid = user.."@"..host;
+	local path = "data/"..jid..".xml";
+	if xml then
+		local f = io.open(path, "w");
+		if not f then return; end
+		local s = tostring(xml);
+		f:write(s);
+		f:close();
+		return true;
+	else
+		return os.remove(path);
+	end
+end
+local function getUserElement(xml)
+	if xml and xml.name == "server-data" then
+		local host = xml.tags[1];
+		if host and host.name == "host" then
+			local user = host.tags[1];
+			if user and user.name == "user" then
+				return user;
+			end
+		end
+	end
+end
+local function createOuterXml(user, host)
+	return st.stanza("server-data", {xmlns='http://www.xmpp.org/extensions/xep-0227.html#ns'})
+		:tag("host", {jid=host})
+			:tag("user", {name = user});
+end
+local function removeFromArray(array, value)
+	for i,item in ipairs(array) do
+		if item == value then
+			table.remove(array, i);
+			return;
+		end
+	end
+end
+local function removeStanzaChild(s, child)
+	removeFromArray(s.tags, child);
+	removeFromArray(s, child);
+end
+
+local handlers = {};
+
+handlers.accounts = {
+	get = function(self, user)
+		local user = getUserElement(getXml(user, self.host));
+		if user and user.attr.password then
+			return { password = user.attr.password };
+		end
+	end;
+	set = function(self, user, data)
+		if data and data.password then
+			local xml = getXml(user, self.host);
+			if not xml then xml = createOuterXml(user, self.host); end
+			local usere = getUserElement(xml);
+			usere.attr.password = data.password;
+			return setXml(user, self.host, xml);
+		else
+			return setXml(user, self.host, nil);
+		end
+	end;
+};
+handlers.vcard = {
+	get = function(self, user)
+		local user = getUserElement(getXml(user, self.host));
+		if user then
+			local vcard = user:get_child("vCard", 'vcard-temp');
+			if vcard then
+				return st.preserialize(vcard);
+			end
+		end
+	end;
+	set = function(self, user, data)
+		local xml = getXml(user, self.host);
+		local usere = xml and getUserElement(xml);
+		if usere then
+			local vcard = usere:get_child("vCard", 'vcard-temp');
+			if vcard then
+				removeStanzaChild(usere, vcard);
+			elseif not data then
+				return true;
+			end
+			if data then
+				vcard = st.deserialize(data);
+				usere:add_child(vcard);
+			end
+			return setXml(user, self.host, xml);
+		end
+		return true;
+	end;
+};
+handlers.private = {
+	get = function(self, user)
+		local user = getUserElement(getXml(user, self.host));
+		if user then
+			local private = user:get_child("query", "jabber:iq:private");
+			if private then
+				local r = {};
+				for _, tag in ipairs(private.tags) do
+					r[tag.name..":"..tag.attr.xmlns] = st.preserialize(tag);
+				end
+				return r;
+			end
+		end
+	end;
+	set = function(self, user, data)
+		local xml = getXml(user, self.host);
+		local usere = xml and getUserElement(xml);
+		if usere then
+			local private = usere:get_child("query", 'jabber:iq:private');
+			if private then removeStanzaChild(usere, private); end
+			if data and next(data) ~= nil then
+				private = st.stanza("query", {xmlns='jabber:iq:private'});
+				for _,tag in pairs(data) do
+					private:add_child(st.deserialize(tag));
+				end
+				usere:add_child(private);
+			end
+			return setXml(user, self.host, xml);
+		end
+		return true;
+	end;
+};
+
+-----------------------------
+local driver = {};
+driver.__index = driver;
+
+function driver:open(host, datastore, typ)
+	local cache_key = host.." "..datastore;
+	if self.ds_cache[cache_key] then return self.ds_cache[cache_key]; end
+	local instance = setmetatable({}, self);
+	instance.host = host;
+	instance.datastore = datastore;
+	local handler = handlers[datastore];
+	if not handler then return nil; end
+	for key,val in pairs(handler) do
+		instance[key] = val;
+	end
+	if instance.init then instance:init(); end
+	self.ds_cache[cache_key] = instance;
+	return instance;
+end
+
+-----------------------------
+local _M = {};
+
+function _M.new()
+	local instance = setmetatable({}, driver);
+	instance.__index = instance;
+	instance.ds_cache = {};
+	return instance;
+end
+
+return _M;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/storage/xmlparse.lib.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,56 @@
+
+local st = require "util.stanza";
+
+-- 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
+
+return parse_xml;
--- a/prosody	Fri Jun 11 21:30:24 2010 +0500
+++ b/prosody	Sat Jun 12 02:39:18 2010 +0100
@@ -22,9 +22,6 @@
 	package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
 end
 
-package.path = package.path..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.lua";
-package.cpath = package.cpath..";"..(CFG_SOURCEDIR or ".").."/fallbacks/?.so";
-
 -- Substitute ~ with path to home directory in data path
 if CFG_DATADIR then
 	if os.getenv("HOME") then
@@ -32,6 +29,10 @@
 	end
 end
 
+-- Global 'prosody' object
+prosody = { events = require "util.events".new(); };
+local prosody = prosody;
+
 -- Load the config-parsing module
 config = require "core.configmanager"
 
@@ -155,10 +156,6 @@
 	full_sessions = {};
 	hosts = {};
 
-	-- Global 'prosody' object
-	prosody = {};
-	local prosody = prosody;
-	
 	prosody.bare_sessions = bare_sessions;
 	prosody.full_sessions = full_sessions;
 	prosody.hosts = hosts;
@@ -168,8 +165,6 @@
 	
 	prosody.arg = _G.arg;
 
-	prosody.events = require "util.events".new();
-	
 	prosody.platform = "unknown";
 	if os.getenv("WINDIR") then
 		prosody.platform = "windows";
@@ -200,7 +195,6 @@
 	-- 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
 
@@ -291,9 +285,9 @@
 function load_secondary_libraries()
 	--- Load and initialise core modules
 	require "util.import"
+	require "util.xmppstream"
 	require "core.xmlhandlers"
 	require "core.rostermanager"
-	require "core.eventmanager"
 	require "core.hostmanager"
 	require "core.modulemanager"
 	require "core.usermanager"
@@ -337,7 +331,6 @@
 function prepare_to_start()
 	log("debug", "Prosody is using the %s backend for connection handling", server.get_backend());
 	-- Signal to modules that we are ready to start
-	eventmanager.fire_event("server-starting");
 	prosody.events.fire_event("server-starting");
 
 	-- start listening on sockets
@@ -455,14 +448,12 @@
 init_global_protection();
 prepare_to_start();
 
-eventmanager.fire_event("server-started");
 prosody.events.fire_event("server-started");
 
 loop();
 
 log("info", "Shutting down...");
 cleanup();
-eventmanager.fire_event("server-stopped");
 prosody.events.fire_event("server-stopped");
 log("info", "Shutdown complete");
 
--- a/prosody.cfg.lua.dist	Fri Jun 11 21:30:24 2010 +0500
+++ b/prosody.cfg.lua.dist	Sat Jun 12 02:39:18 2010 +0100
@@ -71,6 +71,7 @@
 	-- "presence";
 	-- "message";
 	-- "iq";
+	-- "defaultauth";
 };
 
 -- Disable account creation by default, for security
--- a/prosodyctl	Fri Jun 11 21:30:24 2010 +0500
+++ b/prosodyctl	Sat Jun 12 02:39:18 2010 +0100
@@ -29,6 +29,14 @@
 	end
 end
 
+-- Global 'prosody' object
+prosody = {
+	hosts = {},
+	events = require "util.events".new(),
+	platform = "posix"
+};
+local prosody = prosody;
+
 config = require "core.configmanager"
 
 do
@@ -63,8 +71,6 @@
 	os.exit(1);
 end
 
-prosody = { hosts = {}, events = events, platform = "posix" };
-
 local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
 require "util.datamanager".set_data_path(data_path);
 
@@ -114,12 +120,14 @@
 		["not-running"] = "Prosody is not running";
 		}, { __index = function (t,k) return "Error: "..(tostring(k):gsub("%-", " "):gsub("^.", string.upper)); end });
 
-local events = require "util.events".new();
-
 hosts = prosody.hosts;
 
+local function make_host(hostname)
+	return { events = prosody.events, users = require "core.usermanager".new_null_provider(hostname) };
+end
+
 for hostname, config in pairs(config.getconfig()) do
-	hosts[hostname] = { events = events };
+	hosts[hostname] = make_host(hostname);
 end
 	
 require "core.modulemanager"
@@ -231,16 +239,23 @@
 		return 1;
 	end
 	
+	if not hosts[host] then
+		show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
+		show_warning("The user will not be able to log in until this is changed.");
+		hosts[host] = make_host(host);
+	elseif config.get(host, "core", "authentication")
+		and config.get(host, "core", "authentication") ~= "default" then
+		show_warning("The host '%s' is configured to use the '%s' authentication provider", host,
+			config.get(host, "core", "authentication"));
+		show_warning("prosodyctl currently only supports the default provider, sorry :(");
+		return 1;
+	end
+	
 	if prosodyctl.user_exists{ user = user, host = host } then
 		show_message [[That user already exists]];
 		return 1;
 	end
 	
-	if not hosts[host] then
-		show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
-		show_warning("The user will not be able to log in until this is changed.");
-	end
-	
 	local password = read_password();
 	if not password then return 1; end
 	
@@ -269,6 +284,18 @@
 		return 1;
 	end
 	
+	if not hosts[host] then
+		show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
+		show_warning("The user will not be able to log in until this is changed.");
+		hosts[host] = make_host(host);
+	elseif config.get(host, "core", "authentication")
+		and config.get(host, "core", "authentication") ~= "default" then
+		show_warning("The host '%s' is configured to use the '%s' authentication provider", host,
+			config.get(host, "core", "authentication"));
+		show_warning("prosodyctl currently only supports the default provider, sorry :(");
+		return 1;
+	end
+	
 	if not prosodyctl.user_exists { user = user, host = host } then
 		show_message [[That user does not exist, use prosodyctl adduser to create a new user]]
 		return 1;
@@ -302,6 +329,18 @@
 		return 1;
 	end
 	
+	if not hosts[host] then
+		show_warning("The host '%s' is not listed in the configuration file (or is not enabled).", host)
+		show_warning("The user will not be able to log in until this is changed.");
+		hosts[host] = make_host(host);
+	elseif config.get(host, "core", "authentication")
+		and config.get(host, "core", "authentication") ~= "default" then
+		show_warning("The host '%s' is configured to use the '%s' authentication provider", host,
+			config.get(host, "core", "authentication"));
+		show_warning("prosodyctl currently only supports the default provider, sorry :(");
+		return 1;
+	end
+
 	if not prosodyctl.user_exists { user = user, host = host } then
 		show_message [[That user does not exist on this server]]
 		return 1;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/filters.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,68 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local t_insert, t_remove = table.insert, table.remove;
+
+module "filters"
+
+function initialize(session)
+	if not session.filters then
+		local filters = {};
+		session.filters = filters;
+		
+		function session.filter(type, data)
+			local filter_list = filters[type];
+			if filter_list then
+				for i = 1, #filter_list do
+					data = filter_list[i](data);
+					if data == nil then break; end
+				end
+			end
+			return data;
+		end
+	end
+	return session.filter;
+end
+
+function add_filter(session, type, callback, priority)
+	if not session.filters then
+		initialize(session);
+	end
+	
+	local filter_list = session.filters[type];
+	if not filter_list then
+		filter_list = {};
+		session.filters[type] = filter_list;
+	end
+	
+	priority = priority or 0;
+	
+	local i = 0;
+	repeat
+		i = i + 1;
+	until not filter_list[i] or filter_list[filter_list[i]] >= priority;
+	
+	t_insert(filter_list, i, callback);
+	filter_list[callback] = priority;
+end
+
+function remove_filter(session, type, callback)
+	if not session.filters then return; end
+	local filter_list = session.filters[type];
+	if filter_list and filter_list[callback] then
+		for i=1, #filter_list do
+			if filter_list[i] == callback then
+				t_remove(filter_list, i);
+				filter_list[callback] = nil;
+				return true;
+			end
+		end
+	end
+end
+
+return _M;
\ No newline at end of file
--- a/util/sasl.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/util/sasl.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -108,11 +108,8 @@
 		return false;
 	end
 	
-	self.mech_i = mechanisms[mechanism]
-	if self.mech_i == nil then 
-		return false;
-	end
-	return true;
+	self.mech_i = mechanisms[mechanism];
+	return (self.mech_i ~= nil);
 end
 
 -- feed new messages to process into the library
--- a/util/sasl/anonymous.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/util/sasl/anonymous.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -20,12 +20,22 @@
 
 --=========================
 --SASL ANONYMOUS according to RFC 4505
+
+--[[
+Supported Authentication Backends
+
+anonymous:
+	function(username, realm)
+		return true; --for normal usage just return true; if you don't like the supplied username you can return false.
+	end
+]]
+
 local function anonymous(self, message)
 	local username;
 	repeat
 		username = generate_uuid();
 	until self.profile.anonymous(username, self.realm);
-	self["username"] = username;
+	self.username = username;
 	return "success"
 end
 
--- a/util/sasl/plain.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/util/sasl/plain.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -29,7 +29,7 @@
 	end
 
 plain_test:
-	function(username, realm, password)
+	function(username, password, realm)
 		return true or false, state;
 	end
 ]]
@@ -58,9 +58,9 @@
 	if self.profile.plain then
 		local correct_password;
 		correct_password, state = self.profile.plain(authentication, self.realm);
-		if correct_password == password then correct = true; else correct = false; end
+		correct = (correct_password == password);
 	elseif self.profile.plain_test then
-		correct, state = self.profile.plain_test(authentication, self.realm, password);
+		correct, state = self.profile.plain_test(authentication, password, self.realm);
 	end
 
 	self.username = authentication
--- a/util/sasl/scram.lua	Fri Jun 11 21:30:24 2010 +0500
+++ b/util/sasl/scram.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -35,7 +35,7 @@
 scram_{MECH}:
 	-- MECH being a standard hash name (like those at IANA's hash registry) with '-' replaced with '_'
 	function(username, realm)
-		return salted_password, iteration_count, salt, state;
+		return stored_key, server_key, iteration_count, salt, state;
 	end
 ]]
 
@@ -93,22 +93,21 @@
 	return username;
 end
 
-local function hashprep( hashname ) 
-	local hash = hashname:lower()
-	hash = hash:gsub("-", "_")
-	return hash
+local function hashprep(hashname)
+	return hashname:lower():gsub("-", "_");
 end
 
-function saltedPasswordSHA1(password, salt, iteration_count)
-	local salted_password
+function getAuthenticationDatabaseSHA1(password, salt, iteration_count)
 	if type(password) ~= "string" or type(salt) ~= "string" or type(iteration_count) ~= "number" then
 		return false, "inappropriate argument types"
 	end
 	if iteration_count < 4096 then
 		log("warn", "Iteration count < 4096 which is the suggested minimum according to RFC 5802.")
 	end
-
-	return true, Hi(hmac_sha1, password, salt, iteration_count);
+	local salted_password = Hi(hmac_sha1, password, salt, iteration_count);
+	local stored_key = sha1(hmac_sha1(salted_password, "Client Key"))
+	local server_key = hmac_sha1(salted_password, "Server Key");
+	return true, stored_key, server_key
 end
 
 local function scram_gen(hash_name, H_f, HMAC_f)
@@ -158,17 +157,18 @@
 				self.state.iteration_count = default_i;
 
 				local succ = false;
-				succ, self.state.salted_password = saltedPasswordSHA1(password, self.state.salt, default_i, self.state.iteration_count);
+				succ, self.state.stored_key, self.state.server_key = getAuthenticationDatabaseSHA1(password, self.state.salt, default_i, self.state.iteration_count);
 				if not succ then
-					log("error", "Generating salted password failed. Reason: %s", self.state.salted_password);
+					log("error", "Generating authentication database failed. Reason: %s", self.state.stored_key);
 					return "failure", "temporary-auth-failure";
 				end
 			elseif self.profile["scram_"..hashprep(hash_name)] then
-				local salted_password, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self.state.name, self.realm);
+				local stored_key, server_key, iteration_count, salt, state = self.profile["scram_"..hashprep(hash_name)](self.state.name, self.realm);
 				if state == nil then return "failure", "not-authorized"
 				elseif state == false then return "failure", "account-disabled" end
 				
-				self.state.salted_password = salted_password;
+				self.state.stored_key = stored_key;
+				self.state.server_key = server_key;
 				self.state.iteration_count = iteration_count;
 				self.state.salt = salt
 			end
@@ -190,16 +190,15 @@
 				return "failure", "malformed-request", "Wrong nonce in client-final-message.";
 			end
 			
-			local SaltedPassword = self.state.salted_password;
-			local ClientKey = HMAC_f(SaltedPassword, "Client Key")
-			local ServerKey = HMAC_f(SaltedPassword, "Server Key")
-			local StoredKey = H_f(ClientKey)
+			local ServerKey = self.state.server_key;
+			local StoredKey = self.state.stored_key;
+			
 			local AuthMessage = "n=" .. s_match(self.state.client_first_message,"n=(.+)") .. "," .. self.state.server_first_message .. "," .. s_match(client_final_message, "(.+),p=.+")
 			local ClientSignature = HMAC_f(StoredKey, AuthMessage)
-			local ClientProof     = binaryXOR(ClientKey, ClientSignature)
+			local ClientKey = binaryXOR(ClientSignature, base64.decode(self.state.proof))
 			local ServerSignature = HMAC_f(ServerKey, AuthMessage)
 
-			if base64.encode(ClientProof) == self.state.proof then
+			if StoredKey == H_f(ClientKey) then
 				local server_final_message = "v="..base64.encode(ServerSignature);
 				self["username"] = self.state.name;
 				return "success", server_final_message;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/xmppstream.lua	Sat Jun 12 02:39:18 2010 +0100
@@ -0,0 +1,168 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+
+local lxp = require "lxp";
+local st = require "util.stanza";
+
+local tostring = tostring;
+local t_insert = table.insert;
+local t_concat = table.concat;
+
+local default_log = require "util.logger".init("xmlhandlers");
+
+local error = error;
+
+module "xmppstream"
+
+local new_parser = lxp.new;
+
+local ns_prefixes = {
+	["http://www.w3.org/XML/1998/namespace"] = "xml";
+};
+
+local xmlns_streams = "http://etherx.jabber.org/streams";
+
+local ns_separator = "\1";
+local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
+
+function new_sax_handlers(session, stream_callbacks)
+	local xml_handlers = {};
+	
+	local log = session.log or default_log;
+	
+	local cb_streamopened = stream_callbacks.streamopened;
+	local cb_streamclosed = stream_callbacks.streamclosed;
+	local cb_error = stream_callbacks.error or function(session, e) error("XML stream error: "..tostring(e)); end;
+	local cb_handlestanza = stream_callbacks.handlestanza;
+	
+	local stream_ns = stream_callbacks.stream_ns or xmlns_streams;
+	local stream_tag = stream_ns..ns_separator..(stream_callbacks.stream_tag or "stream");
+	local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error");
+	
+	local stream_default_ns = stream_callbacks.default_ns;
+	
+	local chardata, stanza = {};
+	function xml_handlers:StartElement(tagname, attr)
+		if stanza and #chardata > 0 then
+			-- We have some character data in the buffer
+			stanza:text(t_concat(chardata));
+			chardata = {};
+		end
+		local curr_ns,name = tagname:match(ns_pattern);
+		if name == "" then
+			curr_ns, name = "", curr_ns;
+		end
+
+		if curr_ns ~= stream_default_ns then
+			attr.xmlns = curr_ns;
+		end
+		
+		-- FIXME !!!!!
+		for i=1,#attr do
+			local k = attr[i];
+			attr[i] = nil;
+			local ns, nm = k:match(ns_pattern);
+			if nm ~= "" then
+				ns = ns_prefixes[ns]; 
+				if ns then 
+					attr[ns..":"..nm] = attr[k];
+					attr[k] = nil;
+				end
+			end
+		end
+		
+		if not stanza then --if we are not currently inside a stanza
+			if session.notopen then
+				if tagname == stream_tag then
+					if cb_streamopened then
+						cb_streamopened(session, attr);
+					end
+				else
+					-- Garbage before stream?
+					cb_error(session, "no-stream");
+				end
+				return;
+			end
+			if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then
+				cb_error(session, "invalid-top-level-element");
+			end
+			
+			stanza = st.stanza(name, attr);
+		else -- we are inside a stanza, so add a tag
+			attr.xmlns = nil;
+			if curr_ns ~= stream_default_ns then
+				attr.xmlns = curr_ns;
+			end
+			stanza:tag(name, attr);
+		end
+	end
+	function xml_handlers:CharacterData(data)
+		if stanza then
+			t_insert(chardata, data);
+		end
+	end
+	function xml_handlers:EndElement(tagname)
+		if stanza then
+			if #chardata > 0 then
+				-- We have some character data in the buffer
+				stanza:text(t_concat(chardata));
+				chardata = {};
+			end
+			-- Complete stanza
+			if #stanza.last_add == 0 then
+				if tagname ~= stream_error_tag then
+					cb_handlestanza(session, stanza);
+				else
+					cb_error(session, "stream-error", stanza);
+				end
+				stanza = nil;
+			else
+				stanza:up();
+			end
+		else
+			if tagname == stream_tag then
+				if cb_streamclosed then
+					cb_streamclosed(session);
+				end
+			else
+				local curr_ns,name = tagname:match(ns_pattern);
+				if name == "" then
+					curr_ns, name = "", curr_ns;
+				end
+				cb_error(session, "parse-error", "unexpected-element-close", name);
+			end
+			stanza, chardata = nil, {};
+		end
+	end
+	
+	local function reset()
+		stanza, chardata = nil, {};
+	end
+	
+	return xml_handlers, { reset = reset };
+end
+
+function new(session, stream_callbacks)
+	local handlers, meta = new_sax_handlers(session, stream_callbacks);
+	local parser = new_parser(handlers, ns_separator);
+	local parse = parser.parse;
+
+	return {
+		reset = function ()
+			parser = new_parser(handlers, ns_separator);
+			parse = parser.parse;
+			meta.reset();
+		end,
+		feed = function (self, data)
+			return parse(parser, data);
+		end
+	};
+end
+
+return _M;

mercurial