Changeset

2877:1edeb8fe7d14

Merge 0.6.2/waqas with 0.6.2/MattJ
author Matthew Wild <mwild1@gmail.com>
date Wed, 03 Mar 2010 22:05:05 +0000
parents 2813:46dfcc33ea9e (current diff) 2876:fa84451e9b35 (diff)
children 2878:9384ee36fc03 2882:4e72048d4a24
files core/hostmanager.lua core/s2smanager.lua net/dns.lua net/httpserver.lua net/xmppserver_listener.lua plugins/mod_debug.lua plugins/mod_offline.lua plugins/mod_saslauth.lua plugins/mod_selftests.lua plugins/mod_tls.lua prosody
diffstat 37 files changed, 319 insertions(+), 415 deletions(-) [+]
line wrap: on
line diff
--- a/core/configmanager.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/core/configmanager.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -9,8 +9,9 @@
 
 
 local _G = _G;
-local 	setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type = 
-		setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type;
+local 	setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table, format =
+		setmetatable, loadfile, pcall, rawget, rawset, io, error, dofile, type, pairs, table, string.format;
+
 
 local eventmanager = require "core.eventmanager";
 
@@ -115,6 +116,10 @@
 		
 		rawset(env, "__currenthost", "*") -- Default is global
 		function env.Host(name)
+			if rawget(config, name) and rawget(config[name].core, "component_module") then
+				error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s",
+					name, config[name].core.component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0);
+			end
 			rawset(env, "__currenthost", name);
 			-- Needs at least one setting to logically exist :)
 			set(name or "*", "core", "defined", true);
@@ -122,6 +127,10 @@
 		env.host = env.Host;
 		
 		function env.Component(name)
+			if rawget(config, name) and rawget(config[name].core, "defined") and not rawget(config[name].core, "component_module") then
+				error(format("Component %q clashes with previously defined Host %q, for services use a sub-domain like conference.%s",
+					name, name, name), 0);
+			end
 			set(name, "core", "component_module", "component");
 			-- Don't load the global modules by default
 			set(name, "core", "load_global_modules", false);
--- a/core/hostmanager.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/core/hostmanager.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -33,12 +33,19 @@
 
 local function load_enabled_hosts(config)
 	local defined_hosts = config or configmanager.getconfig();
+	local activated_any_host;
 	
 	for host, host_config in pairs(defined_hosts) do
 		if host ~= "*" and (host_config.core.enabled == nil or host_config.core.enabled) and not host_config.core.component_module then
+			activated_any_host = true;
 			activate(host, host_config);
 		end
 	end
+	
+	if not activated_any_host then
+		log("error", "No hosts defined in the config file. This may cause unexpected behaviour as no modules will be loaded.");
+	end
+	
 	eventmanager.fire_event("hosts-activated", defined_hosts);
 	hosts_loaded_once = true;
 end
--- a/core/modulemanager.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/core/modulemanager.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -197,6 +197,15 @@
 		end
 	end
 	hooks:remove(host, name);
+	if mod.module.items then -- remove items
+		for key,t in pairs(mod.module.items) do
+			for i = #t,1,-1 do
+				local value = t[i];
+				t[i] = nil;
+				hosts[host].events.fire_event("item-removed/"..key, {source = self, item = value});
+			end
+		end
+	end
 	modulemap[host][name] = nil;
 	return true;
 end
--- a/core/s2smanager.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/core/s2smanager.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -504,6 +504,8 @@
 	end
 end
 
+local function null_data_handler(conn, data) log("debug", "Discarding data from destroyed s2s session: %s", data); end
+
 function destroy_session(session, reason)
 	(session.log or log)("info", "Destroying "..tostring(session.direction).." session "..tostring(session.from_host).."->"..tostring(session.to_host));
 	
@@ -519,6 +521,7 @@
 			session[k] = nil;
 		end
 	end
+	session.data = null_data_handler;
 end
 
 return _M;
--- a/core/sessionmanager.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/core/sessionmanager.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -10,7 +10,6 @@
 
 local tonumber, tostring = tonumber, tostring;
 local ipairs, pairs, print, next= ipairs, pairs, print, next;
-local collectgarbage = collectgarbage;
 local format = import("string", "format");
 
 local hosts = hosts;
@@ -25,6 +24,7 @@
 local rm_load_roster = require "core.rostermanager".load_roster;
 local config_get = require "core.configmanager".get;
 local nameprep = require "util.encodings".stringprep.nameprep;
+local resourceprep = require "util.encodings".stringprep.resourceprep;
 
 local fire_event = require "core.eventmanager".fire_event;
 local add_task = require "util.timer".add_task;
@@ -66,6 +66,8 @@
 	return session;
 end
 
+local function null_data_handler(conn, data) log("debug", "Discarding data from destroyed c2s session: %s", data); end
+
 function destroy_session(session, err)
 	(session.log or log)("info", "Destroying session for %s (%s@%s)", session.full_jid or "(unknown)", session.username or "(unknown)", session.host or "(unknown)");
 	
@@ -88,6 +90,7 @@
 			session[k] = nil;
 		end
 	end
+	session.data = null_data_handler;
 end
 
 function make_authenticated(session, username)
@@ -106,7 +109,8 @@
 	if session.resource then return nil, "cancel", "already-bound", "Cannot bind multiple resources on a single connection"; end
 	-- We don't support binding multiple resources
 
-	resource = resource or uuid_generate();
+	resource = resourceprep(resource);
+	resource = resource ~= "" and resource or uuid_generate();
 	--FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing
 	
 	if not hosts[session.host].sessions[session.username] then
--- a/core/stanza_router.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/core/stanza_router.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -36,12 +36,14 @@
 		end
 	end
 
-	if origin.type == "c2s" then
+	if origin.type == "c2s" and stanza.attr.xmlns == "jabber:client" then
 		if not origin.full_jid
 			and not(stanza.name == "iq" and stanza.attr.type == "set" and stanza.tags[1] and stanza.tags[1].name == "bind"
 					and stanza.tags[1].attr.xmlns == "urn:ietf:params:xml:ns:xmpp-bind") then
 			-- authenticated client isn't bound and current stanza is not a bind request
-			origin.send(st.error_reply(stanza, "auth", "not-authorized")); -- FIXME maybe allow stanzas to account or server
+			if stanza.attr.type ~= "result" and stanza.attr.type ~= "error" then
+				origin.send(st.error_reply(stanza, "auth", "not-authorized")); -- FIXME maybe allow stanzas to account or server
+			end
 			return;
 		end
 
@@ -98,7 +100,7 @@
 				return; -- FIXME what should we do here? does this work with subdomains?
 			end
 		end
-		core_post_stanza(origin, stanza);
+		core_post_stanza(origin, stanza, origin.full_jid);
 	else
 		local h = hosts[stanza.attr.to or origin.host or origin.to_host];
 		if h then
@@ -119,7 +121,7 @@
 	end
 end
 
-function core_post_stanza(origin, stanza)
+function core_post_stanza(origin, stanza, preevents)
 	local to = stanza.attr.to;
 	local node, host, resource = jid_split(to);
 	local to_bare = node and (node.."@"..host) or host; -- bare JID
@@ -143,7 +145,7 @@
 	end
 
 	local event_data = {origin=origin, stanza=stanza};
-	if origin.full_jid == stanza.attr.from then -- c2s connection
+	if preevents then -- c2s connection
 		if hosts[origin.host].events.fire_event('pre-'..stanza.name..to_type, event_data) then return; end -- do preprocessing
 	end
 	local h = hosts[to_bare] or hosts[host or origin.host];
@@ -191,6 +193,6 @@
 		log("debug", "Routing outgoing stanza for %s to %s", from_host, host);
 		send_s2s(from_host, host, stanza);
 	else
-		log("warn", "received stanza from unhandled connection type: %s", origin.type);
+		log("warn", "received %s stanza from unhandled connection type: %s", tostring(stanza.name), tostring(origin.type));
 	end
 end
--- a/man/prosodyctl.man	Fri Feb 19 03:30:27 2010 +0000
+++ b/man/prosodyctl.man	Wed Mar 03 22:05:05 2010 +0000
@@ -76,4 +76,4 @@
 More information may be found online at: \fIhttp://prosody.im/\fP
 
 .SH AUTHORS
-Dwayne Bent <dbb.0@liqd.org>
+Dwayne Bent <dbb.1@liqd.org>
--- a/net/dns.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/net/dns.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -594,17 +594,18 @@
 
 function resolver:remember(rr, type)    -- - - - - - - - - - - - - -  remember
 	--print ('remember', type, rr.class, rr.type, rr.name)
+	local qname, qtype, qclass = standardize(rr.name, rr.type, rr.class);
 
 	if type ~= '*' then
-		type = rr.type;
-		local all = get(self.cache, rr.class, '*', rr.name);
+		type = qtype;
+		local all = get(self.cache, qclass, '*', qname);
 		--print('remember all', all);
 		if all then append(all, rr); end
 	end
 
 	self.cache = self.cache or setmetatable({}, cache_metatable);
-	local rrs = get(self.cache, rr.class, type, rr.name) or
-		set(self.cache, rr.class, type, rr.name, setmetatable({}, rrs_metatable));
+	local rrs = get(self.cache, qclass, type, qname) or
+		set(self.cache, qclass, type, qname, setmetatable({}, rrs_metatable));
 	append(rrs, rr);
 
 	if type == 'MX' then self.unsorted[rrs] = true; end
--- a/net/httpserver.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/net/httpserver.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -36,8 +36,8 @@
 local function send_response(request, response)
 	-- Write status line
 	local resp;
-	if response.body then
-		local body = tostring(response.body);
+	if response.body or response.headers then
+		local body = response.body and tostring(response.body);
 		log("debug", "Sending response to %s", request.id);
 		resp = { "HTTP/1.0 ", response.status or "200 OK", "\r\n"};
 		local h = response.headers;
@@ -49,14 +49,14 @@
 				t_insert(resp, "\r\n");
 			end
 		end
-		if not (h and h["Content-Length"]) then
+		if body and not (h and h["Content-Length"]) then
 			t_insert(resp, "Content-Length: ");
 			t_insert(resp, #body);
 			t_insert(resp, "\r\n");
 		end
 		t_insert(resp, "\r\n");
 		
-		if request.method ~= "HEAD" then
+		if body and request.method ~= "HEAD" then
 			t_insert(resp, body);
 		end
 	else
@@ -147,22 +147,29 @@
 	elseif request.state == "headers" then
 		log("debug", "Reading headers...")
 		local pos = startpos;
-		local headers = request.headers or {};
+		local headers, headers_complete = request.headers;
+		if not headers then
+			headers = {};
+			request.headers = headers;
+		end
+		
 		for line in data:gmatch("(.-)\r\n") do
 			startpos = (startpos or 1) + #line + 2;
 			local k, v = line:match("(%S+): (.+)");
 			if k and v then
 				headers[k:lower()] = v;
---				log("debug", "Header: "..k:lower().." = "..v);
+				--log("debug", "Header: '"..k:lower().."' = '"..v.."'");
 			elseif #line == 0 then
-				request.headers = headers;
+				headers_complete = true;
 				break;
 			else
 				log("debug", "Unhandled header line: "..line);
 			end
 		end
 		
-		if not expectbody(request) then 
+		if not headers_complete then return; end
+		
+		if not expectbody(request) then
 			call_callback(request);
 			return;
 		end
@@ -176,7 +183,10 @@
 		log("debug", "Reading request line...")
 		local method, path, http, linelen = data:match("^(%S+) (%S+) HTTP/(%S+)\r\n()", startpos);
 		if not method then
-			return call_callback(request, "invalid-status-line");
+			log("warn", "Invalid HTTP status line, telling callback then closing");
+			local ret = call_callback(request, "invalid-status-line");
+			request:destroy();
+			return ret;
 		end
 		
 		request.method, request.path, request.httpversion = method, path, http;
--- a/net/xmppclient_listener.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/net/xmppclient_listener.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -140,7 +140,6 @@
 		sm_destroy_session(session, err);
 		sessions[conn]  = nil;
 		session = nil;
-		collectgarbage("collect");
 	end
 end
 
--- a/net/xmppcomponent_listener.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/net/xmppcomponent_listener.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -169,7 +169,6 @@
 		sessions[conn]  = nil;
 		for k in pairs(session) do session[k] = nil; end
 		session = nil;
-		collectgarbage("collect");
 	end
 end
 
--- a/net/xmppserver_listener.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/net/xmppserver_listener.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -162,7 +162,6 @@
 		s2s_destroy_session(session, err);
 		sessions[conn]  = nil;
 		session = nil;
-		collectgarbage("collect");
 	end
 end
 
--- a/plugins/mod_bosh.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/plugins/mod_bosh.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -152,7 +152,7 @@
 		local r, send_buffer = session.requests, session.send_buffer;
 		local response = { headers = default_headers }
 		function session.send(s)
-			log("debug", "Sending BOSH data: %s", tostring(s));
+			--log("debug", "Sending BOSH data: %s", tostring(s));
 			local oldest_request = r[1];
 			while oldest_request and oldest_request.destroyed do
 				t_remove(r, 1);
@@ -160,7 +160,7 @@
 				oldest_request = r[1];
 			end
 			if oldest_request then
-				log("debug", "We have an open request, so using that to send with");
+				log("debug", "We have an open request, so sending on that");
 				response.body = t_concat{"<body xmlns='http://jabber.org/protocol/httpbind' sid='", sid, "' xmlns:stream = 'http://etherx.jabber.org/streams'>", tostring(s), "</body>" };
 				oldest_request:send(response);
 				--log("debug", "Sent");
--- a/plugins/mod_component.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/plugins/mod_component.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -44,7 +44,7 @@
 	
 	local secret = config.get(session.user, "core", "component_secret");
 	if not secret then
-		(session.log or log)("warn", "Component attempted to identify as %s, but component_password is not set", session.user);
+		(session.log or log)("warn", "Component attempted to identify as %s, but component_secret is not set", session.user);
 		session:close("not-authorized");
 		return;
 	end
--- a/plugins/mod_compression.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/plugins/mod_compression.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -14,10 +14,10 @@
 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));
@@ -41,7 +41,7 @@
 			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.");
+				session.log("warn", "Tried to establish another compression layer.");
 			end
 			
 			-- checking if the compression method is supported
@@ -56,7 +56,7 @@
 				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.");
+					session.log("error", "Failed to create zlib.deflate filter.");
 					module:log("error", deflate_stream);
 					return
 				end
@@ -65,7 +65,7 @@
 				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.");
+					session.log("error", "Failed to create zlib.deflate filter.");
 					module:log("error", inflate_stream);
 					return
 				end
--- a/plugins/mod_console.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/plugins/mod_console.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -192,7 +192,7 @@
 	elseif section == "server" then
 		print [[server:version() - Show the server's version number]]
 		print [[server:uptime() - Show how long the server has been running]]
-		--print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]]
+		print [[server:shutdown(reason) - Shut down the server, with an optional reason to be broadcast to all connections]]
 	elseif section == "config" then
 		print [[config:reload() - Reload the server configuration. Modules may need to be reloaded for changes to take effect.]]
 	elseif section == "console" then
--- a/plugins/mod_debug.lua	Fri Feb 19 03:30:27 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,191 +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.
---
-
-module.host = "*";
-
-local connlisteners_register = require "net.connlisteners".register;
-
-local console_listener = { default_port = 5583; default_mode = "*l"; default_interface = "127.0.0.1" };
-
-local sha256, missingglobal = require "util.hashes".sha256;
-
-local commands = {};
-local debug_env = {};
-local debug_env_mt = { __index = function (t, k) return rawget(_G, k) or missingglobal(k); end, __newindex = function (t, k, v) rawset(_G, k, v); end };
-
-local t_insert, t_concat = table.insert, table.concat;
-local t_concatall = function (t, sep) local tt = {}; for k, s in pairs(t) do tt[k] = tostring(s); end return t_concat(tt, sep); end
-
-
-setmetatable(debug_env, debug_env_mt);
-
-console = {};
-
-function console:new_session(conn)
-	local w = function(s) conn.write(s:gsub("\n", "\r\n")); end;
-	local session = { conn = conn;
-			send = function (t) w(tostring(t)); end;
-			print = function (t) w("| "..tostring(t).."\n"); end;
-			disconnect = function () conn.close(); end;
-			};
-	
-	return session;
-end
-
-local sessions = {};
-
-function console_listener.listener(conn, data)
-	local session = sessions[conn];
-	
-	if not session then
-		-- Handle new connection
-		session = console:new_session(conn);
-		sessions[conn] = session;
-		printbanner(session);
-	end
-	if data then
-		-- Handle data
-		(function(session, data)
-			if data:match("[!.]$") then
-				local command = data:lower();
-				command = data:match("^%w+") or data:match("%p");
-				if commands[command] then
-					commands[command](session, data);
-					return;
-				end
-			end
-			
-			local chunk, err = loadstring("return "..data);
-			if not chunk then
-				chunk, err = loadstring(data);
-				if not chunk then
-					err = err:gsub("^%[string .-%]:%d+: ", "");
-					err = err:gsub("^:%d+: ", "");
-					err = err:gsub("'<eof>'", "the end of the line");
-					session.print("Sorry, I couldn't understand that... "..err);
-					return;
-				end
-			end
-			
-			debug_env.print = session.print;
-			
-			setfenv(chunk, debug_env);
-			
-			local ret = { pcall(chunk) };
-			
-			if not ret[1] then
-				session.print("Fatal error while running command, it did not complete");
-				session.print("Error: "..ret[2]);
-				return;
-			end
-			
-			table.remove(ret, 1);
-			
-			local retstr = t_concatall(ret, ", ");
-			if retstr ~= "" then
-				session.print("Result: "..retstr);
-			else
-				session.print("No result, or nil");
-				return;
-			end
-		end)(session, data);
-	end
-	session.send(string.char(0));
-end
-
-function console_listener.disconnect(conn, err)
-	
-end
-
-connlisteners_register('debug', console_listener);
-require "net.connlisteners".start("debug");
-
--- Console commands --
--- These are simple commands, not valid standalone in Lua
-
-function commands.bye(session)
-	session.print("See you! :)");
-	session.disconnect();
-end
-
-commands["!"] = function (session, data)
-	if data:match("^!!") then
-		session.print("!> "..session.env._);
-		return console_listener.listener(session.conn, session.env._);
-	end
-	local old, new = data:match("^!(.-[^\\])!(.-)!$");
-	if old and new then
-		local ok, res = pcall(string.gsub, session.env._, old, new);
-		if not ok then
-			session.print(res)
-			return;
-		end
-		session.print("!> "..res);
-		return console_listener.listener(session.conn, res);
-	end
-	session.print("Sorry, not sure what you want");
-end
-
-function printbanner(session)
-session.print [[
-                   ____                \   /     _       
-                    |  _ \ _ __ ___  ___  _-_   __| |_   _ 
-                    | |_) | '__/ _ \/ __|/ _ \ / _` | | | |
-                    |  __/| | | (_) \__ \ |_| | (_| | |_| |
-                    |_|   |_|  \___/|___/\___/ \__,_|\__, |
-                    A study in simplicity            |___/ 
-
-]]
-session.print("Welcome to the Prosody debug console. For a list of commands, type: help");
-session.print("You may find more help on using this console in our online documentation at ");
-session.print("http://prosody.im/doc/debugconsole\n");
-end
-
-local byte, char = string.byte, string.char;
-local gmatch, gsub = string.gmatch, string.gsub;
-
-local function vdecode(text, key)
-	local keyarr = {};
-	for l in gmatch(key, ".") do t_insert(keyarr, byte(l) - 32) end
-	local pos, keylen = 0, #keyarr;
-	return (gsub(text, ".",	function (letter)
-							if byte(letter) < 32 then return ""; end
-							pos = (pos%keylen)+1;
-							return char(((byte(letter) - 32 - keyarr[pos]) % 94) + 32);
-						end));
-end
-
-local subst = {
-	["f880c08056ba7dbecb1ccfe5d7728bd6dcd654e94f7a9b21788c43397bae0bc5"] =
-		[=[nRYeKR$l'5Ix%u*1Mc-K}*bwv*\ $1KLMBd$KH R38`$[6}VQ@,6Qn]=];
-	["92f718858322157202ec740698c1390e47bc819e52b6a099c54c378a9f7529d6"] =
-		[=[V\Z5`WZ5,T$<)7LM'w3Z}M(7V'{pa) &'>0+{v)O(0M*V5K$$LL$|2wT}6
-		 1as*")e!>]=];
-	["467b65edcc7c7cd70abf2136cc56abd037216a6cd9e17291a2219645be2e2216"] =
-		[=[i#'Z,E1-"YaHW(j/0xs]I4x&%(Jx1h&18'(exNWT D3b+K{*8}w(%D {]=];
-	["f73729d7f2fbe686243a25ac088c7e6aead3d535e081329f2817438a5c78bee5"] =
-		[=[,3+(Q{3+W\ftQ%wvv/C0z-l%f>ABc(vkp<bb8]=];
-	["6afa189489b096742890d0c5bd17d5bb8af8ac460c7026984b64e8f14a40404e"] =
-		[=[9N{)5j34gd*}&]H&dy"I&7(",a F1v6jY+IY7&S+86)1z(Vo]=];
-	["cc5e5293ef8a1acbd9dd2bcda092c5c77ef46d3ec5aea65024fca7ed4b3c94a9"] = 
-		[=[_]Rc}IF'Kfa&))Ry+6|x!K2|T*Vze)%4Hwz'L3uI|OwIa)|q#uq2+Qu u7
-		[V3(z(*TYY|T\1_W'2] Dwr{-{@df#W.H5^x(ydtr{c){UuV@]=];
-	["b3df231fd7ddf73f72f39cb2510b1fe39318f4724728ed58948a180663184d3e"] =
-		[=[iH!"9NLS'%geYw3^R*fvWM1)MwxLS!d[zP(p0sQ|8tX{dWO{9w!+W)b"MU
-		W)V8&(2Wx"'dTL9*PP%1"JV(I|Jr1^f'-Hc3U\2H3Z='K#,)dPm]=];
-	}
-
-function missingglobal(name)
-	if sha256 then
-		local hash = sha256(name.."|"..name:reverse(), true);
-		
-		if subst[hash] then
-			return vdecode(subst[hash], sha256(name:reverse(), true));
-		end
-	end
-end
--- a/plugins/mod_offline.lua	Fri Feb 19 03:30:27 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +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 st = require "util.stanza";
-local datetime = require "util.datetime";
-local ipairs = ipairs;
-local jid_split = require "util.jid".split;
-
-module:add_feature("msgoffline");
-
-module:hook("message/offline/store", function(event)
-	local origin, stanza = event.origin, event.stanza;
-	local to = stanza.attr.to;
-	local node, host;
-	if to then
-		node, host = jid_split(to)
-	else
-		node, host = origin.username, origin.host;
-	end
-	
-	stanza.attr.stamp, stanza.attr.stamp_legacy = datetime.datetime(), datetime.legacy();
-	local result = datamanager.list_append(node, host, "offline", st.preserialize(stanza));
-	stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
-	
-	return true;
-end);
-
-module:hook("message/offline/broadcast", function(event)
-	local origin = event.origin;
-	local node, host = origin.username, origin.host;
-	
-	local data = datamanager.list_load(node, host, "offline");
-	if not data then return true; end
-	for _, stanza in ipairs(data) do
-		stanza = st.deserialize(stanza);
-		stanza:tag("delay", {xmlns = "urn:xmpp:delay", from = host, stamp = stanza.attr.stamp}):up(); -- XEP-0203
-		stanza:tag("x", {xmlns = "jabber:x:delay", from = host, stamp = stanza.attr.stamp_legacy}):up(); -- XEP-0091 (deprecated)
-		stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
-		origin.send(stanza);
-	end
-	return true;
-end);
-
-module:hook("message/offline/delete", function(event)
-	local origin = event.origin;
-	local node, host = origin.username, origin.host;
-
-	return datamanager.list_store(node, host, "offline", nil);
-end);
--- a/plugins/mod_pep.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/plugins/mod_pep.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -37,9 +37,16 @@
 module:add_identity("pubsub", "pep", "Prosody");
 module:add_feature("http://jabber.org/protocol/pubsub#publish");
 
-local function publish(session, node, item)
+local function subscription_presence(user_bare, recipient)
+	local recipient_bare = jid_bare(recipient);
+	if (recipient_bare == user_bare) then return true end
+	local item = load_roster(jid_split(user_bare))[recipient_bare];
+	return item and (item.subscription == 'from' or item.subscription == 'both');
+end
+
+local function publish(session, node, id, item)
 	item.attr.xmlns = nil;
-	local disable = #item.tags ~= 1 or #item.tags[1].tags == 0;
+	local disable = #item.tags ~= 1 or #item.tags[1] == 0;
 	if #item.tags == 0 then item.name = "retract"; end
 	local bare = session.username..'@'..session.host;
 	local stanza = st.message({from=bare, type='headline'})
@@ -58,9 +65,9 @@
 		end
 	else
 		if not user_data then user_data = {}; data[bare] = user_data; end
-		user_data[node] = stanza;
+		user_data[node] = {id or "1", item};
 	end
-	
+
 	-- broadcast
 	for recipient, notify in pairs(recipients[bare] or NULL) do
 		if notify[node] then
@@ -74,10 +81,14 @@
 	local notify = recipients[user] and recipients[user][recipient];
 	if d and notify then
 		for node in pairs(notify) do
-			local message = d[node];
-			if message then
-				message.attr.to = recipient;
-				session.send(message);
+			if d[node] then
+				local id, item = unpack(d[node]);
+				session.send(st.message({from=user, to=recipient, type='headline'})
+					:tag('event', {xmlns='http://jabber.org/protocol/pubsub#event'})
+						:tag('items', {node=node})
+							:add_child(item)
+						:up()
+					:up());
 			end
 		end
 	end
@@ -106,11 +117,9 @@
 module:hook("presence/bare", function(event)
 	-- inbound presence to bare JID recieved
 	local origin, stanza = event.origin, event.stanza;
-	
 	local user = stanza.attr.to or (origin.username..'@'..origin.host);
-	local bare = jid_bare(stanza.attr.from);
-	local item = load_roster(jid_split(user))[bare];
-	if not stanza.attr.to or (item and (item.subscription == 'from' or item.subscription == 'both')) then
+
+	if not stanza.attr.to or subscription_presence(user, stanza.attr.from) then
 		local recipient = stanza.attr.from;
 		local current = recipients[user] and recipients[user][recipient];
 		local hash = get_caps_hash_from_presence(stanza, current);
@@ -135,19 +144,63 @@
 
 module:hook("iq/bare/http://jabber.org/protocol/pubsub:pubsub", function(event)
 	local session, stanza = event.origin, event.stanza;
+	local payload = stanza.tags[1];
+
 	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 -- <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+		payload = payload.tags[1];
+		if payload and (payload.name == 'publish' or payload.name == 'retract') and payload.attr.node then -- <publish node='http://jabber.org/protocol/tune'>
+			local node = payload.attr.node;
+			payload = payload.tags[1];
+			if payload and payload.name == "item" then -- <item>
+				local id = payload.attr.id;
+				session.send(st.reply(stanza));
+				publish(session, node, id, st.clone(payload));
+				return true;
+			end
+		end
+	elseif stanza.attr.type == 'get' then
+		local user = stanza.attr.to and jid_bare(stanza.attr.to) or session.username..'@'..session.host;
+		if subscription_presence(user, stanza.attr.from) then
+			local user_data = data[user];
+			local node, requested_id;
 			payload = payload.tags[1];
-			if payload and (payload.name == 'publish' or payload.name == 'retract') and payload.attr.node then -- <publish node='http://jabber.org/protocol/tune'>
-				local node = payload.attr.node;
-				payload = payload.tags[1];
-				if payload and payload.name == "item" then -- <item>
-					session.send(st.reply(stanza));
-					publish(session, node, st.clone(payload));
+			if payload and payload.name == 'items' then
+				node = payload.attr.node;
+				local item = payload.tags[1];
+				if item and item.name == "item" then
+					requested_id = item.attr.id;
+				end
+			end
+			if node and user_data and user_data[node] then -- Send the last item
+				local id, item = unpack(user_data[node]);
+				if not requested_id or id == requested_id then
+					local stanza = st.reply(stanza)
+						:tag('pubsub', {xmlns='http://jabber.org/protocol/pubsub'})
+							:tag('items', {node=node})
+								:add_child(item)
+							:up()
+						:up();
+					session.send(stanza);
+					return true;
+				else -- requested item doesn't exist
+					local stanza = st.reply(stanza)
+						:tag('pubsub', {xmlns='http://jabber.org/protocol/pubsub'})
+							:tag('items', {node=node})
+						:up();
+					session.send(stanza);
 					return true;
 				end
+			elseif node then -- node doesn't exist
+				session.send(st.error_reply(stanza, 'cancel', 'item-not-found'));
+				return true;
+			else --invalid request
+				session.send(st.error_reply(stanza, 'modify', 'bad-request'));
+				return true;
 			end
+		else --no presence subscription
+			session.send(st.error_reply(stanza, 'auth', 'not-authorized')
+				:tag('presence-subscription-required', {xmlns='http://jabber.org/protocol/pubsub#errors'}));
+			return true;
 		end
 	end
 end);
--- a/plugins/mod_presence.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/plugins/mod_presence.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -18,6 +18,7 @@
 local jid_split = require "util.jid".split;
 local jid_bare = require "util.jid".bare;
 local hosts = hosts;
+local NULL = {};
 
 local rostermanager = require "core.rostermanager";
 local sessionmanager = require "core.sessionmanager";
@@ -54,16 +55,21 @@
 	end
 	return recipients;
 end
-local function recalc_resource_map(origin)
-	local user = hosts[origin.host].sessions[origin.username];
-	user.top_resources = select_top_resources(user);
-	if #user.top_resources == 0 then user.top_resources = nil; end
+local function recalc_resource_map(user)
+	if user then
+		user.top_resources = select_top_resources(user);
+		if #user.top_resources == 0 then user.top_resources = nil; end
+	end
 end
 
 function handle_normal_presence(origin, stanza, core_route_stanza)
+	if full_sessions[origin.full_jid] then -- if user is still connected
+		origin.send(stanza); -- reflect their presence back to them
+	end
 	local roster = origin.roster;
 	local node, host = origin.username, origin.host;
-	for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast to all resources
+	local user = bare_sessions[node.."@"..host];
+	for _, res in pairs(user and user.sessions or NULL) do -- broadcast to all resources
 		if res ~= origin and res.presence then -- to resource
 			stanza.attr.to = res.full_jid;
 			core_route_stanza(origin, stanza);
@@ -76,6 +82,7 @@
 		end
 	end
 	if stanza.attr.type == nil and not origin.presence then -- initial presence
+		origin.presence = stanza; -- FIXME repeated later
 		local probe = st.presence({from = origin.full_jid, type = "probe"});
 		for jid, item in pairs(roster) do -- probe all contacts we are subscribed to
 			if item.subscription == "both" or item.subscription == "to" then
@@ -83,7 +90,7 @@
 				core_route_stanza(origin, probe);
 			end
 		end
-		for _, res in pairs(hosts[host].sessions[node].sessions) do -- broadcast from all available resources
+		for _, res in pairs(user and user.sessions or NULL) do -- broadcast from all available resources
 			if res ~= origin and res.presence then
 				res.presence.attr.to = origin.full_jid;
 				core_route_stanza(res, res.presence);
@@ -114,7 +121,7 @@
 		origin.presence = nil;
 		if origin.priority then
 			origin.priority = nil;
-			recalc_resource_map(origin);
+			recalc_resource_map(user);
 		end
 		if origin.directed then
 			for jid in pairs(origin.directed) do
@@ -136,7 +143,7 @@
 		else priority = 0; end
 		if origin.priority ~= priority then
 			origin.priority = priority;
-			recalc_resource_map(origin);
+			recalc_resource_map(user);
 		end
 	end
 	stanza.attr.to = nil; -- reset it
@@ -217,7 +224,7 @@
 	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, st_from, origin, core_route_stanza) then
-				-- TODO send last recieved unavailable presence (or we MAY do nothing, which is fine too)
+				core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- TODO send last activity
 			end
 		else
 			core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unsubscribed"}));
@@ -227,7 +234,7 @@
 			core_route_stanza(hosts[host], 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)
+				core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- TODO send last activity
 			end
 		else
 			core_route_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"})); -- acknowledging receipt
@@ -326,6 +333,20 @@
 	end -- resource not online, discard
 	return true;
 end);
+module:hook("presence/host", function(data)
+	-- inbound presence to the host
+	local origin, stanza = data.origin, data.stanza;
+	
+	local from_bare = jid_bare(stanza.attr.from);
+	local t = stanza.attr.type;
+	if t == "probe" then
+		core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
+	elseif t == "subscribe" then
+		core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id, type = "subscribed" }));
+		core_route_stanza(hosts[module.host], st.presence({ from = module.host, to = from_bare, id = stanza.attr.id }));
+	end
+	return true;
+end);
 
 module:hook("resource-unbind", function(event)
 	local session, err = event.session, event.error;
--- a/plugins/mod_register.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/plugins/mod_register.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -141,7 +141,7 @@
 					username = nodeprep(table.concat(username));
 					password = table.concat(password);
 					local host = module.host;
-					if not username then
+					if not username or username == "" then
 						session.send(st.error_reply(stanza, "modify", "not-acceptable", "The requested username is invalid."));
 					elseif usermanager_user_exists(username, host) then
 						session.send(st.error_reply(stanza, "cancel", "conflict", "The requested username already exists."));
--- a/plugins/mod_saslauth.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/plugins/mod_saslauth.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -38,13 +38,13 @@
 local function build_reply(status, ret, err_msg)
 	local reply = st.stanza(status, {xmlns = xmlns_sasl});
 	if status == "challenge" then
-		log("debug", "%s", ret or "");
+		--log("debug", "CHALLENGE: %s", ret or "");
 		reply:text(base64.encode(ret or ""));
 	elseif status == "failure" then
 		reply:tag(ret):up();
 		if err_msg then reply:tag("text"):text(err_msg); end
 	elseif status == "success" then
-		log("debug", "%s", ret or "");
+		--log("debug", "SUCCESS: %s", ret or "");
 		reply:text(base64.encode(ret or ""));
 	else
 		module:log("error", "Unknown sasl status: %s", status);
@@ -124,7 +124,7 @@
 	local text = stanza[1];
 	if text then
 		text = base64.decode(text);
-		log("debug", "%s", text);
+		--log("debug", "AUTH: %s", text:gsub("[%z\001-\008\011\012\014-\031]", " "));
 		if not text then
 			session.sasl_handler = nil;
 			session.send(build_reply("failure", "incorrect-encoding"));
--- a/plugins/mod_selftests.lua	Fri Feb 19 03:30:27 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +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.
---
-
-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 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 = {};
-
-local t_insert = table.insert;
-
-local log = require "util.logger".init("mod_selftests");
-
-local tests_jid = "self_tests@getjabber.ath.cx";
-local host = "getjabber.ath.cx";
-
-if not (tests_jid and host) then
-	for currhost in pairs(host) do
-		if currhost ~= "localhost" then
-			tests_jid, host = "self_tests@"..currhost, currhost;
-		end
-	end
-end
-
-if tests_jid and host then
-	local bot = register_component(tests_jid, 	function(origin, stanza, ourhost)
-										local time = open_pings[stanza.attr.id];
-										
-										if time then
-											log("info", "Ping reply from %s in %fs", tostring(stanza.attr.from), socket.gettime() - time);
-										else
-											log("info", "Unexpected reply: %s", stanza:pretty_print());
-										end
-									end);
-
-
-	local our_origin = hosts[host];
-	module:add_event_hook("server-started", 
-					function ()
-						local id = st.new_id();
-						local ping_attr = { xmlns = 'urn:xmpp:ping' };
-						local function send_ping(to)
-							log("info", "Sending ping to %s", to);
-							core_route_stanza(our_origin, st.iq{ to = to, from = tests_jid, id = id, type = "get" }:tag("ping", ping_attr));
-							open_pings[id] = socket.gettime();
-						end
-						
-						for _, host in ipairs(ping_hosts) do
-							send_ping(host);
-						end
-					end);
-end
--- a/plugins/mod_tls.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/plugins/mod_tls.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -14,9 +14,11 @@
 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");
 
+local host = hosts[module.host];
+
 module:add_handler("c2s_unauthed", "starttls", xmlns_starttls,
 		function (session, stanza)
-			if session.conn.starttls then
+			if session.conn.starttls and host.ssl_ctx_in then
 				session.send(st.stanza("proceed", { xmlns = xmlns_starttls }));
 				session:reset_stream();
 				if session.host and hosts[session.host].ssl_ctx_in then
@@ -26,14 +28,15 @@
 				session.log("info", "TLS negotiation started...");
 				session.secure = false;
 			else
-				-- FIXME: What reply?
 				session.log("warn", "Attempt to start TLS, but TLS is not available on this connection");
+				(session.sends2s or session.send)(st.stanza("failure", { xmlns = xmlns_starttls }));
+				session:close();
 			end
 		end);
 		
 module:add_handler("s2sin_unauthed", "starttls", xmlns_starttls,
 		function (session, stanza)
-			if session.conn.starttls then
+			if session.conn.starttls and host.ssl_ctx_in then
 				session.sends2s(st.stanza("proceed", { xmlns = xmlns_starttls }));
 				session:reset_stream();
 				if session.to_host and hosts[session.to_host].ssl_ctx_in then
@@ -43,8 +46,9 @@
 				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");
+				(session.sends2s or session.send)(st.stanza("failure", { xmlns = xmlns_starttls }));
+				session:close();
 			end
 		end);
 
@@ -66,7 +70,7 @@
 		function (data)
 			local session, features = data.session, data.features;
 			if session.to_host and session.conn.starttls then
-				features:tag("starttls", starttls_attr):up();
+				features:tag("starttls", starttls_attr);
 				if secure_s2s_only then
 					features:tag("required"):up():up();
 				else
--- a/plugins/muc/muc.lib.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/plugins/muc/muc.lib.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -128,19 +128,21 @@
 	end
 end
 function room_mt:broadcast_message(stanza, historic)
+	local to = stanza.attr.to;
 	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
+	stanza.attr.to = to;
 	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 = 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)));
+		t_insert(history, st.preserialize(stanza));
 		while #history > history_length do t_remove(history, 1) end
 	end
 end
@@ -461,6 +463,9 @@
 					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
+					elseif not item.attr.nick and item.attr.jid then
+						local nick = self._jid_nick[item.attr.jid];
+						if nick then item.attr.nick = select(3, jid_split(nick)); end
 					end
 					local reason = item.tags[1] and item.tags[1].name == "reason" and #item.tags[1] == 1 and item.tags[1][1];
 					if item.attr.affiliation and item.attr.jid and not item.attr.role then
@@ -492,9 +497,14 @@
 							-- 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
+							for occupant_jid, 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();
+									reply:tag("item", {
+										nick = select(3, jid_split(occupant_jid)),
+										role = _rol or "none",
+										affiliation = occupant.affiliation or "none",
+										jid = occupant.jid
+										}):up();
 								end
 							end
 							origin.send(reply);
@@ -517,17 +527,26 @@
 		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
+		local occupant = self._occupants[current_nick];
+		if not occupant then -- not in room
 			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+		elseif occupant.role == "visitor" then
+			origin.send(st.error_reply(stanza, "cancel", "forbidden"));
 		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
+				if occupant.role == "moderator" then
+					self:set_subject(current_nick, subject); -- TODO use broadcast_message_stanza
+				else
+					stanza.attr.from = from;
+					origin.send(st.error_reply(stanza, "cancel", "forbidden"));
+				end
 			else
 				self:broadcast_message(stanza, true);
 			end
+			stanza.attr.from = from;
 		end
 	elseif stanza.name == "message" and type == "error" and is_kickable_error(stanza) then
 		local current_nick = self._jid_nick[stanza.attr.from];
@@ -651,21 +670,21 @@
 	local session = self._occupants[nick];
 	return session and session.role or nil;
 end
-function room_mt:set_role(actor, nick, role, callback, reason)
+function room_mt:set_role(actor, occupant_jid, role, callback, reason)
 	if role == "none" then role = nil; end
 	if role and role ~= "moderator" and role ~= "participant" and role ~= "visitor" then return nil, "modify", "not-acceptable"; end
 	if self:get_affiliation(actor) ~= "owner" then return nil, "cancel", "not-allowed"; end
-	local occupant = self._occupants[nick];
+	local occupant = self._occupants[occupant_jid];
 	if not occupant then return nil, "modify", "not-acceptable"; end
 	if occupant.affiliation == "owner" or occupant.affiliation == "admin" then return nil, "cancel", "not-allowed"; end
-	local p = st.presence({from = nick})
+	local p = st.presence({from = occupant_jid})
 		:tag("x", {xmlns = "http://jabber.org/protocol/muc#user"})
-			:tag("item", {affiliation=occupant.affiliation or "none", nick=nick, role=role or "none"})
+			:tag("item", {affiliation=occupant.affiliation or "none", nick=select(3, jid_split(occupant_jid)), role=role or "none"})
 				:tag("reason"):text(reason or ""):up()
 			:up();
 	if not role then -- kick
 		p.attr.type = "unavailable";
-		self._occupants[nick] = nil;
+		self._occupants[occupant_jid] = nil;
 		for jid in pairs(occupant.sessions) do -- remove for all sessions of the nick
 			self._jid_nick[jid] = nil;
 		end
@@ -678,7 +697,7 @@
 		self:_route_stanza(p);
 	end
 	if callback then callback(); end
-	self:broadcast_except_nick(p, nick);
+	self:broadcast_except_nick(p, occupant_jid);
 	return true;
 end
 
--- a/prosody	Fri Feb 19 03:30:27 2010 +0000
+++ b/prosody	Wed Mar 03 22:05:05 2010 +0000
@@ -198,7 +198,7 @@
 				if type(port) ~= "number" then
 					log("error", "Non-numeric "..option.."_ports: "..tostring(port));
 				else
-					cl.start(listener, { 
+					local ok, err = cl.start(listener, {
 						ssl = conntype ~= "tcp" and global_ssl_ctx,
 						port = port,
 						interface = (option and config.get("*", "core", option.."_interface"))
@@ -206,6 +206,33 @@
 							or config.get("*", "core", "interface"),
 						type = conntype
 					});
+					if not ok then
+						local friendly_message = err;
+						if err:match(" in use") then
+							if port == 5222 or port == 5223 or port == 5269 then
+								friendly_message = "check that Prosody or another XMPP server is "
+									.."not already running and using this port";
+							elseif port == 80 or port == 81 then
+								friendly_message = "check that a HTTP server is not already using "
+									.."this port";
+							elseif port == 5280 then
+								friendly_message = "check that Prosody or a BOSH connection manager "
+									.."is not already running";
+							else
+								friendly_message = "this port is in use by another application";
+							end
+						elseif err:match("permission") then
+							friendly_message = "Prosody does not have sufficient privileges to use this port";
+						elseif err == "no ssl context" then
+							if not config.get("*", "core", "ssl") then
+								friendly_message = "there is no 'ssl' config under Host \"*\" which is "
+									.."require for legacy SSL ports";
+							else
+								friendly_message = "initializing SSL support failed, see previous log entries";
+							end
+						end
+						log("error", "Failed to open server port %d, %s", port, friendly_message);
+					end
 				end
 			end
 		end
@@ -280,15 +307,18 @@
 	-- start listening on sockets
 	prosody.net_activate_ports("c2s", "xmppclient", {5222});
 	prosody.net_activate_ports("s2s", "xmppserver", {5269});
-	prosody.net_activate_ports("component", "xmppcomponent", {}, "tcp");
+	prosody.net_activate_ports("component", "xmppcomponent", {5347}, "tcp");
 	prosody.net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl");
 
 	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 }
+	-- Catch global accesses
+	local locked_globals_mt = {
+		__index = function (t, k) log("warn", "%s", debug.traceback("Attempt to read a non-existent global '"..tostring(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);
--- a/prosody.cfg.lua.dist	Fri Feb 19 03:30:27 2010 +0000
+++ b/prosody.cfg.lua.dist	Wed Mar 03 22:05:05 2010 +0000
@@ -63,7 +63,7 @@
 
 			-- Other specific functionality
 				--"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
-			  	--"console"; -- telnet to port 5582 (needs console_enabled = true)
+			  	--"console"; -- Opens admin telnet interface on localhost port 5582
 				--"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
 				--"httpserver"; -- Serve static files from a directory over HTTP
 			  };
--- a/tests/test_util_jid.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/tests/test_util_jid.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -6,6 +6,16 @@
 -- COPYING file in the source package for more information.
 --
 
+function join(join)
+	assert_equal(join("a", "b", "c"), "a@b/c", "builds full JID");
+	assert_equal(join("a", "b", nil), "a@b", "builds bare JID");
+	assert_equal(join(nil, "b", "c"), "b/c", "builds full host JID");
+	assert_equal(join(nil, "b", nil), "b", "builds bare host JID");
+	assert_equal(join(nil, nil, nil), nil, "invalid JID is nil");
+	assert_equal(join("a", nil, nil), nil, "invalid JID is nil");
+	assert_equal(join(nil, nil, "c"), nil, "invalid JID is nil");
+	assert_equal(join("a", nil, "c"), nil, "invalid JID is nil");
+end
 
 
 function split(split)
@@ -43,3 +53,4 @@
 	assert_equal(bare("user@@host/resource"), nil, "invalid JID is nil");
 	assert_equal(bare("user@host/"), nil, "invalid JID is nil");
 end
+
--- a/tools/ejabberd2prosody.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/tools/ejabberd2prosody.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -49,7 +49,7 @@
 end
 function password(node, host, password)
 	local ret, err = dm.store(node, host, "accounts", {password = password});
-	print("["..(err or "success").."] accounts: "..node.."@"..host.." = "..password);
+	print("["..(err or "success").."] accounts: "..node.."@"..host);
 end
 function roster(node, host, jid, item)
 	local roster = dm.load(node, host, "roster") or {};
--- a/tools/ejabberdsql2prosody.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/tools/ejabberdsql2prosody.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -254,7 +254,7 @@
 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);
+	print("["..(err or "success").."] accounts: "..node.."@"..host);
 end
 
 function roster(node, host, jid, item)
--- a/util-src/encodings.c	Fri Feb 19 03:30:27 2010 +0000
+++ b/util-src/encodings.c	Wed Mar 03 22:05:05 2010 +0000
@@ -174,7 +174,7 @@
 	size_t len;
 	const char *s = luaL_checklstring(L, 1, &len);
 	char* output = NULL;
-	int ret = idna_to_ascii_8z(s, &output, 0);
+	int ret = idna_to_ascii_8z(s, &output, IDNA_USE_STD3_ASCII_RULES);
 	if (ret == IDNA_SUCCESS) {
 		lua_pushstring(L, output);
 		idn_free(output);
--- a/util-src/pposix.c	Fri Feb 19 03:30:27 2010 +0000
+++ b/util-src/pposix.c	Wed Mar 03 22:05:05 2010 +0000
@@ -463,9 +463,10 @@
 	return 3;
 }
 
-void lc_abort(lua_State* L)
+int lc_abort(lua_State* L)
 {
 	abort();
+	return 0;
 }
 
 /* Register functions */
--- a/util/dataforms.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/util/dataforms.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -23,8 +23,8 @@
 	return setmetatable(layout, form_mt);
 end
 
-function form_t.form(layout, data)
-	local form = st.stanza("x", { xmlns = xmlns_forms, type = "form" });
+function form_t.form(layout, data, formtype)
+	local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype or "form" });
 	if layout.title then
 		form:tag("title"):text(layout.title):up();
 	end
--- a/util/dependencies.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/util/dependencies.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -17,8 +17,12 @@
 	print("Prosody was unable to find "..tostring(name));
 	print("This package can be obtained in the following ways:");
 	print("");
-	for k,v in pairs(sources) do
-		print("", k, v);
+	local longest_platform = 0;
+	for platform in pairs(sources) do
+		longest_platform = math.max(longest_platform, #platform);
+	end
+	for platform, source in pairs(sources) do
+		print("", platform..":"..(" "):rep(4+longest_platform-#platform)..source);
 	end
 	print("");
 	print(msg or (name.." is required for Prosody to run, so we will now exit."));
--- a/util/jid.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/util/jid.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -65,4 +65,17 @@
 	return host;
 end
 
+function join(node, host, resource)
+	if node and host and resource then
+		return node.."@"..host.."/"..resource;
+	elseif node and host then
+		return node.."@"..host;
+	elseif host and resource then
+		return host.."/"..resource;
+	elseif host then
+		return host;
+	end
+	return nil; -- Invalid JID
+end
+
 return _M;
--- a/util/sasl.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/util/sasl.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -38,7 +38,7 @@
 	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 authorization = s_match(response, "([^%z]*)")
 		local authentication = s_match(response, "%z([^%z]+)%z")
 		local password = s_match(response, "%z[^%z]+%z([^%z]+)")
 
--- a/util/stanza.lua	Fri Feb 19 03:30:27 2010 +0000
+++ b/util/stanza.lua	Wed Mar 03 22:05:05 2010 +0000
@@ -93,6 +93,17 @@
 	return self;
 end
 
+function stanza_mt:get_child(name, xmlns)
+	for _, child in ipairs(self.tags) do
+		if (not name or child.name == name) 
+			and ((not xmlns and self.attr.xmlns == child.attr.xmlns)
+				or child.attr.xmlns == xmlns) then
+			
+			return child;
+		end
+	end
+end
+
 function stanza_mt:child_with_name(name)
 	for _, child in ipairs(self.tags) do	
 		if child.name == name then return child; end
@@ -280,13 +291,16 @@
 	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)
-	local t = reply(orig);
-	t.attr.type = "error";
-	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
-	return t; -- stanza ready for adding app-specific errors
+do
+	local xmpp_stanzas_attr = { xmlns = xmlns_stanzas };
+	function error_reply(orig, type, condition, message)
+		local t = reply(orig);
+		t.attr.type = "error";
+		t:tag("error", {type = type}) --COMPAT: Some day xmlns:stanzas goes here
+			:tag(condition, xmpp_stanzas_attr):up();
+		if (message) then t:tag("text", xmpp_stanzas_attr):text(message):up(); end
+		return t; -- stanza ready for adding app-specific errors
+	end
 end
 
 function presence(attr)