Merge with 0.5

Tue, 13 Oct 2009 15:00:59 +0500

author
Waqas Hussain <waqas20@gmail.com>
date
Tue, 13 Oct 2009 15:00:59 +0500
changeset 1948
50128afa7858
parent 1946
0eb3835ef9bf (diff)
parent 1947
ff2b86076e9c (current diff)
child 1950
a1d186c8c65a

Merge with 0.5

--- a/Makefile	Tue Oct 13 14:54:07 2009 +0500
+++ b/Makefile	Tue Oct 13 15:00:59 2009 +0500
@@ -28,9 +28,11 @@
 	install -m644 net/* $(SOURCE)/net
 	install -m644 util/* $(SOURCE)/util
 	install -m644 fallbacks/* $(SOURCE)/fallbacks
-	install -m644 plugins/* $(MODULES)
+	install -m644 plugins/*.lua $(MODULES)
+	install -d $(MODULES)/muc
+	install -m644 plugins/muc/* $(MODULES)/muc
 	install -m644 certs/* $(CONFIG)/certs
-	install -m644 plugins/* $(MODULES)
+	install -m644 plugins/*.lua $(MODULES)
 	install -m644 man/prosodyctl.man $(MAN)/man1/prosodyctl.1
 	test -e $(CONFIG)/prosody.cfg.lua || install -m644 prosody.cfg.lua.install $(CONFIG)/prosody.cfg.lua
 	test -e prosody.version && install prosody.version $(SOURCE)/prosody.version || true
--- a/core/componentmanager.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/core/componentmanager.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -6,19 +6,15 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
 local prosody = prosody;
 local log = require "util.logger".init("componentmanager");
 local configmanager = require "core.configmanager";
 local modulemanager = require "core.modulemanager";
-local core_route_stanza = core_route_stanza;
 local jid_split = require "util.jid".split;
 local fire_event = require "core.eventmanager".fire_event;
 local events_new = require "util.events".new;
 local st = require "util.stanza";
 local hosts = hosts;
-local serialize = require "util.serialization".serialize
 
 local pairs, type, tostring = pairs, type, tostring;
 
@@ -26,27 +22,16 @@
 
 local disco_items = require "util.multitable".new();
 local NULL = {};
-require "core.discomanager".addDiscoItemsHandler("*host", function(reply, to, from, node)
-	if #node == 0 and hosts[to] then
-		for jid in pairs(disco_items:get(to) or NULL) do
-			reply:tag("item", {jid = jid}):up();
-		end
-		return true;
-	end
-end);
-
-prosody.events.add_handler("server-starting", function () core_route_stanza = _G.core_route_stanza; end);
 
 module "componentmanager"
 
 local function default_component_handler(origin, stanza)
 	log("warn", "Stanza being handled by default component, bouncing error");
-	if stanza.attr.type ~= "error" then
-		core_route_stanza(nil, st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
+	if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
+		origin.send(st.error_reply(stanza, "wait", "service-unavailable", "Component unavailable"));
 	end
 end
 
-local components_loaded_once;
 function load_enabled_components(config)
 	local defined_hosts = config or configmanager.getconfig();
 		
@@ -79,13 +64,24 @@
 		log("debug", "%s stanza being handled by component: %s", stanza.name, host);
 		component(origin, stanza, hosts[host]);
 	else
-		log("error", "Component manager recieved a stanza for a non-existing component: " .. (stanza.attr.to or serialize(stanza)));
+		log("error", "Component manager recieved a stanza for a non-existing component: "..tostring(stanza));
 	end
 end
 
 function create_component(host, component, events)
 	-- TODO check for host well-formedness
-	return { type = "component", host = host, connected = true, s2sout = {}, events = events or events_new() };
+	local ssl_ctx;
+	if host then
+		-- We need to find SSL context to use...
+		-- Discussion in prosody@ concluded that
+		-- 1 level back is usually enough by default
+		local base_host = host:gsub("^[^%.]+%.", "");
+		if hosts[base_host] then
+			ssl_ctx = hosts[base_host].ssl_ctx;
+		end
+	end
+	return { type = "component", host = host, connected = true, s2sout = {}, 
+			ssl_ctx = ssl_ctx, events = events or events_new() };
 end
 
 function register_component(host, component, session)
@@ -104,8 +100,8 @@
 		if not(host:find("@", 1, true) or host:find("/", 1, true)) and host:find(".", 1, true) then
 			disco_items:set(host:sub(host:find(".", 1, true)+1), host, true);
 		end
-		-- FIXME only load for a.b.c if b.c has dialback, and/or check in config
 		modulemanager.load(host, "dialback");
+		modulemanager.load(host, "tls");
 		log("debug", "component added: "..host);
 		return session or hosts[host];
 	else
@@ -141,4 +137,8 @@
 	components[host] = handler;
 end
 
+function get_children(host)
+	return disco_items:get(host) or NULL;
+end
+
 return _M;
--- a/core/configmanager.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/core/configmanager.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -68,7 +68,7 @@
 	if parsers[format] and parsers[format].load then
 		local f, err = io.open(filename);
 		if f then 
-			local ok, err = parsers[format].load(f:read("*a"));
+			local ok, err = parsers[format].load(f:read("*a"), filename);
 			f:close();
 			if ok then
 				eventmanager.fire_event("config-reloaded", { filename = filename, format = format });
@@ -99,7 +99,7 @@
 	local loadstring, pcall, setmetatable = _G.loadstring, _G.pcall, _G.setmetatable;
 	local setfenv, rawget, tostring = _G.setfenv, _G.rawget, _G.tostring;
 	parsers.lua = {};
-	function parsers.lua.load(data)
+	function parsers.lua.load(data, filename)
 		local env;
 		-- The ' = true' are needed so as not to set off __newindex when we assign the functions below
 		env = setmetatable({ Host = true; host = true; Component = true, component = true,
@@ -139,7 +139,7 @@
 			local f, err = io.open(file);
 			if f then
 				local data = f:read("*a");
-				local ok, err = parsers.lua.load(data);
+				local ok, err = parsers.lua.load(data, file);
 				if not ok then error(err:gsub("%[string.-%]", file), 0); end
 			end
 			if not f then error("Error loading included "..file..": "..err, 0); end
@@ -147,7 +147,7 @@
 		end
 		env.include = env.Include;
 		
-		local chunk, err = loadstring(data);
+		local chunk, err = loadstring(data, "@"..filename);
 		
 		if not chunk then
 			return nil, err;
--- a/core/discomanager.lua	Tue Oct 13 14:54:07 2009 +0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-
-local helper = require "util.discohelper".new();
-local hosts = hosts;
-local jid_split = require "util.jid".split;
-local jid_bare = require "util.jid".bare;
-local usermanager_user_exists = require "core.usermanager".user_exists;
-local rostermanager_is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
-local print = print;
-
-do
-	helper:addDiscoInfoHandler("*host", function(reply, to, from, node)
-		if hosts[to] then
-			reply:tag("identity", {category="server", type="im", name="Prosody"}):up();
-			return true;
-		end
-	end);
-	helper:addDiscoInfoHandler("*node", function(reply, to, from, node)
-		local node, host = jid_split(to);
-		if hosts[host] and rostermanager_is_contact_subscribed(node, host, jid_bare(from)) then
-			reply:tag("identity", {category="account", type="registered"}):up();
-			return true;
-		end
-	end);
-	helper:addDiscoItemsHandler("*host", function(reply, to, from, node)
-		if hosts[to] and hosts[to].type == "local" then
-			return true;
-		end
-	end);
-end
-
-module "discomanager"
-
-function handle(stanza)
-	return helper:handle(stanza);
-end
-
-function addDiscoItemsHandler(jid, func)
-	return helper:addDiscoItemsHandler(jid, func);
-end
-
-function addDiscoInfoHandler(jid, func)
-	return helper:addDiscoInfoHandler(jid, func);
-end
-
-function set(plugin, var, origin)
-	-- TODO handle origin and host based on plugin.
-	local handler = function(reply, to, from, node) -- service discovery
-		if #node == 0 then
-			reply:tag("feature", {var = var}):up();
-			return true;
-		end
-	end
-	addDiscoInfoHandler("*node", handler);
-	addDiscoInfoHandler("*host", handler);
-end
-
-return _M;
--- a/core/hostmanager.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/core/hostmanager.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -6,15 +6,19 @@
 -- COPYING file in the source package for more information.
 --
 
+local ssl = ssl
 
 local hosts = hosts;
 local configmanager = require "core.configmanager";
 local eventmanager = require "core.eventmanager";
 local events_new = require "util.events".new;
 
+-- These are the defaults if not overridden in the config
+local default_ssl_ctx = { mode = "client", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
+
 local log = require "util.logger".init("hostmanager");
 
-local pairs = pairs;
+local pairs, setmetatable = pairs, setmetatable;
 
 module "hostmanager"
 
@@ -46,6 +50,14 @@
 			log("warn", "%s: Option '%s' has no effect for virtual hosts - put it in global Host \"*\" instead", host, option_name);
 		end
 	end
+	
+	if ssl then
+		local ssl_config = host_config.core.ssl or configmanager.get("*", "core", "ssl");
+		if ssl_config then
+        		hosts[host].ssl_ctx = ssl.newcontext(setmetatable(ssl_config, { __index = default_ssl_ctx }));
+        	end
+        end
+
 	log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
 	eventmanager.fire_event("host-activated", host, host_config);
 end
--- a/core/loggingmanager.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/core/loggingmanager.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -187,6 +187,7 @@
 		return function (name, level, message, ...)
 			sourcewidth = math_max(#name+2, sourcewidth);
 			local namelen = #name;
+			
 			if timestamps then
 				io_write(os_date(timestamps), " ");
 			end
--- a/core/modulemanager.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/core/modulemanager.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -6,13 +6,10 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
 local plugin_dir = CFG_PLUGINDIR or "./plugins/";
 
 local logger = require "util.logger";
 local log = logger.init("modulemanager");
-local addDiscoInfoHandler = require "core.discomanager".addDiscoInfoHandler;
 local eventmanager = require "core.eventmanager";
 local config = require "core.configmanager";
 local multitable_new = require "util.multitable".new;
@@ -50,8 +47,6 @@
 
 local modulehelpers = setmetatable({}, { __index = _G });
 
-local features_table = multitable_new();
-local identities_table = multitable_new();
 local handler_table = multitable_new();
 local hooked = multitable_new();
 local hooks = multitable_new();
@@ -173,8 +168,6 @@
 		end
 	end
 	modulemap[host][name] = nil;
-	features_table:remove(host, name);
-	identities_table:remove(host, name);
 	local params = handler_table:get(host, name); -- , {module.host, origin_type, tag, xmlns}
 	for _, param in pairs(params or NULL) do
 		local handlers = stanza_handlers:get(param[1], param[2], param[3], param[4]);
@@ -254,12 +247,13 @@
 		(handlers[1])(origin, stanza);
 		return true;
 	else
-		log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
 		if stanza.attr.xmlns == "jabber:client" then
+			log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
 			if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
 				origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
 			end
 		elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
+			log("warn", "Unhandled %s stream element: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it
 			origin:close("unsupported-stanza-type");
 		end
 	end
@@ -330,50 +324,11 @@
 	self:add_handler(origin_type, "iq", xmlns, handler);
 end
 
-addDiscoInfoHandler("*host", function(reply, to, from, node)
-	if #node == 0 then
-		local done = {};
-		for module, identities in pairs(identities_table:get(to) or NULL) do -- for each module
-			for identity, attr in pairs(identities) do
-				if not done[identity] then
-					reply:tag("identity", attr):up(); -- TODO cache
-					done[identity] = true;
-				end
-			end
-		end
-		for module, identities in pairs(identities_table:get("*") or NULL) do -- for each module
-			for identity, attr in pairs(identities) do
-				if not done[identity] then
-					reply:tag("identity", attr):up(); -- TODO cache
-					done[identity] = true;
-				end
-			end
-		end
-		for module, features in pairs(features_table:get(to) or NULL) do -- for each module
-			for feature in pairs(features) do
-				if not done[feature] then
-					reply:tag("feature", {var = feature}):up(); -- TODO cache
-					done[feature] = true;
-				end
-			end
-		end
-		for module, features in pairs(features_table:get("*") or NULL) do -- for each module
-			for feature in pairs(features) do
-				if not done[feature] then
-					reply:tag("feature", {var = feature}):up(); -- TODO cache
-					done[feature] = true;
-				end
-			end
-		end
-		return next(done) ~= nil;
-	end
-end);
-
 function api:add_feature(xmlns)
-	features_table:set(self.host, self.name, xmlns, true);
+	self:add_item("feature", xmlns);
 end
-function api:add_identity(category, type)
-	identities_table:set(self.host, self.name, category.."\0"..type, {category = category, type = type});
+function api:add_identity(category, type, name)
+	self:add_item("identity", {category = category, type = type, name = name});
 end
 
 local event_hook = function(host, mod_name, event_name, ...)
@@ -420,6 +375,50 @@
 	return f();
 end
 
+function api:get_option(name, default_value)
+	return config.get(self.host, self.name, name) or config.get(self.host, "core", name) or default_value;
+end
+
+local t_remove = _G.table.remove;
+local module_items = multitable_new();
+function api:add_item(key, value)
+	self.items = self.items or {};
+	self.items[key] = self.items[key] or {};
+	t_insert(self.items[key], value);
+	self:fire_event("item-added/"..key, {source = self, item = value});
+end
+function api:remove_item(key, value)
+	local t = self.items and self.items[key] or NULL;
+	for i = #t,1,-1 do
+		if t[i] == value then
+			t_remove(self.items[key], i);
+			self:fire_event("item-removed/"..key, {source = self, item = value});
+			return value;
+		end
+	end
+end
+
+function api:get_host_items(key)
+	local result = {};
+	for mod_name, module in pairs(modulemap[self.host]) do
+		module = module.module;
+		if module.items then
+			for _, item in ipairs(module.items[key] or NULL) do
+				t_insert(result, item);
+			end
+		end
+	end
+	for mod_name, module in pairs(modulemap["*"]) do
+		module = module.module;
+		if module.items then
+			for _, item in ipairs(module.items[key] or NULL) do
+				t_insert(result, item);
+			end
+		end
+	end
+	return result;
+end
+
 --------------------------------------------------------------------
 
 local actions = {};
--- a/core/s2smanager.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/core/s2smanager.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -27,6 +27,7 @@
 local stanza = st.stanza;
 local nameprep = require "util.encodings".stringprep.nameprep;
 
+local fire_event = require "core.eventmanager".fire_event;
 local uuid_gen = require "util.uuid".generate;
 
 local logger_init = require "util.logger".init;
@@ -128,6 +129,7 @@
 	end
 	open_sessions = open_sessions + 1;
 	local w, log = conn.write, logger_init("s2sin"..tostring(conn):match("[a-f0-9]+$"));
+	session.log = log;
 	session.sends2s = function (t) log("debug", "sending: %s", tostring(t)); w(tostring(t)); end
 	incoming_s2s[session] = true;
 	add_task(connect_timeout, function ()
@@ -144,7 +146,9 @@
 end
 
 function new_outgoing(from_host, to_host)
-		local host_session = { to_host = to_host, from_host = from_host, notopen = true, type = "s2sout_unauthed", direction = "outgoing" };
+		local host_session = { to_host = to_host, from_host = from_host, host = from_host, 
+		                       notopen = true, type = "s2sout_unauthed", direction = "outgoing" };
+		
 		hosts[from_host].s2sout[to_host] = host_session;
 		
 		local log;
@@ -333,10 +337,16 @@
 	local send = session.sends2s;
 	
 	-- TODO: #29: SASL/TLS on s2s streams
-	session.version = 0; --tonumber(attr.version) or 0;
+	session.version = tonumber(attr.version) or 0;
+	
+	if session.secure == false then
+		session.secure = true;
+	end
 	
 	if session.version >= 1.0 and not (attr.to and attr.from) then
-		log("warn", (session.to_host or "(unknown)").." failed to specify 'to' or 'from' hostname as per RFC");
+		
+		(session.log or log)("warn", "Remote of stream "..(session.from_host or "(unknown)").."->"..(session.to_host or "(unknown)")
+			.." failed to specify to (%s) and/or from (%s) hostname as per RFC", tostring(attr.to), tostring(attr.from));
 	end
 	
 	if session.direction == "incoming" then
@@ -348,15 +358,23 @@
 		(session.log or log)("debug", "incoming s2s received <stream:stream>");
 		send("<?xml version='1.0'?>");
 		send(stanza("stream:stream", { xmlns='jabber:server', ["xmlns:db"]='jabber:server:dialback', 
-				["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host }):top_tag());
+				["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.to_host, version=(session.version > 0 and "1.0" or nil) }):top_tag());
 		if session.to_host and not hosts[session.to_host] then
 			-- Attempting to connect to a host we don't serve
 			session:close({ condition = "host-unknown"; text = "This host does not serve "..session.to_host });
 			return;
 		end
 		if session.version >= 1.0 then
-			send(st.stanza("stream:features")
-					:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):tag("optional"):up():up());
+			local features = st.stanza("stream:features");
+							
+			if session.to_host then
+				hosts[session.to_host].events.fire_event("s2s-stream-features", { session = session, features = features });
+			else
+				(session.log or log)("warn", "No 'to' on stream header from %s means we can't offer any features", session.from_host or "unknown host");
+			end
+			
+			log("debug", "Sending stream features: %s", tostring(features));
+			send(features);
 		end
 	elseif session.direction == "outgoing" then
 		-- If we are just using the connection for verifying dialback keys, we won't try and auth it
@@ -377,10 +395,14 @@
 		end
 		session.send_buffer = nil;
 	
-		if not session.dialback_verifying then
-			initiate_dialback(session);
-		else
-			mark_connected(session);
+		-- If server is pre-1.0, don't wait for features, just do dialback
+		if session.version < 1.0 then
+			if not session.dialback_verifying then
+				log("debug", "Initiating dialback...");
+				initiate_dialback(session);
+			else
+				mark_connected(session);
+			end
 		end
 	end
 
@@ -430,6 +452,7 @@
 	return true;
 end
 
+-- Stream is authorised, and ready for normal stanzas
 function mark_connected(session)
 	local sendq, send = session.sendq, session.sends2s;
 	
--- a/core/sessionmanager.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/core/sessionmanager.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -11,7 +11,6 @@
 local tonumber, tostring = tonumber, tostring;
 local ipairs, pairs, print, next= ipairs, pairs, print, next;
 local collectgarbage = collectgarbage;
-local m_random = import("math", "random");
 local format = import("string", "format");
 
 local hosts = hosts;
@@ -19,7 +18,8 @@
 local bare_sessions = bare_sessions;
 
 local modulemanager = require "core.modulemanager";
-local log = require "util.logger".init("sessionmanager");
+local logger = require "util.logger";
+local log = logger.init("sessionmanager");
 local error = error;
 local uuid_generate = require "util.uuid".generate;
 local rm_load_roster = require "core.rostermanager".load_roster;
@@ -27,11 +27,13 @@
 local nameprep = require "util.encodings".stringprep.nameprep;
 
 local fire_event = require "core.eventmanager".fire_event;
-
+local add_task = require "util.timer".add_task;
 local gettime = require "socket".gettime;
 
 local st = require "util.stanza";
 
+local c2s_timeout = config_get("*", "core", "c2s_timeout");
+
 local newproxy = newproxy;
 local getmetatable = getmetatable;
 
@@ -50,6 +52,17 @@
 	local w = conn.write;
 	session.send = function (t) w(tostring(t)); end
 	session.ip = conn.ip();
+	local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
+	session.log = logger.init(conn_name);
+	
+	if c2s_timeout then
+		add_task(c2s_timeout, function ()
+			if session.type == "c2s_unauthed" then
+				session:close("connection-timeout");
+			end
+		end);
+	end
+		
 	return session;
 end
 
@@ -154,31 +167,32 @@
 	session.host = attr.to or error("Client failed to specify destination hostname");
 	session.host = nameprep(session.host);
 	session.version = tonumber(attr.version) or 0;
-	session.streamid = m_random(1000000, 99999999);
+	session.streamid = uuid_generate();
 	(session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
-	
-	send("<?xml version='1.0'?>");
-	send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host));
 
 	if not hosts[session.host] then
 		-- We don't serve this host...
 		session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
 		return;
 	end
-	
+
+	send("<?xml version='1.0'?>");
+	send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host));
+
+	(session.log or log)("debug", "Sent reply <stream:stream> to client");
+	session.notopen = nil;
+
 	-- If session.secure is *false* (not nil) then it means we /were/ encrypting
 	-- since we now have a new stream header, session is secured
 	if session.secure == false then
 		session.secure = true;
 	end
-						
+
 	local features = st.stanza("stream:features");
 	fire_event("stream-features", session, features);
-	
+
 	send(features);
-	
-	(session.log or log)("debug", "Sent reply <stream:stream> to client");
-	session.notopen = nil;
+
 end
 
 function streamclosed(session)
--- a/core/stanza_router.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/core/stanza_router.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -114,7 +114,7 @@
 			end
 			if h.events.fire_event(event, {origin = origin, stanza = stanza}) then return; end
 		end
-		if host and not hosts[host] then host = nil; end -- workaround for a Pidgin bug which sets 'to' to the SRV result
+		if host and not hosts[host] then host = nil; end -- COMPAT: workaround for a Pidgin bug which sets 'to' to the SRV result
 		modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
 	end
 end
--- a/core/usermanager.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/core/usermanager.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -1,7 +1,7 @@
 -- Prosody IM
 -- Copyright (C) 2008-2009 Matthew Wild
 -- Copyright (C) 2008-2009 Waqas Hussain
--- 
+--
 -- This project is MIT/X11 licensed. Please see the
 -- COPYING file in the source package for more information.
 --
@@ -23,6 +23,7 @@
 function validate_credentials(host, username, password, method)
 	log("debug", "User '%s' is being validated", username);
 	local credentials = datamanager.load(username, host, "accounts") or {};
+
 	if method == nil then method = "PLAIN"; end
 	if method == "PLAIN" and credentials.password then -- PLAIN, do directly
 		if password == credentials.password then
@@ -30,7 +31,7 @@
 		else
 			return nil, "Auth failed. Invalid username or password.";
 		end
-	end
+  end
 	-- must do md5
 	-- make credentials md5
 	local pwd = credentials.password;
@@ -49,6 +50,10 @@
 	end
 end
 
+function get_password(username, host)
+  return (datamanager.load(username, host, "accounts") or {}).password
+end
+
 function user_exists(username, host)
 	return datamanager.load(username, host, "accounts") ~= nil; -- FIXME also check for empty credentials
 end
@@ -58,13 +63,11 @@
 end
 
 function get_supported_methods(host)
-	local methods = {["PLAIN"] = true}; -- TODO this should be taken from the config
-	methods["DIGEST-MD5"] = true;
-	return methods;
+	return {["PLAIN"] = true, ["DIGEST-MD5"] = true}; -- TODO this should be taken from the config
 end
 
 function is_admin(jid)
-	local admins = config.get("*", "core", "admins") or {};
+	local admins = config.get("*", "core", "admins");
 	if type(admins) == "table" then
 		jid = jid_bare(jid);
 		for _,admin in ipairs(admins) do
--- a/net/adns.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/net/adns.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -29,7 +29,7 @@
 				log("debug", "Reply for %s (%s)", qname, tostring(coroutine.running()));
 				local ok, err = pcall(handler, dns.peek(qname, qtype, qclass));
 				if not ok then
-					log("debug", "Error in DNS response handler: %s", tostring(err));
+					log("error", "Error in DNS response handler: %s", tostring(err));
 				end
 			end)(dns.peek(qname, qtype, qclass));
 end
--- a/net/httpserver.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/net/httpserver.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -61,7 +61,7 @@
 		end
 	else
 		-- Response we have is just a string (the body)
-		log("debug", "Sending response to %s: %s", request.id or "<none>", response or "<none>");
+		log("debug", "Sending 200 response to %s", request.id or "<none>");
 		
 		resp = { "HTTP/1.0 200 OK\r\n" };
 		t_insert(resp, "Connection: close\r\n");
@@ -89,9 +89,6 @@
 		end
 		
 		callback = (request.server and request.server.handlers[base]) or default_handler;
-		if callback == default_handler then
-			log("debug", "Default callback for this request (base: "..tostring(base)..")")
-		end
 	end
 	if callback then
 		if err then
@@ -251,13 +248,27 @@
 	end
 end
 
-function new_from_config(ports, default_base, handle_request)
+function set_default_handler(handler)
+	default_handler = handler;
+end
+
+function new_from_config(ports, handle_request, default_options)
+	if type(handle_request) == "string" then -- COMPAT with old plugins
+		log("warn", "Old syntax of httpserver.new_from_config being used to register %s", handle_request);
+		handle_request, default_options = default_options, { base = handle_request };
+	end
 	for _, options in ipairs(ports) do
-		local port, base, ssl, interface = 5280, default_base, false, nil;
+		local port = default_options.port or 5280;
+		local base = default_options.base;
+		local ssl = default_options.ssl or false;
+		local interface = default_options.interface;
 		if type(options) == "number" then
 			port = options;
 		elseif type(options) == "table" then
-			port, base, ssl, interface = options.port or 5280, options.path or default_base, options.ssl or false, options.interface;
+			port = options.port or port;
+			base = options.path or base;
+			ssl = options.ssl or ssl;
+			interface = options.interface or interface;
 		elseif type(options) == "string" then
 			base = options;
 		end
--- a/net/server.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/net/server.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -157,6 +157,7 @@
 
 _maxclientsperserver = 1000
 
+_maxsslhandshake = 30 -- max handshake round-trips
 ----------------------------------// PRIVATE //--
 
 wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxconnections, startssl )    -- this function wraps a server
@@ -230,6 +231,9 @@
     handler.ssl = function( )
         return ssl
     end
+    handler.sslctx = function( )
+        return sslctx
+    end
     handler.remove = function( )
         connections = connections - 1
     end
@@ -246,7 +250,7 @@
         _socketlist[ socket ] = nil
         handler = nil
         socket = nil
-        mem_free( )
+        --mem_free( )
         out_put "server.lua: closed server handler and removed sockets from list"
     end
     handler.ip = function( )
@@ -297,6 +301,7 @@
     local ssl
 
     local dispatch = listeners.incoming or listeners.listener
+    local status = listeners.status
     local disconnect = listeners.disconnect
 
     local bufferqueue = { }    -- buffer array
@@ -336,6 +341,9 @@
     handler.ssl = function( )
         return ssl
     end
+    handler.sslctx = function ( )
+        return sslctx
+    end
     handler.send = function( _, data, i, j )
         return send( socket, data, i, j )
     end
@@ -374,7 +382,7 @@
             handler = nil
         end
         socket = nil
-        mem_free( )
+        --mem_free( )
 	if server then
 		server.remove( )
 	end
@@ -473,10 +481,10 @@
             readtraffic = readtraffic + count
             _readtraffic = _readtraffic + count
             _readtimes[ handler ] = _currenttime
-            --out_put( "server.lua: read data '", buffer, "', error: ", err )
+            --out_put( "server.lua: read data '", buffer:gsub("[^%w%p ]", "."), "', error: ", err )
             return dispatch( handler, buffer, err )
         else    -- connections was closed or fatal error
-            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " error: ", tostring(err) )
+            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " read error: ", tostring(err) )
             fatalerror = true
             disconnect( handler, err )
 	    _ = handler and handler.close( )
@@ -513,7 +521,7 @@
             _writetimes[ handler ] = _currenttime
             return true
         else    -- connection was closed during sending or fatal error
-            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " error: ", tostring(err) )
+            out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " write error: ", tostring(err) )
             fatalerror = true
             disconnect( handler, err )
             _ = handler and handler.close( )
@@ -527,7 +535,7 @@
         local read
         local handshake = coroutine_wrap( function( client )    -- create handshake coroutine
                 local err
-                for i = 1, 10 do    -- 10 handshake attemps
+                for i = 1, _maxsslhandshake do
                     _sendlistlen = ( wrote and removesocket( _sendlist, socket, _sendlistlen ) ) or _sendlistlen
                     _readlistlen = ( read and removesocket( _readlist, socket, _readlistlen ) ) or _readlistlen
                     read, wrote = nil, nil
@@ -536,7 +544,7 @@
                         out_put( "server.lua: ssl handshake done" )
                         handler.readbuffer = _readbuffer    -- when handshake is done, replace the handshake function with regular functions
                         handler.sendbuffer = _sendbuffer
-                        -- return dispatch( handler )
+                        _ = status and status( handler, "ssl-handshake-complete" )
                         return true
                     else
                         out_put( "server.lua: error during ssl handshake: ", tostring(err) )
@@ -566,7 +574,7 @@
             socket, err = ssl_wrap( socket, sslctx )    -- wrap socket
             if err then
                 out_put( "server.lua: ssl error: ", tostring(err) )
-                mem_free( )
+                --mem_free( )
                 return nil, nil, err    -- fatal error
             end
             socket:settimeout( 0 )
@@ -671,7 +679,7 @@
     _readlistlen = removesocket( _readlist, socket, _readlistlen )
     _socketlist[ socket ] = nil
     socket:close( )
-    mem_free( )
+    --mem_free( )
 end
 
 ----------------------------------// PUBLIC //--
@@ -740,11 +748,11 @@
     _sendlist = { }
     _timerlist = { }
     _socketlist = { }
-    mem_free( )
+    --mem_free( )
 end
 
 getsettings = function( )
-    return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver
+    return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver, _maxsslhandshake
 end
 
 changesettings = function( new )
@@ -760,6 +768,7 @@
     _readtimeout = tonumber( new.readtimeout ) or _readtimeout
     _cleanqueue = new.cleanqueue
     _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver
+    _maxsslhandshake = new._maxsslhandshake or _maxsslhandshake
     return true
 end
 
@@ -812,7 +821,7 @@
         _currenttime = os_time( )
         if os_difftime( _currenttime - _timer ) >= 1 then
             for i = 1, _timerlistlen do
-                _timerlist[ i ]( )    -- fire timers
+                _timerlist[ i ]( _currenttime )    -- fire timers
             end
             _timer = _currenttime
         end
--- a/net/xmppclient_listener.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/net/xmppclient_listener.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -61,6 +61,7 @@
 		function session.data(conn, data)
 			local ok, err = parser:parse(data);
 			if ok then return; end
+			log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "));
 			session:close("xml-not-well-formed");
 		end
 		
@@ -100,7 +101,7 @@
 		end
 		session.send("</stream:stream>");
 		session.conn.close();
-		xmppclient.disconnect(session.conn, (reason and reason.condition) or reason or "session closed");
+		xmppclient.disconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
 	end
 end
 
@@ -113,11 +114,6 @@
 		session = sm_new_session(conn);
 		sessions[conn] = session;
 
-		-- Logging functions --
-
-		local conn_name = "c2s"..tostring(conn):match("[a-f0-9]+$");
-		session.log = logger.init(conn_name);
-		
 		session.log("info", "Client connected");
 		
 		-- Client is using legacy SSL (otherwise mod_tls sets this flag)
--- a/net/xmppserver_listener.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/net/xmppserver_listener.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -61,13 +61,14 @@
 		function session.data(conn, data)
 			local ok, err = parser:parse(data);
 			if ok then return; end
+			session.log("warn", "Received invalid XML: %s", data);
+			session.log("warn", "Problem was: %s", err);
 			session:close("xml-not-well-formed");
 		end
 		
 		return true;
 end
 
-
 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
 local default_stream_attr = { ["xmlns:stream"] = stream_callbacks.stream_tag:gsub("%|[^|]+$", ""), xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
 local function session_close(session, reason)
@@ -133,6 +134,17 @@
 	end
 end
 	
+function xmppserver.status(conn, status)
+	if status == "ssl-handshake-complete" then
+		local session = sessions[conn];
+		if session and session.direction == "outgoing" then
+			local format, to_host, from_host = string.format, session.to_host, session.from_host;
+			session.log("debug", "Sending stream header...");
+			session.sends2s(format([[<stream:stream xmlns='jabber:server' xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' from='%s' to='%s' version='1.0'>]], from_host, to_host));
+		end
+	end
+end
+
 function xmppserver.disconnect(conn, err)
 	local session = sessions[conn];
 	if session then
--- a/plugins/mod_bosh.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_bosh.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -6,7 +6,6 @@
 -- COPYING file in the source package for more information.
 --
 
-
 module.host = "*" -- Global module
 
 local hosts = _G.hosts;
@@ -22,17 +21,18 @@
 local st = require "util.stanza";
 local logger = require "util.logger";
 local log = logger.init("mod_bosh");
-local stream_callbacks = { stream_tag = "http://jabber.org/protocol/httpbind|body" };
-local config = require "core.configmanager";
+
 local xmlns_bosh = "http://jabber.org/protocol/httpbind"; -- (hard-coded into a literal in session.send)
+local stream_callbacks = { stream_tag = "http://jabber.org/protocol/httpbind|body", default_ns = xmlns_bosh };
 
-local BOSH_DEFAULT_HOLD = tonumber(config.get("*", "core", "bosh_default_hold")) or 1;
-local BOSH_DEFAULT_INACTIVITY = tonumber(config.get("*", "core", "bosh_max_inactivity")) or 60;
-local BOSH_DEFAULT_POLLING = tonumber(config.get("*", "core", "bosh_max_polling")) or 5;
-local BOSH_DEFAULT_REQUESTS = tonumber(config.get("*", "core", "bosh_max_requests")) or 2;
-local BOSH_DEFAULT_MAXPAUSE = tonumber(config.get("*", "core", "bosh_max_pause")) or 300;
+local BOSH_DEFAULT_HOLD = tonumber(module:get_option("bosh_default_hold")) or 1;
+local BOSH_DEFAULT_INACTIVITY = tonumber(module:get_option("bosh_max_inactivity")) or 60;
+local BOSH_DEFAULT_POLLING = tonumber(module:get_option("bosh_max_polling")) or 5;
+local BOSH_DEFAULT_REQUESTS = tonumber(module:get_option("bosh_max_requests")) or 2;
+local BOSH_DEFAULT_MAXPAUSE = tonumber(module:get_option("bosh_max_pause")) or 300;
 
 local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" };
+local session_close_reply = { headers = default_headers, body = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate" }), attr = {} };
 
 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat;
 local os_time = os.time;
@@ -112,11 +112,9 @@
 
 local function bosh_reset_stream(session) session.notopen = true; end
 
-local session_close_reply = { headers = default_headers, body = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate" }), attr = {} };
 local function bosh_close_stream(session, reason)
 	(session.log or log)("info", "BOSH client disconnected");
 	session_close_reply.attr.condition = reason;
-	local session_close_reply = tostring(session_close_reply);
 	for _, held_request in ipairs(session.requests) do
 		held_request:send(session_close_reply);
 		held_request:destroy();
@@ -144,7 +142,7 @@
 		
 		-- New session
 		sid = new_uuid();
-		local session = { type = "c2s_unauthed", conn = {}, sid = sid, rid = attr.rid, host = attr.to, bosh_version = attr.ver, bosh_wait = attr.wait, streamid = sid, 
+		local session = { type = "c2s_unauthed", conn = {}, sid = sid, rid = tonumber(attr.rid), host = attr.to, bosh_version = attr.ver, bosh_wait = attr.wait, streamid = sid, 
 						bosh_hold = BOSH_DEFAULT_HOLD, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY,
 						requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream, close = bosh_close_stream, 
 						dispatch_stanza = core_process_stanza, log = logger.init("bosh"..sid), secure = request.secure };
@@ -209,6 +207,21 @@
 		return;
 	end
 	
+	if session.rid then
+		local rid = tonumber(attr.rid);
+		local diff = rid - session.rid;
+		if diff > 1 then
+			session.log("warn", "rid too large (means a request was lost). Last rid: %d New rid: %s", session.rid, attr.rid);
+		elseif diff <= 0 then
+			-- Repeated, ignore
+			session.log("debug", "rid repeated (on request %s), ignoring: %d", request.id, session.rid);
+			request.notopen = nil;
+			t_insert(session.requests, request);
+			return;
+		end
+		session.rid = rid;
+	end
+	
 	if attr.type == "terminate" then
 		-- Client wants to end this session
 		session:close();
@@ -284,7 +297,7 @@
 	end
 end
 
-local ports = config.get(module.host, "core", "bosh_ports") or { 5280 };
-httpserver.new_from_config(ports, "http-bind", handle_request);
+local ports = module:get_option("bosh_ports") or { 5280 };
+httpserver.new_from_config(ports, handle_request, { base = "http-bind" });
 
 server.addtimer(on_timer);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_compression.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -0,0 +1,122 @@
+-- Prosody IM
+-- Copyright (C) 2009 Tobias Markmann
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local st = require "util.stanza";
+local zlib = require "zlib";
+local pcall = pcall;
+
+local xmlns_compression_feature = "http://jabber.org/features/compress"
+local xmlns_compression_protocol = "http://jabber.org/protocol/compress"
+local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
+
+local compression_level = module:get_option("compression_level");
+
+-- if not defined assume admin wants best compression
+if compression_level == nil then compression_level = 9 end;
+
+compression_level = tonumber(compression_level);
+if not compression_level or compression_level < 1 or compression_level > 9 then
+	module:log("warn", "Invalid compression level in config: %s", tostring(compression_level));
+	module:log("warn", "Module loading aborted. Compression won't be available.");
+	return;
+end
+
+module:add_event_hook("stream-features",
+		function (session, features)
+			if not session.compressed then
+				-- FIXME only advertise compression support when TLS layer has no compression enabled
+				features:add_child(compression_stream_feature);
+			end
+		end
+);
+
+-- TODO Support compression on S2S level too.
+module:add_handler({"c2s_unauthed", "c2s"}, "compress", xmlns_compression_protocol,
+		function(session, stanza)
+			-- fail if we are already compressed
+			if session.compressed then
+				local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
+				session.send(error_st);
+				session:log("warn", "Tried to establish another compression layer.");
+			end
+			
+			-- checking if the compression method is supported
+			local method = stanza:child_with_name("method")[1];
+			if method == "zlib" then
+				session.log("info", method.." compression selected.");
+				session.send(st.stanza("compressed", {xmlns=xmlns_compression_protocol}));
+				session:reset_stream();
+				
+				-- create deflate and inflate streams
+				local status, deflate_stream = pcall(zlib.deflate, compression_level);
+				if status == false then
+					local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
+					session.send(error_st);
+					session:log("error", "Failed to create zlib.deflate filter.");
+					module:log("error", deflate_stream);
+					return
+				end
+				
+				local status, inflate_stream = pcall(zlib.inflate);
+				if status == false then
+					local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("setup-failed");
+					session.send(error_st);
+					session:log("error", "Failed to create zlib.deflate filter.");
+					module:log("error", inflate_stream);
+					return
+				end
+				
+				-- setup compression for session.w
+				local old_send = session.send;
+				
+				session.send = function(t)
+						local status, compressed, eof = pcall(deflate_stream, tostring(t), 'sync');
+						if status == false then
+							session:close({
+								condition = "undefined-condition";
+								text = compressed;
+								extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+							});
+							module:log("warn", compressed);
+							return;
+						end
+						old_send(compressed);
+					end;
+					
+				-- setup decompression for session.data
+				local function setup_decompression(session)
+					local old_data = session.data
+					session.data = function(conn, data)
+							local status, decompressed, eof = pcall(inflate_stream, data);
+							if status == false then
+								session:close({
+									condition = "undefined-condition";
+									text = decompressed;
+									extra = st.stanza("failure", {xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
+								});
+								module:log("warn", decompressed);
+								return;
+							end
+							old_data(conn, decompressed);
+						end;
+				end
+				setup_decompression(session);
+				
+				local session_reset_stream = session.reset_stream;
+				session.reset_stream = function(session)
+						session_reset_stream(session);
+						setup_decompression(session);
+						return true;
+					end;
+				session.compressed = true;
+			else
+				session.log("info", method.." compression selected. But we don't support it.");
+				local error_st = st.stanza("failure", {xmlns=xmlns_compression_protocol}):tag("unsupported-method");
+				session.send(error_st);
+			end
+		end
+);
--- a/plugins/mod_console.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_console.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -70,6 +70,9 @@
 			if data:match("^>") then
 				data = data:gsub("^>", "");
 				useglobalenv = true;
+			elseif data == "\004" then
+				commands["bye"](session, data);
+				return;
 			else
 				local command = data:lower();
 				command = data:match("^%w+") or data:match("%p");
@@ -206,7 +209,8 @@
 -- Anything in def_env will be accessible within the session as a global variable
 
 def_env.server = {};
-function def_env.server:reload()
+
+function def_env.server:insane_reload()
 	prosody.unlock_globals();
 	dofile "prosody"
 	prosody = _G.prosody;
@@ -231,6 +235,11 @@
 		minutes, (minutes ~= 1 and "s") or "", os.date("%c", prosody.start_time));
 end
 
+function def_env.server:shutdown(reason)
+	prosody.shutdown(reason);
+	return true, "Shutdown initiated";
+end
+
 def_env.module = {};
 
 local function get_hosts_set(hosts, module)
@@ -359,6 +368,11 @@
 	return true, tostring(config_get(host, section, key));
 end
 
+function def_env.config:reload()
+	local ok, err = prosody.reload_config();
+	return ok, (ok and "Config reloaded (you may need to reload modules to take effect)") or tostring(err);
+end
+
 def_env.hosts = {};
 function def_env.hosts:list()
 	for host, host_session in pairs(hosts) do
@@ -385,7 +399,12 @@
 
 function def_env.c2s:show(match_jid)
 	local print, count = self.session.print, 0;
+	local curr_host;
 	show_c2s(function (jid, session)
+		if curr_host ~= session.host then
+			curr_host = session.host;
+			print(curr_host);
+		end
 		if (not match_jid) or jid:match(match_jid) then
 			count = count + 1;
 			local status, priority = "unavailable", tostring(session.priority or "-");
@@ -397,7 +416,7 @@
 					status = "available";
 				end
 			end
-			print(jid.." - "..status.."("..priority..")");
+			print("   "..jid.." - "..status.."("..priority..")");
 		end		
 	end);
 	return true, "Total: "..count.." clients";
@@ -448,7 +467,7 @@
 		for remotehost, session in pairs(host_session.s2sout) do
 			if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
 				count_out = count_out + 1;
-				print("    "..host.." -> "..remotehost);
+				print("    "..host.." -> "..remotehost..(session.secure and " (encrypted)" or ""));
 				if session.sendq then
 					print("        There are "..#session.sendq.." queued outgoing stanzas for this connection");
 				end
@@ -476,12 +495,16 @@
 				end
 			end
 		end	
-		
+		local subhost_filter = function (h) 
+				return (match_jid and h:match(match_jid));
+			end
 		for session in pairs(incoming_s2s) do
 			if session.to_host == host and ((not match_jid) or host:match(match_jid) 
-				or (session.from_host and session.from_host:match(match_jid))) then
+				or (session.from_host and session.from_host:match(match_jid))
+				-- Pft! is what I say to list comprehensions
+				or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
 				count_in = count_in + 1;
-				print("    "..host.." <- "..(session.from_host or "(unknown)"));
+				print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.secure and " (encrypted)" or ""));
 				if session.type == "s2sin_unauthed" then
 						print("        Connection not yet authenticated");
 				end
--- a/plugins/mod_dialback.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_dialback.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -10,6 +10,7 @@
 local hosts = _G.hosts;
 local send_s2s = require "core.s2smanager".send_to_host;
 local s2s_make_authenticated = require "core.s2smanager".make_authenticated;
+local s2s_initiate_dialback = require "core.s2smanager".initiate_dialback;
 local s2s_verify_dialback = require "core.s2smanager".verify_dialback;
 local s2s_destroy_session = require "core.s2smanager".destroy_session;
 
@@ -17,6 +18,7 @@
 
 local st = require "util.stanza";
 
+local xmlns_stream = "http://etherx.jabber.org/streams";
 local xmlns_dialback = "jabber:server:dialback";
 
 local dialback_requests = setmetatable({}, { __mode = 'v' });
@@ -113,3 +115,13 @@
 			s2s_destroy_session(origin)
 		end
 	end);
+
+module:hook_stanza(xmlns_stream, "features", function (origin, stanza)
+		s2s_initiate_dialback(origin);
+		return true;
+	end, 100);
+
+-- Offer dialback to incoming hosts
+module:hook("s2s-stream-features", function (data)
+		data.features:tag("dialback", { xmlns='urn:xmpp:features:dialback' }):tag("optional"):up():up();
+	end);
--- a/plugins/mod_disco.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_disco.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -6,16 +6,47 @@
 -- COPYING file in the source package for more information.
 --
 
-
+local componentmanager_get_children = require "core.componentmanager".get_children;
+local st = require "util.stanza"
 
-local discomanager_handle = require "core.discomanager".handle;
-
+module:add_identity("server", "im", "Prosody"); -- FIXME should be in the non-existing mod_router
 module:add_feature("http://jabber.org/protocol/disco#info");
 module:add_feature("http://jabber.org/protocol/disco#items");
 
-module:add_iq_handler({"c2s", "s2sin"}, "http://jabber.org/protocol/disco#info", function (session, stanza)
-	session.send(discomanager_handle(stanza));
+module:hook("iq/host/http://jabber.org/protocol/disco#info:query", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	if stanza.attr.type ~= "get" then return; end
+	local node = stanza.tags[1].attr.node;
+	if node and node ~= "" then return; end -- TODO fire event?
+
+	local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#info");
+	local done = {};
+	for _,identity in ipairs(module:get_host_items("identity")) do
+		local identity_s = identity.category.."\0"..identity.type;
+		if not done[identity_s] then
+			reply:tag("identity", identity):up();
+			done[identity_s] = true;
+		end
+	end
+	for _,feature in ipairs(module:get_host_items("feature")) do
+		if not done[feature] then
+			reply:tag("feature", {var=feature}):up();
+			done[feature] = true;
+		end
+	end
+	origin.send(reply);
+	return true;
 end);
-module:add_iq_handler({"c2s", "s2sin"}, "http://jabber.org/protocol/disco#items", function (session, stanza)
-	session.send(discomanager_handle(stanza));
+module:hook("iq/host/http://jabber.org/protocol/disco#items:query", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	if stanza.attr.type ~= "get" then return; end
+	local node = stanza.tags[1].attr.node;
+	if node and node ~= "" then return; end -- TODO fire event?
+
+	local reply = st.reply(stanza):query("http://jabber.org/protocol/disco#items");
+	for jid in pairs(componentmanager_get_children(module.host)) do
+		reply:tag("item", {jid = jid}):up();
+	end
+	origin.send(reply);
+	return true;
 end);
--- a/plugins/mod_httpserver.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_httpserver.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -11,45 +11,51 @@
 
 local open = io.open;
 local t_concat = table.concat;
-local check_http_path;
 
 local http_base = config.get("*", "core", "http_path") or "www_files";
 
-local response_403 = { status = "403 Forbidden", body = "<h1>Invalid URL</h1>Sorry, we couldn't find what you were looking for :(" };
+local response_400 = { status = "400 Bad Request", body = "<h1>Bad Request</h1>Sorry, we didn't understand your request :(" };
 local response_404 = { status = "404 Not Found", body = "<h1>Page Not Found</h1>Sorry, we couldn't find what you were looking for :(" };
 
-local http_path = { http_base };
-local function handle_request(method, body, request)
-	local path = check_http_path(request.url.path:gsub("^/[^/]+%.*", ""));
-	if not path then
-		return response_403;
+local function preprocess_path(path)
+	if path:sub(1,1) ~= "/" then
+		path = "/"..path;
 	end
-	http_path[2] = path;
-	local f, err = open(t_concat(http_path), "r");
+	local level = 0;
+	for component in path:gmatch("([^/]+)/") do
+		if component == ".." then
+			level = level - 1;
+		elseif component ~= "." then
+			level = level + 1;
+		end
+		if level < 0 then
+			return nil;
+		end
+	end
+	return path;
+end
+
+function serve_file(path)
+	local f, err = open(http_base..path, "r");
 	if not f then return response_404; end
 	local data = f:read("*a");
 	f:close();
 	return data;
 end
 
-local ports = config.get(module.host, "core", "http_ports") or { 5280 };
-httpserver.new_from_config(ports, "files", handle_request);
+local function handle_file_request(method, body, request)
+	local path = preprocess_path(request.url.path);
+	if not path then return response_400; end
+	path = path:gsub("^/[^/]+", ""); -- Strip /files/
+	return serve_file(path);
+end
 
-function check_http_path(url)
-	if url:sub(1,1) ~= "/" then
-		url = "/"..url;
-	end
-	
-	local level = 0;
-	for part in url:gmatch("%/([^/]+)") do
-		if part == ".." then
-			level = level - 1;
-		elseif part ~= "." then
-			level = level + 1;
-		end
-		if level < 0 then
-			return nil;
-		end
-	end
-	return url;
+local function handle_default_request(method, body, request)
+	local path = preprocess_path(request.url.path);
+	if not path then return response_400; end
+	return serve_file(path);
 end
+
+local ports = config.get(module.host, "core", "http_ports") or { 5280 };
+httpserver.set_default_handler(handle_default_request);
+httpserver.new_from_config(ports, handle_file_request, { base = "files" });
--- a/plugins/mod_legacyauth.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_legacyauth.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -11,8 +11,7 @@
 local st = require "util.stanza";
 local t_concat = table.concat;
 
-local config = require "core.configmanager";
-local secure_auth_only = config.get(module:get_host(), "core", "require_encryption");
+local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
 
 local sessionmanager = require "core.sessionmanager";
 local usermanager = require "core.usermanager";
@@ -45,14 +44,12 @@
 					:tag("username"):up()
 					:tag("password"):up()
 					:tag("resource"):up());
-				return true;			
 			else
 				username, password, resource = t_concat(username), t_concat(password), t_concat(resource);
 				username = nodeprep(username);
 				resource = resourceprep(resource)
 				local reply = st.reply(stanza);
-				require "core.usermanager"
-				if username and resource and usermanager.validate_credentials(session.host, username, password) then
+				if usermanager.validate_credentials(session.host, username, password) then
 					-- Authentication successful!
 					local success, err = sessionmanager.make_authenticated(session, username);
 					if success then
@@ -69,15 +66,9 @@
 						end
 					end
 					session.send(st.reply(stanza));
-					return true;
 				else
-					local reply = st.reply(stanza);
-					reply.attr.type = "error";
-					reply:tag("error", { code = "401", type = "auth" })
-						:tag("not-authorized", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" });
-					session.send(reply);
-					return true;
+					session.send(st.error_reply(stanza, "auth", "not-authorized"));
 				end
 			end
-			
+			return true;
 		end);
--- a/plugins/mod_muc.lua	Tue Oct 13 14:54:07 2009 +0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-if module:get_host_type() ~= "component" then
-	error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0);
-end
-
-local muc_host = module:get_host();
-local muc_name = "Chatrooms";
-local history_length = 20;
-
-local muc_new_room = require "util.muc".new_room;
-local register_component = require "core.componentmanager".register_component;
-local deregister_component = require "core.componentmanager".deregister_component;
-local jid_split = require "util.jid".split;
-local st = require "util.stanza";
-
-local rooms = {};
-local component;
-local host_room = muc_new_room(muc_host);
-host_room.route_stanza = function(room, stanza) core_post_stanza(component, stanza); end;
-
-local function get_disco_info(stanza)
-	return st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
-		:tag("identity", {category='conference', type='text', name=muc_name}):up()
-		:tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
-end
-local function get_disco_items(stanza)
-	local reply = st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
-	for jid, room in pairs(rooms) do
-		reply:tag("item", {jid=jid, name=jid}):up();
-	end
-	return reply; -- TODO cache disco reply
-end
-
-local function handle_to_domain(origin, stanza)
-	local type = stanza.attr.type;
-	if type == "error" or type == "result" then return; end
-	if stanza.name == "iq" and type == "get" then
-		local xmlns = stanza.tags[1].attr.xmlns;
-		if xmlns == "http://jabber.org/protocol/disco#info" then
-			origin.send(get_disco_info(stanza));
-		elseif xmlns == "http://jabber.org/protocol/disco#items" then
-			origin.send(get_disco_items(stanza));
-		else
-			origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc
-		end
-	else
-		host_room:handle_stanza(origin, stanza);
-		--origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it"));
-	end
-end
-
-component = register_component(muc_host, function(origin, stanza)
-	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
-	if to_node then
-		local bare = to_node.."@"..to_host;
-		if to_host == muc_host or bare == muc_host then
-			local room = rooms[bare];
-			if not room then
-				room = muc_new_room(bare);
-				room.route_stanza = function(room, stanza) core_post_stanza(component, stanza); end;
-				rooms[bare] = room;
-			end
-			room:handle_stanza(origin, stanza);
-		else --[[not for us?]] end
-		return;
-	end
-	-- to the main muc domain
-	handle_to_domain(origin, stanza);
-end);
-
-module.unload = function()
-	deregister_component(muc_host);
-end
-module.save = function()
-	return {rooms = rooms};
-end
-module.restore = function(data)
-	rooms = data.rooms or {};
-end
--- a/plugins/mod_offline.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_offline.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -10,7 +10,8 @@
 local datamanager = require "util.datamanager";
 local st = require "util.stanza";
 local datetime = require "util.datetime";
-local ipairs = ipairs;
+local ipairs = ipairs;
+local jid_split = require "util.jid".split;
 
 module:add_feature("msgoffline");
 
--- a/plugins/mod_pep.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_pep.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -25,7 +25,7 @@
 local recipients = {};
 local hash_map = {};
 
-module:add_identity("pubsub", "pep");
+module:add_identity("pubsub", "pep", "Prosody");
 module:add_feature("http://jabber.org/protocol/pubsub#publish");
 
 local function publish(session, node, item)
--- a/plugins/mod_posix.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_posix.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -17,19 +17,45 @@
 	module:log("warn", "Couldn't load signal library, won't respond to SIGTERM");
 end
 
-local config_get = require "core.configmanager".get;
 local logger_set = require "util.logger".setwriter;
 
 local prosody = _G.prosody;
 
 module.host = "*"; -- we're a global module
 
+-- Allow switching away from root, some people like strange ports.
+module:add_event_hook("server-started", function ()
+		local uid = module:get_option("setuid");
+		local gid = module:get_option("setgid");
+		if gid then
+			local success, msg = pposix.setgid(gid);
+			if success then
+				module:log("debug", "Changed group to "..gid.." successfully.");
+			else
+				module:log("error", "Failed to change group to "..gid..". Error: "..msg);
+				prosody.shutdown("Failed to change group to "..gid);
+			end
+		end
+		if uid then
+			local success, msg = pposix.setuid(uid);
+			if success then
+				module:log("debug", "Changed user to "..uid.." successfully.");
+			else
+				module:log("error", "Failed to change user to "..uid..". Error: "..msg);
+				prosody.shutdown("Failed to change user to "..uid);
+			end
+		end
+	end);
+
 -- Don't even think about it!
 module:add_event_hook("server-starting", function ()
-		if pposix.getuid() == 0 and not config_get("*", "core", "run_as_root") then
-			module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
-			module:log("error", "For more information on running Prosody as root, see http://prosody.im/doc/root");
-			prosody.shutdown("Refusing to run as root");
+		local suid = module:get_option("setuid");
+		if not suid or suid == 0 or suid == "root" then
+			if pposix.getuid() == 0 and not module:get_option("run_as_root") then
+				module:log("error", "Danger, Will Robinson! Prosody doesn't need to be run as root, so don't do it!");
+				module:log("error", "For more information on running Prosody as root, see http://prosody.im/doc/root");
+				prosody.shutdown("Refusing to run as root");
+			end
 		end
 	end);
 
@@ -46,7 +72,7 @@
 	if pidfile_written then
 		remove_pidfile();
 	end
-	local pidfile = config_get("*", "core", "pidfile");
+	local pidfile = module:get_option("pidfile");
 	if pidfile then
 		local pf, err = io.open(pidfile, "w+");
 		if not pf then
@@ -76,7 +102,7 @@
 end
 require "core.loggingmanager".register_sink_type("syslog", syslog_sink_maker);
 
-if not config_get("*", "core", "no_daemonize") then
+if not module:get_option("no_daemonize") then
 	local function daemonize_server()
 		local ok, ret = pposix.daemonize();
 		if not ok then
--- a/plugins/mod_register.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_register.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -9,7 +9,6 @@
 
 local hosts = _G.hosts;
 local st = require "util.stanza";
-local config = require "core.configmanager";
 local datamanager = require "util.datamanager";
 local usermanager_user_exists = require "core.usermanager".user_exists;
 local usermanager_create_user = require "core.usermanager".create_user;
@@ -90,16 +89,16 @@
 end);
 
 local recent_ips = {};
-local min_seconds_between_registrations = config.get(module.host, "core", "min_seconds_between_registrations");
-local whitelist_only = config.get(module.host, "core", "whitelist_registration_only");
-local whitelisted_ips = config.get(module.host, "core", "registration_whitelist") or { "127.0.0.1" };
-local blacklisted_ips = config.get(module.host, "core", "registration_blacklist") or {};
+local min_seconds_between_registrations = module:get_option("min_seconds_between_registrations");
+local whitelist_only = module:get_option("whitelist_registration_only");
+local whitelisted_ips = module:get_option("registration_whitelist") or { "127.0.0.1" };
+local blacklisted_ips = module:get_option("registration_blacklist") or {};
 
 for _, ip in ipairs(whitelisted_ips) do whitelisted_ips[ip] = true; end
 for _, ip in ipairs(blacklisted_ips) do blacklisted_ips[ip] = true; end
 
 module:add_iq_handler("c2s_unauthed", "jabber:iq:register", function (session, stanza)
-	if config.get(module.host, "core", "allow_registration") == false then
+	if module:get_option("allow_registration") == false then
 		session.send(st.error_reply(stanza, "cancel", "service-unavailable"));
 	elseif stanza.tags[1].name == "query" then
 		local query = stanza.tags[1];
--- a/plugins/mod_roster.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_roster.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -24,7 +24,7 @@
 
 local rosterver_stream_feature = st.stanza("ver", {xmlns="urn:xmpp:features:rosterver"}):tag("optional"):up();
 module:add_event_hook("stream-features", 
-		function (session, features)												
+		function (session, features)
 			if session.username then
 				features:add_child(rosterver_stream_feature);
 			end
--- a/plugins/mod_saslauth.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_saslauth.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -12,16 +12,20 @@
 local sm_bind_resource = require "core.sessionmanager".bind_resource;
 local sm_make_authenticated = require "core.sessionmanager".make_authenticated;
 local base64 = require "util.encodings".base64;
+
 local nodeprep = require "util.encodings".stringprep.nodeprep;
 local datamanager_load = require "util.datamanager".load;
 local usermanager_validate_credentials = require "core.usermanager".validate_credentials;
+local usermanager_get_supported_methods = require "core.usermanager".get_supported_methods;
+local usermanager_user_exists = require "core.usermanager".user_exists;
+local usermanager_get_password = require "core.usermanager".get_password;
 local t_concat, t_insert = table.concat, table.insert;
 local tostring = tostring;
 local jid_split = require "util.jid".split
 local md5 = require "util.hashes".md5;
 local config = require "core.configmanager";
 
-local secure_auth_only = config.get(module:get_host(), "core", "require_encryption");
+local secure_auth_only = config.get(module:get_host(), "core", "c2s_require_encryption") or config.get(module:get_host(), "core", "require_encryption");
 
 local log = module._log;
 
@@ -56,6 +60,7 @@
 		session.sasl_handler = nil;
 		if not username then -- TODO move this to sessionmanager
 			module:log("warn", "SASL succeeded but we didn't get a username!");
+			session.sasl_handler = nil;
 			session:reset_stream();
 			return;
 		end 
@@ -64,22 +69,36 @@
 	end
 end
 
-local function password_callback(node, hostname, realm, mechanism, decoder)
-	local func = function(x) return x; end;
-	local node = nodeprep(node);
-	if not node then
-		return func, nil;
-	end
-	local password = (datamanager_load(node, hostname, "accounts") or {}).password; -- FIXME handle hashed passwords
-	if password then
-		if mechanism == "PLAIN" then
-			return func, password;
-		elseif mechanism == "DIGEST-MD5" then
-			if decoder then node, realm, password = decoder(node), decoder(realm), decoder(password); end
+local function credentials_callback(mechanism, ...)
+	if mechanism == "PLAIN" then
+		local username, hostname, password = ...;
+		username = nodeprep(username);
+		if not username then
+			return false;
+		end
+		local response = usermanager_validate_credentials(hostname, username, password, mechanism);
+		if response == nil then
+			return false;
+		else
+			return response;
+		end
+	elseif mechanism == "DIGEST-MD5" then
+		function func(x) return x; end
+		local node, domain, realm, decoder = ...;
+		local prepped_node = nodeprep(node);
+		if not prepped_node then
+			return func, nil;
+		end
+		local password = usermanager_get_password(prepped_node, domain);
+		if password then
+			if decoder then
+				node, realm, password = decoder(node), decoder(realm), decoder(password);
+			end
 			return func, md5(node..":"..realm..":"..password);
+		else
+			return func, nil;
 		end
 	end
-	return func, nil;
 end
 
 local function sasl_handler(session, stanza)
@@ -92,7 +111,7 @@
 		elseif stanza.attr.mechanism == "ANONYMOUS" then
 			return session.send(build_reply("failure", "mechanism-too-weak"));
 		end
-		session.sasl_handler = new_sasl(stanza.attr.mechanism, session.host, password_callback);
+		session.sasl_handler = new_sasl(stanza.attr.mechanism, session.host, credentials_callback);
 		if not session.sasl_handler then
 			return session.send(build_reply("failure", "invalid-mechanism"));
 		end
@@ -111,7 +130,7 @@
 	end
 	local status, ret, err_msg = session.sasl_handler:feed(text);
 	handle_status(session, status);
-	local s = build_reply(status, ret, err_msg); 
+	local s = build_reply(status, ret, err_msg);
 	log("debug", "sasl reply: %s", tostring(s));
 	session.send(s);
 end
@@ -123,8 +142,8 @@
 local mechanisms_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-sasl' };
 local bind_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-bind' };
 local xmpp_session_attr = { xmlns='urn:ietf:params:xml:ns:xmpp-session' };
-module:add_event_hook("stream-features", 
-		function (session, features)												
+module:add_event_hook("stream-features",
+		function (session, features)
 			if not session.username then
 				if secure_auth_only and not session.secure then
 					return;
@@ -134,8 +153,10 @@
 					if config.get(session.host or "*", "core", "anonymous_login") then
 						features:tag("mechanism"):text("ANONYMOUS"):up();
 					else
-						features:tag("mechanism"):text("DIGEST-MD5"):up();
-						features:tag("mechanism"):text("PLAIN"):up();
+						mechanisms = usermanager_get_supported_methods(session.host or "*");
+						for k, v in pairs(mechanisms) do
+							features:tag("mechanism"):text(k):up();
+						end
 					end
 				features:up();
 			else
@@ -143,8 +164,8 @@
 				features:tag("session", xmpp_session_attr):up();
 			end
 		end);
-					
-module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind", 
+
+module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind",
 		function (session, stanza)
 			log("debug", "Client requesting a resource bind");
 			local resource;
@@ -166,8 +187,8 @@
 					:tag("jid"):text(session.full_jid));
 			end
 		end);
-		
-module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-session", 
+
+module:add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-session",
 		function (session, stanza)
 			log("debug", "Client requesting a session");
 			session.send(st.reply(stanza));
--- a/plugins/mod_selftests.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_selftests.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -6,14 +6,13 @@
 -- COPYING file in the source package for more information.
 --
 
-
+module.host = "*" -- Global module
 
 local st = require "util.stanza";
 local register_component = require "core.componentmanager".register_component;
 local core_route_stanza = core_route_stanza;
 local socket = require "socket";
-local config = require "core.configmanager";
-local ping_hosts = config.get("*", "mod_selftests", "ping_hosts") or { "coversant.interop.xmpp.org", "djabberd.interop.xmpp.org", "djabberd-trunk.interop.xmpp.org", "ejabberd.interop.xmpp.org", "openfire.interop.xmpp.org" };
+local ping_hosts = module:get_option("ping_hosts") or { "coversant.interop.xmpp.org", "djabberd.interop.xmpp.org", "djabberd-trunk.interop.xmpp.org", "ejabberd.interop.xmpp.org", "openfire.interop.xmpp.org" };
 
 local open_pings = {};
 
--- a/plugins/mod_tls.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_tls.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -6,14 +6,13 @@
 -- COPYING file in the source package for more information.
 --
 
-
-
 local st = require "util.stanza";
 
-local xmlns_starttls ='urn:ietf:params:xml:ns:xmpp-tls';
+local xmlns_stream = 'http://etherx.jabber.org/streams';
+local xmlns_starttls = 'urn:ietf:params:xml:ns:xmpp-tls';
 
-local config = require "core.configmanager";
-local secure_auth_only = config.get("*", "core", "require_encryption");
+local secure_auth_only = module:get_option("c2s_require_encryption") or module:get_option("require_encryption");
+local secure_s2s_only = module:get_option("s2s_require_encryption");
 
 module:add_handler("c2s_unauthed", "starttls", xmlns_starttls,
 		function (session, stanza)
@@ -29,9 +28,24 @@
 			end
 		end);
 		
+module:add_handler("s2sin_unauthed", "starttls", xmlns_starttls,
+		function (session, stanza)
+			if session.conn.starttls then
+				session.sends2s(st.stanza("proceed", { xmlns = xmlns_starttls }));
+				session:reset_stream();
+				session.conn.starttls();
+				session.log("info", "TLS negotiation started for incoming s2s...");
+				session.secure = false;
+			else
+				-- FIXME: What reply?
+				session.log("warn", "Attempt to start TLS, but TLS is not available on this s2s connection");
+			end
+		end);
+
+
 local starttls_attr = { xmlns = xmlns_starttls };
 module:add_event_hook("stream-features", 
-		function (session, features)												
+		function (session, features)
 			if session.conn.starttls then
 				features:tag("starttls", starttls_attr);
 				if secure_auth_only then
@@ -41,3 +55,37 @@
 				end
 			end
 		end);
+
+module:hook("s2s-stream-features", 
+		function (data)
+			local session, features = data.session, data.features;
+			if session.to_host and session.conn.starttls then
+				features:tag("starttls", starttls_attr):up();
+				if secure_s2s_only then
+					features:tag("required"):up():up();
+				else
+					features:up();
+				end
+			end
+		end);
+
+-- For s2sout connections, start TLS if we can
+module:hook_stanza(xmlns_stream, "features",
+		function (session, stanza)
+			module:log("debug", "Received features element");
+			if session.conn.starttls and stanza:child_with_ns(xmlns_starttls) then
+				module:log("%s is offering TLS, taking up the offer...", session.to_host);
+				session.sends2s("<starttls xmlns='"..xmlns_starttls.."'/>");
+				return true;
+			end
+		end, 500);
+
+module:hook_stanza(xmlns_starttls, "proceed",
+		function (session, stanza)
+			module:log("debug", "Proceeding with TLS on s2sout...");
+			local format, to_host, from_host = string.format, session.to_host, session.from_host;
+			session:reset_stream();
+			session.conn.starttls(true);
+			session.secure = false;
+			return true;
+		end);
--- a/plugins/mod_version.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_version.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -6,17 +6,13 @@
 -- COPYING file in the source package for more information.
 --
 
-
-local prosody = prosody;
 local st = require "util.stanza";
 
-local xmlns_version = "jabber:iq:version"
-
-module:add_feature(xmlns_version);
+module:add_feature("jabber:iq:version");
 
 local version = "the best operating system ever!";
 
-if not require "core.configmanager".get("*", "core", "hide_os_type") then
+if not module:get_option("hide_os_type") then
 	if os.getenv("WINDIR") then
 		version = "Windows";
 	else
@@ -31,11 +27,15 @@
 
 version = version:match("^%s*(.-)%s*$") or version;
 
-module:add_iq_handler({"c2s", "s2sin"}, xmlns_version, function(session, stanza)
-	if stanza.attr.type == "get" then
-		session.send(st.reply(stanza):query(xmlns_version)
-			:tag("name"):text("Prosody"):up()
-			:tag("version"):text(prosody.version):up()
-			:tag("os"):text(version));
+local query = st.stanza("query", {xmlns = "jabber:iq:version"})
+	:tag("name"):text("Prosody"):up()
+	:tag("version"):text(prosody.version):up()
+	:tag("os"):text(version);
+
+module:hook("iq/host/jabber:iq:version:query", function(event)
+	local stanza = event.stanza;
+	if stanza.attr.type == "get" and stanza.attr.to == module.host then
+		event.origin.send(st.reply(stanza):add_child(query));
+		return true;
 	end
 end);
--- a/plugins/mod_watchregistrations.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_watchregistrations.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -9,12 +9,10 @@
 
 local host = module:get_host();
 
-local config = require "core.configmanager";
+local registration_watchers = module:get_option("registration_watchers") 
+	or module:get_option("admins") or {};
 
-local registration_watchers = config.get(host, "core", "registration_watchers") 
-	or config.get(host, "core", "admins") or {};
-
-local registration_alert = config.get(host, "core", "registration_notification") or "User $username just registered on $host from $ip";
+local registration_alert = module:get_option("registration_notification") or "User $username just registered on $host from $ip";
 
 local st = require "util.stanza";
 
--- a/plugins/mod_welcome.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_welcome.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -6,10 +6,8 @@
 -- COPYING file in the source package for more information.
 --
 
-local config = require "core.configmanager";
-
 local host = module:get_host();
-local welcome_text = config.get("*", "core", "welcome_message") or "Hello $username, welcome to the $host IM server!";
+local welcome_text = module:get_option("welcome_message") or "Hello $user, welcome to the $host IM server!";
 
 local st = require "util.stanza";
 
--- a/plugins/mod_xmlrpc.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/plugins/mod_xmlrpc.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -16,6 +16,7 @@
 local tostring = tostring;
 local is_admin = require "core.usermanager".is_admin;
 local jid_split = require "util.jid".split;
+local jid_bare = require "util.jid".bare;
 local b64_decode = require "util.encodings".base64.decode;
 local get_method = require "core.objectmanager".get_object;
 local validate_credentials = require "core.usermanager".validate_credentials;
@@ -65,10 +66,15 @@
 	return stanza.tags[1];
 end
 
-local function handle_xmlrpc_request(method, args)
+local function handle_xmlrpc_request(jid, method, args)
+	local is_secure_call = (method:sub(1,7) == "secure/");
+	if not is_admin(jid) and not is_secure_call then
+		return create_error_response(401, "not authorized");
+	end
 	method = get_method(method);
 	if not method then return create_error_response(404, "method not found"); end
 	args = args or {};
+	if is_secure_call then table.insert(args, 1, jid); end
 	local success, result = pcall(method, unpack(args));
 	if success then
 		success, result = pcall(create_response, result or "nil");
@@ -77,22 +83,20 @@
 		end
 		return create_error_response(500, "Error in creating response: "..result);
 	end
-	return create_error_response(0, result or "nil");
+	return create_error_response(0, tostring(result):gsub("^[^:]+:%d+: ", ""));
 end
 
 local function handle_xmpp_request(origin, stanza)
 	local query = stanza.tags[1];
 	if query.name == "query" then
 		if #query.tags == 1 then
-			if is_admin(stanza.attr.from) then
-				local success, method, args = pcall(translate_request, query.tags[1]);
-				if success then
-					local result = handle_xmlrpc_request(method, args);
-					origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:rpc'}):add_child(result));
-				else
-					origin.send(st.error_reply(stanza, "modify", "bad-request", method));
-				end
-			else origin.send(st.error_reply(stanza, "auth", "forbidden", "No content in XML-RPC request")); end
+			local success, method, args = pcall(translate_request, query.tags[1]);
+			if success then
+				local result = handle_xmlrpc_request(jid_bare(stanza.attr.from), method, args);
+				origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:rpc'}):add_child(result));
+			else
+				origin.send(st.error_reply(stanza, "modify", "bad-request", method));
+			end
 		else origin.send(st.error_reply(stanza, "modify", "bad-request", "No content in XML-RPC request")); end
 	else origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end
 end
@@ -106,7 +110,7 @@
 	-- authenticate user
 	local username, password = b64_decode(request['authorization'] or ''):gmatch('([^:]*):(.*)')(); -- TODO digest auth
 	local node, host = jid_split(username);
-	if not validate_credentials(host, node, password) and is_admin(username) then
+	if not validate_credentials(host, node, password) then
 		return unauthorized_response;
 	end
 	-- parse request
@@ -117,7 +121,7 @@
 	-- execute request
 	local success, method, args = pcall(translate_request, stanza);
 	if success then
-		return { headers = default_headers; body = tostring(handle_xmlrpc_request(method, args)) };
+		return { headers = default_headers; body = tostring(handle_xmlrpc_request(node.."@"..host, method, args)) };
 	end
 	return "<html><body>Error parsing XML-RPC request: "..tostring(method).."</body></html>";
 end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/muc/mod_muc.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -0,0 +1,149 @@
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+
+if module:get_host_type() ~= "component" then
+	error("MUC should be loaded as a component, please see http://prosody.im/doc/components", 0);
+end
+
+local muc_host = module:get_host();
+local muc_name = "Chatrooms";
+local history_length = 20;
+
+local muc_new_room = module:require "muc".new_room;
+local register_component = require "core.componentmanager".register_component;
+local deregister_component = require "core.componentmanager".deregister_component;
+local jid_split = require "util.jid".split;
+local st = require "util.stanza";
+local uuid_gen = require "util.uuid".generate;
+local datamanager = require "util.datamanager";
+
+local rooms = {};
+local persistent_rooms = datamanager.load(nil, muc_host, "persistent") or {};
+local component;
+
+local function room_route_stanza(room, stanza) core_post_stanza(component, stanza); end
+local function room_save(room, forced)
+	local node = jid_split(room.jid);
+	persistent_rooms[room.jid] = room._data.persistent;
+	if room._data.persistent then
+		local history = room._data.history;
+		room._data.history = nil;
+		local data = {
+			jid = room.jid;
+			_data = room._data;
+			_affiliations = room._affiliations;
+		};
+		datamanager.store(node, muc_host, "config", data);
+		room._data.history = history;
+	elseif forced then
+		datamanager.store(node, muc_host, "config", nil);
+	end
+	if forced then datamanager.store(nil, muc_host, "persistent", persistent_rooms); end
+end
+
+for jid in pairs(persistent_rooms) do
+	local node = jid_split(jid);
+	local data = datamanager.load(node, muc_host, "config") or {};
+	local room = muc_new_room(jid);
+	room._data = data._data;
+	room._affiliations = data._affiliations;
+	room.route_stanza = room_route_stanza;
+	room.save = room_save;
+	rooms[jid] = room;
+end
+
+local host_room = muc_new_room(muc_host);
+host_room.route_stanza = room_route_stanza;
+host_room.save = room_save;
+
+local function get_disco_info(stanza)
+	return st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
+		:tag("identity", {category='conference', type='text', name=muc_name}):up()
+		:tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
+end
+local function get_disco_items(stanza)
+	local reply = st.iq({type='result', id=stanza.attr.id, from=muc_host, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
+	for jid, room in pairs(rooms) do
+		if not room._data.hidden then
+			reply:tag("item", {jid=jid, name=jid}):up();
+		end
+	end
+	return reply; -- TODO cache disco reply
+end
+
+local function handle_to_domain(origin, stanza)
+	local type = stanza.attr.type;
+	if type == "error" or type == "result" then return; end
+	if stanza.name == "iq" and type == "get" then
+		local xmlns = stanza.tags[1].attr.xmlns;
+		if xmlns == "http://jabber.org/protocol/disco#info" then
+			origin.send(get_disco_info(stanza));
+		elseif xmlns == "http://jabber.org/protocol/disco#items" then
+			origin.send(get_disco_items(stanza));
+		elseif xmlns == "http://jabber.org/protocol/muc#unique" then
+			origin.send(st.reply(stanza):tag("unique", {xmlns = xmlns}):text(uuid_gen())); -- FIXME Random UUIDs can theoretically have collisions
+		else
+			origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc
+		end
+	else
+		host_room:handle_stanza(origin, stanza);
+		--origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it"));
+	end
+end
+
+component = register_component(muc_host, function(origin, stanza)
+	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
+	if to_node then
+		local bare = to_node.."@"..to_host;
+		if to_host == muc_host or bare == muc_host then
+			local room = rooms[bare];
+			if not room then
+				room = muc_new_room(bare);
+				room.route_stanza = room_route_stanza;
+				room.save = room_save;
+				rooms[bare] = room;
+			end
+			room:handle_stanza(origin, stanza);
+			if not next(room._occupants) and not persistent_rooms[room.jid] then -- empty, non-persistent room
+				rooms[bare] = nil; -- discard room
+			end
+		else --[[not for us?]] end
+		return;
+	end
+	-- to the main muc domain
+	handle_to_domain(origin, stanza);
+end);
+function component.send(stanza) -- FIXME do a generic fix
+	if stanza.attr.type == "result" or stanza.attr.type == "error" then
+		core_post_stanza(component, stanza);
+	else error("component.send only supports result and error stanzas at the moment"); end
+end
+
+prosody.hosts[module:get_host()].muc = { rooms = rooms };
+
+module.unload = function()
+	deregister_component(muc_host);
+end
+module.save = function()
+	return {rooms = rooms};
+end
+module.restore = function(data)
+	rooms = {};
+	for jid, oldroom in pairs(data.rooms or {}) do
+		local room = muc_new_room(jid);
+		room._jid_nick = oldroom._jid_nick;
+		room._occupants = oldroom._occupants;
+		room._data = oldroom._data;
+		room._affiliations = oldroom._affiliations;
+		room.route_stanza = room_route_stanza;
+		room.save = room_save;
+		rooms[jid] = room;
+	end
+	prosody.hosts[module:get_host()].muc = { rooms = rooms };
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/muc/muc.lib.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -0,0 +1,636 @@
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local datamanager = require "util.datamanager";
+local datetime = require "util.datetime";
+
+local jid_split = require "util.jid".split;
+local jid_bare = require "util.jid".bare;
+local jid_prep = require "util.jid".prep;
+local st = require "util.stanza";
+local log = require "util.logger".init("mod_muc");
+local multitable_new = require "util.multitable".new;
+local t_insert, t_remove = table.insert, table.remove;
+local setmetatable = setmetatable;
+local base64 = require "util.encodings".base64;
+local md5 = require "util.hashes".md5;
+
+local muc_domain = nil; --module:get_host();
+local history_length = 20;
+
+------------
+local function filter_xmlns_from_array(array, filters)
+	local count = 0;
+	for i=#array,1,-1 do
+		local attr = array[i].attr;
+		if filters[attr and attr.xmlns] then
+			t_remove(array, i);
+			count = count + 1;
+		end
+	end
+	return count;
+end
+local function filter_xmlns_from_stanza(stanza, filters)
+	if filters then
+		if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then
+			return stanza, filter_xmlns_from_array(stanza, filters);
+		end
+	end
+	return stanza, 0;
+end
+local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
+local function get_filtered_presence(stanza)
+	return filter_xmlns_from_stanza(st.clone(stanza), presence_filters);
+end
+local kickable_error_conditions = {
+	["gone"] = true;
+	["internal-server-error"] = true;
+	["item-not-found"] = true;
+	["jid-malformed"] = true;
+	["recipient-unavailable"] = true;
+	["redirect"] = true;
+	["remote-server-not-found"] = true;
+	["remote-server-timeout"] = true;
+	["service-unavailable"] = true;
+};
+local function get_kickable_error(stanza)
+	for _, tag in ipairs(stanza.tags) do
+		if tag.name == "error" and tag.attr.xmlns == "jabber:client" then
+			for _, cond in ipairs(tag.tags) do
+				if cond.attr.xmlns == "urn:ietf:params:xml:ns:xmpp-stanzas" then
+					return kickable_error_conditions[cond.name] and cond.name;
+				end
+			end
+			return true; -- malformed error message
+		end
+	end
+	return true; -- malformed error message
+end
+local function getUsingPath(stanza, path, getText)
+	local tag = stanza;
+	for _, name in ipairs(path) do
+		if type(tag) ~= 'table' then return; end
+		tag = tag:child_with_name(name);
+	end
+	if tag and getText then tag = table.concat(tag); end
+	return tag;
+end
+local function getTag(stanza, path) return getUsingPath(stanza, path); end
+local function getText(stanza, path) return getUsingPath(stanza, path, true); end
+-----------
+
+--[[function get_room_disco_info(room, stanza)
+	return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
+		:tag("identity", {category='conference', type='text', name=room._data["name"]):up()
+		:tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
+end
+function get_room_disco_items(room, stanza)
+	return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
+end -- TODO allow non-private rooms]]
+
+--
+
+local room_mt = {};
+room_mt.__index = room_mt;
+
+function room_mt:get_default_role(affiliation)
+	if affiliation == "owner" or affiliation == "admin" then
+		return "moderator";
+	elseif affiliation == "member" or not affiliation then
+		return "participant";
+	end
+end
+
+function room_mt:broadcast_presence(stanza, code, nick)
+	stanza = get_filtered_presence(stanza);
+	local occupant = self._occupants[stanza.attr.from];
+	stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+		:tag("item", {affiliation=occupant.affiliation, role=occupant.role, nick=nick}):up();
+	if code then
+		stanza:tag("status", {code=code}):up();
+	end
+	self:broadcast_except_nick(stanza, stanza.attr.from);
+	local me = self._occupants[stanza.attr.from];
+	if me then
+		stanza:tag("status", {code='110'});
+		for jid in pairs(me.sessions) do
+			stanza.attr.to = jid;
+			self:route_stanza(stanza);
+		end
+	end
+end
+function room_mt:broadcast_message(stanza, historic)
+	for occupant, o_data in pairs(self._occupants) do
+		for jid in pairs(o_data.sessions) do
+			stanza.attr.to = jid;
+			self:route_stanza(stanza);
+		end
+	end
+	if historic then -- add to history
+		local history = self._data['history'];
+		if not history then history = {}; self._data['history'] = history; end
+		-- stanza = st.clone(stanza);
+		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
+		stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
+		t_insert(history, st.clone(st.preserialize(stanza)));
+		while #history > history_length do t_remove(history, 1) end
+	end
+end
+function room_mt:broadcast_except_nick(stanza, nick)
+	for rnick, occupant in pairs(self._occupants) do
+		if rnick ~= nick then
+			for jid in pairs(occupant.sessions) do
+				stanza.attr.to = jid;
+				self:route_stanza(stanza);
+			end
+		end
+	end
+end
+
+function room_mt:send_occupant_list(to)
+	local current_nick = self._jid_nick[to];
+	for occupant, o_data in pairs(self._occupants) do
+		if occupant ~= current_nick then
+			local pres = get_filtered_presence(o_data.sessions[o_data.jid]);
+			pres.attr.to, pres.attr.from = to, occupant;
+			pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+				:tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up();
+			self:route_stanza(pres);
+		end
+	end
+end
+function room_mt:send_history(to)
+	local history = self._data['history']; -- send discussion history
+	if history then
+		for _, msg in ipairs(history) do
+			msg = st.deserialize(msg);
+			msg.attr.to=to;
+			self:route_stanza(msg);
+		end
+	end
+	if self._data['subject'] then
+		self:route_stanza(st.message({type='groupchat', from=self.jid, to=to}):tag("subject"):text(self._data['subject']));
+	end
+end
+
+local function room_get_disco_info(self, stanza)
+	return st.reply(stanza):query("http://jabber.org/protocol/disco#info")
+		:tag("identity", {category="conference", type="text"}):up()
+		:tag("feature", {var="http://jabber.org/protocol/muc"});
+end
+local function room_get_disco_items(self, stanza)
+	return st.reply(stanza):query("http://jabber.org/protocol/disco#items");
+end
+function room_mt:set_subject(current_nick, subject)
+	-- TODO check nick's authority
+	if subject == "" then subject = nil; end
+	self._data['subject'] = subject;
+	if self.save then self:save(); end
+	local msg = st.message({type='groupchat', from=current_nick})
+		:tag('subject'):text(subject):up();
+	self:broadcast_message(msg, false);
+	return true;
+end
+
+function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
+	local from, to = stanza.attr.from, stanza.attr.to;
+	local room = jid_bare(to);
+	local current_nick = self._jid_nick[from];
+	local type = stanza.attr.type;
+	log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag());
+	if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end
+	if stanza.name == "presence" then
+		local pr = get_filtered_presence(stanza);
+		pr.attr.from = current_nick;
+		if type == "error" then -- error, kick em out!
+			if current_nick then
+				log("debug", "kicking %s from %s", current_nick, room);
+				self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to})
+					:tag('status'):text('This participant is kicked from the room because he sent an error presence')); -- send unavailable
+			end
+		elseif type == "unavailable" then -- unavailable
+			if current_nick then
+				log("debug", "%s leaving %s", current_nick, room);
+				local occupant = self._occupants[current_nick];
+				local old_session = occupant.sessions[from];
+				local new_jid = next(occupant.sessions);
+				if new_jid == from then new_jid = next(occupant.sessions, new_jid); end
+				if new_jid then
+					occupant.jid = new_jid;
+					occupant.sessions[from] = nil;
+					local pr = st.clone(occupant[new_jid])
+						:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
+						:tag("item", {affiliation=occupant.affiliation, role=occupant.role});
+					self:broadcast_except_nick(pr, current_nick);
+				else
+					occupant.role = 'none';
+					self:broadcast_presence(pr);
+					self._occupants[current_nick] = nil;
+				end
+				self._jid_nick[from] = nil;
+			end
+		elseif not type then -- available
+			if current_nick then
+				--if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence
+					if current_nick == to then -- simple presence
+						log("debug", "%s broadcasted presence", current_nick);
+						self._occupants[current_nick].sessions[from] = pr;
+						self:broadcast_presence(pr);
+					else -- change nick
+						if self._occupants[to] then
+							log("debug", "%s couldn't change nick", current_nick);
+							local reply = st.error_reply(stanza, "cancel", "conflict"):up();
+							reply.tags[1].attr.code = "409";
+							origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
+						else
+							local data = self._occupants[current_nick];
+							local to_nick = select(3, jid_split(to));
+							if to_nick then
+								log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to);
+								local p = st.presence({type='unavailable', from=current_nick});
+								self:broadcast_presence(p, '303', to_nick);
+								self._occupants[current_nick] = nil;
+								self._occupants[to] = data;
+								self._jid_nick[from] = to;
+								pr.attr.from = to;
+								self._occupants[to].sessions[from] = pr;
+								self:broadcast_presence(pr);
+							else
+								--TODO malformed-jid
+							end
+						end
+					end
+				--else -- possible rejoin
+				--	log("debug", "%s had connection replaced", current_nick);
+				--	self:handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to})
+				--		:tag('status'):text('Replaced by new connection'):up()); -- send unavailable
+				--	self:handle_to_occupant(origin, stanza); -- resend available
+				--end
+			else -- enter room
+				local new_nick = to;
+				if self._occupants[to] then
+					new_nick = nil;
+				end
+				if not new_nick then
+					log("debug", "%s couldn't join due to nick conflict: %s", from, to);
+					local reply = st.error_reply(stanza, "cancel", "conflict"):up();
+					reply.tags[1].attr.code = "409";
+					origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
+				else
+					log("debug", "%s joining as %s", from, to);
+					if not next(self._affiliations) then -- new room, no owners
+						self._affiliations[jid_bare(from)] = "owner";
+					end
+					local affiliation = self:get_affiliation(from);
+					local role = self:get_default_role(affiliation)
+					if role then -- new occupant
+						self._occupants[to] = {affiliation=affiliation, role=role, jid=from, sessions={[from]=get_filtered_presence(stanza)}};
+						self._jid_nick[from] = to;
+						self:send_occupant_list(from);
+						pr.attr.from = to;
+						self:broadcast_presence(pr);
+						self:send_history(from);
+					else -- banned
+						local reply = st.error_reply(stanza, "auth", "forbidden"):up();
+						reply.tags[1].attr.code = "403";
+						origin.send(reply:tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
+					end
+				end
+			end
+		elseif type ~= 'result' then -- bad type
+			origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error?
+		end
+	elseif not current_nick then -- not in room
+		if type == "error" or type == "result" then
+			local id = stanza.name == "iq" and stanza.attr.id and base64.decode(stanza.attr.id);
+			local _nick, _id, _hash = (id or ""):match("^(.+)%z(.*)%z(.+)$");
+			local occupant = self._occupants[stanza.attr.to];
+			if occupant and _nick and self._jid_nick[_nick] and _id and _hash then
+				local id, _to = stanza.attr.id;
+				for jid in pairs(occupant.sessions) do
+					if md5(jid) == _hash then
+						_to = jid;
+						break;
+					end
+				end
+				if _to then
+					stanza.attr.to, stanza.attr.from, stanza.attr.id = _to, self._jid_nick[_nick], _id;
+					self:route_stanza(stanza);
+					stanza.attr.to, stanza.attr.from, stanza.attr.id = to, from, id;
+				end
+			end
+		else
+			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+		end
+	elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM
+		origin.send(st.error_reply(stanza, "modify", "bad-request"));
+	else -- private stanza
+		local o_data = self._occupants[to];
+		if o_data then
+			log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid);
+			local jid = o_data.jid;
+			local bare = jid_bare(jid);
+			stanza.attr.to, stanza.attr.from = jid, current_nick;
+			local id = stanza.attr.id;
+			if stanza.name=='iq' and type=='get' and stanza.tags[1].attr.xmlns == 'vcard-temp' and bare ~= jid then
+				stanza.attr.to = bare;
+				stanza.attr.id = base64.encode(jid.."\0"..id.."\0"..md5(from));
+			end
+			self:route_stanza(stanza);
+			stanza.attr.to, stanza.attr.from, stanza.attr.id = to, from, id;
+		elseif type ~= "error" and type ~= "result" then -- recipient not in room
+			origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room"));
+		end
+	end
+end
+
+function room_mt:handle_form(origin, stanza)
+	if self:get_affiliation(stanza.attr.from) ~= "owner" then origin.send(st.error_reply(stanza, "auth", "forbidden")); return; end
+	if stanza.attr.type == "get" then
+		local title = "Configuration for "..self.jid;
+		origin.send(st.reply(stanza):query("http://jabber.org/protocol/muc#owner")
+			:tag("x", {xmlns='jabber:x:data', type='form'})
+				:tag("title"):text(title):up()
+				:tag("instructions"):text(title):up()
+				:tag("field", {type='hidden', var='FORM_TYPE'}):tag("value"):text("http://jabber.org/protocol/muc#roomconfig"):up():up()
+				:tag("field", {type='boolean', label='Make Room Persistent?', var='muc#roomconfig_persistentroom'})
+					:tag("value"):text(self._data.persistent and "1" or "0"):up()
+				:up()
+				:tag("field", {type='boolean', label='Make Room Publicly Searchable?', var='muc#roomconfig_publicroom'})
+					:tag("value"):text(self._data.hidden and "0" or "1"):up()
+				:up()
+		);
+	elseif stanza.attr.type == "set" then
+		local query = stanza.tags[1];
+		local form;
+		for _, tag in ipairs(query.tags) do if tag.name == "x" and tag.attr.xmlns == "jabber:x:data" then form = tag; break; end end
+		if not form then origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); return; end
+		if form.attr.type == "cancel" then origin.send(st.reply(stanza)); return; end
+		if form.attr.type ~= "submit" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+		local fields = {};
+		for _, field in pairs(form.tags) do
+			if field.name == "field" and field.attr.var and field.tags[1].name == "value" and #field.tags[1].tags == 0 then
+				fields[field.attr.var] = field.tags[1][1] or "";
+			end
+		end
+		if fields.FORM_TYPE ~= "http://jabber.org/protocol/muc#roomconfig" then origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+
+		local persistent = fields['muc#roomconfig_persistentroom'];
+		if persistent == "0" or persistent == "false" then persistent = nil; elseif persistent == "1" or persistent == "true" then persistent = true;
+		else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+		self._data.persistent = persistent;
+		module:log("debug", "persistent=%s", tostring(persistent));
+
+		local public = fields['muc#roomconfig_publicroom'];
+		if public == "0" or public == "false" then public = nil; elseif public == "1" or public == "true" then public = true;
+		else origin.send(st.error_reply(stanza, "cancel", "bad-request")); return; end
+		self._data.hidden = not public and true or nil;
+
+		if self.save then self:save(true); end
+		origin.send(st.reply(stanza));
+	end
+end
+
+function room_mt:handle_to_room(origin, stanza) -- presence changes and groupchat messages, along with disco/etc
+	local type = stanza.attr.type;
+	local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns;
+	if stanza.name == "iq" then
+		if xmlns == "http://jabber.org/protocol/disco#info" and type == "get" then
+			origin.send(room_get_disco_info(self, stanza));
+		elseif xmlns == "http://jabber.org/protocol/disco#items" and type == "get" then
+			origin.send(room_get_disco_items(self, stanza));
+		elseif xmlns == "http://jabber.org/protocol/muc#admin" then
+			local actor = stanza.attr.from;
+			local affiliation = self:get_affiliation(actor);
+			local current_nick = self._jid_nick[actor];
+			local role = current_nick and self._occupants[current_nick].role or self:get_default_role(affiliation);
+			local item = stanza.tags[1].tags[1];
+			if item and item.name == "item" then
+				if type == "set" then
+					local callback = function() origin.send(st.reply(stanza)); end
+					if item.attr.jid then -- Validate provided JID
+						item.attr.jid = jid_prep(item.attr.jid);
+						if not item.attr.jid then
+							origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
+							return;
+						end
+					end
+					if not item.attr.jid and item.attr.nick then -- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
+						local occupant = self._occupants[self.jid.."/"..item.attr.nick];
+						if occupant then item.attr.jid = occupant.jid; end
+					end
+					if item.attr.affiliation and item.attr.jid and not item.attr.role then
+						local success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, callback);
+						if not success then origin.send(st.error_reply(stanza, errtype, err)); end
+					elseif item.attr.role and item.attr.nick and not item.attr.affiliation then
+						local success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, callback);
+						if not success then origin.send(st.error_reply(stanza, errtype, err)); end
+					else
+						origin.send(st.error_reply(stanza, "cancel", "bad-request"));
+					end
+				elseif type == "get" then
+					local _aff = item.attr.affiliation;
+					local _rol = item.attr.role;
+					if _aff and not _rol then
+						if affiliation == "owner" or (affiliation == "admin" and _aff ~= "owner" and _aff ~= "admin") then
+							local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
+							for jid, affiliation in pairs(self._affiliations) do
+								if affiliation == _aff then
+									reply:tag("item", {affiliation = _aff, jid = jid}):up();
+								end
+							end
+							origin.send(reply);
+						else
+							origin.send(st.error_reply(stanza, "auth", "forbidden"));
+						end
+					elseif _rol and not _aff then
+						if role == "moderator" then
+							-- TODO allow admins and owners not in room? Provide read-only access to everyone who can see the participants anyway?
+							if _rol == "none" then _rol = nil; end
+							local reply = st.reply(stanza):query("http://jabber.org/protocol/muc#admin");
+							for nick, occupant in pairs(self._occupants) do
+								if occupant.role == _rol then
+									reply:tag("item", {nick = nick, role = _rol or "none", affiliation = occupant.affiliation or "none", jid = occupant.jid}):up();
+								end
+							end
+							origin.send(reply);
+						else
+							origin.send(st.error_reply(stanza, "auth", "forbidden"));
+						end
+					else
+						origin.send(st.error_reply(stanza, "cancel", "bad-request"));
+					end
+				end
+			elseif type == "set" or type == "get" then
+				origin.send(st.error_reply(stanza, "cancel", "bad-request"));
+			end
+		elseif xmlns == "http://jabber.org/protocol/muc#owner" and (type == "get" or type == "set") and stanza.tags[1].name == "query" then
+			self:handle_form(origin, stanza);
+		elseif type == "set" or type == "get" then
+			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+		end
+	elseif stanza.name == "message" and type == "groupchat" then
+		local from, to = stanza.attr.from, stanza.attr.to;
+		local room = jid_bare(to);
+		local current_nick = self._jid_nick[from];
+		if not current_nick then -- not in room
+			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+		else
+			local from = stanza.attr.from;
+			stanza.attr.from = current_nick;
+			local subject = getText(stanza, {"subject"});
+			if subject then
+				self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
+			else
+				self:broadcast_message(stanza, true);
+			end
+		end
+	elseif stanza.name == "message" and type == "error" and get_kickable_error(stanza) then
+		local current_nick = self._jid_nick[stanza.attr.from];
+		log("debug", "%s kicked from %s for sending an error message", current_nick, self.jid);
+		self:handle_to_occupant(origin, st.presence({type='unavailable', from=stanza.attr.from, to=stanza.attr.to})
+			:tag('status'):text('This participant is kicked from the room because he sent an error message to another occupant')); -- send unavailable
+	elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick
+		local to = stanza.attr.to;
+		local current_nick = self._jid_nick[stanza.attr.from];
+		if current_nick then
+			stanza.attr.to = current_nick;
+			self:handle_to_occupant(origin, stanza);
+			stanza.attr.to = to;
+		elseif type ~= "error" and type ~= "result" then
+			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+		end
+	elseif stanza.name == "message" and not stanza.attr.type and #stanza.tags == 1 and self._jid_nick[stanza.attr.from]
+		and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" and #stanza.tags[1].tags == 1
+		and stanza.tags[1].tags[1].name == "invite" and stanza.tags[1].tags[1].attr.to then
+		local _from, _to = stanza.attr.from, stanza.attr.to;
+		local _invitee = stanza.tags[1].tags[1].attr.to;
+		stanza.attr.from, stanza.attr.to = _to, _invitee;
+		stanza.tags[1].tags[1].attr.from, stanza.tags[1].tags[1].attr.to = _from, nil;
+		self:route_stanza(stanza);
+		stanza.tags[1].tags[1].attr.from, stanza.tags[1].tags[1].attr.to = nil, _invitee;
+		stanza.attr.from, stanza.attr.to = _from, _to;
+	else
+		if type == "error" or type == "result" then return; end
+		origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+	end
+end
+
+function room_mt:handle_stanza(origin, stanza)
+	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
+	if to_resource then
+		self:handle_to_occupant(origin, stanza);
+	else
+		self:handle_to_room(origin, stanza);
+	end
+end
+
+function room_mt:route_stanza(stanza) end -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end
+
+function room_mt:get_affiliation(jid)
+	local node, host, resource = jid_split(jid);
+	local bare = node and node.."@"..host or host;
+	local result = self._affiliations[bare]; -- Affiliations are granted, revoked, and maintained based on the user's bare JID.
+	if not result and self._affiliations[host] == "outcast" then result = "outcast"; end -- host banned
+	return result;
+end
+function room_mt:set_affiliation(actor, jid, affiliation, callback)
+	jid = jid_bare(jid);
+	if affiliation == "none" then affiliation = nil; end
+	if affiliation and affiliation ~= "outcast" and affiliation ~= "owner" and affiliation ~= "admin" and affiliation ~= "member" then
+		return nil, "modify", "not-acceptable";
+	end
+	if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end
+	if jid_bare(actor) == jid then return nil, "cancel", "not-allowed"; end
+	self._affiliations[jid] = affiliation;
+	local role = self:get_default_role(affiliation);
+	local p = st.presence()
+		:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
+			:tag("item", {affiliation=affiliation or "none", role=role or "none"}):up();
+	local x = p.tags[1];
+	local item = x.tags[1];
+	if not role then -- getting kicked
+		p.attr.type = "unavailable";
+		if affiliation == "outcast" then
+			x:tag("status", {code="301"}):up(); -- banned
+		else
+			x:tag("status", {code="321"}):up(); -- affiliation change
+		end
+	end
+	local modified_nicks = {};
+	for nick, occupant in pairs(self._occupants) do
+		if jid_bare(occupant.jid) == jid then
+			if not role then -- getting kicked
+				self._occupants[nick] = nil;
+			else
+				t_insert(modified_nicks, nick);
+				occupant.affiliation, occupant.role = affiliation, role;
+			end
+			p.attr.from = nick;
+			for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick
+				if not role then self._jid_nick[jid] = nil; end
+				p.attr.to = jid;
+				self:route_stanza(p);
+			end
+		end
+	end
+	if self.save then self:save(); end
+	if callback then callback(); end
+	for _, nick in ipairs(modified_nicks) do
+		p.attr.from = nick;
+		self:broadcast_except_nick(p, nick);
+	end
+	return true;
+end
+
+function room_mt:get_role(nick)
+	local session = self._occupants[nick];
+	return session and session.role or nil;
+end
+function room_mt:set_role(actor, nick, role, callback)
+	if role == "none" then role = nil; end
+	if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end
+	if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end
+	local occupant = self._occupants[nick];
+	if not occupant then return nil, "modify", "not-acceptable"; end
+	if occupant.affiliation == "owner" or occupant.affiliation == "admin" then return nil, "cancel", "not-allowed"; end
+	local p = st.presence({from = nick})
+		:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
+			:tag("item", {affiliation=occupant.affiliation or "none", nick=nick, role=role or "none"}):up();
+	if not role then -- kick
+		p.attr.type = "unavailable";
+		self._occupants[nick] = nil;
+		for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick
+			self._jid_nick[jid] = nil;
+		end
+		p:tag("status", {code = "307"}):up();
+	else
+		occupant.role = role;
+	end
+	for jid in pairs(occupant.sessions) do -- send to all sessions of the nick
+		p.attr.to = jid;
+		self:route_stanza(p);
+	end
+	if callback then callback(); end
+	self:broadcast_except_nick(p, nick);
+	return true;
+end
+
+local _M = {}; -- module "muc"
+
+function _M.new_room(jid)
+	return setmetatable({
+		jid = jid;
+		_jid_nick = {};
+		_occupants = {};
+		_data = {};
+		_affiliations = {};
+	}, room_mt);
+end
+
+return _M;
--- a/prosody	Tue Oct 13 14:54:07 2009 +0500
+++ b/prosody	Tue Oct 13 15:00:59 2009 +0500
@@ -36,7 +36,7 @@
 
 config = require "core.configmanager"
 
-do
+function read_config()
 	-- TODO: Check for other formats when we add support for them
 	-- Use lfs? Make a new conf/ dir?
 	local ok, level, err = config.load((CFG_CONFIGDIR or ".").."/prosody.cfg.lua");
@@ -62,236 +62,280 @@
 	end
 end
 
---- Initialize logging
-require "core.loggingmanager"
-
---- Check runtime dependencies
-require "util.dependencies"
+function load_libraries()
+	--- Initialize logging
+	require "core.loggingmanager"
+	
+	--- Check runtime dependencies
+	require "util.dependencies"
+	
+	--- Load socket framework
+	server = require "net.server"
+end	
 
---- Load socket framework
-local server = require "net.server"
+function init_global_state()
+	bare_sessions = {};
+	full_sessions = {};
+	hosts = {};
 
-bare_sessions = {};
-full_sessions = {};
-hosts = {};
-
--- Global 'prosody' object
-prosody = {};
-local prosody = prosody;
+	-- Global 'prosody' object
+	prosody = {};
+	local prosody = prosody;
+	
+	prosody.bare_sessions = bare_sessions;
+	prosody.full_sessions = full_sessions;
+	prosody.hosts = hosts;
+	
+	prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
+	                  plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
+	
+	prosody.arg = _G.arg;
 
-prosody.bare_sessions = bare_sessions;
-prosody.full_sessions = full_sessions;
-prosody.hosts = hosts;
-
-prosody.paths = { source = CFG_SOURCEDIR, config = CFG_CONFIGDIR, 
-                  plugins = CFG_PLUGINDIR, data = CFG_DATADIR };
-
-prosody.arg = arg;
-
-prosody.events = require "util.events".new();
+	prosody.events = require "util.events".new();
+	
+	prosody.platform = "unknown";
+	if os.getenv("WINDIR") then
+		prosody.platform = "windows";
+	elseif package.config:sub(1,1) == "/" then
+		prosody.platform = "posix";
+	end
+	
+	prosody.installed = nil;
+	if CFG_SOURCEDIR and (prosody.platform == "windows" or CFG_SOURCEDIR:match("^/")) then
+		prosody.installed = true;
+	end
+	
+	-- Function to reload the config file
+	function prosody.reload_config()
+		log("info", "Reloading configuration file");
+		prosody.events.fire_event("reloading-config");
+		local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
+		if not ok then
+			if level == "parser" then
+				log("error", "There was an error parsing the configuration file: %s", tostring(err));
+			elseif level == "file" then
+				log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
+			end
+		end
+		return ok, (err and tostring(level)..": "..tostring(err)) or nil;
+	end
 
--- Try to determine version
-local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
-if version_file then
-	prosody.version = version_file:read("*a"):gsub("%s*$", "");
-	version_file:close();
-	if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
-		prosody.version = "hg:"..prosody.version;
+	-- Function to reopen logfiles
+	function prosody.reopen_logfiles()
+		log("info", "Re-opening log files");
+		eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
+		prosody.events.fire_event("reopen-log-files");
 	end
-else
-	prosody.version = "unknown";
+
+	-- Function to initiate prosody shutdown
+	function prosody.shutdown(reason)
+		log("info", "Shutting down: %s", reason or "unknown reason");
+		prosody.shutdown_reason = reason;
+		prosody.events.fire_event("server-stopping", {reason = reason});
+		server.setquitting(true);
+	end
 end
 
-log("info", "Hello and welcome to Prosody version %s", prosody.version);
-
---- Load and initialise core modules
-require "util.import"
-require "core.xmlhandlers"
-require "core.rostermanager"
-require "core.eventmanager"
-require "core.hostmanager"
-require "core.modulemanager"
-require "core.usermanager"
-require "core.sessionmanager"
-require "core.stanza_router"
-
-require "util.array"
-require "util.iterators"
-require "util.timer"
-
--- Commented to protect us from 
--- the second kind of people
---[[ 
-pcall(require, "remdebug.engine");
-if remdebug then remdebug.engine.start() end
-]]
-
-local cl = require "net.connlisteners";
-
-require "util.stanza"
-require "util.jid"
-
-------------------------------------------------------------------------
-
-
-------------- Begin code without a home ---------------------
-
-local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
-require "util.datamanager".set_data_path(data_path);
-require "util.datamanager".add_callback(function(username, host, datastore, data)
-	if config.get(host, "core", "anonymous_login") then
-		return false;
-	end
-	return username, host, datastore, data;
-end);
-
------------ End of out-of-place code --------------
-
--- Function to reload the config file
-function prosody.reload_config()
-	log("info", "Reloading configuration file");
-	prosody.events.fire_event("reloading-config");
-	local ok, level, err = config.load((rawget(_G, "CFG_CONFIGDIR") or ".").."/prosody.cfg.lua");
-	if not ok then
-		if level == "parser" then
-			log("error", "There was an error parsing the configuration file: %s", tostring(err));
-		elseif level == "file" then
-			log("error", "Couldn't read the config file when trying to reload: %s", tostring(err));
+function read_version()
+	-- Try to determine version
+	local version_file = io.open((CFG_SOURCEDIR or ".").."/prosody.version");
+	if version_file then
+		prosody.version = version_file:read("*a"):gsub("%s*$", "");
+		version_file:close();
+		if #prosody.version == 12 and prosody.version:match("^[a-f0-9]+$") then
+			prosody.version = "hg:"..prosody.version;
 		end
+	else
+		prosody.version = "unknown";
 	end
 end
 
--- Function to reopen logfiles
-function prosody.reopen_logfiles()
-	log("info", "Re-opening log files");
-	eventmanager.fire_event("reopen-log-files"); -- Handled by appropriate log sinks
-	prosody.events.fire_event("reopen-log-files");
+function load_secondary_libraries()
+	--- Load and initialise core modules
+	require "util.import"
+	require "core.xmlhandlers"
+	require "core.rostermanager"
+	require "core.eventmanager"
+	require "core.hostmanager"
+	require "core.modulemanager"
+	require "core.usermanager"
+	require "core.sessionmanager"
+	require "core.stanza_router"
+
+	require "util.array"
+	require "util.iterators"
+	require "util.timer"
+	require "util.helpers"
+	
+	-- Commented to protect us from 
+	-- the second kind of people
+	--[[ 
+	pcall(require, "remdebug.engine");
+	if remdebug then remdebug.engine.start() end
+	]]
+
+	require "net.connlisteners";
+	
+	require "util.stanza"
+	require "util.jid"
+end
+
+function init_data_store()
+	local data_path = config.get("*", "core", "data_path") or CFG_DATADIR or "data";
+	require "util.datamanager".set_data_path(data_path);
+	require "util.datamanager".add_callback(function(username, host, datastore, data)
+		if config.get(host, "core", "anonymous_login") then
+			return false;
+		end
+		return username, host, datastore, data;
+	end);
 end
 
--- Function to initiate prosody shutdown
-function prosody.shutdown(reason)
-	log("info", "Shutting down: %s", reason or "unknown reason");
-	prosody.events.fire_event("server-stopping", {reason = reason});
+function prepare_to_start()
+	-- Signal to modules that we are ready to start
+	eventmanager.fire_event("server-starting");
+	prosody.events.fire_event("server-starting");
+
+	-- Load SSL settings from config, and create a ctx table
+	local global_ssl_ctx = ssl and config.get("*", "core", "ssl");
+	if global_ssl_ctx then
+		local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
+		setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
+	end
+
+	local cl = require "net.connlisteners";
+	-- start listening on sockets
+	function net_activate_ports(option, listener, default, conntype)
+		if not cl.get(listener) then return; end
+		local ports = config.get("*", "core", option.."_ports") or default;
+		if type(ports) == "number" then ports = {ports} end;
+		
+		if type(ports) ~= "table" then
+			log("error", "core."..option.." is not a table");
+		else
+			for _, port in ipairs(ports) do
+				if type(port) ~= "number" then
+					log("error", "Non-numeric "..option.."_ports: "..tostring(port));
+				else
+					cl.start(listener, { 
+						ssl = conntype ~= "tcp" and global_ssl_ctx,
+						port = port,
+						interface = config.get("*", "core", option.."_interface") 
+							or cl.get(listener).default_interface 
+							or config.get("*", "core", "interface"),
+						type = conntype
+					});
+				end
+			end
+		end
+	end
+
+	net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
+	net_activate_ports("s2s", "xmppserver", {5269}, (global_ssl_ctx and "tls") or "tcp");
+	net_activate_ports("component", "xmppcomponent", {}, "tcp");
+	net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
+	net_activate_ports("console", "console", {5582}, "tcp");
+
+	prosody.start_time = os.time();
+end	
+
+function init_global_protection()
+	-- Catch global accesses --
+	local locked_globals_mt = { __index = function (t, k) error("Attempt to read a non-existent global '"..k.."'", 2); end, __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end }
+		
+	function prosody.unlock_globals()
+		setmetatable(_G, nil);
+	end
+	
+	function prosody.lock_globals()
+		setmetatable(_G, locked_globals_mt);
+	end
+
+	-- And lock now...
+	prosody.lock_globals();
+end
+
+function loop()
+	-- Error handler for errors that make it this far
+	local function catch_uncaught_error(err)
+		if err:match("%d*: interrupted!$") then
+			return "quitting";
+		end
+		
+		log("error", "Top-level error, please report:\n%s", tostring(err));
+		local traceback = debug.traceback("", 2);
+		if traceback then
+			log("error", "%s", traceback);
+		end
+		
+		prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
+	end
+	
+	while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
+		socket.sleep(0.2);
+	end
+end
+
+function cleanup()
+	log("info", "Shutdown status: Cleaning up");
+	prosody.events.fire_event("server-cleanup");
+	
+	-- Ok, we're quitting I know, but we
+	-- need to do some tidying before we go :)
+	server.setquitting(false);
+	
+	log("info", "Shutdown status: Closing all active sessions");
+	for hostname, host in pairs(hosts) do
+		log("debug", "Shutdown status: Closing client connections for %s", hostname)
+		if host.sessions then
+			local reason = { condition = "system-shutdown", text = "Server is shutting down" };
+			if prosody.shutdown_reason then
+				reason.text = reason.text..": "..prosody.shutdown_reason;
+			end
+			for username, user in pairs(host.sessions) do
+				for resource, session in pairs(user.sessions) do
+					log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
+					session:close(reason);
+				end
+			end
+		end
+	
+		log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
+		if host.s2sout then
+			for remotehost, session in pairs(host.s2sout) do
+				if session.close then
+					session:close("system-shutdown");
+				else
+					log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
+				end
+			end
+		end
+	end
+
+	log("info", "Shutdown status: Closing all server connections");
+	server.closeall();
+	
 	server.setquitting(true);
 end
 
--- Signal to modules that we are ready to start
-eventmanager.fire_event("server-starting");
-prosody.events.fire_event("server-starting");
-
--- Load SSL settings from config, and create a ctx table
-local global_ssl_ctx = ssl and config.get("*", "core", "ssl");
-if global_ssl_ctx then
-	local default_ssl_ctx = { mode = "server", protocol = "sslv23", capath = "/etc/ssl/certs", verify = "none"; };
-	setmetatable(global_ssl_ctx, { __index = default_ssl_ctx });
-end
-
--- start listening on sockets
-function net_activate_ports(option, listener, default, conntype)
-	if not cl.get(listener) then return; end
-	local ports = config.get("*", "core", option.."_ports") or default;
-	if type(ports) == "number" then ports = {ports} end;
-	
-	if type(ports) ~= "table" then
-		log("error", "core."..option.." is not a table");
-	else
-		for _, port in ipairs(ports) do
-			if type(port) ~= "number" then
-				log("error", "Non-numeric "..option.."_ports: "..tostring(port));
-			else
-				cl.start(listener, { 
-					ssl = conntype ~= "tcp" and global_ssl_ctx,
-					port = port,
-					interface = config.get("*", "core", option.."_interface") 
-						or cl.get(listener).default_interface 
-						or config.get("*", "core", "interface"),
-					type = conntype
-				});
-			end
-		end
-	end
-end
-
-net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp");
-net_activate_ports("s2s", "xmppserver", {5269}, "tcp");
-net_activate_ports("component", "xmppcomponent", {}, "tcp");
-net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
-net_activate_ports("console", "console", {5582}, "tcp");
-
--- Catch global accesses --
-local locked_globals_mt = { __index = function (t, k) error("Attempt to read a non-existent global '"..k.."'", 2); end, __newindex = function (t, k, v) error("Attempt to set a global: "..tostring(k).." = "..tostring(v), 2); end }
-
-function prosody.unlock_globals()
-	setmetatable(_G, nil);
-end
-
-function prosody.lock_globals()
-	setmetatable(_G, locked_globals_mt);
-end
-
--- And lock now...
-prosody.lock_globals();
-
-prosody.start_time = os.time();
+read_config();
+load_libraries();
+init_global_state();
+read_version();
+log("info", "Hello and welcome to Prosody version %s", prosody.version);
+load_secondary_libraries();
+init_data_store();
+prepare_to_start();
+init_global_protection();
 
 eventmanager.fire_event("server-started");
 prosody.events.fire_event("server-started");
 
--- Error handler for errors that make it this far
-local function catch_uncaught_error(err)
-	if err:match("%d*: interrupted!$") then
-		return "quitting";
-	end
-	
-	log("error", "Top-level error, please report:\n%s", tostring(err));
-	local traceback = debug.traceback("", 2);
-	if traceback then
-		log("error", "%s", traceback);
-	end
-	
-	prosody.events.fire_event("very-bad-error", {error = err, traceback = traceback});
-end
-
-while select(2, xpcall(server.loop, catch_uncaught_error)) ~= "quitting" do
-	socket.sleep(0.2);
-end
-
-log("info", "Shutdown status: Cleaning up");
-prosody.events.fire_event("server-cleanup");
-
--- Ok, we're quitting I know, but we
--- need to do some tidying before we go :)
-server.setquitting(false);
+loop();
 
-log("info", "Shutdown status: Closing all active sessions");
-for hostname, host in pairs(hosts) do
-	log("debug", "Shutdown status: Closing client connections for %s", hostname)
-	if host.sessions then
-		for username, user in pairs(host.sessions) do
-			for resource, session in pairs(user.sessions) do
-				log("debug", "Closing connection for %s@%s/%s", username, hostname, resource);
-				session:close("system-shutdown");
-			end
-		end
-	end
-	
-	log("debug", "Shutdown status: Closing outgoing s2s connections from %s", hostname);
-	if host.s2sout then
-		for remotehost, session in pairs(host.s2sout) do
-			if session.close then
-				session:close("system-shutdown");
-			else
-				log("warn", "Unable to close outgoing s2s session to %s, no session:close()?!", remotehost);
-			end
-		end
-	end
-end
-
-log("info", "Shutdown status: Closing all server connections");
-server.closeall();
-
-server.setquitting(true);
-
+log("info", "Shutting down...");
+cleanup();
 eventmanager.fire_event("server-stopped");
 prosody.events.fire_event("server-stopped");
-log("info", "Shutdown status: Complete!");
+log("info", "Shutdown complete");
+
--- a/tools/ejabberd2prosody.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/tools/ejabberd2prosody.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -91,13 +91,24 @@
 		local name = tuple[5]; local subscription = tuple[6];
 		local ask = tuple[7]; local groups = tuple[8];
 		if type(name) ~= type("") then name = nil; end
-		if ask == "none" then ask = nil; elseif ask == "out" then ask = "subscribe" elseif ask == "in" then
+		if ask == "none" then
+			ask = nil;
+		elseif ask == "out" then
+			ask = "subscribe"
+		elseif ask == "in" then
 			roster_pending(node, host, contact);
-			return;
-		else error(ask) end
+			ask = nil;
+		elseif ask == "both" then
+			roster_pending(node, host, contact);
+			ask = "subscribe";
+		else error("Unknown ask type: "..ask); end
 		if subscription ~= "both" and subscription ~= "from" and subscription ~= "to" and subscription ~= "none" then error(subscription) end
 		local item = {name = name, ask = ask, subscription = subscription, groups = {}};
-		for _, g in ipairs(groups) do item.groups[g] = true; end
+		for _, g in ipairs(groups) do
+			if type(g) == "string" then
+				item.groups[g] = true;
+			end
+		end
 		roster(node, host, contact, item);
 	end;
 	private_storage = function(tuple)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/ejabberdsql2prosody.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -0,0 +1,346 @@
+#!/usr/bin/env lua
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+package.path = package.path ..";../?.lua";
+local serialize = require "util.serialization".serialize;
+local st = require "util.stanza";
+package.loaded["util.logger"] = {init = function() return function() end; end}
+local dm = require "util.datamanager"
+dm.set_data_path("data");
+
+function parseFile(filename)
+------
+
+local file = nil;
+local last = nil;
+local function read(expected)
+	local ch;
+	if last then
+		ch = last; last = nil;
+	else ch = file:read(1); end
+	if expected and ch ~= expected then error("expected: "..expected.."; got: "..(ch or "nil")); end
+	return ch;
+end
+local function pushback(ch)
+	if last then error(); end
+	last = ch;
+end
+local function peek()
+	if not last then last = read(); end
+	return last;
+end
+
+local escapes = {
+	["\\0"] = "\0";
+	["\\'"] = "'";
+	["\\\""] = "\"";
+	["\\b"] = "\b";
+	["\\n"] = "\n";
+	["\\r"] = "\r";
+	["\\t"] = "\t";
+	["\\Z"] = "\26";
+	["\\\\"] = "\\";
+	["\\%"] = "%";
+	["\\_"] = "_";
+}
+local function unescape(s)
+	return escapes[s] or error("Unknown escape sequence: "..s);
+end
+local function readString()
+	read("'");
+	local s = "";
+	while true do
+		local ch = peek();
+		if ch == "\\" then
+			s = s..unescape(read()..read());
+		elseif ch == "'" then
+			break;
+		else
+			s = s..read();
+		end
+	end
+	read("'");
+	return s;
+end
+local function readNonString()
+	local s = "";
+	while true do
+		if peek() == "," or peek() == ")" then
+			break;
+		else
+			s = s..read();
+		end
+	end
+	return tonumber(s);
+end
+local function readItem()
+	if peek() == "'" then
+		return readString();
+	else
+		return readNonString();
+	end
+end
+local function readTuple()
+	local items = {}
+	read("(");
+	while peek() ~= ")" do
+		table.insert(items, readItem());
+		if peek() == ")" then break; end
+		read(",");
+	end
+	read(")");
+	return items;
+end
+local function readTuples()
+	if peek() ~= "(" then read("("); end
+	local tuples = {};
+	while true do
+		table.insert(tuples, readTuple());
+		if peek() == "," then read() end
+		if peek() == ";" then break; end
+	end
+	return tuples;
+end
+local function readTableName()
+	local tname = "";
+	while peek() ~= "`" do tname = tname..read(); end
+	return tname;
+end
+local function readInsert()
+	if peek() == nil then return nil; end
+	for ch in ("INSERT INTO `"):gmatch(".") do -- find line starting with this
+		if peek() == ch then
+			read(); -- found
+		else -- match failed, skip line
+			while peek() and read() ~= "\n" do end
+			return nil;
+		end
+	end
+	local tname = readTableName();
+	for ch in ("` VALUES "):gmatch(".") do read(ch); end -- expect this
+	local tuples = readTuples();
+	read(";"); read("\n");
+	return tname, tuples;
+end
+
+local function readFile(filename)
+	file = io.open(filename);
+	if not file then error("File not found: "..filename); os.exit(0); end
+	local t = {};
+	while true do
+		local tname, tuples = readInsert();
+		if tname then
+			if t[tname] then
+				local t_name = t[tname];
+				for i=1,#tuples do
+					table.insert(t_name, tuples[i]);
+				end
+			else
+				t[tname] = tuples;
+			end
+		elseif peek() == nil then
+			break;
+		end
+	end
+	return t;
+end
+
+return readFile(filename);
+
+------
+end
+
+-- XML parser
+local parse_xml = (function()
+	local entity_map = setmetatable({
+		["amp"] = "&";
+		["gt"] = ">";
+		["lt"] = "<";
+		["apos"] = "'";
+		["quot"] = "\"";
+	}, {__index = function(_, s)
+			if s:sub(1,1) == "#" then
+				if s:sub(2,2) == "x" then
+					return string.char(tonumber(s:sub(3), 16));
+				else
+					return string.char(tonumber(s:sub(2)));
+				end
+			end
+		end
+	});
+	local function xml_unescape(str)
+		return (str:gsub("&(.-);", entity_map));
+	end
+	local function parse_tag(s)
+		local name,sattr=(s):gmatch("([^%s]+)(.*)")();
+		local attr = {};
+		for a,b in (sattr):gmatch("([^=%s]+)=['\"]([^'\"]*)['\"]") do attr[a] = xml_unescape(b); end
+		return name, attr;
+	end
+	return function(xml)
+		local stanza = st.stanza("root");
+		local regexp = "<([^>]*)>([^<]*)";
+		for elem, text in xml:gmatch(regexp) do
+			if elem:sub(1,1) == "!" or elem:sub(1,1) == "?" then -- neglect comments and processing-instructions
+			elseif elem:sub(1,1) == "/" then -- end tag
+				elem = elem:sub(2);
+				stanza:up(); -- TODO check for start-end tag name match
+			elseif elem:sub(-1,-1) == "/" then -- empty tag
+				elem = elem:sub(1,-2);
+				local name,attr = parse_tag(elem);
+				stanza:tag(name, attr):up();
+			else -- start tag
+				local name,attr = parse_tag(elem);
+				stanza:tag(name, attr);
+			end
+			if #text ~= 0 then -- text
+				stanza:text(xml_unescape(text));
+			end
+		end
+		return stanza.tags[1];
+	end
+end)();
+-- end of XML parser
+
+local arg, host = ...;
+local help = "/? -? ? /h -h /help -help --help";
+if not(arg and host) or help:find(arg, 1, true) then
+	print([[ejabberd SQL DB dump importer for Prosody
+
+  Usage: ejabberdsql2prosody.lua filename.txt hostname
+
+The file can be generated using mysqldump:
+  mysqldump db_name > filename.txt]]);
+	os.exit(1);
+end
+local map = {
+	["last"] = {"username", "seconds", "state"};
+	["privacy_default_list"] = {"username", "name"};
+	["privacy_list"] = {"username", "name", "id"};
+	["privacy_list_data"] = {"id", "t", "value", "action", "ord", "match_all", "match_iq", "match_message", "match_presence_in", "match_presence_out"};
+	["private_storage"] = {"username", "namespace", "data"};
+	["rostergroups"] = {"username", "jid", "grp"};
+	["rosterusers"] = {"username", "jid", "nick", "subscription", "ask", "askmessage", "server", "subscribe", "type"};
+	["spool"] = {"username", "xml", "seq"};
+	["users"] = {"username", "password"};
+	["vcard"] = {"username", "vcard"};
+	--["vcard_search"] = {};
+}
+local NULL = {};
+local t = parseFile(arg);
+for name, data in pairs(t) do
+	local m = map[name];
+	if m then
+		if #data > 0 and #data[1] ~= #m then
+			print("[warning] expected "..#m.." columns for table `"..name.."`, found "..#data[1]);
+		end
+		for i=1,#data do
+			local row = data[i];
+			for j=1,#m do
+				row[m[j]] = row[j];
+				row[j] = nil;
+			end
+		end
+	end
+end
+--print(serialize(t));
+
+for i, row in ipairs(t["users"] or NULL) do
+	local node, password = row.username, row.password;
+	local ret, err = dm.store(node, host, "accounts", {password = password});
+	print("["..(err or "success").."] accounts: "..node.."@"..host.." = "..password);
+end
+
+function roster(node, host, jid, item)
+	local roster = dm.load(node, host, "roster") or {};
+	roster[jid] = item;
+	local ret, err = dm.store(node, host, "roster", roster);
+	print("["..(err or "success").."] roster: " ..node.."@"..host.." - "..jid);
+end
+function roster_pending(node, host, jid)
+	local roster = dm.load(node, host, "roster") or {};
+	roster.pending = roster.pending or {};
+	roster.pending[jid] = true;
+	local ret, err = dm.store(node, host, "roster", roster);
+	print("["..(err or "success").."] roster-pending: " ..node.."@"..host.." - "..jid);
+end
+function roster_group(node, host, jid, group)
+	local roster = dm.load(node, host, "roster") or {};
+	local item = roster[jid];
+	if not item then print("Warning: No roster item "..jid.." for user "..node..", can't put in group "..group); return; end
+	item.groups[group] = true;
+	local ret, err = dm.store(node, host, "roster", roster);
+	print("["..(err or "success").."] roster-group: " ..node.."@"..host.." - "..jid.." - "..group);
+end
+function private_storage(node, host, xmlns, stanza)
+	local private = dm.load(node, host, "private") or {};
+	private[stanza.name..":"..xmlns] = st.preserialize(stanza);
+	local ret, err = dm.store(node, host, "private", private);
+	print("["..(err or "success").."] private: " ..node.."@"..host.." - "..xmlns);
+end
+function offline_msg(node, host, t, stanza)
+	stanza.attr.stamp = os.date("!%Y-%m-%dT%H:%M:%SZ", t);
+	stanza.attr.stamp_legacy = os.date("!%Y%m%dT%H:%M:%S", t);
+	local ret, err = dm.list_append(node, host, "offline", st.preserialize(stanza));
+	print("["..(err or "success").."] offline: " ..node.."@"..host.." - "..os.date("!%Y-%m-%dT%H:%M:%SZ", t));
+end
+for i, row in ipairs(t["rosterusers"] or NULL) do
+	local node, contact = row.username, row.jid;
+	local name = row.nick;
+	if name == "" then name = nil; end
+	local subscription = row.subscription;
+	if subscription == "N" then
+		subscription = "none"
+	elseif subscription == "B" then
+		subscription = "both"
+	elseif subscription == "F" then
+		subscription = "from"
+	elseif subscription == "T" then
+		subscription = "to"
+	else error("Unknown subscription type: "..subscription) end;
+	local ask = row.ask;
+	if ask == "N" then
+		ask = nil;
+	elseif ask == "O" then
+		ask = "subscribe";
+	elseif ask == "I" then
+		roster_pending(node, host, contact);
+		ask = nil;
+	elseif ask == "B" then
+		roster_pending(node, host, contact);
+		ask = "subscribe";
+	else error("Unknown ask type: "..ask); end
+	local item = {name = name, ask = ask, subscription = subscription, groups = {}};
+	roster(node, host, contact, item);
+end
+for i, row in ipairs(t["rostergroups"] or NULL) do
+	roster_group(row.username, host, row.jid, row.grp);
+end
+for i, row in ipairs(t["vcard"] or NULL) do
+	local ret, err = dm.store(row.username, host, "vcard", st.preserialize(parse_xml(row.vcard)));
+	print("["..(err or "success").."] vCard: "..row.username.."@"..host);
+end
+for i, row in ipairs(t["private_storage"] or NULL) do
+	private_storage(row.username, host, row.namespace, parse_xml(row.data));
+end
+table.sort(t["spool"] or NULL, function(a,b) return a.seq < b.seq; end); -- sort by sequence number, just in case
+local time_offset = os.difftime(os.time(os.date("!*t")), os.time(os.date("*t"))) -- to deal with timezones
+local date_parse = function(s)
+	local year, month, day, hour, min, sec = s:match("(....)-?(..)-?(..)T(..):(..):(..)");
+	return os.time({year=year, month=month, day=day, hour=hour, min=min, sec=sec-time_offset});
+end
+for i, row in ipairs(t["spool"] or NULL) do
+	local stanza = parse_xml(row.xml);
+	local last_child = stanza.tags[#stanza.tags];
+	if not last_child or last_child ~= stanza[#stanza] then error("Last child of offline message is not a tag"); end
+	if last_child.name ~= "x" and last_child.attr.xmlns ~= "jabber:x:delay" then error("Last child of offline message is not a timestamp"); end
+	stanza[#stanza], stanza.tags[#stanza.tags] = nil, nil;
+	local t = date_parse(last_child.attr.stamp);
+	offline_msg(row.username, host, t, stanza);
+end
--- a/tools/erlparse.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/tools/erlparse.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -45,16 +45,26 @@
 	return ch <= _space;
 end
 
+local escapes = {["\\b"]="\b", ["\\d"]="\d", ["\\e"]="\e", ["\\f"]="\f", ["\\n"]="\n", ["\\r"]="\r", ["\\s"]="\s", ["\\t"]="\t", ["\\v"]="\v", ["\\\""]="\"", ["\\'"]="'", ["\\\\"]="\\"};
 local function readString()
 	read("\""); -- skip quote
 	local slash = nil;
 	local str = "";
 	while true do
 		local ch = read();
-		if ch == "\"" and not slash then break; end
-		str = str..ch;
+		if slash then
+			slash = slash..ch;
+			if not escapes[slash] then error("Unknown escape sequence: "..slash); end
+			str = str..escapes[slash];
+			slash = nil;
+		elseif ch == "\"" then
+			break;
+		elseif ch == "\\" then
+			slash = ch;
+		else
+			str = str..ch;
+		end
 	end
-	str = str:gsub("\\.", {["\\b"]="\b", ["\\d"]="\d", ["\\e"]="\e", ["\\f"]="\f", ["\\n"]="\n", ["\\r"]="\r", ["\\s"]="\s", ["\\t"]="\t", ["\\v"]="\v", ["\\\""]="\"", ["\\'"]="'", ["\\\\"]="\\"});
 	return str;
 end
 local function readAtom1()
--- a/util/array.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/util/array.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -111,7 +111,6 @@
 
 -- Setup methods from array_base
 for method, f in pairs(array_base) do
-	local method = method; -- Yes, this is necessary :)
 	local base_method = f;
 	-- Setup global array method which makes new array
 	array[method] = function (old_a, ...)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/broadcast.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -0,0 +1,68 @@
+-- Prosody IM
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+
+local ipairs, pairs, setmetatable, type = 
+        ipairs, pairs, setmetatable, type;
+
+module "pubsub"
+
+local pubsub_node_mt = { __index = _M };
+
+function new_node(name)
+	return setmetatable({ name = name, subscribers = {} }, pubsub_node_mt);
+end
+
+function set_subscribers(node, subscribers_list, list_type)
+	local subscribers = node.subscribers;
+	
+	if list_type == "array" then
+		for _, jid in ipairs(subscribers_list) do
+			if not subscribers[jid] then
+				node:add_subscriber(jid);
+			end
+		end
+	elseif (not list_type) or list_type == "set" then
+		for jid in pairs(subscribers_list) do
+			if type(jid) == "string" then
+				node:add_subscriber(jid);
+			end
+		end
+	end
+end
+
+function get_subscribers(node)
+	return node.subscribers;
+end
+
+function publish(node, item, dispatcher, data)
+	local subscribers = node.subscribers;
+	for i = 1,#subscribers do
+		item.attr.to = subscribers[i];
+		dispatcher(data, item);
+	end
+end
+
+function add_subscriber(node, jid)
+	local subscribers = node.subscribers;
+	if not subscribers[jid] then
+		local space = #subscribers;
+		subscribers[space] = jid;
+		subscribers[jid] = space;
+	end
+end
+
+function remove_subscriber(node, jid)
+	local subscribers = node.subscribers;
+	if subscribers[jid] then
+		subscribers[subscribers[jid]] = nil;
+		subscribers[jid] = nil;
+	end
+end
+
+return _M;
--- a/util/dataforms.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/util/dataforms.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -37,7 +37,7 @@
 		-- Add field tag
 		form:tag("field", { type = field_type, var = field.name, label = field.label });
 
-		local value = data[field.name] or field.value;
+		local value = (data and data[field.name]) or field.value;
 		
 		-- Add value, depending on type
 		if field_type == "hidden" then
@@ -106,6 +106,9 @@
 field_readers["text-private"] = 
 	field_readers["text-single"];
 
+field_readers["jid-single"] =
+	field_readers["text-single"];
+
 field_readers["text-multi"] = 
 	function (field_tag)
 		local result = {};
--- a/util/discohelper.lua	Tue Oct 13 14:54:07 2009 +0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-
-local t_insert = table.insert;
-local jid_split = require "util.jid".split;
-local ipairs = ipairs;
-local st = require "util.stanza";
-
-module "discohelper";
-
-local function addDiscoItemsHandler(self, jid, func)
-	if self.item_handlers[jid] then
-		t_insert(self.item_handlers[jid], func);
-	else
-		self.item_handlers[jid] = {func};
-	end
-end
-
-local function addDiscoInfoHandler(self, jid, func)
-	if self.info_handlers[jid] then
-		t_insert(self.info_handlers[jid], func);
-	else
-		self.info_handlers[jid] = {func};
-	end
-end
-
-local function handle(self, stanza)
-	if stanza.name == "iq" and stanza.tags[1].name == "query" then
-		local query = stanza.tags[1];
-		local to = stanza.attr.to;
-		local from = stanza.attr.from
-		local node = query.attr.node or "";
-		local to_node, to_host = jid_split(to);
-
-		local reply = st.reply(stanza):query(query.attr.xmlns);
-		local handlers;
-		if query.attr.xmlns == "http://jabber.org/protocol/disco#info" then -- select handler set
-			handlers = self.info_handlers;
-		elseif query.attr.xmlns == "http://jabber.org/protocol/disco#items" then
-			handlers = self.item_handlers;
-		end
-		local handler;
-		local found; -- to keep track of any handlers found
-		if to_node then -- handlers which get called always
-			handler = handlers["*node"];
-		else
-			handler = handlers["*host"];
-		end
-		if handler then -- call always called handler
-			for _, h in ipairs(handler) do
-				if h(reply, to, from, node) then found = true; end
-			end
-		end
-		handler = handlers[to]; -- get the handler
-		if not handler then -- if not found then use default handler
-			if to_node then
-				handler = handlers["*defaultnode"];
-			else
-				handler = handlers["*defaulthost"];
-			end
-		end
-		if handler then
-			for _, h in ipairs(handler) do
-				if h(reply, to, from, node) then found = true; end
-			end
-		end
-		if found then return reply; end -- return the reply if there was one
-		return st.error_reply(stanza, "cancel", "service-unavailable");
-	end
-end
-
-function new()
-	return {
-		item_handlers = {};
-		info_handlers = {};
-		addDiscoItemsHandler = addDiscoItemsHandler;
-		addDiscoInfoHandler = addDiscoInfoHandler;
-		handle = handle;
-	};
-end
-
-return _M;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/helpers.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -0,0 +1,27 @@
+
+module("helpers", package.seeall);
+
+-- Helper functions for debugging
+
+local log = require "util.logger".init("util.debug");
+
+function log_events(events, name, logger)
+	local f = events.fire_event;
+	if not f then
+		error("Object does not appear to be a util.events object");
+	end
+	logger = logger or log;
+	name = name or tostring(events);
+	function events.fire_event(event, ...)
+		logger("debug", "%s firing event: %s", name, event);
+		return f(event, ...);
+	end
+	events[events.fire_event] = f;
+	return events;
+end
+
+function revert_log_events(events)
+	events.fire_event, events[events.fire_event] = events[events.fire_event], nil; -- :)
+end
+
+return _M;
--- a/util/iterators.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/util/iterators.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -78,6 +78,39 @@
 	return x;
 end
 
+-- Return the first n items an iterator returns
+function head(n, f, s, var)
+	local c = 0;
+	return function (s, var)
+		if c >= n then
+			return nil;
+		end
+		c = c + 1;
+		return f(s, var);
+	end, s;
+end
+
+function tail(n, f, s, var)
+	local results, count = {}, 0;
+	while true do
+		local ret = { f(s, var) };
+		var = ret[1];
+	        if var == nil then break; end
+		results[(count%n)+1] = ret;
+		count = count + 1;
+	end
+
+	if n > count then n = count; end
+
+	local pos = 0;
+	return function ()
+		pos = pos + 1;
+		if pos > n then return nil; end
+		return unpack(results[((count-1+pos)%n)+1]);
+	end
+	--return reverse(head(n, reverse(f, s, var)));
+end
+
 -- Convert the values returned by an iterator to an array
 function it2array(f, s, var)
 	local t, var = {};
--- a/util/muc.lua	Tue Oct 13 14:54:07 2009 +0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,418 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-local datamanager = require "util.datamanager";
-local datetime = require "util.datetime";
-
-local jid_split = require "util.jid".split;
-local jid_bare = require "util.jid".bare;
-local st = require "util.stanza";
-local log = require "util.logger".init("mod_muc");
-local multitable_new = require "util.multitable".new;
-local t_insert, t_remove = table.insert, table.remove;
-
-local muc_domain = nil; --module:get_host();
-local history_length = 20;
-
-------------
-local function filter_xmlns_from_array(array, filters)
-	local count = 0;
-	for i=#array,1,-1 do
-		local attr = array[i].attr;
-		if filters[attr and attr.xmlns] then
-			t_remove(array, i);
-			count = count + 1;
-		end
-	end
-	return count;
-end
-local function filter_xmlns_from_stanza(stanza, filters)
-	if filters then
-		if filter_xmlns_from_array(stanza.tags, filters) ~= 0 then
-			return stanza, filter_xmlns_from_array(stanza, filters);
-		end
-	end
-	return stanza, 0;
-end
-local presence_filters = {["http://jabber.org/protocol/muc"]=true;["http://jabber.org/protocol/muc#user"]=true};
-local function get_filtered_presence(stanza)
-	return filter_xmlns_from_stanza(st.clone(stanza), presence_filters);
-end
-local kickable_error_conditions = {
-	["gone"] = true;
-	["internal-server-error"] = true;
-	["item-not-found"] = true;
-	["jid-malformed"] = true;
-	["recipient-unavailable"] = true;
-	["redirect"] = true;
-	["remote-server-not-found"] = true;
-	["remote-server-timeout"] = true;
-	["service-unavailable"] = true;
-};
-local function get_kickable_error(stanza)
-	for _, tag in ipairs(stanza.tags) do
-		if tag.name == "error" and tag.attr.xmlns == "jabber:client" then
-			for _, cond in ipairs(tag.tags) do
-				if cond.attr.xmlns == "urn:ietf:params:xml:ns:xmpp-stanzas" then
-					return kickable_error_conditions[cond.name] and cond.name;
-				end
-			end
-			return true; -- malformed error message
-		end
-	end
-	return true; -- malformed error message
-end
-local function getUsingPath(stanza, path, getText)
-	local tag = stanza;
-	for _, name in ipairs(path) do
-		if type(tag) ~= 'table' then return; end
-		tag = tag:child_with_name(name);
-	end
-	if tag and getText then tag = table.concat(tag); end
-	return tag;
-end
-local function getTag(stanza, path) return getUsingPath(stanza, path); end
-local function getText(stanza, path) return getUsingPath(stanza, path, true); end
------------
-
---[[function get_room_disco_info(room, stanza)
-	return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
-		:tag("identity", {category='conference', type='text', name=room._data["name"]):up()
-		:tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
-end
-function get_room_disco_items(room, stanza)
-	return st.iq({type='result', id=stanza.attr.id, from=stanza.attr.to, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
-end -- TODO allow non-private rooms]]
-
---
-
-local function room_broadcast_presence(room, stanza, code, nick)
-	stanza = get_filtered_presence(stanza);
-	local data = room._participants[stanza.attr.from];
-	stanza:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
-		:tag("item", {affiliation=data.affiliation, role=data.role, nick=nick}):up();
-	if code then
-		stanza:tag("status", {code=code}):up();
-	end
-	local me;
-	for occupant, o_data in pairs(room._participants) do
-		if occupant ~= stanza.attr.from then
-			for jid in pairs(o_data.sessions) do
-				stanza.attr.to = jid;
-				room:route_stanza(stanza);
-			end
-		else
-			me = o_data;
-		end
-	end
-	if me then
-		stanza:tag("status", {code='110'});
-		for jid in pairs(me.sessions) do
-			stanza.attr.to = jid;
-			room:route_stanza(stanza);
-		end
-	end
-end
-local function room_broadcast_message(room, stanza, historic)
-	for occupant, o_data in pairs(room._participants) do
-		for jid in pairs(o_data.sessions) do
-			stanza.attr.to = jid;
-			room:route_stanza(stanza);
-		end
-	end
-	if historic then -- add to history
-		local history = room._data['history'];
-		if not history then history = {}; room._data['history'] = history; end
-		-- stanza = st.clone(stanza);
-		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = muc_domain, stamp = datetime.datetime()}):up(); -- XEP-0203
-		stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
-		t_insert(history, st.clone(st.preserialize(stanza)));
-		while #history > history_length do t_remove(history, 1) end
-	end
-end
-
-
-local function room_send_occupant_list(room, to)
-	local current_nick = room._jid_nick[to];
-	for occupant, o_data in pairs(room._participants) do
-		if occupant ~= current_nick then
-			local pres = get_filtered_presence(o_data.sessions[o_data.jid]);
-			pres.attr.to, pres.attr.from = to, occupant;
-			pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
-				:tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up();
-			room:route_stanza(pres);
-		end
-	end
-end
-local function room_send_history(room, to)
-	local history = room._data['history']; -- send discussion history
-	if history then
-		for _, msg in ipairs(history) do
-			msg = st.deserialize(msg);
-			msg.attr.to=to;
-			room:route_stanza(msg);
-		end
-	end
-	if room._data['subject'] then
-		room:route_stanza(st.message({type='groupchat', from=room.jid, to=to}):tag("subject"):text(room._data['subject']));
-	end
-end
-
-local function room_get_disco_info(self, stanza) end
-local function room_get_disco_items(self, stanza) end
-local function room_set_subject(room, current_nick, subject)
-	-- TODO check nick's authority
-	if subject == "" then subject = nil; end
-	room._data['subject'] = subject;
-	local msg = st.message({type='groupchat', from=current_nick})
-		:tag('subject'):text(subject):up();
-	room_broadcast_message(room, msg, false);
-	return true;
-end
-
-local function room_handle_to_occupant(self, origin, stanza) -- PM, vCards, etc
-	local from, to = stanza.attr.from, stanza.attr.to;
-	local room = jid_bare(to);
-	local current_nick = self._jid_nick[from];
-	local type = stanza.attr.type;
-	log("debug", "room: %s, current_nick: %s, stanza: %s", room or "nil", current_nick or "nil", stanza:top_tag());
-	if (select(2, jid_split(from)) == muc_domain) then error("Presence from the MUC itself!!!"); end
-	if stanza.name == "presence" then
-		local pr = get_filtered_presence(stanza);
-		pr.attr.from = current_nick;
-		if type == "error" then -- error, kick em out!
-			if current_nick then
-				log("debug", "kicking %s from %s", current_nick, room);
-				room_handle_to_occupant(self, origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error presence')); -- send unavailable
-			end
-		elseif type == "unavailable" then -- unavailable
-			if current_nick then
-				log("debug", "%s leaving %s", current_nick, room);
-				local data = self._participants[current_nick];
-				data.role = 'none';
-				room_broadcast_presence(self, pr);
-				self._participants[current_nick] = nil;
-				self._jid_nick[from] = nil;
-			end
-		elseif not type then -- available
-			if current_nick then
-				--if #pr == #stanza or current_nick ~= to then -- commented because google keeps resending directed presence
-					if current_nick == to then -- simple presence
-						log("debug", "%s broadcasted presence", current_nick);
-						self._participants[current_nick].sessions[from] = pr;
-						room_broadcast_presence(self, pr);
-					else -- change nick
-						if self._participants[to] then
-							log("debug", "%s couldn't change nick", current_nick);
-							origin.send(st.error_reply(stanza, "cancel", "conflict"):up():tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
-						else
-							local data = self._participants[current_nick];
-							local to_nick = select(3, jid_split(to));
-							if to_nick then
-								log("debug", "%s (%s) changing nick to %s", current_nick, data.jid, to);
-								local p = st.presence({type='unavailable', from=current_nick});
-								room_broadcast_presence(self, p, '303', to_nick);
-								self._participants[current_nick] = nil;
-								self._participants[to] = data;
-								self._jid_nick[from] = to;
-								pr.attr.from = to;
-								self._participants[to].sessions[from] = pr;
-								room_broadcast_presence(self, pr);
-							else
-								--TODO malformed-jid
-							end
-						end
-					end
-				--else -- possible rejoin
-				--	log("debug", "%s had connection replaced", current_nick);
-				--	handle_to_occupant(origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('Replaced by new connection'):up()); -- send unavailable
-				--	handle_to_occupant(origin, stanza); -- resend available
-				--end
-			else -- enter room
-				local new_nick = to;
-				if self._participants[to] then
-					new_nick = nil;
-				end
-				if not new_nick then
-					log("debug", "%s couldn't join due to nick conflict: %s", from, to);
-					origin.send(st.error_reply(stanza, "cancel", "conflict"):up():tag("x", {xmlns = "http://jabber.org/protocol/muc"}));
-				else
-					log("debug", "%s joining as %s", from, to);
-					local data;
---					if not rooms:get(room) and not rooms_info:get(room) then -- new room
---						rooms_info:set(room, 'name', (jid_split(room)));
---						data = {affiliation='owner', role='moderator', jid=from, sessions={[from]=get_filtered_presence(stanza)}};
---					end
-					if not data then -- new occupant
-						data = {affiliation='none', role='participant', jid=from, sessions={[from]=get_filtered_presence(stanza)}};
-					end
-					self._participants[to] = data;
-					self._jid_nick[from] = to;
-					room_send_occupant_list(self, from);
-					pr.attr.from = to;
-					room_broadcast_presence(self, pr);
-					room_send_history(self, from);
-				end
-			end
-		elseif type ~= 'result' then -- bad type
-			origin.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME correct error?
-		end
-	elseif not current_nick and type ~= "error" and type ~= "result" then -- not in room
-		origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
-	elseif stanza.name == "message" and type == "groupchat" then -- groupchat messages not allowed in PM
-		origin.send(st.error_reply(stanza, "modify", "bad-request"));
-	elseif stanza.name == "message" and type == "error" and get_kickable_error(stanza) then
-		log("debug", "%s kicked from %s for sending an error message", current_nick, room);
-		room_handle_to_occupant(self, origin, st.presence({type='unavailable', from=from, to=to}):tag('status'):text('This participant is kicked from the room because he sent an error message to another occupant')); -- send unavailable
-	else -- private stanza
-		local o_data = self._participants[to];
-		if o_data then
-			log("debug", "%s sent private stanza to %s (%s)", from, to, o_data.jid);
-			local jid = o_data.jid;
-			-- TODO if stanza.name=='iq' and type=='get' and stanza.tags[1].attr.xmlns == 'vcard-temp' then jid = jid_bare(jid); end
-			stanza.attr.to, stanza.attr.from = jid, current_nick;
-			self:route_stanza(stanza);
-		elseif type ~= "error" and type ~= "result" then -- recipient not in room
-			origin.send(st.error_reply(stanza, "cancel", "item-not-found", "Recipient not in room"));
-		end
-	end
-end
-
-local function room_handle_to_room(self, origin, stanza) -- presence changes and groupchat messages, along with disco/etc
-	local type = stanza.attr.type;
-	if stanza.name == "iq" and type == "get" then -- disco requests
-		local xmlns = stanza.tags[1].attr.xmlns;
-		if xmlns == "http://jabber.org/protocol/disco#info" then
-			origin.send(room_get_disco_info(self, stanza));
-		elseif xmlns == "http://jabber.org/protocol/disco#items" then
-			origin.send(room_get_disco_items(self, stanza));
-		else
-			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
-		end
-	elseif stanza.name == "message" and type == "groupchat" then
-		local from, to = stanza.attr.from, stanza.attr.to;
-		local room = jid_bare(to);
-		local current_nick = self._jid_nick[from];
-		if not current_nick then -- not in room
-			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
-		else
-			local from = stanza.attr.from;
-			stanza.attr.from = current_nick;
-			local subject = getText(stanza, {"subject"});
-			if subject then
-				self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
-			else
-				room_broadcast_message(self, stanza, true);
-			end
-		end
-	elseif stanza.name == "presence" then -- hack - some buggy clients send presence updates to the room rather than their nick
-		local to = stanza.attr.to;
-		local current_nick = self._jid_nick[stanza.attr.from];
-		if current_nick then
-			stanza.attr.to = current_nick;
-			room_handle_to_occupant(self, origin, stanza);
-			stanza.attr.to = to;
-		elseif type ~= "error" and type ~= "result" then
-			origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
-		end
-	elseif stanza.name == "message" and not stanza.attr.type and #stanza.tags == 1 and self._jid_nick[stanza.attr.from]
-		and stanza.tags[1].name == "x" and stanza.tags[1].attr.xmlns == "http://jabber.org/protocol/muc#user" and #stanza.tags[1].tags == 1
-		and stanza.tags[1].tags[1].name == "invite" and stanza.tags[1].tags[1].attr.to then
-		local _from, _to = stanza.attr.from, stanza.attr.to;
-		local _invitee = stanza.tags[1].tags[1].attr.to;
-		stanza.attr.from, stanza.attr.to = _to, _invitee;
-		stanza.tags[1].tags[1].attr.from, stanza.tags[1].tags[1].attr.to = _from, nil;
-		self:route_stanza(stanza);
-		stanza.tags[1].tags[1].attr.from, stanza.tags[1].tags[1].attr.to = nil, _invitee;
-		stanza.attr.from, stanza.attr.to = _from, _to;
-	else
-		if type == "error" or type == "result" then return; end
-		origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
-	end
-end
-
-local function room_handle_stanza(self, origin, stanza)
-	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
-	if to_resource then
-		room_handle_to_occupant(self, origin, stanza);
-	else
-		room_handle_to_room(self, origin, stanza);
-	end
-end
-
-module "muc"
-
-function new_room(jid)
-	return {
-		jid = jid;
-		handle_stanza = room_handle_stanza;
-		set_subject = room_set_subject;
-		route_stanza = function(room, stanza) end; -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end
-		_jid_nick = {};
-		_participants = {};
-		_data = {};
-	}
-end
-
-return _M;
-
---[[function get_disco_info(stanza)
-	return st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
-		:tag("identity", {category='conference', type='text', name=muc_name}):up()
-		:tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
-end
-function get_disco_items(stanza)
-	local reply = st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
-	for room in pairs(rooms_info:get()) do
-		reply:tag("item", {jid=room, name=rooms_info:get(room, "name")}):up();
-	end
-	return reply; -- TODO cache disco reply
-end]]
-
---[[function handle_to_domain(origin, stanza)
-	local type = stanza.attr.type;
-	if type == "error" or type == "result" then return; end
-	if stanza.name == "iq" and type == "get" then
-		local xmlns = stanza.tags[1].attr.xmlns;
-		if xmlns == "http://jabber.org/protocol/disco#info" then
-			origin.send(get_disco_info(stanza));
-		elseif xmlns == "http://jabber.org/protocol/disco#items" then
-			origin.send(get_disco_items(stanza));
-		else
-			origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc
-		end
-	else
-		origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it"));
-	end
-end
-
-register_component(muc_domain, function(origin, stanza)
-	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
-	if to_resource and not to_node then
-		if type == "error" or type == "result" then return; end
-		origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- host/resource
-	elseif to_resource then
-		handle_to_occupant(origin, stanza);
-	elseif to_node then
-		handle_to_room(origin, stanza)
-	else -- to the main muc domain
-		if type == "error" or type == "result" then return; end
-		handle_to_domain(origin, stanza);
-	end
-end);]]
-
---[[module.unload = function()
-	deregister_component(muc_domain);
-end
-module.save = function()
-	return {rooms = rooms.data; jid_nick = jid_nick.data; rooms_info = rooms_info.data; persist_list = persist_list};
-end
-module.restore = function(data)
-	rooms.data, jid_nick.data, rooms_info.data, persist_list =
-	data.rooms or {}, data.jid_nick or {}, data.rooms_info or {}, data.persist_list or {};
-end]]
--- a/util/pubsub.lua	Tue Oct 13 14:54:07 2009 +0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
--- Prosody IM
--- Copyright (C) 2008-2009 Matthew Wild
--- Copyright (C) 2008-2009 Waqas Hussain
--- 
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
-
-
-local ipairs, pairs, setmetatable, type = 
-        ipairs, pairs, setmetatable, type;
-
-module "pubsub"
-
-local pubsub_node_mt = { __index = _M };
-
-function new_node(name)
-	return setmetatable({ name = name, subscribers = {} }, pubsub_node_mt);
-end
-
-function set_subscribers(node, subscribers_list, list_type)
-	local subscribers = node.subscribers;
-	
-	if list_type == "array" then
-		for _, jid in ipairs(subscribers_list) do
-			if not subscribers[jid] then
-				node:add_subscriber(jid);
-			end
-		end
-	elseif (not list_type) or list_type == "set" then
-		for jid in pairs(subscribers_list) do
-			if type(jid) == "string" then
-				node:add_subscriber(jid);
-			end
-		end
-	end
-end
-
-function get_subscribers(node)
-	return node.subscribers;
-end
-
-function publish(node, item, dispatcher, data)
-	local subscribers = node.subscribers;
-	for i = 1,#subscribers do
-		item.attr.to = subscribers[i];
-		dispatcher(data, item);
-	end
-end
-
-function add_subscriber(node, jid)
-	local subscribers = node.subscribers;
-	if not subscribers[jid] then
-		local space = #subscribers;
-		subscribers[space] = jid;
-		subscribers[jid] = space;
-	end
-end
-
-function remove_subscriber(node, jid)
-	local subscribers = node.subscribers;
-	if subscribers[jid] then
-		subscribers[subscribers[jid]] = nil;
-		subscribers[jid] = nil;
-	end
-end
-
-return _M;
--- a/util/sasl.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/util/sasl.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -1,14 +1,14 @@
 -- sasl.lua v0.4
 -- Copyright (C) 2008-2009 Tobias Markmann
--- 
+--
 --    All rights reserved.
---    
+--
 --    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
---    
+--
 --        * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 --        * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 --        * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
---    
+--
 --    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
@@ -20,7 +20,6 @@
 local t_insert, t_concat = table.insert, table.concat;
 local to_byte, to_char = string.byte, string.char;
 local to_unicode = require "util.encodings".idna.to_unicode;
-local saslprep = require "util.encodings".stringprep.saslprep;
 local s_match = string.match;
 local gmatch = string.gmatch
 local string = string
@@ -31,60 +30,56 @@
 
 module "sasl"
 
-local function new_plain(realm, password_handler)
-	local object = { mechanism = "PLAIN", realm = realm, password_handler = password_handler}
+-- Credentials handler:
+--   Arguments: ("PLAIN", user, host, password)
+--   Returns: true (success) | false (fail) | nil (user unknown)
+local function new_plain(realm, credentials_handler)
+	local object = { mechanism = "PLAIN", realm = realm, credentials_handler = credentials_handler}
 	function object.feed(self, message)
-	
 		if message == "" or message == nil then return "failure", "malformed-request" end
 		local response = message
 		local authorization = s_match(response, "([^%z]+)")
 		local authentication = s_match(response, "%z([^%z]+)%z")
 		local password = s_match(response, "%z[^%z]+%z([^%z]+)")
-		authorization, authentication, password = saslprep(authorization), saslprep(authentication), saslprep(password);
-		
-		if authentication == nil or password == nil then return "failure", "malformed-request" end
-		
-		local password_encoding, correct_password = self.password_handler(authentication, self.realm, self.realm, "PLAIN")
-		
-		if correct_password == nil then return "failure", "not-authorized"
-		elseif correct_password == false then return "failure", "account-disabled" end
-		
-		local claimed_password = ""
-		if password_encoding == nil then claimed_password = password
-		else claimed_password = password_encoding(password) end
-		claimed_password = saslprep(claimed_password);
-		
-		self.username = authentication
-		if claimed_password == correct_password then
-			return "success"
-		else
-			return "failure", "not-authorized"
-		end
-	end
-	return object
+
+    if authentication == nil or password == nil then return "failure", "malformed-request" end
+    self.username = authentication
+    local auth_success = self.credentials_handler("PLAIN", self.username, self.realm, password)
+
+    if auth_success then
+      return "success"
+    elseif auth_success == nil then
+      return "failure", "account-disabled"
+    else
+      return "failure", "not-authorized"
+    end
+  end
+  return object
 end
 
-
+-- credentials_handler:
+--   Arguments: (mechanism, node, domain, realm, decoder)
+--   Returns: Password encoding, (plaintext) password
 -- implementing RFC 2831
-local function new_digest_md5(realm, password_handler)
+local function new_digest_md5(realm, credentials_handler)
 	--TODO complete support for authzid
 
 	local function serialize(message)
 		local data = ""
-		
+
 		if type(message) ~= "table" then error("serialize needs an argument of type table.") end
-		
+
 		-- testing all possible values
+		if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
 		if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end
 		if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end
 		if message["charset"] then data = data..[[charset=]]..message.charset.."," end
 		if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end
-		if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
 		if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end
 		data = data:gsub(",$", "")
 		return data
 	end
-	
+
 	local function utf8tolatin1ifpossible(passwd)
 		local i = 1;
 		while i <= #passwd do
@@ -140,16 +135,16 @@
 		return message;
 	end
 
-	local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler};
-	
+	local object = { mechanism = "DIGEST-MD5", realm = realm, credentials_handler = credentials_handler};
+
 	object.nonce = generate_uuid();
 	object.step = 0;
 	object.nonce_count = {};
-												
+
 	function object.feed(self, message)
 		self.step = self.step + 1;
 		if (self.step == 1) then
-			local challenge = serialize({	nonce = object.nonce, 
+			local challenge = serialize({	nonce = object.nonce,
 											qop = "auth",
 											charset = "utf-8",
 											algorithm = "md5-sess",
@@ -161,13 +156,13 @@
 			if response["nc"] then
 				if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end
 			end
-			
+
 			-- check for username, it's REQUIRED by RFC 2831
 			if not response["username"] then
 				return "failure", "malformed-request";
 			end
 			self["username"] = response["username"];
-			
+
 			-- check for nonce, ...
 			if not response["nonce"] then
 				return "failure", "malformed-request";
@@ -175,23 +170,23 @@
 				-- check if it's the right nonce
 				if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end
 			end
-			
+
 			if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end
 			if not response["qop"] then response["qop"] = "auth" end
-			
+
 			if response["realm"] == nil or response["realm"] == "" then
 				response["realm"] = "";
 			elseif response["realm"] ~= self.realm then
 				return "failure", "not-authorized", "Incorrect realm value";
 			end
-			
+
 			local decoder;
 			if response["charset"] == nil then
 				decoder = utf8tolatin1ifpossible;
 			elseif response["charset"] ~= "utf-8" then
 				return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8.";
 			end
-			
+
 			local domain = "";
 			local protocol = "";
 			if response["digest-uri"] then
@@ -200,10 +195,10 @@
 			else
 				return "failure", "malformed-request", "Missing entry for digest-uri in SASL message."
 			end
-			
+
 			--TODO maybe realm support
 			self.username = response["username"];
-			local password_encoding, Y = self.password_handler(response["username"], to_unicode(domain), response["realm"], "DIGEST-MD5", decoder);
+			local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder);
 			if Y == nil then return "failure", "not-authorized"
 			elseif Y == false then return "failure", "account-disabled" end
 			local A1 = "";
@@ -219,27 +214,27 @@
 				A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
 			end
 			local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
-			
+
 			local HA1 = md5(A1, true);
 			local HA2 = md5(A2, true);
-			
+
 			local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2;
 			local response_value = md5(KD, true);
-			
+
 			if response_value == response["response"] then
 				-- calculate rspauth
 				A2 = ":"..protocol.."/"..domain;
-				
+
 				HA1 = md5(A1, true);
 				HA2 = md5(A2, true);
-				
+
 				KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
 				local rspauth = md5(KD, true);
 				self.authenticated = true;
 				return "challenge", serialize({rspauth = rspauth});
 			else
 				return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
-			end							
+			end
 		elseif self.step == 3 then
 			if self.authenticated ~= nil then return "success"
 			else return "failure", "malformed-request" end
@@ -248,8 +243,10 @@
 	return object;
 end
 
-local function new_anonymous(realm, password_handler)
-	local object = { mechanism = "ANONYMOUS", realm = realm, password_handler = password_handler}
+-- Credentials handler: Can be nil. If specified, should take the mechanism as
+-- the only argument, and return true for OK, or false for not-OK (TODO)
+local function new_anonymous(realm, credentials_handler)
+	local object = { mechanism = "ANONYMOUS", realm = realm, credentials_handler = credentials_handler}
 		function object.feed(self, message)
 			return "success"
 		end
@@ -258,11 +255,11 @@
 end
 
 
-function new(mechanism, realm, password_handler)
+function new(mechanism, realm, credentials_handler)
 	local object
-	if mechanism == "PLAIN" then object = new_plain(realm, password_handler)
-	elseif mechanism == "DIGEST-MD5" then object = new_digest_md5(realm, password_handler)
-	elseif mechanism == "ANONYMOUS" then object = new_anonymous(realm, password_handler)
+	if mechanism == "PLAIN" then object = new_plain(realm, credentials_handler)
+	elseif mechanism == "DIGEST-MD5" then object = new_digest_md5(realm, credentials_handler)
+	elseif mechanism == "ANONYMOUS" then object = new_anonymous(realm, credentials_handler)
 	else
 		log("debug", "Unsupported SASL mechanism: "..tostring(mechanism));
 		return nil
--- a/util/stanza.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/util/stanza.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -6,7 +6,6 @@
 -- COPYING file in the source package for more information.
 --
 
-
 local t_insert      =  table.insert;
 local t_concat      =  table.concat;
 local t_remove      =  table.remove;
@@ -28,13 +27,19 @@
 local os            =            os;
 
 local do_pretty_printing = not os.getenv("WINDIR");
-local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
-
-local log = require "util.logger".init("stanza");
+local getstyle, getstring;
+if do_pretty_printing then
+	local ok, termcolours = pcall(require, "util.termcolours");
+	if ok then
+		getstyle, getstring = termcolours.getstyle, termcolours.getstring;
+	else
+		do_pretty_printing = nil;
+	end
+end
 
 module "stanza"
 
-stanza_mt = {};
+stanza_mt = { __type = "stanza" };
 stanza_mt.__index = stanza_mt;
 
 function stanza(name, attr)
@@ -118,10 +123,13 @@
 	                                    
 end
 
-local xml_escape = (function()
+local xml_escape
+do
 	local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
-	return function(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
-end)();
+	function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
+	_M.xml_escape = xml_escape;
+end
+
 local function _dostring(t, buf, self, xml_escape)
 	local nsid = 0;
 	local name = t.name
--- a/util/timer.lua	Tue Oct 13 14:54:07 2009 +0500
+++ b/util/timer.lua	Tue Oct 13 15:00:59 2009 +0500
@@ -42,7 +42,7 @@
 		local t, func = d[1], d[2];
 		if t <= current_time then
 			data[i] = nil;
-			local r = func();
+			local r = func(current_time);
 			if type(r) == "number" then _add_task(r, func); end
 		end
 	end

mercurial