Merge 0.7->trunk

Fri, 23 Jul 2010 09:22:27 +0100

author
Matthew Wild <mwild1@gmail.com>
date
Fri, 23 Jul 2010 09:22:27 +0100
changeset 3400
502a634f0578
parent 3398
abc4a52aef02 (diff)
parent 3399
3976bad56640 (current diff)
child 3401
2387f35db5c8

Merge 0.7->trunk

core/certmanager.lua file | annotate | diff | comparison | revisions
--- a/Makefile	Fri Jul 23 09:17:11 2010 +0100
+++ b/Makefile	Fri Jul 23 09:22:27 2010 +0100
@@ -34,7 +34,8 @@
 	install -d $(MODULES)/muc
 	install -m644 plugins/muc/* $(MODULES)/muc
 	install -m644 certs/* $(CONFIG)/certs
-	install -m644 plugins/*.lua $(MODULES)
+	install -d $(MODULES)/adhoc
+	install -m644 plugins/adhoc/*.lua $(MODULES)/adhoc
 	install -m644 man/prosodyctl.man $(MAN)/man1/prosodyctl.1
 	test -e $(CONFIG)/prosody.cfg.lua || install -m644 prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
 	test -e prosody.version && install prosody.version $(SOURCE)/prosody.version || true
--- a/configure	Fri Jul 23 09:17:11 2010 +0100
+++ b/configure	Fri Jul 23 09:22:27 2010 +0100
@@ -26,7 +26,7 @@
 
 --help                      This help.
 --ostype=OS                 Use one of the OS presets.
-                            May be one of: debian, macosx, linux
+                            May be one of: debian, macosx, linux, freebsd
 --prefix=DIR                Prefix where Prosody should be installed.
                             Default is $PREFIX
 --sysconfdir=DIR            Location where the config file should be installed.
@@ -85,6 +85,38 @@
    --ostype=*)
       OSTYPE="$value"
       OSTYPE_SET=yes
+      if [ "$OSTYPE" = "debian" ]
+      then LUA_SUFFIX="5.1";
+	LUA_SUFFIX_SET=yes
+	LUA_INCDIR=/usr/include/lua5.1;
+	LUA_INCDIR_SET=yes
+	fi
+	if [ "$OSTYPE" = "macosx" ]
+	then LUA_INCDIR=/usr/local/include;
+	LUA_INCDIR_SET=yes
+	LUA_LIBDIR=/usr/local/lib
+	LUA_LIBDIR_SET=yes
+	CFLAGS="-Wall"
+	LDFLAGS="-bundle -undefined dynamic_lookup"
+	fi
+        if [ "$OSTYPE" = "linux" ]
+        then LUA_INCDIR=/usr/local/include;
+        LUA_INCDIR_SET=yes
+        LUA_LIBDIR=/usr/local/lib
+        LUA_LIBDIR_SET=yes
+        CFLAGS="-Wall -fPIC"
+        LDFLAGS="-shared"
+        fi
+        if [ "$OSTYPE" = "freebsd" ]
+        then LUA_INCDIR="/usr/local/include/lua51"
+        LUA_INCDIR_SET=yes
+        CFLAGS="-Wall -fPIC -I/usr/local/include"
+        LDFLAGS="-I/usr/local/include -L/usr/local/lib -shared"
+        LUA_SUFFIX="-5.1"
+        LUA_SUFFIX_SET=yes
+        LUA_DIR=/usr/local
+        LUA_DIR_SET=yes
+        fi
       ;;
    --datadir=*)
    	DATADIR="$value"
@@ -134,32 +166,6 @@
    shift
 done
 
-if [ "$OSTYPE_SET" = "yes" ]
-then
-	if [ "$OSTYPE" = "debian" ]
-	then LUA_SUFFIX="5.1";
-	LUA_SUFFIX_SET=yes
-	LUA_INCDIR=/usr/include/lua5.1;
-	LUA_INCDIR_SET=yes
-	fi
-	if [ "$OSTYPE" = "macosx" ]
-	then LUA_INCDIR=/usr/local/include;
-	LUA_INCDIR_SET=yes
-	LUA_LIBDIR=/usr/local/lib
-	LUA_LIBDIR_SET=yes
-	CFLAGS="-Wall"
-	LDFLAGS="-bundle -undefined dynamic_lookup"
-	fi
-        if [ "$OSTYPE" = "linux" ]
-        then LUA_INCDIR=/usr/local/include;
-        LUA_INCDIR_SET=yes
-        LUA_LIBDIR=/usr/local/lib
-        LUA_LIBDIR_SET=yes
-        CFLAGS="-Wall -fPIC"
-        LDFLAGS="-shared"
-        fi
-fi
-
 if [ "$PREFIX_SET" = "yes" -a ! "$SYSCONFDIR_SET" = "yes" ]
 then
    if [ "$PREFIX" = "/usr" ]
--- a/core/certmanager.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/core/certmanager.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -1,3 +1,11 @@
+-- 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 configmanager = require "core.configmanager";
 local log = require "util.logger".init("certmanager");
 local ssl = ssl;
@@ -6,54 +14,62 @@
 local setmetatable, tostring = setmetatable, tostring;
 
 local prosody = prosody;
+local resolve_path = prosody.resolve_relative_path;
 
 module "certmanager"
 
--- These are the defaults if not overridden in the config
-local default_ssl_ctx = { mode = "client", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
-local default_ssl_ctx_in = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none", options = "no_sslv2"; };
-
-local default_ssl_ctx_mt = { __index = default_ssl_ctx };
-local default_ssl_ctx_in_mt = { __index = default_ssl_ctx_in };
-
 -- Global SSL options if not overridden per-host
 local default_ssl_config = configmanager.get("*", "core", "ssl");
+local default_capath = "/etc/ssl/certs";
 
 function create_context(host, mode, config)
-	local ssl_config = config and config.core.ssl or default_ssl_config;
-	if ssl and ssl_config then
-		local ctx, err = ssl_newcontext(setmetatable(ssl_config, mode == "client" and default_ssl_ctx_mt or default_ssl_ctx_in_mt));
-		if not ctx then
-			err = err or "invalid ssl config"
-			local file = err:match("^error loading (.-) %(");
-			if file then
-				if file == "private key" then
-					file = ssl_config.key or "your private key";
-				elseif file == "certificate" then
-					file = ssl_config.certificate or "your certificate file";
-				end
-				local reason = err:match("%((.+)%)$") or "some reason";
-				if reason == "Permission denied" then
-					reason = "Check that the permissions allow Prosody to read this file.";
-				elseif reason == "No such file or directory" then
-					reason = "Check that the path is correct, and the file exists.";
-				elseif reason == "system lib" then
-					reason = "Previous error (see logs), or other system error.";
-				elseif reason == "(null)" or not reason then
-					reason = "Check that the file exists and the permissions are correct";
-				else
-					reason = "Reason: "..tostring(reason):lower();
-				end
-				log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
+	local user_ssl_config = config and config.core.ssl or default_ssl_config;
+
+	if not ssl then return nil, "LuaSec (required for encryption) was not found"; end
+	if not user_ssl_config then return nil, "No SSL/TLS configuration present for "..host; end
+	
+	local ssl_config = {
+		mode = mode;
+		protocol = user_ssl_config.protocol or "sslv23";
+		key = resolve_path(user_ssl_config.key);
+		password = user_ssl_config.password;
+		certificate = resolve_path(user_ssl_config.certificate);
+		capath = resolve_path(user_ssl_config.capath or default_capath);
+		cafile = resolve_path(user_ssl_config.cafile);
+		verify = user_ssl_config.verify or "none";
+		options = user_ssl_config.options or "no_sslv2";
+		ciphers = user_ssl_config.ciphers;
+		depth = user_ssl_config.depth;
+	};
+
+	local ctx, err = ssl_newcontext(ssl_config);
+	if not ctx then
+		err = err or "invalid ssl config"
+		local file = err:match("^error loading (.-) %(");
+		if file then
+			if file == "private key" then
+				file = ssl_config.key or "your private key";
+			elseif file == "certificate" then
+				file = ssl_config.certificate or "your certificate file";
+			end
+			local reason = err:match("%((.+)%)$") or "some reason";
+			if reason == "Permission denied" then
+				reason = "Check that the permissions allow Prosody to read this file.";
+			elseif reason == "No such file or directory" then
+				reason = "Check that the path is correct, and the file exists.";
+			elseif reason == "system lib" then
+				reason = "Previous error (see logs), or other system error.";
+			elseif reason == "(null)" or not reason then
+				reason = "Check that the file exists and the permissions are correct";
 			else
-				log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
+				reason = "Reason: "..tostring(reason):lower();
 			end
-        	end
-        	return ctx, err;
-	elseif not ssl then
-		return nil, "LuaSec (required for encryption) was not found";
-	end
-	return nil, "No SSL/TLS configuration present for "..host;
+			log("error", "SSL/TLS: Failed to load %s: %s", file, reason);
+		else
+			log("error", "SSL/TLS: Error initialising for host %s: %s", host, err );
+		end
+        end
+        return ctx, err;
 end
 
 function reload_ssl_config()
--- a/core/configmanager.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/core/configmanager.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -13,7 +13,7 @@
 		setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table, string.format;
 
 
-local eventmanager = require "core.eventmanager";
+local fire_event = prosody and prosody.events.fire_event or function () end;
 
 module "configmanager"
 
@@ -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()
@@ -72,7 +73,7 @@
 			local ok, err = parsers[format].load(f:read("*a"), filename);
 			f:close();
 			if ok then
-				eventmanager.fire_event("config-reloaded", { filename = filename, format = format });
+				fire_event("config-reloaded", { filename = filename, format = format });
 			end
 			return ok, "parser", err;
 		end
@@ -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 Jul 23 09:17:11 2010 +0100
+++ b/core/eventmanager.lua	Fri Jul 23 09:22:27 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/loggingmanager.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/core/loggingmanager.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -26,18 +26,20 @@
 local config = require "core.configmanager";
 local eventmanager = require "core.eventmanager";
 local logger = require "util.logger";
+local prosody = prosody;
+
 local debug_mode = config.get("*", "core", "debug");
 
 _G.log = logger.init("general");
 
 module "loggingmanager"
 
--- The log config used if none specified in the config file
-local default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } };
-local default_file_logging = { { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true } };
+-- The log config used if none specified in the config file (see reload_logging for initialization)
+local default_logging;
+local default_file_logging;
 local default_timestamp = "%b %d %H:%M:%S";
 -- The actual config loggingmanager is using
-local logging_config = config.get("*", "core", "log") or default_logging;
+local logging_config;
 
 local apply_sink_rules;
 local log_sink_types = setmetatable({}, { __newindex = function (t, k, v) rawset(t, k, v); apply_sink_rules(k); end; });
@@ -138,6 +140,34 @@
 	return set;
 end
 
+-- Initialize config, etc. --
+function reload_logging()
+	local old_sink_types = {};
+	
+	for name, sink_maker in pairs(log_sink_types) do
+		old_sink_types[name] = sink_maker;
+		log_sink_types[name] = nil;
+	end
+	
+	logger.reset();
+
+	default_logging = { { to = "console" , levels = { min = (debug_mode and "debug") or "info" } } };
+	default_file_logging = { { to = "file", levels = { min = (debug_mode and "debug") or "info" }, timestamps = true } };
+	default_timestamp = "%b %d %H:%M:%S";
+
+	logging_config = config.get("*", "core", "log") or default_logging;
+	
+	
+	for name, sink_maker in pairs(old_sink_types) do
+		log_sink_types[name] = sink_maker;
+	end
+	
+	prosody.events.fire_event("logging-reloaded");
+end
+
+reload_logging();
+prosody.events.add_handler("config-reloaded", reload_logging);
+
 --- Definition of built-in logging sinks ---
 
 -- Null sink, must enter log_sink_types *first*
@@ -215,16 +245,10 @@
 	end
 	local write, flush = logfile.write, logfile.flush;
 
-	eventmanager.add_event_hook("reopen-log-files", function ()
+	prosody.events.add_handler("logging-reloading", function ()
 			if logfile then
 				logfile:close();
 			end
-			logfile = io_open(log, "a+");
-			if not logfile then
-				write, flush = empty_function, empty_function;
-			else
-				write, flush = logfile.write, logfile.flush;
-			end
 		end);
 
 	local timestamps = config.timestamps;
--- a/core/rostermanager.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/core/rostermanager.lua	Fri Jul 23 09:22:27 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 Jul 23 09:17:11 2010 +0100
+++ b/core/s2smanager.lua	Fri Jul 23 09:22:27 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";
@@ -41,9 +42,11 @@
 local adns, dns = require "net.adns", require "net.dns";
 local config = require "core.configmanager";
 local connect_timeout = config.get("*", "core", "s2s_timeout") or 60;
-local dns_timeout = config.get("*", "core", "dns_timeout") or 60;
+local dns_timeout = config.get("*", "core", "dns_timeout") or 15;
 local max_dns_depth = config.get("*", "core", "dns_max_depth") or 3;
 
+dns.settimeout(dns_timeout);
+
 incoming_s2s = {};
 _G.prosody.incoming_s2s = incoming_s2s;
 local incoming_s2s = incoming_s2s;
@@ -137,7 +140,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 +181,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);
@@ -234,13 +251,6 @@
 			end
 		end, "_xmpp-server._tcp."..connect_host..".", "SRV");
 		
-		-- Set handler for DNS timeout
-		add_task(dns_timeout, function ()
-			if handle then
-				adns.cancel(handle, true);
-			end
-		end);
-		
 		return true; -- Attempt in progress
 	elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV
 		host_session.srv_choice = host_session.srv_choice + 1;
@@ -293,13 +303,6 @@
 		end
 	end, connect_host, "A", "IN");
 
-	-- Set handler for DNS timeout
-	add_task(dns_timeout, function ()
-		if handle then
-			adns.cancel(handle, true);
-		end
-	end);
-	
 	return true;
 end
 
@@ -327,13 +330,25 @@
 	conn = wrapclient(conn, connect_host, connect_port, cl, cl.default_mode or 1 );
 	host_session.conn = conn;
 	
+	local filter = initialize_filters(host_session);
+	local w, log = conn.write, host_session.log;
+	host_session.sends2s = function (t)
+		log("debug", "sending: %s", (t.top_tag and t:top_tag()) or t:match("^[^>]*>?"));
+		if t.name then
+			t = filter("stanzas/out", t);
+		end
+		if t then
+			t = filter("bytes/out", tostring(t));
+			if t then
+				return w(conn, tostring(t));
+			end
+		end
+	end
+	
 	-- Register this outgoing connection so that xmppserver_listener knows about it
 	-- otherwise it will assume it is a new incoming connection
 	cl.register_outgoing(conn, host_session);
 	
-	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:open_stream(from_host, to_host);
 	
 	log("debug", "Connection attempt in progress...");
@@ -375,10 +390,22 @@
 	
 		session.streamid = uuid_gen();
 		(session.log or log)("debug", "incoming s2s received <stream:stream>");
-		if session.to_host and not hosts[session.to_host] then
-			-- Attempting to connect to a host we don't serve
-			session:close({ condition = "host-unknown"; text = "This host does not serve "..session.to_host });
-			return;
+		if session.to_host then
+			if not hosts[session.to_host] then
+				-- Attempting to connect to a host we don't serve
+				session:close({
+					condition = "host-unknown";
+					text = "This host does not serve "..session.to_host
+				});
+				return;
+			elseif hosts[session.to_host].disallow_s2s then
+				-- Attempting to connect to a host that disallows s2s
+				session:close({
+					condition = "policy-violation";
+					text = "Server-to-server communication is not allowed to this host";
+				});
+				return;
+			end
 		end
 		send("<?xml version='1.0'?>");
 		send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback',
--- a/core/sessionmanager.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/core/sessionmanager.lua	Fri Jul 23 09:22:27 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 Jul 23 09:17:11 2010 +0100
+++ b/core/usermanager.lua	Fri Jul 23 09:22:27 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;
@@ -15,86 +16,120 @@
 local jid_bare = require "util.jid".bare;
 local config = require "core.configmanager";
 local hosts = hosts;
+local sasl_new = require "util.sasl".new;
 
 local require_provisioning = config.get("*", "core", "cyrus_require_provisioning") or false;
 
+local prosody = _G.prosody;
+
+local setmetatable = setmetatable;
+
+local default_provider = "internal_plain";
+
 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;
+	local function dummy_get_sasl_handler() return sasl_new(nil, {}); end
+	return setmetatable({name = "null", get_sasl_handler = dummy_get_sasl_handler}, { __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.";
+function initialize_host(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", initialize_host, 100);
+prosody.events.add_handler("component-activated", initialize_host, 100);
+
+function is_cyrus(host) return config.get(host, "core", "sasl_backend") == "cyrus"; end
+
+function test_password(username, host, password)
+	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)
+	local is_admin;
+	jid = jid_bare(jid);
 	host = host or "*";
-	local admins = config.get(host, "core", "admins");
-	if host ~= "*" and admins == config.get("*", "core", "admins") then
-		return nil;
+	
+	local host_admins = config.get(host, "core", "admins");
+	local global_admins = config.get("*", "core", "admins");
+	
+	if host_admins and host_admins ~= global_admins then
+		if type(host_admins) == "table" then
+			for _,admin in ipairs(host_admins) do
+				if admin == jid then
+					is_admin = true;
+					break;
+				end
+			end
+		elseif admins then
+			log("error", "Option 'admins' for host '%s' is not a list", host);
+		end
 	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 and global_admins then
+		if type(global_admins) == "table" then
+			for _,admin in ipairs(global_admins) do
+				if admin == jid then
+					is_admin = true;
+					break;
+				end
+			end
+		elseif admins then
+			log("error", "Global option 'admins' is not a list");
 		end
-	elseif admins then log("warn", "Option 'admins' for host '%s' is not a table", host); end
-	return nil;
+	end
+	
+	-- Still not an admin, check with auth provider
+	if not is_admin and host ~= "*" and hosts[host].users.is_admin then
+		is_admin = hosts[host].users.is_admin(jid);
+	end
+	return is_admin or false;
 end
 
 return _M;
--- a/net/adns.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/net/adns.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -36,12 +36,9 @@
 			end)(dns.peek(qname, qtype, qclass));
 end
 
-function cancel(handle, call_handler)
+function cancel(handle, call_handler, reason)
 	log("warn", "Cancelling DNS lookup for %s", tostring(handle[3]));
-	dns.cancel(handle);
-	if call_handler then
-		coroutine.resume(handle[4]);
-	end
+	dns.cancel(handle[1], handle[2], handle[3], handle[4], call_handler);
 end
 
 function new_async_socket(sock, resolver)
--- a/net/dns.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/net/dns.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -16,6 +16,8 @@
 
 local socket = require "socket";
 local ztact = require "util.ztact";
+local timer = require "util.timer";
+
 local _, windows = pcall(require, "util.windows");
 local is_windows = (_ and windows) or os.getenv("WINDIR");
 
@@ -27,6 +29,7 @@
 
 local get, set = ztact.get, ztact.set;
 
+local default_timeout = 15;
 
 -------------------------------------------------- module dns
 module('dns')
@@ -115,6 +118,7 @@
 local resolver = {};
 resolver.__index = resolver;
 
+resolver.timeout = default_timeout;
 
 local SRV_tostring;
 
@@ -678,7 +682,28 @@
 		--set(self.yielded, co, qclass, qtype, qname, true);
 	end
 
-	self:getsocket (o.server):send (o.packet)
+	local conn = self:getsocket(o.server)
+	conn:send (o.packet)
+	
+	if timer and self.timeout then
+		local num_servers = #self.server;
+		local i = 1;
+		timer.add_task(self.timeout, function ()
+			if get(self.wanted, qclass, qtype, qname, co) then
+				if i < num_servers then
+					i = i + 1;
+					self:servfail(conn);
+					o.server = self.best_server;
+					conn = self:getsocket(o.server);
+					conn:send(o.packet);
+					return self.timeout;
+				else
+					-- Tried everything, failed
+					self:cancel(qclass, qtype, qname, co, true);
+				end
+			end
+		end)
+	end
 end
 
 function resolver:servfail(sock)
@@ -720,6 +745,10 @@
 	end
 end
 
+function resolver:settimeout(seconds)
+	self.timeout = seconds;
+end
+
 function resolver:receive(rset)    -- - - - - - - - - - - - - - - - -  receive
 	--print('receive');  print(self.socket);
 	self.time = socket.gettime();
@@ -806,10 +835,13 @@
 	return response;
 end
 
-function resolver:cancel(data)
-	local cos = get(self.wanted, unpack(data, 1, 3));
+function resolver:cancel(qclass, qtype, qname, co, call_handler)
+	local cos = get(self.wanted, qclass, qtype, qname);
 	if cos then
-		cos[data[4]] = nil;
+		if call_handler then
+			coroutine.resume(co);
+		end
+		cos[co] = nil;
 	end
 end
 
@@ -961,6 +993,10 @@
 	return _resolver:cancel(...);
 end
 
+function dns.settimeout(...)
+	return _resolver:settimeout(...);
+end
+
 function dns.socket_wrapper_set(...)    -- - - - - - - - -  socket_wrapper_set
 	return _resolver:socket_wrapper_set(...);
 end
--- a/net/multiplex_listener.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/net/multiplex_listener.lua	Fri Jul 23 09:22:27 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/server_event.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/net/server_event.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -143,9 +143,9 @@
 					debug( "new connection failed. id:", self.id, "error:", self.fatalerror )
 				else
 					if plainssl and ssl then  -- start ssl session
-						self:starttls()
+						self:starttls(nil, true)
 					else  -- normal connection
-						self:_start_session( self.listener.onconnect )
+						self:_start_session(true)
 					end
 					debug( "new connection established. id:", self.id )
 				end
@@ -155,13 +155,18 @@
 			self.eventconnect = addevent( base, self.conn, EV_WRITE, callback, cfg.CONNECT_TIMEOUT )
 			return true
 	end
-	function interface_mt:_start_session(onconnect) -- new session, for example after startssl
+	function interface_mt:_start_session(call_onconnect) -- new session, for example after startssl
 		if self.type == "client" then
 			local callback = function( )
 				self:_lock( false,  false, false )
 				--vdebug( "start listening on client socket with id:", self.id )
 				self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT );  -- register callback
-				self:onconnect()
+				if call_onconnect then
+					debug("CALLING ONCONNECT")
+					self:onconnect()
+				else
+					debug("NOT CALLING ONCONNECT");
+				end
 				self.eventsession = nil
 				return -1
 			end
@@ -173,7 +178,7 @@
 		end
 		return true
 	end
-	function interface_mt:_start_ssl(arg) -- old socket will be destroyed, therefore we have to close read/write events first
+	function interface_mt:_start_ssl(call_onconnect) -- old socket will be destroyed, therefore we have to close read/write events first
 			--vdebug( "starting ssl session with client id:", self.id )
 			local _
 			_ = self.eventread and self.eventread:close( )  -- close events; this must be called outside of the event callbacks!
@@ -184,7 +189,7 @@
 			if err then
 				self.fatalerror = err
 				self.conn = nil  -- cannot be used anymore
-				if "onconnect" == arg then
+				if call_onconnect then
 					self.ondisconnect = nil  -- dont call this when client isnt really connected
 				end
 				self:_close()
@@ -211,14 +216,11 @@
 								self.send = self.conn.send  -- caching table lookups with new client object
 								self.receive = self.conn.receive
 								local onsomething
-								if "onconnect" == arg then  -- trigger listener
-									onsomething = self.onconnect
-								else
-									onsomething = self.onsslconnection
+								if not call_onconnect then  -- trigger listener
+									self:onstatus("ssl-handshake-complete");
 								end
-								self:_start_session( onsomething )
+								self:_start_session( call_onconnect )
 								debug( "ssl handshake done" )
-								self:onstatus("ssl-handshake-complete");
 								self.eventhandshake = nil
 								return -1
 							end
@@ -232,7 +234,7 @@
 							end
 						end
 						if self.fatalerror then
-							if "onconnect" == arg then
+							if call_onconnect then
 								self.ondisconnect = nil  -- dont call this when client isnt really connected
 							end
 							self:_close()
@@ -414,7 +416,7 @@
 		-- No-op, we always use the underlying connection's send
 	end
 	
-	function interface_mt:starttls(sslctx)
+	function interface_mt:starttls(sslctx, call_onconnect)
 		debug( "try to start ssl at client id:", self.id )
 		local err
 		self._sslctx = sslctx;
@@ -428,7 +430,7 @@
 		self._usingssl = true
 		self.startsslcallback = function( )  -- we have to start the handshake outside of a read/write event
 			self.startsslcallback = nil
-			self:_start_ssl();
+			self:_start_ssl(call_onconnect);
 			self.eventstarthandshake = nil
 			return -1
 		end
@@ -468,7 +470,6 @@
 	function interface_mt:ondrain()
 	end
 	function interface_mt:onstatus()
-		debug("server.lua: Dummy onstatus()")
 	end
 end
 
@@ -700,9 +701,9 @@
 				local clientinterface = handleclient( client, client_ip, client_port, interface, pattern, listener, nil, sslctx )
 				--vdebug( "client id:", clientinterface, "startssl:", startssl )
 				if ssl and sslctx then
-					clientinterface:starttls(sslctx)
+					clientinterface:starttls(sslctx, true)
 				else
-					clientinterface:_start_session( clientinterface.onconnect )
+					clientinterface:_start_session( true )
 				end
 				debug( "accepted incoming client connection from:", client_ip or "<unknown IP>", client_port or "<unknown port>", "to", port or "<unknown port>");
 				
--- a/net/server_select.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/net/server_select.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -167,7 +167,7 @@
 
 	local connections = 0
 
-	local dispatch, disconnect = listeners.onincoming, listeners.ondisconnect
+	local dispatch, disconnect = listeners.onconnect or listeners.onincoming, listeners.ondisconnect
 
 	local accept = socket.accept
 
@@ -483,7 +483,7 @@
 			if drain then
 				drain(handler)
 			end
-			_ = needtls and handler:starttls(nil, true)
+			_ = needtls and handler:starttls(nil)
 			_ = toclose and handler:close( )
 			return true
 		elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write
@@ -564,13 +564,13 @@
 			end
 		else
 			local sslctx;
-			handler.starttls = function( self, _sslctx, now )
+			handler.starttls = function( self, _sslctx)
 				if _sslctx then
 					sslctx = _sslctx;
 					handler:set_sslctx(sslctx);
 				end
-				if not now then
-					out_put "server.lua: we need to do tls, but delaying until later"
+				if bufferqueuelen > 0 then
+					out_put "server.lua: we need to do tls, but delaying until send buffer empty"
 					needtls = true
 					return
 				end
@@ -623,16 +623,6 @@
 
 	_socketlist[ socket ] = handler
 	_readlistlen = addsocket(_readlist, socket, _readlistlen)
-	if listeners.onconnect then
-		_sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
-		handler.sendbuffer = function ()
-			listeners.onconnect(handler);
-			handler.sendbuffer = _sendbuffer;
-			if bufferqueuelen > 0 then
-				return _sendbuffer();
-			end
-		end
-	end
 	return handler, socket
 end
 
@@ -854,6 +844,18 @@
 	local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx )
 	_socketlist[ socket ] = handler
 	_sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
+	if listeners.onconnect then
+		-- When socket is writeable, call onconnect
+		local _sendbuffer = handler.sendbuffer;
+		handler.sendbuffer = function ()
+			listeners.onconnect(handler);
+			handler.sendbuffer = _sendbuffer;
+			-- If there was data with the incoming packet, handle it now.
+			if #handler:bufferqueue() > 0 then
+				return _sendbuffer();
+			end
+		end
+	end
 	return handler, socket
 end
 
--- a/net/xmppclient_listener.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/net/xmppclient_listener.lua	Fri Jul 23 09:22:27 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;
@@ -63,8 +63,11 @@
 end
 
 local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), debug.traceback()); end
-function stream_callbacks.handlestanza(a, b)
-	xpcall(function () core_process_stanza(a, b) end, handleerr);
+function stream_callbacks.handlestanza(session, stanza)
+	stanza = session.filter("stanzas/in", stanza);
+	if stanza then
+		return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+	end
 end
 
 local sessions = {};
@@ -72,23 +75,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 +114,54 @@
 
 -- 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)
+		return handlestanza(session, stanza);
+	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/xmppcomponent_listener.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/net/xmppcomponent_listener.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -18,6 +18,7 @@
 local cm_register_component = require "core.componentmanager".register_component;
 local cm_deregister_component = require "core.componentmanager".deregister_component;
 local uuid_gen = require "util.uuid".generate;
+local jid_split = require "util.jid".split;
 local sha1 = require "util.hashes".sha1;
 local st = require "util.stanza";
 local init_xmlhandlers = require "core.xmlhandlers";
@@ -99,6 +100,31 @@
 	if not stanza.attr.xmlns and stanza.name == "handshake" then
 		stanza.attr.xmlns = xmlns_component;
 	end
+	if not stanza.attr.xmlns or stanza.attr.xmlns == "jabber:client" then
+		local from = stanza.attr.from;
+		if from then
+			if session.component_validate_from then
+				local _, domain = jid_split(stanza.attr.from);
+				if domain ~= session.host then
+					-- Return error
+					session.log("warn", "Component sent stanza with missing or invalid 'from' address");
+					session:close{
+						condition = "invalid-from";
+						text = "Component tried to send from address <"..tostring(from)
+							   .."> which is not in domain <"..tostring(session.host)..">";
+					};
+					return;
+				end
+			end
+		else
+			stanza.attr.from = session.host;
+		end
+		if not stanza.attr.to then
+			session.log("warn", "Rejecting stanza with no 'to' address");
+			session.send(st.error_reply(stanza, "modify", "bad-request", "Components MUST specify a 'to' address on stanzas"));
+			return;
+		end
+	end
 	return core_process_stanza(session, stanza);
 end
 
--- a/net/xmppserver_listener.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/net/xmppserver_listener.lua	Fri Jul 23 09:22:27 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;
@@ -49,11 +49,14 @@
 end
 
 local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), debug.traceback()); end
-function stream_callbacks.handlestanza(a, b)
-	if b.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
-		b.attr.xmlns = nil;
+function stream_callbacks.handlestanza(session, stanza)
+	if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
+		stanza.attr.xmlns = nil;
 	end
-	xpcall(function () core_process_stanza(a, b) end, handleerr);
+	stanza = session.filter("stanzas/in", stanza);
+	if stanza then
+		return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+	end
 end
 
 local connlisteners_register = require "net.connlisteners".register;
@@ -72,24 +75,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 +117,55 @@
 
 -- 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)
+		return handlestanza(session, stanza);
+	end
+end
+
+function xmppserver.onconnect(conn)
+	if not sessions[conn] then -- May be an existing outgoing session
+		local session = s2s_new_incoming(conn);
 		sessions[conn] = session;
-
+	
 		-- Logging functions --
-
-		
 		local conn_name = "s2sin"..tostring(conn):match("[a-f0-9]+$");
 		session.log = logger.init(conn_name);
 		
 		session.log("info", "Incoming s2s connection");
 		
-		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	Fri Jul 23 09:22:27 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.layout or content):form(content.data));
+		elseif name == "result" then
+			cmdtag:add_child((content.layout or content):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	Fri Jul 23 09:22:27 2010 +0100
@@ -0,0 +1,74 @@
+-- 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;
+	local privileged = 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 Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_announce.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -6,14 +6,38 @@
 -- COPYING file in the source package for more information.
 --
 
-local st, jid, set = require "util.stanza", require "util.jid", require "util.set";
+local st, jid = require "util.stanza", require "util.jid";
 
 local is_admin = require "core.usermanager".is_admin;
-local admins = set.new(config.get(module:get_host(), "core", "admins"));
+
+function send_to_online(message, host)
+	local sessions;
+	if host then
+		sessions = { [host] = hosts[host] };
+	else
+		sessions = hosts;
+	end
 
-function handle_announcement(data)
-	local origin, stanza = data.origin, data.stanza;
-	local host, resource = select(2, jid.split(stanza.attr.to));
+	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(event)
+	local origin, stanza = event.origin, event.stanza;
+	local node, host, resource = jid.split(stanza.attr.to);
 	
 	if resource ~= "announce/online" then
 		return; -- Not an announcement
@@ -21,25 +45,56 @@
 	
 	if not is_admin(stanza.attr.from) then
 		-- Not an admin? Not allowed!
-		module:log("warn", "Non-admin %s tried to send server announcement", tostring(jid.bare(stanza.attr.from)));
+		module:log("warn", "Non-admin '%s' tried to send server announcement", stanza.attr.from);
 		return;
 	end
 	
 	module:log("info", "Sending server announcement to all online users");
-	local host_session = hosts[host];
 	local message = st.clone(stanza);
 	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	Fri Jul 23 09:22:27 2010 +0100
@@ -0,0 +1,69 @@
+-- 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("auth_anonymous");
+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
+
+	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	Fri Jul 23 09:22:27 2010 +0100
@@ -0,0 +1,61 @@
+-- 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("auth_cyrus");
+
+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
+
+	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	Fri Jul 23 09:22:27 2010 +0100
@@ -0,0 +1,178 @@
+-- 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("auth_internal_hashed");
+local type = type;
+local error = error;
+local ipairs = ipairs;
+local hashes = require "util.hashes";
+local jid_bare = require "util.jid".bare;
+local getAuthenticationDatabaseSHA1 = require "util.sasl.scram".getAuthenticationDatabaseSHA1;
+local config = require "core.configmanager";
+local usermanager = require "core.usermanager";
+local generate_uuid = require "util.uuid".generate;
+local new_sasl = require "util.sasl".new;
+local nodeprep = require "util.encodings".stringprep.nodeprep;
+local hosts = hosts;
+
+-- COMPAT w/old trunk: remove these two lines before 0.8 release
+local hmac_sha1 = require "util.hmac".sha1;
+local sha1 = require "util.hashes".sha1;
+
+local to_hex;
+do
+	local function replace_byte_with_hex(byte)
+		return ("%02x"):format(byte:byte());
+	end
+	function to_hex(binary_string)
+		return binary_string:gsub(".", replace_byte_with_hex);
+	end
+end
+
+local from_hex;
+do
+	local function replace_hex_with_byte(hex)
+		return string.char(tonumber(hex, 16));
+	end
+	function from_hex(hex_string)
+		return hex_string:gsub("..", replace_hex_with_byte);
+	end
+end
+
+
+local prosody = _G.prosody;
+
+-- Default; can be set per-user
+local iteration_count = 4096;
+
+function new_hashpass_provider(host)
+	local provider = { name = "internal_hashed" };
+	log("debug", "initializing hashpass authentication provider for host '%s'", host);
+
+	function provider.test_password(username, password)
+		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
+		-- COMPAT w/old trunk: remove before 0.8 release
+		if credentials.hashpass then
+			local salted_password = from_hex(credentials.hashpass);
+			credentials.stored_key = sha1(hmac_sha1(salted_password, "Client Key"), true);
+			credentials.server_key = to_hex(hmac_sha1(salted_password, "Server Key"));
+			credentials.hashpass = nil
+			datamanager.store(username, host, "accounts", credentials);
+		end
+		
+		local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, credentials.salt, credentials.iteration_count);
+		
+		local stored_key_hex = to_hex(stored_key);
+		local server_key_hex = to_hex(server_key);
+		
+		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)
+		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 = to_hex(stored_key);
+			local server_key_hex = to_hex(server_key);
+			
+			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)
+		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
+		return true;
+	end
+
+	function provider.create_user(username, password)
+		local salt = generate_uuid();
+		local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count);
+		local stored_key_hex = to_hex(stored_key);
+		local server_key_hex = to_hex(server_key);
+		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, realm, password), true;
+			end,
+			scram_sha_1 = function(username, realm)
+				local credentials = datamanager.load(username, host, "accounts");
+				if not credentials then return; end
+				if credentials.password then
+					usermanager.set_password(username, credentials.password, host);
+					credentials = datamanager.load(username, host, "accounts");
+					if not credentials then return; end
+				end
+				
+				-- convert hexpass to stored_key and server_key
+				-- COMPAT w/old trunk: remove before 0.8 release
+				if credentials.hashpass then
+					local salted_password = from_hex(credentials.hashpass);
+					credentials.stored_key = sha1(hmac_sha1(salted_password, "Client Key"), true);
+					credentials.server_key = to_hex(hmac_sha1(salted_password, "Server Key"));
+					credentials.hashpass = nil
+					datamanager.store(username, host, "accounts", credentials);
+				end
+			
+				local stored_key, server_key, iteration_count, salt = credentials.stored_key, credentials.server_key, credentials.iteration_count, credentials.salt;
+				stored_key = stored_key and from_hex(stored_key);
+				server_key = server_key and from_hex(server_key);
+				return stored_key, server_key, iteration_count, salt, true;
+			end
+		};
+		return new_sasl(realm, testpass_authentication_profile);
+	end
+	
+	return provider;
+end
+
+module:add_item("auth-provider", new_hashpass_provider(module.host));
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_auth_internal_plain.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -0,0 +1,97 @@
+-- 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("auth_internal_plain");
+local type = type;
+local error = error;
+local ipairs = ipairs;
+local hashes = require "util.hashes";
+local jid_bare = require "util.jid".bare;
+local config = require "core.configmanager";
+local usermanager = require "core.usermanager";
+local new_sasl = require "util.sasl".new;
+local nodeprep = require "util.encodings".stringprep.nodeprep;
+local hosts = hosts;
+
+local prosody = _G.prosody;
+
+local is_cyrus = usermanager.is_cyrus;
+
+function new_default_provider(host)
+	local provider = { name = "internal_plain" };
+	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
+		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
+	
+	return provider;
+end
+
+module:add_item("auth-provider", new_default_provider(module.host));
+
--- a/plugins/mod_bosh.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_bosh.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -190,6 +190,11 @@
 		local r, send_buffer = session.requests, session.send_buffer;
 		local response = { headers = default_headers }
 		function session.send(s)
+			-- We need to ensure that outgoing stanzas have the jabber:client xmlns
+			if s.attr and not s.attr.xmlns then
+				s = st.clone(s);
+				s.attr.xmlns = "jabber:client";
+			end
 			--log("debug", "Sending BOSH data: %s", tostring(s));
 			local oldest_request = r[1];
 			if oldest_request then
--- a/plugins/mod_component.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_component.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -50,6 +50,8 @@
 	-- Authenticated now
 	log("info", "Component authenticated: %s", session.host);
 	
+	session.component_validate_from = module:get_option_boolean("validate_from_addresses") ~= false;
+	
 	-- If component not already created for this host, create one now
 	if not hosts[session.host].connected then
 		local send = session.send;
--- a/plugins/mod_compression.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_compression.lua	Fri Jul 23 09:22:27 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_disco.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_disco.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -11,6 +11,7 @@
 local jid_split = require "util.jid".split;
 local jid_bare = require "util.jid".bare;
 local st = require "util.stanza"
+local calculate_hash = require "util.caps".calculate_hash;
 
 local disco_items = module:get_option("disco_items") or {};
 do -- validate disco_items
@@ -35,27 +36,61 @@
 module:add_feature("http://jabber.org/protocol/disco#info");
 module:add_feature("http://jabber.org/protocol/disco#items");
 
-module:hook("iq/host/http://jabber.org/protocol/disco#info:query", function(event)
-	local origin, stanza = event.origin, event.stanza;
-	if stanza.attr.type ~= "get" then return; end
-	local node = stanza.tags[1].attr.node;
-	if node and node ~= "" then return; end -- TODO fire event?
-
-	local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#info");
+-- Generate and cache disco result and caps hash
+local _cached_server_disco_info, _cached_server_caps_feature, _cached_server_caps_hash;
+local function build_server_disco_info()
+	local query = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info" });
 	local done = {};
 	for _,identity in ipairs(module:get_host_items("identity")) do
 		local identity_s = identity.category.."\0"..identity.type;
 		if not done[identity_s] then
-			reply:tag("identity", identity):up();
+			query:tag("identity", identity):up();
 			done[identity_s] = true;
 		end
 	end
 	for _,feature in ipairs(module:get_host_items("feature")) do
 		if not done[feature] then
-			reply:tag("feature", {var=feature}):up();
+			query:tag("feature", {var=feature}):up();
 			done[feature] = true;
 		end
 	end
+	_cached_server_disco_info = query;
+	_cached_server_caps_hash = calculate_hash(query);
+	_cached_server_caps_feature = st.stanza("c", {
+		xmlns = "http://jabber.org/protocol/caps";
+		hash = "sha-1";
+		node = "http://prosody.im";
+		ver = _cached_server_caps_hash;
+	});
+end
+local function clear_disco_cache()
+	_cached_server_disco_info, _cached_server_caps_feature, _cached_server_caps_hash = nil, nil, nil;
+end
+local function get_server_disco_info()
+	if not _cached_server_disco_info then build_server_disco_info(); end
+	return _cached_server_disco_info;
+end
+local function get_server_caps_feature()
+	if not _cached_server_caps_feature then build_server_disco_info(); end
+	return _cached_server_caps_feature;
+end
+local function get_server_caps_hash()
+	if not _cached_server_caps_hash then build_server_disco_info(); end
+	return _cached_server_caps_hash;
+end
+
+module:hook("item-added/identity", clear_disco_cache);
+module:hook("item-added/feature", clear_disco_cache);
+
+-- Handle disco requests to the server
+module:hook("iq/host/http://jabber.org/protocol/disco#info:query", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	if stanza.attr.type ~= "get" then return; end
+	local node = stanza.tags[1].attr.node;
+	if node and node ~= "" and node ~= "http://prosody.im#"..get_server_caps_hash() then return; end -- TODO fire event?
+	local reply_query = get_server_disco_info();
+	reply_query.node = node;
+	local reply = st.reply(stanza):add_child(reply_query);
 	origin.send(reply);
 	return true;
 end);
@@ -75,6 +110,13 @@
 	origin.send(reply);
 	return true;
 end);
+
+-- Handle caps stream feature
+module:hook("stream-features", function (event)
+	event.features:add_child(get_server_caps_feature());
+end);
+
+-- Handle disco requests to user accounts
 module:hook("iq/bare/http://jabber.org/protocol/disco#info:query", function(event)
 	local origin, stanza = event.origin, event.stanza;
 	if stanza.attr.type ~= "get" then return; end
--- a/plugins/mod_groups.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_groups.lua	Fri Jul 23 09:22:27 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_httpserver.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_httpserver.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -8,9 +8,11 @@
 
 
 local httpserver = require "net.httpserver";
+local lfs = require "lfs";
 
 local open = io.open;
 local t_concat = table.concat;
+local stat = lfs.attributes;
 
 local http_base = config.get("*", "core", "http_path") or "www_files";
 
@@ -48,7 +50,14 @@
 end
 
 function serve_file(path)
-	local f, err = open(http_base..path, "rb");
+	local full_path = http_base..path;
+	if stat(full_path, "mode") == "directory" then
+		if stat(full_path.."/index.html", "mode") == "file" then
+			return serve_file(path.."/index.html");
+		end
+		return response_403;
+	end
+	local f, err = open(full_path, "rb");
 	if not f then return response_404; end
 	local data = f:read("*a");
 	f:close();
--- a/plugins/mod_iq.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_iq.lua	Fri Jul 23 09:22:27 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 Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_legacyauth.lua	Fri Jul 23 09:22:27 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, session.host, password) 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	Fri Jul 23 09:22:27 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	Fri Jul 23 09:22:27 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 Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_pep.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -16,9 +16,7 @@
 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;
+local calculate_hash = require "util.caps".calculate_hash;
 
 local NULL = {};
 local data = {};
@@ -40,8 +38,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 +116,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);
 
@@ -205,50 +208,6 @@
 	end
 end);
 
-local function calculate_hash(disco_info)
-	local identities, features, extensions = {}, {}, {};
-	for _, tag in pairs(disco_info) do
-		if tag.name == "identity" then
-			table.insert(identities, (tag.attr.category or "").."\0"..(tag.attr.type or "").."\0"..(tag.attr["xml:lang"] or "").."\0"..(tag.attr.name or ""));
-		elseif tag.name == "feature" then
-			table.insert(features, tag.attr.var or "");
-		elseif tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then
-			local form = {};
-			local FORM_TYPE;
-			for _, field in pairs(tag.tags) do
-				if field.name == "field" and field.attr.var then
-					local values = {};
-					for _, val in pairs(field.tags) do
-						val = #val.tags == 0 and table.concat(val); -- FIXME use get_text?
-						if val then table.insert(values, val); end
-					end
-					table.sort(values);
-					if field.attr.var == "FORM_TYPE" then
-						FORM_TYPE = values[1];
-					elseif #values > 0 then
-						table.insert(form, field.attr.var.."\0"..table.concat(values, "<"));
-					else
-						table.insert(form, field.attr.var);
-					end
-				end
-			end
-			table.sort(form);
-			form = table.concat(form, "<");
-			if FORM_TYPE then form = FORM_TYPE.."\0"..form; end
-			table.insert(extensions, form);
-		end
-	end
-	table.sort(identities);
-	table.sort(features);
-	table.sort(extensions);
-	if #identities > 0 then identities = table.concat(identities, "<"):gsub("%z", "/").."<"; else identities = ""; end
-	if #features > 0 then features = table.concat(features, "<").."<"; else features = ""; end
-	if #extensions > 0 then extensions = table.concat(extensions, "<"):gsub("%z", "<").."<"; else extensions = ""; end
-	local S = identities..features..extensions;
-	local ver = base64(sha1(S));
-	return ver, S;
-end
-
 module:hook("iq/bare/disco", function(event)
 	local session, stanza = event.origin, event.stanza;
 	if stanza.attr.type == "result" then
@@ -285,8 +244,8 @@
 end);
 
 module:hook("account-disco-items", function(event)
-	local session, stanza = event.session, event.stanza;
-	local bare = session.username..'@'..session.host;
+	local stanza = event.stanza;
+	local bare = stanza.attr.to;
 	local user_data = data[bare];
 
 	if user_data then
--- a/plugins/mod_posix.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_posix.lua	Fri Jul 23 09:22:27 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;
@@ -95,8 +95,17 @@
 				pidfile_handle = nil;
 				prosody.shutdown("Prosody already running");
 			else
-				pidfile_handle:write(tostring(pposix.getpid()));
-				pidfile_handle:flush();
+				pidfile_handle:close();
+				pidfile_handle, err = io.open(pidfile, "w+");
+				if not pidfile_handle then
+					module:log("error", "Couldn't write pidfile at %s; %s", pidfile, err);
+					prosody.shutdown("Couldn't write pidfile");
+				else
+					if lfs.lock(pidfile_handle, "w") then
+						pidfile_handle:write(tostring(pposix.getpid()));
+						pidfile_handle:flush();
+					end
+				end
 			end
 		end
 	end
@@ -141,7 +150,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 Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_presence.lua	Fri Jul 23 09:22:27 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 Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_privacy.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -442,7 +442,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_proxy65.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_proxy65.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -14,7 +14,7 @@
 	error("proxy65 should be loaded as a component, please see http://prosody.im/doc/components", 0);
 end
 
-local jid_split, jid_join = require "util.jid".split, require "util.jid".join;
+local jid_split, jid_join, jid_compare = require "util.jid".split, require "util.jid".join, require "util.jid".compare;
 local st = require "util.stanza";
 local componentmanager = require "core.componentmanager";
 local config_get = require "core.configmanager".get;
@@ -151,24 +151,11 @@
 	local err_reply = replies_cache.stream_host_err;
 	local sid = stanza.tags[1].attr.sid;
 	local allow = false;
-	local jid_node, jid_host, jid_resource = jid_split(stanza.attr.from);
-	
-	if stanza.attr.from == nil then
-		jid_node = origin.username;
-		jid_host = origin.host;
-		jid_resource = origin.resource;
-	end
+	local jid = stanza.attr.from;
 	
 	if proxy_acl and #proxy_acl > 0 then
-		if host ~= nil then -- at least a domain is needed.
-			for _, acl in ipairs(proxy_acl) do
-				local acl_node, acl_host, acl_resource = jid_split(acl);
-				if ((acl_node ~= nil and acl_node == jid_node) or acl_node == nil) and
-				   ((acl_host ~= nil and acl_host == jid_host) or acl_host == nil) and
-				   ((acl_resource ~= nil and acl_resource == jid_resource) or acl_resource == nil) then
-					allow = true;
-				end
-			end
+		for _, acl in ipairs(proxy_acl) do
+			if jid_compare(jid, acl) then allow = true; end
 		end
 	else
 		allow = true;
@@ -181,7 +168,7 @@
 			replies_cache.stream_host = reply;
 		end
 	else
-		module:log("warn", "Denying use of proxy for %s", tostring(jid_join(jid_node, jid_host, jid_resource)));
+		module:log("warn", "Denying use of proxy for %s", tostring(jid));
 		if err_reply == nil then
 			err_reply = st.iq({type="error", from=host})
 				:query("http://jabber.org/protocol/bytestreams")
--- a/plugins/mod_register.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_register.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -13,7 +13,6 @@
 local usermanager_user_exists = require "core.usermanager".user_exists;
 local usermanager_create_user = require "core.usermanager".create_user;
 local usermanager_set_password = require "core.usermanager".set_password;
-local datamanager_store = require "util.datamanager".store;
 local os_time = os.time;
 local nodeprep = require "util.encodings".stringprep.nodeprep;
 
@@ -35,7 +34,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));
@@ -71,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_roster.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_roster.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -42,15 +42,15 @@
 					if not (client_ver and server_ver) or client_ver ~= server_ver then
 						roster:query("jabber:iq:roster");
 						-- Client does not support versioning, or has stale roster
-						for jid in pairs(session.roster) do
+						for jid, item in pairs(session.roster) do
 							if jid ~= "pending" and jid then
 								roster:tag("item", {
 									jid = jid,
-									subscription = session.roster[jid].subscription,
-									ask = session.roster[jid].ask,
-									name = session.roster[jid].name,
+									subscription = item.subscription,
+									ask = item.ask,
+									name = item.name,
 								});
-								for group in pairs(session.roster[jid].groups) do
+								for group in pairs(item.groups) do
 									roster:tag("group"):text(group):up();
 								end
 								roster:up(); -- move out from item
--- a/plugins/mod_saslauth.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_saslauth.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -14,19 +14,14 @@
 local base64 = require "util.encodings".base64;
 
 local nodeprep = require "util.encodings".stringprep.nodeprep;
-local datamanager_load = require "util.datamanager".load;
-local usermanager_validate_credentials = require "core.usermanager".validate_credentials;
-local usermanager_get_supported_methods = require "core.usermanager".get_supported_methods;
+local usermanager_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 t_concat, t_insert = table.concat, table.insert;
 local tostring = tostring;
-local jid_split = require "util.jid".split;
-local md5 = require "util.hashes".md5;
-local config = require "core.configmanager";
 
 local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
 local sasl_backend = module:get_option("sasl_backend") or "builtin";
+local anonymous_login = module:get_option("anonymous_login");
 
 -- Cyrus config options
 local require_provisioning = module:get_option("cyrus_require_provisioning") or false;
@@ -66,21 +61,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
@@ -111,8 +91,8 @@
 		local username = nodeprep(session.sasl_handler.username);
 
 		if not(require_provisioning) or usermanager_user_exists(username, session.host) then
-			local aret, err = sm_make_authenticated(session, session.sasl_handler.username);
-			if aret then
+			local ok, err = sm_make_authenticated(session, session.sasl_handler.username);
+			if ok then
 				session.sasl_handler = nil;
 				session:reset_stream();
 			else
@@ -132,7 +112,7 @@
 local function sasl_handler(session, stanza)
 	if stanza.name == "auth" then
 		-- FIXME ignoring duplicates because ejabberd does
-		if config.get(session.host or "*", "core", "anonymous_login") then
+		if anonymous_login then
 			if stanza.attr.mechanism ~= "ANONYMOUS" then
 				return session.send(build_reply("failure", "invalid-mechanism"));
 			end
@@ -179,18 +159,17 @@
 		if secure_auth_only and not origin.secure then
 			return;
 		end
-		local realm = module:get_option("sasl_realm") or origin.host;
-		if module:get_option("anonymous_login") then
-			origin.sasl_handler = new_sasl(realm, anonymous_authentication_profile);
+		if anonymous_login then
+			origin.sasl_handler = new_sasl(module.host, 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
 		end
 		features:tag("mechanisms", mechanisms_attr);
-		for k, v in pairs(origin.sasl_handler:mechanisms()) do
-			features:tag("mechanism"):text(v):up();
+		for k in pairs(origin.sasl_handler:mechanisms()) do
+			features:tag("mechanism"):text(k):up();
 		end
 		features:up();
 	else
--- a/plugins/mod_tls.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_tls.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -83,7 +83,7 @@
 	module:log("debug", "Proceeding with TLS on s2sout...");
 	session:reset_stream();
 	local ssl_ctx = session.from_host and hosts[session.from_host].ssl_ctx or global_ssl_ctx;
-	session.conn:starttls(ssl_ctx, true);
+	session.conn:starttls(ssl_ctx);
 	session.secure = false;
 	return true;
 end);
--- a/plugins/mod_uptime.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/mod_uptime.lua	Fri Jul 23 09:22:27 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/mod_muc.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/muc/mod_muc.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -31,8 +31,11 @@
 local persistent_rooms = datamanager.load(nil, muc_host, "persistent") or {};
 local component;
 
+-- Configurable options
+local max_history_messages = module:get_option_number("max_history_messages");
+
 local function is_admin(jid)
-	return um_is_admin(jid) or um_is_admin(jid, module.host);
+	return um_is_admin(jid, module.host);
 end
 
 local function room_route_stanza(room, stanza) core_post_stanza(component, stanza); end
@@ -58,15 +61,20 @@
 for jid in pairs(persistent_rooms) do
 	local node = jid_split(jid);
 	local data = datamanager.load(node, muc_host, "config") or {};
-	local room = muc_new_room(jid);
+	local room = muc_new_room(jid, {
+		history_length = max_history_messages;
+	});
 	room._data = data._data;
+	room._data.history_length = max_history_messages; --TODO: Need to allow per-room with a global limit
 	room._affiliations = data._affiliations;
 	room.route_stanza = room_route_stanza;
 	room.save = room_save;
 	rooms[jid] = room;
 end
 
-local host_room = muc_new_room(muc_host);
+local host_room = muc_new_room(muc_host, {
+	history_length = max_history_messages;
+});
 host_room.route_stanza = room_route_stanza;
 host_room.save = room_save;
 
@@ -78,7 +86,7 @@
 local function get_disco_items(stanza)
 	local reply = st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
 	for jid, room in pairs(rooms) do
-		if not room._data.hidden then
+		if not room:is_hidden() then
 			reply:tag("item", {jid=jid, name=jid}):up();
 		end
 	end
@@ -113,7 +121,9 @@
 			local room = rooms[bare];
 			if not room then
 				if not(restrict_room_creation) or is_admin(stanza.attr.from) then
-					room = muc_new_room(bare);
+					room = muc_new_room(bare, {
+						history_length = max_history_messages;
+					});
 					room.route_stanza = room_route_stanza;
 					room.save = room_save;
 					rooms[bare] = room;
--- a/plugins/muc/muc.lib.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/plugins/muc/muc.lib.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -6,6 +6,9 @@
 -- COPYING file in the source package for more information.
 --
 
+local select = select;
+local pairs, ipairs = pairs, ipairs;
+
 local datamanager = require "util.datamanager";
 local datetime = require "util.datetime";
 
@@ -21,7 +24,7 @@
 local md5 = require "util.hashes".md5;
 
 local muc_domain = nil; --module:get_host();
-local history_length = 20;
+local default_history_length = 20;
 
 ------------
 local function filter_xmlns_from_array(array, filters)
@@ -88,8 +91,12 @@
 function room_mt:get_default_role(affiliation)
 	if affiliation == "owner" or affiliation == "admin" then
 		return "moderator";
-	elseif affiliation == "member" or not affiliation then
+	elseif affiliation == "member" then
 		return "participant";
+	elseif not affiliation then
+		if not self:is_members_only() then
+			return self:is_moderated() and "visitor" or "participant";
+		end
 	end
 end
 
@@ -122,10 +129,14 @@
 		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));
-		while #history > history_length do t_remove(history, 1) end
+		local entry = { stanza = stanza, stamp = stamp };
+		t_insert(history, entry);
+		while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
 	end
 end
 function room_mt:broadcast_except_nick(stanza, nick)
@@ -151,24 +162,65 @@
 		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
 	if self._data['subject'] then
-		self:_route_stanza(st.message({type='groupchat', from=self.jid, to=to}):tag("subject"):text(self._data['subject']));
+		self:_route_stanza(st.message({type='groupchat', from=self._data['subject_from'] or self.jid, to=to}):tag("subject"):text(self._data['subject']));
 	end
 end
 
 function room_mt:get_disco_info(stanza)
 	return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
 		:tag("identity", {category="conference", type="text"}):up()
-		:tag("feature", {var="http://jabber.org/protocol/muc"});
+		:tag("feature", {var="http://jabber.org/protocol/muc"}):up()
+		:tag("feature", {var=self:get_password() and "muc_passwordprotected" or "muc_unsecured"}):up()
+		:tag("feature", {var=self:is_moderated() and "muc_moderated" or "muc_unmoderated"}):up()
+		:tag("feature", {var=self:is_members_only() and "muc_membersonly" or "muc_open"}):up()
+		:tag("feature", {var=self:is_persistent() and "muc_persistent" or "muc_temporary"}):up()
+		:tag("feature", {var=self:is_hidden() and "muc_hidden" or "muc_public"}):up()
+		:tag("feature", {var=self._data.whois ~= "anyone" and "muc_semianonymous" or "muc_nonanonymous"}):up()
+	;
 end
 function room_mt:get_disco_items(stanza)
 	local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
@@ -181,6 +233,7 @@
 	-- TODO check nick's authority
 	if subject == "" then subject = nil; end
 	self._data['subject'] = subject;
+	self._data['subject_from'] = current_nick;
 	if self.save then self:save(); end
 	local msg = st.message({type='groupchat', from=current_nick})
 		:tag('subject'):text(subject):up();
@@ -198,6 +251,57 @@
 		:tag('status'):text(error_message);
 end
 
+function room_mt:set_password(password)
+	if password == "" or type(password) ~= "string" then password = nil; end
+	if self._data.password ~= password then
+		self._data.password = password;
+		if self.save then self:save(true); end
+	end
+end
+function room_mt:get_password()
+	return self._data.password;
+end
+function room_mt:set_moderated(moderated)
+	moderated = moderated and true or nil;
+	if self._data.moderated ~= moderated then
+		self._data.moderated = moderated;
+		if self.save then self:save(true); end
+	end
+end
+function room_mt:is_moderated()
+	return self._data.moderated;
+end
+function room_mt:set_members_only(members_only)
+	members_only = members_only and true or nil;
+	if self._data.members_only ~= members_only then
+		self._data.members_only = members_only;
+		if self.save then self:save(true); end
+	end
+end
+function room_mt:is_members_only()
+	return self._data.members_only;
+end
+function room_mt:set_persistent(persistent)
+	persistent = persistent and true or nil;
+	if self._data.persistent ~= persistent then
+		self._data.persistent = persistent;
+		if self.save then self:save(true); end
+	end
+end
+function room_mt:is_persistent()
+	return self._data.persistent;
+end
+function room_mt:set_hidden(hidden)
+	hidden = hidden and true or nil;
+	if self._data.hidden ~= hidden then
+		self._data.hidden = hidden;
+		if self.save then self:save(true); end
+	end
+end
+function room_mt:is_hidden()
+	return self._data.hidden;
+end
+
 function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
 	local from, to = stanza.attr.from, stanza.attr.to;
 	local room = jid_bare(to);
@@ -290,7 +394,15 @@
 					end
 					is_merge = true;
 				end
-				if not new_nick then
+				local password = stanza:get_child("x", "http://jabber.org/protocol/muc");
+				password = password and password:get_child("password", "http://jabber.org/protocol/muc");
+				password = password and password[1] ~= "" and password[1];
+				if self:get_password() and self:get_password() ~= password then
+					log("debug", "%s couldn't join due to invalid password: %s", from, to);
+					local reply = st.error_reply(stanza, "auth", "not-authorized"):up();
+					reply.tags[1].attr.code = "401";
+					origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
+				elseif not new_nick then
 					log("debug", "%s couldn't join due to nick conflict: %s", from, to);
 					local reply = st.error_reply(stanza, "cancel", "conflict"):up();
 					reply.tags[1].attr.code = "409";
@@ -319,12 +431,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";
@@ -392,10 +499,10 @@
 			:tag("instructions"):text(title):up()
 			:tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
 			:tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
-				:tag("value"):text(self._data.persistent and "1" or "0"):up()
+				:tag("value"):text(self:is_persistent() and "1" or "0"):up()
 			:up()
 			:tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
-				:tag("value"):text(self._data.hidden and "0" or "1"):up()
+				:tag("value"):text(self:is_hidden() and "0" or "1"):up()
 			:up()
 			:tag("field", {type='list-single', label='Who May Discover Real JIDs?', var='muc#roomconfig_whois'})
 			    :tag("value"):text(self._data.whois or 'moderators'):up()
@@ -406,6 +513,15 @@
 				:tag("value"):text('anyone'):up()
 				:up()
 			:up()
+			:tag("field", {type='text-private', label='Password', var='muc#roomconfig_roomsecret'})
+				:tag("value"):text(self:get_password() or ""):up()
+			:up()
+			:tag("field", {type='boolean', label='Make Room Moderated?', var='muc#roomconfig_moderatedroom'})
+				:tag("value"):text(self:is_moderated() and "1" or "0"):up()
+			:up()
+			:tag("field", {type='boolean', label='Make Room Members-Only?', var='muc#roomconfig_membersonly'})
+				:tag("value"):text(self:is_members_only() and "1" or "0"):up()
+			:up()
 	);
 end
 
@@ -434,15 +550,25 @@
 	local persistent = fields['muc#roomconfig_persistentroom'];
 	if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
 	else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
-	dirty = dirty or (self._data.persistent ~= persistent)
-	self._data.persistent = persistent;
+	dirty = dirty or (self:is_persistent() ~= persistent)
 	module:log("debug", "persistent=%s", tostring(persistent));
 
+	local moderated = fields['muc#roomconfig_moderatedroom'];
+	if moderated == "0" or moderated == "false" then moderated = nil; elseif moderated == "1" or moderated == "true" then moderated = true;
+	else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+	dirty = dirty or (self:is_moderated() ~= moderated)
+	module:log("debug", "moderated=%s", tostring(moderated));
+
+	local membersonly = fields['muc#roomconfig_membersonly'];
+	if membersonly == "0" or membersonly == "false" then membersonly = nil; elseif membersonly == "1" or membersonly == "true" then membersonly = true;
+	else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+	dirty = dirty or (self:is_members_only() ~= membersonly)
+	module:log("debug", "membersonly=%s", tostring(membersonly));
+
 	local public = fields['muc#roomconfig_publicroom'];
 	if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
 	else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
-	dirty = dirty or (self._data.hidden ~= (not public and true or nil))
-	self._data.hidden = not public and true or nil;
+	dirty = dirty or (self:is_hidden() ~= (not public and true or nil))
 
 	local whois = fields['muc#roomconfig_whois'];
 	if not valid_whois[whois] then
@@ -451,7 +577,16 @@
 	end
 	local whois_changed = self._data.whois ~= whois
 	self._data.whois = whois
-	module:log('debug', 'whois=%s', tostring(whois))
+	module:log('debug', 'whois=%s', whois)
+
+	local password = fields['muc#roomconfig_roomsecret'];
+	if password then
+		self:set_password(password);
+	end
+	self:set_moderated(moderated);
+	self:set_members_only(membersonly);
+	self:set_persistent(persistent);
+	self:set_hidden(not public);
 
 	if self.save then self:save(true); end
 	origin.send(st.reply(stanza));
@@ -488,8 +623,7 @@
 		end
 		self._occupants[nick] = nil;
 	end
-	self._data.persistent = nil;
-	if self.save then self:save(true); end
+	self:set_persistent(false);
 end
 
 function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc
@@ -654,8 +788,11 @@
 					:tag('x', {xmlns='http://jabber.org/protocol/muc#user'})
 						:tag('invite', {from=_from})
 							:tag('reason'):text(_reason or ""):up()
-						:up()
-					:up()
+						:up();
+						if self:get_password() then
+							invite:tag("password"):text(self:get_password()):up();
+						end
+					invite:up()
 					:tag('x', {xmlns="jabber:x:conference", jid=_to}) -- COMPAT: Some older clients expect this
 						:text(_reason or "")
 					:up()
@@ -748,13 +885,29 @@
 	local session = self._occupants[nick];
 	return session and session.role or nil;
 end
+function room_mt:can_set_role(actor_jid, occupant_jid, role)
+	local actor = self._occupants[self._jid_nick[actor_jid]];
+	local occupant = self._occupants[occupant_jid];
+	
+	if not occupant or not actor then return nil, "modify", "not-acceptable"; end
+
+	if actor.role == "moderator" then
+		if occupant.affiliation ~= "owner" and occupant.affiliation ~= "admin" then
+			if actor.affiliation == "owner" or actor.affiliation == "admin" then
+				return true;
+			elseif occupant.role ~= "moderator" and role ~= "moderator" then
+				return true;
+			end
+		end
+	end
+	return nil, "cancel", "not-allowed";
+end
 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
+	local allowed, err_type, err_condition = self:can_set_role(actor, occupant_jid, role);
+	if not allowed then return allowed, err_type, err_condition; 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
 	local p = st.presence({from = occupant_jid})
 		:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
 			:tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"})
@@ -804,6 +957,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
@@ -817,13 +973,14 @@
 
 local _M = {}; -- module "muc"
 
-function _M.new_room(jid)
+function _M.new_room(jid, config)
 	return setmetatable({
 		jid = jid;
 		_jid_nick = {};
 		_occupants = {};
 		_data = {
-		    whois = 'moderators',
+		    whois = 'moderators';
+		    history_length = (config and config.history_length);
 		};
 		_affiliations = {};
 	}, room_mt);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/storage/ejabberdstore.lib.lua	Fri Jul 23 09:22:27 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	Fri Jul 23 09:22:27 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 = pcall(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	Fri Jul 23 09:22:27 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	Fri Jul 23 09:22:27 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	Fri Jul 23 09:22:27 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 Jul 23 09:17:11 2010 +0100
+++ b/prosody	Fri Jul 23 09:22:27 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;
@@ -166,10 +163,25 @@
 	prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
 	                  plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
 	
+	local path_sep = package.config:sub(1,1);
+	local rel_path_start = ".."..path_sep;
+	function prosody.resolve_relative_path(path)
+		if path then
+			local is_relative;
+			if path_sep == "/" and path:sub(1,1) ~= "/" then
+				is_relative = true;
+			elseif path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\") then
+				is_relative = true;
+			end
+			if is_relative then
+				return CFG_CONFIGDIR..path_sep..path;
+			end
+		end
+		return path;
+	end
+	
 	prosody.arg = _G.arg;
 
-	prosody.events = require "util.events".new();
-	
 	prosody.platform = "unknown";
 	if os.getenv("WINDIR") then
 		prosody.platform = "windows";
@@ -200,7 +212,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 +302,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 +348,6 @@
 function prepare_to_start()
 	log("info", "Prosody is using the %s backend for connection handling", server.get_backend());
 	-- Signal to modules that we are ready to start
-	eventmanager.fire_event("server-starting");
 	prosody.events.fire_event("server-starting");
 
 	-- start listening on sockets
@@ -455,14 +465,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/prosodyctl	Fri Jul 23 09:17:11 2010 +0100
+++ b/prosodyctl	Fri Jul 23 09:22:27 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
@@ -56,6 +64,8 @@
 		os.exit(1);
 	end
 end
+local original_logging_config = config.get("*", "core", "log");
+config.set("*", "core", "log", { { levels = { min="info" }, to = "console" } });
 
 require "core.loggingmanager"
 
@@ -63,8 +73,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);
 
@@ -103,6 +111,45 @@
 	print(tostring(pposix))
 end
 
+local function test_writeable(filename)
+	local f, err = io.open(filename, "a");
+	if not f then
+		return false, err;
+	end
+	f:close();
+	return true;
+end
+
+local unwriteable_files = {};
+if type(original_logging_config) == "string" and original_logging_config:sub(1,1) ~= "*" then
+	local ok, err = test_writeable(original_logging_config);
+	if not ok then
+		table.insert(unwriteable_files, err);
+	end
+elseif type(original_logging_config) == "table" then
+	for _, rule in ipairs(original_logging_config) do
+		if rule.filename then
+			local ok, err = test_writeable(rule.filename);
+			if not ok then
+				table.insert(unwriteable_files, err);
+			end
+		end
+	end
+end
+
+if #unwriteable_files > 0 then
+	print("One of more of the Prosody log files are not");
+	print("writeable, please correct the errors and try");
+	print("starting prosodyctl again.");
+	print("");
+	for _, err in ipairs(unwriteable_files) do
+		print(err);
+	end
+	print("");
+	os.exit(1);
+end
+
+
 local error_messages = setmetatable({ 
 		["invalid-username"] = "The given username is invalid in a Jabber ID";
 		["invalid-hostname"] = "The given hostname is invalid";
@@ -114,12 +161,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 +280,17 @@
 		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);
+	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 +319,12 @@
 		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);
+	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 +358,12 @@
 		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);
+	end
+
 	if not prosodyctl.user_exists { user = user, host = host } then
 		show_message [[That user does not exist on this server]]
 		return 1;
--- a/tests/test_util_jid.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/tests/test_util_jid.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -54,3 +54,14 @@
 	assert_equal(bare("user@host/"), nil, "invalid JID is nil");
 end
 
+function compare(compare)
+	assert_equal(compare("host", "host"), true, "host should match");
+	assert_equal(compare("host", "other-host"), false, "host should not match");
+	assert_equal(compare("other-user@host/resource", "host"), true, "host should match");
+	assert_equal(compare("other-user@host", "user@host"), false, "user should not match");
+	assert_equal(compare("user@host", "host"), true, "host should match");
+	assert_equal(compare("user@host/resource", "host"), true, "host should match");
+	assert_equal(compare("user@host/resource", "user@host"), true, "user and host should match");
+	assert_equal(compare("user@other-host", "host"), false, "host should not match");
+	assert_equal(compare("user@other-host", "user@host"), false, "host should not match");
+end
--- a/util-src/Makefile	Fri Jul 23 09:17:11 2010 +0100
+++ b/util-src/Makefile	Fri Jul 23 09:22:27 2010 +0100
@@ -16,7 +16,7 @@
 
 .o.so:
 	MACOSX_DEPLOYMENT_TARGET="10.3"; export MACOSX_DEPLOYMENT_TARGET;
-	$(LD) $(LDFLAGS) -o $@ $< -L$(LUA_LIBDIR) -llua$(LUA_SUFFIX) -lidn -lcrypto
+	$(LD) $(LDFLAGS) -o $@ $< -lidn -lcrypto
 
 all: encodings.so hashes.so pposix.so signal.so
 
--- a/util-src/signal.c	Fri Jul 23 09:17:11 2010 +0100
+++ b/util-src/signal.c	Fri Jul 23 09:22:27 2010 +0100
@@ -165,13 +165,13 @@
 
 static void sighook(lua_State *L, lua_Debug *ar)
 {
+  struct signal_event *event;
   /* restore the old hook */
   lua_sethook(L, Hsig, Hmask, Hcount);
 
   lua_pushstring(L, LUA_SIGNAL);
   lua_gettable(L, LUA_REGISTRYINDEX);
 
-  struct signal_event *event;
   while((event = signal_queue))
   {
     lua_pushnumber(L, event->Nsig);
@@ -326,7 +326,7 @@
   return 1;
 }
 
-#if defined _POSIX_SOURCE || (defined(sun) || defined(__sun))
+#if defined(__unix__) || defined(__APPLE__)
 
 /* define some posix only functions */
 
@@ -373,7 +373,7 @@
 static const struct luaL_Reg lsignal_lib[] = {
   {"signal", l_signal},
   {"raise", l_raise},
-#if defined _POSIX_SOURCE || (defined(sun) || defined(__sun))
+#if defined(__unix__) || defined(__APPLE__)
   {"kill", l_kill},
 #endif
   {NULL, NULL}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/caps.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -0,0 +1,61 @@
+-- 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 base64 = require "util.encodings".base64.encode;
+local sha1 = require "util.hashes".sha1;
+
+local t_insert, t_sort, t_concat = table.insert, table.sort, table.concat;
+local ipairs = ipairs;
+
+module "caps"
+
+function calculate_hash(disco_info)
+	local identities, features, extensions = {}, {}, {};
+	for _, tag in ipairs(disco_info) do
+		if tag.name == "identity" then
+			t_insert(identities, (tag.attr.category or "").."\0"..(tag.attr.type or "").."\0"..(tag.attr["xml:lang"] or "").."\0"..(tag.attr.name or ""));
+		elseif tag.name == "feature" then
+			t_insert(features, tag.attr.var or "");
+		elseif tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then
+			local form = {};
+			local FORM_TYPE;
+			for _, field in ipairs(tag.tags) do
+				if field.name == "field" and field.attr.var then
+					local values = {};
+					for _, val in ipairs(field.tags) do
+						val = #val.tags == 0 and val:get_text();
+						if val then t_insert(values, val); end
+					end
+					t_sort(values);
+					if field.attr.var == "FORM_TYPE" then
+						FORM_TYPE = values[1];
+					elseif #values > 0 then
+						t_insert(form, field.attr.var.."\0"..t_concat(values, "<"));
+					else
+						t_insert(form, field.attr.var);
+					end
+				end
+			end
+			t_sort(form);
+			form = t_concat(form, "<");
+			if FORM_TYPE then form = FORM_TYPE.."\0"..form; end
+			t_insert(extensions, form);
+		end
+	end
+	t_sort(identities);
+	t_sort(features);
+	t_sort(extensions);
+	if #identities > 0 then identities = t_concat(identities, "<"):gsub("%z", "/").."<"; else identities = ""; end
+	if #features > 0 then features = t_concat(features, "<").."<"; else features = ""; end
+	if #extensions > 0 then extensions = t_concat(extensions, "<"):gsub("%z", "<").."<"; else extensions = ""; end
+	local S = identities..features..extensions;
+	local ver = base64(sha1(S));
+	return ver, S;
+end
+
+return _M;
--- a/util/dataforms.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/util/dataforms.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -67,9 +67,25 @@
 					form:tag("value"):text(line):up();
 				end
 			elseif field_type == "list-single" then
+				local has_default = false;
 				for _, val in ipairs(value) do
 					if type(val) == "table" then
 						form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
+						if val.default and (not has_default) then
+							form:tag("value"):text(val.value):up();
+							has_default = true;
+						end
+					else
+						form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
+					end
+				end
+			elseif field_type == "list-multi" then
+				for _, val in ipairs(value) do
+					if type(val) == "table" then
+						form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up();
+						if val.default then
+							form:tag("value"):text(val.value):up();
+						end
 					else
 						form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up();
 					end
@@ -149,6 +165,17 @@
 field_readers["list-single"] =
 	field_readers["text-single"];
 
+field_readers["list-multi"] =
+	function (field_tag)
+		local result = {};
+		for value_tag in field_tag:childtags() do
+			if value_tag.name == "value" then
+				result[#result+1] = value_tag[1];
+			end
+		end
+		return result;
+	end
+
 field_readers["boolean"] = 
 	function (field_tag)
 		local value = field_tag:child_with_name("value");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/filters.lua	Fri Jul 23 09:22:27 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;
--- a/util/iterators.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/util/iterators.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -90,6 +90,15 @@
 	end, s;
 end
 
+-- Skip the first n items an iterator returns
+function skip(n, f, s, var)
+	for i=1,n do
+		var = f(s, var);
+	end
+	return f, s, var;
+end
+
+-- Return the last n items an iterator returns
 function tail(n, f, s, var)
 	local results, count = {}, 0;
 	while true do
--- a/util/jid.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/util/jid.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -78,4 +78,17 @@
 	return nil; -- Invalid JID
 end
 
+function compare(jid, acl)
+	-- compare jid to single acl rule
+	-- TODO compare to table of rules?
+	local jid_node, jid_host, jid_resource = _split(jid);
+	local acl_node, acl_host, acl_resource = _split(acl);
+	if ((acl_node ~= nil and acl_node == jid_node) or acl_node == nil) and
+		((acl_host ~= nil and acl_host == jid_host) or acl_host == nil) and
+		((acl_resource ~= nil and acl_resource == jid_resource) or acl_resource == nil) then
+		return true
+	end
+	return false
+end
+
 return _M;
--- a/util/logger.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/util/logger.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -103,6 +103,21 @@
 	return ok, ret;
 end
 
+function reset()
+	for k in pairs(name_sinks) do name_sinks[k] = nil; end
+	for level, handler_list in pairs(level_sinks) do
+		-- Clear all handlers for this level
+		for i = 1, #handler_list do
+			handler_list[i] = nil;
+		end
+	end
+	for k in pairs(name_patterns) do name_patterns[k] = nil; end
+
+	for _, modify_hook in pairs(modify_hooks) do
+		modify_hook();
+	end
+end
+
 function add_level_sink(level, sink_function)
 	if not level_sinks[level] then
 		level_sinks[level] = { sink_function };
--- a/util/prosodyctl.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/util/prosodyctl.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -21,6 +21,8 @@
 
 local CFG_SOURCEDIR = _G.CFG_SOURCEDIR;
 
+local prosody = prosody;
+
 module "prosodyctl"
 
 function adduser(params)
@@ -30,6 +32,11 @@
 	elseif not host then
 		return false, "invalid-hostname";
 	end
+
+	local provider = prosody.hosts[host].users;
+	if not(provider) or provider.name == "null" then
+		usermanager.initialize_host(host);
+	end
 	
 	local ok = usermanager.create_user(user, password, host);
 	if not ok then
@@ -39,6 +46,11 @@
 end
 
 function user_exists(params)
+	local provider = prosody.hosts[params.host].users;
+	if not(provider) or provider.name == "null" then
+		usermanager.initialize_host(params.host);
+	end
+	
 	return usermanager.user_exists(params.user, params.host);
 end
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/roster.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -0,0 +1,19 @@
+module "roster"
+
+local roster = {};
+roster.__index = roster;
+
+function new()
+	return setmetatable({}, roster);
+end
+
+function roster:subscribers()
+end
+
+function roster:subscriptions()
+end
+
+function roster:items()
+end
+
+return _M;
--- a/util/sasl.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/util/sasl.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -88,18 +88,21 @@
 
 -- get a list of possible SASL mechanims to use
 function method:mechanisms()
-	local mechanisms = {}
-	for backend, f in pairs(self.profile) do
-		if backend_mechanism[backend] then
-			for _, mechanism in ipairs(backend_mechanism[backend]) do
-				if not self.restrict:contains(mechanism) then
-					mechanisms[mechanism] = true;
+	local mechanisms = self.mechs;
+	if not mechanisms then
+		mechanisms = {}
+		for backend, f in pairs(self.profile) do
+			if backend_mechanism[backend] then
+				for _, mechanism in ipairs(backend_mechanism[backend]) do
+					if not self.restrict:contains(mechanism) then
+						mechanisms[mechanism] = true;
+					end
 				end
 			end
 		end
+		self.mechs = mechanisms;
 	end
-	self["possible_mechanisms"] = mechanisms;
-	return array.collect(keys(mechanisms));
+	return mechanisms;
 end
 
 -- select a mechanism to use
@@ -108,11 +111,8 @@
 		return false;
 	end
 	
-	self.mech_i = mechanisms[mechanism]
-	if self.mech_i == nil then 
-		return false;
-	end
-	return true;
+	self.mech_i = mechanisms[self:mechanisms()[mechanism] and mechanism];
+	return (self.mech_i ~= nil);
 end
 
 -- feed new messages to process into the library
--- a/util/sasl/anonymous.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/util/sasl/anonymous.lua	Fri Jul 23 09:22:27 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 Jul 23 09:17:11 2010 +0100
+++ b/util/sasl/plain.lua	Fri Jul 23 09:22:27 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 Jul 23 09:17:11 2010 +0100
+++ b/util/sasl/scram.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -27,7 +27,7 @@
 module "scram"
 
 --=========================
---SASL SCRAM-SHA-1 according to draft-ietf-sasl-scram-10
+--SASL SCRAM-SHA-1 according to RFC 5802
 
 --[[
 Supported Authentication Backends
@@ -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;
--- a/util/sasl_cyrus.lua	Fri Jul 23 09:17:11 2010 +0100
+++ b/util/sasl_cyrus.lua	Fri Jul 23 09:22:27 2010 +0100
@@ -129,20 +129,22 @@
 
 -- get a list of possible SASL mechanims to use
 function method:mechanisms()
-	local mechanisms = {}
-	local cyrus_mechs = cyrussasl.listmech(self.cyrus, nil, "", " ", "")
-	for w in s_gmatch(cyrus_mechs, "[^ ]+") do
-		mechanisms[w] = true;
+	local mechanisms = self.mechs;
+	if not mechanisms then
+		mechanisms = {}
+		local cyrus_mechs = cyrussasl.listmech(self.cyrus, nil, "", " ", "")
+		for w in s_gmatch(cyrus_mechs, "[^ ]+") do
+			mechanisms[w] = true;
+		end
+		self.mechs = mechanisms
 	end
-	self.mechs = mechanisms
-	return array.collect(keys(mechanisms));
+	return mechanisms;
 end
 
 -- select a mechanism to use
 function method:select(mechanism)
 	self.mechanism = mechanism;
-	if not self.mechs then self:mechanisms(); end
-	return self.mechs[mechanism];
+	return self:mechanisms()[mechanism];
 end
 
 -- feed new messages to process into the library
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/xmppstream.lua	Fri Jul 23 09:22:27 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