File prosody-0.11-upstream-fixes.patch of Package prosody

# Upstream fixes on 0.11 branch since 0.11.2 release
# hg export --git -r 0.11%0.11.2 > prosody-0.11-upstream-fixes.patch
# Up to 9712:7a36b7ac309b
#
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1547122042 -3600
#      Thu Jan 10 13:07:22 2019 +0100
# Branch 0.11
# Node ID 2e07d2f7159941a23084f6ead0c2aa7e86d3823c
# Parent  c6cf32de940dcaf0fb27cf20214a87da32ee8153
mod_bosh: Handle missing wait attribute (fixes #1288)

250855633092 did not fix this completely.

diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua
--- a/plugins/mod_bosh.lua
+++ b/plugins/mod_bosh.lua
@@ -277,7 +277,7 @@
 			response:send(tostring(close_reply));
 			return;
 		end
-		if not rid or (not wait and attr.wait or wait < 0 or wait % 1 ~= 0) then
+		if not rid or (not attr.wait or not wait or wait < 0 or wait % 1 ~= 0) then
 			log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", tostring(attr.rid), tostring(attr.wait));
 			local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate",
 				["xmlns:stream"] = xmlns_streams, condition = "bad-request" });
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1547683479 -3600
#      Thu Jan 17 01:04:39 2019 +0100
# Branch 0.11
# Node ID f47b2ab877d0f2975d7f88b501a510b20cfce5ec
# Parent  2e07d2f7159941a23084f6ead0c2aa7e86d3823c
mod_presence: Revert empty 'to' attribute of presence before presence/initial event (fixes #1296)

diff --git a/plugins/mod_presence.lua b/plugins/mod_presence.lua
--- a/plugins/mod_presence.lua
+++ b/plugins/mod_presence.lua
@@ -63,6 +63,7 @@
 			core_post_stanza(origin, stanza, true);
 		end
 	end
+	stanza.attr.to = nil;
 	if stanza.attr.type == nil and not origin.presence then -- initial presence
 		module:fire_event("presence/initial", { origin = origin, stanza = stanza } );
 		origin.presence = stanza; -- FIXME repeated later
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1547683547 -3600
#      Thu Jan 17 01:05:47 2019 +0100
# Branch 0.11
# Node ID abd32bc33a9c3ed109f20e5e5be138348a53e489
# Parent  f47b2ab877d0f2975d7f88b501a510b20cfce5ec
mod_motd: Remove redundant conditions

The stanza should always be like this in the presence/initial event

diff --git a/plugins/mod_motd.lua b/plugins/mod_motd.lua
--- a/plugins/mod_motd.lua
+++ b/plugins/mod_motd.lua
@@ -18,12 +18,10 @@
 motd_text = motd_text:gsub("^%s*(.-)%s*$", "%1"):gsub("\n[ \t]+", "\n"); -- Strip indentation from the config
 
 module:hook("presence/initial", function (event)
-		local session, stanza = event.origin, event.stanza;
-		if not stanza.attr.type and not stanza.attr.to then
-			local motd_stanza =
-				st.message({ to = session.full_jid, from = motd_jid })
-					:tag("body"):text(motd_text);
-			module:send(motd_stanza);
-			module:log("debug", "MOTD send to user %s", session.full_jid);
-		end
+	local session, stanza = event.origin, event.stanza;
+	local motd_stanza =
+		st.message({ to = session.full_jid, from = motd_jid })
+			:tag("body"):text(motd_text);
+	module:send(motd_stanza);
+	module:log("debug", "MOTD send to user %s", session.full_jid);
 end, 1);
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1547684129 -3600
#      Thu Jan 17 01:15:29 2019 +0100
# Branch 0.11
# Node ID ca0473cadd1c110daaecf726c4c98590c5187bd4
# Parent  abd32bc33a9c3ed109f20e5e5be138348a53e489
mod_motd: Remove unused variable [luacheck]

diff --git a/plugins/mod_motd.lua b/plugins/mod_motd.lua
--- a/plugins/mod_motd.lua
+++ b/plugins/mod_motd.lua
@@ -18,7 +18,7 @@
 motd_text = motd_text:gsub("^%s*(.-)%s*$", "%1"):gsub("\n[ \t]+", "\n"); -- Strip indentation from the config
 
 module:hook("presence/initial", function (event)
-	local session, stanza = event.origin, event.stanza;
+	local session = event.origin;
 	local motd_stanza =
 		st.message({ to = session.full_jid, from = motd_jid })
 			:tag("body"):text(motd_text);
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1547833024 -3600
#      Fri Jan 18 18:37:04 2019 +0100
# Branch 0.11
# Node ID 5c5117d41133e3ac4ce412d6f72157fdeb1f6551
# Parent  ca0473cadd1c110daaecf726c4c98590c5187bd4
mod_websocket: Include the value of cross_domain_websocket in debug message

diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua
--- a/plugins/mod_websocket.lua
+++ b/plugins/mod_websocket.lua
@@ -159,7 +159,7 @@
 	end
 
 	if not check_origin(request.headers.origin or "") then
-		module:log("debug", "Origin %s is not allowed by 'cross_domain_websocket'", request.headers.origin or "(missing header)");
+		module:log("debug", "Origin %s is not allowed by 'cross_domain_websocket' [ %s ]", request.headers.origin or "(missing header)", cross_domain);
 		return 403;
 	end
 
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1548150060 -3600
#      Tue Jan 22 10:41:00 2019 +0100
# Branch 0.11
# Node ID 7bfc4269dc36e4f51ceffa9b0b9c71388a693448
# Parent  5c5117d41133e3ac4ce412d6f72157fdeb1f6551
mod_websocket: Log an error if cross_domain_websocket = true is set in a VirtualHost section

diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua
--- a/plugins/mod_websocket.lua
+++ b/plugins/mod_websocket.lua
@@ -338,6 +338,11 @@
 		url_components.path = nil;
 		local this_origin = url.build(url_components);
 		local local_cross_domain = module:get_option_set("cross_domain_websocket", { this_origin });
+		if local_cross_domain:contains(true) then
+			module:log("error", "cross_domain_websocket = true only works in the global section");
+			return;
+		end
+
 		-- Don't add / remove something added by another host
 		-- This might be weird with random load order
 		local_cross_domain:exclude(cross_domain);
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1547194845 -3600
#      Fri Jan 11 09:20:45 2019 +0100
# Branch 0.11
# Node ID 00d210deea28fd188e366fdef9ac96929b721351
# Parent  7bfc4269dc36e4f51ceffa9b0b9c71388a693448
mod_vcard_legacy: Factor out conversion from vcard-temp to 4

diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua
--- a/plugins/mod_vcard_legacy.lua
+++ b/plugins/mod_vcard_legacy.lua
@@ -143,19 +143,12 @@
 	_defaults_only = true;
 };
 
-module:hook("iq-set/self/vcard-temp:vCard", function (event)
-	local origin, stanza = event.origin, event.stanza;
-	local pep_service = mod_pep.get_pep_service(origin.username);
-
-	local vcard_temp = stanza.tags[1];
+function vcard_to_pep(vcard_temp)
+	local avatars = {};
 
 	local vcard4 = st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub", id = "current" })
 		:tag("vcard", { xmlns = 'urn:ietf:params:xml:ns:vcard-4.0' });
 
-	if pep_service:purge("urn:xmpp:avatar:metadata", origin.full_jid) then
-		pep_service:purge("urn:xmpp:avatar:data", origin.full_jid);
-	end
-
 	vcard4:tag("fn"):text_tag("text", vcard_temp:get_child_text("FN")):up();
 
 	local N = vcard_temp:get_child("N");
@@ -248,19 +241,39 @@
 					:tag("data", { xmlns="urn:xmpp:avatar:data" })
 						:text(avatar_payload);
 
-				local ok, err = pep_service:publish("urn:xmpp:avatar:data", origin.full_jid, avatar_hash, avatar_data, node_defaults)
-				if ok then
-					ok, err = pep_service:publish("urn:xmpp:avatar:metadata", origin.full_jid, avatar_hash, avatar_meta, node_defaults);
-				end
-				if not ok then
-					handle_error(origin, stanza, err);
-					return true;
-				end
+				table.insert(avatars, { hash = avatar_hash, meta = avatar_meta, data = avatar_data });
 			end
 		end
 	end
+	return vcard4, avatars;
+end
 
-	local ok, err = pep_service:publish("urn:xmpp:vcard4", origin.full_jid, "current", vcard4, node_defaults);
+function save_to_pep(pep_service, actor, vcard4, avatars)
+
+	if pep_service:purge("urn:xmpp:avatar:metadata", actor) then
+		pep_service:purge("urn:xmpp:avatar:data", actor);
+	end
+
+	for _, avatar in ipairs(avatars) do
+		local ok, err = pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, node_defaults)
+		if ok then
+			ok, err = pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, node_defaults);
+		end
+		if not ok then
+			return ok, err;
+		end
+	end
+
+	return pep_service:publish("urn:xmpp:vcard4", actor, "current", vcard4, node_defaults);
+end
+
+module:hook("iq-set/self/vcard-temp:vCard", function (event)
+	local origin, stanza = event.origin, event.stanza;
+	local pep_service = mod_pep.get_pep_service(origin.username);
+
+	local vcard_temp = stanza.tags[1];
+
+	local ok, err = save_to_pep(pep_service, origin.full_jid, vcard_to_pep(vcard_temp));
 	if ok then
 		origin.send(st.reply(stanza));
 	else
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1547194848 -3600
#      Fri Jan 11 09:20:48 2019 +0100
# Branch 0.11
# Node ID 5648e8733569e1df0038a564ede0c4cdfe0c0832
# Parent  00d210deea28fd188e366fdef9ac96929b721351
mod_vcard_legacy: Adapt node defaults to number of avatars

diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua
--- a/plugins/mod_vcard_legacy.lua
+++ b/plugins/mod_vcard_legacy.lua
@@ -254,10 +254,18 @@
 		pep_service:purge("urn:xmpp:avatar:data", actor);
 	end
 
+	local avatar_defaults = node_defaults;
+	if #avatars > 1 then
+		avatar_defaults = {};
+		for k,v in pairs(node_defaults) do
+			avatar_defaults[k] = v;
+		end
+		avatar_defaults.max_items = #avatars;
+	end
 	for _, avatar in ipairs(avatars) do
-		local ok, err = pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, node_defaults)
+		local ok, err = pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, avatar_defaults)
 		if ok then
-			ok, err = pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, node_defaults);
+			ok, err = pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, avatar_defaults);
 		end
 		if not ok then
 			return ok, err;
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1547131142 -3600
#      Thu Jan 10 15:39:02 2019 +0100
# Branch 0.11
# Node ID 2d8e2de36654f7372a32de4656f8b87955aa9936
# Parent  5648e8733569e1df0038a564ede0c4cdfe0c0832
mod_vcard_legacy: Upgrade vcard-temp on login (fixes #1289)

diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua
--- a/plugins/mod_vcard_legacy.lua
+++ b/plugins/mod_vcard_legacy.lua
@@ -130,10 +130,6 @@
 		end
 	end
 
-	if not vcard_temp.tags[1] then
-		vcard_temp = st.deserialize(vcards:get(jid_split(stanza.attr.to) or origin.username)) or vcard_temp;
-	end
-
 	origin.send(st.reply(stanza):add_child(vcard_temp));
 	return true;
 end);
@@ -310,3 +306,21 @@
 module:hook("pre-presence/full", inject_xep153, 1);
 module:hook("pre-presence/bare", inject_xep153, 1);
 module:hook("pre-presence/host", inject_xep153, 1);
+
+module:hook("resource-bind", function (event)
+	local session = event.session;
+	local username = session.username;
+	local vcard_temp = vcards:get(username);
+	if not vcard_temp then
+		session.log("debug", "No legacy vCard to migrate or already migrated");
+		return;
+	end
+	vcard_temp = st.deserialize(vcard_temp);
+	local pep_service = mod_pep.get_pep_service(username);
+	local ok, err = save_to_pep(pep_service, true, vcard_to_pep(vcard_temp));
+	if ok and vcards:set(username, nil) then
+		session.log("info", "Migrated vCard-temp to PEP");
+	else
+		session.log("info", "Failed to migrate vCard-temp to PEP: %s", err or "problem emptying 'vcard' store");
+	end
+end);
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1548454873 -3600
#      Fri Jan 25 23:21:13 2019 +0100
# Branch 0.11
# Node ID ce6eb482eb50b66dc8b7d275ab90beaae855e806
# Parent  2d8e2de36654f7372a32de4656f8b87955aa9936
mod_vcard_legacy: Add some missing semicolons

diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua
--- a/plugins/mod_vcard_legacy.lua
+++ b/plugins/mod_vcard_legacy.lua
@@ -1,4 +1,4 @@
-local st = require "util.stanza"
+local st = require "util.stanza";
 local jid_split = require "util.jid".split;
 
 local mod_pep = module:depends("pep");
@@ -259,7 +259,7 @@
 		avatar_defaults.max_items = #avatars;
 	end
 	for _, avatar in ipairs(avatars) do
-		local ok, err = pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, avatar_defaults)
+		local ok, err = pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, avatar_defaults);
 		if ok then
 			ok, err = pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, avatar_defaults);
 		end
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1548455243 -3600
#      Fri Jan 25 23:27:23 2019 +0100
# Branch 0.11
# Node ID 330a937e085ea2d146e8b689f7b62875cba08781
# Parent  ce6eb482eb50b66dc8b7d275ab90beaae855e806
mod_vcard_legacy: Don't overwrite existing PEP data

diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua
--- a/plugins/mod_vcard_legacy.lua
+++ b/plugins/mod_vcard_legacy.lua
@@ -315,8 +315,15 @@
 		session.log("debug", "No legacy vCard to migrate or already migrated");
 		return;
 	end
+	local pep_service = mod_pep.get_pep_service(username);
+	if pep_service:get_last_item("urn:xmpp:vcard4", true)
+	or pep_service:get_last_item("urn:xmpp:avatar:metadata", true)
+	or pep_service:get_last_item("urn:xmpp:avatar:data", true) then
+		session.log("debug", "Already PEP data, not overwriting with migrated data");
+		vcards:set(username, nil);
+		return;
+	end
 	vcard_temp = st.deserialize(vcard_temp);
-	local pep_service = mod_pep.get_pep_service(username);
 	local ok, err = save_to_pep(pep_service, true, vcard_to_pep(vcard_temp));
 	if ok and vcards:set(username, nil) then
 		session.log("info", "Migrated vCard-temp to PEP");
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1548455462 -3600
#      Fri Jan 25 23:31:02 2019 +0100
# Branch 0.11
# Node ID 071aaaa5cb341219572dadd8a799fcf66e47d1a5
# Parent  330a937e085ea2d146e8b689f7b62875cba08781
mod_vcard_legacy: Allow disabling vcard conversion

Once everyone has been migrated it might be nice to skip these checks

diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua
--- a/plugins/mod_vcard_legacy.lua
+++ b/plugins/mod_vcard_legacy.lua
@@ -307,6 +307,7 @@
 module:hook("pre-presence/bare", inject_xep153, 1);
 module:hook("pre-presence/host", inject_xep153, 1);
 
+if module:get_option_boolean("upgrade_legacy_vcards", true) then
 module:hook("resource-bind", function (event)
 	local session = event.session;
 	local username = session.username;
@@ -331,3 +332,4 @@
 		session.log("info", "Failed to migrate vCard-temp to PEP: %s", err or "problem emptying 'vcard' store");
 	end
 end);
+end
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1548505946 -3600
#      Sat Jan 26 13:32:26 2019 +0100
# Branch 0.11
# Node ID 5eb4ef537e983e567209a2dc0d7e88747310c3cc
# Parent  071aaaa5cb341219572dadd8a799fcf66e47d1a5
mod_vcard_legacy: Handle partial migration

Eg in case the user already published an avatar, migrate only the vcard,
and vice versa.

diff --git a/plugins/mod_vcard_legacy.lua b/plugins/mod_vcard_legacy.lua
--- a/plugins/mod_vcard_legacy.lua
+++ b/plugins/mod_vcard_legacy.lua
@@ -245,30 +245,36 @@
 end
 
 function save_to_pep(pep_service, actor, vcard4, avatars)
+	if avatars then
 
-	if pep_service:purge("urn:xmpp:avatar:metadata", actor) then
-		pep_service:purge("urn:xmpp:avatar:data", actor);
-	end
+		if pep_service:purge("urn:xmpp:avatar:metadata", actor) then
+			pep_service:purge("urn:xmpp:avatar:data", actor);
+		end
 
-	local avatar_defaults = node_defaults;
-	if #avatars > 1 then
-		avatar_defaults = {};
-		for k,v in pairs(node_defaults) do
-			avatar_defaults[k] = v;
+		local avatar_defaults = node_defaults;
+		if #avatars > 1 then
+			avatar_defaults = {};
+			for k,v in pairs(node_defaults) do
+				avatar_defaults[k] = v;
+			end
+			avatar_defaults.max_items = #avatars;
 		end
-		avatar_defaults.max_items = #avatars;
-	end
-	for _, avatar in ipairs(avatars) do
-		local ok, err = pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, avatar_defaults);
-		if ok then
-			ok, err = pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, avatar_defaults);
-		end
-		if not ok then
-			return ok, err;
+		for _, avatar in ipairs(avatars) do
+			local ok, err = pep_service:publish("urn:xmpp:avatar:data", actor, avatar.hash, avatar.data, avatar_defaults);
+			if ok then
+				ok, err = pep_service:publish("urn:xmpp:avatar:metadata", actor, avatar.hash, avatar.meta, avatar_defaults);
+			end
+			if not ok then
+				return ok, err;
+			end
 		end
 	end
 
-	return pep_service:publish("urn:xmpp:vcard4", actor, "current", vcard4, node_defaults);
+	if vcard4 then
+		return pep_service:publish("urn:xmpp:vcard4", actor, "current", vcard4, node_defaults);
+	end
+
+	return true;
 end
 
 module:hook("iq-set/self/vcard-temp:vCard", function (event)
@@ -317,15 +323,21 @@
 		return;
 	end
 	local pep_service = mod_pep.get_pep_service(username);
-	if pep_service:get_last_item("urn:xmpp:vcard4", true)
-	or pep_service:get_last_item("urn:xmpp:avatar:metadata", true)
+	vcard_temp = st.deserialize(vcard_temp);
+	local vcard4, avatars = vcard_to_pep(vcard_temp);
+	if pep_service:get_last_item("urn:xmpp:vcard4", true) then
+		vcard4 = nil;
+	end
+	if pep_service:get_last_item("urn:xmpp:avatar:metadata", true)
 	or pep_service:get_last_item("urn:xmpp:avatar:data", true) then
+		avatars = nil;
+	end
+	if not (vcard4 or avatars) then
 		session.log("debug", "Already PEP data, not overwriting with migrated data");
 		vcards:set(username, nil);
 		return;
 	end
-	vcard_temp = st.deserialize(vcard_temp);
-	local ok, err = save_to_pep(pep_service, true, vcard_to_pep(vcard_temp));
+	local ok, err = save_to_pep(pep_service, true, vcard4, avatars);
 	if ok and vcards:set(username, nil) then
 		session.log("info", "Migrated vCard-temp to PEP");
 	else
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1548636061 -3600
#      Mon Jan 28 01:41:01 2019 +0100
# Branch 0.11
# Node ID 7f84d7f77a001033910c9413328789f8aeb9352d
# Parent  5eb4ef537e983e567209a2dc0d7e88747310c3cc
util.pubsub: Add support for requesting multiple specific items (needed for #1305)

diff --git a/spec/util_pubsub_spec.lua b/spec/util_pubsub_spec.lua
--- a/spec/util_pubsub_spec.lua
+++ b/spec/util_pubsub_spec.lua
@@ -170,6 +170,37 @@
 
 	end);
 
+	describe("the thing", function ()
+		randomize(false); -- These tests are ordered
+
+		local service = pubsub.new();
+
+		it("creates a node with some items", function ()
+			assert.truthy(service:create("node", true, { max_items = 3 }));
+			assert.truthy(service:publish("node", true, "1", "item 1"));
+			assert.truthy(service:publish("node", true, "2", "item 2"));
+			assert.truthy(service:publish("node", true, "3", "item 3"));
+		end);
+
+		it("should return the requested item", function ()
+			local ok, ret = service:get_items("node", true, "1");
+			assert.truthy(ok);
+			assert.same({ "1", ["1"] = "item 1" }, ret);
+		end);
+
+		it("should return multiple requested items", function ()
+			local ok, ret = service:get_items("node", true, { "1", "2" });
+			assert.truthy(ok);
+			assert.same({
+				"1",
+				"2",
+				["1"] = "item 1",
+				["2"] = "item 2",
+			}, ret);
+		end);
+	end);
+
+
 	describe("node config", function ()
 		local service;
 		before_each(function ()
diff --git a/util/pubsub.lua b/util/pubsub.lua
--- a/util/pubsub.lua
+++ b/util/pubsub.lua
@@ -600,7 +600,7 @@
 	return true
 end
 
-function service:get_items(node, actor, id) --> (true, { id, [id] = node }) or (false, err)
+function service:get_items(node, actor, ids) --> (true, { id, [id] = node }) or (false, err)
 	-- Access checking
 	if not self:may(node, actor, "get_items") then
 		return false, "forbidden";
@@ -610,20 +610,25 @@
 	if not node_obj then
 		return false, "item-not-found";
 	end
-	if id then -- Restrict results to a single specific item
-		local with_id = self.data[node]:get(id);
-		if not with_id then
-			return true, { };
+	if type(ids) == "string" then -- COMPAT see #1305
+		ids = { ids };
+	end
+	local data = {};
+	if ids then
+		for _, key in ipairs(ids) do
+			local value = self.data[node]:get(key);
+			if value then
+				data[#data+1] = key;
+				data[key] = value;
+			end
 		end
-		return true, { id, [id] = with_id };
 	else
-		local data = {}
 		for key, value in self.data[node]:items() do
 			data[#data+1] = key;
 			data[key] = value;
 		end
-		return true, data;
 	end
+	return true, data;
 end
 
 function service:get_last_item(node, actor) --> (true, id, node) or (false, err)
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1548636102 -3600
#      Mon Jan 28 01:41:42 2019 +0100
# Branch 0.11
# Node ID 7aad9eb7f050c87093eaebce3cceff594f3d6526
# Parent  7f84d7f77a001033910c9413328789f8aeb9352d
mod_pubsub: Support requests for multiple items (fixes #1305)

diff --git a/plugins/mod_pubsub/pubsub.lib.lua b/plugins/mod_pubsub/pubsub.lib.lua
--- a/plugins/mod_pubsub/pubsub.lib.lua
+++ b/plugins/mod_pubsub/pubsub.lib.lua
@@ -295,14 +295,20 @@
 
 function handlers.get_items(origin, stanza, items, service)
 	local node = items.attr.node;
-	local item = items:get_child("item");
-	local item_id = item and item.attr.id;
+
+	local requested_items = {};
+	for item in items:childtags("item") do
+		table.insert(requested_items, item.attr.id);
+	end
+	if requested_items[1] == nil then
+		requested_items = nil;
+	end
 
 	if not node then
 		origin.send(pubsub_error_reply(stanza, "nodeid-required"));
 		return true;
 	end
-	local ok, results = service:get_items(node, stanza.attr.from, item_id);
+	local ok, results = service:get_items(node, stanza.attr.from, requested_items);
 	if not ok then
 		origin.send(pubsub_error_reply(stanza, results));
 		return true;
# HG changeset patch
# User Matthew Wild <mwild1@gmail.com>
# Date 1549272463 0
#      Mon Feb 04 09:27:43 2019 +0000
# Branch 0.11
# Node ID 11671a2e07a90ce0536361d26b7b0549014fcf19
# Parent  7aad9eb7f050c87093eaebce3cceff594f3d6526
MUC: Add error message to error bounces when not joined to room

diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua
--- a/plugins/muc/muc.lib.lua
+++ b/plugins/muc/muc.lib.lua
@@ -727,7 +727,7 @@
 	else -- Type is "get" or "set"
 		local current_nick = self:get_occupant_jid(from);
 		if not current_nick then
-			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+			origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat"));
 			return true;
 		end
 		if not occupant then -- recipient not in room
@@ -760,7 +760,7 @@
 	local type = stanza.attr.type;
 	if not current_nick then -- not in room
 		if type ~= "error" then
-			origin.send(st.error_reply(stanza, "cancel", "not-acceptable"));
+			origin.send(st.error_reply(stanza, "cancel", "not-acceptable", "You are not currently connected to this chat"));
 		end
 		return true;
 	end
@@ -1057,7 +1057,7 @@
 module:hook("muc-occupant-groupchat", function(event)
 	local role_rank = valid_roles[event.occupant and event.occupant.role or "none"];
 	if role_rank <= valid_roles.none then
-		event.origin.send(st.error_reply(event.stanza, "cancel", "not-acceptable"));
+		event.origin.send(st.error_reply(event.stanza, "cancel", "not-acceptable", "You are not currently connected to this chat"));
 		return true;
 	elseif role_rank <= valid_roles.visitor then
 		event.origin.send(st.error_reply(event.stanza, "auth", "forbidden"));
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1549544535 -3600
#      Thu Feb 07 14:02:15 2019 +0100
# Branch 0.11
# Node ID 7712488c8e49c94b988fda24d13176a80e8fef1d
# Parent  11671a2e07a90ce0536361d26b7b0549014fcf19
net.server_epoll: Use send_timeout for write timout like other implementations (fixes #1316)

diff --git a/net/server_epoll.lua b/net/server_epoll.lua
--- a/net/server_epoll.lua
+++ b/net/server_epoll.lua
@@ -39,7 +39,7 @@
 	read_timeout = 14 * 60;
 
 	-- How long to wait for a socket to become writable after queuing data to send
-	write_timeout = 60;
+	send_timeout = 60;
 
 	-- Some number possibly influencing how many pending connections can be accepted
 	tcp_backlog = 128;
@@ -266,7 +266,7 @@
 		end
 		return
 	end
-	t = t or cfg.write_timeout;
+	t = t or cfg.send_timeout;
 	if self._writetimeout then
 		self._writetimeout[1] = gettime() + t;
 		resort_timers = true;
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1549736904 -3600
#      Sat Feb 09 19:28:24 2019 +0100
# Branch 0.11
# Node ID 13c2707d2417137ddbb8351e216849b0c0ee21ab
# Parent  7712488c8e49c94b988fda24d13176a80e8fef1d
net.server_epoll: Rename handshake_timeout to ssl_handshake_timeout (fixes #1319)

This is to match server_event, see 430797a8fc81

diff --git a/net/server_epoll.lua b/net/server_epoll.lua
--- a/net/server_epoll.lua
+++ b/net/server_epoll.lua
@@ -54,7 +54,7 @@
 	read_size = 8192;
 
 	-- Timeout used during between steps in TLS handshakes
-	handshake_timeout = 60;
+	ssl_handshake_timeout = 60;
 
 	-- Maximum and minimum amount of time to sleep waiting for events (adjusted for pending timers)
 	max_wait = 86400;
@@ -516,11 +516,11 @@
 	elseif err == "wantread" then
 		log("debug", "TLS handshake on %s to wait until readable", self);
 		self:set(true, false);
-		self:setreadtimeout(cfg.handshake_timeout);
+		self:setreadtimeout(cfg.ssl_handshake_timeout);
 	elseif err == "wantwrite" then
 		log("debug", "TLS handshake on %s to wait until writable", self);
 		self:set(false, true);
-		self:setwritetimeout(cfg.handshake_timeout);
+		self:setwritetimeout(cfg.ssl_handshake_timeout);
 	else
 		log("debug", "TLS handshake error on %s: %s", self, err);
 		self:on("disconnect", err);
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1550817344 -3600
#      Fri Feb 22 07:35:44 2019 +0100
# Branch 0.11
# Node ID fd8aaab6669c075e35035feef9fb0e6e3b024b75
# Parent  13c2707d2417137ddbb8351e216849b0c0ee21ab
mod_pep: Simplify configuration for node data (fixes #1320)

diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua
--- a/plugins/mod_pep.lua
+++ b/plugins/mod_pep.lua
@@ -8,6 +8,7 @@
 local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
 local cache = require "util.cache";
 local set = require "util.set";
+local storagemanager = require "core.storagemanager";
 
 local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
 local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
@@ -113,10 +114,11 @@
 end
 
 local function simple_itemstore(username)
+	local driver = storagemanager.get_driver(module.host, "pep_data");
 	return function (config, node)
 		if config["persist_items"] then
 			module:log("debug", "Creating new persistent item store for user %s, node %q", username, node);
-			local archive = module:open_store("pep_"..node, "archive");
+			local archive = driver:open("pep_"..node, "archive");
 			return lib_pubsub.archive_itemstore(archive, config, username, node, false);
 		else
 			module:log("debug", "Creating new ephemeral item store for user %s, node %q", username, node);
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1550817203 -3600
#      Fri Feb 22 07:33:23 2019 +0100
# Branch 0.11
# Node ID 8e68136cde089f98bc8912b2b777e4e071472839
# Parent  fd8aaab6669c075e35035feef9fb0e6e3b024b75
mod_pubsub: Simplify configuration for node data (see #1302)

diff --git a/plugins/mod_pubsub/mod_pubsub.lua b/plugins/mod_pubsub/mod_pubsub.lua
--- a/plugins/mod_pubsub/mod_pubsub.lua
+++ b/plugins/mod_pubsub/mod_pubsub.lua
@@ -3,6 +3,7 @@
 local jid_bare = require "util.jid".bare;
 local usermanager = require "core.usermanager";
 local new_id = require "util.id".medium;
+local storagemanager = require "core.storagemanager";
 
 local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
 local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
@@ -42,7 +43,8 @@
 local node_store = module:open_store(module.name.."_nodes");
 
 local function create_simple_itemstore(node_config, node_name)
-	local archive = module:open_store("pubsub_"..node_name, "archive");
+	local driver = storagemanager.get_driver(module.host, "pubsub_data");
+	local archive = driver:open("pubsub_"..node_name, "archive");
 	return lib_pubsub.archive_itemstore(archive, node_config, nil, node_name);
 end
 
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1551259238 -3600
#      Wed Feb 27 10:20:38 2019 +0100
# Branch 0.11
# Node ID 96d9c121547b1cbdee7bbee95f04c5792a6c441a
# Parent  8e68136cde089f98bc8912b2b777e4e071472839
mod_storage_memory: Replace query function with one based on storage_internal (fixes #1322)

The :find method in storage_internal works and is easier to read and
understand. Future changes should be simpler to apply to both modules.

diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua
--- a/plugins/mod_storage_memory.lua
+++ b/plugins/mod_storage_memory.lua
@@ -68,46 +68,66 @@
 	return key;
 end
 
-local function archive_iter (a, start, stop, step, limit, when_start, when_end, match_with)
-	local item, when, with;
-	local count = 0;
-	coroutine.yield(true); -- Ready
-	for i = start, stop, step do
-		item = a[i];
-		when, with = item.when, item.with;
-		if when >= when_start and when_end >= when and (not match_with or match_with == with) then
-			coroutine.yield(item.key, item.value(), when, with);
-			count = count + 1;
-			if limit and count >= limit then return end
+function archive_store:find(username, query)
+	local items = self.store[username or NULL];
+	if not items then
+		return function () end, 0;
+	end
+	local count = #items;
+	local i = 0;
+	if query then
+		items = array():append(items);
+		if query.key then
+			items:filter(function (item)
+				return item.key == query.key;
+			end);
+		end
+		if query.with then
+			items:filter(function (item)
+				return item.with == query.with;
+			end);
+		end
+		if query.start then
+			items:filter(function (item)
+				return item.when >= query.start;
+			end);
+		end
+		if query["end"] then
+			items:filter(function (item)
+				return item.when <= query["end"];
+			end);
+		end
+		count = #items;
+		if query.reverse then
+			items:reverse();
+			if query.before then
+				for j = 1, count do
+					if (items[j].key or tostring(j)) == query.before then
+						i = j;
+						break;
+					end
+				end
+			end
+		elseif query.after then
+			for j = 1, count do
+				if (items[j].key or tostring(j)) == query.after then
+					i = j;
+					break;
+				end
+			end
+		end
+		if query.limit and #items - i > query.limit then
+			items[i+query.limit+1] = nil;
 		end
 	end
+	return function ()
+		i = i + 1;
+		local item = items[i];
+		if not item then return; end
+		return item.key, item.value(), item.when, item.with;
+	end, count;
 end
 
-function archive_store:find(username, query)
-	local a = self.store[username or NULL] or {};
-	local start, stop, step = 1, #a, 1;
-	local qstart, qend, qwith = -math.huge, math.huge;
-	local limit;
-	if query then
-		module:log("debug", "query included")
-		if query.reverse then
-			start, stop, step = stop, start, -1;
-			if query.before then
-				start = a[query.before];
-			end
-		elseif query.after then
-			start = a[query.after];
-		end
-		limit = query.limit;
-		qstart = query.start or qstart;
-		qend = query["end"] or qend;
-		qwith = query.with;
-	end
-	if not start then return nil, "invalid-key"; end
-	local iter = coroutine.wrap(archive_iter);
-	iter(a, start, stop, step, limit, qstart, qend, qwith);
-	return iter;
-end
 
 function archive_store:delete(username, query)
 	if not query or next(query) == nil then
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1551259637 -3600
#      Wed Feb 27 10:27:17 2019 +0100
# Branch 0.11
# Node ID 86fe021f16a65e5704cbb2b50000d38172e60fdf
# Parent  96d9c121547b1cbdee7bbee95f04c5792a6c441a
spec: Add test case for #1322

diff --git a/spec/scansion/pubsub_multi_items.scs b/spec/scansion/pubsub_multi_items.scs
new file mode 100644
--- /dev/null
+++ b/spec/scansion/pubsub_multi_items.scs
@@ -0,0 +1,202 @@
+# Pubsub: Requesting multiple specific items from a node (#1322)
+
+[Client] Alice
+	jid: admin@localhost
+	password: password
+
+---------
+
+Alice connects
+
+Alice sends:
+	<presence xmlns:stream="http://etherx.jabber.org/streams" id=":7IoqYcT3191rfk_dZGo2"/>
+
+Alice receives:
+	<presence xmlns:stream="http://etherx.jabber.org/streams" from="${Alice's full JID}" id=":7IoqYcT3191rfk_dZGo2"/>
+
+Alice sends:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":m0SM8Hn5JxP9BJJ_X4Mz" type="set">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub">
+	    <create node="e96caf12-264f-4e5a-988e-00ae191771b6"/>
+	  </pubsub>
+	</iq>
+
+Alice receives:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":m0SM8Hn5JxP9BJJ_X4Mz"/>
+
+Alice sends:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":gwZgEQmzAHcQz-FZOxi-" type="get">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+	    <configure node="e96caf12-264f-4e5a-988e-00ae191771b6"/>
+	  </pubsub>
+	</iq>
+
+Alice receives:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":gwZgEQmzAHcQz-FZOxi-">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+	    <configure node="e96caf12-264f-4e5a-988e-00ae191771b6">
+	      <x xmlns="jabber:x:data" type="form">
+		<field var="FORM_TYPE" type="hidden">
+		  <value>http://jabber.org/protocol/pubsub#node_config</value>
+		</field>
+		<field var="pubsub#title" label="Title" type="text-single"/>
+		<field var="pubsub#description" label="Description" type="text-single"/>
+		<field var="pubsub#type" label="The type of node data, usually specified by the namespace of the payload (if any)" type="text-single"/>
+		<field var="pubsub#max_items" label="Max # of items to persist" type="text-single">
+		  <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:integer"/>
+		  <value>20</value>
+		</field>
+		<field var="pubsub#persist_items" label="Persist items to storage" type="boolean">
+		  <value>0</value>
+		</field>
+		<field var="pubsub#access_model" label="Specify the subscriber model" type="list-single">
+		  <option label="authorize">
+		    <value>authorize</value>
+		  </option>
+		  <option label="open">
+		    <value>open</value>
+		  </option>
+		  <option label="presence">
+		    <value>presence</value>
+		  </option>
+		  <option label="roster">
+		    <value>roster</value>
+		  </option>
+		  <option label="whitelist">
+		    <value>whitelist</value>
+		  </option>
+		  <value>open</value>
+		</field>
+		<field var="pubsub#publish_model" label="Specify the publisher model" type="list-single">
+		  <option label="publishers">
+		    <value>publishers</value>
+		  </option>
+		  <option label="subscribers">
+		    <value>subscribers</value>
+		  </option>
+		  <option label="open">
+		    <value>open</value>
+		  </option>
+		  <value>publishers</value>
+		</field>
+		<field var="pubsub#deliver_notifications" label="Whether to deliver event notifications" type="boolean">
+		  <value>1</value>
+		</field>
+		<field var="pubsub#deliver_payloads" label="Whether to deliver payloads with event notifications" type="boolean">
+		  <value>1</value>
+		</field>
+		<field var="pubsub#notification_type" label="Specify the delivery style for notifications" type="list-single">
+		  <option label="Messages of type normal">
+		    <value>normal</value>
+		  </option>
+		  <option label="Messages of type headline">
+		    <value>headline</value>
+		  </option>
+		  <value>headline</value>
+		</field>
+		<field var="pubsub#notify_delete" label="Whether to notify subscribers when the node is deleted" type="boolean">
+		  <value>1</value>
+		</field>
+		<field var="pubsub#notify_retract" label="Whether to notify subscribers when items are removed from the node" type="boolean">
+		  <value>1</value>
+		</field>
+	      </x>
+	    </configure>
+	  </pubsub>
+	</iq>
+
+Alice sends:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":pfWBQ2MNIq8ieul57Qp7" type="set">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub">
+	    <publish node="e96caf12-264f-4e5a-988e-00ae191771b6">
+	      <item id="20e9eb9e-8acb-436e-a486-40e80400faf1">
+		<foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">foo</foo>
+	      </item>
+	    </publish>
+	  </pubsub>
+	</iq>
+
+Alice receives:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":pfWBQ2MNIq8ieul57Qp7">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub">
+	    <publish node="e96caf12-264f-4e5a-988e-00ae191771b6">
+	      <item id="20e9eb9e-8acb-436e-a486-40e80400faf1"/>
+	    </publish>
+	  </pubsub>
+	</iq>
+
+Alice sends:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":Q5TLT6nsW0HHdkDgrPPe" type="set">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub">
+	    <publish node="e96caf12-264f-4e5a-988e-00ae191771b6">
+	      <item id="4b94623d-1127-41c0-ac47-e283fd890557">
+		<foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">bar</foo>
+	      </item>
+	    </publish>
+	  </pubsub>
+	</iq>
+
+Alice receives:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":Q5TLT6nsW0HHdkDgrPPe">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub">
+	    <publish node="e96caf12-264f-4e5a-988e-00ae191771b6">
+	      <item id="4b94623d-1127-41c0-ac47-e283fd890557"/>
+	    </publish>
+	  </pubsub>
+	</iq>
+
+Alice sends:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":3nvB2E20p1iuM6lOPaP6" type="get">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub">
+	    <items node="e96caf12-264f-4e5a-988e-00ae191771b6">
+	      <item id="20e9eb9e-8acb-436e-a486-40e80400faf1"/>
+	      <item id="4b94623d-1127-41c0-ac47-e283fd890557"/>
+	    </items>
+	  </pubsub>
+	</iq>
+
+Alice receives:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":3nvB2E20p1iuM6lOPaP6">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub">
+	    <items node="e96caf12-264f-4e5a-988e-00ae191771b6">
+	      <item xmlns="http://jabber.org/protocol/pubsub" id="20e9eb9e-8acb-436e-a486-40e80400faf1">
+		<foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">foo</foo>
+	      </item>
+	      <item xmlns="http://jabber.org/protocol/pubsub" id="4b94623d-1127-41c0-ac47-e283fd890557">
+		<foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">bar</foo>
+	      </item>
+	    </items>
+	  </pubsub>
+	</iq>
+
+Alice sends:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":XQdyK54iyOKiJvUoX9t_" type="get">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub">
+	    <items node="e96caf12-264f-4e5a-988e-00ae191771b6"/>
+	  </pubsub>
+	</iq>
+
+Alice receives:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":XQdyK54iyOKiJvUoX9t_">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub">
+	    <items node="e96caf12-264f-4e5a-988e-00ae191771b6">
+	      <item xmlns="http://jabber.org/protocol/pubsub" id="20e9eb9e-8acb-436e-a486-40e80400faf1">
+		<foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">foo</foo>
+	      </item>
+	      <item xmlns="http://jabber.org/protocol/pubsub" id="4b94623d-1127-41c0-ac47-e283fd890557">
+		<foo xmlns="https://zombofant.net/xmlns/aioxmpp#test">bar</foo>
+	      </item>
+	    </items>
+	  </pubsub>
+	</iq>
+
+Alice sends:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="pubsub.localhost" id=":ySGQOz5tnyWT82idwJZP" type="set">
+	  <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+	    <delete node="e96caf12-264f-4e5a-988e-00ae191771b6"/>
+	  </pubsub>
+	</iq>
+
+Alice receives:
+	<iq xmlns:stream="http://etherx.jabber.org/streams" to="${Alice's full JID}" from="pubsub.localhost" type="result" id=":ySGQOz5tnyWT82idwJZP"/>
+
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1551561961 -3600
#      Sat Mar 02 22:26:01 2019 +0100
# Branch 0.11
# Node ID 40ed04014b9784c1f7cef60eab0c18686579f08e
# Parent  86fe021f16a65e5704cbb2b50000d38172e60fdf
mod_storage_memory: Generate ID using standard util (fixes #1326)

The previous method relied on tostring(table) returning the pointer address,
which might not be portable.

diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua
--- a/plugins/mod_storage_memory.lua
+++ b/plugins/mod_storage_memory.lua
@@ -3,6 +3,7 @@
 local envload = require "util.envload".envload;
 local st = require "util.stanza";
 local is_stanza = st.is_stanza or function (s) return getmetatable(s) == st.stanza_mt end
+local new_id = require "util.id".medium;
 
 local auto_purge_enabled = module:get_option_boolean("storage_memory_temporary", false);
 local auto_purge_stores = module:get_option_set("storage_memory_temporary_stores", {});
@@ -56,7 +57,7 @@
 	end
 	local v = { key = key, when = when, with = with, value = value };
 	if not key then
-		key = tostring(a):match"%x+$"..tostring(v):match"%x+$";
+		key = new_id();
 		v.key = key;
 	end
 	if a[key] then
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1551637916 -3600
#      Sun Mar 03 19:31:56 2019 +0100
# Branch 0.11
# Node ID ec353524b739a887ac0695039f3fdc266f88dc3d
# Parent  40ed04014b9784c1f7cef60eab0c18686579f08e
util.pubsub: Validate node configuration on node creation (fixes #1328)

diff --git a/spec/util_pubsub_spec.lua b/spec/util_pubsub_spec.lua
--- a/spec/util_pubsub_spec.lua
+++ b/spec/util_pubsub_spec.lua
@@ -436,4 +436,45 @@
 		end);
 	end);
 
+	describe("node config checking", function ()
+		local service;
+		before_each(function ()
+			service = pubsub.new({
+				check_node_config = function (node, actor, config) -- luacheck: ignore 212
+					return config["max_items"] <= 20;
+				end;
+			});
+		end);
+
+		it("defaults, then configure", function ()
+			local ok, err = service:create("node", true);
+			assert.is_true(ok, err);
+
+			local ok, err = service:set_node_config("node", true, { max_items = 10 });
+			assert.is_true(ok, err);
+
+			local ok, err = service:set_node_config("node", true, { max_items = 100 });
+			assert.falsy(ok, err);
+			assert.equals(err, "not-acceptable");
+		end);
+
+		it("create with ok config, then configure", function ()
+			local ok, err = service:create("node", true, { max_items = 10 });
+			assert.is_true(ok, err);
+
+			local ok, err = service:set_node_config("node", true, { max_items = 100 });
+			assert.falsy(ok, err);
+
+			local ok, err = service:set_node_config("node", true, { max_items = 10 });
+			assert.is_true(ok, err);
+		end);
+
+		it("create with unacceptable config", function ()
+			local ok, err = service:create("node", true, { max_items = 100 });
+			assert.falsy(ok, err);
+		end);
+
+
+	end);
+
 end);
diff --git a/util/pubsub.lua b/util/pubsub.lua
--- a/util/pubsub.lua
+++ b/util/pubsub.lua
@@ -436,10 +436,19 @@
 		return false, "conflict";
 	end
 
+	local config = setmetatable(options or {}, {__index=self.node_defaults});
+
+	if self.config.check_node_config then
+		local ok = self.config.check_node_config(node, actor, config);
+		if not ok then
+			return false, "not-acceptable";
+		end
+	end
+
 	self.nodes[node] = {
 		name = node;
 		subscribers = {};
-		config = setmetatable(options or {}, {__index=self.node_defaults});
+		config = config;
 		affiliations = {};
 	};
 
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1551700591 -3600
#      Mon Mar 04 12:56:31 2019 +0100
# Branch 0.11
# Node ID a44f562e01a53f552687dc1fd784a855de635b73
# Parent  ec353524b739a887ac0695039f3fdc266f88dc3d
mod_muc_mam: Strip the stanza 'to' attribute (fixes #1259)

diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua
--- a/plugins/mod_muc_mam.lua
+++ b/plugins/mod_muc_mam.lua
@@ -208,6 +208,7 @@
 		if not is_stanza(item) then
 			item = st.deserialize(item);
 		end
+		item.attr.to = nil;
 		item.attr.xmlns = "jabber:client";
 		fwd_st:add_child(item);
 
@@ -329,6 +330,7 @@
 
 	if stanza.name == "message" and self:get_whois() == "anyone" then
 		stored_stanza = st.clone(stanza);
+		stored_stanza.attr.to = nil;
 		local actor = jid_bare(self._occupants[stanza.attr.from].jid);
 		local affiliation = self:get_affiliation(actor) or "none";
 		local role = self:get_role(actor) or self:get_default_role(affiliation);
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1551700662 -3600
#      Mon Mar 04 12:57:42 2019 +0100
# Branch 0.11
# Node ID 6f39be2e0be540e3f968182171753d662a1a78ec
# Parent  a44f562e01a53f552687dc1fd784a855de635b73
mod_muc_mam: Move a comment to the line it describes

diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua
--- a/plugins/mod_muc_mam.lua
+++ b/plugins/mod_muc_mam.lua
@@ -341,12 +341,12 @@
 	-- Policy check
 	if not archiving_enabled(self) then return end -- Don't log
 
-	-- And stash it
 	local with = stanza.name
 	if stanza.attr.type then
 		with = with .. "<" .. stanza.attr.type
 	end
 
+	-- And stash it
 	local id = archive:append(room_node, nil, stored_stanza, time_now(), with);
 
 	if id then
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1551700700 -3600
#      Mon Mar 04 12:58:20 2019 +0100
# Branch 0.11
# Node ID 17060708d0eb36cacd8f4590dc9b883e08f874cf
# Parent  6f39be2e0be540e3f968182171753d662a1a78ec
mod_muc_mam: Add comment about the tricks done with the 'with' field

diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua
--- a/plugins/mod_muc_mam.lua
+++ b/plugins/mod_muc_mam.lua
@@ -341,6 +341,7 @@
 	-- Policy check
 	if not archiving_enabled(self) then return end -- Don't log
 
+	-- Save the type in the 'with' field, allows storing presence without conflicts
 	local with = stanza.name
 	if stanza.attr.type then
 		with = with .. "<" .. stanza.attr.type
# HG changeset patch
# User Matthew Wild <mwild1@gmail.com>
# Date 1552550989 0
#      Thu Mar 14 08:09:49 2019 +0000
# Branch 0.11
# Node ID 4be2af104bf0a70bfe0fc10b31b132d18c2d75b4
# Parent  17060708d0eb36cacd8f4590dc9b883e08f874cf
prosodyctl about: Report network backend in use

diff --git a/prosodyctl b/prosodyctl
--- a/prosodyctl
+++ b/prosodyctl
@@ -387,6 +387,10 @@
 	end
 	print("LuaRocks:        ", luarocks_status);
 	print("");
+	print("# Network");
+	print("");
+	print("Backend: "..require "net.server".get_backend());
+	print("");
 	print("# Lua module versions");
 	local module_versions, longest_name = {}, 8;
 	local luaevent =dependencies.softreq"luaevent";
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1553272376 -3600
#      Fri Mar 22 17:32:56 2019 +0100
# Branch 0.11
# Node ID ddc07fb8dcd442c004c0c6e1558ca7b2d07b1233
# Parent  4be2af104bf0a70bfe0fc10b31b132d18c2d75b4
mod_mam: Perform message expiry based on building an index by date (backport of 39ee70fbb009 from trunk)

For each day, store a set of users that have new messages. To expire
messages, we collect the union of sets of users from dates that fall
outside the cleanup range.

The previous algoritm did not work well with many users, especially with
the default settings.

diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua
--- a/plugins/mod_mam/mod_mam.lua
+++ b/plugins/mod_mam/mod_mam.lua
@@ -33,7 +33,7 @@
 local tostring = tostring;
 local time_now = os.time;
 local m_min = math.min;
-local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse;
+local timestamp, timestamp_parse, datestamp = import( "util.datetime", "datetime", "parse", "date");
 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50);
 local strip_tags = module:get_option_set("dont_archive_namespaces", { "http://jabber.org/protocol/chatstates" });
 
@@ -46,13 +46,8 @@
 end
 local use_total = module:get_option_boolean("mam_include_total", true);
 
-local cleanup;
-
-local function schedule_cleanup(username)
-	if cleanup and not cleanup[username] then
-		table.insert(cleanup, username);
-		cleanup[username] = true;
-	end
+function schedule_cleanup()
+	-- replaced by non-noop later if cleanup is enabled
 end
 
 -- Handle prefs.
@@ -96,7 +91,6 @@
 	local qid = query.attr.queryid;
 
 	get_prefs(origin.username, true);
-	schedule_cleanup(origin.username);
 
 	-- Search query parameters
 	local qwith, qstart, qend;
@@ -212,6 +206,7 @@
 local function shall_store(user, who)
 	-- TODO Cache this?
 	if not um.user_exists(user, host) then
+		module:log("debug", "%s@%s does not exist", user, host)
 		return false;
 	end
 	local prefs = get_prefs(user);
@@ -329,6 +324,9 @@
 local cleanup_after = module:get_option_string("archive_expires_after", "1w");
 local cleanup_interval = module:get_option_number("archive_cleanup_interval", 4 * 60 * 60);
 if cleanup_after ~= "never" then
+	local cleanup_storage = module:open_store("archive_cleanup");
+	local cleanup_map = module:open_store("archive_cleanup", "map");
+
 	local day = 86400;
 	local multipliers = { d = day, w = day * 7, m = 31 * day, y = 365.2425 * day };
 	local n, m = cleanup_after:lower():match("(%d+)%s*([dwmy]?)");
@@ -346,33 +344,47 @@
 		return false;
 	end
 
-	-- Set of known users to do message expiry for
-	-- Populated either below or when new messages are added
-	cleanup = {};
+	-- For each day, store a set of users that have new messages. To expire
+	-- messages, we collect the union of sets of users from dates that fall
+	-- outside the cleanup range.
+
+	function schedule_cleanup(username, date)
+		cleanup_map:set(date or datestamp(), username, true);
+	end
 
-	-- Iterating over users is not supported by all authentication modules
-	-- Catch and ignore error if not supported
-	pcall(function ()
-		-- If this works, then we schedule cleanup for all known users on startup
-		for user in um.users(module.host) do
-			schedule_cleanup(user);
+	cleanup_runner = require "util.async".runner(function ()
+		local users = {};
+		local cut_off = datestamp(os.time() - cleanup_after);
+		for date in cleanup_storage:users() do
+			if date <= cut_off then
+				module:log("debug", "Messages from %q should be expired", date);
+				local messages_this_day = cleanup_storage:get(date);
+				if messages_this_day then
+					for user in pairs(messages_this_day) do
+						users[user] = true;
+					end
+					if date < cut_off then
+						-- Messages from the same day as the cut-off might not have expired yet,
+						-- but all earlier will have, so clear storage for those days.
+						cleanup_storage:set(date, nil);
+					end
+				end
+			end
 		end
+		local sum, num_users = 0, 0;
+		for user in pairs(users) do
+			local ok, err = archive:delete(user, { ["end"] = os.time() - cleanup_after; })
+			if ok then
+				num_users = num_users + 1;
+				sum = sum + (tonumber(ok) or 0);
+			end
+		end
+		module:log("info", "Deleted %d expired messages for %d users", sum, num_users);
 	end);
 
-	-- At odd intervals, delete old messages for one user
-	module:add_timer(math.random(10, 60), function()
-		local user = table.remove(cleanup, 1);
-		if user then
-			module:log("debug", "Removing old messages for user %q", user);
-			local ok, err = archive:delete(user, { ["end"] = os.time() - cleanup_after; })
-			if not ok then
-				module:log("warn", "Could not expire archives for user %s: %s", user, err);
-			elseif type(ok) == "number" then
-				module:log("debug", "Removed %d messages", ok);
-			end
-			cleanup[user] = nil;
-		end
-		return math.random(cleanup_interval, cleanup_interval * 2);
+	cleanup_task = module:add_timer(1, function ()
+		cleanup_runner:run(true);
+		return cleanup_interval;
 	end);
 else
 	module:log("debug", "Archive expiry disabled");
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1553272807 -3600
#      Fri Mar 22 17:40:07 2019 +0100
# Branch 0.11
# Node ID 78885b1bbb919d25dbb5eec0628136fc985d285e
# Parent  ddc07fb8dcd442c004c0c6e1558ca7b2d07b1233
mod_muc_mam: Copy cleanup mechanism from mod_mam (fixes #672)

diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua
--- a/plugins/mod_muc_mam.lua
+++ b/plugins/mod_muc_mam.lua
@@ -29,7 +29,7 @@
 local tostring = tostring;
 local time_now = os.time;
 local m_min = math.min;
-local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse;
+local timestamp, timestamp_parse, datestamp = import( "util.datetime", "datetime", "parse", "date");
 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50);
 
 local default_history_length = 20;
@@ -39,6 +39,10 @@
 	return math.min(room._data.history_length or default_history_length, max_history_length);
 end
 
+function schedule_cleanup()
+	-- replaced by non-noop later if cleanup is enabled
+end
+
 local log_all_rooms = module:get_option_boolean("muc_log_all_rooms", false);
 local log_by_default = module:get_option_boolean("muc_log_by_default", true);
 
@@ -351,6 +355,7 @@
 	local id = archive:append(room_node, nil, stored_stanza, time_now(), with);
 
 	if id then
+		schedule_cleanup(room_node);
 		stanza:add_direct_child(st.stanza("stanza-id", { xmlns = xmlns_st_id, by = self.jid, id = id }));
 	end
 end
@@ -386,3 +391,75 @@
 module:hook("muc-disco#info", function(event)
 	event.reply:tag("feature", {var=xmlns_mam}):up();
 end);
+
+-- Cleanup
+
+local cleanup_after = module:get_option_string("muc_log_expires_after", "1w");
+local cleanup_interval = module:get_option_number("muc_log_cleanup_interval", 4 * 60 * 60);
+
+if cleanup_after ~= "never" then
+	local cleanup_storage = module:open_store("muc_log_cleanup");
+	local cleanup_map = module:open_store("muc_log_cleanup", "map");
+
+	local day = 86400;
+	local multipliers = { d = day, w = day * 7, m = 31 * day, y = 365.2425 * day };
+	local n, m = cleanup_after:lower():match("(%d+)%s*([dwmy]?)");
+	if not n then
+		module:log("error", "Could not parse muc_log_expires_after string %q", cleanup_after);
+		return false;
+	end
+
+	cleanup_after = tonumber(n) * ( multipliers[m] or 1 );
+
+	module:log("debug", "muc_log_expires_after = %d -- in seconds", cleanup_after);
+
+	if not archive.delete then
+		module:log("error", "muc_log_expires_after set but mod_%s does not support deleting", archive._provided_by);
+		return false;
+	end
+
+	-- For each day, store a set of rooms that have new messages. To expire
+	-- messages, we collect the union of sets of rooms from dates that fall
+	-- outside the cleanup range.
+
+	function schedule_cleanup(roomname, date)
+		cleanup_map:set(date or datestamp(), roomname, true);
+	end
+
+	cleanup_runner = require "util.async".runner(function ()
+		local rooms = {};
+		local cut_off = datestamp(os.time() - cleanup_after);
+		for date in cleanup_storage:users() do
+			if date <= cut_off then
+				module:log("debug", "Messages from %q should be expired", date);
+				local messages_this_day = cleanup_storage:get(date);
+				if messages_this_day then
+					for room in pairs(messages_this_day) do
+						rooms[room] = true;
+					end
+					if date < cut_off then
+						-- Messages from the same day as the cut-off might not have expired yet,
+						-- but all earlier will have, so clear storage for those days.
+						cleanup_storage:set(date, nil);
+					end
+				end
+			end
+		end
+		local sum, num_rooms = 0, 0;
+		for room in pairs(rooms) do
+			local ok, err = archive:delete(room, { ["end"] = os.time() - cleanup_after; })
+			if ok then
+				num_rooms = num_rooms + 1;
+				sum = sum + (tonumber(ok) or 0);
+			end
+		end
+		module:log("info", "Deleted %d expired messages for %d rooms", sum, num_rooms);
+	end);
+
+	cleanup_task = module:add_timer(1, function ()
+		cleanup_runner:run(true);
+		return cleanup_interval;
+	end);
+else
+	module:log("debug", "Archive expiry disabled");
+end
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1547421422 -3600
#      Mon Jan 14 00:17:02 2019 +0100
# Branch 0.11
# Node ID 68faa0c1a99c2a5ce2db532cc87fbb68ed67a1b3
# Parent  78885b1bbb919d25dbb5eec0628136fc985d285e
mod_storage_memory: Implement :user iteration API

diff --git a/plugins/mod_storage_memory.lua b/plugins/mod_storage_memory.lua
--- a/plugins/mod_storage_memory.lua
+++ b/plugins/mod_storage_memory.lua
@@ -23,6 +23,10 @@
 	return true;
 end
 
+local function _users(self)
+	return next, self.store, nil;
+end
+
 local keyval_store = {};
 keyval_store.__index = keyval_store;
 
@@ -40,9 +44,13 @@
 
 keyval_store.purge = _purge_store;
 
+keyval_store.users = _users;
+
 local archive_store = {};
 archive_store.__index = archive_store;
 
+archive_store.users = _users;
+
 function archive_store:append(username, key, value, when, with)
 	if is_stanza(value) then
 		value = st.preserialize(value);
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1553772674 -3600
#      Thu Mar 28 12:31:14 2019 +0100
# Branch 0.11
# Node ID 485231f8b75d0ac679d1a2cf857a1ef1f0f6be91
# Parent  68faa0c1a99c2a5ce2db532cc87fbb68ed67a1b3
net.server_epoll: Handle LuaSec wantread/wantwrite conditions before callbacks (fixes #1333)

This prevents the :set(true) call from resuming a connection that was
paused in the onincoming callback.

diff --git a/net/server_epoll.lua b/net/server_epoll.lua
--- a/net/server_epoll.lua
+++ b/net/server_epoll.lua
@@ -355,15 +355,18 @@
 		self:onconnect();
 		self:on("incoming", data);
 	else
+		if err == "wantread" then
+			self:set(true, nil);
+			err = "timeout";
+		elseif err == "wantwrite" then
+			self:set(nil, true);
+			err = "timeout";
+		end
 		if partial and partial ~= "" then
 			self:onconnect();
 			self:on("incoming", partial, err);
 		end
-		if err == "wantread" then
-			self:set(true, nil);
-		elseif err == "wantwrite" then
-			self:set(nil, true);
-		elseif err ~= "timeout" then
+		if err ~= "timeout" then
 			self:on("disconnect", err);
 			self:destroy()
 			return;
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1553773975 -3600
#      Thu Mar 28 12:52:55 2019 +0100
# Branch 0.11
# Node ID df73ca804719f7cc318f145f176f2ed497c308ff
# Parent  485231f8b75d0ac679d1a2cf857a1ef1f0f6be91
net.server_epoll: Skip delayed continuation read on paused connections

This should prevent #1333 in cases where LuaSockets buffer is "dirty",
i.e. contains more data after a read, where it gets resumed with a
short delay.

diff --git a/net/server_epoll.lua b/net/server_epoll.lua
--- a/net/server_epoll.lua
+++ b/net/server_epoll.lua
@@ -373,7 +373,7 @@
 		end
 	end
 	if not self.conn then return; end
-	if self.conn:dirty() then
+	if self._wantread and self.conn:dirty() then
 		self:setreadtimeout(false);
 		self:pausefor(cfg.read_retry_delay);
 	else
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1555172188 -7200
#      Sat Apr 13 18:16:28 2019 +0200
# Branch 0.11
# Node ID 6402bc76f51a0581ba062c21cd5824df5efa4afb
# Parent  df73ca804719f7cc318f145f176f2ed497c308ff
net.dns: Close resolv.conf handle when done (fixes #1342)

diff --git a/net/dns.lua b/net/dns.lua
--- a/net/dns.lua
+++ b/net/dns.lua
@@ -704,6 +704,7 @@
 					end
 				end
 			end
+			resolv_conf:close();
 		end
 		if not self.server or #self.server == 0 then
 			-- TODO log warning about no nameservers, adding localhost as the default nameserver
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1555200380 -7200
#      Sun Apr 14 02:06:20 2019 +0200
# Branch 0.11
# Node ID c74c89a96cbf4508eb90d4cb4234ff295583706e
# Parent  6402bc76f51a0581ba062c21cd5824df5efa4afb
util.ip: Add missing netmask for 192.168/16 range (fixes #1343)

diff --git a/util/ip.lua b/util/ip.lua
--- a/util/ip.lua
+++ b/util/ip.lua
@@ -203,7 +203,7 @@
 function ip_methods:private()
 	local private = self.scope ~= 0xE;
 	if not private and self.proto == "IPv4" then
-		return match(self, rfc1918_8, 8) or match(self, rfc1918_12, 12) or match(self, rfc1918_16) or match(self, rfc6598, 10);
+		return match(self, rfc1918_8, 8) or match(self, rfc1918_12, 12) or match(self, rfc1918_16, 16) or match(self, rfc6598, 10);
 	end
 	return private;
 end
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1555672320 -7200
#      Fri Apr 19 13:12:00 2019 +0200
# Branch 0.11
# Node ID 29bc3dff341963966b7bb9edaf784fc4aa4eaed5
# Parent  c74c89a96cbf4508eb90d4cb4234ff295583706e
util.hashes: Use HMAC function provided by OpenSSL (fixes #1345)

diff --git a/util-src/hashes.c b/util-src/hashes.c
--- a/util-src/hashes.c
+++ b/util-src/hashes.c
@@ -25,6 +25,7 @@
 #include "lauxlib.h"
 #include <openssl/sha.h>
 #include <openssl/md5.h>
+#include <openssl/hmac.h>
 
 #if (LUA_VERSION_NUM == 501)
 #define luaL_setfuncs(L, R, N) luaL_register(L, NULL, R)
@@ -112,35 +113,28 @@
 	desc->Final(result, desc->ctxo);
 }
 
-#define MAKE_HMAC_FUNCTION(myFunc, func, size, type) \
+#define MAKE_HMAC_FUNCTION(myFunc, evp, size, type) \
 static int myFunc(lua_State *L) { \
-	type ctx, ctxo; \
 	unsigned char hash[size], result[2*size]; \
 	size_t key_len, msg_len; \
+	unsigned int out_len; \
 	const char *key = luaL_checklstring(L, 1, &key_len); \
 	const char *msg = luaL_checklstring(L, 2, &msg_len); \
 	const int hex_out = lua_toboolean(L, 3); \
-	struct hash_desc desc; \
-	desc.Init = (int (*)(void*))func##_Init; \
-	desc.Update = (int (*)(void*, const void *, size_t))func##_Update; \
-	desc.Final = (int (*)(unsigned char*, void*))func##_Final; \
-	desc.digestLength = size; \
-	desc.ctx = &ctx; \
-	desc.ctxo = &ctxo; \
-	hmac(&desc, key, key_len, msg, msg_len, hash); \
+	HMAC(evp(), key, key_len, (const unsigned char*)msg, msg_len, (unsigned char*)hash, &out_len); \
 	if (hex_out) { \
-		toHex(hash, size, result); \
-		lua_pushlstring(L, (char*)result, size*2); \
+		toHex(hash, out_len, result); \
+		lua_pushlstring(L, (char*)result, out_len*2); \
 	} else { \
-		lua_pushlstring(L, (char*)hash, size); \
+		lua_pushlstring(L, (char*)hash, out_len); \
 	} \
 	return 1; \
 }
 
-MAKE_HMAC_FUNCTION(Lhmac_sha1, SHA1, SHA_DIGEST_LENGTH, SHA_CTX)
-MAKE_HMAC_FUNCTION(Lhmac_sha256, SHA256, SHA256_DIGEST_LENGTH, SHA256_CTX)
-MAKE_HMAC_FUNCTION(Lhmac_sha512, SHA512, SHA512_DIGEST_LENGTH, SHA512_CTX)
-MAKE_HMAC_FUNCTION(Lhmac_md5, MD5, MD5_DIGEST_LENGTH, MD5_CTX)
+MAKE_HMAC_FUNCTION(Lhmac_sha1, EVP_sha1, SHA_DIGEST_LENGTH, SHA_CTX)
+MAKE_HMAC_FUNCTION(Lhmac_sha256, EVP_sha256, SHA256_DIGEST_LENGTH, SHA256_CTX)
+MAKE_HMAC_FUNCTION(Lhmac_sha512, EVP_sha512, SHA512_DIGEST_LENGTH, SHA512_CTX)
+MAKE_HMAC_FUNCTION(Lhmac_md5, EVP_md5, MD5_DIGEST_LENGTH, MD5_CTX)
 
 static int LscramHi(lua_State *L) {
 	union xory {
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1556039630 -7200
#      Tue Apr 23 19:13:50 2019 +0200
# Branch 0.11
# Node ID 5b048ccd106f3333f6f3257fdc6f54515f87539d
# Parent  29bc3dff341963966b7bb9edaf784fc4aa4eaed5
mod_tls: Log debug message for each kind of TLS context created

Creating TLS contexts triggers a lot of messages from certmanager that
don't really describe their purpose. This is meant to provide hints
about that.

diff --git a/plugins/mod_tls.lua b/plugins/mod_tls.lua
--- a/plugins/mod_tls.lua
+++ b/plugins/mod_tls.lua
@@ -52,12 +52,15 @@
 	local parent_s2s = rawgetopt(parent,  "s2s_ssl") or NULL;
 	local host_s2s   = rawgetopt(modhost, "s2s_ssl") or parent_s2s;
 
+	module:log("debug", "Creating context for c2s");
 	ssl_ctx_c2s, err, ssl_cfg_c2s = create_context(host.host, "server", host_c2s, host_ssl, global_c2s); -- for incoming client connections
 	if not ssl_ctx_c2s then module:log("error", "Error creating context for c2s: %s", err); end
 
+	module:log("debug", "Creating context for s2sout");
 	ssl_ctx_s2sout, err, ssl_cfg_s2sout = create_context(host.host, "client", host_s2s, host_ssl, global_s2s); -- for outgoing server connections
 	if not ssl_ctx_s2sout then module:log("error", "Error creating contexts for s2sout: %s", err); end
 
+	module:log("debug", "Creating context for s2sin");
 	ssl_ctx_s2sin, err, ssl_cfg_s2sin = create_context(host.host, "server", host_s2s, host_ssl, global_s2s); -- for incoming server connections
 	if not ssl_ctx_s2sin then module:log("error", "Error creating contexts for s2sin: %s", err); end
 end
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1556040066 -7200
#      Tue Apr 23 19:21:06 2019 +0200
# Branch 0.11
# Node ID 240d3f1f7dee0282835e9742c021f93c860d28a6
# Parent  5b048ccd106f3333f6f3257fdc6f54515f87539d
core.portmanager: Log debug message for each kind of TLS context created

diff --git a/core/portmanager.lua b/core/portmanager.lua
--- a/core/portmanager.lua
+++ b/core/portmanager.lua
@@ -112,6 +112,7 @@
 				if service_info.encryption == "ssl" then
 					local global_ssl_config = config.get("*", "ssl") or {};
 					local prefix_ssl_config = config.get("*", config_prefix.."ssl") or global_ssl_config;
+					log("debug", "Creating context for direct TLS service %s on port %d", service_info.name, port);
 					ssl, err = certmanager.create_context(service_info.name.." port "..port, "server",
 						prefix_ssl_config[interface],
 						prefix_ssl_config[port],
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1556115379 -7200
#      Wed Apr 24 16:16:19 2019 +0200
# Branch 0.11
# Node ID 640a2b8e7806e59cafb6e4f6aa3dd876e35ea9f1
# Parent  240d3f1f7dee0282835e9742c021f93c860d28a6
util.encodings: Allow unassigned code points in ICU mode to match libidn behavior (fixes #1348)

diff --git a/util-src/encodings.c b/util-src/encodings.c
--- a/util-src/encodings.c
+++ b/util-src/encodings.c
@@ -299,7 +299,7 @@
 		return 1;
 	}
 
-	prepped_len = usprep_prepare(profile, unprepped, unprepped_len, prepped, 1024, 0, NULL, &err);
+	prepped_len = usprep_prepare(profile, unprepped, unprepped_len, prepped, 1024, USPREP_ALLOW_UNASSIGNED, NULL, &err);
 
 	if(U_FAILURE(err)) {
 		lua_pushnil(L);
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1556742855 -7200
#      Wed May 01 22:34:15 2019 +0200
# Branch 0.11
# Node ID 8cd180dc18a85e98383e4655e677c7b5ec5f5eb4
# Parent  640a2b8e7806e59cafb6e4f6aa3dd876e35ea9f1
prosodyctl: Include version of LuaDBI in 'about'

diff --git a/prosodyctl b/prosodyctl
--- a/prosodyctl
+++ b/prosodyctl
@@ -395,6 +395,7 @@
 	local module_versions, longest_name = {}, 8;
 	local luaevent =dependencies.softreq"luaevent";
 	dependencies.softreq"ssl";
+	dependencies.softreq"DBI";
 	for name, module in pairs(package.loaded) do
 		if type(module) == "table" and rawget(module, "_VERSION")
 		and name ~= "_G" and not name:match("%.") then
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1556936615 -7200
#      Sat May 04 04:23:35 2019 +0200
# Branch 0.11
# Node ID 524b8cd76780ca2b3e537ffc0b52724902bcf7f3
# Parent  8cd180dc18a85e98383e4655e677c7b5ec5f5eb4
net.server_epoll: Restore wantread flag after pause (fixes #1354)

If a chunk of data has been received that is larger than the amount read
at a time, then the connection is paused for a short time after which it
tries to read some more. If, after that, there is still more data to
read, it should do the same thing. However, because the "want read" flag
is removed and was restored after the delayed reading, it would not
schedule another delayed read.

diff --git a/net/server_epoll.lua b/net/server_epoll.lua
--- a/net/server_epoll.lua
+++ b/net/server_epoll.lua
@@ -600,10 +600,10 @@
 	self:set(false);
 	self._pausefor = addtimer(t, function ()
 		self._pausefor = nil;
+		self:set(true);
 		if self.conn and self.conn:dirty() then
 			self:onreadable();
 		end
-		self:set(true);
 	end);
 end
 
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1557002939 -7200
#      Sat May 04 22:48:59 2019 +0200
# Branch 0.11
# Node ID c8646f65767aef4f64b22ec2374b73c59c49b172
# Parent  524b8cd76780ca2b3e537ffc0b52724902bcf7f3
configure: Handle lua being found in /bin (workaround for #1353)

diff --git a/configure b/configure
--- a/configure
+++ b/configure
@@ -419,6 +419,13 @@
 }
 fi
 
+# See #1353
+if [ "$LUA_DIR_SET" != "yes" ] && [ "$LUA_DIR" = "/" ]
+then
+   LUA_DIR="/usr"
+fi
+
+
 if [ "$lua_interp_found" != "yes" ] && [ "$RUNWITH_SET" != "yes" ]
 then
    if [ "$LUA_VERSION_SET" ]; then
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1557751661 -7200
#      Mon May 13 14:47:41 2019 +0200
# Branch 0.11
# Node ID 2408e6362c1586db28d792fcee88bec2be78952d
# Parent  c8646f65767aef4f64b22ec2374b73c59c49b172
mod_storage_sql: Move code out of if-else chain

diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua
--- a/plugins/mod_storage_sql.lua
+++ b/plugins/mod_storage_sql.lua
@@ -390,6 +390,14 @@
 		else
 			args[#args+1] = query.truncate;
 			local unlimited = "ALL";
+			sql_query = [[
+			DELETE FROM "prosodyarchive"
+			WHERE "sort_id" IN (
+				SELECT "sort_id" FROM "prosodyarchive"
+				WHERE %s
+				ORDER BY "sort_id" %s
+				LIMIT %s OFFSET ?
+			);]];
 			if engine.params.driver == "SQLite3" then
 				sql_query = [[
 				DELETE FROM "prosodyarchive"
@@ -407,15 +415,6 @@
 					LIMIT %s OFFSET ?
 				) AS limiter on result.sort_id = limiter.sort_id;]];
 				unlimited = "18446744073709551615";
-			else
-				sql_query = [[
-				DELETE FROM "prosodyarchive"
-				WHERE "sort_id" IN (
-					SELECT "sort_id" FROM "prosodyarchive"
-					WHERE %s
-					ORDER BY "sort_id" %s
-					LIMIT %s OFFSET ?
-				);]];
 			end
 			sql_query = string.format(sql_query, t_concat(where, " AND "),
 				query.reverse and "ASC" or "DESC", unlimited);
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1557751178 -7200
#      Mon May 13 14:39:38 2019 +0200
# Branch 0.11
# Node ID acf4a7bfb6aa8c9be2d6258f42f9e46c78698346
# Parent  2408e6362c1586db28d792fcee88bec2be78952d
mod_storage_sql: Handle SQLite DELETE with LIMIT being optional (fixes #1359)

diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua
--- a/plugins/mod_storage_sql.lua
+++ b/plugins/mod_storage_sql.lua
@@ -399,12 +399,14 @@
 				LIMIT %s OFFSET ?
 			);]];
 			if engine.params.driver == "SQLite3" then
-				sql_query = [[
-				DELETE FROM "prosodyarchive"
-				WHERE %s
-				ORDER BY "sort_id" %s
-				LIMIT %s OFFSET ?;
-				]];
+				if engine._have_delete_limit then
+					sql_query = [[
+					DELETE FROM "prosodyarchive"
+					WHERE %s
+					ORDER BY "sort_id" %s
+					LIMIT %s OFFSET ?;
+					]];
+				end
 				unlimited = "-1";
 			elseif engine.params.driver == "MySQL" then
 				sql_query = [[
@@ -620,6 +622,13 @@
 					module:log("error", "Old database format detected. Please run: prosodyctl mod_%s upgrade", module.name);
 					return false, "database upgrade needed";
 				end
+				if engine.params.driver == "SQLite3" then
+					for row in engine:select("PRAGMA compile_options") do
+						if row[1] == "ENABLE_UPDATE_DELETE_LIMIT" then
+							engine._have_delete_limit = true;
+						end
+					end
+				end
 			end
 		end);
 		engines[sql.db2uri(params)] = engine;
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1558194320 -7200
#      Sat May 18 17:45:20 2019 +0200
# Branch 0.11
# Node ID 62d8689beafbebe1bf188df4edb710fae4716ff3
# Parent  acf4a7bfb6aa8c9be2d6258f42f9e46c78698346
mod_c2s: Associate connection with session last (fixes #1313)

This way, any fatal error in the callback will not leave a
half-established session.

diff --git a/plugins/mod_c2s.lua b/plugins/mod_c2s.lua
--- a/plugins/mod_c2s.lua
+++ b/plugins/mod_c2s.lua
@@ -239,7 +239,6 @@
 --- Port listener
 function listener.onconnect(conn)
 	local session = sm_new_session(conn);
-	sessions[conn] = session;
 
 	session.log("info", "Client connected");
 
@@ -300,6 +299,8 @@
 	end
 
 	session.dispatch_stanza = stream_callbacks.handlestanza;
+
+	sessions[conn] = session;
 end
 
 function listener.onincoming(conn, data)
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1558193301 -7200
#      Sat May 18 17:28:21 2019 +0200
# Branch 0.11
# Node ID 5d2f7144fa1227318c13a521d92bb8f9ba7cd391
# Parent  62d8689beafbebe1bf188df4edb710fae4716ff3
util.random: Handle unlikely read errors from /dev/urandom (see #1313)

diff --git a/util/random.lua b/util/random.lua
--- a/util/random.lua
+++ b/util/random.lua
@@ -12,7 +12,11 @@
 local urandom, urandom_err = io.open("/dev/urandom", "r");
 
 local function bytes(n)
-	return urandom:read(n);
+	local data, err = urandom:read(n);
+	if not data then
+		error("Unable to retrieve data from secure random number generator (/dev/urandom): "..err);
+	end
+	return data;
 end
 
 if not urandom then
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1558198285 -7200
#      Sat May 18 18:51:25 2019 +0200
# Branch 0.11
# Node ID 8297408db58bcde4ba06beb8c8fc436ff8d172b4
# Parent  5d2f7144fa1227318c13a521d92bb8f9ba7cd391
util.random: Coerce error to string (thanks waqas)

In theory this could happen in an EOF condition, which should be
impossible with a read from /dev/urandom.

diff --git a/util/random.lua b/util/random.lua
--- a/util/random.lua
+++ b/util/random.lua
@@ -14,7 +14,7 @@
 local function bytes(n)
 	local data, err = urandom:read(n);
 	if not data then
-		error("Unable to retrieve data from secure random number generator (/dev/urandom): "..err);
+		error("Unable to retrieve data from secure random number generator (/dev/urandom): "..tostring(err));
 	end
 	return data;
 end
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1558421541 -7200
#      Tue May 21 08:52:21 2019 +0200
# Branch 0.11
# Node ID af8c514e5cf78cae0fb70cfd69b6f8136f12ff4c
# Parent  8297408db58bcde4ba06beb8c8fc436ff8d172b4
util.random: Throw different error for EOL condition

diff --git a/util/random.lua b/util/random.lua
--- a/util/random.lua
+++ b/util/random.lua
@@ -14,7 +14,11 @@
 local function bytes(n)
 	local data, err = urandom:read(n);
 	if not data then
-		error("Unable to retrieve data from secure random number generator (/dev/urandom): "..tostring(err));
+		if err then
+			error("Unable to retrieve data from secure random number generator (/dev/urandom): "..tostring(err));
+		else
+			error("Secure random number generator (/dev/urandom) returned an end-of-file condition");
+		end
 	end
 	return data;
 end
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1558951535 -7200
#      Mon May 27 12:05:35 2019 +0200
# Branch 0.11
# Node ID 1117138fa37285419354eb6cd9028dea04cab026
# Parent  af8c514e5cf78cae0fb70cfd69b6f8136f12ff4c
mod_announce: Check for admin on current virtualhost instead of global (fixes #1365) (thanks yc)

diff --git a/plugins/mod_announce.lua b/plugins/mod_announce.lua
--- a/plugins/mod_announce.lua
+++ b/plugins/mod_announce.lua
@@ -44,7 +44,7 @@
 		return; -- Not an announcement
 	end
 
-	if not is_admin(stanza.attr.from) then
+	if not is_admin(stanza.attr.from, host) then
 		-- Not an admin? Not allowed!
 		module:log("warn", "Non-admin '%s' tried to send server announcement", stanza.attr.from);
 		return;
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1558976432 -7200
#      Mon May 27 19:00:32 2019 +0200
# Branch 0.11
# Node ID 79ba2d709e7276c2a63456936be6a86cf03e83f3
# Parent  1117138fa37285419354eb6cd9028dea04cab026
mod_mam: Cache last date that archive owner has messages to reduce writes (fixes #1368)

diff --git a/plugins/mod_mam/mod_mam.lua b/plugins/mod_mam/mod_mam.lua
--- a/plugins/mod_mam/mod_mam.lua
+++ b/plugins/mod_mam/mod_mam.lua
@@ -348,8 +348,14 @@
 	-- messages, we collect the union of sets of users from dates that fall
 	-- outside the cleanup range.
 
+	local last_date = require "util.cache".new(module:get_option_number("archive_cleanup_date_cache_size", 1000));
 	function schedule_cleanup(username, date)
-		cleanup_map:set(date or datestamp(), username, true);
+		date = date or datestamp();
+		if last_date:get(username) == date then return end
+		local ok = cleanup_map:set(date, username, true);
+		if ok then
+			last_date:set(username, date);
+		end
 	end
 
 	cleanup_runner = require "util.async".runner(function ()
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1558976434 -7200
#      Mon May 27 19:00:34 2019 +0200
# Branch 0.11
# Node ID 2c8f674b9243c1daa5df0b60f6bb80c729586452
# Parent  79ba2d709e7276c2a63456936be6a86cf03e83f3
mod_muc_mam: Cache last date that archive owner has messages to reduce writes (fixes #1368)

diff --git a/plugins/mod_muc_mam.lua b/plugins/mod_muc_mam.lua
--- a/plugins/mod_muc_mam.lua
+++ b/plugins/mod_muc_mam.lua
@@ -422,8 +422,14 @@
 	-- messages, we collect the union of sets of rooms from dates that fall
 	-- outside the cleanup range.
 
+	local last_date = require "util.cache".new(module:get_option_number("muc_log_cleanup_date_cache_size", 1000));
 	function schedule_cleanup(roomname, date)
-		cleanup_map:set(date or datestamp(), roomname, true);
+		date = date or datestamp();
+		if last_date:get(roomname) == date then return end
+		local ok = cleanup_map:set(date, roomname, true);
+		if ok then
+			last_date:set(roomname, date);
+		end
 	end
 
 	cleanup_runner = require "util.async".runner(function ()
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1559222216 -7200
#      Thu May 30 15:16:56 2019 +0200
# Branch 0.11
# Node ID 045209b41b3a23027d8d265e9da010a1710be5c2
# Parent  2c8f674b9243c1daa5df0b60f6bb80c729586452
mod_pep: Handle presence based subscription outside of util.pubsub (fixes #1372)

Subscriptions were updated for each incoming presence stanza from
contacts. Each subscription change triggered a configuration save, which
would filter out the presence based subscriptions and usually end up
replacing the existing data with identical data. With many subscribed
nodes this adds up to a fair bit of IO bound work that is avoided by
keeping them separate.

diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua
--- a/plugins/mod_pep.lua
+++ b/plugins/mod_pep.lua
@@ -90,21 +90,6 @@
 		return data, err;
 	end
 	function store:set(node, data)
-		if data then
-			-- Save the data without subscriptions
-			local subscribers = {};
-			for jid, sub in pairs(data.subscribers) do
-				if type(sub) ~= "table" or not sub.presence then
-					subscribers[jid] = sub;
-				end
-			end
-			data = {
-				name = data.name;
-				config = data.config;
-				affiliations = data.affiliations;
-				subscribers = subscribers;
-			};
-		end
 		return node_config:set(username, node, data);
 	end
 	function store:users()
@@ -151,7 +136,23 @@
 			end
 			message:add_child(item);
 		end
+
+		local broadcast_to = {};
 		for jid in pairs(jids) do
+			broadcast_to[jid] = true;
+		end
+
+		local service_recipients = recipients[username];
+		if service_recipients then
+			local service = services[username];
+			for recipient, nodes in pairs(service_recipients) do
+				if nodes:contains(node) and service:may(node, recipient, "subscribe") then
+					broadcast_to[recipient] = true;
+				end
+			end
+		end
+
+		for jid in pairs(broadcast_to) do
 			module:log("debug", "Sending notification to %s from %s: %s", jid, user_bare, tostring(item));
 			message.attr.to = jid;
 			module:send(message);
@@ -160,20 +161,6 @@
 	return simple_broadcast;
 end
 
-local function on_node_creation(event)
-	local service = event.service;
-	local node = event.node;
-	local username = service.config.pep_username;
-
-	local service_recipients = recipients[username];
-	if not service_recipients then return; end
-
-	for recipient, nodes in pairs(service_recipients) do
-		if nodes:contains(node) then
-			service:add_subscription(node, recipient, recipient, { presence = true });
-		end
-	end
-end
 
 function get_pep_service(username)
 	module:log("debug", "get_pep_service(%q)", username);
@@ -233,10 +220,6 @@
 	return service;
 end
 
-module:hook("item-added/pep-service", function (event)
-	local service = event.item.service;
-	module:hook_object_event(service.events, "node-created", on_node_creation);
-end);
 
 function handle_pubsub_iq(event)
 	local origin, stanza = event.origin, event.stanza;
@@ -303,12 +286,9 @@
 	end
 
 	local service = get_pep_service(service_name);
-	for node in current - nodes do
-		service:remove_subscription(node, recipient, recipient);
-	end
 
 	for node in nodes - current do
-		if service:add_subscription(node, recipient, recipient, { presence = true }) then
+		if service:may(node, recipient, "subscribe") then
 			resend_last_item(recipient, node, service);
 		end
 	end
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1559253028 -7200
#      Thu May 30 23:50:28 2019 +0200
# Branch 0.11
# Node ID 7dd0dddd8e0226e7cfd4f0c9f0dabb81ed90369c
# Parent  045209b41b3a23027d8d265e9da010a1710be5c2
util.sql: Ignore if tables and indices already exist on creation (fixes #1064)

Tested with SQLite3 3.16.2 and 3.27.2 and Postgres 11.

MySQL does not support IF NOT EXISTS for indices so not handled here.

diff --git a/util/sql.lua b/util/sql.lua
--- a/util/sql.lua
+++ b/util/sql.lua
@@ -238,6 +238,9 @@
 end
 function engine:_create_index(index)
 	local sql = "CREATE INDEX \""..index.name.."\" ON \""..index.table.."\" (";
+	if self.params.driver ~= "MySQL" then
+		sql = sql:gsub("^CREATE INDEX", "%1 IF NOT EXISTS");
+	end
 	for i=1,#index do
 		sql = sql.."\""..index[i].."\"";
 		if i ~= #index then sql = sql..", "; end
@@ -256,6 +259,9 @@
 end
 function engine:_create_table(table)
 	local sql = "CREATE TABLE \""..table.name.."\" (";
+	do
+		sql = sql:gsub("^CREATE TABLE", "%1 IF NOT EXISTS");
+	end
 	for i,col in ipairs(table.c) do
 		local col_type = col.type;
 		if col_type == "MEDIUMTEXT" and self.params.driver ~= "MySQL" then
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1560164280 -7200
#      Mon Jun 10 12:58:00 2019 +0200
# Branch 0.11
# Node ID 6714578cfd6e6a14a3e5efabab0ee2144becff16
# Parent  7dd0dddd8e0226e7cfd4f0c9f0dabb81ed90369c
mod_pep: Revert 045209b41b3a, caused a regression

Adding in all presence based subscriptions in the broadcaster caused
resend_last_item() to unintentionally send out more notifications than
it should have.

diff --git a/plugins/mod_pep.lua b/plugins/mod_pep.lua
--- a/plugins/mod_pep.lua
+++ b/plugins/mod_pep.lua
@@ -90,6 +90,21 @@
 		return data, err;
 	end
 	function store:set(node, data)
+		if data then
+			-- Save the data without subscriptions
+			local subscribers = {};
+			for jid, sub in pairs(data.subscribers) do
+				if type(sub) ~= "table" or not sub.presence then
+					subscribers[jid] = sub;
+				end
+			end
+			data = {
+				name = data.name;
+				config = data.config;
+				affiliations = data.affiliations;
+				subscribers = subscribers;
+			};
+		end
 		return node_config:set(username, node, data);
 	end
 	function store:users()
@@ -136,23 +151,7 @@
 			end
 			message:add_child(item);
 		end
-
-		local broadcast_to = {};
 		for jid in pairs(jids) do
-			broadcast_to[jid] = true;
-		end
-
-		local service_recipients = recipients[username];
-		if service_recipients then
-			local service = services[username];
-			for recipient, nodes in pairs(service_recipients) do
-				if nodes:contains(node) and service:may(node, recipient, "subscribe") then
-					broadcast_to[recipient] = true;
-				end
-			end
-		end
-
-		for jid in pairs(broadcast_to) do
 			module:log("debug", "Sending notification to %s from %s: %s", jid, user_bare, tostring(item));
 			message.attr.to = jid;
 			module:send(message);
@@ -161,6 +160,20 @@
 	return simple_broadcast;
 end
 
+local function on_node_creation(event)
+	local service = event.service;
+	local node = event.node;
+	local username = service.config.pep_username;
+
+	local service_recipients = recipients[username];
+	if not service_recipients then return; end
+
+	for recipient, nodes in pairs(service_recipients) do
+		if nodes:contains(node) then
+			service:add_subscription(node, recipient, recipient, { presence = true });
+		end
+	end
+end
 
 function get_pep_service(username)
 	module:log("debug", "get_pep_service(%q)", username);
@@ -220,6 +233,10 @@
 	return service;
 end
 
+module:hook("item-added/pep-service", function (event)
+	local service = event.item.service;
+	module:hook_object_event(service.events, "node-created", on_node_creation);
+end);
 
 function handle_pubsub_iq(event)
 	local origin, stanza = event.origin, event.stanza;
@@ -286,9 +303,12 @@
 	end
 
 	local service = get_pep_service(service_name);
+	for node in current - nodes do
+		service:remove_subscription(node, recipient, recipient);
+	end
 
 	for node in nodes - current do
-		if service:may(node, recipient, "subscribe") then
+		if service:add_subscription(node, recipient, recipient, { presence = true }) then
 			resend_last_item(recipient, node, service);
 		end
 	end
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1561057692 -7200
#      Thu Jun 20 21:08:12 2019 +0200
# Branch 0.11
# Node ID f29d662d16f8bee0a34b0304788d3ea5c395ea95
# Parent  6714578cfd6e6a14a3e5efabab0ee2144becff16
mod_blocklist: Add test case capturing current behavior

diff --git a/spec/scansion/blocking.scs b/spec/scansion/blocking.scs
new file mode 100644
--- /dev/null
+++ b/spec/scansion/blocking.scs
@@ -0,0 +1,162 @@
+# XEP-0191: Blocking Command
+
+[Client] Romeo
+	jid: blocker@localhost
+	password: password
+
+[Client] Juliet
+	jid: blockee@localhost
+	password: password
+
+-----
+
+# The parties connect
+Romeo connects
+
+Romeo sends:
+	<presence/>
+
+Romeo receives:
+	<presence from="${Romeo's full JID}">
+	  <x xmlns="vcard-temp:x:update"/>
+	</presence>
+
+Juliet connects
+
+Juliet sends:
+	<presence/>
+
+Juliet receives:
+	<presence from="${Juliet's full JID}">
+	  <x xmlns="vcard-temp:x:update"/>
+	</presence>
+
+# They add each other
+Romeo sends:
+	<presence type="subscribe" to="${Juliet's JID}"/>
+
+Romeo receives:
+	<presence from="${Juliet's JID}" to="${Romeo's JID}" type="unavailable"/>
+
+Juliet receives:
+	<presence type="subscribe" to="${Juliet's JID}" from="${Romeo's JID}"/>
+
+Juliet sends:
+	<presence type="subscribed" to="${Romeo's JID}"/>
+
+Romeo receives:
+	<presence from="${Juliet's full JID}" to="${Romeo's JID}">
+	  <delay xmlns="urn:xmpp:delay" stamp="{scansion:any}" from="localhost"/>
+	  <x xmlns="vcard-temp:x:update"/>
+	</presence>
+
+Juliet sends:
+	<presence type="subscribe" to="${Romeo's JID}"/>
+
+Juliet receives:
+	<presence from="${Romeo's JID}" to="${Juliet's JID}" type="unavailable"/>
+
+Romeo receives:
+	<presence type="subscribe" to="${Romeo's JID}" from="${Juliet's JID}"/>
+
+Romeo sends:
+	<presence type="subscribed" to="${Juliet's JID}"/>
+
+Juliet receives:
+	<presence from="${Romeo's full JID}" to="${Juliet's JID}">
+	  <delay xmlns="urn:xmpp:delay" stamp="{scansion:any}" from="localhost"/>
+	  <x xmlns="vcard-temp:x:update"/>
+	</presence>
+
+Romeo receives:
+	<presence from="${Juliet's full JID}" to="${Romeo's JID}">
+	  <delay xmlns="urn:xmpp:delay" stamp="{scansion:any}" from="localhost"/>
+	  <x xmlns="vcard-temp:x:update"/>
+	</presence>
+
+# They can now talk
+Juliet sends:
+	<message type="chat" to="${Romeo's JID}">
+	  <body>ohai</body>
+	</message>
+
+Romeo receives:
+	<message type="chat" to="${Romeo's JID}" from="${Juliet's full JID}">
+	  <body>ohai</body>
+	</message>
+
+# And now to the blockining
+
+Romeo sends:
+	<iq type="set" id="lx2">
+	  <block xmlns="urn:xmpp:blocking">
+	    <item jid="${Juliet's JID}"/>
+	  </block>
+	</iq>
+
+Romeo receives:
+	<iq type="result" id="lx2"/>
+
+Juliet receives:
+	<presence type="unavailable" to="${Juliet's JID}" from="${Romeo's full JID}"/>
+
+# Can"t talk anymore
+Romeo sends:
+	<message type="chat" to="${Juliet's JID}">
+	  <body>hello?</body>
+	</message>
+
+Romeo receives:
+	<message type="error" from="${Juliet's JID}">
+	  <error type="cancel">
+	    <not-acceptable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+	    <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">You have blocked this JID</text>
+	    <blocked xmlns="urn:xmpp:blocking:errors"/>
+	  </error>
+	</message>
+
+Juliet sends:
+	<message type="chat" to="${Romeo's JID}"/>
+
+Juliet receives:
+	<message type="error" from="${Romeo's JID}">
+	  <error type="cancel">
+	    <service-unavailable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+	  </error>
+	</message>
+
+Romeo sends:
+	<iq type="set" id="lx3">
+	  <unblock xmlns="urn:xmpp:blocking">
+	    <item jid="${Juliet's JID}"/>
+	  </unblock>
+	</iq>
+
+Romeo receives:
+	<iq type="result" id="lx3"/>
+
+# Can talk again
+Romeo sends:
+	<message type="chat" to="${Juliet's JID}">
+	  <body>hello!</body>
+	</message>
+
+Juliet receives:
+	<message type="chat" to="${Juliet's JID}" from="${Romeo's full JID}">
+	  <body>hello!</body>
+	</message>
+
+# Bye
+Juliet disconnects
+
+Juliet sends:
+	<presence type="unavailable"/>
+
+Romeo receives:
+	<presence from="${Juliet's full JID}" to="${Romeo's JID}" type="unavailable"/>
+
+Romeo disconnects
+
+Romeo sends:
+	<presence type="unavailable"/>
+
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1561058326 -7200
#      Thu Jun 20 21:18:46 2019 +0200
# Branch 0.11
# Node ID 7d312e77c857eba4000cb49f60912b48a604f1f5
# Parent  f29d662d16f8bee0a34b0304788d3ea5c395ea95
mod_blocklist: Update test case with correct behavior (see #1380)

Expect failure

diff --git a/spec/scansion/blocking.scs b/spec/scansion/blocking.scs
--- a/spec/scansion/blocking.scs
+++ b/spec/scansion/blocking.scs
@@ -132,6 +132,12 @@
 	  </unblock>
 	</iq>
 
+Juliet receives:
+	<presence to="${Juliet's JID}" from="${Romeo's full JID}">
+	  <delay xmlns="urn:xmpp:delay" stamp="{scansion:any}" from="localhost"/>
+	  <x xmlns="vcard-temp:x:update"/>
+	</presence>
+
 Romeo receives:
 	<iq type="result" id="lx3"/>
 
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1561058400 -7200
#      Thu Jun 20 21:20:00 2019 +0200
# Branch 0.11
# Node ID 0c35f353db68ff771b167eef4b32b1abed936a46
# Parent  7d312e77c857eba4000cb49f60912b48a604f1f5
mod_blocklist: Trigger resend of presence when unblocking a contact (fixes #1380)

diff --git a/plugins/mod_blocklist.lua b/plugins/mod_blocklist.lua
--- a/plugins/mod_blocklist.lua
+++ b/plugins/mod_blocklist.lua
@@ -128,6 +128,7 @@
 	-- > only if the contact is allowed to receive presence notifications [...]
 	-- So contacts we need to do that for are added to the set below.
 	local send_unavailable = is_blocking and {};
+	local send_available = not is_blocking and {};
 
 	-- Because blocking someone currently also blocks the ability to reject
 	-- subscription requests, we'll preemptively reject such
@@ -147,6 +148,8 @@
 			elseif is_contact_pending_in(username, module.host, jid) then
 				remove_pending[jid] = true;
 			end
+		elseif is_contact_subscribed(username, module.host, jid) then
+			send_available[jid] = true;
 		end
 	end
 
@@ -203,6 +206,11 @@
 			save_roster(username, module.host, roster);
 			-- Not much we can do about save failing here
 		end
+	else
+		local user_bare = username .. "@" .. module.host;
+		for jid in pairs(send_available) do
+			module:send(st.presence({ type = "probe", to = user_bare, from = jid }));
+		end
 	end
 
 	local blocklist_push = st.iq({ type = "set", id = "blocklist-push" })
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1561061419 -7200
#      Thu Jun 20 22:10:19 2019 +0200
# Branch 0.11
# Node ID d67e8ef632352c50754b948b0069fae6d0abd6b0
# Parent  0c35f353db68ff771b167eef4b32b1abed936a46
mod_blocklist: Remove unrelated tags from test case

diff --git a/spec/scansion/blocking.scs b/spec/scansion/blocking.scs
--- a/spec/scansion/blocking.scs
+++ b/spec/scansion/blocking.scs
@@ -17,9 +17,7 @@
 	<presence/>
 
 Romeo receives:
-	<presence from="${Romeo's full JID}">
-	  <x xmlns="vcard-temp:x:update"/>
-	</presence>
+	<presence from="${Romeo's full JID}"/>
 
 Juliet connects
 
@@ -27,9 +25,7 @@
 	<presence/>
 
 Juliet receives:
-	<presence from="${Juliet's full JID}">
-	  <x xmlns="vcard-temp:x:update"/>
-	</presence>
+	<presence from="${Juliet's full JID}"/>
 
 # They add each other
 Romeo sends:
@@ -47,7 +43,6 @@
 Romeo receives:
 	<presence from="${Juliet's full JID}" to="${Romeo's JID}">
 	  <delay xmlns="urn:xmpp:delay" stamp="{scansion:any}" from="localhost"/>
-	  <x xmlns="vcard-temp:x:update"/>
 	</presence>
 
 Juliet sends:
@@ -65,13 +60,11 @@
 Juliet receives:
 	<presence from="${Romeo's full JID}" to="${Juliet's JID}">
 	  <delay xmlns="urn:xmpp:delay" stamp="{scansion:any}" from="localhost"/>
-	  <x xmlns="vcard-temp:x:update"/>
 	</presence>
 
 Romeo receives:
 	<presence from="${Juliet's full JID}" to="${Romeo's JID}">
 	  <delay xmlns="urn:xmpp:delay" stamp="{scansion:any}" from="localhost"/>
-	  <x xmlns="vcard-temp:x:update"/>
 	</presence>
 
 # They can now talk
@@ -134,7 +127,6 @@
 
 Juliet receives:
 	<presence to="${Juliet's JID}" from="${Romeo's full JID}">
-	  <delay xmlns="urn:xmpp:delay" stamp="{scansion:any}" from="localhost"/>
 	  <x xmlns="vcard-temp:x:update"/>
 	</presence>
 
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1561062346 -7200
#      Thu Jun 20 22:25:46 2019 +0200
# Branch 0.11
# Node ID 5c9341a1b36f94bb18f47d0bbd1de585f4b018fd
# Parent  d67e8ef632352c50754b948b0069fae6d0abd6b0
scansion/blocking: Remove the right irrelevant thing

diff --git a/spec/scansion/blocking.scs b/spec/scansion/blocking.scs
--- a/spec/scansion/blocking.scs
+++ b/spec/scansion/blocking.scs
@@ -127,7 +127,7 @@
 
 Juliet receives:
 	<presence to="${Juliet's JID}" from="${Romeo's full JID}">
-	  <x xmlns="vcard-temp:x:update"/>
+	  <delay xmlns="urn:xmpp:delay" stamp="{scansion:any}" from="localhost"/>
 	</presence>
 
 Romeo receives:
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1562541454 -7200
#      Mon Jul 08 01:17:34 2019 +0200
# Branch 0.11
# Node ID c8c3f2eba8981ad83dcae18f4e69ebee23498f32
# Parent  5c9341a1b36f94bb18f47d0bbd1de585f4b018fd
net.server_epoll: Backport timer optimization 6c2370f17027 from trunk (see #1388)

The previous timer handling did not scale well and led to high CPU usage
with many connections (each with at least an read timeout).

diff --git a/net/server_epoll.lua b/net/server_epoll.lua
--- a/net/server_epoll.lua
+++ b/net/server_epoll.lua
@@ -6,9 +6,7 @@
 --
 
 
-local t_sort = table.sort;
 local t_insert = table.insert;
-local t_remove = table.remove;
 local t_concat = table.concat;
 local setmetatable = setmetatable;
 local tostring = tostring;
@@ -20,6 +18,7 @@
 local socket = require "socket";
 local luasec = require "ssl";
 local gettime = require "util.time".now;
+local indexedbheap = require "util.indexedbheap";
 local createtable = require "util.table".create;
 local inet = require "util.net";
 local inet_pton = inet.pton;
@@ -66,22 +65,24 @@
 
 -- Timer and scheduling --
 
-local timers = {};
+local timers = indexedbheap.create();
 
 local function noop() end
 local function closetimer(t)
 	t[1] = 0;
 	t[2] = noop;
+	timers:remove(t.id);
 end
 
--- Set to true when timers have changed
-local resort_timers = false;
+local function reschedule(t, time)
+	t[1] = time;
+	timers:reprioritize(t.id, time);
+end
 
 -- Add absolute timer
 local function at(time, f)
-	local timer = { time, f, close = closetimer };
-	t_insert(timers, timer);
-	resort_timers = true;
+	local timer = { time, f, close = closetimer, reschedule = reschedule, id = nil };
+	timer.id = timers:insert(timer, time);
 	return timer;
 end
 
@@ -94,50 +95,32 @@
 -- Return time until next timeout
 local function runtimers(next_delay, min_wait)
 	-- Any timers at all?
-	if not timers[1] then
+	local now = gettime();
+	local peek = timers:peek();
+	while peek do
+
+		if peek > now then
+			next_delay = peek - now;
+			break;
+	end
+
+		local _, timer, id = timers:pop();
+		local ok, ret = pcall(timer[2], now);
+		if ok and type(ret) == "number"  then
+			local next_time = now+ret;
+			timer[1] = next_time;
+			timers:insert(timer, next_time);
+	end
+
+		peek = timers:peek();
+			end
+	if peek == nil then
 		return next_delay;
 	end
 
-	if resort_timers then
-		-- Sort earliest timers to the end
-		t_sort(timers, function (a, b) return a[1] > b[1]; end);
-		resort_timers = false;
+	if next_delay < min_wait then
+		return min_wait;
 	end
-
-	-- Iterate from the end and remove completed timers
-	for i = #timers, 1, -1 do
-		local timer = timers[i];
-		local t, f = timer[1], timer[2];
-		-- Get time for every iteration to increase accuracy
-		local now = gettime();
-		if t > now then
-			-- This timer should not fire yet
-			local diff = t - now;
-			if diff < next_delay then
-				next_delay = diff;
-			end
-			break;
-		end
-		local new_timeout = f(now);
-		if new_timeout then
-			-- Schedule for 'delay' from the time actually scheduled,
-			-- not from now, in order to prevent timer drift.
-			timer[1] = t + new_timeout;
-			resort_timers = true;
-		else
-			t_remove(timers, i);
-		end
-	end
-
-	if resort_timers or next_delay < min_wait then
-		-- Timers may be added from within a timer callback.
-		-- Those would not be considered for next_delay,
-		-- and we might sleep for too long, so instead
-		-- we return a shorter timeout so we can
-		-- properly sort all new timers.
-		next_delay = min_wait;
-	end
-
 	return next_delay;
 end
 
@@ -243,8 +226,7 @@
 	end
 	t = t or cfg.read_timeout;
 	if self._readtimeout then
-		self._readtimeout[1] = gettime() + t;
-		resort_timers = true;
+		self._readtimeout:reschedule(gettime() + t);
 	else
 		self._readtimeout = addtimer(t, function ()
 			if self:on("readtimeout") then
@@ -268,8 +250,7 @@
 	end
 	t = t or cfg.send_timeout;
 	if self._writetimeout then
-		self._writetimeout[1] = gettime() + t;
-		resort_timers = true;
+		self._writetimeout:reschedule(gettime() + t);
 	else
 		self._writetimeout = addtimer(t, function ()
 			self:on("disconnect", "write timeout");
# HG changeset patch
# User Kim Alvefur <zash@zash.se>
# Date 1562546787 -7200
#      Mon Jul 08 02:46:27 2019 +0200
# Branch 0.11
# Node ID 7a36b7ac309bbca74ad75edd9cf86db33535ba7d
# Parent  c8c3f2eba8981ad83dcae18f4e69ebee23498f32
util.serialization: Cache default serialization instance (fixes #1389)

Most serialization uses still use the default serialize() and thus
duplicate much of the setup, which negates some of the performance
improvements of the rewrite.

diff --git a/util/serialization.lua b/util/serialization.lua
--- a/util/serialization.lua
+++ b/util/serialization.lua
@@ -272,10 +272,15 @@
 	return ret;
 end
 
+local default = new();
 return {
 	new = new;
 	serialize = function (x, opt)
-		return new(opt)(x);
+		if opt == nil then
+			return default(x);
+		else
+			return new(opt)(x);
+		end
 	end;
 	deserialize = deserialize;
 };