changeset 1324:853a382c9bd6

mod_turncredentials: Advertise the XEP-0215 feature (thanks Gryffus)
author Kim Alvefur <zash@zash.se>
date Fri, 28 Feb 2014 15:36:06 +0100
parents c84ff82658cb
children b21236b6b8d8
files mod_auth_ccert/mod_auth_ccert.lua mod_auth_dovecot/auth_dovecot/sasl_dovecot.lib.lua mod_block_strangers/mod_block_strangers.lua mod_carbons/mod_carbons.lua mod_default_bookmarks/mod_default_bookmarks.lua mod_firewall/mod_firewall.lua mod_http_altconnect/mod_http_altconnect.lua mod_http_dir_listing/http_dir_listing/resources/style.css mod_http_dir_listing/http_dir_listing/resources/template.html mod_mam/mod_mam.lua mod_mam_muc/mod_mam_muc.lua mod_manifesto/mod_manifesto.lua mod_muc_log/mod_muc_log.lua mod_pubsub_feeds/mod_pubsub_feeds.lua mod_pubsub_hub/mod_pubsub_hub.lua mod_s2s_auth_dane/mod_s2s_auth_dane.lua mod_s2s_auth_fingerprint/mod_s2s_auth_fingerprint.lua mod_s2s_blacklist/mod_s2s_blacklist.lua mod_s2s_keysize_policy/mod_s2s_keysize_policy.lua mod_smacks/mod_smacks.lua mod_srvinjection/mod_srvinjection.lua mod_storage_mongodb/mod_storage_mongodb.lua mod_turncredentials/mod_turncredentials.lua mod_websocket/mod_websocket.lua
diffstat 24 files changed, 280 insertions(+), 86 deletions(-) [+]
line wrap: on
line diff
--- a/mod_auth_ccert/mod_auth_ccert.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_auth_ccert/mod_auth_ccert.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -60,7 +60,7 @@
 function get_sasl_handler(session)
 	return new_sasl(module.host, {
 		external = session.secure and function(authz)
-			if not session.secure then
+			if not session.secure or not session.conn:ssl() then
 				-- getpeercertificate() on a TCP connection would be bad, abort!
 				(session.log or log)("error", "How did you manage to select EXTERNAL without TLS?");
 				return nil, false;
--- a/mod_auth_dovecot/auth_dovecot/sasl_dovecot.lib.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_auth_dovecot/auth_dovecot/sasl_dovecot.lib.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -62,8 +62,8 @@
 	end
 
 	if not ok then
-		return false, "error connecting to dovecot "..tostring(socket_type).." socket at '"
-			..tostring(socket_path or socket_info).."'. error was '"..tostring(err).."'";
+		log("error", "error connecting to dovecot %s socket at '%s'. error was '%s'", socket_type, socket_path or socket_info, err);
+		return false;
 	end
 
 	-- Send our handshake
@@ -95,6 +95,7 @@
 			local major_version = parts();
 
 			if major_version ~= "1" then
+				log("error", "dovecot server version is not 1.x. it is %s.x", major_version);
 				conn:close();
 				return false, "dovecot server version is not 1.x. it is "..tostring(major_version)..".x";
 			end
--- a/mod_block_strangers/mod_block_strangers.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_block_strangers/mod_block_strangers.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -2,6 +2,7 @@
 local jid_split = require "util.jid".split;
 local jid_bare = require "util.jid".bare;
 local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
+local error_reply = require "util.stanza".error_reply;
 
 function check_subscribed(event)
 	local stanza = event.stanza;
@@ -11,7 +12,7 @@
 		if to_resource and stanza.attr.type == "groupchat" then
 			return nil; -- Pass through
 		end
-		return true; -- Drop stanza
+		return error_reply("auth", "forbidden", "Subscription required"); -- Bounce with error
 	end
 end
 
--- a/mod_carbons/mod_carbons.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_carbons/mod_carbons.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -62,10 +62,11 @@
 		return -- No use in sending carbons to an offline user
 	end
 
-	if stanza:get_child("private", xmlns_carbons) then
+	local private_tag = stanza:child_with_name("private");
+	if private_tag and private.attr.xmlns == xmlns_carbons or private.attr.xmlns == xmlns_carbons_old then
 		if not c2s then
 			stanza:maptags(function(tag)
-				if not ( tag.attr.xmlns == xmlns_carbons and tag.name == "private" ) then
+				if tag ~= private_tag then
 					return tag;
 				end
 			end);
--- a/mod_default_bookmarks/mod_default_bookmarks.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_default_bookmarks/mod_default_bookmarks.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -13,37 +13,34 @@
 local dm_load = require "util.datamanager".load
 local jid_split = require "util.jid".split
 
-module:hook("iq/self/jabber:iq:private:query", function(event)
+local private_bookmarks_ns = "storage:storage:bookmarks";
+
+local bookmarks = module:get_option("default_bookmarks");
+
+module:hook("iq-get/self/jabber:iq:private:query", function(event)
 	local origin, stanza = event.origin, event.stanza;
-	local typ = stanza.attr.type;
 	local from = stanza.attr.from;
-	local query = stanza.tags[1];
-	if #query.tags == 1 and typ == "get" then
-		local tag = query.tags[1];
-		local key = tag.name..":"..tag.attr.xmlns;
-		if key == "storage:storage:bookmarks" then
-			local data, err = dm_load(origin.username, origin.host, "private");
-			if not(data and data[key]) then
-				local bookmarks = module:get_option("default_bookmarks");
-				if bookmarks and #bookmarks > 0 then
-					local reply = st.reply(stanza):tag("query", {xmlns = "jabber:iq:private"})
-						:tag("storage", { xmlns = "storage:bookmarks" });
-					local nick = jid_split(from);
-					for i=1,#bookmarks do
-						local bookmark = bookmarks[i];
-						if type(bookmark) ~= "table" then -- assume it's only a jid
-							bookmark = { jid = bookmark, name = jid_split(bookmark) };
-						end
-						reply:tag("conference", {
-							jid = bookmark.jid,
-							name = bookmark.name,
-							autojoin = "1",
-						}):tag("nick"):text(nick):up():up();
-					end
-					origin.send(reply);
-					return true;
-				end
-			end
+	if not stanza.tags[1]:get_child("storage", "storage:bookmarks") then return end
+	local data, err = dm_load(origin.username, origin.host, "private");
+	if data and data[private_bookmarks_ns] then return end
+
+	local reply = st.reply(stanza):tag("query", {xmlns = "jabber:iq:private"})
+		:tag("storage", { xmlns = "storage:bookmarks" });
+
+	local nick = jid_split(from);
+
+	local bookmark;
+	for i=1,#bookmarks do
+		bookmark = bookmarks[i];
+		if type(bookmark) ~= "table" then -- assume it's only a jid
+			bookmark = { jid = bookmark, name = jid_split(bookmark) };
 		end
+		reply:tag("conference", {
+			jid = bookmark.jid,
+			name = bookmark.name,
+			autojoin = "1",
+		}):tag("nick"):text(nick):up():up();
 	end
+	origin.send(reply);
+	return true;
 end, 1);
--- a/mod_firewall/mod_firewall.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_firewall/mod_firewall.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -27,6 +27,10 @@
 		type = "event"; "route/remote";
 		priority = 0.1;
 	};
+	send_remote = { -- FIXME name
+		type = "filter"; "s2sout";
+		priority = 0.1;
+	};
 };
 
 local function idsafe(name)
@@ -372,9 +376,13 @@
 					module:log("error", "Compilation error for %s: %s", script, err);
 				else
 					local chain_definition = chains[chain];
-					if chain_definition and chain_definition.type == "event" then
-						for _, event_name in ipairs(chain_definition) do
-							module:hook(event_name, handler, chain_definition.priority);
+					if chain_definition then
+						if chain_definition.type == "event" then
+							for _, event_name in ipairs(chain_definition) do
+								module:hook(event_name, handler, chain_definition.priority);
+							end
+						elseif chain_definition.type == "filter" then
+							-- TODO
 						end
 					elseif not chain:match("^user/") then
 						module:log("warn", "Unknown chain %q", chain);
--- a/mod_http_altconnect/mod_http_altconnect.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_http_altconnect/mod_http_altconnect.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -6,16 +6,20 @@
 local json = require"util.json";
 local st = require"util.stanza";
 local array = require"util.array";
+local it = require"util.iterators";
 
 local host_modules = hosts[module.host].modules;
 
 local function get_supported()
-	local uris = array();
-	if host_modules["bosh"] then
-		uris:push({ rel = "urn:xmpp:alt-connections:xbosh", href = module:http_url("bosh", "/http-bind") });
-	end
-	if host_modules["websocket"] then
-		uris:push({ rel = "urn:xmpp:alt-connections:websocket", href = module:http_url("websocket", "xmpp-websocket"):gsub("^http", "ws") });
+	local uris = array(it.values(module:get_host_items("alt-conn-method")));
+	if #uris == 0 then
+		-- COMPAT for with before item array was added
+		if host_modules["bosh"] then
+			uris:push({ rel = "urn:xmpp:alt-connections:xbosh", href = module:http_url("bosh", "/http-bind") });
+		end
+		if host_modules["websocket"] then
+			uris:push({ rel = "urn:xmpp:alt-connections:websocket", href = module:http_url("websocket", "xmpp-websocket"):gsub("^http", "ws") });
+		end
 	end
 	return uris;
 end
--- a/mod_http_dir_listing/http_dir_listing/resources/style.css	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_http_dir_listing/http_dir_listing/resources/style.css	Fri Feb 28 15:36:06 2014 +0100
@@ -5,6 +5,12 @@
 a:link:hover,a:visited:hover{color:#3465a4;}
 .filelist{background-color:white;padding:1em;list-style-position:inside;-moz-column-width:20em;-webkit-column-width:20em;-ms-column-width:20em;column-width:20em;}
 .file{list-style-image:url(text-x-generic.png);}
+.file.image{list-style-image:url(image-x-generic.png);}
+.file.video{list-style-image:url(video-x-generic.png);}
+.file.audio{list-style-image:url(audio-x-generic.png);}
+.file.vcf{list-style-image:url(x-office-address-book.png);}
+.file.text.html{list-style-image:url(text-html.png);}
+.file.application{list-style-image:url(application-x-executable.png);}
 .directory{list-style-image:url(folder.png);}
 .parent{list-style-image:url(user-home.png);}
 footer{margin-top:1ex;font-size:smaller;color:#babdb6;}
--- a/mod_http_dir_listing/http_dir_listing/resources/template.html	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_http_dir_listing/http_dir_listing/resources/template.html	Fri Feb 28 15:36:06 2014 +0100
@@ -6,9 +6,9 @@
   </head>
   <body>
     <h1>Index of {path}</h1>
-
-		{filelist}
-
+      <article>
+        {filelist}
+      </article>
     <footer>{footer}</footer>
   </body>
 </html>
--- a/mod_mam/mod_mam.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_mam/mod_mam.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -1,9 +1,9 @@
 -- XEP-0313: Message Archive Management for Prosody
--- Copyright (C) 2011-2012 Kim Alvefur
+-- Copyright (C) 2011-2014 Kim Alvefur
 --
 -- This file is MIT/X11 licensed.
 
-local xmlns_mam     = "urn:xmpp:mam:tmp";
+local xmlns_mam     = "urn:xmpp:mam:0" or ":1";
 local xmlns_delay   = "urn:xmpp:delay";
 local xmlns_forward = "urn:xmpp:forward:0";
 
@@ -16,6 +16,7 @@
 local jid_bare = require "util.jid".bare;
 local jid_split = require "util.jid".split;
 local jid_prep = require "util.jid".prep;
+local dataform = require "util.dataforms".new;
 local host = module.host;
 
 local rm_load_roster = require "core.rostermanager".load_roster;
@@ -61,18 +62,37 @@
 	end
 end);
 
+local query_form = dataform {
+	{ name = "FORM_TYPE"; type = "hidden"; value = "urn:xmpp:mam:0"; };
+	{ name = "with"; type = "jid-single"; };
+	{ name = "start"; type = "text-single" };
+	{ name = "end"; type = "text-single"; };
+};
+
+-- Serve form
+module:hook("iq-get/self/"..xmlns_mam..":query", function(event)
+	local origin, stanza = event.origin, event.stanza;
+	return origin.send(st.reply(stanza):add_child(query_form:form()));
+end);
+
 -- Handle archive queries
-module:hook("iq-get/self/"..xmlns_mam..":query", function(event)
+module:hook("iq-set/self/"..xmlns_mam..":query", function(event)
 	local origin, stanza = event.origin, event.stanza;
 	local query = stanza.tags[1];
 	local qid = query.attr.queryid;
 
 	-- Search query parameters
-	local qwith = query:get_child_text("with");
-	local qstart = query:get_child_text("start");
-	local qend = query:get_child_text("end");
-	module:log("debug", "Archive query, id %s with %s from %s until %s)",
-		tostring(qid), qwith or "anyone", qstart or "the dawn of time", qend or "now");
+	local qwith, qstart, qend;
+	local form = query:get_child("x", "jabber:x:data");
+	if form then
+		local err;
+		form, err = query_form:data(form);
+		if err then
+			return origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err))))
+		end
+		qwith, qstart, qend = form["with"], form["start"], form["end"];
+		qwith = qwith and jid_bare(qwith);
+	end
 
 	if qstart or qend then -- Validate timestamps
 		local vstart, vend = (qstart and timestamp_parse(qstart)), (qend and timestamp_parse(qend))
@@ -83,14 +103,8 @@
 		qstart, qend = vstart, vend;
 	end
 
-	if qwith then -- Validate the 'with' jid
-		local pwith = qwith and jid_prep(qwith);
-		if pwith and not qwith then -- it failed prepping
-			origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid JID"))
-			return true
-		end
-		qwith = jid_bare(pwith);
-	end
+	module:log("debug", "Archive query, id %s with %s from %s until %s)",
+		tostring(qid), qwith or "anyone", qstart or "the dawn of time", qend or "now");
 
 	-- RSM stuff
 	local qset = rsm.get(query);
@@ -116,7 +130,7 @@
 	local count = err;
 
 	-- Wrap it in stuff and deliver
-	local first, last;
+	local first_id, last_id, first_time, last_time;
 	for id, item, when in data do
 		local fwd_st = st.message{ to = origin.full_jid }
 			:tag("result", { xmlns = xmlns_mam, queryid = qid, id = id })
@@ -129,18 +143,27 @@
 		item.attr.xmlns = "jabber:client";
 		fwd_st:add_child(item);
 
-		if not first then first = id; end
-		last = id;
+		if not first_id then
+			first_id = id;
+			first_time = when;
+		end
+		last_id = id;
+		last_time = when;
 
 		origin.send(fwd_st);
 	end
 	-- That's all folks!
 	module:log("debug", "Archive query %s completed", tostring(qid));
 
-	if reverse then first, last = last, first; end
+	if reverse then
+		first_id, last_id, first_time, last_time =
+		last_id, first_id, last_time, first_time;
+	end
 	return origin.send(st.reply(stanza)
-		:query(xmlns_mam):add_child(rsm.generate {
-			first = first, last = last, count = count }));
+		:query(xmlns_mam)
+			:add_child(query_form:form({ start = timestamp(first_time), ["end"] = timestamp(last_time), with = qwith  }))
+			:add_child(rsm.generate {
+				first = first_id, last = last_id, count = count }));
 end);
 
 local function has_in_roster(user, who)
--- a/mod_mam_muc/mod_mam_muc.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_mam_muc/mod_mam_muc.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -111,6 +111,36 @@
 	end);
 end
 
+module:hook("muc-config-form", function(event)
+	local room, form = event.room, event.form;
+	local mam_query = room._data.mam_query or 'anyone';
+	table.insert(form, {
+		name = muc_form_allow_who,
+		type = 'list-single',
+		label = 'Who may query the archive?',
+		value = {
+			{ value = 'moderators', label = 'Moderators Only',     default = mam_query == 'moderators' },
+			{ value = 'members',    label = 'Members',             default = mam_query == 'members' },
+			{ value = 'anyone',     label = 'Anyone who can join', default = mam_query == 'anyone' },
+		}
+	}
+	);
+end);
+
+module:hook("muc-config-submitted", function(event)
+	local room, fields, changed = event.room, event.fields, event.changed;
+	local new = fields[muc_form_allow_who];
+	if new ~= room._data.mam_query then
+		room._data.mam_query = new;
+		if type(changed) == "table" then
+			changed[muc_form_allow_who] = true;
+		else
+			event.changed = true;
+		end
+	end
+end);
+
+
 -- Handle archive queries
 module:hook("iq-get/bare/"..xmlns_mam..":query", function(event)
 	local origin, stanza = event.origin, event.stanza;
@@ -126,8 +156,11 @@
 
 	-- Banned or not a member of a members-only room?
 	local from_affiliation = room_obj:get_affiliation(from);
+	local allowed_to_query = room_obj._data.mam_query or "anyone";
 	if from_affiliation == "outcast" -- banned
-		or room_obj:get_members_only() and not from_affiliation then -- members-only, not a member
+		or room_obj:get_members_only() and not from_affiliation -- members-only, not a member
+		or allowed_to_query == "moderators" and not (from_affiliation == "owner" or from_affiliation == "admin" )
+		or allowed_to_query ~= "anyone" then
 		return origin.send(st.error_reply(stanza, "auth", "forbidden"))
 	end
 
--- a/mod_manifesto/mod_manifesto.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_manifesto/mod_manifesto.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -57,14 +57,18 @@
 
 module:hook("resource-bind", function (event)
 	local session = event.session;
+	module:log("debug", "mod_%s sees that %s logged in", module.name, session.username);
 
 	local now = time();
 	local last_notify = notified[session.username] or 0;
 	if last_notify > ( now - 86400 * 7 ) then
+		module:log("debug", "Already notified %s", session.username);
 		return
 	end
 
+	module:log("debug", "Waiting 15 seconds");
 	timer.add_task(15, function ()
+		module:log("debug", "15 seconds later... session.type is %q", session.type);
 		if session.type ~= "c2s" then return end -- user quit already
 		local bad_contacts, bad_hosts = {}, {};
 		for contact_jid, item in pairs(session.roster or {}) do
@@ -96,6 +100,7 @@
 				end
 			end
 		end
+		module:log("debug", "%s has %d bad contacts", session.username, #bad_contacts);
 		if #bad_contacts > 0 then
 			local vars = {
 				HOST = host;
@@ -103,6 +108,7 @@
 				SERVICES = "    "..table.concat(bad_hosts, "\n    ");
 				CONTACTVIA = contact_method, CONTACT = contact;
 			};
+			module:log("debug", "Sending notification to %s", session.username);
 			session.send(st.message({ type = "headline", from = host }):tag("body"):text(message:gsub("$(%w+)", vars)));
 			notified[session.username] = now;
 		end
@@ -159,7 +165,7 @@
 		config_set(host, "s2s_require_encryption", true);
 
 		for _, session in pairs(s2s_sessions) do
-			if not session.secure then
+			if session.type == "s2sin" or session.type == "s2sout" and not session.secure then
 				(session.close or s2s_destroy_session)(session);
 			end
 		end
--- a/mod_muc_log/mod_muc_log.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_muc_log/mod_muc_log.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -96,6 +96,7 @@
 							end
 						end
 					end
+					datamanager.getpath(node, host, datastore, nil, true); -- create the datastore dir
 					data_store(node, host, datastore .. "/" .. today, data);
 				end
 			end
--- a/mod_pubsub_feeds/mod_pubsub_feeds.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_pubsub_feeds/mod_pubsub_feeds.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -17,8 +17,8 @@
 
 local pubsub = module:depends"pubsub";
 
-local date, time = os.date, os.time;
-local dt_parse, dt_datetime = require "util.datetime".parse, require "util.datetime".datetime;
+local date, time = import("os", "date", "time");
+local dt_parse, dt_datetime = import("util.datetime", "parse", "datetime");
 local uuid = require "util.uuid".generate;
 local hmac_sha1 = require "util.hashes".hmac_sha1;
 local parse_feed = require "feeds".feed_from_string;
--- a/mod_pubsub_hub/mod_pubsub_hub.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_pubsub_hub/mod_pubsub_hub.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -2,15 +2,14 @@
 --
 -- This file is MIT/X11 licensed.
 
-local http = require "net.http";
-local formdecode = http.formdecode;
-local formencode = http.formencode;
+local http_request, formdecode, formencode = import("net.http", "request", "formdecode", "formencode");
 local uuid = require "util.uuid".generate;
 local hmac_sha1 = require "util.hmac".sha1;
 local json_encode = require "util.json".encode;
 local time = os.time;
 local m_min, m_max = math.min, math.max;
 local tostring = tostring;
+
 local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
 local xmlns_pubsub_event = xmlns_pubsub .. "#event";
 local subs_by_topic = module:shared"subscriptions";
@@ -91,7 +90,7 @@
 			module:log("debug", require"util.serialization".serialize(verify_modes));
 			if verify_modes["async"] then
 				module:log("debug", "Sending async verification request to %s for %s", tostring(callback_url), tostring(subscription));
-				http.request(callback_url, nil, function(body, code)
+				http_request(callback_url, nil, function(body, code)
 					if body == challenge and code > 199 and code < 300 then
 						if not subscription.want_state then
 							module:log("warn", "Verification of already verified request, probably");
@@ -109,7 +108,7 @@
 				end)
 				return 202;
 			elseif verify_modes["sync"] then
-				http.request(callback_url, nil, function(body, code)
+				http_request(callback_url, nil, function(body, code)
 					if body == challenge and code > 199 and code < 300 then
 						if not subscription.want_state then
 							module:log("warn", "Verification of already verified request, probably");
@@ -161,7 +160,7 @@
 						["hub.lease_seconds"] = subscription.lease_seconds,
 						["hub.verify_token"] = subscription.verify_token,
 					}
-					http.request(callback_url, nil, function(body, code)
+					http_request(callback_url, nil, function(body, code)
 						if body == challenge and code > 199 and code < 300 then
 							subscription.expires = now + subscription.lease_seconds;
 						end
@@ -201,7 +200,7 @@
 	if subscription.secret then
 		headers["X-Hub-Signature"] = "sha1="..hmac_sha1(subscription.secret, body, true);
 	end
-	http.request(subscription.callback, { method = "POST", body = body, headers = headers }, function(body, code)
+	http_request(subscription.callback, { method = "POST", body = body, headers = headers }, function(body, code)
 		if code >= 200 and code <= 299 then
 			module:log("debug", "Delivered");
 		else
--- a/mod_s2s_auth_dane/mod_s2s_auth_dane.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_s2s_auth_dane/mod_s2s_auth_dane.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -25,6 +25,7 @@
 -- TODO Things to test/handle:
 -- Negative or bogus answers
 -- No SRV records
+-- No encryption offered
 
 function s2sout.try_connect(host_session, connect_host, connect_port, err)
 	local srv_hosts = host_session.srv_hosts;
--- a/mod_s2s_auth_fingerprint/mod_s2s_auth_fingerprint.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_s2s_auth_fingerprint/mod_s2s_auth_fingerprint.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -5,6 +5,7 @@
 
 local digest_algo = module:get_option_string(module:get_name().."_digest", "sha1");
 local must_match = module:get_option_boolean("s2s_pin_fingerprints", false);
+local tofu = module:get_option_boolean("s2s_tofu", false);
 
 local fingerprints = {};
 
@@ -38,5 +39,20 @@
 			session.cert_chain_status = "invalid";
 			session.cert_identity_status = "invalid";
 		end
+	elseif tofu
+			and ( session.cert_chain_status ~= "valid"
+			or session.cert_identity_status ~= "valid" ) then
+		local digest = cert and cert:digest(digest_algo);
+		fingerprints[host] = {
+			[digest] = true;
+		}
 	end
 end);
+
+function module.save()
+	return { fingerprints = fingerprints };
+end
+
+function module.restore(state)
+	fingerprints = state.fingerprints;
+end
--- a/mod_s2s_blacklist/mod_s2s_blacklist.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_s2s_blacklist/mod_s2s_blacklist.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -1,16 +1,16 @@
 local st = require "util.stanza";
 
-local blacklist = module:get_option_inherited_set("s2s_blacklist", {});
+local whitelist = module:get_option_inherited_set("s2s_whitelist", {});
 
 module:hook("route/remote", function (event)
-	if blacklist:contains(event.to_host) then
+	if not whitelist:contains(event.to_host) then
 		module:send(st.error_reply(event.stanza, "cancel", "not-allowed", "Communication with this domain is restricted"));
 		return true;
 	end
 end, 100);
 
 module:hook("s2s-stream-features", function (event)
-	if blacklist:contains(event.origin.from_host) then
+	if not whitelist:contains(event.origin.from_host) then
 		event.origin:close({
 			condition = "policy-violation";
 			text = "Communication with this domain is restricted";
--- a/mod_s2s_keysize_policy/mod_s2s_keysize_policy.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_s2s_keysize_policy/mod_s2s_keysize_policy.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -26,9 +26,9 @@
 	if cert and cert.pubkey then
 		local _, key_type, key_size = cert:pubkey();
 		if key_size < ( weak_key_size[key_type] or 0 ) then
-			local issued = parse_x509_datetime(cert:notbefore());
-			if issued > weak_key_cutoff then
-				session.log("error", "%s has a %s-bit %s key issued after 31 December 2013, invalidating trust!", host, key_size, key_type);
+			local expires = parse_x509_datetime(cert:notafter());
+			if expires > weak_key_cutoff then
+				session.log("error", "%s has a %s-bit %s key valid after 31 December 2013, invalidating trust!", host, key_size, key_type);
 				session.cert_chain_status = "invalid";
 				session.cert_identity_status = "invalid";
 			else
--- a/mod_smacks/mod_smacks.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_smacks/mod_smacks.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -8,6 +8,7 @@
 local add_filter = require "util.filters".add_filter;
 local timer = require "util.timer";
 local datetime = require "util.datetime";
+local tb = require"util.debug".traceback;
 
 local xmlns_sm2 = "urn:xmpp:sm:2";
 local xmlns_sm3 = "urn:xmpp:sm:3";
@@ -84,6 +85,7 @@
 	local function new_send(stanza)
 		local attr = stanza.attr;
 		if attr and not attr.xmlns then -- Stanza in default stream namespace
+			session.log("debug", "Sending stanza %s", stanza:top_tag());
 			local cached_stanza = st.clone(stanza);
 			
 			if cached_stanza and cached_stanza:get_child("delay", xmlns_delay) == nil then
@@ -91,8 +93,10 @@
 			end
 			
 			queue[#queue+1] = cached_stanza;
+			session.log("debug", "#queue = %d", #queue);
 		end
 		if session.hibernating then
+			session.log("debug", "hibernating, stanza queued")
 			-- The session is hibernating, no point in sending the stanza
 			-- over a dead connection.  It will be delivered upon resumption.
 			return true;
@@ -204,6 +208,7 @@
 	for i=1,math_min(handled_stanza_count,#queue) do
 		t_remove(origin.outgoing_stanza_queue, 1);
 	end
+	origin.log("debug", "#queue = %d", #queue);
 	origin.last_acknowledged_stanza = origin.last_acknowledged_stanza + handled_stanza_count;
 	return true;
 end
@@ -333,9 +338,11 @@
 		-- 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;
+		session.log("debug", "#queue = %d", #queue);
 		for i=1,#queue do
 			session.send(queue[i]);
 		end
+		session.log("debug", "#queue = %d -- after send", #queue);
 	else
 		module:log("warn", "Client %s@%s[%s] tried to resume stream for %s@%s[%s]",
 			session.username or "?", session.host or "?", session.type,
--- a/mod_srvinjection/mod_srvinjection.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_srvinjection/mod_srvinjection.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -1,3 +1,4 @@
+local s = require"util.serialization".new"oneline".serialize;
 
 module:set_global();
 
@@ -25,8 +26,12 @@
 
 local original_lookup = adns.lookup;
 function adns.lookup(handler, qname, qtype, qclass)
+	module:log("debug", "adns.lookup(%s, %s, %s)", s(qname), s(qtype), s(qclass));
 	if qtype == "SRV" then
 		local host = qname:match("^_xmpp%-server%._tcp%.(.*)%.$");
+		module:log("debug", "qname:match(...) → %s", s(host));
+		local mapping = map[host] or map["*"];
+		module:log("debug", "map[%s] → %s", s(host), s(mapping));
 		local mapping = map[host] or map["*"];
 		if mapping then
 			handler(mapping);
--- a/mod_storage_mongodb/mod_storage_mongodb.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_storage_mongodb/mod_storage_mongodb.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -1,5 +1,8 @@
 local next = next;
 local setmetatable = setmetatable;
+local set = require"util.set";
+local it = require"util.iterators";
+local array = require"util.array";
 
 local params = assert ( module:get_option("mongodb") , "mongodb configuration not found" );
 
@@ -46,6 +49,75 @@
 	end;
 end
 
+local roster_store = {};
+roster_store.__index = roster_store;
+
+function roster_store:get(username)
+	local host = module.host or "_global";
+	local store = self.store;
+
+	-- The database name can't have a period in it (hence it can't be a host/ip)
+	local namespace = params.dbname .. "." .. host;
+	local v = { _id = { store = store ; username = username } };
+
+	local cursor , err = conn:query ( namespace , v );
+	if not cursor then return nil , err end;
+
+	local r , err = cursor:next ( );
+	if not r then return nil , err end;
+	local roster = {
+		[false] = {
+			version = r.version;
+		};
+		pending = set.new( r.pending )._items;
+	};
+	local items = r.items;
+	for i = 1, #items do
+		local item = items[i];
+		roster[item.jid] = {
+			subscription = item.subscription;
+			groups = set.new( item.groups )._items;
+			ask = item.ask;
+			name = item.name;
+		}
+	end
+	return roster;
+end
+
+function roster_store:set(username, data)
+	local host = module.host or "_global";
+	local store = self.store;
+
+	-- The database name can't have a period in it (hence it can't be a host/ip)
+	local namespace = params.dbname .. "." .. host;
+	local v = { _id = { store = store ; username = username } };
+
+	if data == nil or next(data) == nil then -- delete data
+		return conn:remove ( namespace , v );
+	end
+
+	v.version = data[false].version
+	if data.pending then
+		v.pending = array(it.keys(v.pending))
+	end
+
+	local items  = {}
+	for jid, item in pairs(data) do
+		if jid and jid ~=  "pending" then
+			table.insert(items, {
+				jid = jid;
+				subscription = item.subscription;
+				groups = array(it.keys( item.groups ));
+				name = item.name;
+				ask = item.ask;
+			});
+		end
+	end
+	v.items = items;
+
+	return conn:insert ( namespace , v );
+end
+
 local driver = {};
 
 function driver:open(store, typ)
@@ -58,6 +130,9 @@
 	end
 
 	if not typ then -- default key-value store
+		if store == "roster" then
+			return setmetatable({ store = store }, roster_store);
+		end
 		return setmetatable({ store = store }, keyval_store);
 	end;
 	return nil, "unsupported-store";
--- a/mod_turncredentials/mod_turncredentials.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_turncredentials/mod_turncredentials.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -15,6 +15,8 @@
     return;
 end
 
+module:add_feature("urn:xmpp:extdisco:1");
+
 module:hook("iq-get/host/urn:xmpp:extdisco:1:services", function(event)
     local origin, stanza = event.origin, event.stanza;
     if origin.type ~= "c2s" then
--- a/mod_websocket/mod_websocket.lua	Wed Feb 26 13:08:47 2014 -0800
+++ b/mod_websocket/mod_websocket.lua	Fri Feb 28 15:36:06 2014 +0100
@@ -242,6 +242,7 @@
 	c2s_listener.onconnect(conn);
 
 	local session = sessions[conn];
+	session._http_request_headers = request.headers;
 
 	session.secure = consider_websocket_secure or session.secure;
 
@@ -285,4 +286,11 @@
 			["GET /"] = handle_request;
 		};
 	});
+
+	module:add_item("alt-conn-method", {
+		rel = "urn:xmpp:altconnect:websocket";
+		href = module:http_url(nil, "xmpp-websocket"):gsub("^http", "ws");
+	});
 end
+
+