changeset 1725:d85d5b0bf977

Merge with Goffi
author Kim Alvefur <zash@zash.se>
date Thu, 07 May 2015 23:39:54 +0200
parents e4867211cddb (diff) 2440a75e868f (current diff)
children 160c35d2a5a2 7bfc23b2c038
files
diffstat 16 files changed, 483 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/mod_auto_accept_subscriptions/mod_auto_accept_subscriptions.lua	Thu May 07 22:40:58 2015 +0200
+++ b/mod_auto_accept_subscriptions/mod_auto_accept_subscriptions.lua	Thu May 07 23:39:54 2015 +0200
@@ -7,7 +7,7 @@
 	local to_bare, from_bare = jid.bare(stanza.attr.to), jid.bare(stanza.attr.from);
 	local node, host = jid.split(to_bare);
 	stanza.attr.from, stanza.attr.to = from_bare, to_bare;
-	module:log("info", "Auto-accepting inbound subscription request from %s to %s", from_bare, to_bare);
+	module:log("info", "Auto-accepting inbound subscription request from %s to %s", tostring(from_bare), tostring(to_bare));
 
 	if not rostermanager.is_contact_subscribed(node, host, from_bare) then
 		core_post_stanza(hosts[host], st.presence({from=to_bare, to=from_bare, type="unavailable"}), true); -- acknowledging receipt
@@ -35,7 +35,7 @@
 			end
 		end
 	end
-	module:log("warn", "Failed to auto-accept subscription request from %s to %s", from_bare, to_bare);
+	module:log("warn", "Failed to auto-accept subscription request from %s to %s", tostring(from_bare), tostring(to_bare));
 end
 
 module:hook("presence/bare", function (event)
--- a/mod_block_registrations/mod_block_registrations.lua	Thu May 07 22:40:58 2015 +0200
+++ b/mod_block_registrations/mod_block_registrations.lua	Thu May 07 23:39:54 2015 +0200
@@ -10,7 +10,7 @@
         if block_users:contains(username) then return true; end
 
         for pattern in block_patterns do
-                if username:match(pattern) then
+                if username:find(pattern) then
                         return true;
                 end
         end
--- a/mod_checkcerts/mod_checkcerts.lua	Thu May 07 22:40:58 2015 +0200
+++ b/mod_checkcerts/mod_checkcerts.lua	Thu May 07 23:39:54 2015 +0200
@@ -1,6 +1,6 @@
 local ssl = require"ssl";
 local datetime_parse = require"util.datetime".parse;
-local load_cert = ssl.x509 and ssl.x509.load;
+local load_cert = ssl.loadcertificate;
 local st = require"util.stanza"
 
 -- These are in days.
--- a/mod_http_muc_log/mod_http_muc_log.lua	Thu May 07 22:40:58 2015 +0200
+++ b/mod_http_muc_log/mod_http_muc_log.lua	Thu May 07 23:39:54 2015 +0200
@@ -238,7 +238,7 @@
 	local next_when = find_once(room, { after = last }, 3);
 	if next_when then
 		next_when = datetime.date(next_when);
-		module:log("debug", "Next message: %s", datetime.datetime(next_when));
+		module:log("debug", "Next message: %s", next_when);
 	else
 		next_when = "";
 	end
@@ -247,7 +247,7 @@
 	local prev_when = find_once(room, { before = first, reverse = true }, 3);
 	if prev_when then
 		prev_when = datetime.date(prev_when);
-		module:log("debug", "Previous message: %s", datetime.datetime(prev_when));
+		module:log("debug", "Previous message: %s", prev_when);
 	else
 		prev_when = "";
 	end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_log_slow_events/mod_log_slow_events.lua	Thu May 07 23:39:54 2015 +0200
@@ -0,0 +1,55 @@
+local time = require "socket".gettime;
+local base64_decode = require "util.encodings".base64.decode;
+
+local max_seconds = module:get_option_number("log_slow_events_threshold", 0.5);
+
+function event_wrapper(handlers, event_name, event_data)
+	local start = time();
+	local ret = handlers(event_name, event_data);
+	local duration = time()-start;
+	if duration > max_seconds then
+		local data = {};
+		if event_data then
+			local function log_data(name, value)
+				if value then
+					table.insert(data, ("%s=%q"):format(name, value));
+					return true;
+				end
+			end
+			local sess = event_data.origin or event_data.session;
+			if sess then
+				log_data("ip", sess.ip);
+				if not log_data("full_jid", sess.full_jid) then
+					log_data("username", sess.username);
+				end
+				log_data("type", sess.type);
+				log_data("host", sess.host);
+			end
+			local stanza = event_data.stanza;
+			if stanza then
+				log_data("stanza", tostring(stanza));
+			else
+				local request = event_data.request;
+				if request then
+					log_data("http_method", request.method);
+					log_data("http_path", request.path);
+					local auth = request.headers.authorization;
+					if auth then
+						local creds = auth:match("^Basic +(.+)$");
+						if creds then
+							local user = string.match(base64_decode(creds) or "", "^([^:]+):");
+							log_data("http_user", user);
+						end
+					end
+				end
+			end
+		end
+		module:log("warn", "Slow event '%s' took %0.2fs: %s", event_name, duration, next(data) and table.concat(data, ", ") or "no recognised data");
+	end
+	return ret;
+end
+
+module:wrap_event(false, event_wrapper);
+local http_events = require "net.http.server"._events;
+module:wrap_object_event(http_events, false, event_wrapper);
+
--- a/mod_mam/mod_mam.lua	Thu May 07 22:40:58 2015 +0200
+++ b/mod_mam/mod_mam.lua	Thu May 07 23:39:54 2015 +0200
@@ -39,10 +39,10 @@
 local archive = module:open_store(archive_store, "archive");
 if not archive or archive.name == "null" then
 	module:log("error", "Could not open archive storage");
-	return
+	return;
 elseif not archive.find then
 	module:log("error", "mod_%s does not support archiving, switch to mod_storage_sql2", archive._provided_by);
-	return
+	return;
 end
 
 -- Handle prefs.
@@ -52,16 +52,18 @@
 	if stanza.attr.type == "get" then
 		local prefs = prefs_to_stanza(get_prefs(user));
 		local reply = st.reply(stanza):add_child(prefs);
-		return origin.send(reply);
+		origin.send(reply);
 	else -- type == "set"
 		local new_prefs = stanza:get_child("prefs", xmlns_mam);
 		local prefs = prefs_from_stanza(new_prefs);
 		local ok, err = set_prefs(user, prefs);
 		if not ok then
-			return origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error storing preferences: "..tostring(err)));
+			origin.send(st.error_reply(stanza, "cancel", "internal-server-error", "Error storing preferences: "..tostring(err)));
+		else
+			origin.send(st.reply(stanza));
 		end
-		return origin.send(st.reply(stanza));
 	end
+	return true;
 end);
 
 local query_form = dataform {
@@ -74,7 +76,8 @@
 -- Serve form
 module:hook("iq-get/self/"..xmlns_mam..":query", function(event)
 	local origin, stanza = event.origin, event.stanza;
-	return origin.send(st.reply(stanza):add_child(query_form:form()));
+	origin.send(st.reply(stanza):add_child(query_form:form()));
+	return true;
 end);
 
 -- Handle archive queries
@@ -90,17 +93,18 @@
 		local err;
 		form, err = query_form:data(form);
 		if err then
-			return origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))))
+			origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))));
+			return true;
 		end
 		qwith, qstart, qend = form["with"], form["start"], form["end"];
 		qwith = qwith and jid_bare(qwith); -- dataforms does jidprep
 	end
 
 	if qstart or qend then -- Validate timestamps
-		local vstart, vend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend))
+		local vstart, vend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend));
 		if (qstart and not vstart) or (qend and not vend) then
 			origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid timestamp"))
-			return true
+			return true;
 		end
 		qstart, qend = vstart, vend;
 	end
@@ -120,24 +124,34 @@
 	local data, err = archive:find(origin.username, {
 		start = qstart; ["end"] = qend; -- Time range
 		with = qwith;
-		limit = qmax;
+		limit = qmax + 1;
 		before = before; after = after;
 		reverse = reverse;
 		total = true;
 	});
 
 	if not data then
-		return origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err));
+		origin.send(st.error_reply(stanza, "cancel", "internal-server-error", err));
+		return true;
 	end
-	local count = err;
+	local total = err;
 
-	origin.send(st.reply(stanza))
+	origin.send(st.reply(stanza));
 	local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
 
+	local results = {};
+
 	-- Wrap it in stuff and deliver
-	local fwd_st, first, last;
+	local first, last;
+	local count = 0;
+	local complete = "true";
 	for id, item, when in data do
-		fwd_st = st.message(msg_reply_attr)
+		count = count + 1;
+		if count > qmax then
+			complete = nil;
+			break;
+		end
+		local fwd_st = st.message(msg_reply_attr)
 			:tag("result", { xmlns = xmlns_mam, queryid = qid, id = id })
 				:tag("forwarded", { xmlns = xmlns_forward })
 					:tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up();
@@ -151,16 +165,27 @@
 		if not first then first = id; end
 		last = id;
 
-		origin.send(fwd_st);
+		if reverse then
+			results[count] = fwd_st;
+		else
+			origin.send(fwd_st);
+		end
 	end
+	if reverse then
+		for i = #results, 1, -1 do
+			origin.send(results[i]);
+		end
+	end
+
 	-- That's all folks!
 	module:log("debug", "Archive query %s completed", tostring(qid));
 
 	if reverse then first, last = last, first; end
-	return origin.send(st.message(msg_reply_attr)
-		:tag("fin", { xmlns = xmlns_mam, queryid = qid })
+	origin.send(st.message(msg_reply_attr)
+		:tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
 			:add_child(rsm.generate {
-				first = first, last = last, count = count }));
+				first = first, last = last, count = total }));
+	return true;
 end);
 
 local function has_in_roster(user, who)
@@ -173,15 +198,15 @@
 	-- TODO Cache this?
 	local prefs = get_prefs(user);
 	local rule = prefs[who];
-	module:log("debug", "%s's rule for %s is %s", user, who, tostring(rule))
+	module:log("debug", "%s's rule for %s is %s", user, who, tostring(rule));
 	if rule ~= nil then
 		return rule;
 	else -- Below could be done by a metatable
 		local default = prefs[false];
-		module:log("debug", "%s's default rule is %s", user, tostring(default))
+		module:log("debug", "%s's default rule is %s", user, tostring(default));
 		if default == nil then
 			default = global_default_policy;
-			module:log("debug", "Using global default rule, %s", tostring(default))
+			module:log("debug", "Using global default rule, %s", tostring(default));
 		end
 		if default == "roster" then
 			return has_in_roster(user, who);
@@ -238,5 +263,9 @@
 module:hook("message/bare", message_handler, 2);
 module:hook("message/full", message_handler, 2);
 
-module:add_feature(xmlns_mam);
+module:add_feature(xmlns_mam); -- COMPAT with XEP-0313 v 0.1
 
+module:hook("account-disco-info", function(event)
+	event.reply:tag("feature", {var=xmlns_mam}):up();
+end);
+
--- a/mod_mam_muc/mod_mam_muc.lua	Thu May 07 22:40:58 2015 +0200
+++ b/mod_mam_muc/mod_mam_muc.lua	Thu May 07 23:39:54 2015 +0200
@@ -139,7 +139,8 @@
 -- Serve form
 module:hook("iq-get/bare/"..xmlns_mam..":query", function(event)
 	local origin, stanza = event.origin, event.stanza;
-	return origin.send(st.reply(stanza):add_child(query_form:form()));
+	origin.send(st.reply(stanza):add_child(query_form:form()));
+	return true;
 end);
 
 -- Handle archive queries
@@ -172,7 +173,8 @@
 		local err;
 		form, err = query_form:data(form);
 		if err then
-			return origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))))
+			origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))));
+			return true;
 		end
 		qstart, qend = form["start"], form["end"];
 	end
@@ -197,7 +199,7 @@
 	-- Load all the data!
 	local data, err = archive:find(room_node, {
 		start = qstart; ["end"] = qend; -- Time range
-		limit = qmax;
+		limit = qmax + 1;
 		before = before; after = after;
 		reverse = reverse;
 		total = true;
@@ -207,15 +209,24 @@
 	if not data then
 		return origin.send(st.error_reply(stanza, "cancel", "internal-server-error"));
 	end
-	local count = err;
+	local total = err;
 
 	origin.send(st.reply(stanza))
 	local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
 
+	local results = {};
+
 	-- Wrap it in stuff and deliver
-	local fwd_st, first, last;
+	local first, last;
+	local count = 0;
+	local complete = "true";
 	for id, item, when in data do
-		fwd_st = st.message(msg_reply_attr)
+		count = count + 1;
+		if count > qmax then
+			complete = nil;
+			break;
+		end
+		local fwd_st = st.message(msg_reply_attr)
 			:tag("result", { xmlns = xmlns_mam, queryid = qid, id = id })
 				:tag("forwarded", { xmlns = xmlns_forward })
 					:tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up();
@@ -229,16 +240,27 @@
 		if not first then first = id; end
 		last = id;
 
-		origin.send(fwd_st);
+		if reverse then
+			results[count] = fwd_st;
+		else
+			origin.send(fwd_st);
+		end
 	end
+	if reverse then
+		for i = #results, 1, -1 do
+			origin.send(results[i]);
+		end
+	end
+
 	-- That's all folks!
 	module:log("debug", "Archive query %s completed", tostring(qid));
 
 	if reverse then first, last = last, first; end
-	return origin.send(st.message(msg_reply_attr)
-		:tag("fin", { xmlns = xmlns_mam, queryid = qid })
+	origin.send(st.message(msg_reply_attr)
+		:tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
 			:add_child(rsm.generate {
-				first = first, last = last, count = count }));
+				first = first, last = last, count = total }));
+	return true;
 end);
 
 module:hook("muc-get-history", function (event)
@@ -360,3 +382,7 @@
 -- And role/affiliation changes?
 
 module:add_feature(xmlns_mam);
+
+module:hook("muc-disco#info", function(event)
+	event.reply:tag("feature", {var=xmlns_mam}):up();
+end);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_measure_cpu/mod_measure_cpu.lua	Thu May 07 23:39:54 2015 +0200
@@ -0,0 +1,33 @@
+module:set_global();
+
+local measure = require"core.statsmanager".measure;
+local mt = require"util.multitable";
+local get_time = require "socket".gettime;
+local get_clock = os.clock;
+
+local measure_cpu_now = measure("amount", "cpu.percent"); -- Current percentage
+
+local last_cpu_wall, last_cpu_clock;
+module:hook("stats-update", function ()
+	local new_wall, new_clock = get_time(), get_clock();
+	local pc = 0;
+	if last_cpu_wall then
+		pc = 100/((new_wall-last_cpu_wall)/(new_clock-last_cpu_clock));
+	end
+	last_cpu_wall, last_cpu_clock = new_wall, new_clock;
+
+	measure_cpu_now(pc);
+end);
+
+-- Some metadata for mod_munin
+local munin_meta = mt.new(); munin_meta.data = module:shared"munin/meta";
+local key = "global_cpu_amount";
+
+munin_meta:set(key, "", "graph_args", "--base 1000 -r --lower-limit 0 --upper-limit 100");
+munin_meta:set(key, "", "graph_title", "Prosody CPU Usage");
+munin_meta:set(key, "", "graph_vlabel", "%");
+munin_meta:set(key, "", "graph_category", "cpu");
+
+munin_meta:set(key, "percent", "label", "CPU Usage");
+munin_meta:set(key, "percent", "min", "0");
+
--- a/mod_munin/mod_munin.lua	Thu May 07 22:40:58 2015 +0200
+++ b/mod_munin/mod_munin.lua	Thu May 07 23:39:54 2015 +0200
@@ -58,7 +58,7 @@
 		conn:write(s_format("%s %s\n", k, value));
 	end
 	for _, name, k, value in meta:iter(stat, nil, nil) do
-		if name ~= "" then
+		if name ~= "" and not ignore_stats:contains(name) then
 			conn:write(s_format("%s.%s %s\n", name, k, value));
 		end
 	end
@@ -69,7 +69,9 @@
 	local stat = line:match("%s(%S+)");
 	if not stat then conn:write("# Unknown service\n.\n"); return end
 	for _, name, value in data:iter(stat, nil) do
-		conn:write(s_format("%s.value %s\n", name, tostring(value)));
+		if not ignore_stats:contains(name) then
+			conn:write(s_format("%s.value %.12f\n", name, value));
+		end
 	end
 	conn:write(".\n");
 end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_proctitle/mod_proctitle.lua	Thu May 07 23:39:54 2015 +0200
@@ -0,0 +1,10 @@
+-- Changes the process name to 'prosody' rather than 'lua'/'lua5.1'
+-- Copyright (C) 2015 Rob Hoelz
+--
+-- This file is MIT/X11 licensed.
+
+-- To use this module, you'll need the proctitle Lua library:
+-- https://github.com/hoelzro/lua-proctitle
+local proctitle = require 'proctitle';
+
+proctitle 'prosody';
--- a/mod_s2s_auth_dane/mod_s2s_auth_dane.lua	Thu May 07 22:40:58 2015 +0200
+++ b/mod_s2s_auth_dane/mod_s2s_auth_dane.lua	Thu May 07 23:39:54 2015 +0200
@@ -68,15 +68,19 @@
 	if host_session.dane ~= nil then return end -- Has already done a lookup
 
 	if host_session.direction == "incoming" then
+		if not host_session.from_host then
+			module:log("debug", "Session doesn't have a 'from' host set");
+			return;
+		end
 		-- We don't know what hostname or port to use for Incoming connections
 		-- so we do a SRV lookup and then request TLSA records for each SRV
 		-- Most servers will probably use the same certificate on outgoing
 		-- and incoming connections, so this should work well
 		local name = host_session.from_host and idna_to_ascii(host_session.from_host);
 		if not name then
-			module:log("error", "Could not convert '%s' to ASCII for DNS lookup", tostring(host_session.from_host));
-				return;
-			end
+			module:log("warn", "Could not convert '%s' to ASCII for DNS lookup", tostring(host_session.from_host));
+			return;
+		end
 		host_session.dane = dns_lookup(function (answer, err)
 			host_session.dane = false; -- Mark that we already did the lookup
 
@@ -90,7 +94,7 @@
 				return cb(host_session);
 			end
 
-			local n = #answer
+			local n = answer.n or #answer;
 			if n == 0 then
 				-- No SRV records, we could proceed with the domainname and
 				-- default port but that will currently not work properly since
@@ -101,14 +105,30 @@
 				return cb(host_session); -- No service ... This shouldn't happen?
 			end
 			local srv_hosts = { answer = answer };
-			local dane = {};
-			host_session.dane = dane;
 			host_session.srv_hosts = srv_hosts;
+			local dane;
 			for _, record in ipairs(answer) do
 				t_insert(srv_hosts, record.srv);
 				dns_lookup(function(dane_answer)
 					n = n - 1;
-					if dane_answer.bogus then
+					-- There are three kinds of answers
+					-- Insecure, Secure and Bogus
+					--
+					-- We collect Secure answers for later use
+					--
+					-- Insecure (legacy) answers are simply ignored
+					--
+					-- If we get a Bogus (dnssec error) reply, keep the
+					-- status around.  If there were only bogus replies, the
+					-- connection will be aborted.  If there were at least
+					-- one non-Bogus reply, we proceed.  If none of the
+					-- replies matched, we consider the connection insecure.
+
+					if (dane_answer.bogus or dane_answer.secure) and not dane then
+						-- The first answer we care about
+						-- For services with only one SRV record, this will be the only one
+						dane = dane_answer;
+					elseif dane_answer.bogus then
 						dane.bogus = dane_answer.bogus;
 					elseif dane_answer.secure then
 						for _, dane_record in ipairs(dane_answer) do
@@ -116,15 +136,18 @@
 						end
 					end
 					if n == 0 then
-						if #dane > 0 and dane.bogus then
-							-- Got at least one non-bogus reply,
-							-- This should trigger a failure if one of them did not match
-							host_session.log("warn", "Ignoring bogus replies");
-							dane.bogus = nil;
-						end
-						if #dane == 0 and dane.bogus == nil then
-							-- Got no usable data
-							host_session.dane = false;
+						if dane then
+							host_session.dane = dane;
+							if #dane > 0 and dane.bogus then
+								-- Got at least one non-bogus reply,
+								-- This should trigger a failure if one of them did not match
+								host_session.log("warn", "Ignoring bogus replies");
+								dane.bogus = nil;
+							end
+							if #dane == 0 and dane.bogus == nil then
+								-- Got no usable data
+								host_session.dane = false;
+							end
 						end
 						return cb(host_session);
 					end
--- a/mod_smacks/mod_smacks.lua	Thu May 07 22:40:58 2015 +0200
+++ b/mod_smacks/mod_smacks.lua	Thu May 07 23:39:54 2015 +0200
@@ -124,8 +124,8 @@
 local function wrap_session_in(session, resume)
 	if not resume then
 		session.handled_stanza_count = 0;
-		add_filter(session, "stanzas/in", count_incoming_stanzas, 1000);
 	end
+	add_filter(session, "stanzas/in", count_incoming_stanzas, 1000);
 
 	return session;
 end
@@ -328,8 +328,8 @@
 		original_session.conn = session.conn;
 		original_session.send = session.send;
 		original_session.filter = session.filter;
-		original_session.send.filter = session.filter;
-		original_session.data.filter = session.filter;
+		original_session.filter.session = original_session;
+		original_session.filters = session.filters;
 		original_session.stream = session.stream;
 		original_session.secure = session.secure;
 		original_session.hibernating = nil;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_smacks_offline/mod_smacks_offline.lua	Thu May 07 23:39:54 2015 +0200
@@ -0,0 +1,33 @@
+local t_insert = table.insert;
+
+local mod_smacks = module:depends"smacks"
+
+local function store_unacked_stanzas(session)
+	local queue = session.outgoing_stanza_queue;
+	local replacement_queue = {};
+	session.outgoing_stanza_queue = replacement_queue;
+
+	for _, stanza in ipairs(queue) do
+		if stanza.name == "message" and stanza.attr.xmlns == nil and
+				( stanza.attr.type == "chat" or ( stanza.attr.type or "normal" ) == "normal" ) then
+			module:fire_event("message/offline/handle", { origin = session, stanza = stanza } )
+		else
+			t_insert(replacement_queue, stanza);
+		end
+	end
+end
+
+local handle_unacked_stanzas = mod_smacks.handle_unacked_stanzas;
+
+local host_sessions = prosody.hosts[module.host].sessions;
+mod_smacks.handle_unacked_stanzas = function (session)
+	local sessions = host_sessions[session.username].sessions;
+	if next(sessions) == session.resource and next(sessions, session.resource) == nil then
+		store_unacked_stanzas(session)
+	end
+	return handle_unacked_stanzas(session);
+end
+
+function module.unload()
+	mod_smacks.handle_unacked_stanzas = handle_unacked_stanzas;
+end
--- a/mod_statistics/prosodytop.lua	Thu May 07 22:40:58 2015 +0200
+++ b/mod_statistics/prosodytop.lua	Thu May 07 23:39:54 2015 +0200
@@ -104,7 +104,7 @@
 
 	local conn = require "socket".tcp();
 	assert(conn:connect("localhost", 5782));
-	handler = server.wrapclient(conn, "localhost", 5279, stats_listener, "*a");
+	handler = server.wrapclient(conn, "localhost", 5782, stats_listener, "*a");
 end
 
 return {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_storage_xmlarchive/mod_storage_xmlarchive.lua	Thu May 07 23:39:54 2015 +0200
@@ -0,0 +1,199 @@
+local dm = require "core.storagemanager".olddm;
+local hmac_sha256 = require"util.hashes".hmac_sha256;
+local st = require"util.stanza";
+local dt = require"util.datetime";
+local new_stream = require "util.xmppstream".new;
+local empty = {};
+
+local function fallocate(f, offset, len)
+	-- This assumes that current position == offset
+	local fake_data = (" "):rep(len);
+	local ok, msg = f:write(fake_data);
+	if not ok then
+		return ok, msg;
+	end
+	f:seek("set", offset);
+	return true;
+end;
+pcall(function()
+	local pposix = require "util.pposix";
+	fallocate = pposix.fallocate or fallocate;
+end);
+
+local archive = {};
+local archive_mt = { __index = archive };
+
+function archive:append(username, _, when, with, data)
+	if getmetatable(data) ~= st.stanza_mt then
+		return nil, "unsupported-datatype";
+	end
+	username = username or "@";
+	data = tostring(data) .. "\n";
+	local day = dt.date(when);
+	local filename = dm.getpath(username.."@"..day, module.host, self.store, "xml", true);
+	local ok, err;
+	local f = io.open(filename, "r+");
+	if not f then
+		f, err = io.open(filename, "w");
+		if not f then return nil, err; end
+		ok, err = dm.list_append(username, module.host, self.store, day);
+		if not ok then return nil, err; end
+	end
+	local offset = f and f:seek("end");
+	ok, err = fallocate(f, offset, #data);
+	if not ok then return nil, err; end
+	f:seek("set", offset);
+	ok, err = f:write(data);
+	if not ok then return nil, err; end
+	ok, err = f:close();
+	if not ok then return nil, err; end
+	local id = day .. "-" .. hmac_sha256(username.."@"..day.."+"..offset, data, true):sub(-16);
+	ok, err = dm.list_append(username.."@"..day, module.host, self.store, { id = id, when = when, with = with, offset = offset, length = #data });
+	if not ok then return nil, err; end
+	return id;
+end
+
+function archive:find(username, query)
+	username = username or "@";
+	query = query or empty;
+
+	local result;
+	local function cb(_, stanza)
+		if result then
+			module:log("warn", "Multiple items in chunk");
+		end
+		result = stanza;
+	end
+
+	local stream_sess = { notopen = true };
+	local stream = new_stream(stream_sess, { handlestanza = cb, stream_ns = "jabber:client"});
+	local dates = dm.list_load(username, module.host, self.store) or empty;
+	stream:feed(st.stanza("stream", { xmlns = "jabber:client" }):top_tag());
+	stream_sess.notopen = nil;
+
+	local limit = query.limit;
+	local start_day, step, last_day = 1, 1, #dates;
+	local count = 0;
+	local rev = query.reverse;
+	local in_range = not (query.after or query.before);
+	if query.after or query.start then
+		local d = query.after and query.after:sub(1, 10) or dt.date(query.start);
+		for i = 1, #dates do
+			if dates[i] == d then
+				start_day = i; break;
+			end
+		end
+	end
+	if query.before or query["end"] then
+		local d = query.before and query.before:sub(1, 10) or dt.date(query["end"]);
+		for i = #dates, 1, -1 do
+			if dates[i] == d then
+				last_day = i; break;
+			end
+		end
+	end
+	if rev then
+		start_day, step, last_day = last_day, -step, start_day;
+	end
+	local items, xmlfile;
+	local first_item, last_item;
+
+	return function ()
+		if limit and count >= limit then xmlfile:close() return; end
+
+		for d = start_day, last_day, step do
+			if d ~= start_day or not items then
+				module:log("debug", "Load items for %s", dates[d]);
+				start_day = d;
+				items = dm.list_load(username .. "@" .. dates[d], module.host, self.store) or empty;
+				if not rev then
+					first_item, last_item = 1, #items;
+				else
+					first_item, last_item = #items, 1;
+				end
+				local ferr;
+				xmlfile, ferr = io.open(dm.getpath(username .. "@" .. dates[d], module.host, self.store, "xml"));
+				if not xmlfile then
+					module:log("error", "Error: %s", ferr);
+					return;
+				end
+			end
+
+			for i = first_item, last_item, step do
+				module:log("debug", "data[%q][%d]", dates[d], i);
+				local item = items[i];
+				if not item then
+					module:log("debug", "data[%q][%d] is nil", dates[d], i);
+					break;
+				end
+				if xmlfile and in_range
+				and (not query.with or item.with == query.with)
+				and (not query.start or item.when >= query.start)
+				and (not query["end"] or item.when <= query["end"]) then
+					count = count + 1;
+					first_item = i + step;
+
+					xmlfile:seek("set", item.offset);
+					local data = xmlfile:read(item.length);
+					local ok, err = stream:feed(data);
+					if not ok then
+						module:log("warn", "Parse error: %s", err);
+					end
+					if result then
+						local stanza = result;
+						result = nil;
+						return item.id, stanza, item.when, item.with;
+					end
+				end
+				if (rev and item.id == query.after) or
+					(not rev and item.id == query.before) then
+					in_range = false;
+					limit = count;
+				end
+				if (rev and item.id == query.before) or
+					(not rev and item.id == query.after) then
+					in_range = true;
+				end
+			end
+		end
+		if xmlfile then
+			xmlfile:close();
+			xmlfile = nil;
+		end
+	end
+end
+
+function archive:delete(username, query)
+	username = username or "@";
+	query = query or empty;
+	if query.with or query.start or query.after then
+		return nil, "not-implemented"; -- Only trimming the oldest messages
+	end
+	local before = query.before or query["end"] or "9999-12-31";
+	if type(before) == "number" then before = dt.date(before); else before = before:sub(1, 10); end
+	local dates = dm.list_load(username, module.host, self.store) or empty;
+	local remaining_dates = {};
+	for d = 1, #dates do
+		if dates[d] >= before then
+			table.insert(remaining_dates, dates[d]);
+		end
+	end
+	table.sort(remaining_dates);
+	local ok, err = dm.list_store(username, module.host, self.store, remaining_dates);
+	if not ok then return ok, err; end
+	for d = 1, #dates do
+		if dates[d] < before then
+			os.remove(dm.getpath(username .. "@" .. dates[d], module.host, self.store, "list"));
+			os.remove(dm.getpath(username .. "@" .. dates[d], module.host, self.store, "xml"));
+		end
+	end
+	return true;
+end
+
+local provider = {};
+function provider:open(store, typ)
+	if typ ~= "archive" then return nil, "unsupported-store"; end
+	return setmetatable({ store = store }, archive_mt);
+end
+
+module:provides("storage", provider);
--- a/mod_watchuntrusted/mod_watchuntrusted.lua	Thu May 07 22:40:58 2015 +0200
+++ b/mod_watchuntrusted/mod_watchuntrusted.lua	Thu May 07 23:39:54 2015 +0200
@@ -9,8 +9,11 @@
 
 local st = require "util.stanza";
 
+local notified_about_already = { };
+
 module:hook_global("s2s-check-certificate", function (event)
     local session, host = event.session, event.host;
+    if not host then return end
     local conn = session.conn:socket();
     local local_host = session.direction == "outgoing" and session.from_host or session.to_host;
 
@@ -25,7 +28,8 @@
             must_secure = false;
     end
 
-    if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") then
+    if must_secure and (session.cert_chain_status ~= "valid" or session.cert_identity_status ~= "valid") and not notified_about_already[host] then
+		notified_about_already[host] = os.time();
 		local _, errors = conn:getpeerverification();
 		local error_message = "";
 
@@ -54,3 +58,10 @@
 	end
 end, -0.5);
 
+module:add_timer(14400, function (now)
+	for host, time in pairs(notified_about_already) do
+		if time + 86400 > now then
+			notified_about_already[host] = nil;
+		end
+	end
+end)