Changeset

4684:dc70c4ffb66d

Merge timber->trunk - thanks everyone!
author Matthew Wild <mwild1@gmail.com>
date Tue, 24 Apr 2012 21:59:20 +0100
parents 4529:12621337471f (diff) 4683:c1374e083c97 (current diff)
children 4685:3d90264c7b3d
files core/s2smanager.lua net/connlisteners.lua net/httpclient_listener.lua net/multiplex_listener.lua net/xmppclient_listener.lua net/xmppcomponent_listener.lua net/xmppserver_listener.lua plugins/mod_admin_telnet.lua plugins/mod_bosh.lua plugins/mod_httpserver.lua util/debug.lua
diffstat 15 files changed, 381 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- a/core/hostmanager.lua	Tue Apr 24 19:07:12 2012 +0100
+++ b/core/hostmanager.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -12,6 +12,7 @@
 local disco_items = require "util.multitable".new();
 local NULL = {};
 
+local jid_split = require "util.jid".split;
 local uuid_gen = require "util.uuid".generate;
 
 local log = require "util.logger".init("hostmanager");
@@ -23,7 +24,7 @@
 end
 local incoming_s2s = _G.prosody.incoming_s2s;
 
-local pairs, setmetatable = pairs, setmetatable;
+local pairs, setmetatable, select = pairs, setmetatable, select;
 local tostring, type = tostring, type;
 
 module "hostmanager"
--- a/core/s2smanager.lua	Tue Apr 24 19:07:12 2012 +0100
+++ b/core/s2smanager.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -99,7 +99,7 @@
 	else
 		return false;
 	end
-	session.log("debug", "connection %s->%s is now authenticated", session.from_host or "(unknown)", session.to_host or "(unknown)");
+	session.log("debug", "connection %s->%s is now authenticated for %s", session.from_host or "(unknown)", session.to_host or "(unknown)", host);
 	
 	mark_connected(session);
 	
--- a/plugins/mod_admin_telnet.lua	Tue Apr 24 19:07:12 2012 +0100
+++ b/plugins/mod_admin_telnet.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -498,6 +498,24 @@
 	return true, "Total: "..count.." sessions closed";
 end
 
+local function session_flags(session, line)
+	if session.cert_identity_status == "valid" then
+		line[#line+1] = "(secure)";
+	elseif session.secure then
+		line[#line+1] = "(encrypted)";
+	end
+	if session.compressed then
+		line[#line+1] = "(compressed)";
+	end
+	if session.smacks then
+		line[#line+1] = "(sm)";
+	end
+	if session.conn and session.conn:ip():match(":") then
+		line[#line+1] = "(IPv6)";
+	end
+	return table.concat(line, " ");
+end
+
 def_env.s2s = {};
 function def_env.s2s:show(match_jid)
 	local _print = self.session.print;
@@ -510,7 +528,7 @@
 		for remotehost, session in pairs(host_session.s2sout) do
 			if (not match_jid) or remotehost:match(match_jid) or host:match(match_jid) then
 				count_out = count_out + 1;
-				print("    "..host.." -> "..remotehost..(session.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
+				print(session_flags(session, {"   ", host, "->", remotehost}));
 				if session.sendq then
 					print("        There are "..#session.sendq.." queued outgoing stanzas for this connection");
 				end
@@ -547,7 +565,7 @@
 				-- Pft! is what I say to list comprehensions
 				or (session.hosts and #array.collect(keys(session.hosts)):filter(subhost_filter)>0)) then
 				count_in = count_in + 1;
-				print("    "..host.." <- "..(session.from_host or "(unknown)")..(session.cert_identity_status == "valid" and " (secure)" or "")..(session.secure and " (encrypted)" or "")..(session.compressed and " (compressed)" or ""));
+				print(session_flags(session, {"   ", host, "<-", session.from_host or "(unknown)"}));
 				if session.type == "s2sin_unauthed" then
 						print("        Connection not yet authenticated");
 				end
--- a/plugins/mod_bosh.lua	Tue Apr 24 19:07:12 2012 +0100
+++ b/plugins/mod_bosh.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -123,10 +123,10 @@
 	
 	-- stream:feed() calls the stream_callbacks, so all stanzas in
 	-- the body are processed in this next line before it returns.
-	-- In particular, the streamopened() stream callback is where
-	-- much of the session logic happens, because it's where we first
-	-- get to see the 'sid' of this request.
-	stream:feed(body);
+	local ok, err = stream:feed(body);
+	if not ok then
+		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
--- a/plugins/mod_saslauth.lua	Tue Apr 24 19:07:12 2012 +0100
+++ b/plugins/mod_saslauth.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -16,7 +16,6 @@
 
 local cert_verify_identity = require "util.x509".verify_identity;
 
-local nodeprep = require "util.encodings".stringprep.nodeprep;
 local usermanager_get_sasl_handler = require "core.usermanager".get_sasl_handler;
 local tostring = tostring;
 
@@ -51,15 +50,14 @@
 		module:fire_event("authentication-failure", { session = session, condition = ret, text = err_msg });
 		session.sasl_handler = session.sasl_handler:clean_clone();
 	elseif status == "success" then
-		module:fire_event("authentication-success", { session = session });
-		local username = nodeprep(session.sasl_handler.username);
-
 		local ok, err = sm_make_authenticated(session, session.sasl_handler.username);
 		if ok then
+			module:fire_event("authentication-success", { session = session });
 			session.sasl_handler = nil;
 			session:reset_stream();
 		else
 			module:log("warn", "SASL succeeded but username was invalid");
+			module:fire_event("authentication-failure", { session = session, condition = "not-authorized", text = err });
 			session.sasl_handler = session.sasl_handler:clean_clone();
 			return "failure", "not-authorized", "User authenticated successfully, but username was invalid";
 		end
@@ -191,8 +189,10 @@
 		session.from_host = text;
 	end
 	session.sends2s(build_reply("success"))
-	module:log("info", "Accepting SASL EXTERNAL identity from %s", text or session.from_host);
-	s2s_make_authenticated(session, text or session.from_host)
+
+	local domain = text ~= "" and text or session.from_host;
+	module:log("info", "Accepting SASL EXTERNAL identity from %s", domain);
+	s2s_make_authenticated(session, domain);
 	session:reset_stream();
 	return true
 end
--- a/plugins/muc/mod_muc.lua	Tue Apr 24 19:07:12 2012 +0100
+++ b/plugins/muc/mod_muc.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -69,10 +69,10 @@
 	local node = jid_split(jid);
 	local data = datamanager.load(node, muc_host, "config") or {};
 	local room = muc_new_room(jid, {
-		history_length = max_history_messages;
+		max_history_length = max_history_messages;
 	});
 	room._data = data._data;
-	room._data.history_length = max_history_messages; --TODO: Need to allow per-room with a global limit
+	room._data.max_history_length = max_history_messages; -- Overwrite old max_history_length in data with current settings
 	room._affiliations = data._affiliations;
 	room.route_stanza = room_route_stanza;
 	room.save = room_save;
@@ -80,7 +80,7 @@
 end
 
 local host_room = muc_new_room(muc_host, {
-	history_length = max_history_messages;
+	max_history_length = max_history_messages;
 });
 host_room.route_stanza = room_route_stanza;
 host_room.save = room_save;
@@ -131,7 +131,7 @@
 		  (restrict_room_creation == "admin" and is_admin(stanza.attr.from)) or
 		  (restrict_room_creation == "local" and select(2, jid_split(stanza.attr.from)) == module.host:gsub("^[^%.]+%.", "")) then
 			room = muc_new_room(bare, {
-				history_length = max_history_messages;
+				max_history_length = max_history_messages;
 			});
 			room.route_stanza = room_route_stanza;
 			room.save = room_save;
--- a/plugins/muc/muc.lib.lua	Tue Apr 24 19:07:12 2012 +0100
+++ b/plugins/muc/muc.lib.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -138,7 +138,7 @@
 		stanza:tag("x", {xmlns = "jabber:x:delay", from = muc_domain, stamp = datetime.legacy()}):up(); -- XEP-0091 (deprecated)
 		local entry = { stanza = stanza, stamp = stamp };
 		t_insert(history, entry);
-		while #history > (self._data.history_length or default_history_length) do t_remove(history, 1) end
+		while #history > self._data.history_length do t_remove(history, 1) end
 	end
 end
 function room_mt:broadcast_except_nick(stanza, nick)
@@ -339,6 +339,21 @@
 function room_mt:get_changesubject()
 	return self._data.changesubject;
 end
+function room_mt:get_historylength()
+	return self._data.history_length
+end
+function room_mt:set_historylength(length)
+	if tonumber(length) == nil then
+		return
+	end
+	length = tonumber(length);
+	log("debug", "max_history_length %s", self._data.max_history_length or "nil");
+	if self._data.max_history_length and length > self._data.max_history_length then
+		length = self._data.max_history_length
+	end
+	self._data.history_length = length;
+end
+
 
 function room_mt:handle_to_occupant(origin, stanza) -- PM, vCards, etc
 	local from, to = stanza.attr.from, stanza.attr.to;
@@ -608,6 +623,12 @@
 			type = 'boolean',
 			label = 'Make Room Members-Only?',
 			value = self:is_members_only()
+		},
+		{
+			name = 'muc#roomconfig_historylength',
+			type = 'text-single',
+			label = 'Maximum Number of History Messages Returned by Room',
+			value = tostring(self:get_historylength())
 		}
 	});
 end
@@ -659,6 +680,11 @@
 	dirty = dirty or (self:get_changesubject() ~= (not changesubject and true or nil))
 	module:log('debug', 'changesubject=%s', changesubject and "true" or "false")
 
+	local historylength = fields['muc#roomconfig_historylength'];
+	dirty = dirty or (self:get_historylength() ~= (historylength and true or nil))
+	module:log('debug', 'historylength=%s', historylength)
+
+
 	local whois = fields['muc#roomconfig_whois'];
 	if not valid_whois[whois] then
 	    origin.send(st.error_reply(stanza, 'cancel', 'bad-request', "Invalid value for 'whois'"));
@@ -677,6 +703,7 @@
 	self:set_persistent(persistent);
 	self:set_hidden(not public);
 	self:set_changesubject(changesubject);
+	self:set_historylength(historylength);
 
 	if self.save then self:save(true); end
 	origin.send(st.reply(stanza));
@@ -848,7 +875,7 @@
 					origin.send(st.error_reply(stanza, "cancel", "forbidden"));
 				end
 			else
-				self:broadcast_message(stanza, true);
+				self:broadcast_message(stanza, self:get_historylength() > 0);
 			end
 			stanza.attr.from = from;
 		end
@@ -1102,7 +1129,8 @@
 		_occupants = {};
 		_data = {
 		    whois = 'moderators';
-		    history_length = (config and config.history_length);
+		    history_length = (config and config.max_history_length) or default_history_length;
+		    max_history_length = (config and config.max_history_length) or default_history_length;
 		};
 		_affiliations = {};
 	}, room_mt);
--- a/prosody.cfg.lua.dist	Tue Apr 24 19:07:12 2012 +0100
+++ b/prosody.cfg.lua.dist	Tue Apr 24 21:59:20 2012 +0100
@@ -45,7 +45,6 @@
 		--"compression"; -- Stream compression
 
 	-- Nice to have
-		"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
 		"version"; -- Replies to server version requests
 		"uptime"; -- Report how long server has been running
 		"time"; -- Let others know the time here on this server
@@ -67,6 +66,7 @@
 		--"welcome"; -- Welcome users who register accounts
 		--"watchregistrations"; -- Alert admins of registrations
 		--"motd"; -- Send a message to users when they log in
+		--"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
 };
 
 -- These modules are auto-loaded, should you
--- a/prosodyctl	Tue Apr 24 19:07:12 2012 +0100
+++ b/prosodyctl	Tue Apr 24 21:59:20 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/tools/ejabberdsql2prosody.lua	Tue Apr 24 19:07:12 2012 +0100
+++ b/tools/ejabberdsql2prosody.lua	Tue Apr 24 21:59:20 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/util/debug.lua	Tue Apr 24 19:07:12 2012 +0100
+++ b/util/debug.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -7,7 +7,21 @@
 	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
 module("debugx", package.seeall);
 
 function get_locals_table(level)
@@ -90,30 +104,51 @@
 	return levels;
 end
 
-function traceback(thread, message, level)
+function traceback(...)
+	local ok, ret = pcall(_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 _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
@@ -122,20 +157,27 @@
 			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/prosodyctl.lua	Tue Apr 24 19:07:12 2012 +0100
+++ b/util/prosodyctl.lua	Tue Apr 24 21:59:20 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;
@@ -121,7 +136,11 @@
 		return false, "invalid-hostname";
 	end
 
-	local provider = prosody.hosts[host].users;
+	local host_session = prosody.hosts[host];
+	if not host_session then
+		return false, "no-such-host";
+	end
+	local provider = host_session.users;
 	if not(provider) or provider.name == "null" then
 		usermanager.initialize_host(host);
 	end
--- a/util/template.lua	Tue Apr 24 19:07:12 2012 +0100
+++ b/util/template.lua	Tue Apr 24 21:59:20 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	Tue Apr 24 19:07:12 2012 +0100
+++ b/util/x509.lua	Tue Apr 24 21:59:20 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	Tue Apr 24 19:07:12 2012 +0100
+++ b/util/xmppstream.lua	Tue Apr 24 21:59:20 2012 +0100
@@ -25,8 +25,11 @@
 
 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";
@@ -73,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
 		
@@ -140,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