changeset 2753:77adb2b5c847

Merge with goffi
author Kim Alvefur <zash@zash.se>
date Sun, 27 Aug 2017 21:11:26 +0200
parents 6b710a8bdf03 (diff) d0e75bf21d30 (current diff)
children dbab58abd3e2
files
diffstat 18 files changed, 331 insertions(+), 193 deletions(-) [+]
line wrap: on
line diff
--- a/mod_auth_http_async/mod_auth_http_async.lua	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_auth_http_async/mod_auth_http_async.lua	Sun Aug 27 21:11:26 2017 +0200
@@ -28,6 +28,7 @@
 end
 
 local function async_http_auth(url, username, password)
+module:log("debug", "async_http_auth()");
 	local http = require "net.http";
 	local wait, done = async.waiter();
 	local content, code, request, response;
@@ -49,7 +50,8 @@
 	return nil, "Auth failed. Invalid username or password.";
 end
 
-local function sync_http_auth(url)
+local function sync_http_auth(url,username, password)
+module:log("debug", "sync_http_auth()");
 	local http = require "socket.http";
 	local https = require "ssl.https";
 	local request;
@@ -60,7 +62,7 @@
 	end
 	local _, code, headers, status = request{
 		url = url,
-		headers = { ACCEPT = "application/json, text/plain, */*"; }
+		headers = { Authorization = "Basic "..base64(username..":"..password);  }
 	};
 	if type(code) == "number" and code >= 200 and code <= 299 then
 		module:log("debug", "HTTP auth provider confirmed valid password");
@@ -77,7 +79,7 @@
 	if (have_async) then
 		return async_http_auth(url, username, password);
 	else
-		return sync_http_auth(url);
+		return sync_http_auth(url, username, password);
 	end
 end
 
@@ -111,4 +113,4 @@
 	});
 end
 
-module:provides("auth", provider);
+module:provides("auth", provider);
\ No newline at end of file
--- a/mod_block_registrations/README.markdown	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_block_registrations/README.markdown	Sun Aug 27 21:11:26 2017 +0200
@@ -31,7 +31,7 @@
     block_registrations_matching = {
       "master$" -- matches anything ending with master: postmaster, hostmaster, webmaster, etc.
     }
-    block_registrations_require = "^[a-zA-Z0-9_-.]+$" -- Allow only simple ASCII characters in usernames
+    block_registrations_require = "^[a-zA-Z0-9_.-]+$" -- Allow only simple ASCII characters in usernames
 
 Compatibility
 =============
--- a/mod_captcha_registration/modules/mod_register.lua	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_captcha_registration/modules/mod_register.lua	Sun Aug 27 21:11:26 2017 +0200
@@ -205,7 +205,7 @@
 local recent_ips = {};
 local min_seconds_between_registrations = module:get_option("min_seconds_between_registrations");
 local whitelist_only = module:get_option("whitelist_registration_only");
-local whitelisted_ips = module:get_option("registration_whitelist") or { "127.0.0.1" };
+local whitelisted_ips = module:get_option("registration_whitelist") or { "127.0.0.1", "::1" };
 local blacklisted_ips = module:get_option("registration_blacklist") or {};
 
 for _, ip in ipairs(whitelisted_ips) do whitelisted_ips[ip] = true; end
@@ -386,4 +386,4 @@
 	route = {
 		["GET /*"] = handle_http_request;
 	};
-});
\ No newline at end of file
+});
--- a/mod_cloud_notify/mod_cloud_notify.lua	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_cloud_notify/mod_cloud_notify.lua	Sun Aug 27 21:11:26 2017 +0200
@@ -4,6 +4,7 @@
 --
 -- This file is MIT/X11 licensed.
 
+local t_insert = table.insert;
 local st = require"util.stanza";
 local jid = require"util.jid";
 local dataform = require"util.dataforms".new;
@@ -76,10 +77,12 @@
 				if push_errors[push_identifier] >= max_push_errors then
 					module:log("warn", "Disabling push notifications for identifier '%s'", push_identifier);
 					-- remove push settings from sessions
-					for _, session in pairs(host_sessions[node].sessions) do
-						if session.push_identifier == push_identifier then
-							session.push_identifier = nil;
-							session.push_settings = nil;
+					if host_sessions[node] then
+						for _, session in pairs(host_sessions[node].sessions) do
+							if session.push_identifier == push_identifier then
+								session.push_identifier = nil;
+								session.push_settings = nil;
+							end
 						end
 					end
 					-- save changed global config
@@ -110,7 +113,7 @@
 		if hashes.sha256(push_identifier, true) == stanza.attr.id then
 			if user_push_services[push_identifier] and user_push_services[push_identifier].jid == from and push_errors[push_identifier] > 0 then
 				push_errors[push_identifier] = 0;
-				module:log("debug", "Push succeeded, error count for identifier '%s' is now at %s", push_identifier, tostring(push_errors[push_identifier]));
+				module:log("debug", "Push succeeded, error count for identifier '%s' is now at %s again", push_identifier, tostring(push_errors[push_identifier]));
 			end
 		end
 	end
@@ -132,6 +135,8 @@
 	local push_jid = enable.attr.jid;
 	-- SHOULD contain a 'node' attribute
 	local push_node = enable.attr.node;
+	-- CAN contain a 'include_payload' attribute
+	local include_payload = enable.attr.include_payload;
 	if not push_jid then
 		origin.log("debug", "Push notification enable request missing the 'jid' field");
 		origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing jid"));
@@ -146,6 +151,7 @@
 	local push_service = {
 		jid = push_jid;
 		node = push_node;
+		include_payload = include_payload;
 		count = 0;
 		options = publish_options and st.preserialize(publish_options);
 	};
@@ -197,6 +203,23 @@
 end
 module:hook("iq-set/self/"..xmlns_push..":disable", push_disable);
 
+-- clone a stanza and strip it
+local function strip_stanza(stanza)
+	local tags = {};
+	local new = { name = stanza.name, attr = { xmlns = stanza.attr.xmlns, type = stanza.attr.type }, tags = tags };
+	for i=1,#stanza do
+		local child = stanza[i];
+		if type(child) == "table" then		-- don't add raw text nodes
+			if child.name then
+				child = strip_stanza(child);
+				t_insert(tags, child);
+			end
+			t_insert(new, child);
+		end
+	end
+	return setmetatable(new, st.stanza_mt);
+end
+
 local push_form = dataform {
 	{ name = "FORM_TYPE"; type = "hidden"; value = "urn:xmpp:push:summary"; };
 	{ name = "message-count"; type = "text-single"; };
@@ -207,54 +230,71 @@
 
 -- http://xmpp.org/extensions/xep-0357.html#publishing
 local function handle_notify_request(stanza, node, user_push_services)
-	if not user_push_services or not #user_push_services then return end
+	local pushes = 0;
+	if not user_push_services or not #user_push_services then return pushes end
 	
 	for push_identifier, push_info in pairs(user_push_services) do
+		local send_push = true;		-- only send push to this node when not already done for this stanza or if no stanza is given at all
 		if stanza then
 			if not stanza._push_notify then stanza._push_notify = {}; end
 			if stanza._push_notify[push_identifier] then
 				module:log("debug", "Already sent push notification for %s@%s to %s (%s)", node, module.host, push_info.jid, tostring(push_info.node));
-				return;
+				send_push = false;
 			end
 			stanza._push_notify[push_identifier] = true;
 		end
-
-		-- increment count and save it
-		push_info.count = push_info.count + 1;
-		push_store:set_identifier(node, push_identifier, push_info);
-		-- construct push stanza
-		local stanza_id = hashes.sha256(push_identifier, true);
-		local push_publish = st.iq({ to = push_info.jid, from = node .. "@" .. module.host, type = "set", id = stanza_id })
-			:tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" })
-				:tag("publish", { node = push_info.node })
-					:tag("item")
-						:tag("notification", { xmlns = xmlns_push });
-		local form_data = {
-			["message-count"] = tostring(push_info.count);
-		};
-		if stanza and include_sender then
-			form_data["last-message-sender"] = stanza.attr.from;
+		
+		if send_push then
+			-- increment count and save it
+			push_info.count = push_info.count + 1;
+			push_store:set_identifier(node, push_identifier, push_info);
+			-- construct push stanza
+			local stanza_id = hashes.sha256(push_identifier, true);
+			local push_publish = st.iq({ to = push_info.jid, from = module.host, type = "set", id = stanza_id })
+				:tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" })
+					:tag("publish", { node = push_info.node })
+						:tag("item")
+							:tag("notification", { xmlns = xmlns_push });
+			local form_data = {
+				["message-count"] = tostring(push_info.count);
+			};
+			if stanza and include_sender then
+				form_data["last-message-sender"] = stanza.attr.from;
+			end
+			if stanza and include_body then
+				form_data["last-message-body"] = stanza:get_child_text("body");
+			end
+			push_publish:add_child(push_form:form(form_data));
+			if stanza and push_info.include_payload == "stripped" then
+				push_publish:tag("payload", { type = "stripped" })
+					:add_child(strip_stanza(stanza));
+				push_publish:up(); -- / payload
+			end
+			if stanza and push_info.include_payload == "full" then
+				push_publish:tag("payload", { type = "full" })
+					:add_child(st.clone(stanza));
+				push_publish:up(); -- / payload
+			end
+			push_publish:up(); -- / notification
+			push_publish:up(); -- / publish
+			push_publish:up(); -- / pubsub
+			if push_info.options then
+				push_publish:tag("publish-options"):add_child(st.deserialize(push_info.options));
+			end
+			-- send out push
+			module:log("debug", "Sending push notification for %s@%s to %s (%s)", node, module.host, push_info.jid, tostring(push_info.node));
+			-- module:log("debug", "PUSH STANZA: %s", tostring(push_publish));
+			-- handle push errors for this node
+			if push_errors[push_identifier] == nil then
+				push_errors[push_identifier] = 0;
+				module:hook("iq-error/bare/"..stanza_id, handle_push_error);
+				module:hook("iq-result/bare/"..stanza_id, handle_push_success);
+			end
+			module:send(push_publish);
+			pushes = pushes + 1;
 		end
-		if stanza and include_body then
-			form_data["last-message-body"] = stanza:get_child_text("body");
-		end
-		push_publish:add_child(push_form:form(form_data));
-		push_publish:up(); -- / notification
-		push_publish:up(); -- / publish
-		push_publish:up(); -- / pubsub
-		if push_info.options then
-			push_publish:tag("publish-options"):add_child(st.deserialize(push_info.options));
-		end
-		-- send out push
-		module:log("debug", "Sending push notification for %s@%s to %s (%s)", node, module.host, push_info.jid, tostring(push_info.node));
-		-- handle push errors for this node
-		if push_errors[push_identifier] == nil then
-			push_errors[push_identifier] = 0;
-			module:hook("iq-error/bare/"..stanza_id, handle_push_error);
-			module:hook("iq-result/bare/"..stanza_id, handle_push_success);
-		end
-		module:send(push_publish);
 	end
+	return pushes;
 end
 
 -- small helper function to extract relevant push settings
@@ -268,13 +308,14 @@
 -- publish on offline message
 module:hook("message/offline/handle", function(event)
 	local node, user_push_services = get_push_settings(event.stanza, event.origin);
-	return handle_notify_request(event.stanza, node, user_push_services);
+	module:log("debug", "Invoking cloud handle_notify_request() for offline stanza");
+	handle_notify_request(event.stanza, node, user_push_services);
 end, 1);
 
 -- publish on unacked smacks message
 local function process_smacks_stanza(stanza, session)
 	if session.push_identifier then
-		session.log("debug", "Invoking cloud handle_notify_request for smacks queued stanza");
+		session.log("debug", "Invoking cloud handle_notify_request() for smacks queued stanza");
 		local user_push_services = {[session.push_identifier] = session.push_settings};
 		local node = get_push_settings(stanza, session);
 		handle_notify_request(stanza, node, user_push_services);
@@ -282,16 +323,28 @@
 	return stanza;
 end
 
+local function process_smacks_queue(queue, session)
+	if not session.push_identifier then return; end
+	local user_push_services = {[session.push_identifier] = session.push_settings};
+	for i=1, #queue do
+		local stanza = queue[i];
+		local node = get_push_settings(stanza, session);
+		session.log("debug", "Invoking cloud handle_notify_request() for smacks queued stanza: %d", i);
+		if handle_notify_request(stanza, node, user_push_services) ~= 0 then
+			session.log("debug", "Cloud handle_notify_request() > 0, not notifying for other queued stanzas");
+			return;		-- only notify for one stanza in the queue, not for all in a row
+		end
+	end
+end
+
 -- smacks hibernation is started
 local function hibernate_session(event)
 	local session = event.origin;
 	local queue = event.queue;
 	-- process unacked stanzas
-	for i=1,#queue do
-		process_smacks_stanza(queue[i], session);
-	end
+	process_smacks_queue(queue, session);
 	-- process future unacked (hibernated) stanzas
-	filters.add_filter(session, "stanzas/out", process_smacks_stanza);
+	filters.add_filter(session, "stanzas/out", process_smacks_stanza, -990);
 end
 
 -- smacks hibernation is ended
@@ -312,9 +365,7 @@
 	local session = event.origin;
 	local queue = event.queue;
 	-- process unacked stanzas (handle_notify_request() will only send push requests for new stanzas)
-	for i=1,#queue do
-		process_smacks_stanza(queue[i], session);
-	end
+	process_smacks_queue(queue, session);
 end
 
 -- archive message added
@@ -344,13 +395,13 @@
 				end
 			end
 			if identifier_found then
-				identifier_found.log("debug", "Not notifying '%s' of new MAM stanza (session still alive)", identifier);
+				identifier_found.log("debug", "Not cloud notifying '%s' of new MAM stanza (session still alive)", identifier);
 			else
 				notify_push_sevices[identifier] = push_info;
 			end
 		end
 
-		return handle_notify_request(event.stanza, to, notify_push_sevices);
+		handle_notify_request(event.stanza, to, notify_push_sevices);
 	end
 end
 
@@ -363,26 +414,11 @@
 	local user = event.user;
 	local user_push_services = push_store:get(user);
 	local push_services = event.push_services or user_push_services;
-	return handle_notify_request(nil, user, push_services);
+	handle_notify_request(nil, user, push_services);
 end
 -- can be used by other modules to ping one or more (or all) push endpoints
 module:hook("cloud-notify-ping", send_ping);
 
--- TODO: this has to be done on first connect not on offline broadcast, else the counter will be incorrect
--- TODO: it seems this is already done, so this could be safely removed, couldn't it?
--- module:hook("message/offline/broadcast", function(event)
--- 	local origin = event.origin;
--- 	local user_push_services = push_store:get(origin.username);
--- 	if not #user_push_services then return end
--- 
--- 	for _, push_info in pairs(user_push_services) do
--- 		if push_info then
--- 			push_info.count = 0;
--- 		end
--- 	end
--- 	push_store:set(origin.username, user_push_services);
--- end, 1);
-
 module:log("info", "Module loaded");
 function module.unload()
 	if module.unhook then
--- a/mod_csi_battery_saver/README.markdown	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_csi_battery_saver/README.markdown	Sun Aug 27 21:11:26 2017 +0200
@@ -4,23 +4,41 @@
 - 'Stage-Alpha'
 ---
 
+Please use this module instead of [mod_csi_pump] if you want timestamping,
+properly handled carbon copies, support for handling encrypted messages and
+correctly handled smacks events.
+
+If smacks is used on the same server this needs at least version [f70c02c14161]
+of the smacks module! There could be message reordering on resume otherwise.
+
 Stanzas are queued in a buffer until either an "important" stanza is
 encountered or the buffer becomes full. Then all queued stanzas are sent
 at the same time. This way, nothing is lost or reordered while still
 allowing for power usage savings by not requiring mobile clients to
 bring up their radio for unimportant stanzas.
 
-`IQ` stanzas, smacks "stanzas" and `message` stanzas containing a body are
-considered important. Groupchat messages must set a subject or have
-the user's username or nickname in their messages to count as "important".
-`Presence` stanzas are not "important".
+`IQ` stanzas, and `message` stanzas containing a body or being encypted
+and all nonzas are considered important.
+If the config option `csi_battery_saver_filter_muc` is set to true,
+groupchat messages must set a subject or have the user's username or nickname
+in their messages to count as "important", if the config is false (default), all
+groupchat messages havin a body or being encrypted are considered "important".
+`Presence` stanzas are always considered not "important".
 
 All buffered stanzas that allow timestamping are properly stamped to
 reflect their original send time, see [XEP-0203].
 
 Use with other CSI plugins such as [mod_throttle_presence],
 [mod_filter_chatstates] or [mod_csi_pump] is *not* supported.
-Please use this module instead of [mod_csi_pump] if you want timestamping
-and properly handled carbon copies.
 
 The internal stanza buffer of this module is hardcoded to 100 stanzas.
+
+Configuration
+=============
+
+  Option                              Default           Description
+  ----------------------------------  ---------- -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  `csi_battery_saver_filter_muc`      false      Controls whether all muc messages having a body should be considered as important (false) or only such containing the user's room nic (true). Warning: you should only set this to true if your users can live with muc messages being delayed several minutes. 
+
+
+[f70c02c14161]: //hg.prosody.im/prosody-modules/raw-file/f70c02c14161/mod_smacks/mod_smacks.lua
\ No newline at end of file
--- a/mod_csi_battery_saver/mod_csi_battery_saver.lua	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_csi_battery_saver/mod_csi_battery_saver.lua	Sun Aug 27 21:11:26 2017 +0200
@@ -2,25 +2,29 @@
 -- Copyright (C) 2017 Thilo Molitor
 --
 
+local filter_muc = module:get_option_boolean("csi_battery_saver_filter_muc", false);
+
 module:depends"csi"
-module:depends"track_muc_joins"
+if filter_muc then module:depends"track_muc_joins"; end		-- only depend on this module if we actually use it
 local s_match = string.match;
 local s_sub = string.sub;
 local jid = require "util.jid";
 local new_queue = require "util.queue".new;
 local datetime = require "util.datetime";
+local st = require "util.stanza";
 
 local xmlns_delay = "urn:xmpp:delay";
 
 -- a log id for this module instance
 local id = s_sub(require "util.hashes".sha256(datetime.datetime(), true), 1, 4);
 
+
 -- Patched version of util.stanza:find() that supports giving stanza names
 -- without their namespace, allowing for every namespace.
 local function find(self, path)
 	local pos = 1;
 	local len = #path + 1;
-	
+
 	repeat
 		local xmlns, name, text;
 		local char = s_sub(path, pos, pos);
@@ -65,10 +69,11 @@
 		end
 		return true;
 	end
-	function q:flush()
+	function q:flush(alternative_output)
+		local out = alternative_output or output;
 		local item = self:pop();
 		while item do
-			output(item, self);
+			out(item, self);
 			item = self:pop();
 		end
 		return true;
@@ -94,7 +99,7 @@
 
 local function is_important(stanza, session)
 	local st_name = stanza and stanza.name or nil;
-	if not st_name then return false; end
+	if not st_name then return true; end	-- nonzas are always important
 	if st_name == "presence" then
 		-- TODO check for MUC status codes?
 		return false;
@@ -110,21 +115,39 @@
 		if carbon then stanza = carbon; end
 		-- carbon copied outgoing messages aren't important (but incoming carbon copies are!)
 		if carbon and stanza_direction == "out" then return false; end
-		
+
 		local st_type = stanza.attr.type;
 		if st_type == "headline" then
 			return false;
 		end
+
+		-- We can't check for nick in encrypted groupchat messages, so let's treat them as important
+		-- Some clients don't set a body or an empty body for encrypted messages
+
+		-- check omemo https://xmpp.org/extensions/inbox/omemo.html
+		if stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") or stanza:get_child("encrypted", "urn:xmpp:omemo:0") then return true; end
+
+		-- check xep27 pgp https://xmpp.org/extensions/xep-0027.html
+		if stanza:get_child("x", "jabber:x:encrypted") then return true; end
+
+		-- check xep373 pgp (OX) https://xmpp.org/extensions/xep-0373.html
+		if stanza:get_child("openpgp", "urn:xmpp:openpgp:0") then return true; end
+
 		local body = stanza:get_child_text("body");
 		if st_type == "groupchat" then
 			if stanza:get_child_text("subject") then return true; end
-			if not body then return false; end
-			if body:find(session.username, 1, true) then return true; end
-			local rooms = session.rooms_joined;
-			if not rooms then return false; end
-			local room_nick = rooms[jid.bare(stanza_direction == "in" and stanza.attr.from or stanza.attr.to)];
-			if room_nick and body:find(room_nick, 1, true) then return true; end
-			return false;
+			if body == nil or body == "" then return false; end
+			-- body contains text, let's see if we want to process it further
+			if filter_muc then
+				if body:find(session.username, 1, true) then return true; end
+				local rooms = session.rooms_joined;
+				if not rooms then return false; end
+				local room_nick = rooms[jid.bare(stanza_direction == "in" and stanza.attr.from or stanza.attr.to)];
+				if room_nick and body:find(room_nick, 1, true) then return true; end
+				return false;
+			else
+				return true;
+			end
 		end
 		return body ~= nil and body ~= "";
 	end
@@ -134,6 +157,7 @@
 module:hook("csi-client-inactive", function (event)
 	local session = event.origin;
 	if session.pump then
+		session.log("debug", "mod_csi_battery_saver(%s): Client is inactive, buffering unimportant outgoing stanzas", id);
 		session.pump:pause();
 	else
 		session.log("debug", "mod_csi_battery_saver(%s): Client is inactive the first time, initializing module for this session", id);
@@ -142,19 +166,21 @@
 		session.pump = pump;
 		session._pump_orig_send = session.send;
 		function session.send(stanza)
-			session.log("debug", "mod_csi_battery_saver(%s): Got stanza: <%s>", id, tostring(stanza.name));
+			session.log("debug", "mod_csi_battery_saver(%s): Got outgoing stanza: <%s>", id, tostring(stanza.name or stanza));
 			local important = is_important(stanza, session);
-			-- add delay stamp to unimported (buffered) stanzas that can/need be stamped
+			-- clone stanzas before adding delay stamp and putting them into the queue
+			if st.is_stanza(stanza) then stanza = st.clone(stanza); end
+			-- add delay stamp to unimportant (buffered) stanzas that can/need be stamped
 			if not important and is_stamp_needed(stanza, session) then stanza = add_stamp(stanza, session); end
+			-- add stanza to outgoing queue and flush the buffer if needed
 			pump:push(stanza);
 			if important then
-				session.log("debug", "mod_csi_battery_saver(%s): Encountered important stanza, flushing buffer: <%s>", id, tostring(stanza.name));
+				session.log("debug", "mod_csi_battery_saver(%s): Encountered important stanza, flushing buffer: <%s>", id, tostring(stanza.name or stanza));
 				pump:flush();
 			end
 			return true;
 		end
 	end
-	session.log("debug", "mod_csi_battery_saver(%s): Client is inactive, buffering unimportant stanzas", id);
 end);
 
 module:hook("csi-client-active", function (event)
@@ -165,12 +191,39 @@
 	end
 end);
 
+-- clean up this session on hibernation start
+module:hook("smacks-hibernation-start", function (event)
+	local session = event.origin;
+	if session.pump then
+		session.log("debug", "mod_csi_battery_saver(%s): Hibernation started, flushing buffer and afterwards disabling for this session", id);
+		session.pump:flush();
+		session.send = session._pump_orig_send;
+		session.pump = nil;
+		session._pump_orig_send = nil;
+	end
+end);
+
+-- clean up this session on hibernation end as well
+-- but don't change resumed.send(), it is already overwritten with session.send() by the smacks module
+module:hook("smacks-hibernation-end", function (event)
+	local session = event.resumed;
+	if session.pump then
+		session.log("debug", "mod_csi_battery_saver(%s): Hibernation ended without being started, flushing buffer and afterwards disabling for this session", id);
+		session.pump:flush(session.send);		-- use the fresh session.send() introduced by the smacks resume
+		-- don't reset session.send() because this is not the send previously overwritten by this module, but a fresh one
+		-- session.send = session._pump_orig_send;
+		session.pump = nil;
+		session._pump_orig_send = nil;
+	end
+end);
+
 function module.unload()
 	module:log("info", "%s: Unloading module, flushing all buffers", id);
 	local host_sessions = prosody.hosts[module.host].sessions;
 	for _, user in pairs(host_sessions) do
 		for _, session in pairs(user.sessions) do
 			if session.pump then
+				session.log("debug", "mod_csi_battery_saver(%s): Flushing buffer and restoring to original session.send()", id);
 				session.pump:flush();
 				session.send = session._pump_orig_send;
 				session.pump = nil;
--- a/mod_http_upload/README.markdown	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_http_upload/README.markdown	Sun Aug 27 21:11:26 2017 +0200
@@ -55,14 +55,6 @@
 http_upload_quota = 1234 -- bytes
 ```
 
-### File types
-
-Accepted file types can be limited by MIME type:
-
-``` lua
-http_upload_allowed_file_types = { "image/*", "text/plain" }
-```
-
 Path
 ----
 
--- a/mod_http_upload/mod_http_upload.lua	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_http_upload/mod_http_upload.lua	Sun Aug 27 21:11:26 2017 +0200
@@ -31,7 +31,6 @@
 local file_size_limit = module:get_option_number(module.name .. "_file_size_limit", 1024 * 1024); -- 1 MB
 local quota = module:get_option_number(module.name .. "_quota");
 local max_age = module:get_option_number(module.name .. "_expire_after");
-local allowed_file_types = module:get_option_set(module.name .. "_allowed_file_types");
 
 --- sanity
 local parser_body_limit = module:context("*"):get_option_number("http_max_content_size", 10*1024*1024);
@@ -46,7 +45,6 @@
 module:depends("disco");
 
 local http_files = module:depends("http_files");
-local mime_map = module:shared("/*/http_files/mime").types;
 
 -- namespaces
 local namespace = "urn:xmpp:http:upload:0";
@@ -90,6 +88,7 @@
 			if not deleted then
 				module:log("warn", "Could not delete expired upload %s: %s", filename, whynot or "delete failed");
 			end
+			os.remove(filename:match("^(.*)[/\\]"));
 			return false;
 		elseif item.time < upload_window and not lfs.attributes(filename) then
 			return false; -- File was not uploaded or has been deleted since
@@ -110,7 +109,7 @@
 	return sum < quota;
 end
 
-local function handle_request(origin, stanza, xmlns, filename, filesize, mimetype)
+local function handle_request(origin, stanza, xmlns, filename, filesize)
 	local username, host = origin.username, origin.host;
 	-- local clients only
 	if origin.type ~= "c2s" then
@@ -141,28 +140,6 @@
 		return true;
 	end
 
-	if mime_map then
-		local file_ext = filename:match("%.([^.]+)$");
-		if not mimetype then
-			mimetype = "application/octet-stream";
-			if file_ext then
-				mimetype = mime_map[file_ext] or mimetype;
-			end
-		else
-			if (not file_ext and mimetype ~= "application/octet-stream") or (file_ext and mime_map[file_ext] ~= mimetype) then
-				origin.send(st.error_reply(stanza, "modify", "bad-request", "MIME type does not match file extension"));
-				return true;
-			end
-		end
-	end
-
-	if allowed_file_types then
-		if not (allowed_file_types:contains(mimetype) or allowed_file_types:contains(mimetype:gsub("/.*", "/*"))) then
-			origin.send(st.error_reply(stanza, "cancel", "not-allowed", "File type not allowed"));
-			return true;
-		end
-	end
-
 	local reply = st.reply(stanza);
 	reply:tag("slot", { xmlns = xmlns });
 
@@ -207,8 +184,7 @@
 	local request = stanza.tags[1];
 	local filename = request.attr.filename;
 	local filesize = tonumber(request.attr.size);
-	local mimetype = request.attr["content-type"];
-	return handle_request(origin, stanza, namespace, filename, filesize, mimetype);
+	return handle_request(origin, stanza, namespace, filename, filesize);
 end);
 
 module:hook("iq/host/"..legacy_namespace..":request", function (event)
@@ -216,8 +192,7 @@
 	local request = stanza.tags[1];
 	local filename = request:get_child_text("filename");
 	local filesize = tonumber(request:get_child_text("size"));
-	local mimetype = request:get_child_text("content-type");
-	return handle_request(origin, stanza, legacy_namespace, filename, filesize, mimetype);
+	return handle_request(origin, stanza, legacy_namespace, filename, filesize);
 end);
 
 -- http service
@@ -281,8 +256,12 @@
 
 	local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]);
 	local headers = response.headers;
-	body = body or response.body or "";
-	headers.content_length = #body;
+	if type(body) == "string" then
+		headers.content_length = #body;
+	elseif io.type(body) == "file" then
+		headers.content_length = body:seek("end");
+		body:close();
+	end
 
 	local output = { status_line };
 	for k,v in pairs(headers) do
@@ -307,6 +286,7 @@
 
 local function serve_head(event, path)
 	event.response.send = send_response_sans_body;
+	event.response.send_file = send_response_sans_body;
 	return serve_uploaded_files(event, path);
 end
 
--- a/mod_mam/mod_mam.lua	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_mam/mod_mam.lua	Sun Aug 27 21:11:26 2017 +0200
@@ -56,6 +56,8 @@
 	archive = module:require "fallback_archive";
 end
 
+local use_total = true;
+
 local cleanup;
 
 -- Handle prefs.
@@ -154,7 +156,7 @@
 		limit = qmax + 1;
 		before = before; after = after;
 		reverse = reverse;
-		total = true;
+		total = use_total;
 	});
 
 	if not data then
@@ -383,14 +385,18 @@
 		end
 		return math.random(cleanup_interval, cleanup_interval * 2);
 	end);
+else
+	-- Don't ask the backend to count the potentially unbounded number of items,
+	-- it'll get slow.
+	use_total = false;
 end
 
 -- Stanzas sent by local clients
-module:hook("pre-message/bare", c2s_message_handler, 2);
-module:hook("pre-message/full", c2s_message_handler, 2);
+module:hook("pre-message/bare", c2s_message_handler, 0);
+module:hook("pre-message/full", c2s_message_handler, 0);
 -- Stanszas to local clients
-module:hook("message/bare", message_handler, 2);
-module:hook("message/full", message_handler, 2);
+module:hook("message/bare", message_handler, 0);
+module:hook("message/full", message_handler, 0);
 
 module:add_feature(xmlns_mam0); -- COMPAT with XEP-0313 v 0.1
 
--- a/mod_mam_muc/README.markdown	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_mam_muc/README.markdown	Sun Aug 27 21:11:26 2017 +0200
@@ -41,11 +41,8 @@
 
 muc_log_all_rooms = false; -- set to true to force logging of all rooms
 
--- This is the largest number of messages that are allowed to be retrieved in one MAM request.
-max_archive_query_results = 20;
-
 -- This is the largest number of messages that are allowed to be retrieved when joining a room.
-max_history_messages = 1000;
+max_history_messages = 20;
 ```
 
 Compatibility
--- a/mod_mam_muc/mod_mam_muc.lua	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_mam_muc/mod_mam_muc.lua	Sun Aug 27 21:11:26 2017 +0200
@@ -12,7 +12,8 @@
 local xmlns_delay   = "urn:xmpp:delay";
 local xmlns_forward = "urn:xmpp:forward:0";
 local xmlns_st_id   = "urn:xmpp:sid:0";
-local muc_form_enable_logging = "muc#roomconfig_enablelogging"
+local xmlns_muc_user = "http://jabber.org/protocol/muc#user";
+local muc_form_enable = "muc#roomconfig_enablearchiving"
 
 local st = require "util.stanza";
 local rsm = require "util.rsm";
@@ -40,8 +41,13 @@
 local time_now = os.time;
 local m_min = math.min;
 local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse;
-local max_history_length = module:get_option_number("max_history_messages", 1000);
-local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", max_history_length);
+
+local default_history_length = 20;
+local max_history_length = module:get_option_number("max_history_messages", math.huge);
+
+local function get_historylength(room)
+	return math.min(room._data.history_length or default_history_length, max_history_length);
+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);
@@ -61,11 +67,11 @@
 	return false;
 end
 
-local function logging_enabled(room)
+local function archiving_enabled(room)
 	if log_all_rooms then
 		return true;
 	end
-	local enabled = room._data.logging;
+	local enabled = room._data.archiving;
 	if enabled == nil then
 		return log_by_default;
 	end
@@ -78,7 +84,7 @@
 if not new_muc then -- 0.10 or older
 	module:hook("muc-room-created", function (event)
 		local room = event.room;
-		if logging_enabled(room) then
+		if archiving_enabled(room) then
 			room.send_history = send_history;
 			room.save_to_history = save_to_history;
 		end
@@ -86,7 +92,7 @@
 
 	function module.load()
 		for room in each_room() do
-			if logging_enabled(room) then
+			if archiving_enabled(room) then
 				room.send_history = send_history;
 				room.save_to_history = save_to_history;
 			end
@@ -107,21 +113,21 @@
 		local room, form = event.room, event.form;
 		table.insert(form,
 		{
-			name = muc_form_enable_logging,
+			name = muc_form_enable,
 			type = "boolean",
-			label = "Enable Logging?",
-			value = logging_enabled(room),
+			label = "Enable archiving?",
+			value = archiving_enabled(room),
 		}
 		);
 	end);
 
 	module:hook("muc-config-submitted", function(event)
 		local room, fields, changed = event.room, event.fields, event.changed;
-		local new = fields[muc_form_enable_logging];
-		if new ~= room._data.logging then
-			room._data.logging = new;
+		local new = fields[muc_form_enable];
+		if new ~= room._data.archiving then
+			room._data.archiving = new;
 			if type(changed) == "table" then
-				changed[muc_form_enable_logging] = true;
+				changed[muc_form_enable] = true;
 			else
 				event.changed = true;
 			end
@@ -198,9 +204,14 @@
 		qstart, qend = vstart, vend;
 	end
 
+	module:log("debug", "Archive query id %s from %s until %s)",
+		tostring(qid),
+		qstart and timestamp(qstart) or "the dawn of time",
+		qend and timestamp(qend) or "now");
+
 	-- RSM stuff
 	local qset = rsm.get(query);
-	local qmax = m_min(qset and qset.max or default_max_items, max_max_items);
+	local qmax = m_min(qset and qset.max or 20, 20);
 	local reverse = qset and qset.before or false;
 
 	local before, after = qset and qset.before, qset and qset.after;
@@ -212,7 +223,6 @@
 		limit = qmax + 1;
 		before = before; after = after;
 		reverse = reverse;
-		total = true;
 		with = "message<groupchat";
 	});
 
@@ -220,7 +230,6 @@
 		origin.send(st.error_reply(stanza, "cancel", "internal-server-error"));
 		return true;
 	end
-	local total = tonumber(err);
 
 	local msg_reply_attr = { to = stanza.attr.from, from = stanza.attr.to };
 
@@ -241,6 +250,14 @@
 				:tag("forwarded", { xmlns = xmlns_forward })
 					:tag("delay", { xmlns = xmlns_delay, stamp = timestamp(when) }):up();
 
+		if room:get_whois() ~= "anyone" then
+			item:maptags(function (tag)
+				if tag.name == "x" and tag.attr.xmlns == xmlns_muc_user then
+					return nil;
+				end
+				return tag;
+			end);
+		end
 		if not is_stanza(item) then
 			item = st.deserialize(item);
 		end
@@ -270,22 +287,22 @@
 	origin.send(st.reply(stanza)
 		:tag("fin", { xmlns = xmlns_mam, queryid = qid, complete = complete })
 			:add_child(rsm.generate {
-				first = first, last = last, count = total }));
+				first = first, last = last }));
 	return true;
 end);
 
 module:hook("muc-get-history", function (event)
 	local room = event.room;
-	if not logging_enabled(room) then return end
+	if not archiving_enabled(room) then return end
 	local room_jid = room.jid;
-	local maxstanzas = event.maxstanzas;
+	local maxstanzas = event.maxstanzas or math.huge;
 	local maxchars = event.maxchars;
 	local since = event.since;
 	local to = event.to;
 
 	-- Load all the data!
 	local query = {
-		limit = m_min(maxstanzas or 20, max_history_length);
+		limit = math.min(maxstanzas, get_historylength(room));
 		start = since;
 		reverse = true;
 		with = "message<groupchat";
@@ -303,6 +320,14 @@
 		item.attr.to = to;
 		item:tag("delay", { xmlns = "urn:xmpp:delay", from = room_jid, stamp = timestamp(when) }):up(); -- XEP-0203
 		item:tag("stanza-id", { xmlns = xmlns_st_id, by = room_jid, id = id }):up();
+		if room:get_whois() ~= "anyone" then
+			item:maptags(function (tag)
+				if tag.name == "x" and tag.attr.xmlns == xmlns_muc_user then
+					return nil;
+				end
+				return tag;
+			end);
+		end
 		if maxchars then
 			local chars = #tostring(item);
 			if maxchars - chars < 0 then
@@ -362,11 +387,25 @@
 		and jid_prep(tag.attr.by) == self.jid then
 			return nil;
 		end
+		if tag.name == "x" and tag.attr.xmlns == xmlns_muc_user then
+			return nil;
+		end
 		return tag;
 	end);
 
+	local stored_stanza = stanza;
+
+	if stanza.name == "message" and self:get_whois() == "anyone" then
+		stored_stanza = st.clone(stanza);
+		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);
+		stored_stanza:add_direct_child(st.stanza("x", { xmlns = xmlns_muc_user })
+			:tag("item", { affiliation = affiliation; role = role; jid = actor }));
+	end
+
 	-- Policy check
-	if not logging_enabled(self) then return end -- Don't log
+	if not archiving_enabled(self) then return end -- Don't log
 
 	-- And stash it
 	local with = stanza.name
@@ -374,7 +413,7 @@
 		with = with .. "<" .. stanza.attr.type
 	end
 
-	local id = archive:append(room_node, nil, stanza, time_now(), with);
+	local id = archive:append(room_node, nil, stored_stanza, time_now(), with);
 
 	if id then
 		stanza:add_direct_child(st.stanza("stanza-id", { xmlns = xmlns_st_id, by = self.jid, id = id }));
--- a/mod_muc_log/README.markdown	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_muc_log/README.markdown	Sun Aug 27 21:11:26 2017 +0200
@@ -39,7 +39,6 @@
   0.7   Works
   0.8   Works
   0.9   Works
-  0.6   Works
   ----- -------
 
 **Note** that per-room configuration only works in 0.9+.
--- a/mod_register_redirect/mod_register_redirect.lua	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_register_redirect/mod_register_redirect.lua	Sun Aug 27 21:11:26 2017 +0200
@@ -8,7 +8,7 @@
 local st = require "util.stanza"
 local cman = configmanager
 
-local ip_wl = module:get_option_set("registration_whitelist", { "127.0.0.1" })
+local ip_wl = module:get_option_set("registration_whitelist", { "127.0.0.1", "::1" })
 local url = module:get_option_string("registration_url", nil)
 local inst_text = module:get_option_string("registration_text", nil)
 local oob = module:get_option_boolean("registration_oob", true)
--- a/mod_register_web/mod_register_web.lua	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_register_web/mod_register_web.lua	Sun Aug 27 21:11:26 2017 +0200
@@ -1,6 +1,7 @@
 local captcha_options = module:get_option("captcha_options", {});
 local nodeprep = require "util.encodings".stringprep.nodeprep;
 local usermanager = require "core.usermanager";
+local datamanager = require "util.datamanager";
 local http = require "net.http";
 local path_sep = package.config:sub(1,1);
 local json = require "util.json".decode;
@@ -39,12 +40,12 @@
 
 	function generate_captcha(display_options)
 		return recaptcha_tpl.apply(setmetatable({
-	  		recaptcha_display_error = display_options and display_options.recaptcha_error
-	  			and ("&error="..display_options.recaptcha_error) or "";
-	  	}, {
-	  		__index = function (t, k)
-	  			if captcha_options[k] then return captcha_options[k]; end
-	  			module:log("error", "Missing parameter from captcha_options: %s", k);
+			recaptcha_display_error = display_options and display_options.recaptcha_error
+			and ("&error="..display_options.recaptcha_error) or "";
+		}, {
+			__index = function (t, k)
+				if captcha_options[k] then return captcha_options[k]; end
+				module:log("error", "Missing parameter from captcha_options: %s", k);
 			end
 		}));
 	end
@@ -128,6 +129,9 @@
 	if not registering.allowed then
 		return nil, "Registration not allowed";
 	end
+	if form.confirm_password ~= form.password then
+		return nil, "Passwords don't match";
+	end
 	local ok, err = usermanager.create_user(prepped_username, form.password, module.host);
 	if ok then
 		local extra_data = {};
--- a/mod_register_web/templates/register.html	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_register_web/templates/register.html	Sun Aug 27 21:11:26 2017 +0200
@@ -18,6 +18,10 @@
             <th>Password:</th>
             <td><input name="password" required type="password"></td>
           </tr>
+          <tr>
+            <th>Confirm Password:</th>
+            <td><input name="confirm_password" required type="password"></td>
+          </tr>
           {captcha}
           <tr>
             <td colspan="2"><input type="submit" value="Register!"></td>
--- a/mod_secure_interfaces/mod_secure_interfaces.lua	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_secure_interfaces/mod_secure_interfaces.lua	Sun Aug 27 21:11:26 2017 +0200
@@ -1,13 +1,18 @@
-local secure_interfaces = module:get_option_set("secure_interfaces", { "127.0.0.1" });
+local secure_interfaces = module:get_option_set("secure_interfaces", { "127.0.0.1", "::1" });
 
 module:hook("stream-features", function (event)
 	local session = event.origin;
 	if session.type ~= "c2s_unauthed" then return; end
 	local socket = session.conn:socket();
-	if not socket.getsockname then return; end
+	if not socket.getsockname then
+		module:log("debug", "Unable to determine local address of incoming connection");
+		return;
+	end
 	local localip = socket:getsockname();
 	if secure_interfaces:contains(localip) then
-		module:log("debug", "Marking session from %s as secure", session.ip or "[?]");
+		module:log("debug", "Marking session from %s to %s as secure", session.ip or "[?]", localip);
 		session.secure = true;
+	else
+		module:log("debug", "Not marking session from %s to %s as secure", session.ip or "[?]", localip);
 	end
 end, 2500);
--- a/mod_smacks/mod_smacks.lua	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_smacks/mod_smacks.lua	Sun Aug 27 21:11:26 2017 +0200
@@ -152,12 +152,12 @@
 
 local function request_ack_if_needed(session, force)
 	local queue = session.outgoing_stanza_queue;
-	if session.awaiting_ack == nil then
+	if session.awaiting_ack == nil and not session.hibernating then
 		if (#queue > max_unacked_stanzas and session.last_queue_count ~= #queue) or force then
 			session.log("debug", "Queuing <r> (in a moment)");
 			session.awaiting_ack = false;
 			session.awaiting_ack_timer = stoppable_timer(1e-06, function ()
-				if not session.awaiting_ack then
+				if not session.awaiting_ack and not session.hibernating then
 					session.log("debug", "Sending <r> (inside timer, before send)");
 					(session.sends2s or session.send)(st.stanza("r", { xmlns = session.smacks }))
 					session.log("debug", "Sending <r> (inside timer, after send)");
@@ -170,15 +170,17 @@
 				end
 			end);
 		end
-		-- Trigger "smacks-ack-delayed"-event if we added new (ackable) stanzas to the outgoing queue
-		-- and there isn't already a timer for this event running.
-		-- If we wouldn't do this, stanzas added to the queue after the first "smacks-ack-delayed"-event
-		-- would not trigger this event (again).
-		if #queue > max_unacked_stanzas and session.awaiting_ack and session.delayed_ack_timer == nil then
-			session.log("debug", "Calling delayed_ack_function directly (still waiting for ack)");
-			delayed_ack_function(session);
-		end
 	end
+	
+	-- Trigger "smacks-ack-delayed"-event if we added new (ackable) stanzas to the outgoing queue
+	-- and there isn't already a timer for this event running.
+	-- If we wouldn't do this, stanzas added to the queue after the first "smacks-ack-delayed"-event
+	-- would not trigger this event (again).
+	if #queue > max_unacked_stanzas and session.awaiting_ack and session.delayed_ack_timer == nil then
+		session.log("debug", "Calling delayed_ack_function directly (still waiting for ack)");
+		delayed_ack_function(session);
+	end
+	
 	session.last_queue_count = #queue;
 end
 
@@ -507,7 +509,6 @@
 		-- Ok, we need to re-send any stanzas that the client didn't see
 		-- ...they are what is now left in the outgoing stanza queue
 		local queue = original_session.outgoing_stanza_queue;
-		module:fire_event("smacks-hibernation-end", {origin = session, resumed = original_session, queue = queue});
 		original_session.log("debug", "#queue = %d", #queue);
 		for i=1,#queue do
 			original_session.send(queue[i]);
@@ -517,6 +518,7 @@
 			session.log("warn", "Tried to send stanza on old session migrated by smacks resume (maybe there is a bug?): %s", tostring(stanza));
 			return false;
 		end
+		module:fire_event("smacks-hibernation-end", {origin = session, resumed = original_session, queue = queue});
 		request_ack_if_needed(original_session, true);
 	else
 		module:log("warn", "Client %s@%s[%s] tried to resume stream for %s@%s[%s]",
--- a/mod_webpresence/README.markdown	Sun Aug 27 20:46:04 2017 +0200
+++ b/mod_webpresence/README.markdown	Sun Aug 27 21:11:26 2017 +0200
@@ -50,6 +50,8 @@
 =============
 
   ----- -------
+  trunk   Works
+  0.10   Works
   0.9   Works
   0.8   Works
   0.7   Works
@@ -59,7 +61,6 @@
 Todo
 ====
 
--   JSON?
 -   Display PEP information (maybe a new plugin?)
 -   More (free) iconsets
 -   Internal/external image generator (GD, ImageMagick)