Changeset

4527:d00f31470fcf

Merge with 0.9
author Matthew Wild <mwild1@gmail.com>
date Thu, 19 Apr 2012 19:35:10 +0100
parents 4525:e00b4ec5fca4 (diff) 4526:cdab466551bd (current diff)
children 4529:12621337471f
files plugins/mod_bosh.lua
diffstat 34 files changed, 935 insertions(+), 178 deletions(-) [+]
line wrap: on
line diff
--- a/configure	Thu Apr 19 19:30:47 2012 +0100
+++ b/configure	Thu Apr 19 19:35:10 2012 +0100
@@ -112,7 +112,7 @@
         CFLAGS="-Wall -fPIC"
         LDFLAGS="-shared"
         fi
-        if [ "$OSTYPE" = "freebsd" ]
+        if [ "$OSTYPE" = "freebsd" -o "$OSTYPE" = "openbsd" ]
         then LUA_INCDIR="/usr/local/include/lua51"
         LUA_INCDIR_SET=yes
         CFLAGS="-Wall -fPIC -I/usr/local/include"
@@ -122,6 +122,9 @@
         LUA_DIR=/usr/local
         LUA_DIR_SET=yes
         fi
+        if [ "$OSTYPE" = "openbsd" ]
+        then LUA_INCDIR="/usr/local/include";
+        fi
       ;;
    --datadir=*)
    	DATADIR="$value"
--- a/core/loggingmanager.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/core/loggingmanager.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -41,7 +41,7 @@
 local apply_sink_rules;
 local log_sink_types = setmetatable({}, { __newindex = function (t, k, v) rawset(t, k, v); apply_sink_rules(k); end; });
 local get_levels;
-local logging_levels = { "debug", "info", "warn", "error", "critical" }
+local logging_levels = { "debug", "info", "warn", "error" }
 
 -- Put a rule into action. Requires that the sink type has already been registered.
 -- This function is called automatically when a new sink type is added [see apply_sink_rules()]
--- a/core/modulemanager.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/core/modulemanager.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -117,7 +117,7 @@
 	end
 
 	local _log = logger.init(host..":"..module_name);
-	local api_instance = setmetatable({ name = module_name, host = host, path = err, config = config,  _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
+	local api_instance = setmetatable({ name = module_name, host = host, path = err, _log = _log, log = function (self, ...) return _log(...); end }, { __index = api });
 
 	local pluginenv = setmetatable({ module = api_instance }, { __index = _G });
 	api_instance.environment = pluginenv;
--- a/core/s2smanager.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/core/s2smanager.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -16,18 +16,19 @@
 local format = string.format;
 local t_insert, t_sort = table.insert, table.sort;
 local get_traceback = debug.traceback;
-local tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber, setmetatable
-    = tostring, pairs, ipairs, getmetatable, newproxy, error, tonumber, setmetatable;
+local tostring, pairs, ipairs, getmetatable, newproxy, type, error, tonumber, setmetatable
+    = tostring, pairs, ipairs, getmetatable, newproxy, type, error, tonumber, setmetatable;
 
 local idna_to_ascii = require "util.encodings".idna.to_ascii;
 local connlisteners_get = require "net.connlisteners".get;
 local initialize_filters = require "util.filters".initialize;
 local wrapclient = require "net.server".wrapclient;
-local modulemanager = require "core.modulemanager";
 local st = require "stanza";
 local stanza = st.stanza;
 local nameprep = require "util.encodings".stringprep.nameprep;
 local cert_verify_identity = require "util.x509".verify_identity;
+local new_ip = require "util.ip".new_ip;
+local rfc3484_dest = require "util.rfc3484".destination;
 
 local fire_event = prosody.events.fire_event;
 local uuid_gen = require "util.uuid".generate;
@@ -43,6 +44,7 @@
 local connect_timeout = config.get("*", "core", "s2s_timeout") or 60;
 local dns_timeout = config.get("*", "core", "dns_timeout") or 15;
 local max_dns_depth = config.get("*", "core", "dns_max_depth") or 3;
+local sources;
 
 dns.settimeout(dns_timeout);
 
@@ -243,6 +245,11 @@
 				for _, record in ipairs(answer) do
 					t_insert(srv_hosts, record.srv);
 				end
+				if #srv_hosts == 1 and srv_hosts[1].target == "." then
+					log("debug", to_host.." does not provide a XMPP service");
+					destroy_session(host_session, err); -- Nothing to see here
+					return;
+				end
 				t_sort(srv_hosts, compare_srv_priorities);
 				
 				local srv_choice = srv_hosts[1];
@@ -265,6 +272,8 @@
 		end, "_xmpp-server._tcp."..connect_host..".", "SRV");
 		
 		return true; -- Attempt in progress
+	elseif host_session.ip_hosts then
+		return try_connect(host_session, connect_host, connect_port, err);
 	elseif host_session.srv_hosts and #host_session.srv_hosts > host_session.srv_choice then -- Not our first attempt, and we also have SRV
 		host_session.srv_choice = host_session.srv_choice + 1;
 		local srv_choice = host_session.srv_hosts[host_session.srv_choice];
@@ -285,54 +294,147 @@
 	return try_connect(host_session, connect_host, connect_port);
 end
 
-function try_connect(host_session, connect_host, connect_port)
+function try_next_ip(host_session)
+	host_session.connecting = nil;
+	host_session.ip_choice = host_session.ip_choice + 1;
+	local ip = host_session.ip_hosts[host_session.ip_choice];
+	local ok, err= make_connect(host_session, ip.ip, ip.port);
+	if not ok then
+		if not attempt_connection(host_session, err or "closed") then
+			err = err and (": "..err) or "";
+			destroy_session(host_session, "Connection failed"..err);
+		end
+	end
+end
+
+function try_connect(host_session, connect_host, connect_port, err)
 	host_session.connecting = true;
-	local handle;
-	handle = adns.lookup(function (reply, err)
-		handle = nil;
-		host_session.connecting = nil;
-		
-		-- COMPAT: This is a compromise for all you CNAME-(ab)users :)
-		if not (reply and reply[#reply] and reply[#reply].a) then
-			local count = max_dns_depth;
-			reply = dns.peek(connect_host, "CNAME", "IN");
-			while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do
-				log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count);
-				reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN");
-				count = count - 1;
+
+	if not err then
+		local IPs = {};
+		host_session.ip_hosts = IPs;
+		local handle4, handle6;
+		local has_other = false;
+
+		if not sources then
+			sources =  {};
+			local cfg_sources = config.get("*", "core", "interface") or connlisteners_get("xmppserver").default_interface;
+			if type(cfg_sources) == "string" then
+				cfg_sources = { cfg_sources };
+			end
+			for i, source in ipairs(cfg_sources) do
+				if source == "*" then
+					sources[i] = new_ip("0.0.0.0", "IPv4");
+				else
+					sources[i] = new_ip(source, (source:find(":") and "IPv6") or "IPv4");
+				end
 			end
 		end
-		-- end of CNAME resolving
-		
-		if reply and reply[#reply] and reply[#reply].a then
-			log("debug", "DNS reply for %s gives us %s", connect_host, reply[#reply].a);
-			local ok, err = make_connect(host_session, reply[#reply].a, connect_port);
-			if not ok then
-				if not attempt_connection(host_session, err or "closed") then
-					err = err and (": "..err) or "";
-					destroy_session(host_session, "Connection failed"..err);
+
+		handle4 = adns.lookup(function (reply, err)
+			handle4 = nil;
+
+			-- COMPAT: This is a compromise for all you CNAME-(ab)users :)
+			if not (reply and reply[#reply] and reply[#reply].a) then
+				local count = max_dns_depth;
+				reply = dns.peek(connect_host, "CNAME", "IN");
+				while count > 0 and reply and reply[#reply] and not reply[#reply].a and reply[#reply].cname do
+					log("debug", "Looking up %s (DNS depth is %d)", tostring(reply[#reply].cname), count);
+					reply = dns.peek(reply[#reply].cname, "A", "IN") or dns.peek(reply[#reply].cname, "CNAME", "IN");
+					count = count - 1;
+				end
+			end
+			-- end of CNAME resolving
+
+			if reply and reply[#reply] and reply[#reply].a then
+				for _, ip in ipairs(reply) do
+					log("debug", "DNS reply for %s gives us %s", connect_host, ip.a);
+					IPs[#IPs+1] = new_ip(ip.a, "IPv4");
 				end
 			end
-		else
-			log("debug", "DNS lookup failed to get a response for %s", connect_host);
-			if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can
-				log("debug", "No other records to try for %s - destroying", host_session.to_host);
-				err = err and (": "..err) or "";
-				destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+
+			if has_other then
+				if #IPs > 0 then
+					rfc3484_dest(host_session.ip_hosts, sources);
+					for i = 1, #IPs do
+						IPs[i] = {ip = IPs[i], port = connect_port};
+					end
+					host_session.ip_choice = 0;
+					try_next_ip(host_session);
+				else
+					log("debug", "DNS lookup failed to get a response for %s", connect_host);
+					host_session.ip_hosts = nil;
+					if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can
+						log("debug", "No other records to try for %s - destroying", host_session.to_host);
+						err = err and (": "..err) or "";
+						destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+					end
+				end
+			else
+				has_other = true;
+			end
+		end, connect_host, "A", "IN");
+
+		handle6 = adns.lookup(function (reply, err)
+			handle6 = nil;
+
+			if reply and reply[#reply] and reply[#reply].aaaa then
+				for _, ip in ipairs(reply) do
+					log("debug", "DNS reply for %s gives us %s", connect_host, ip.aaaa);
+					IPs[#IPs+1] = new_ip(ip.aaaa, "IPv6");
+				end
 			end
+
+			if has_other then
+				if #IPs > 0 then
+					rfc3484_dest(host_session.ip_hosts, sources);
+					for i = 1, #IPs do
+						IPs[i] = {ip = IPs[i], port = connect_port};
+					end
+					host_session.ip_choice = 0;
+					try_next_ip(host_session);
+				else
+					log("debug", "DNS lookup failed to get a response for %s", connect_host);
+					host_session.ip_hosts = nil;
+					if not attempt_connection(host_session, "name resolution failed") then -- Retry if we can
+						log("debug", "No other records to try for %s - destroying", host_session.to_host);
+						err = err and (": "..err) or "";
+						destroy_session(host_session, "DNS resolution failed"..err); -- End of the line, we can't
+					end
+				end
+			else
+				has_other = true;
+			end
+		end, connect_host, "AAAA", "IN");
+
+		return true;
+	elseif host_session.ip_hosts and #host_session.ip_hosts > host_session.ip_choice then -- Not our first attempt, and we also have IPs left to try
+		try_next_ip(host_session);
+	else
+		host_session.ip_hosts = nil;
+		if not attempt_connection(host_session, "out of IP addresses") then -- Retry if we can
+			log("debug", "No other records to try for %s - destroying", host_session.to_host);
+			err = err and (": "..err) or "";
+			destroy_session(host_session, "Connecting failed"..err); -- End of the line, we can't
+			return false;
 		end
-	end, connect_host, "A", "IN");
+	end
 
 	return true;
 end
 
 function make_connect(host_session, connect_host, connect_port)
-	(host_session.log or log)("info", "Beginning new connection attempt to %s (%s:%d)", host_session.to_host, connect_host, connect_port);
+	(host_session.log or log)("info", "Beginning new connection attempt to %s ([%s]:%d)", host_session.to_host, connect_host.addr, connect_port);
 	-- Ok, we're going to try to connect
 	
 	local from_host, to_host = host_session.from_host, host_session.to_host;
 	
-	local conn, handler = socket.tcp();
+	local conn, handler;
+	if connect_host.proto == "IPv4" then
+		conn, handler = socket.tcp();
+	elseif socket.tcp6 then
+		conn, handler = socket.tcp6();
+	end
 	
 	if not conn then
 		log("warn", "Failed to create outgoing connection, system error: %s", handler);
@@ -340,14 +442,14 @@
 	end
 
 	conn:settimeout(0);
-	local success, err = conn:connect(connect_host, connect_port);
+	local success, err = conn:connect(connect_host.addr, connect_port);
 	if not success and err ~= "timeout" then
-		log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host, connect_port, err);
+		log("warn", "s2s connect() to %s (%s:%d) failed: %s", host_session.to_host, connect_host.addr, connect_port, err);
 		return false, err;
 	end
 	
 	local cl = connlisteners_get("xmppserver");
-	conn = wrapclient(conn, connect_host, connect_port, cl, cl.default_mode or 1 );
+	conn = wrapclient(conn, connect_host.addr, connect_port, cl, cl.default_mode or 1 );
 	host_session.conn = conn;
 	
 	local filter = initialize_filters(host_session);
--- a/core/sessionmanager.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/core/sessionmanager.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -16,7 +16,6 @@
 local full_sessions = full_sessions;
 local bare_sessions = bare_sessions;
 
-local modulemanager = require "core.modulemanager";
 local logger = require "util.logger";
 local log = logger.init("sessionmanager");
 local error = error;
--- a/core/usermanager.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/core/usermanager.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -11,6 +11,7 @@
 local type = type;
 local ipairs = ipairs;
 local jid_bare = require "util.jid".bare;
+local jid_prep = require "util.jid".prep;
 local config = require "core.configmanager";
 local hosts = hosts;
 local sasl_new = require "util.sasl".new;
@@ -97,6 +98,7 @@
 
 function is_admin(jid, host)
 	if host and not hosts[host] then return false; end
+	if type(jid) ~= "string" then return false; end
 
 	local is_admin;
 	jid = jid_bare(jid);
@@ -108,7 +110,7 @@
 	if host_admins and host_admins ~= global_admins then
 		if type(host_admins) == "table" then
 			for _,admin in ipairs(host_admins) do
-				if admin == jid then
+				if jid_prep(admin) == jid then
 					is_admin = true;
 					break;
 				end
@@ -121,7 +123,7 @@
 	if not is_admin and global_admins then
 		if type(global_admins) == "table" then
 			for _,admin in ipairs(global_admins) do
-				if admin == jid then
+				if jid_prep(admin) == jid then
 					is_admin = true;
 					break;
 				end
--- a/net/dns.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/net/dns.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -358,6 +358,7 @@
 	local remember, pointers = nil, 0;
 	local len = self:byte();
 	local n = {};
+	if len == 0 then return "." end -- Root label
 	while len > 0 do
 		if len >= 0xc0 then    -- name is "compressed"
 			pointers = pointers + 1;
--- a/net/httpserver.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/net/httpserver.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -7,7 +7,6 @@
 --
 
 
-local server = require "net.server"
 local url_parse = require "socket.url".parse;
 local httpstream_new = require "util.httpstream".new;
 
--- a/net/server_event.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/net/server_event.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -295,7 +295,10 @@
 	end
 
 	function interface_mt:resume()
-		return self:_lock(self.nointerface, false, self.nowriting);
+		self:_lock(self.nointerface, false, self.nowriting);
+		if not self.eventread then
+			self.eventread = addevent( base, self.conn, EV_READ, self.readcallback, cfg.READ_TIMEOUT );  -- register callback
+		end
 	end
 
 	function interface_mt:counter(c)
@@ -642,6 +645,10 @@
 						return -1
 					end
 				end
+				if interface.noreading then
+					interface.eventread = nil;
+					return -1;
+				end
 				return EV_READ, cfg.READ_TIMEOUT
 			end
 		end
--- a/net/server_select.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/net/server_select.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -596,25 +596,23 @@
 			handler.sendbuffer = handshake
 			handshake( socket ) -- do handshake
 		end
-		handler.readbuffer = _readbuffer
-		handler.sendbuffer = _sendbuffer
-		
-		if sslctx then
-			out_put "server.lua: auto-starting ssl negotiation..."
-			handler.autostart_ssl = true;
-			handler:starttls(sslctx);
-		end
+	end
 
-	else
-		handler.readbuffer = _readbuffer
-		handler.sendbuffer = _sendbuffer
-	end
+	handler.readbuffer = _readbuffer
+	handler.sendbuffer = _sendbuffer
 	send = socket.send
 	receive = socket.receive
 	shutdown = ( ssl and id ) or socket.shutdown
 
 	_socketlist[ socket ] = handler
 	_readlistlen = addsocket(_readlist, socket, _readlistlen)
+
+	if sslctx and luasec then
+		out_put "server.lua: auto-starting ssl negotiation..."
+		handler.autostart_ssl = true;
+		handler:starttls(sslctx);
+	end
+
 	return handler, socket
 end
 
--- a/net/xmppcomponent_listener.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/net/xmppcomponent_listener.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -7,8 +7,6 @@
 --
 
 
-local hosts = _G.hosts;
-
 local t_concat = table.concat;
 local tostring = tostring;
 local type = type;
--- a/net/xmppserver_listener.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/net/xmppserver_listener.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -66,7 +66,7 @@
 end
 
 local sessions = {};
-local xmppserver = { default_port = 5269, default_mode = "*a" };
+local xmppserver = { default_port = 5269, default_mode = "*a", default_interface = "*" };
 
 -- These are session methods --
 
@@ -178,7 +178,7 @@
 function xmppserver.ondisconnect(conn, err)
 	local session = sessions[conn];
 	if session then
-		if err and err ~= "closed" and session.srv_hosts then
+		if err and err ~= "closed"  and session.type == "s2sout_unauthed" then
 			(session.log or log)("debug", "s2s connection attempt failed: %s", err);
 			if s2s_attempt_connect(session, err) then
 				(session.log or log)("debug", "...so we're going to try another target");
--- a/plugins/adhoc/mod_adhoc.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/plugins/adhoc/mod_adhoc.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -90,19 +90,13 @@
 	end
 end, 500);
 
-local function handle_item_added(item)
+local function adhoc_added(event)
+	local item = event.item;
 	commands[item.node] = item;
 end
 
-module:hook("item-added/adhoc", function (event)
-	return handle_item_added(event.item);
-end, 500);
-
-module:hook("item-removed/adhoc", function (event)
+local function adhoc_removed(event)
 	commands[event.item.node] = nil;
-end, 500);
+end
 
--- Pick up any items that are already added
-for _, item in ipairs(module:get_host_items("adhoc")) do
-	handle_item_added(item);
-end
+module:handle_items("adhoc", adhoc_added, adhoc_removed);
--- a/plugins/mod_bosh.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/plugins/mod_bosh.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -9,7 +9,6 @@
 module.host = "*" -- Global module
 
 local hosts = _G.hosts;
-local lxp = require "lxp";
 local new_xmpp_stream = require "util.xmppstream".new;
 local httpserver = require "net.httpserver";
 local sm = require "core.sessionmanager";
@@ -35,6 +34,7 @@
 local BOSH_DEFAULT_REQUESTS = module:get_option_number("bosh_max_requests", 2);
 
 local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure");
+local auto_cork = module:get_option_boolean("bosh_auto_cork", false);
 
 local default_headers = { ["Content-Type"] = "text/xml; charset=utf-8" };
 
@@ -57,7 +57,7 @@
 local trusted_proxies = module:get_option_set("trusted_proxies", {"127.0.0.1"})._items;
 
 local function get_ip_from_request(request)
-	local ip = request.handler:ip();
+	local ip = request.conn:ip();
 	local forwarded_for = request.headers["x-forwarded-for"];
 	if forwarded_for then
 		forwarded_for = forwarded_for..", "..ip;
@@ -91,9 +91,10 @@
 		end
 		
 		-- If this session now has no requests open, mark it as inactive
-		if #requests == 0 and session.bosh_max_inactive and not inactive_sessions[session] then
-			inactive_sessions[session] = os_time();
-			(session.log or log)("debug", "BOSH session marked as inactive at %d", inactive_sessions[session]);
+		local max_inactive = session.bosh_max_inactive;
+		if max_inactive and #requests == 0 then
+			inactive_sessions[session] = os_time() + max_inactive;
+			(session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive);
 		end
 	end
 end
@@ -119,6 +120,7 @@
 	request.on_destroy = on_destroy_request;
 	
 	local stream = new_xmpp_stream(request, stream_callbacks);
+	
 	-- stream:feed() calls the stream_callbacks, so all stanzas in
 	-- the body are processed in this next line before it returns.
 	local ok, err = stream:feed(body);
@@ -126,6 +128,9 @@
 		log("error", "Failed to parse BOSH payload: %s", err);
 	end
 	
+	-- Stanzas (if any) in the request have now been processed, and
+	-- we take care of the high-level BOSH logic here, including
+	-- giving a response or putting the request "on hold".
 	local session = sessions[request.sid];
 	if session then
 		-- Session was marked as inactive, since we have
@@ -216,9 +221,11 @@
 		held_request:destroy();
 	end
 	sessions[session.sid]  = nil;
+	inactive_sessions[session] = nil;
 	sm_destroy_session(session);
 end
 
+-- Handle the <body> tag in the request payload.
 function stream_callbacks.streamopened(request, attr)
 	local sid = attr.sid;
 	log("debug", "BOSH body open (sid: %s)", sid or "<none>");
@@ -261,7 +268,7 @@
 			end
 			--log("debug", "Sending BOSH data: %s", tostring(s));
 			local oldest_request = r[1];
-			if oldest_request then
+			if oldest_request and (not(auto_cork) or waiting_requests[oldest_request]) then
 				log("debug", "We have an open request, so sending on that");
 				response.body = t_concat({
 					"<body xmlns='http://jabber.org/protocol/httpbind' ",
@@ -341,14 +348,6 @@
 		session.rid = rid;
 	end
 	
-	if session.notopen then
-		local features = st.stanza("stream:features");
-		hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
-		fire_event("stream-features", session, features);
-		session.send(features);
-		session.notopen = nil;
-	end
-	
 	if attr.type == "terminate" then
 		-- Client wants to end this session, which we'll do
 		-- after processing any stanzas in this request
@@ -358,6 +357,14 @@
 	request.notopen = nil; -- Signals that we accept this opening tag
 	t_insert(session.requests, request);
 	request.sid = sid;
+
+	if session.notopen then
+		local features = st.stanza("stream:features");
+		hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
+		fire_event("stream-features", session, features);
+		session.send(features);
+		session.notopen = nil;
+	end
 end
 
 function stream_callbacks.handlestanza(request, stanza)
@@ -405,17 +412,13 @@
 	
 	now = now - 3;
 	local n_dead_sessions = 0;
-	for session, inactive_since in pairs(inactive_sessions) do
-		if session.bosh_max_inactive then
-			if now - inactive_since > session.bosh_max_inactive then
-				(session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now);
-				sessions[session.sid]  = nil;
-				inactive_sessions[session] = nil;
-				n_dead_sessions = n_dead_sessions + 1;
-				dead_sessions[n_dead_sessions] = session;
-			end
-		else
+	for session, close_after in pairs(inactive_sessions) do
+		if close_after < now then
+			(session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now);
+			sessions[session.sid]  = nil;
 			inactive_sessions[session] = nil;
+			n_dead_sessions = n_dead_sessions + 1;
+			dead_sessions[n_dead_sessions] = session;
 		end
 	end
 
--- a/plugins/mod_component.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/plugins/mod_component.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -10,8 +10,6 @@
 	error("Don't load mod_component manually, it should be for a component, please see http://prosody.im/doc/components", 0);
 end
 
-local hosts = _G.hosts;
-
 local t_concat = table.concat;
 
 local sha1 = require "util.hashes".sha1;
@@ -23,6 +21,7 @@
 
 local function on_destroy(session, err)
 	if main_session == session then
+		connected = false;
 		main_session = nil;
 		send = nil;
 		session.on_destroy = nil;
@@ -83,6 +82,7 @@
 	
 	-- If component not already created for this host, create one now
 	if not main_session then
+		connected = true;
 		send = session.send;
 		main_session = session;
 		session.on_destroy = on_destroy;
--- a/plugins/mod_compression.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/plugins/mod_compression.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -16,12 +16,8 @@
 local compression_stream_feature = st.stanza("compression", {xmlns=xmlns_compression_feature}):tag("method"):text("zlib"):up();
 local add_filter = require "util.filters".add_filter;
 
-local compression_level = module:get_option("compression_level");
--- if not defined assume admin wants best compression
-if compression_level == nil then compression_level = 9 end;
+local compression_level = module:get_option_number("compression_level", 7);
 
-
-compression_level = tonumber(compression_level);
 if not compression_level or compression_level < 1 or compression_level > 9 then
 	module:log("warn", "Invalid compression level in config: %s", tostring(compression_level));
 	module:log("warn", "Module loading aborted. Compression won't be available.");
--- a/plugins/mod_tls.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/plugins/mod_tls.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -75,7 +75,7 @@
 module:hook_stanza("http://etherx.jabber.org/streams", "features", function (session, stanza)
 	module:log("debug", "Received features element");
 	if can_do_tls(session) and stanza:child_with_ns(xmlns_starttls) then
-		module:log("%s is offering TLS, taking up the offer...", session.to_host);
+		module:log("debug", "%s is offering TLS, taking up the offer...", session.to_host);
 		session.sends2s("<starttls xmlns='"..xmlns_starttls.."'/>");
 		return true;
 	end
--- a/prosodyctl	Thu Apr 19 19:30:47 2012 +0100
+++ b/prosodyctl	Thu Apr 19 19:35:10 2012 +0100
@@ -236,6 +236,7 @@
 local show_usage = prosodyctl.show_usage;
 local getchar, getpass = prosodyctl.getchar, prosodyctl.getpass;
 local show_yesno = prosodyctl.show_yesno;
+local show_prompt = prosodyctl.show_prompt;
 local read_password = prosodyctl.read_password;
 
 local prosodyctl_timeout = (config.get("*", "core", "prosodyctl_timeout") or 5) * 2;
@@ -612,6 +613,106 @@
 	return 1;
 end
 
+local x509 = require "util.x509";
+local genx509san = x509.genx509san;
+local opensslbaseconf = x509.baseconf;
+local seralizeopensslbaseconf = x509.serialize_conf;
+
+local cert_commands = {};
+
+-- TODO Should this be moved to util.prosodyctl or x509?
+function cert_commands.config(arg)
+	if #arg >= 1 and arg[1] ~= "--help" then
+		local conf_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".cnf";
+		if os.execute("test -f "..conf_filename) == 0
+			and not show_yesno("Overwrite "..conf_filename .. "?") then
+			return nil, conf_filename;
+		end
+		local conf = opensslbaseconf();
+		conf.subject_alternative_name = genx509san(hosts, config, arg, true)
+		for k, v in pairs(conf.distinguished_name) do
+			local nv;
+			if k == "commonName" then 
+				v = arg[1]
+			elseif k == "emailAddress" then
+				v = "xmpp@" .. arg[1];
+			end
+			nv = show_prompt(("%s (%s):"):format(k, nv or v));
+			nv = (not nv or nv == "") and v or nv;
+			conf.distinguished_name[k] = nv ~= "." and nv or nil;
+		end
+		local conf_file = io.open(conf_filename, "w");
+		conf_file:write(seralizeopensslbaseconf(conf));
+		conf_file:close();
+		print("");
+		show_message("Config written to " .. conf_filename);
+		return nil, conf_filename;
+	else
+		show_usage("cert config HOSTNAME", "generates config for OpenSSL")
+	end
+end
+
+function cert_commands.key(arg)
+	if #arg >= 1 and arg[1] ~= "--help" then
+		local key_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".key";
+		if os.execute("test -f "..key_filename) == 0
+			and not show_yesno("Overwrite "..key_filename .. "?") then
+			return nil, key_filename;
+		end
+		local key_size = tonumber(arg[2] or show_prompt("Choose key size (2048):") or 2048);
+		os.execute(("openssl genrsa -out %s %d"):format(key_filename, tonumber(key_size)));
+		os.execute(("chmod 400 %s"):format(key_filename));
+		show_message("Key written to ".. key_filename);
+		return nil, key_filename;
+	else
+		show_usage("cert key HOSTNAME <bits>", "Generates a RSA key")
+	end
+end
+
+function cert_commands.request(arg)
+	if #arg >= 1 and arg[1] ~= "--help" then
+		local req_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".req";
+		if os.execute("test -f "..req_filename) == 0
+			and not show_yesno("Overwrite "..req_filename .. "?") then
+			return nil, req_filename;
+		end
+		local _, key_filename = cert_commands.key({arg[1]});
+		local _, conf_filename = cert_commands.config({arg[1]});
+		os.execute(("openssl req -new -key %s -utf8 -config %s -out %s")
+			:format(key_filename, conf_filename, req_filename));
+		show_message("Certificate request written to ".. req_filename);
+	else
+		show_usage("cert request HOSTNAME", "Generates a certificate request")
+	end
+end
+
+function cert_commands.generate(arg)
+	if #arg >= 1 and arg[1] ~= "--help" then
+		local cert_filename = (CFG_DATADIR or ".") .. "/" .. arg[1] .. ".cert";
+		if os.execute("test -f "..cert_filename) == 0
+			and not show_yesno("Overwrite "..cert_filename .. "?") then
+			return nil, cert_filename;
+		end
+		local _, key_filename = cert_commands.key({arg[1]});
+		local _, conf_filename = cert_commands.config({arg[1]});
+		os.execute(("openssl req -new -x509 -nodes -key %s -days 365 -sha1 -utf8 -config %s -out %s")
+			:format(key_filename, conf_filename, cert_filename));
+		show_message("Certificate written to ".. cert_filename);
+	else
+		show_usage("cert generate HOSTNAME", "Generates a self-signed certificate")
+	end
+end
+
+function commands.cert(arg)
+	if #arg >= 1 and arg[1] ~= "--help" then
+		local subcmd = table.remove(arg, 1);
+		if type(cert_commands[subcmd]) == "function" then
+			return cert_commands[subcmd](arg);
+		end
+	end
+	show_usage("cert config|request|generate|key", "Helpers for X.509 certificates.")
+end
+
 ---------------------
 
 if command and command:match("^mod_") then -- Is a command in a module
--- a/tests/test.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/tests/test.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -12,6 +12,7 @@
 	package.loaded["net.connlisteners"] = { get = function () return {} end };
 	dotest "util.jid"
 	dotest "util.multitable"
+	dotest "util.rfc3484"
 	dotest "net.http"
 	dotest "core.modulemanager"
 	dotest "core.stanza_router"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_util_rfc3484.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -0,0 +1,51 @@
+-- Prosody IM
+-- Copyright (C) 2011 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+function source(source)
+	local new_ip = require"util.ip".new_ip;
+	assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("3ffe::1", "IPv6"), new_ip("fe80::1", "IPv6")}).addr, "3ffe::1", "prefer appropriate scope");
+	assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope");
+	assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "2001::1", "prefer appropriate scope");
+	assert_equal(source(new_ip("ff05::1", "IPv6"), {new_ip("fe80::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::1", "prefer appropriate scope");
+	assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::1", "IPv6"), new_ip("2002::1", "IPv6")}).addr, "2001::1", "prefer same address");
+	assert_equal(source(new_ip("fec0::1", "IPv6"), {new_ip("fec0::2", "IPv6"), new_ip("2001::1", "IPv6")}).addr, "fec0::2", "prefer appropriate scope");
+	assert_equal(source(new_ip("2001::1", "IPv6"), {new_ip("2001::2", "IPv6"), new_ip("3ffe::2", "IPv6")}).addr, "2001::2", "longest matching prefix");
+	assert_equal(source(new_ip("2002:836b:2179::1", "IPv6"), {new_ip("2002:836b:2179::d5e3:7953:13eb:22e8", "IPv6"), new_ip("2001::2", "IPv6")}).addr, "2002:836b:2179::d5e3:7953:13eb:22e8", "prefer matching label");
+end
+
+function destination(dest)
+	local order;
+	local new_ip = require"util.ip".new_ip;
+	order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("169.254.13.78", "IPv4")})
+	assert_equal(order[1].addr, "2001::1", "prefer matching scope");
+	assert_equal(order[2].addr, "131.107.65.121", "prefer matching scope")
+
+	order = dest({new_ip("2001::1", "IPv6"), new_ip("131.107.65.121", "IPv4")}, {new_ip("fe80::1", "IPv6"), new_ip("131.107.65.117", "IPv4")})
+	assert_equal(order[1].addr, "131.107.65.121", "prefer matching scope")
+	assert_equal(order[2].addr, "2001::1", "prefer matching scope")
+
+	order = dest({new_ip("2001::1", "IPv6"), new_ip("10.1.2.3", "IPv4")}, {new_ip("2001::2", "IPv6"), new_ip("fe80::1", "IPv6"), new_ip("10.1.2.4", "IPv4")})
+	assert_equal(order[1].addr, "2001::1", "prefer higher precedence");
+	assert_equal(order[2].addr, "10.1.2.3", "prefer higher precedence");
+
+	order = dest({new_ip("2001::1", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("fec0::1", "IPv6"), new_ip("fe80::2", "IPv6")})
+	assert_equal(order[1].addr, "fe80::1", "prefer smaller scope");
+	assert_equal(order[2].addr, "fec0::1", "prefer smaller scope");
+	assert_equal(order[3].addr, "2001::1", "prefer smaller scope");
+
+	order = dest({new_ip("2001::1", "IPv6"), new_ip("3ffe::1", "IPv6")}, {new_ip("2001::2", "IPv6"), new_ip("3f44::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+	assert_equal(order[1].addr, "2001::1", "longest matching prefix");
+	assert_equal(order[2].addr, "3ffe::1", "longest matching prefix");
+
+	order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+	assert_equal(order[1].addr, "2002:836b:4179::1", "prefer matching label");
+	assert_equal(order[2].addr, "2001::1", "prefer matching label");
+
+	order = dest({new_ip("2002:836b:4179::1", "IPv6"), new_ip("2001::1", "IPv6")}, {new_ip("2002:836b:4179::2", "IPv6"), new_ip("2001::2", "IPv6"), new_ip("fe80::2", "IPv6")})
+	assert_equal(order[1].addr, "2001::1", "prefer higher precedence");
+	assert_equal(order[2].addr, "2002:836b:4179::1", "prefer higher precedence");
+end
--- a/tools/ejabberdsql2prosody.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/tools/ejabberdsql2prosody.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -129,7 +129,12 @@
 		end
 	end
 	local tname = readTableName();
-	for ch in ("` VALUES "):gmatch(".") do read(ch); end -- expect this
+	read("`"); read(" ") -- expect this
+	if peek() == "(" then -- skip column list
+		repeat until read() == ")";
+		read(" ");
+	end
+	for ch in ("VALUES "):gmatch(".") do read(ch); end -- expect this
 	local tuples = readTuples();
 	read(";"); read("\n");
 	return tname, tuples;
--- a/tools/migration/migrator/jabberd14.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/tools/migration/migrator/jabberd14.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -67,15 +67,12 @@
 end)();
 
 local function load_xml(path)
-	if path then
-		local f, err = io_open(path);
-		if not f then return f, err; end
-		local data = f:read("*a");
-		f:close();
-		if data then
-			return parse_xml(data);
-		end
-	end
+	local f, err = io_open(path);
+	if not f then return f, err; end
+	local data = f:read("*a");
+	f:close();
+	if not data then return; end
+	return parse_xml(data);
 end
 
 local function load_spool_file(host, filename, path)
--- a/util/array.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/util/array.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -9,6 +9,11 @@
 local t_insert, t_sort, t_remove, t_concat
     = table.insert, table.sort, table.remove, table.concat;
 
+local setmetatable = setmetatable;
+local math_random = math.random;
+local pairs, ipairs = pairs, ipairs;
+local tostring = tostring;
+
 local array = {};
 local array_base = {};
 local array_methods = {};
@@ -25,6 +30,15 @@
 
 setmetatable(array, { __call = new_array });
 
+-- Read-only methods
+function array_methods:random()
+	return self[math_random(1,#self)];
+end
+
+-- These methods can be called two ways:
+--   array.method(existing_array, [params [, ...]]) -- Create new array for result
+--   existing_array:method([params, ...]) -- Transform existing array into result
+--
 function array_base.map(outa, ina, func)
 	for k,v in ipairs(ina) do
 		outa[k] = func(v);
@@ -60,15 +74,18 @@
 	return outa;
 end
 
---- These methods only mutate
-function array_methods:random()
-	return self[math.random(1,#self)];
+function array_base.pluck(outa, ina, key)
+	for i=1,#ina do
+		outa[i] = ina[i][key];
+	end
+	return outa;
 end
 
+--- These methods only mutate the array
 function array_methods:shuffle(outa, ina)
 	local len = #self;
 	for i=1,#self do
-		local r = math.random(i,len);
+		local r = math_random(i,len);
 		self[i], self[r] = self[r], self[i];
 	end
 	return self;
@@ -91,10 +108,24 @@
 	return self;
 end
 
-array_methods.push = table.insert;
-array_methods.pop = table.remove;
-array_methods.concat = table.concat;
-array_methods.length = function (t) return #t; end
+function array_methods:push(x)
+	t_insert(self, x);
+	return self;
+end
+
+function array_methods:pop(x)
+	local v = self[x];
+	t_remove(self, x);
+	return v;
+end
+
+function array_methods:concat(sep)
+	return t_concat(array.map(self, tostring), sep);
+end
+
+function array_methods:length()
+	return #self;
+end
 
 --- These methods always create a new array
 function array.collect(f, s, var)
@@ -102,7 +133,7 @@
 	while true do
 		var = f(s, var);
 	        if var == nil then break; end
-		table.insert(t, var);
+		t_insert(t, var);
 	end
 	return setmetatable(t, array_mt);
 end
--- a/util/datamanager.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/util/datamanager.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -1,7 +1,7 @@
 -- Prosody IM
 -- Copyright (C) 2008-2010 Matthew Wild
 -- Copyright (C) 2008-2010 Waqas Hussain
--- 
+--
 -- This project is MIT/X11 licensed. Please see the
 -- COPYING file in the source package for more information.
 --
@@ -20,7 +20,7 @@
 local next = next;
 local t_insert = table.insert;
 local append = require "util.serialization".append;
-local path_separator = "/"; if os.getenv("WINDIR") then path_separator = "\\" end
+local path_separator = assert ( package.config:match ( "^([^\n]+)" ) , "package.config not in standard form" ) -- Extract directory seperator from package.config (an undocumented string that comes with lua)
 local lfs = require "lfs";
 local prosody = prosody;
 local raw_mkdir;
@@ -72,7 +72,7 @@
 		username, host, datastore, data = f(username, host, datastore, data);
 		if username == false then break; end
 	end
-	
+
 	return username, host, datastore, data;
 end
 function add_callback(func)
--- a/util/debug.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/util/debug.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -7,8 +7,24 @@
 	pass = true;
 	pwd = true;
 };
+local optimal_line_length = 65;
+
+local termcolours = require "util.termcolours";
+local getstring = termcolours.getstring;
+local styles;
+do
+	_ = termcolours.getstyle;
+	styles = {
+		boundary_padding = _("bright", "white");
+		filename         = _("bright", "blue");
+		level_num        = _("green");
+		funcname         = _("yellow");
+		location         = _("yellow");
+	};
+end
 
 local function get_locals_table(level)
+	level = level + 1; -- Skip this function itself
 	local locals = {};
 	for local_num = 1, math.huge do
 		local name, value = debug.getlocal(level, local_num);
@@ -87,30 +103,50 @@
 	return levels;
 end
 
-function debug.traceback(thread, message, level)
+function debug.traceback(...)
+	local ok, ret = pcall(debug._traceback, ...);
+	if not ok then
+		return "Error in error handling: "..ret;
+	end
+	return ret;
+end
+
+local function build_source_boundary_marker(last_source_desc)
+	local padding = string.rep("-", math.floor(((optimal_line_length - 6) - #last_source_desc)/2));
+	return getstring(styles.boundary_padding, "v"..padding).." "..getstring(styles.filename, last_source_desc).." "..getstring(styles.boundary_padding, padding..(#last_source_desc%2==0 and "-v" or "v "));
+end
+
+function debug._traceback(thread, message, level)
 	if type(thread) ~= "thread" then
 		thread, message, level = coroutine.running(), thread, message;
 	end
 	if level and type(message) ~= "string" then
 		return nil, "invalid message";
 	elseif not level then
-		level = message or 2;
+		if type(message) == "number" then
+			level, message = message, nil;
+		else
+			level = 2;
+		end
 	end
 	
 	message = message and (message.."\n") or "";
 	
 	local levels = get_traceback_table(thread, level+2);
 	
+	local last_source_desc;
+	
 	local lines = {};
 	for nlevel, level in ipairs(levels) do
 		local info = level.info;
 		local line = "...";
 		local func_type = info.namewhat.." ";
+		local source_desc = (info.short_src == "[C]" and "C code") or info.short_src or "Unknown";
 		if func_type == " " then func_type = ""; end;
 		if info.short_src == "[C]" then
-			line = "[ C ] "..func_type.."C function "..(info.name and ("%q"):format(info.name) or "(unknown name)")
+			line = "[ C ] "..func_type.."C function "..getstring(styles.location, (info.name and ("%q"):format(info.name) or "(unknown name)"));
 		elseif info.what == "main" then
-			line = "[Lua] "..info.short_src.." line "..info.currentline;
+			line = "[Lua] "..getstring(styles.location, info.short_src.." line "..info.currentline);
 		else
 			local name = info.name or " ";
 			if name ~= " " then
@@ -119,19 +155,26 @@
 			if func_type == "global " or func_type == "local " then
 				func_type = func_type.."function ";
 			end
-			line = "[Lua] "..info.short_src.." line "..info.currentline.." in "..func_type..name.." defined on line "..info.linedefined;
+			line = "[Lua] "..getstring(styles.location, info.short_src.." line "..info.currentline).." in "..func_type..getstring(styles.funcname, name).." (defined on line "..info.linedefined..")";
+		end
+		if source_desc ~= last_source_desc then -- Venturing into a new source, add marker for previous
+			last_source_desc = source_desc;
+			table.insert(lines, "\t "..build_source_boundary_marker(last_source_desc));
 		end
 		nlevel = nlevel-1;
-		table.insert(lines, "\t"..(nlevel==0 and ">" or " ").."("..nlevel..") "..line);
+		table.insert(lines, "\t"..(nlevel==0 and ">" or " ")..getstring(styles.level_num, "("..nlevel..") ")..line);
 		local npadding = (" "):rep(#tostring(nlevel));
-		local locals_str = string_from_var_table(level.locals, 65, "\t            "..npadding);
+		local locals_str = string_from_var_table(level.locals, optimal_line_length, "\t            "..npadding);
 		if locals_str then
 			table.insert(lines, "\t    "..npadding.."Locals: "..locals_str);
 		end
-		local upvalues_str = string_from_var_table(level.upvalues, 65, "\t            "..npadding);
+		local upvalues_str = string_from_var_table(level.upvalues, optimal_line_length, "\t            "..npadding);
 		if upvalues_str then
 			table.insert(lines, "\t    "..npadding.."Upvals: "..upvalues_str);
 		end
 	end
+
+--	table.insert(lines, "\t "..build_source_boundary_marker(last_source_desc));
+
 	return message.."stack traceback:\n"..table.concat(lines, "\n");
 end
--- a/util/dependencies.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/util/dependencies.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -136,6 +136,14 @@
 			log("error", "This version of LuaSec contains a known bug that causes disconnects, see http://prosody.im/doc/depends");
 		end
 	end
+	if lxp then
+		if not pcall(lxp.new, { StartDoctypeDecl = false }) then
+			log("error", "The version of LuaExpat on your system leaves Prosody "
+				.."vulnerable to denial-of-service attacks. You should upgrade to "
+				.."LuaExpat 1.1.1 or higher as soon as possible. See "
+				.."http://prosody.im/doc/depends#luaexpat for more information.");
+		end
+	end
 end
 
 return _M;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/ip.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -0,0 +1,176 @@
+-- Prosody IM
+-- Copyright (C) 2008-2011 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local ip_methods = {};
+local ip_mt = { __index = function (ip, key) return (ip_methods[key])(ip); end,
+		__tostring = function (ip) return ip.addr; end,
+		__eq = function (ipA, ipB) return ipA.addr == ipB.addr; end};
+local hex2bits = { ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011", ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111", ["8"] = "1000", ["9"] = "1001", ["A"] = "1010", ["B"] = "1011", ["C"] = "1100", ["D"] = "1101", ["E"] = "1110", ["F"] = "1111" };
+
+local function new_ip(ipStr, proto)
+	if proto ~= "IPv4" and proto ~= "IPv6" then
+		return nil, "invalid protocol";
+	end
+
+	return setmetatable({ addr = ipStr, proto = proto }, ip_mt);
+end
+
+local function toBits(ip)
+	local result = "";
+	local fields = {};
+	if ip.proto == "IPv4" then
+		ip = ip.toV4mapped;
+	end
+	ip = (ip.addr):upper();
+	ip:gsub("([^:]*):?", function (c) fields[#fields + 1] = c end);
+	if not ip:match(":$") then fields[#fields] = nil; end
+	for i, field in ipairs(fields) do
+		if field:len() == 0 and i ~= 1 and i ~= #fields then
+			for i = 1, 16 * (9 - #fields) do
+				result = result .. "0";
+			end
+		else
+			for i = 1, 4 - field:len() do
+				result = result .. "0000";
+			end
+			for i = 1, field:len() do
+				result = result .. hex2bits[field:sub(i,i)];
+			end
+		end
+	end
+	return result;
+end
+
+local function commonPrefixLength(ipA, ipB)
+	ipA, ipB = toBits(ipA), toBits(ipB);
+	for i = 1, 128 do
+		if ipA:sub(i,i) ~= ipB:sub(i,i) then
+			return i-1;
+		end
+	end
+	return 128;
+end
+
+local function v4scope(ip)
+	local fields = {};
+	ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
+	-- Loopback:
+	if fields[1] == 127 then
+		return 0x2;
+	-- Link-local unicast:
+	elseif fields[1] == 169 and fields[2] == 254 then
+		return 0x2;
+	-- Site-local unicast:
+	elseif (fields[1] == 10) or (fields[1] == 192 and fields[2] == 168) or (fields[1] == 172 and (fields[2] >= 16 and fields[2] < 32)) then
+		return 0x5;
+	-- Global unicast:
+	else
+		return 0xE;
+	end
+end
+
+local function v6scope(ip)
+	-- Loopback:
+	if ip:match("^[0:]*1$") then
+		return 0x2;
+	-- Link-local unicast:
+	elseif ip:match("^[Ff][Ee][89ABab]") then 
+		return 0x2;
+	-- Site-local unicast:
+	elseif ip:match("^[Ff][Ee][CcDdEeFf]") then
+		return 0x5;
+	-- Multicast:
+	elseif ip:match("^[Ff][Ff]") then
+		return tonumber("0x"..ip:sub(4,4));
+	-- Global unicast:
+	else
+		return 0xE;
+	end
+end
+
+local function label(ip)
+	if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
+		return 0;
+	elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
+		return 2;
+	elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
+		return 3;
+	elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
+		return 4;
+	else
+		return 1;
+	end
+end
+
+local function precedence(ip)
+	if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
+		return 50;
+	elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
+		return 30;
+	elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
+		return 20;
+	elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
+		return 10;
+	else
+		return 40;
+	end
+end
+
+local function toV4mapped(ip)
+	local fields = {};
+	local ret = "::ffff:";
+	ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
+	ret = ret .. ("%02x"):format(fields[1]);
+	ret = ret .. ("%02x"):format(fields[2]);
+	ret = ret .. ":"
+	ret = ret .. ("%02x"):format(fields[3]);
+	ret = ret .. ("%02x"):format(fields[4]);
+	return new_ip(ret, "IPv6");
+end
+
+function ip_methods:toV4mapped()
+	if self.proto ~= "IPv4" then return nil, "No IPv4 address" end
+	local value = toV4mapped(self.addr);
+	self.toV4mapped = value;
+	return value;
+end
+
+function ip_methods:label()
+	local value;
+	if self.proto == "IPv4" then
+		value = label(self.toV4mapped);
+	else
+		value = label(self);
+	end
+	self.label = value;
+	return value;
+end
+
+function ip_methods:precedence()
+	local value;
+	if self.proto == "IPv4" then
+		value = precedence(self.toV4mapped);
+	else
+		value = precedence(self);
+	end
+	self.precedence = value;
+	return value;
+end
+
+function ip_methods:scope()
+	local value;
+	if self.proto == "IPv4" then
+		value = v4scope(self.addr);
+	else
+		value = v6scope(self.addr);
+	end
+	self.scope = value;
+	return value;
+end
+
+return {new_ip = new_ip,
+	commonPrefixLength = commonPrefixLength};
--- a/util/iterators.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/util/iterators.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -140,7 +140,7 @@
 -- Treat the return of an iterator as key,value pairs,
 -- and build a table
 function it2table(f, s, var)
-	local t, var = {};
+	local t, var2 = {};
 	while true do
 		var, var2 = f(s, var);
 	        if var == nil then break; end
--- a/util/prosodyctl.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/util/prosodyctl.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -16,6 +16,7 @@
 local set = require "util.set";
 local lfs = require "lfs";
 local pcall = pcall;
+local type = type;
 
 local nodeprep, nameprep = stringprep.nodeprep, stringprep.nameprep;
 
@@ -63,6 +64,13 @@
 	end
 end
 
+function getline()
+	local ok, line = pcall(io.read, "*l");
+	if ok then
+		return line;
+	end
+end
+
 function getpass()
 	local stty_ret = os.execute("stty -echo 2>/dev/null");
 	if stty_ret ~= 0 then
@@ -112,6 +120,13 @@
 	return password;
 end
 
+function show_prompt(prompt)
+	io.write(prompt, " ");
+	local line = getline();
+	line = line and line:gsub("\n$","");
+	return (line and #line > 0) and line or nil;
+end
+
 -- Server control
 function adduser(params)
 	local user, host, password = nodeprep(params.user), nameprep(params.host), params.password;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/rfc3484.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -0,0 +1,133 @@
+-- Prosody IM
+-- Copyright (C) 2008-2011 Florian Zeitz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local commonPrefixLength = require"util.ip".commonPrefixLength
+local new_ip = require"util.ip".new_ip;
+
+local function t_sort(t, comp)
+	for i = 1, (#t - 1) do
+		for j = (i + 1), #t do
+			local a, b = t[i], t[j];
+			if not comp(a,b) then
+				t[i], t[j] = b, a;
+			end
+		end
+	end
+end
+
+function source(dest, candidates)
+	local function comp(ipA, ipB)
+		-- Rule 1: Prefer same address
+		if dest == ipA then
+			return true;
+		elseif dest == ipB then
+			return false;
+		end
+
+		-- Rule 2: Prefer appropriate scope
+		if ipA.scope < ipB.scope then
+			if ipA.scope < dest.scope then
+				return false;
+			else
+				return true;
+			end
+		elseif ipA.scope > ipB.scope then
+			if ipB.scope < dest.scope then
+				return true;
+			else
+				return false;
+			end
+		end
+
+		-- Rule 3: Avoid deprecated addresses
+		-- XXX: No way to determine this
+		-- Rule 4: Prefer home addresses
+		-- XXX: Mobility Address related, no way to determine this
+		-- Rule 5: Prefer outgoing interface
+		-- XXX: Interface to address relation. No way to determine this
+		-- Rule 6: Prefer matching label
+		if ipA.label == dest.label and ipB.label ~= dest.label then
+			return true;
+		elseif ipB.label == dest.label and ipA.label ~= dest.label then
+			return false;
+		end
+
+		-- Rule 7: Prefer public addresses (over temporary ones)
+		-- XXX: No way to determine this
+		-- Rule 8: Use longest matching prefix
+		if commonPrefixLength(ipA, dest) > commonPrefixLength(ipB, dest) then
+			return true;
+		else
+			return false;
+		end
+	end
+
+	t_sort(candidates, comp);
+	return candidates[1];
+end
+
+function destination(candidates, sources)
+	local sourceAddrs = {};
+	local function comp(ipA, ipB)
+		local ipAsource = sourceAddrs[ipA];
+		local ipBsource = sourceAddrs[ipB];
+		-- Rule 1: Avoid unusable destinations
+		-- XXX: No such information
+		-- Rule 2: Prefer matching scope
+		if ipA.scope == ipAsource.scope and ipB.scope ~= ipBsource.scope then
+			return true;
+		elseif ipA.scope ~= ipAsource.scope and ipB.scope == ipBsource.scope then
+			return false;
+		end
+
+		-- Rule 3: Avoid deprecated addresses
+		-- XXX: No way to determine this
+		-- Rule 4: Prefer home addresses
+		-- XXX: Mobility Address related, no way to determine this
+		-- Rule 5: Prefer matching label
+		if ipAsource.label == ipA.label and ipBsource.label ~= ipB.label then
+			return true;
+		elseif ipBsource.label == ipB.label and ipAsource.label ~= ipA.label then
+			return false;
+		end
+
+		-- Rule 6: Prefer higher precedence
+		if ipA.precedence > ipB.precedence then
+			return true;
+		elseif ipA.precedence < ipB.precedence then
+			return false;
+		end
+
+		-- Rule 7: Prefer native transport
+		-- XXX: No way to determine this
+		-- Rule 8: Prefer smaller scope
+		if ipA.scope < ipB.scope then
+			return true;
+		elseif ipA.scope > ipB.scope then
+			return false;
+		end
+
+		-- Rule 9: Use longest matching prefix
+		if commonPrefixLength(ipA, ipAsource) > commonPrefixLength(ipB, ipBsource) then
+			return true;
+		elseif commonPrefixLength(ipA, ipAsource) < commonPrefixLength(ipB, ipBsource) then
+			return false;
+		end
+
+		-- Rule 10: Otherwise, leave order unchanged
+		return true;
+	end
+	for _, ip in ipairs(candidates) do
+		sourceAddrs[ip] = source(ip, sources);
+	end
+
+	t_sort(candidates, comp);
+	return candidates;
+end
+
+return {source = source,
+	destination = destination};
--- a/util/stanza.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/util/stanza.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -258,11 +258,6 @@
 	return type, condition or "undefined-condition", text;
 end
 
-function stanza_mt.__add(s1, s2)
-	return s1:add_direct_child(s2);
-end
-
-
 do
 	local id = 0;
 	function new_id()
--- a/util/template.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/util/template.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -7,6 +7,7 @@
 local error = error;
 local loadstring = loadstring;
 local debug = debug;
+local t_remove = table.remove;
 
 module("template")
 
@@ -42,7 +43,6 @@
 			stanza:tag(name, attr);
 		end
 		function handler:CharacterData(data)
-			data = data:gsub("^%s*", ""):gsub("%s*$", "");
 			stanza:text(data);
 		end
 		function handler:EndElement(tagname)
@@ -60,6 +60,19 @@
 	end;
 end)();
 
+local function trim_xml(stanza)
+	for i=#stanza,1,-1 do
+		local child = stanza[i];
+		if child.name then
+			trim_xml(child);
+		else
+			child = child:gsub("^%s*", ""):gsub("%s*$", "");
+			stanza[i] = child;
+			if child == "" then t_remove(stanza, i); end
+		end
+	end
+end
+
 local function create_string_string(str)
 	str = ("%q"):format(str);
 	str = str:gsub("{([^}]*)}", function(s)
@@ -118,6 +131,7 @@
 local function create_template(templates, text)
 	local stanza, err = parse_xml(text);
 	if not stanza then error(err); end
+	trim_xml(stanza);
 
 	local info = debug.getinfo(3, "Sl");
 	info = info and ("template(%s:%d)"):format(info.short_src:match("[^\\/]*$"), info.currentline) or "template(unknown)";
--- a/util/x509.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/util/x509.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -21,6 +21,10 @@
 local nameprep = require "util.encodings".stringprep.nameprep;
 local idna_to_ascii = require "util.encodings".idna.to_ascii;
 local log = require "util.logger".init("x509");
+local pairs, ipairs = pairs, ipairs;
+local s_format = string.format;
+local t_insert = table.insert;
+local t_concat = table.concat;
 
 module "x509"
 
@@ -208,4 +212,109 @@
 	return false
 end
 
+-- TODO Rename? Split out subroutines?
+-- Also, this is probably openssl specific, what TODO about that?
+function genx509san(hosts, config, certhosts, raw) -- recive config through that or some better way?
+	local function utf8string(s)
+		-- This is how we tell openssl not to encode UTF-8 strings as Latin1
+		return s_format("FORMAT:UTF8,UTF8:%s", s);
+	end
+
+	local function ia5string(s)
+		return s_format("IA5STRING:%s", s);
+	end
+
+	local function dnsname(t, host)
+		t_insert(t.DNS, idna_to_ascii(host));
+	end
+
+	local function srvname(t, host, service)
+		t_insert(t.otherName, s_format("%s;%s", oid_dnssrv, ia5string("_" .. service .."." .. idna_to_ascii(host))));
+	end
+
+	local function xmppAddr(t, host)
+		t_insert(t.otherName, s_format("%s;%s", oid_xmppaddr, utf8string(host)));
+	end
+
+	-----------------------------
+
+	local san = {
+		DNS = {};
+		otherName = {};
+	};
+
+	local sslsanconf = { };
+
+	for i = 1,#certhosts do
+		local certhost = certhosts[i];
+		for name, host in pairs(hosts) do
+			if name == certhost or name:sub(-1-#certhost) == "."..certhost then
+				dnsname(san, name);
+				--print(name .. "#component_module: " .. (config.get(name, "core", "component_module") or "nil"));
+				if config.get(name, "core", "component_module") == nil then
+					srvname(san, name, "xmpp-client");
+				end
+				--print(name .. "#anonymous_login: " .. tostring(config.get(name, "core", "anonymous_login")));
+				if not (config.get(name, "core", "anonymous_login") or
+						config.get(name, "core", "authentication") == "anonymous") then
+					srvname(san, name, "xmpp-server");
+				end
+				xmppAddr(san, name);
+			end
+		end
+	end
+
+	for t, n in pairs(san) do
+		for i = 1,#n do
+			t_insert(sslsanconf, s_format("%s.%d = %s", t, i -1, n[i]));
+		end
+	end
+
+	return raw and sslsanconf or t_concat(sslsanconf, "\n");
+end
+
+function baseconf()
+	return {
+		req = {
+			distinguished_name = "distinguished_name",
+			req_extensions = "v3_extensions",
+			x509_extensions = "v3_extensions",
+			prompt = "no",
+		},
+		distinguished_name = {
+			commonName = "example.com",
+			countryName = "GB",
+			localityName = "The Internet",
+			organizationName = "Your Organisation",
+			organizationalUnitName = "XMPP Department",
+			emailAddress = "xmpp@example.com",
+		},
+		v3_extensions = {
+			basicConstraints = "CA:FALSE",
+			keyUsage = "digitalSignature,keyEncipherment",
+			extendedKeyUsage = "serverAuth,clientAuth",
+			subjectAltName = "@subject_alternative_name",
+		},
+		subject_alternative_name = { },
+	}
+end
+
+function serialize_conf(conf)
+	local s = "";
+	for k, t in pairs(conf) do
+		s = s .. ("[%s]\n"):format(k);
+		if t[1] then
+			for i, v in ipairs(t) do
+				s = s .. ("%s\n"):format(v);
+			end
+		else
+			for k, v in pairs(t) do
+				s = s .. ("%s = %s\n"):format(k, v);
+			end
+		end
+		s = s .. "\n";
+	end
+	return s;
+end
+
 return _M;
--- a/util/xmppstream.lua	Thu Apr 19 19:30:47 2012 +0100
+++ b/util/xmppstream.lua	Thu Apr 19 19:35:10 2012 +0100
@@ -11,32 +11,25 @@
 local st = require "util.stanza";
 local stanza_mt = st.stanza_mt;
 
+local error = error;
 local tostring = tostring;
 local t_insert = table.insert;
 local t_concat = table.concat;
 local t_remove = table.remove;
 local setmetatable = setmetatable;
 
-local default_log = require "util.logger".init("xmppstream");
-
 -- COMPAT: w/LuaExpat 1.1.0
 local lxp_supports_doctype = pcall(lxp.new, { StartDoctypeDecl = false });
 
-if not lxp_supports_doctype then
-	default_log("warn", "The version of LuaExpat on your system leaves Prosody "
-		.."vulnerable to denial-of-service attacks. You should upgrade to "
-		.."LuaExpat 1.1.1 or higher as soon as possible. See "
-		.."http://prosody.im/doc/depends#luaexpat for more information.");
-end
-
-local error = error;
-
 module "xmppstream"
 
 local new_parser = lxp.new;
 
-local ns_prefixes = {
-	["http://www.w3.org/XML/1998/namespace"] = "xml";
+local xml_namespace = {
+	["http://www.w3.org/XML/1998/namespace\1lang"] = "xml:lang";
+	["http://www.w3.org/XML/1998/namespace\1space"] = "xml:space";
+	["http://www.w3.org/XML/1998/namespace\1base"] = "xml:base";
+	["http://www.w3.org/XML/1998/namespace\1id"] = "xml:id";
 };
 
 local xmlns_streams = "http://etherx.jabber.org/streams";
@@ -50,8 +43,6 @@
 function new_sax_handlers(session, stream_callbacks)
 	local xml_handlers = {};
 	
-	local log = session.log or default_log;
-	
 	local cb_streamopened = stream_callbacks.streamopened;
 	local cb_streamclosed = stream_callbacks.streamclosed;
 	local cb_error = stream_callbacks.error or function(session, e) error("XML stream error: "..tostring(e)); end;
@@ -85,17 +76,13 @@
 			non_streamns_depth = non_streamns_depth + 1;
 		end
 		
-		-- FIXME !!!!!
 		for i=1,#attr do
 			local k = attr[i];
 			attr[i] = nil;
-			local ns, nm = k:match(ns_pattern);
-			if nm ~= "" then
-				ns = ns_prefixes[ns];
-				if ns then
-					attr[ns..":"..nm] = attr[k];
-					attr[k] = nil;
-				end
+			local xmlk = xml_namespace[k];
+			if xmlk then
+				attr[xmlk] = attr[k];
+				attr[k] = nil;
 			end
 		end
 		
@@ -152,19 +139,9 @@
 				stanza = t_remove(stack);
 			end
 		else
-			if tagname == stream_tag then
-				if cb_streamclosed then
-					cb_streamclosed(session);
-				end
-			else
-				local curr_ns,name = tagname:match(ns_pattern);
-				if name == "" then
-					curr_ns, name = "", curr_ns;
-				end
-				cb_error(session, "parse-error", "unexpected-element-close", name);
+			if cb_streamclosed then
+				cb_streamclosed(session);
 			end
-			stanza, chardata = nil, {};
-			stack = {};
 		end
 	end
 
@@ -188,7 +165,6 @@
 	
 	local function set_session(stream, new_session)
 		session = new_session;
-		log = new_session.log or default_log;
 	end
 	
 	return xml_handlers, { reset = reset, set_session = set_session };