Merge with 0.4

Fri, 22 May 2009 13:35:54 +0100

author
Matthew Wild <mwild1@gmail.com>
date
Fri, 22 May 2009 13:35:54 +0100
changeset 1193
b74acb50220b
parent 1191
7f965bd37d65 (diff)
parent 1192
b1b42ce4f0f6 (current diff)
child 1197
1f4a4e6ae694

Merge with 0.4

--- a/core/componentmanager.lua	Fri May 22 13:34:47 2009 +0100
+++ b/core/componentmanager.lua	Fri May 22 13:35:54 2009 +0100
@@ -15,6 +15,7 @@
 local modulemanager = require "core.modulemanager";
 local core_route_stanza = core_route_stanza;
 local jid_split = require "util.jid".split;
+local events_new = require "util.events".new;
 local st = require "util.stanza";
 local hosts = hosts;
 
@@ -67,9 +68,10 @@
 function handle_stanza(origin, stanza)
 	local node, host = jid_split(stanza.attr.to);
 	local component = nil;
-	if not component then component = components[stanza.attr.to]; end -- hack to allow hooking node@server/resource and server/resource
-	if not component then component = components[node.."@"..host]; end -- hack to allow hooking node@server
-	if not component then component = components[host]; end
+	if host then
+		if node then component = components[node.."@"..host]; end -- hack to allow hooking node@server
+		if not component then component = components[host]; end
+	end
 	if component then
 		log("debug", "%s stanza being handled by component: %s", stanza.name, host);
 		component(origin, stanza, hosts[host]);
@@ -80,8 +82,7 @@
 
 function create_component(host, component)
 	-- TODO check for host well-formedness
-	local session = session or { type = "component", host = host, connected = true, s2sout = {} };
-	return session;
+	return { type = "component", host = host, connected = true, s2sout = {}, events = events_new() };
 end
 
 function register_component(host, component, session)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/eventmanager2.lua	Fri May 22 13:35:54 2009 +0100
@@ -0,0 +1,6 @@
+
+local events = require "util.events".new();
+
+module "eventmanager"
+
+return events;
--- a/core/hostmanager.lua	Fri May 22 13:34:47 2009 +0100
+++ b/core/hostmanager.lua	Fri May 22 13:35:54 2009 +0100
@@ -2,6 +2,7 @@
 local hosts = hosts;
 local configmanager = require "core.configmanager";
 local eventmanager = require "core.eventmanager";
+local events_new = require "util.events".new;
 
 local log = require "util.logger".init("hostmanager");
 
@@ -26,7 +27,7 @@
 eventmanager.add_event_hook("server-starting", load_enabled_hosts);
 
 function activate(host, host_config)
-	hosts[host] = {type = "local", connected = true, sessions = {}, host = host, s2sout = {} };
+	hosts[host] = {type = "local", connected = true, sessions = {}, host = host, s2sout = {}, events = events_new() };
 	log((hosts_loaded_once and "info") or "debug", "Activated host: %s", host);
 	eventmanager.fire_event("host-activated", host, host_config);
 end
--- a/core/modulemanager.lua	Fri May 22 13:34:47 2009 +0100
+++ b/core/modulemanager.lua	Fri May 22 13:35:54 2009 +0100
@@ -17,6 +17,7 @@
 local config = require "core.configmanager";
 local multitable_new = require "util.multitable".new;
 local register_actions = require "core.actions".register;
+local st = require "util.stanza";
 
 local hosts = hosts;
 
@@ -226,7 +227,14 @@
 		(handlers[1])(origin, stanza);
 		return true;
 	else
-		log("debug", "Stanza unhandled by any modules, xmlns: %s", stanza.attr.xmlns); -- we didn't handle it
+		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
+			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" and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
+			origin:close("unsupported-stanza-type");
+		end
 	end
 end
 
@@ -342,6 +350,10 @@
 	event_hooks:set(self.host, self.name, name, handler, true);
 end
 
+function api:fire_event(...)
+	return eventmanager.fire_event(...);
+end
+
 --------------------------------------------------------------------
 
 local actions = {};
--- a/core/stanza_router.lua	Fri May 22 13:34:47 2009 +0100
+++ b/core/stanza_router.lua	Fri May 22 13:35:54 2009 +0100
@@ -10,6 +10,8 @@
 
 local log = require "util.logger".init("stanzarouter")
 
+local hosts = _G.hosts;
+
 local st = require "util.stanza";
 local send_s2s = require "core.s2smanager".send_to_host;
 local user_exists = require "core.usermanager".user_exists;
@@ -24,9 +26,9 @@
 local modules_handle_stanza = require "core.modulemanager".handle_stanza;
 local component_handle_stanza = require "core.componentmanager".handle_stanza;
 
-local handle_outbound_presence_subscriptions_and_probes = require "core.presencemanager".handle_outbound_presence_subscriptions_and_probes;
-local handle_inbound_presence_subscriptions_and_probes = require "core.presencemanager".handle_inbound_presence_subscriptions_and_probes;
-local handle_normal_presence = require "core.presencemanager".handle_normal_presence;
+local handle_outbound_presence_subscriptions_and_probes = function()end;--require "core.presencemanager".handle_outbound_presence_subscriptions_and_probes;
+local handle_inbound_presence_subscriptions_and_probes = function()end;--require "core.presencemanager".handle_inbound_presence_subscriptions_and_probes;
+local handle_normal_presence = function()end;--require "core.presencemanager".handle_normal_presence;
 
 local format = string.format;
 local tostring = tostring;
@@ -40,18 +42,16 @@
 local jid_split = require "util.jid".split;
 local jid_prepped_split = require "util.jid".prepped_split;
 local print = print;
-local function checked_error_reply(origin, stanza)
-	if (stanza.attr.xmlns == "jabber:client" or stanza.attr.xmlns == "jabber:server") and stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
-		origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- FIXME correct error?
-	end
-end
+local fire_event = require "core.eventmanager2".fire_event;
 
 function core_process_stanza(origin, stanza)
 	(origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:top_tag())
 
-	if not stanza.attr.xmlns then stanza.attr.xmlns = "jabber:client"; end -- FIXME Hack. This should be removed when we fix namespace handling.
+	-- Currently we guarantee every stanza to have an xmlns, should we keep this rule?
+	if not stanza.attr.xmlns then stanza.attr.xmlns = "jabber:client"; end
+	
 	-- TODO verify validity of stanza (as well as JID validity)
-	if stanza.attr.xmlns == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log
+	if stanza.attr.type == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log
 	if stanza.name == "iq" then
 		if (stanza.attr.type == "set" or stanza.attr.type == "get") and #stanza.tags ~= 1 then
 			origin.send(st.error_reply(stanza, "modify", "bad-request"));
@@ -103,69 +103,42 @@
 		return; -- FIXME what should we do here?
 	end]] -- FIXME
 
-	-- FIXME do stanzas not of jabber:client get handled by components?
-	if (origin.type == "s2sin" or origin.type == "c2s" or origin.type == "component") and (not xmlns or xmlns == "jabber:server" or xmlns == "jabber:client") then			
+	if (origin.type == "s2sin" or origin.type == "c2s" or origin.type == "component") and xmlns == "jabber:client" then
 		if origin.type == "s2sin" and not origin.dummy then
 			local host_status = origin.hosts[from_host];
 			if not host_status or not host_status.authed then -- remote server trying to impersonate some other server?
-				log("warn", "Received a stanza claiming to be from %s, over a conn authed for %s!", from_host, origin.from_host);
+				log("warn", "Received a stanza claiming to be from %s, over a stream authed for %s!", from_host, origin.from_host);
 				return; -- FIXME what should we do here? does this work with subdomains?
 			end
 		end
-		if origin.type == "c2s" and stanza.name == "presence" and to ~= nil and not(origin.roster[to_bare] and (origin.roster[to_bare].subscription == "both" or origin.roster[to_bare].subscription == "from")) then -- directed presence
-			origin.directed = origin.directed or {};
-			origin.directed[to] = true;
-			--t_insert(origin.directed, to); -- FIXME does it make more sense to add to_bare rather than to?
-		end
-		if not to then
-			core_handle_stanza(origin, stanza);
-		elseif hosts[to] and hosts[to].type == "local" then -- directed at a local server
-			core_handle_stanza(origin, stanza);
-		elseif stanza.attr.xmlns and stanza.attr.xmlns ~= "jabber:client" and stanza.attr.xmlns ~= "jabber:server" then
-			modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
-		elseif origin.type == "c2s" and stanza.name == "presence" and stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
-			handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
-		elseif hosts[to] and hosts[to].type == "component" then -- hack to allow components to handle node@server/resource and server/resource
-			component_handle_stanza(origin, stanza);
-		elseif hosts[to_bare] and hosts[to_bare].type == "component" then -- hack to allow components to handle node@server
-			component_handle_stanza(origin, stanza);
-		elseif hosts[host] and hosts[host].type == "component" then -- directed at a component
-			component_handle_stanza(origin, stanza);
-		elseif hosts[host] and hosts[host].type == "local" and stanza.name == "iq" and not resource then -- directed at bare JID
-			core_handle_stanza(origin, stanza);
-		else
-			core_route_stanza(origin, stanza);
-		end
+		core_post_stanza(origin, stanza);
 	else
-		core_handle_stanza(origin, stanza);
+		modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
 	end
 end
 
--- This function handles stanzas which are not routed any further,
--- that is, they are handled by this server
-function core_handle_stanza(origin, stanza)
-	-- Handlers
-	if modules_handle_stanza(select(2, jid_split(stanza.attr.to)) or origin.host, origin, stanza) then return; end
-	if origin.type == "c2s" or origin.type == "s2sin" then
-		if origin.type == "c2s" then
-			if stanza.name == "presence" and origin.roster then
-				if stanza.attr.type == nil or stanza.attr.type == "unavailable" and stanza.attr.type ~= "error" then
-					handle_normal_presence(origin, stanza, core_route_stanza);
-				else
-					log("warn", "Unhandled c2s presence: %s", tostring(stanza));
-					checked_error_reply(origin, stanza);
-				end
-			else
-				log("warn", "Unhandled c2s stanza: %s", tostring(stanza));
-				checked_error_reply(origin, stanza);
-			end
-		else -- s2s stanzas
-			log("warn", "Unhandled s2s stanza: %s", tostring(stanza));
-			checked_error_reply(origin, stanza);
-		end
+function core_post_stanza(origin, stanza)
+	local to = stanza.attr.to;
+	local node, host, resource = jid_split(to);
+	local to_bare = node and (node.."@"..host) or host; -- bare JID
+
+	local event_data = {origin=origin, stanza=stanza};
+	if host and fire_event(host.."/"..stanza.name, event_data) then
+		-- event handled
+	elseif stanza.name == "presence" and origin.host and fire_event(origin.host.."/"..stanza.name, event_data) then
+		-- event handled
+	elseif not to then
+		modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
+	elseif hosts[to] and hosts[to].type == "local" then -- directed at a local server
+		modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
+	elseif hosts[to_bare] and hosts[to_bare].type == "component" then -- hack to allow components to handle node@server
+		component_handle_stanza(origin, stanza);
+	elseif hosts[host] and hosts[host].type == "component" then -- directed at a component
+		component_handle_stanza(origin, stanza);
+	elseif hosts[host] and hosts[host].type == "local" and stanza.name == "iq" and not resource then -- directed at bare JID
+		modules_handle_stanza(host or origin.host or origin.to_host, origin, stanza);
 	else
-		log("warn", "Unhandled %s stanza: %s", origin.type, tostring(stanza));
-		checked_error_reply(origin, stanza);
+		core_route_stanza(origin, stanza);
 	end
 end
 
@@ -185,9 +158,7 @@
 	origin = origin or hosts[from_host];
 	if not origin then return false; end
 	
-	if hosts[to] and hosts[to].type == "component" then -- hack to allow components to handle node@server/resource and server/resource
-		return component_handle_stanza(origin, stanza);
-	elseif hosts[to_bare] and hosts[to_bare].type == "component" then -- hack to allow components to handle node@server
+	if hosts[to_bare] and hosts[to_bare].type == "component" then -- hack to allow components to handle node@server
 		return component_handle_stanza(origin, stanza);
 	elseif hosts[host] and hosts[host].type == "component" then -- directed at a component
 		return component_handle_stanza(origin, stanza);
@@ -201,7 +172,9 @@
 		local user = host_session.sessions[node];
 		if user then
 			local res = user.sessions[resource];
-			if not res then
+			if res then -- resource is online...
+				res.send(stanza); -- Yay \o/
+			else
 				-- if we get here, resource was not specified or was unavailable
 				if stanza.name == "presence" then
 					if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
@@ -252,10 +225,6 @@
 				elseif stanza.attr.type == "get" or stanza.attr.type == "set" then
 					origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
 				end
-			else
-				-- User + resource is online...
-				stanza.attr.to = res.full_jid; -- reset at the end of function
-				res.send(stanza); -- Yay \o/
 			end
 		else
 			-- user not online
--- a/core/xmlhandlers.lua	Fri May 22 13:34:47 2009 +0100
+++ b/core/xmlhandlers.lua	Fri May 22 13:35:54 2009 +0100
@@ -14,15 +14,8 @@
 local tostring = tostring;
 local pairs = pairs;
 local ipairs = ipairs;
-local type = type;
-local print = print;
-local format = string.format;
-local m_random = math.random;
 local t_insert = table.insert;
-local t_remove = table.remove;
 local t_concat = table.concat;
-local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
-local sm_destroy_session = import("core.sessionmanager", "destroy_session");
 
 local default_log = require "util.logger".init("xmlhandlers");
 
@@ -68,15 +61,13 @@
 			
 			-- FIXME !!!!!
 			for i, k in ipairs(attr) do
-				if type(k) == "string" then
-					local ns, nm = k:match("^([^|]+)|?([^|]-)$")
-					if ns and nm then
-						ns = ns_prefixes[ns]; 
-						if ns then 
-							attr[ns..":"..nm] = attr[k];
-							attr[i] = ns..":"..nm;
-							attr[k] = nil;
-						end
+				local ns, nm = k:match("^([^|]+)|?([^|]-)$")
+				if ns and nm then
+					ns = ns_prefixes[ns]; 
+					if ns then 
+						attr[ns..":"..nm] = attr[k];
+						attr[i] = ns..":"..nm;
+						attr[k] = nil;
 					end
 				end
 			end
@@ -129,19 +120,17 @@
 					cb_error(session, "parse-error", "unexpected-element-close", name);
 				end
 			end
-			if stanza then
-				if #chardata > 0 then
-					-- We have some character data in the buffer
-					stanza:text(t_concat(chardata));
-					chardata = {};
-				end
-				-- Complete stanza
-				if #stanza.last_add == 0 then
-					cb_handlestanza(session, stanza);
-					stanza = nil;
-				else
-					stanza:up();
-				end
+			if #chardata > 0 then
+				-- We have some character data in the buffer
+				stanza:text(t_concat(chardata));
+				chardata = {};
+			end
+			-- Complete stanza
+			if #stanza.last_add == 0 then
+				cb_handlestanza(session, stanza);
+				stanza = nil;
+			else
+				stanza:up();
 			end
 		end
 	return xml_handlers;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fallbacks/bit.lua	Fri May 22 13:35:54 2009 +0100
@@ -0,0 +1,137 @@
+
+local type = type;
+local tonumber = tonumber;
+local setmetatable = setmetatable;
+local error = error;
+local tostring = tostring;
+local print = print;
+
+local xor_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=0;[18]=3;[19]=2;[20]=5;[21]=4;[22]=7;[23]=6;[24]=9;[25]=8;[26]=11;[27]=10;[28]=13;[29]=12;[30]=15;[31]=14;[32]=2;[33]=3;[34]=0;[35]=1;[36]=6;[37]=7;[38]=4;[39]=5;[40]=10;[41]=11;[42]=8;[43]=9;[44]=14;[45]=15;[46]=12;[47]=13;[48]=3;[49]=2;[50]=1;[51]=0;[52]=7;[53]=6;[54]=5;[55]=4;[56]=11;[57]=10;[58]=9;[59]=8;[60]=15;[61]=14;[62]=13;[63]=12;[64]=4;[65]=5;[66]=6;[67]=7;[68]=0;[69]=1;[70]=2;[71]=3;[72]=12;[73]=13;[74]=14;[75]=15;[76]=8;[77]=9;[78]=10;[79]=11;[80]=5;[81]=4;[82]=7;[83]=6;[84]=1;[85]=0;[86]=3;[87]=2;[88]=13;[89]=12;[90]=15;[91]=14;[92]=9;[93]=8;[94]=11;[95]=10;[96]=6;[97]=7;[98]=4;[99]=5;[100]=2;[101]=3;[102]=0;[103]=1;[104]=14;[105]=15;[106]=12;[107]=13;[108]=10;[109]=11;[110]=8;[111]=9;[112]=7;[113]=6;[114]=5;[115]=4;[116]=3;[117]=2;[118]=1;[119]=0;[120]=15;[121]=14;[122]=13;[123]=12;[124]=11;[125]=10;[126]=9;[127]=8;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=0;[137]=1;[138]=2;[139]=3;[140]=4;[141]=5;[142]=6;[143]=7;[144]=9;[145]=8;[146]=11;[147]=10;[148]=13;[149]=12;[150]=15;[151]=14;[152]=1;[153]=0;[154]=3;[155]=2;[156]=5;[157]=4;[158]=7;[159]=6;[160]=10;[161]=11;[162]=8;[163]=9;[164]=14;[165]=15;[166]=12;[167]=13;[168]=2;[169]=3;[170]=0;[171]=1;[172]=6;[173]=7;[174]=4;[175]=5;[176]=11;[177]=10;[178]=9;[179]=8;[180]=15;[181]=14;[182]=13;[183]=12;[184]=3;[185]=2;[186]=1;[187]=0;[188]=7;[189]=6;[190]=5;[191]=4;[192]=12;[193]=13;[194]=14;[195]=15;[196]=8;[197]=9;[198]=10;[199]=11;[200]=4;[201]=5;[202]=6;[203]=7;[204]=0;[205]=1;[206]=2;[207]=3;[208]=13;[209]=12;[210]=15;[211]=14;[212]=9;[213]=8;[214]=11;[215]=10;[216]=5;[217]=4;[218]=7;[219]=6;[220]=1;[221]=0;[222]=3;[223]=2;[224]=14;[225]=15;[226]=12;[227]=13;[228]=10;[229]=11;[230]=8;[231]=9;[232]=6;[233]=7;[234]=4;[235]=5;[236]=2;[237]=3;[238]=0;[239]=1;[240]=15;[241]=14;[242]=13;[243]=12;[244]=11;[245]=10;[246]=9;[247]=8;[248]=7;[249]=6;[250]=5;[251]=4;[252]=3;[253]=2;[254]=1;[255]=0;};
+local or_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=1;[18]=3;[19]=3;[20]=5;[21]=5;[22]=7;[23]=7;[24]=9;[25]=9;[26]=11;[27]=11;[28]=13;[29]=13;[30]=15;[31]=15;[32]=2;[33]=3;[34]=2;[35]=3;[36]=6;[37]=7;[38]=6;[39]=7;[40]=10;[41]=11;[42]=10;[43]=11;[44]=14;[45]=15;[46]=14;[47]=15;[48]=3;[49]=3;[50]=3;[51]=3;[52]=7;[53]=7;[54]=7;[55]=7;[56]=11;[57]=11;[58]=11;[59]=11;[60]=15;[61]=15;[62]=15;[63]=15;[64]=4;[65]=5;[66]=6;[67]=7;[68]=4;[69]=5;[70]=6;[71]=7;[72]=12;[73]=13;[74]=14;[75]=15;[76]=12;[77]=13;[78]=14;[79]=15;[80]=5;[81]=5;[82]=7;[83]=7;[84]=5;[85]=5;[86]=7;[87]=7;[88]=13;[89]=13;[90]=15;[91]=15;[92]=13;[93]=13;[94]=15;[95]=15;[96]=6;[97]=7;[98]=6;[99]=7;[100]=6;[101]=7;[102]=6;[103]=7;[104]=14;[105]=15;[106]=14;[107]=15;[108]=14;[109]=15;[110]=14;[111]=15;[112]=7;[113]=7;[114]=7;[115]=7;[116]=7;[117]=7;[118]=7;[119]=7;[120]=15;[121]=15;[122]=15;[123]=15;[124]=15;[125]=15;[126]=15;[127]=15;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=8;[137]=9;[138]=10;[139]=11;[140]=12;[141]=13;[142]=14;[143]=15;[144]=9;[145]=9;[146]=11;[147]=11;[148]=13;[149]=13;[150]=15;[151]=15;[152]=9;[153]=9;[154]=11;[155]=11;[156]=13;[157]=13;[158]=15;[159]=15;[160]=10;[161]=11;[162]=10;[163]=11;[164]=14;[165]=15;[166]=14;[167]=15;[168]=10;[169]=11;[170]=10;[171]=11;[172]=14;[173]=15;[174]=14;[175]=15;[176]=11;[177]=11;[178]=11;[179]=11;[180]=15;[181]=15;[182]=15;[183]=15;[184]=11;[185]=11;[186]=11;[187]=11;[188]=15;[189]=15;[190]=15;[191]=15;[192]=12;[193]=13;[194]=14;[195]=15;[196]=12;[197]=13;[198]=14;[199]=15;[200]=12;[201]=13;[202]=14;[203]=15;[204]=12;[205]=13;[206]=14;[207]=15;[208]=13;[209]=13;[210]=15;[211]=15;[212]=13;[213]=13;[214]=15;[215]=15;[216]=13;[217]=13;[218]=15;[219]=15;[220]=13;[221]=13;[222]=15;[223]=15;[224]=14;[225]=15;[226]=14;[227]=15;[228]=14;[229]=15;[230]=14;[231]=15;[232]=14;[233]=15;[234]=14;[235]=15;[236]=14;[237]=15;[238]=14;[239]=15;[240]=15;[241]=15;[242]=15;[243]=15;[244]=15;[245]=15;[246]=15;[247]=15;[248]=15;[249]=15;[250]=15;[251]=15;[252]=15;[253]=15;[254]=15;[255]=15;};
+local and_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=0;[9]=0;[10]=0;[11]=0;[12]=0;[13]=0;[14]=0;[15]=0;[16]=0;[17]=1;[18]=0;[19]=1;[20]=0;[21]=1;[22]=0;[23]=1;[24]=0;[25]=1;[26]=0;[27]=1;[28]=0;[29]=1;[30]=0;[31]=1;[32]=0;[33]=0;[34]=2;[35]=2;[36]=0;[37]=0;[38]=2;[39]=2;[40]=0;[41]=0;[42]=2;[43]=2;[44]=0;[45]=0;[46]=2;[47]=2;[48]=0;[49]=1;[50]=2;[51]=3;[52]=0;[53]=1;[54]=2;[55]=3;[56]=0;[57]=1;[58]=2;[59]=3;[60]=0;[61]=1;[62]=2;[63]=3;[64]=0;[65]=0;[66]=0;[67]=0;[68]=4;[69]=4;[70]=4;[71]=4;[72]=0;[73]=0;[74]=0;[75]=0;[76]=4;[77]=4;[78]=4;[79]=4;[80]=0;[81]=1;[82]=0;[83]=1;[84]=4;[85]=5;[86]=4;[87]=5;[88]=0;[89]=1;[90]=0;[91]=1;[92]=4;[93]=5;[94]=4;[95]=5;[96]=0;[97]=0;[98]=2;[99]=2;[100]=4;[101]=4;[102]=6;[103]=6;[104]=0;[105]=0;[106]=2;[107]=2;[108]=4;[109]=4;[110]=6;[111]=6;[112]=0;[113]=1;[114]=2;[115]=3;[116]=4;[117]=5;[118]=6;[119]=7;[120]=0;[121]=1;[122]=2;[123]=3;[124]=4;[125]=5;[126]=6;[127]=7;[128]=0;[129]=0;[130]=0;[131]=0;[132]=0;[133]=0;[134]=0;[135]=0;[136]=8;[137]=8;[138]=8;[139]=8;[140]=8;[141]=8;[142]=8;[143]=8;[144]=0;[145]=1;[146]=0;[147]=1;[148]=0;[149]=1;[150]=0;[151]=1;[152]=8;[153]=9;[154]=8;[155]=9;[156]=8;[157]=9;[158]=8;[159]=9;[160]=0;[161]=0;[162]=2;[163]=2;[164]=0;[165]=0;[166]=2;[167]=2;[168]=8;[169]=8;[170]=10;[171]=10;[172]=8;[173]=8;[174]=10;[175]=10;[176]=0;[177]=1;[178]=2;[179]=3;[180]=0;[181]=1;[182]=2;[183]=3;[184]=8;[185]=9;[186]=10;[187]=11;[188]=8;[189]=9;[190]=10;[191]=11;[192]=0;[193]=0;[194]=0;[195]=0;[196]=4;[197]=4;[198]=4;[199]=4;[200]=8;[201]=8;[202]=8;[203]=8;[204]=12;[205]=12;[206]=12;[207]=12;[208]=0;[209]=1;[210]=0;[211]=1;[212]=4;[213]=5;[214]=4;[215]=5;[216]=8;[217]=9;[218]=8;[219]=9;[220]=12;[221]=13;[222]=12;[223]=13;[224]=0;[225]=0;[226]=2;[227]=2;[228]=4;[229]=4;[230]=6;[231]=6;[232]=8;[233]=8;[234]=10;[235]=10;[236]=12;[237]=12;[238]=14;[239]=14;[240]=0;[241]=1;[242]=2;[243]=3;[244]=4;[245]=5;[246]=6;[247]=7;[248]=8;[249]=9;[250]=10;[251]=11;[252]=12;[253]=13;[254]=14;[255]=15;}
+
+local not_map = {[0]=15;[1]=14;[2]=13;[3]=12;[4]=11;[5]=10;[6]=9;[7]=8;[8]=7;[9]=6;[10]=5;[11]=4;[12]=3;[13]=2;[14]=1;[15]=0;};
+local rshift1_map = {[0]=0;[1]=0;[2]=1;[3]=1;[4]=2;[5]=2;[6]=3;[7]=3;[8]=4;[9]=4;[10]=5;[11]=5;[12]=6;[13]=6;[14]=7;[15]=7;};
+local rshift1carry_map = {[0]=0;[1]=8;[2]=0;[3]=8;[4]=0;[5]=8;[6]=0;[7]=8;[8]=0;[9]=8;[10]=0;[11]=8;[12]=0;[13]=8;[14]=0;[15]=8;};
+local lshift1_map = {[0]=0;[1]=2;[2]=4;[3]=6;[4]=8;[5]=10;[6]=12;[7]=14;[8]=0;[9]=2;[10]=4;[11]=6;[12]=8;[13]=10;[14]=12;[15]=14;};
+local lshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=1;[9]=1;[10]=1;[11]=1;[12]=1;[13]=1;[14]=1;[15]=1;};
+local arshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=8;[9]=8;[10]=8;[11]=8;[12]=8;[13]=8;[14]=8;[15]=8;};
+
+module "bit"
+
+local bit_mt = {__tostring = function(t) return ("%x%x%x%x%x%x%x%x"):format(t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8]); end};
+local function do_bop(a, b, op)
+	return setmetatable({
+		op[a[1]*16+b[1]];
+		op[a[2]*16+b[2]];
+		op[a[3]*16+b[3]];
+		op[a[4]*16+b[4]];
+		op[a[5]*16+b[5]];
+		op[a[6]*16+b[6]];
+		op[a[7]*16+b[7]];
+		op[a[8]*16+b[8]];
+	}, bit_mt);
+end
+local function do_uop(a, op)
+	return setmetatable({
+		op[a[1]];
+		op[a[2]];
+		op[a[3]];
+		op[a[4]];
+		op[a[5]];
+		op[a[6]];
+		op[a[7]];
+		op[a[8]];
+	}, bit_mt);
+end
+
+function bxor(a, b) return do_bop(a, b, xor_map); end
+function bor(a, b) return do_bop(a, b, or_map); end
+function band(a, b) return do_bop(a, b, and_map); end
+
+function bnot(a) return do_uop(a, not_map); end
+local function _rshift1(t)
+	local carry = 0;
+	for i=1,8 do
+		local t_i = rshift1_map[t[i]] + carry;
+		carry = rshift1carry_map[t[i]];
+		t[i] = t_i;
+	end
+end
+function rshift(a, i)
+	local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
+	for n = 1,i do _rshift1(t); end
+	return setmetatable(t, bit_mt);
+end
+local function _arshift1(t)
+	local carry = arshift1carry_map[t[1]];
+	for i=1,8 do
+		local t_i = rshift1_map[t[i]] + carry;
+		carry = rshift1carry_map[t[i]];
+		t[i] = t_i;
+	end
+end
+function arshift(a, i)
+	local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
+	for n = 1,i do _arshift1(t); end
+	return setmetatable(t, bit_mt);
+end
+local function _lshift1(t)
+	local carry = 0;
+	for i=8,1,-1 do
+		local t_i = lshift1_map[t[i]] + carry;
+		carry = lshift1carry_map[t[i]];
+		t[i] = t_i;
+	end
+end
+function lshift(a, i)
+	local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]};
+	for n = 1,i do _lshift1(t); end
+	return setmetatable(t, bit_mt);
+end
+
+local function _cast(a)
+	if type(a) == "number" then a = ("%x"):format(a);
+	elseif type(a) == "table" then return a;
+	elseif type(a) ~= "string" then error("string expected, got "..type(a), 2); end
+	local t = {0,0,0,0,0,0,0,0};
+	a = "00000000"..a;
+	a = a:sub(-8);
+	for i = 1,8 do
+		t[i] = tonumber(a:sub(i,i), 16) or error("Number format error", 2);
+	end
+	return setmetatable(t, bit_mt);
+end
+
+local function wrap1(f)
+	return function(a, ...)
+		if type(a) ~= "table" then a = _cast(a); end
+		a = f(a, ...);
+		a = tonumber(tostring(a), 16);
+		if a > 0x7fffffff then a = a - 1 - 0xffffffff; end
+		return a;
+	end;
+end
+local function wrap2(f)
+	return function(a, b, ...)
+		if type(a) ~= "table" then a = _cast(a); end
+		if type(b) ~= "table" then b = _cast(b); end
+		a = f(a, b, ...);
+		a = tonumber(tostring(a), 16);
+		if a > 0x7fffffff then a = a - 1 - 0xffffffff; end
+		return a;
+	end;
+end
+
+bxor = wrap2(bxor);
+bor = wrap2(bor);
+band = wrap2(band);
+bnot = wrap1(bnot);
+lshift = wrap1(lshift);
+rshift = wrap1(rshift);
+arshift = wrap1(arshift);
+cast = wrap1(_cast);
+
+bits = 32;
+
+return _M;
--- a/net/http.lua	Fri May 22 13:34:47 2009 +0100
+++ b/net/http.lua	Fri May 22 13:35:54 2009 +0100
@@ -9,8 +9,8 @@
 local listener = connlisteners_get("httpclient") or error("No httpclient listener!");
 
 local t_insert, t_concat = table.insert, table.concat;
-local tonumber, tostring, pairs, xpcall, select, debug_traceback = 
-        tonumber, tostring, pairs, xpcall, select, debug.traceback;
+local tonumber, tostring, pairs, xpcall, select, debug_traceback, char = 
+        tonumber, tostring, pairs, xpcall, select, debug.traceback, string.char;
 
 local log = require "util.logger".init("http");
 local print = function () end
--- a/net/server.lua	Fri May 22 13:34:47 2009 +0100
+++ b/net/server.lua	Fri May 22 13:35:54 2009 +0100
@@ -791,7 +791,7 @@
 end
 
 local addclient = function( address, port, listeners, pattern, sslctx, startssl )
-    local client, err = socket.tcp( )
+    local client, err = luasocket.tcp( )
     if err then
         return nil, err
     end
@@ -800,7 +800,7 @@
     if err then    -- try again
         local handler = wrapclient( client, address, port, listeners )
     else
-        wrapconnection( server, listeners, socket, address, port, "clientport", pattern, sslctx, startssl )
+        wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx, startssl )
     end
 end
 
--- a/net/xmppclient_listener.lua	Fri May 22 13:34:47 2009 +0100
+++ b/net/xmppclient_listener.lua	Fri May 22 13:35:54 2009 +0100
@@ -9,6 +9,7 @@
 
 
 local logger = require "logger";
+local log = logger.init("xmppclient_listener");
 local lxp = require "lxp"
 local init_xmlhandlers = require "core.xmlhandlers"
 local sm_new_session = require "core.sessionmanager".new_session;
@@ -20,10 +21,11 @@
 local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
 local m_random = math.random;
 local format = string.format;
-local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; --import("core.sessionmanager", "new_session", "destroy_session");
+local sessionmanager = require "core.sessionmanager";
+local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
 local sm_streamopened = sessionmanager.streamopened;
 local sm_streamclosed = sessionmanager.streamclosed;
-local st = stanza;
+local st = require "util.stanza";
 
 local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams|stream", 
 		default_ns = "jabber:client",
--- a/net/xmppserver_listener.lua	Fri May 22 13:34:47 2009 +0100
+++ b/net/xmppserver_listener.lua	Fri May 22 13:35:54 2009 +0100
@@ -9,9 +9,9 @@
 
 
 local logger = require "logger";
+local log = logger.init("xmppserver_listener");
 local lxp = require "lxp"
 local init_xmlhandlers = require "core.xmlhandlers"
-local sm_new_session = require "core.sessionmanager".new_session;
 local s2s_new_incoming = require "core.s2smanager".new_incoming;
 local s2s_streamopened = require "core.s2smanager".streamopened;
 local s2s_streamclosed = require "core.s2smanager".streamclosed;
@@ -42,8 +42,9 @@
 local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end
 local m_random = math.random;
 local format = string.format;
-local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; --import("core.sessionmanager", "new_session", "destroy_session");
-local st = stanza;
+local sessionmanager = require "core.sessionmanager";
+local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
+local st = require "util.stanza";
 
 local sessions = {};
 local xmppserver = { default_port = 5269, default_mode = "*a" };
--- a/plugins/mod_bosh.lua	Fri May 22 13:34:47 2009 +0100
+++ b/plugins/mod_bosh.lua	Fri May 22 13:35:54 2009 +0100
@@ -1,6 +1,7 @@
 
 module.host = "*" -- Global module
 
+local hosts = _G.hosts;
 local lxp = require "lxp";
 local init_xmlhandlers = require "core.xmlhandlers"
 local server = require "net.server";
--- a/plugins/mod_muc.lua	Fri May 22 13:34:47 2009 +0100
+++ b/plugins/mod_muc.lua	Fri May 22 13:35:54 2009 +0100
@@ -205,6 +205,34 @@
 		end
 	end
 end
+function send_history(room, to)
+	local history = rooms_info:get(room, 'history'); -- send discussion history
+	if history then
+		for _, msg in ipairs(history) do
+			msg = st.deserialize(msg);
+			msg.attr.to=to;
+			core_route_stanza(component, msg);
+		end
+	end
+	if rooms_info:get(room, 'subject') then
+		core_route_stanza(component, st.message({type='groupchat', from=room, to=to}):tag("subject"):text(rooms_info:get(room, 'subject')));
+	end
+end
+function send_occupant_list(room, to)
+	local r = rooms:get(room);
+	if r then
+		local current_nick = jid_nick:get(to, room);
+		for occupant, o_data in pairs(r) 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();
+				core_route_stanza(component, pres);
+			end
+		end
+	end
+end
 
 function handle_to_occupant(origin, stanza) -- PM, vCards, etc
 	local from, to = stanza.attr.from, stanza.attr.to;
@@ -232,7 +260,7 @@
 			end
 		elseif not type then -- available
 			if current_nick then
-				if #pr == #stanza or current_nick ~= to 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);
 						rooms:get(room, current_nick).sessions[from] = pr;
@@ -259,11 +287,11 @@
 							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 -- 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 rooms:get(room, to) then
@@ -284,31 +312,10 @@
 					end
 					rooms:set(room, to, data);
 					jid_nick:set(from, room, to);
-					local r = rooms:get(room);
-					if r then
-						for occupant, o_data in pairs(r) do
-							if occupant ~= to then
-								local pres = get_filtered_presence(o_data.sessions[o_data.jid]);
-								pres.attr.to, pres.attr.from = from, occupant;
-								pres:tag("x", {xmlns='http://jabber.org/protocol/muc#user'})
-									:tag("item", {affiliation=o_data.affiliation, role=o_data.role}):up();
-								core_route_stanza(component, pres);
-							end
-						end
-					end
+					send_occupant_list(room, from);
 					pr.attr.from = to;
 					broadcast_presence_stanza(room, pr);
-					local history = rooms_info:get(room, 'history'); -- send discussion history
-					if history then
-						for _, msg in ipairs(history) do
-							msg = st.deserialize(msg);
-							msg.attr.to=from;
-							core_route_stanza(component, msg);
-						end
-					end
-					if rooms_info:get(room, 'subject') then
-						core_route_stanza(component, st.message({type='groupchat', from=room, to=from}):tag("subject"):text(rooms_info:get(room, 'subject')));
-					end
+					send_history(room, from);
 				end
 			end
 		elseif type ~= 'result' then -- bad type
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_pep.lua	Fri May 22 13:35:54 2009 +0100
@@ -0,0 +1,48 @@
+
+local jid_bare = require "util.jid".bare;
+local jid_split = require "util.jid".split;
+local st = require "util.stanza";
+local hosts = hosts;
+local user_exists = require "core.usermanager".user_exists;
+local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
+local pairs, ipairs = pairs, ipairs;
+
+local function publish(session, node, item)
+	local stanza = st.message({from=session.full_jid, type='headline'})
+		:tag('event', {xmlns='http://jabber.org/protocol/pubsub#event'})
+			:tag('items', {node=node})
+				:add_child(item)
+			:up()
+		:up();
+
+	-- broadcast to resources
+	stanza.attr.to = session.username..'@'..session.host;
+	core_route_stanza(session, stanza);
+
+	-- broadcast to contacts
+	for jid, item in pairs(session.roster) do
+		if jid and jid ~= "pending" and (item.subscription == 'from' or item.subscription == 'both') then
+			stanza.attr.to = jid;
+			core_route_stanza(session, stanza);
+		end
+	end
+end
+
+module:add_iq_handler("c2s", "http://jabber.org/protocol/pubsub", function (session, stanza)
+	if stanza.attr.type == 'set' and (not stanza.attr.to or jid_bare(stanza.attr.from) == stanza.attr.to) then
+		local payload = stanza.tags[1];
+		if payload.name == 'pubsub' then
+			payload = payload.tags[1];
+			if payload and payload.name == 'publish' and payload.attr.node then
+				local node = payload.attr.node;
+				payload = payload.tags[1];
+				if payload then
+					publish(session, node, payload);
+					return true;
+				end -- TODO else error
+			end -- TODO else error
+		end
+	end
+	origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
+end);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_presence.lua	Fri May 22 13:35:54 2009 +0100
@@ -0,0 +1,280 @@
+-- Prosody IM v0.4
+-- 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 log = require "util.logger".init("mod_presence")
+
+local require = require;
+local pairs, ipairs = pairs, ipairs;
+local t_concat = table.concat;
+local s_find = string.find;
+local tonumber = tonumber;
+
+local st = require "util.stanza";
+local jid_split = require "util.jid".split;
+local jid_bare = require "util.jid".bare;
+local hosts = hosts;
+
+local rostermanager = require "core.rostermanager";
+local sessionmanager = require "core.sessionmanager";
+local offlinemanager = require "core.offlinemanager";
+
+local _core_route_stanza = core_route_stanza;
+local core_route_stanza;
+function core_route_stanza(origin, stanza)
+	if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
+		local node, host = jid_split(stanza.attr.to);
+		host = hosts[host];
+		if host and host.type == "local" then
+			handle_inbound_presence_subscriptions_and_probes(origin, stanza, jid_bare(stanza.attr.from), jid_bare(stanza.attr.to), core_route_stanza);
+			return;
+		end
+	end
+	_core_route_stanza(origin, stanza);
+end
+
+function handle_presence(origin, stanza, from_bare, to_bare, core_route_stanza, inbound)
+	local type = stanza.attr.type;
+	if type and type ~= "unavailable" and type ~= "error" then
+		if inbound then
+			handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
+		else
+			handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
+		end
+	elseif not inbound and not stanza.attr.to then
+		handle_normal_presence(origin, stanza, core_route_stanza);
+	else
+		core_route_stanza(origin, stanza);
+	end
+end
+
+function handle_normal_presence(origin, stanza, core_route_stanza)
+	if origin.roster then
+		for jid in pairs(origin.roster) do -- broadcast to all interested contacts
+			local subscription = origin.roster[jid].subscription;
+			if subscription == "both" or subscription == "from" then
+				stanza.attr.to = jid;
+				core_route_stanza(origin, stanza);
+			end
+		end
+		local node, host = jid_split(stanza.attr.from);
+		for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast to all resources
+			if res ~= origin and res.presence then -- to resource
+				stanza.attr.to = res.full_jid;
+				core_route_stanza(origin, stanza);
+			end
+		end
+		if stanza.attr.type == nil and not origin.presence then -- initial presence
+			local probe = st.presence({from = origin.full_jid, type = "probe"});
+			for jid in pairs(origin.roster) do -- probe all contacts we are subscribed to
+				local subscription = origin.roster[jid].subscription;
+				if subscription == "both" or subscription == "to" then
+					probe.attr.to = jid;
+					core_route_stanza(origin, probe);
+				end
+			end
+			for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast from all available resources
+				if res ~= origin and res.presence then
+					res.presence.attr.to = origin.full_jid;
+					core_route_stanza(res, res.presence);
+					res.presence.attr.to = nil;
+				end
+			end
+			if origin.roster.pending then -- resend incoming subscription requests
+				for jid in pairs(origin.roster.pending) do
+					origin.send(st.presence({type="subscribe", from=jid})); -- TODO add to attribute? Use original?
+				end
+			end
+			local request = st.presence({type="subscribe", from=origin.username.."@"..origin.host});
+			for jid, item in pairs(origin.roster) do -- resend outgoing subscription requests
+				if item.ask then
+					request.attr.to = jid;
+					core_route_stanza(origin, request);
+				end
+			end
+			local offline = offlinemanager.load(node, host);
+			if offline then
+				for _, msg in ipairs(offline) do
+					origin.send(msg); -- FIXME do we need to modify to/from in any way?
+				end
+				offlinemanager.deleteAll(node, host);
+			end
+		end
+		origin.priority = 0;
+		if stanza.attr.type == "unavailable" then
+			origin.presence = nil;
+			if origin.directed then
+				local old_from = stanza.attr.from;
+				stanza.attr.from = origin.full_jid;
+				for jid in pairs(origin.directed) do
+					stanza.attr.to = jid;
+					core_route_stanza(origin, stanza);
+				end
+				stanza.attr.from = old_from;
+				origin.directed = nil;
+			end
+		else
+			origin.presence = stanza;
+			local priority = stanza:child_with_name("priority");
+			if priority and #priority > 0 then
+				priority = t_concat(priority);
+				if s_find(priority, "^[+-]?[0-9]+$") then
+					priority = tonumber(priority);
+					if priority < -128 then priority = -128 end
+					if priority > 127 then priority = 127 end
+					origin.priority = priority;
+				end
+			end
+		end
+		stanza.attr.to = nil; -- reset it
+	else
+		log("error", "presence recieved from client with no roster");
+	end
+end
+
+function send_presence_of_available_resources(user, host, jid, recipient_session, core_route_stanza)
+	local h = hosts[host];
+	local count = 0;
+	if h and h.type == "local" then
+		local u = h.sessions[user];
+		if u then
+			for k, session in pairs(u.sessions) do
+				local pres = session.presence;
+				if pres then
+					pres.attr.to = jid;
+					pres.attr.from = session.full_jid;
+					core_route_stanza(session, pres);
+					pres.attr.to = nil;
+					pres.attr.from = nil;
+					count = count + 1;
+				end
+			end
+		end
+	end
+	log("info", "broadcasted presence of "..count.." resources from "..user.."@"..host.." to "..jid);
+	return count;
+end
+
+function handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)
+	local node, host = jid_split(from_bare);
+	local st_from, st_to = stanza.attr.from, stanza.attr.to;
+	stanza.attr.from, stanza.attr.to = from_bare, to_bare;
+	log("debug", "outbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);
+	if stanza.attr.type == "subscribe" then
+		-- 1. route stanza
+		-- 2. roster push (subscription = none, ask = subscribe)
+		if rostermanager.set_contact_pending_out(node, host, to_bare) then
+			rostermanager.roster_push(node, host, to_bare);
+		end -- else file error
+		core_route_stanza(origin, stanza);
+	elseif stanza.attr.type == "unsubscribe" then
+		-- 1. route stanza
+		-- 2. roster push (subscription = none or from)
+		if rostermanager.unsubscribe(node, host, to_bare) then
+			rostermanager.roster_push(node, host, to_bare); -- FIXME do roster push when roster has in fact not changed?
+		end -- else file error
+		core_route_stanza(origin, stanza);
+	elseif stanza.attr.type == "subscribed" then
+		-- 1. route stanza
+		-- 2. roster_push ()
+		-- 3. send_presence_of_available_resources
+		if rostermanager.subscribed(node, host, to_bare) then
+			rostermanager.roster_push(node, host, to_bare);
+		end
+		core_route_stanza(origin, stanza);
+		send_presence_of_available_resources(node, host, to_bare, origin, core_route_stanza);
+	elseif stanza.attr.type == "unsubscribed" then
+		-- 1. route stanza
+		-- 2. roster push (subscription = none or to)
+		if rostermanager.unsubscribed(node, host, to_bare) then
+			rostermanager.roster_push(node, host, to_bare);
+		end
+		core_route_stanza(origin, stanza);
+	end
+	stanza.attr.from, stanza.attr.to = st_from, st_to;
+end
+
+function handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza)
+	local node, host = jid_split(to_bare);
+	local st_from, st_to = stanza.attr.from, stanza.attr.to;
+	stanza.attr.from, stanza.attr.to = from_bare, to_bare;
+	log("debug", "inbound presence "..stanza.attr.type.." from "..from_bare.." for "..to_bare);
+	if stanza.attr.type == "probe" then
+		if rostermanager.is_contact_subscribed(node, host, from_bare) then
+			if 0 == send_presence_of_available_resources(node, host, from_bare, origin, core_route_stanza) then
+				-- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
+			end
+		else
+			core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
+		end
+	elseif stanza.attr.type == "subscribe" then
+		if rostermanager.is_contact_subscribed(node, host, from_bare) then
+			core_route_stanza(origin, st.presence({from=to_bare, to=from_bare, type="subscribed"})); -- already subscribed
+			-- Sending presence is not clearly stated in the RFC, but it seems appropriate
+			if 0 == send_presence_of_available_resources(node, host, from_bare, origin, core_route_stanza) then
+				-- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
+			end
+		else
+			if not rostermanager.is_contact_pending_in(node, host, from_bare) then
+				if rostermanager.set_contact_pending_in(node, host, from_bare) then
+					sessionmanager.send_to_available_resources(node, host, stanza);
+				end -- TODO else return error, unable to save
+			end
+		end
+	elseif stanza.attr.type == "unsubscribe" then
+		if rostermanager.process_inbound_unsubscribe(node, host, from_bare) then
+			rostermanager.roster_push(node, host, from_bare);
+		end
+	elseif stanza.attr.type == "subscribed" then
+		if rostermanager.process_inbound_subscription_approval(node, host, from_bare) then
+			rostermanager.roster_push(node, host, from_bare);
+		end
+	elseif stanza.attr.type == "unsubscribed" then
+		if rostermanager.process_inbound_subscription_cancellation(node, host, from_bare) then
+			rostermanager.roster_push(node, host, from_bare);
+		end
+	end -- discard any other type
+	stanza.attr.from, stanza.attr.to = st_from, st_to;
+end
+
+local function presence_handler(data)
+	local origin, stanza = data.origin, data.stanza;
+	local to = stanza.attr.to;
+	local node, host = jid_split(to);
+	local to_bare = jid_bare(to);
+	local from_bare = jid_bare(stanza.attr.from);
+	if origin.type == "c2s" then
+		if to ~= nil and not(origin.roster[to_bare] and (origin.roster[to_bare].subscription == "both" or origin.roster[to_bare].subscription == "from")) then -- directed presence
+			origin.directed = origin.directed or {};
+			origin.directed[to] = true; -- FIXME does it make more sense to add to_bare rather than to?
+		end
+		if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
+			handle_outbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
+		elseif not to then
+			handle_normal_presence(origin, stanza, core_route_stanza);
+		else
+			core_route_stanza(origin, stanza);
+		end
+	elseif (origin.type == "s2sin" or origin.type == "component") and hosts[host] then
+		if stanza.attr.type ~= nil and stanza.attr.type ~= "unavailable" and stanza.attr.type ~= "error" then
+			handle_inbound_presence_subscriptions_and_probes(origin, stanza, from_bare, to_bare, core_route_stanza);
+		else
+			core_route_stanza(origin, stanza);
+		end
+	end
+	return true;
+end
+
+local add_handler = require "core.eventmanager2".add_handler;
+local remove_handler = require "core.eventmanager2".remove_handler;
+
+add_handler(module:get_host().."/presence", presence_handler);
+module.unload = function()
+	remove_handler(module:get_host().."/presence", presence_handler);
+end
--- a/plugins/mod_register.lua	Fri May 22 13:34:47 2009 +0100
+++ b/plugins/mod_register.lua	Fri May 22 13:35:54 2009 +0100
@@ -60,6 +60,8 @@
 					end
 				end
 				datamanager.store(username, host, "accounts", nil); -- delete accounts datastore at the end
+				module:log("info", "User removed their account: %s@%s", username, host);
+				module:fire_event("user-deregistered", { username = username, host = host, source = "mod_register", session = session });
 			else
 				local username = query:child_with_name("username");
 				local password = query:child_with_name("password");
@@ -143,6 +145,10 @@
 					else
 						if usermanager_create_user(username, password, session.host) then
 							session.send(st.reply(stanza)); -- user created!
+							module:log("info", "User account created: %s@%s", username, session.host);
+							module:fire_event("user-registered", { 
+								username = username, host = session.host, source = "mod_register",
+								session = session });
 						else
 							-- TODO unable to write file, file may be locked, etc, what's the correct error?
 							session.send(st.error_reply(stanza, "wait", "internal-server-error"));
--- a/plugins/mod_roster.lua	Fri May 22 13:34:47 2009 +0100
+++ b/plugins/mod_roster.lua	Fri May 22 13:35:54 2009 +0100
@@ -23,6 +23,14 @@
 
 module:add_feature("jabber:iq:roster");
 
+local rosterver_stream_feature = st.stanza("ver", {xmlns="urn:xmpp:features:rosterver"}):tag("optional"):up();
+module:add_event_hook("stream-features", 
+		function (session, features)												
+			if session.username then
+				features:add_child(rosterver_stream_feature);
+			end
+		end);
+
 module:add_iq_handler("c2s", "jabber:iq:roster", 
 		function (session, stanza)
 			if stanza.tags[1].name == "query" then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_welcome.lua	Fri May 22 13:35:54 2009 +0100
@@ -0,0 +1,23 @@
+-- Prosody IM v0.4
+-- Copyright (C) 2008-2009 Matthew Wild
+-- Copyright (C) 2008-2009 Waqas Hussain
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local config = require "core.configmanager";
+
+local host = module:get_host();
+local welcome_text = config.get("*", "core", "welcome_message") or "Hello $user, welcome to the $host IM server!";
+
+local st = require "util.stanza";
+
+module:add_event_hook("user-registered", 
+	function (user)
+		local welcome_stanza = 
+			st.message({ to = user.username.."@"..user.host, from = host })
+				:tag("body"):text(welcome_text:gsub("$(%w+)", user));
+		core_route_stanza(hosts[host], welcome_stanza);
+		module:log("debug", "Welcomed user %s@%s", user.username, user.host);
+	end);
--- a/prosody.cfg.lua.dist	Fri May 22 13:34:47 2009 +0100
+++ b/prosody.cfg.lua.dist	Fri May 22 13:35:54 2009 +0100
@@ -15,7 +15,7 @@
 -- A table is a list of values, except each value has a name. An 
 -- example would be:
 --
--- logging = { type = "html", directory = "/var/logs", rotate = "daily" }
+-- ssl = { key = "keyfile.key", certificate = "certificate.cert" }
 --
 -- Whitespace (that is tabs, spaces, line breaks) is mostly insignificant, so 
 -- can 
@@ -38,6 +38,7 @@
 	modules_enabled = {
 			-- Generally required
 				"roster"; -- Allow users to have a roster. Recommended ;)
+				"presence"; -- See and broadcast status changes to/from contacts
 				"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
 				"tls"; -- Add support for secure TLS on c2s/s2s connections
 				"dialback"; -- s2s dialback support
--- a/util/events.lua	Fri May 22 13:34:47 2009 +0100
+++ b/util/events.lua	Fri May 22 13:35:54 2009 +0100
@@ -2,6 +2,7 @@
 local ipairs = ipairs;
 local pairs = pairs;
 local t_insert = table.insert;
+local t_sort = table.sort;
 local select = select;
 
 module "events"
@@ -10,32 +11,32 @@
 	local dispatchers = {};
 	local handlers = {};
 	local event_map = {};
-	local function _rebuild_index() -- TODO optimize index rebuilding
-		for event, _handlers in pairs(event_map) do
-			local index = handlers[event];
-			if index then
-				for i=#index,1,-1 do index[i] = nil; end
-			else index = {}; handlers[event] = index; end
-			for handler in pairs(_handlers) do
-				t_insert(index, handler);
-			end
+	local function _rebuild_index(event) -- TODO optimize index rebuilding
+		local _handlers = event_map[event];
+		local index = handlers[event];
+		if index then
+			for i=#index,1,-1 do index[i] = nil; end
+		else index = {}; handlers[event] = index; end
+		for handler in pairs(_handlers) do
+			t_insert(index, handler);
 		end
+		t_sort(index, function(a, b) return _handlers[a] > _handlers[b]; end);
 	end;
-	local function add_handler(event, handler)
+	local function add_handler(event, handler, priority)
 		local map = event_map[event];
 		if map then
-			map[handler] = true;
+			map[handler] = priority or 0;
 		else
-			map = {[handler] = true};
+			map = {[handler] = priority or 0};
 			event_map[event] = map;
 		end
-		_rebuild_index();
+		_rebuild_index(event);
 	end;
 	local function remove_handler(event, handler)
 		local map = event_map[event];
 		if map then
 			map[handler] = nil;
-			_rebuild_index();
+			_rebuild_index(event);
 		end
 	end;
 	local function add_plugin(plugin)
@@ -51,9 +52,10 @@
 	local function _create_dispatcher(event) -- FIXME duplicate code in fire_event
 		local h = handlers[event];
 		if not h then h = {}; handlers[event] = h; end
-		local dispatcher = function(data)
+		local dispatcher = function(...)
 			for _, handler in ipairs(h) do
-				handler(data);
+				local ret = handler(...);
+				if ret ~= nil then return ret; end
 			end
 		end;
 		dispatchers[event] = dispatcher;
@@ -62,11 +64,12 @@
 	local function get_dispatcher(event)
 		return dispatchers[event] or _create_dispatcher(event);
 	end;
-	local function fire_event(event, data) -- FIXME duplicates dispatcher code
+	local function fire_event(event, ...) -- FIXME duplicates dispatcher code
 		local h = handlers[event];
 		if h then
 			for _, handler in ipairs(h) do
-				handler(data);
+				local ret = handler(...);
+				if ret ~= nil then return ret; end
 			end
 		end
 	end;
--- a/util/jid.lua	Fri May 22 13:34:47 2009 +0100
+++ b/util/jid.lua	Fri May 22 13:35:54 2009 +0100
@@ -15,7 +15,7 @@
 
 module "jid"
 
-function split(jid)
+local function _split(jid)
 	if not jid then return; end
 	local node, nodepos = match(jid, "^([^@]+)@()");
 	local host, hostpos = match(jid, "^([^@/]+)()", nodepos)
@@ -24,17 +24,18 @@
 	if (not host) or ((not resource) and #jid >= hostpos) then return nil, nil, nil; end
 	return node, host, resource;
 end
+split = _split;
 
 function bare(jid)
-	local node, host = split(jid);
+	local node, host = _split(jid);
 	if node and host then
 		return node.."@"..host;
 	end
 	return host;
 end
 
-function prepped_split(jid)
-	local node, host, resource = split(jid);
+local function _prepped_split(jid)
+	local node, host, resource = _split(jid);
 	if host then
 		host = nameprep(host);
 		if not host then return; end
@@ -49,9 +50,10 @@
 		return node, host, resource;
 	end
 end
+prepped_split = _prepped_split;
 
 function prep(jid)
-	local node, host, resource = prepped_split(jid);
+	local node, host, resource = _prepped_split(jid);
 	if host then
 		if node then
 			host = node .. "@" .. host;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/muc.lua	Fri May 22 13:35:54 2009 +0100
@@ -0,0 +1,422 @@
+-- Prosody IM v0.4
+-- 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, 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, room, 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(room, 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"));
+						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"));
+				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" 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;
+			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(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 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
+		room_handle_to_occupant(self, origin, stanza);
+	elseif to_node then
+		room_handle_to_room(self, origin, stanza)
+	else -- to the main muc domain
+	end
+end
+
+module "muc"
+
+function new_room(jid)
+	return {
+		jid = jid;
+		handle_stanza = room_handle_stanza;
+		set_subject = room_set_subject;
+		route_stanza = function(room, stanza) end; -- Replace with a routing function, e.g., function(room, stanza) core_route_stanza(origin, stanza); end
+		_jid_nick = {};
+		_participants = {};
+		_data = {};
+	}
+end
+
+return _M;
+
+--[[function get_disco_info(stanza)
+	return st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#info")
+		:tag("identity", {category='conference', type='text', name=muc_name}):up()
+		:tag("feature", {var="http://jabber.org/protocol/muc"}); -- TODO cache disco reply
+end
+function get_disco_items(stanza)
+	local reply = st.iq({type='result', id=stanza.attr.id, from=muc_domain, to=stanza.attr.from}):query("http://jabber.org/protocol/disco#items");
+	for room in pairs(rooms_info:get()) do
+		reply:tag("item", {jid=room, name=rooms_info:get(room, "name")}):up();
+	end
+	return reply; -- TODO cache disco reply
+end]]
+
+--[[function handle_to_domain(origin, stanza)
+	local type = stanza.attr.type;
+	if type == "error" or type == "result" then return; end
+	if stanza.name == "iq" and type == "get" then
+		local xmlns = stanza.tags[1].attr.xmlns;
+		if xmlns == "http://jabber.org/protocol/disco#info" then
+			origin.send(get_disco_info(stanza));
+		elseif xmlns == "http://jabber.org/protocol/disco#items" then
+			origin.send(get_disco_items(stanza));
+		else
+			origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- TODO disco/etc
+		end
+	else
+		origin.send(st.error_reply(stanza, "cancel", "service-unavailable", "The muc server doesn't deal with messages and presence directed at it"));
+	end
+end
+
+register_component(muc_domain, function(origin, stanza)
+	local to_node, to_host, to_resource = jid_split(stanza.attr.to);
+	if to_resource and not to_node then
+		if type == "error" or type == "result" then return; end
+		origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); -- host/resource
+	elseif to_resource then
+		handle_to_occupant(origin, stanza);
+	elseif to_node then
+		handle_to_room(origin, stanza)
+	else -- to the main muc domain
+		if type == "error" or type == "result" then return; end
+		handle_to_domain(origin, stanza);
+	end
+end);]]
+
+--[[module.unload = function()
+	deregister_component(muc_domain);
+end
+module.save = function()
+	return {rooms = rooms.data; jid_nick = jid_nick.data; rooms_info = rooms_info.data; persist_list = persist_list};
+end
+module.restore = function(data)
+	rooms.data, jid_nick.data, rooms_info.data, persist_list =
+	data.rooms or {}, data.jid_nick or {}, data.rooms_info or {}, data.persist_list or {};
+end]]
--- a/util/sasl.lua	Fri May 22 13:34:47 2009 +0100
+++ b/util/sasl.lua	Fri May 22 13:35:54 2009 +0100
@@ -26,8 +26,6 @@
 local type = type
 local error = error
 local print = print
-local idna_ascii = require "util.encodings".idna.to_ascii
-local idna_unicode = require "util.encodings".idna.to_unicode
 
 module "sasl"
 
@@ -62,8 +60,10 @@
 	return object
 end
 
+
+-- implementing RFC 2831
 local function new_digest_md5(realm, password_handler)
-	--TODO maybe support for authzid
+	--TODO complete support for authzid
 
 	local function serialize(message)
 		local data = ""
@@ -131,29 +131,29 @@
 	local function parse(data)
 		message = {}
 		for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder
-			message[k] = v
+			message[k] = v;
 		end
-		return message
+		return message;
 	end
 
-	local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler}
+	local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler};
 	
 	--TODO: something better than math.random would be nice, maybe OpenSSL's random number generator
-	object.nonce = generate_uuid()
-	object.step = 0
-	object.nonce_count = {}
+	object.nonce = generate_uuid();
+	object.step = 0;
+	object.nonce_count = {};
 												
 	function object.feed(self, message)
-		self.step = self.step + 1
+		self.step = self.step + 1;
 		if (self.step == 1) then
 			local challenge = serialize({	nonce = object.nonce, 
 											qop = "auth",
 											charset = "utf-8",
 											algorithm = "md5-sess",
 											realm = self.realm});
-			return "challenge", challenge
+			return "challenge", challenge;
 		elseif (self.step == 2) then
-			local response = parse(message)
+			local response = parse(message);
 			-- check for replay attack
 			if response["nc"] then
 				if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end
@@ -161,13 +161,13 @@
 			
 			-- check for username, it's REQUIRED by RFC 2831
 			if not response["username"] then
-				return "failure", "malformed-request"
+				return "failure", "malformed-request";
 			end
-			self["username"] = response["username"] 
+			self["username"] = response["username"];
 			
 			-- check for nonce, ...
 			if not response["nonce"] then
-				return "failure", "malformed-request"
+				return "failure", "malformed-request";
 			else
 				-- check if it's the right nonce
 				if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end
@@ -186,44 +186,53 @@
 			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."
+				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 = ""
+			local domain = "";
+			local protocol = "";
 			if response["digest-uri"] then
-				protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$")
+				protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$");
 				if protocol == nil or domain == nil then return "failure", "malformed-request" end
 			else
 				return "failure", "malformed-request", "Missing entry for digest-uri in SASL message."
 			end
 			
 			--TODO maybe realm support
-			self.username = response["username"]
+			self.username = response["username"];
 			local password_encoding, Y = self.password_handler(response["username"], response["realm"], "DIGEST-MD5", decoder)
 			if Y == nil then return "failure", "not-authorized"
 			elseif Y == false then return "failure", "account-disabled" end
-			
-			local A1 = Y..":"..response["nonce"]..":"..response["cnonce"]--:authzid
+			local A1 = "";
+			if response.authzid then
+				if response.authzid == self.username.."@"..self.realm then
+					log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920.");
+					A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
+				else
+					A1 = "?";
+				end
+			else
+				A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
+			end
 			local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
 			
-			local HA1 = md5(A1, true)
-			local HA2 = md5(A2, true)
+			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)
+			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)
+				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})
+				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							
@@ -232,7 +241,7 @@
 			else return "failure", "malformed-request" end
 		end
 	end
-	return object
+	return object;
 end
 
 local function new_anonymous(realm, password_handler)
--- a/util/serialization.lua	Fri May 22 13:34:47 2009 +0100
+++ b/util/serialization.lua	Fri May 22 13:35:54 2009 +0100
@@ -53,7 +53,7 @@
 		func(t, (o and "true" or "false"));
 	else
 		log("error", "cannot serialize a %s: %s", type(o), debug_traceback())
-		func(t, "nil,\n");
+		func(t, "nil");
 	end
 end
 
--- a/util/stanza.lua	Fri May 22 13:34:47 2009 +0100
+++ b/util/stanza.lua	Fri May 22 13:35:54 2009 +0100
@@ -258,10 +258,9 @@
 	return stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) });
 end
 
-function error_reply(orig, type, condition, message, clone)
+function error_reply(orig, type, condition, message)
 	local t = reply(orig);
 	t.attr.type = "error";
-	-- TODO use clone
 	t:tag("error", {type = type})
 		:tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
 	if (message) then t:tag("text"):text(message):up(); end

mercurial