changeset 2211:9aecf7c953ba

mod_http_roster_admin: Also log if the status error is 0 (Connection refused) (Also replaced spaced indentation with tabs, as is the convention)
author JC Brand <jcbrand@minddistrict.com>
date Tue, 14 Jun 2016 14:15:59 +0000
parents 2733cb8c82a9 (diff) 126d79bf079b (current diff)
children 57dcad6543c9
files
diffstat 29 files changed, 325 insertions(+), 64 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Tue Jun 14 14:13:30 2016 +0000
+++ b/.hgtags	Tue Jun 14 14:15:59 2016 +0000
@@ -1,1 +1,2 @@
 2c07bcf56a36d6e74dc0f5422e89bd61f4d31239 0.8-diverge
+1656d4fd71d07aa3a52da89d4daf7723a555e7dd last-google-code-commit
--- a/mod_auth_phpbb3/README.markdown	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_auth_phpbb3/README.markdown	Tue Jun 14 14:15:59 2016 +0000
@@ -10,6 +10,11 @@
 
 This module allows you to authenticate against an PHPBB3 database.
 
+To support the `bcrypt` password hashing algorithm, install
+[bcrypt](https://luarocks.org/modules/mikejsavage/bcrypt) from luarocks:
+
+    luarocks install bcrypt
+
 Configuration
 =============
 
--- a/mod_auth_phpbb3/mod_auth_phpbb3.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_auth_phpbb3/mod_auth_phpbb3.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -10,6 +10,7 @@
 local DBI = require "DBI"
 local md5 = require "util.hashes".md5;
 local uuid_gen = require "util.uuid".generate;
+local have_bcrypt, bcrypt = pcall(require, "bcrypt"); -- available from luarocks
 
 local connection;
 local params = module:get_option("sql");
@@ -176,7 +177,10 @@
 end
 local function phpbbCheckHash(password, hash)
 	if #hash == 32 then return hash == md5(password, true); end -- legacy PHPBB2 hash
-	return #hash == 34 and hashCryptPrivate(password, hash) == hash;
+	if #hash == 34 then return hashCryptPrivate(password, hash) == hash; end
+	if #hash == 60 and have_bcrypt then return bcrypt.verify(password, hash); end
+	module:log("error", "Unsupported hash: %s", hash);
+	return false;
 end
 local function phpbbCreateHash(password)
 	local random = uuid_gen():sub(-6);
--- a/mod_benchmark_storage/mod_benchmark_storage.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_benchmark_storage/mod_benchmark_storage.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -17,11 +17,20 @@
 		return print("Usage: prosodyctl mod_"..module.name.." <storage driver>");
 	end
 
-	sm.initialize_host("localhost");
-	um.initialize_host("localhost");
+	local hostname = "benchmark.invalid";
+	prosody.hosts[hostname] = {
+		type = "local",
+		events = prosody.events,
+		modules = {},
+		sessions = {},
+		users = require "core.usermanager".new_null_provider(hostname)
+	};
+
+	sm.initialize_host(hostname);
+	um.initialize_host(hostname);
 
 	local start_time = gettime();
-	local storage = assert(sm.load_driver("localhost", test_driver));
+	local storage = assert(sm.load_driver(hostname, test_driver));
 	storage = assert(storage:open("benchmark"));
 	-- for i = 1, 23 do
 		-- storage:set(test_users[i], test_data);
@@ -43,7 +52,7 @@
 	for i = 1, 23 do
 		storage:set(test_users[i], nil);
 	end
-	mm.unload("localhost", "storage_"..test_driver);
+	mm.unload(hostname, "storage_"..test_driver);
 	local time_taken = gettime() - start_time;
 	io.write("\27[0G\27[K"); -- Clear current line
 	io.flush();
--- a/mod_block_strangers/mod_block_strangers.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_block_strangers/mod_block_strangers.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -3,12 +3,18 @@
 local jid_split = require "util.jid".split;
 local jid_bare = require "util.jid".bare;
 local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
+local full_sessions = prosody.full_sessions;
+
+local function has_directed_presence(user, jid)
+	local session = full_sessions[user];
+	return session and session.directed[jid];
+end
 
 function check_subscribed(event)
 	local stanza = event.stanza;
 	local to_user, to_host, to_resource = jid_split(stanza.attr.to);
 	local from_jid = jid_bare(stanza.attr.from);
-	if to_user and not is_contact_subscribed(to_user, to_host, from_jid) then
+	if to_user and not has_directed_presence(stanza.attr.to, from_jid) and not is_contact_subscribed(to_user, to_host, from_jid) then
 		if to_resource and stanza.attr.type == "groupchat"
 		or stanza.name == "iq" and (stanza.attr.type == "result" or stanza.attr.type == "error") then
 			return nil; -- Pass through
--- a/mod_cloud_notify/README.markdown	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_cloud_notify/README.markdown	Tue Jun 14 14:15:59 2016 +0000
@@ -2,7 +2,7 @@
 labels:
 - 'Stage-Alpha'
 summary: 'XEP-0357: Cloud push notifications'
-...
+---
 
 Introduction
 ============
@@ -18,6 +18,18 @@
 
 App servers are notified about offline messages.
 
+Configuration
+=============
+
+  Option                            Default   Description
+  --------------------------------- --------- ------------------------------------------------------------------
+  `push_notification_with_body`     `false`   Whether or not to send the message body to remote pubsub node.
+  `push_notification_with_sender`   `false`   Whether or not to send the message sender to remote pubsub node.
+
+There are privacy implications for enabling these options because
+plaintext content and metadata will be shared with centralized servers
+(the pubsub node) run by arbitrary app developers.
+
 Installation
 ============
 
--- a/mod_cloud_notify/mod_cloud_notify.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_cloud_notify/mod_cloud_notify.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -11,14 +11,17 @@
 local xmlns_push = "urn:xmpp:push:0";
 
 -- configuration
-local include_body = module:get_option("push_notification_with_body", true);
-local include_sender = module:get_option("push_notification_with_sender", true);
+local include_body = module:get_option("push_notification_with_body", false);
+local include_sender = module:get_option("push_notification_with_sender", false);
 
 -- For keeping state across reloads
-local push_enabled = module:shared("push-enabled-users");
+local push_enabled = module:open_store();
+-- TODO map store would be better here
 
 -- http://xmpp.org/extensions/xep-0357.html#disco
-module:add_feature(xmlns_push);
+module:hook("account-disco-info", function(event)
+	(event.reply or event.stanza):tag("feature", {var=xmlns_push}):up();
+end);
 
 -- http://xmpp.org/extensions/xep-0357.html#enabling
 module:hook("iq-set/self/"..xmlns_push..":enable", function (event)
@@ -36,10 +39,9 @@
 		origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid publish options"));
 		return true;
 	end
-	local user_push_services = push_enabled[origin.username];
+	local user_push_services = push_enabled:get(origin.username);
 	if not user_push_services then
 		user_push_services = {};
-		push_enabled[origin.username] = user_push_services;
 	end
 	user_push_services[push_jid .. "<" .. (push_node or "")] = {
 		jid = push_jid;
@@ -47,7 +49,12 @@
 		count = 0;
 		options = publish_options;
 	};
-	origin.send(st.reply(stanza));
+	local ok, err = push_enabled:set(origin.username, user_push_services);
+	if not ok then
+		origin.send(st.error_reply(stanza, "wait", "internal-server-error"));
+	else
+		origin.send(st.reply(stanza));
+	end
 	return true;
 end);
 
@@ -60,7 +67,7 @@
 		origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing jid"));
 		return true;
 	end
-	local user_push_services = push_enabled[origin.username];
+	local user_push_services = push_enabled:get(origin.username);
 	for key, push_info in pairs(user_push_services) do
 		if push_info.jid == push_jid and (not push_node or push_info.node == push_node) then
 			user_push_services[key] = nil;
@@ -82,7 +89,7 @@
 local function handle_notify_request(origin, stanza)
 	local to = stanza.attr.to;
 	local node = to and jid.split(to) or origin.username;
-	local user_push_services = push_enabled[node];
+	local user_push_services = push_enabled:get(node);
 	if not user_push_services then return end
 
 	for _, push_info in pairs(user_push_services) do
@@ -161,7 +168,7 @@
 
 
 module:hook("message/offline/broadcast", function(event)
-	local user_push_services = push_enabled[event.origin.username];
+	local user_push_services = push_enabled:get(event.origin.username);
 	if not user_push_services then return end
 
 	for _, push_info in pairs(user_push_services) do
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_graceful_shutdown/README.markdown	Tue Jun 14 14:15:59 2016 +0000
@@ -0,0 +1,26 @@
+This module is an experiment about a more graceful shutdown process.
+
+Why
+===
+
+When shutting down, a number of sessions, connections and other things
+are teared down. Due to all these things happening very quickly,
+sometimes eg client unavailable notifications don't make it to all
+remote contacts because the server-to-server connections are teared down
+just after.
+
+How
+===
+
+This module works by breaking the shutdown process into separate steps
+with a brief pause between them.
+
+It goes something like this
+
+1.  Stop accepting new client connections.
+2.  Close all client connections.
+3.  Fire event for everything else.
+4.  Tell `net.server` to quit the main loop.
+5.  ???
+6.  Still here? Kill itself.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_graceful_shutdown/mod_graceful_shutdown.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -0,0 +1,42 @@
+-- luacheck: ignore 122/prosody 113/prosody
+
+local timer = require "util.timer";
+local portman = require "core.portmanager";
+local server = require "net.server";
+
+module:set_global();
+local orig_shutdown = prosody.shutdown;
+
+local pause = module:get_option_number("shutdown_pause", 1);
+
+function module.unload()
+	prosody.shutdown = orig_shutdown;
+end
+
+prosody.shutdown = coroutine.wrap(function (reason, code)
+	prosody.shutdown_reason = reason;
+	prosody.shutdown_code = code;
+	timer.add_task(pause, prosody.shutdown);
+	coroutine.yield(true, "shutdown initiated");
+	-- Close c2s ports, stop accepting new connections
+	portman.deactivate("c2s");
+	-- Close all c2s sessions
+	for _, sess in pairs(prosody.full_sessions) do
+		sess:close{ condition = "system-shutdown", text = reason }
+	end
+	-- Wait for notifications to be sent
+	coroutine.yield(pause);
+	-- Event for everything else to shut down
+	prosody.events.fire_event("server-stopping", {
+		reason = reason;
+		code = code;
+	});
+	-- And wait
+	coroutine.yield(pause);
+	-- And stop main event loop
+	server.setquitting(true);
+	-- And wait for death
+	coroutine.yield(pause * 3);
+	-- you came back? die zombie!
+	os.exit(1);
+end);
--- a/mod_http_logging/mod_http_logging.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_http_logging/mod_http_logging.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -25,7 +25,7 @@
 		local date = os.date("%d/%m/%Y:%H:%M:%S %z");
 		module:log("info", "%s - - [%s] \"%s\" %d %s", ip, date, req, response.status_code, tostring(len));
 	end
-	return server.send_response(response, body);
+	return send_response(response, body);
 end
 
 if module.wrap_object_event then
--- a/mod_http_upload/mod_http_upload.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_http_upload/mod_http_upload.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -50,20 +50,24 @@
 	local request = stanza.tags[1];
 	-- local clients only
 	if origin.type ~= "c2s" then
+		module:log("debug", "Request for upload slot from a %s", origin.type);
 		origin.send(st.error_reply(stanza, "cancel", "not-authorized"));
 		return true;
 	end
 	-- validate
 	local filename = request:get_child_text("filename");
 	if not filename or filename:find("/") then
+		module:log("debug", "Filename %q not allowed", filename or "");
 		origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid filename"));
 		return true;
 	end
 	local filesize = tonumber(request:get_child_text("size"));
 	if not filesize then
+		module:log("debug", "Missing file size");
 		origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing or invalid file size"));
 		return true;
 	elseif filesize > file_size_limit then
+		module:log("debug", "File too large (%d > %d)", filesize, file_size_limit);
 		origin.send(st.error_reply(stanza, "modify", "not-acceptable", "File too large",
 			st.stanza("file-too-large", {xmlns=xmlns_http_upload})
 				:tag("max-size"):text(tostring(file_size_limit))));
@@ -77,25 +81,28 @@
 	reply:tag("get"):text(url):up();
 	reply:tag("put"):text(url):up();
 	origin.send(reply);
+	origin.log("debug", "Given upload slot %q", random);
 	return true;
 end);
 
 -- http service
 local function upload_data(event, path)
 	if not pending_slots[path] then
-		return 401;
+		module:log("warn", "Attempt to upload to unknown slot %q", path);
+		return; -- 404
 	end
 	local random, filename = path:match("^([^/]+)/([^/]+)$");
 	if not random then
+		module:log("warn", "Invalid file path %q", path);
 		return 400;
 	end
 	if #event.request.body > file_size_limit then
-		module:log("error", "Uploaded file too large %d bytes", #event.request.body);
+		module:log("warn", "Uploaded file too large %d bytes", #event.request.body);
 		return 400;
 	end
 	local dirname = join_path(storage_path, random);
 	if not lfs.mkdir(dirname) then
-		module:log("error", "Could not create directory %s for upload", dirname);
+		module:log("warn", "Could not create directory %s for upload", dirname);
 		return 500;
 	end
 	local full_filename = join_path(dirname, filename);
--- a/mod_idlecompat/mod_idlecompat.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_idlecompat/mod_idlecompat.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -1,6 +1,6 @@
 -- Last User Interaction in Presence via Last Activity compatibility module
 -- http://xmpp.org/extensions/xep-0319.html
--- http://xmpp.org/extensions/xep-0012.html
+-- http://xmpp.org/extensions/xep-0256.html
 -- Copyright (C) 2014 Tobias Markmann
 --
 -- This file is MIT/X11 licensed.
@@ -11,7 +11,7 @@
 local function on_presence(event)
 	local stanza = event.stanza;
 
-	local last_activity = stanza.name == "presence" and stanza:get_child("query", "jabber:iq:last") or false;
+	local last_activity = stanza:get_child("query", "jabber:iq:last");
 	local has_idle = stanza:get_child("idle", "urn:xmpp:idle:1");
 	if last_activity and not has_idle then
 		module:log("debug", "Adding XEP-0319 tag from Last Activity.");
@@ -28,4 +28,4 @@
 -- outgoing
 module:hook("pre-presence/bare", on_presence, 900);
 module:hook("pre-presence/full", on_presence, 900);
-module:hook("pre-presence/host", on_presence, 900);
\ No newline at end of file
+module:hook("pre-presence/host", on_presence, 900);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_list_inactive/README.markdown	Tue Jun 14 14:15:59 2016 +0000
@@ -0,0 +1,35 @@
+Description
+===========
+
+This module lists those users, who haven't used their account in a
+defined time-frame.
+
+Dependencies
+============
+
+[mod_lastlog] must be enabled to collect the data used by this module.
+
+Usage
+=====
+
+    prosodyctl mod_list_inactive example.com time [format]
+
+Time is a number followed by 'day', 'week', 'month' or 'year'
+
+Formats are:
+
+  --------- ------------------------------------------------
+  delete    `user:delete"user@example.com" -- last action`
+  default   `user@example.com`
+  event     `user@example.com last action`
+  --------- ------------------------------------------------
+
+Example
+=======
+
+    prosodyctl mod_list_inactive example.com 1year
+
+Help
+====
+
+    prosodyctl mod_list_inactive
--- a/mod_log_mark/mod_log_mark.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_log_mark/mod_log_mark.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -1,6 +1,8 @@
+module:set_global();
+
 local log = _G.log;
 
-module:add_timer(60-os.date("%S"), function ()
+module:add_timer(60-os.date("%S"), function (now)
 	log("info", "-- MARK --");
-	return 60;
+	return 90 - ((now + 30) % 60);
 end);
--- a/mod_log_rate/mod_log_rate.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_log_rate/mod_log_rate.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -1,13 +1,11 @@
 module:set_global();
 
-local measure = require"core.statsmanager".measure;
-
 local function sink_maker(config)
 	local levels = {
-		debug = measure("rate", "log.debug");
-		info = measure("rate", "log.info");
-		warn = measure("rate", "log.warn");
-		error = measure("rate", "log.error");
+		debug = measure("log.debug", "rate");
+		info = measure("log.info", "rate");
+		warn = measure("log.warn", "rate");
+		error = measure("log.error", "rate");
 	};
 	return function (_, level)
 		return levels[level]();
--- a/mod_mam_muc/mod_mam_muc.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_mam_muc/mod_mam_muc.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -366,7 +366,7 @@
 	if stanza.attr.type then
 		with = with .. "<" .. stanza.attr.type
 	end
-	archive:append(room, nil, time_now(), with, stanza);
+	archive:append(room, nil, stanza, time_now(), with);
 end
 
 module:hook("muc-broadcast-message", function (event)
--- a/mod_munin/README.markdown	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_munin/README.markdown	Tue Jun 14 14:15:59 2016 +0000
@@ -27,6 +27,11 @@
 munin_node_name = "xmpp.example.com"
 ```
 
+You will also want to enable statistics collection by setting:
+
+```lua
+statistics_interval = 300 -- every 5 minutes, same as munin
+```
 
 ## Summary
 
@@ -63,8 +68,8 @@
 
 ``` ini
 [xmpp.example.com]
-address = xmpp.example.com
-port = 4949
+address xmpp.example.com
+port 4949
 ```
 
 You can leave out `address` if it equal to the name in brackets, and
--- a/mod_pep_vcard_avatar/mod_pep_vcard_avatar.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_pep_vcard_avatar/mod_pep_vcard_avatar.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -8,6 +8,7 @@
 --
 
 local st = require "util.stanza"
+local jid = require "util.jid";
 local base64 = require"util.encodings".base64;
 local sha1 = require"util.hashes".sha1;
 
@@ -56,7 +57,7 @@
 
 local function publish(session, node, id, item)
 	return module:fire_event("pep-publish-item", {
-		actor = true, session = session, node = node, id = id, item = item;
+		actor = true, user = jid.bare(session.full_jid), session = session, node = node, id = id, item = item;
 	});
 end
 
--- a/mod_presence_cache/mod_presence_cache.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_presence_cache/mod_presence_cache.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -1,3 +1,8 @@
+-- XEP-0280: Message Carbons implementation for Prosody
+-- Copyright (C) 2015-2016 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+
 local is_contact_subscribed = require"core.rostermanager".is_contact_subscribed;
 local jid_split = require"util.jid".split;
 local jid_bare = require"util.jid".bare;
@@ -7,7 +12,7 @@
 
 local cache_size = module:get_option_number("presence_cache_size", 100);
 
-local bare_cache = {}; -- [username NUL bare_jid] = { [full_jid] = timestamp, ... }
+local bare_cache = {}; -- [username NUL bare_jid] = { [full_jid] = { timestamp, ... } }
 
 local function on_evict(cache_key)
 	local bare_cache_key = cache_key:match("^%Z+%z[^/]+");
@@ -51,11 +56,14 @@
 			return;
 		end
 
-		local stamp = datetime.datetime();
+		local presence_bits = {
+			stamp = datetime.datetime();
+			show = stanza:get_child_text("show");
+		};
 		if jids then
-			jids[contact_full] = stamp;
+			jids[contact_full] = presence_bits;
 		else
-			jids = { [contact_full] = stamp };
+			jids = { [contact_full] = presence_bits };
 			bare_cache[bare_cache_key] = jids;
 		end
 		presence_cache:set(cache_key, true);
@@ -76,9 +84,14 @@
 
 	local cached = bare_cache[bare_cache_key];
 	if not cached then return end
-	for jid, stamp in pairs(cached) do
+	for jid, presence_bits in pairs(cached) do
 		local presence = st.presence({ to = origin.full_jid, from = jid })
-			:tag("delay", { xmlns = "urn:xmpp:delay", from = module.host, stamp = stamp }):up();
+		if presence_bits.show then
+			presence:tag("show"):text(presence_bits.show):up();
+		end
+		if presence_bits.stamp then
+			presence:tag("delay", { xmlns = "urn:xmpp:delay", from = module.host, stamp = presence_bits.stamp }):up();
+		end
 		origin.send(presence);
 	end
 end
--- a/mod_register_dnsbl/mod_register_dnsbl.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_register_dnsbl/mod_register_dnsbl.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -18,7 +18,7 @@
 		local log = session.log;
 		adns.lookup(function (reply)
 			if reply and reply[1] then
-				log("warn", "Registration from IP %s found in RBL", ip);
+				log("warn", "Account %s@%s registered from IP %s found in RBL (%s)", event.username, event.host or module.host, ip, reply[1].a);
 			end
 		end, rbl_ip);
 	end
--- a/mod_s2s_auth_dane/mod_s2s_auth_dane.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_s2s_auth_dane/mod_s2s_auth_dane.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -19,6 +19,7 @@
 
 module:set_global();
 
+local have_async, async = pcall(require, "util.async");
 local noop = function () end
 local type = type;
 local t_insert = table.insert;
@@ -198,11 +199,38 @@
 	end
 end
 
+local function pause(host_session)
+	host_session.log("debug", "Pausing connection until DANE lookup is completed");
+	host_session.conn:pause()
+end
+
 local function resume(host_session)
 	host_session.log("debug", "DANE lookup completed, resuming connection");
 	host_session.conn:resume()
 end
 
+if have_async then
+	function pause(host_session)
+		host_session.log("debug", "Pausing connection until DANE lookup is completed");
+		local wait, done = async.waiter();
+		host_session._done_waiting_for_dane = done;
+		wait();
+	end
+	local function _resume(_, host_session)
+		if host_session._done_waiting_for_dane then
+			host_session.log("debug", "DANE lookup completed, resuming connection");
+			host_session._done_waiting_for_dane();
+			host_session._done_waiting_for_dane = nil;
+		end
+	end
+	function resume(host_session)
+		-- Something about the way luaunbound calls callbacks is messed up
+		if host_session._done_waiting_for_dane then
+			module:add_timer(0, _resume, host_session);
+		end
+	end
+end
+
 function module.add_host(module)
 	local function on_new_s2s(event)
 		local host_session = event.origin;
@@ -212,10 +240,8 @@
 		if host_session.dane ~= nil then
 			return; -- Already done DANE lookup
 		end
-		if dane_lookup(host_session, resume) then
-			host_session.log("debug", "Pausing connection until DANE lookup is completed");
-			host_session.conn:pause()
-		end
+		dane_lookup(host_session, resume);
+		-- Let it run in parallell until we need to check the cert
 	end
 
 	-- New outgoing connections
@@ -227,7 +253,7 @@
 
 	module:hook("s2s-authenticated", function(event)
 		local session = event.session;
-		if session.dane and next(session.dane) ~= nil and not session.secure then
+		if session.dane and type(session.dane) == "table" and next(session.dane) ~= nil and not session.secure then
 			-- TLSA record but no TLS, not ok.
 			-- TODO Optional?
 			-- Bogus replies should trigger this path
@@ -277,6 +303,12 @@
 	if not cert then return end
 	local log = session.log or module._log;
 	local dane = session.dane;
+	if type(dane) ~= "table" then
+		if dane == nil and dane_lookup(session, resume) then
+			pause(session);
+			dane = session.dane;
+		end
+	end
 	if type(dane) == "table" then
 		local match_found, supported_found;
 		for i = 1, #dane do
@@ -375,8 +407,6 @@
 	module:depends("admin_telnet"); -- Make sure the env is there
 	local def_env = module:shared("admin_telnet/env");
 
-	local sessions = module:shared("s2s/sessions");
-
 	local function annotate(session, line)
 		line = line or {};
 		table.insert(line, "--");
--- a/mod_s2s_auth_monkeysphere/mod_s2s_auth_monkeysphere.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_s2s_auth_monkeysphere/mod_s2s_auth_monkeysphere.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -6,10 +6,25 @@
 local json_encode, json_decode = json.encode, json.decode;
 local gettime = require"socket".gettime;
 local serialize = require"util.serialization".serialize;
+local have_async, async = pcall(require, "util.async");
 
 local msva_url = assert(os.getenv"MONKEYSPHERE_VALIDATION_AGENT_SOCKET",
 	"MONKEYSPHERE_VALIDATION_AGENT_SOCKET is unset, please set it").."/reviewcert";
 
+if have_async then
+	local _http_request = require "net.http".request;
+	function http_request(url, ex)
+		local wait, done = async.waiter();
+		local content, code, request, response;
+		_http_request(url, ex, function (_content, _code, _request, _response)
+			content, code, request, response = _content, _code, _request, _response;
+			done();
+		end);
+		wait();
+		return content, code, request, response;
+	end
+end
+
 local function check_with_monkeysphere(event)
 	local session, host, cert = event.session, event.host, event.cert;
 	local result = {};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_s2s_auth_samecert/mod_s2s_auth_samecert.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -0,0 +1,15 @@
+module:set_global()
+
+local hosts = prosody.hosts;
+
+module:hook("s2s-check-certificate", function(event)
+	local session, cert = event.session, event.cert;
+	if session.direction ~= "incoming" then return end
+	
+	local outgoing = hosts[session.to_host].s2sout[session.from_host];
+	if outgoing and outgoing.type == "s2sout" and outgoing.secure and outgoing.conn:socket():getpeercertificate():pem() == cert:pem() then
+		session.cert_identity_status = outgoing.cert_identity_status;
+		session.cert_chain_status = outgoing.cert_chain_status;
+		return true;
+	end
+end, 1000);
--- a/mod_s2s_keepalive/README.markdown	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_s2s_keepalive/README.markdown	Tue Jun 14 14:15:59 2016 +0000
@@ -5,7 +5,7 @@
 Introduction
 ============
 
-This module periodically sends XEP-0199 ping requests to remote servers
+This module periodically sends [XEP-0199] ping requests to remote servers
 to keep your connection alive.
 
 Configuration
@@ -15,13 +15,15 @@
 desired servers in `keepalive_servers`. Optionally you can configure
 the ping interval.
 
-    modules_enabled = {
-        ...
-        "s2s_keepalive"
-    }
+``` lua
+modules_enabled = {
+    ...
+    "s2s_keepalive"
+}
 
-    keepalive_servers = { "conference.prosody.im"; "rooms.swift.im" }
-    keepalive_interval = "300" -- (in seconds, default is 60 )
+keepalive_servers = { "conference.prosody.im"; "rooms.swift.im" }
+keepalive_interval = "300" -- (in seconds, default is 60 )
+```
 
 Compatibility
 =============
--- a/mod_statistics/stats.lib.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_statistics/stats.lib.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -32,6 +32,8 @@
 	c2s_sessions, s2s_sessions = module:shared("/*/c2s/sessions", "/*/s2s/sessions");
 end
 
+local memory_update_interval = module:get_option_number("statistics_meminfo_interval", 60);
+
 local stats = {
 	total_users = {
 		get = function () return it.count(it.keys(bare_sessions)); end
@@ -90,20 +92,30 @@
 };
 
 if has_pposix and pposix.meminfo then
+
+	local cached_meminfo, last_cache_update;
+	local function meminfo()
+		if not cached_meminfo or (os.time() - last_cache_update) > memory_update_interval then
+			cached_meminfo = pposix.meminfo();
+			last_cache_update = os.time();
+		end
+		return cached_meminfo;
+	end
+
 	stats.memory_allocated = {
-		get = function () return math.ceil(pposix.meminfo().allocated); end;
+		get = function () return math.ceil(meminfo().allocated); end;
 		tostring = human;
 	}
 	stats.memory_used = {
-		get = function () return math.ceil(pposix.meminfo().used); end;
+		get = function () return math.ceil(meminfo().used); end;
 		tostring = human;
 	}
 	stats.memory_unused = {
-		get = function () return math.ceil(pposix.meminfo().unused); end;
+		get = function () return math.ceil(meminfo().unused); end;
 		tostring = human;
 	}
 	stats.memory_returnable = {
-		get = function () return math.ceil(pposix.meminfo().returnable); end;
+		get = function () return math.ceil(meminfo().returnable); end;
 		tostring = human;
 	}
 end
--- a/mod_storage_appendmap/mod_storage_appendmap.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_storage_appendmap/mod_storage_appendmap.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -32,7 +32,7 @@
 function map:set_keys(user, keyvalues)
 	local keys, values = {}, {};
 	if _VERSION == "Lua 5.1" then
-		assert(not keyvalues._ENV, "'_ENV' is a restricted key");
+		assert(keyvalues._ENV == nil, "'_ENV' is a restricted key");
 	end
 	for key, value in pairs(keyvalues) do
 		module:log("debug", "user %s sets %q to %s", user, key, tostring(value))
--- a/mod_storage_memory/mod_storage_memory.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_storage_memory/mod_storage_memory.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -41,6 +41,20 @@
 	return true;
 end
 
+map_store.remove = {};
+function map_store:set_keys(username, keydatas)
+	local userstore = self.store[username or NULL];
+	if userstore == nil then
+		userstore = {};
+		self.store[username or NULL] = userstore;
+	end
+	for k,v in pairs(keydatas) do
+		if v == self.remove then v = nil; end
+		userstore[k] = v;
+	end
+	return true;
+end
+
 local archive_store = {};
 archive_store.__index = archive_store;
 
--- a/mod_storage_xmlarchive/mod_storage_xmlarchive.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_storage_xmlarchive/mod_storage_xmlarchive.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -61,7 +61,7 @@
 	end
 
 	local stream_sess = { notopen = true };
-	local stream = new_stream(stream_sess, { handlestanza = cb, stream_ns = "jabber:client"});
+	local stream = new_stream(stream_sess, { handlestanza = cb, stream_ns = "jabber:client", default_ns = "jabber:client" });
 	local dates = dm.list_load(username, module.host, self.store) or empty;
 	local function reset_stream()
 		stream:reset();
--- a/mod_vjud/mod_vjud.lua	Tue Jun 14 14:13:30 2016 +0000
+++ b/mod_vjud/mod_vjud.lua	Tue Jun 14 14:15:59 2016 +0000
@@ -59,7 +59,7 @@
 };
 
 local function get_user_vcard(user, host)
-	local vCard err = dm_load(user, host or base_host, "vcard");
+	local vCard, err = dm_load(user, host or base_host, "vcard");
 	if not vCard then return nil, err; end
 	vCard = st.deserialize(vCard);
 	vCard, err = vcard.from_xep54(vCard);