changeset 927:a9dfa7232d88

Merge
author Matthew Wild <mwild1@gmail.com>
date Tue, 12 Mar 2013 12:10:25 +0000
parents f88381a39c56 (current diff) 720b8268778e (diff)
children 4584c3303bb4
files mod_auth_ldap2/mod_auth_ldap.lua
diffstat 36 files changed, 1542 insertions(+), 528 deletions(-) [+]
line wrap: on
line diff
--- a/mod_admin_web/admin_web/mod_admin_web.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_admin_web/admin_web/mod_admin_web.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -22,27 +22,15 @@
 local is_admin = require "core.usermanager".is_admin;
 local pubsub = require "util.pubsub";
 local jid_bare = require "util.jid".bare;
-local lfs = require "lfs";
-local open = io.open;
-local stat = lfs.attributes;
 
 module:set_global();
 
 local service = {};
 
-local http_base = module.path:gsub("/[^/]+$","") .. "/www_files/";
-
 local xmlns_adminsub = "http://prosody.im/adminsub";
 local xmlns_c2s_session = "http://prosody.im/streams/c2s";
 local xmlns_s2s_session = "http://prosody.im/streams/s2s";
 
-local mime_map = {
-	html = "text/html";
-	xml = "text/xml";
-	js = "text/javascript";
-	css = "text/css";
-};
-
 local idmap = {};
 
 function add_client(session, host)
@@ -104,37 +92,14 @@
 	end
 end
 
-function serve_file(event, path)
-	local full_path = http_base .. path;
-
-	if stat(full_path, "mode") == "directory" then
-		if stat(full_path.."/index.html", "mode") == "file" then
-			return serve_file(event, path.."/index.html");
-		end
-		return 403;
-	end
-
-	local f, err = open(full_path, "rb");
-	if not f then
-		return 404;
-	end
-
-	local data = f:read("*a");
-	f:close();
-	if not data then
-		return 403;
-	end
-
-	local ext = path:match("%.([^.]*)$");
-	event.response.headers.content_type = mime_map[ext]; -- Content-Type should be nil when not known
-	return data;
-end
-
 function module.add_host(module)
 	-- Dependencies
 	module:depends("bosh");
 	module:depends("admin_adhoc");
 	module:depends("http");
+	local serve_file = module:depends("http_files").serve {
+		path = module:get_directory() .. "/www_files";
+	};
 
 	-- Setup HTTP server
 	module:provides("http", {
@@ -149,12 +114,14 @@
 	});
 
 	-- Setup adminsub service
-	local function simple_broadcast(node, jids, item)
-		item = st.clone(item);
-		item.attr.xmlns = nil; -- Clear the pubsub namespace
+	local function simple_broadcast(kind, node, jids, item)
+		if item then
+			item = st.clone(item);
+			item.attr.xmlns = nil; -- Clear the pubsub namespace
+		end
 		local message = st.message({ from = module.host, type = "headline" })
 			:tag("event", { xmlns = xmlns_adminsub .. "#event" })
-				:tag("items", { node = node })
+				:tag(kind, { node = node })
 					:add_child(item);
 		for jid in pairs(jids) do
 			module:log("debug", "Sending notification to %s", jid);
--- a/mod_admin_web/admin_web/www_files/js/main.js	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_admin_web/admin_web/www_files/js/main.js	Tue Mar 12 12:10:25 2013 +0000
@@ -96,6 +96,7 @@
     if (status == Strophe.Status.CONNECTING) {
         log('Strophe is connecting.');
     } else if (status == Strophe.Status.CONNFAIL) {
+        alert('Connection failed (Wrong host?)');
         log('Strophe failed to connect.');
         showConnect();
     } else if (status == Strophe.Status.DISCONNECTING) {
@@ -104,6 +105,7 @@
         log('Strophe is disconnected.');
         showConnect();
     } else if (status == Strophe.Status.AUTHFAIL) {
+        alert('Wrong username and/or password');
         log('Authentication failed');
         if (connection) {
             connection.disconnect();
@@ -120,7 +122,12 @@
                     return false;
                 }
                 for (i = 0; i < items.length; i++) {
-                    $('#host').append('<option>' + $(items[i]).text() + '</option>');
+                    var host = $(items[i]).text();
+                    if (host == Strophe.getDomainFromJid(connection.jid)) {
+                        $('#host').append('<option selected>' + host + '</option>');
+                    } else {
+                        $('#host').append('<option>' + host + '</option>');
+                    }
                 }
                 showDisconnect();
                 adminsubHost = $(items[0]).text();
--- a/mod_auth_external/mod_auth_external.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_auth_external/mod_auth_external.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -10,7 +10,6 @@
 --
 
 
-local nodeprep = require "util.encodings".stringprep.nodeprep;
 --local process = require "process";
 local lpc; pcall(function() lpc = require "lpc"; end);
 
@@ -81,8 +80,6 @@
 
 function do_query(kind, username, password)
 	if not username then return nil, "not-acceptable"; end
-	username = nodeprep(username);
-	if not username then return nil, "jid-malformed"; end
 	
 	local query = (password and "%s:%s:%s:%s" or "%s:%s:%s"):format(kind, username, host, password);
 	local len = #query
@@ -132,12 +129,7 @@
 function provider.get_sasl_handler()
 	local testpass_authentication_profile = {
 		plain_test = function(sasl, username, password, realm)
-			local prepped_username = nodeprep(username);
-			if not prepped_username then
-				log("debug", "NODEprep failed on username: %s", username);
-				return "", nil;
-			end
-			return usermanager.test_password(prepped_username, realm, password), true;
+			return usermanager.test_password(username, realm, password), true;
 		end,
 	};
 	return new_sasl(host, testpass_authentication_profile);
--- a/mod_auth_internal_yubikey/mod_auth_internal_yubikey.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_auth_internal_yubikey/mod_auth_internal_yubikey.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -18,7 +18,6 @@
 local config = require "core.configmanager";
 local usermanager = require "core.usermanager";
 local new_sasl = require "util.sasl".new;
-local nodeprep = require "util.encodings".stringprep.nodeprep;
 local hosts = hosts;
 
 local prosody = _G.prosody;
@@ -106,12 +105,6 @@
 	local realm = module:get_option("sasl_realm") or module.host;
 	local getpass_authentication_profile = {
 		plain_test = function(sasl, username, password, realm)
-			local prepped_username = nodeprep(username);
-			if not prepped_username then
-				log("debug", "NODEprep failed on username: %s", username);
-				return false, nil;
-			end
-			
 			return usermanager.test_password(username, realm, password), true;
 		end
 	};
--- a/mod_auth_ldap/mod_auth_ldap.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_auth_ldap/mod_auth_ldap.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -1,6 +1,5 @@
 
 local new_sasl = require "util.sasl".new;
-local nodeprep = require "util.encodings".stringprep.nodeprep;
 local log = require "util.logger".init("auth_ldap");
 
 local ldap_server = module:get_option("ldap_server") or "localhost";
@@ -42,12 +41,7 @@
 function provider.get_sasl_handler()
 	local testpass_authentication_profile = {
 		plain_test = function(sasl, username, password, realm)
-			local prepped_username = nodeprep(username);
-			if not prepped_username then
-				log("debug", "NODEprep failed on username: %s", username);
-				return "", nil;
-			end
-			return provider.test_password(prepped_username, password), true;
+			return provider.test_password(username, password), true;
 		end
 	};
 	return new_sasl(module.host, testpass_authentication_profile);
--- a/mod_auth_ldap2/mod_auth_ldap.lua	Thu Nov 22 18:59:10 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
--- vim:sts=4 sw=4
-
--- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
--- Copyright (C) 2012 Rob Hoelz
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
--- http://code.google.com/p/prosody-modules/source/browse/mod_auth_ldap/mod_auth_ldap.lua
--- adapted to use common LDAP store
-
-local ldap     = module:require 'ldap';
-local new_sasl = require 'util.sasl'.new;
-local nodeprep = require 'util.encodings'.stringprep.nodeprep;
-local jsplit   = require 'util.jid'.split;
-
-if not ldap then
-    return;
-end
-
-local provider = {}
-
-function provider.test_password(username, password)
-    return ldap.bind(username, password);
-end
-
-function provider.user_exists(username)
-    local params = ldap.getparams()
-
-    local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username);
-
-    return ldap.singlematch {
-        base   = params.user.basedn,
-        filter = filter,
-    };
-end
-
-function provider.get_password(username)
-    return nil, "Passwords unavailable for LDAP.";
-end
-
-function provider.set_password(username, password)
-    return nil, "Passwords unavailable for LDAP.";
-end
-
-function provider.create_user(username, password)
-    return nil, "Account creation/modification not available with LDAP.";
-end
-
-function provider.get_sasl_handler()
-    local testpass_authentication_profile = {
-        plain_test = function(sasl, username, password, realm)
-            local prepped_username = nodeprep(username);
-            if not prepped_username then
-                module:log("debug", "NODEprep failed on username: %s", username);
-                return "", nil;
-            end
-            return provider.test_password(prepped_username, password), true;
-        end,
-        mechanisms = { PLAIN = true },
-    };
-    return new_sasl(module.host, testpass_authentication_profile);
-end
-
-function provider.is_admin(jid)
-    local admin_config = ldap.getparams().admin;
-
-    if not admin_config then
-        return;
-    end
-
-    local ld       = ldap:getconnection();
-    local username = jsplit(jid);
-    local filter   = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username);
-
-    return ldap.singlematch {
-        base   = admin_config.basedn,
-        filter = filter,
-    };
-end
-
-module:provides("auth", provider);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_auth_ldap2/mod_auth_ldap2.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,78 @@
+-- vim:sts=4 sw=4
+
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+-- Copyright (C) 2012 Rob Hoelz
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+-- http://code.google.com/p/prosody-modules/source/browse/mod_auth_ldap/mod_auth_ldap.lua
+-- adapted to use common LDAP store
+
+local ldap     = module:require 'ldap';
+local new_sasl = require 'util.sasl'.new;
+local jsplit   = require 'util.jid'.split;
+
+if not ldap then
+    return;
+end
+
+local provider = {}
+
+function provider.test_password(username, password)
+    return ldap.bind(username, password);
+end
+
+function provider.user_exists(username)
+    local params = ldap.getparams()
+
+    local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username);
+
+    return ldap.singlematch {
+        base   = params.user.basedn,
+        filter = filter,
+    };
+end
+
+function provider.get_password(username)
+    return nil, "Passwords unavailable for LDAP.";
+end
+
+function provider.set_password(username, password)
+    return nil, "Passwords unavailable for LDAP.";
+end
+
+function provider.create_user(username, password)
+    return nil, "Account creation/modification not available with LDAP.";
+end
+
+function provider.get_sasl_handler()
+    local testpass_authentication_profile = {
+        plain_test = function(sasl, username, password, realm)
+            return provider.test_password(username, password), true;
+        end,
+        mechanisms = { PLAIN = true },
+    };
+    return new_sasl(module.host, testpass_authentication_profile);
+end
+
+function provider.is_admin(jid)
+    local admin_config = ldap.getparams().admin;
+
+    if not admin_config then
+        return;
+    end
+
+    local ld       = ldap:getconnection();
+    local username = jsplit(jid);
+    local filter   = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username);
+
+    return ldap.singlematch {
+        base   = admin_config.basedn,
+        filter = filter,
+    };
+end
+
+module:provides("auth", provider);
--- a/mod_auth_sql/mod_auth_sql.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_auth_sql/mod_auth_sql.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -5,7 +5,6 @@
 
 local log = require "util.logger".init("auth_sql");
 local new_sasl = require "util.sasl".new;
-local nodeprep = require "util.encodings".stringprep.nodeprep;
 local DBI = require "DBI"
 
 local connection;
@@ -101,12 +100,7 @@
 function provider.get_sasl_handler()
 	local profile = {
 		plain = function(sasl, username, realm)
-			local prepped_username = nodeprep(username);
-			if not prepped_username then
-				module:log("debug", "NODEprep failed on username: %s", username);
-				return "", nil;
-			end
-			local password = get_password(prepped_username);
+			local password = get_password(username);
 			if not password then return "", nil; end
 			return password, true;
 		end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_bidi/mod_bidi.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,129 @@
+-- Bidirectional Server-to-Server Connections
+-- http://xmpp.org/extensions/xep-0288.html
+-- Copyright (C) 2013 Kim Alvefur
+--
+-- This file is MIT/X11 licensed.
+--
+local s2smanager = require"core.s2smanager";
+local add_filter = require "util.filters".add_filter;
+local st = require "util.stanza";
+local jid_split = require"util.jid".prepped_split;
+
+local xmlns_bidi_feature = "urn:xmpp:features:bidi"
+local xmlns_bidi = "urn:xmpp:bidi";
+local noop = function () end
+local core_process_stanza = prosody.core_process_stanza or core_process_stanza;
+local traceback = debug.traceback;
+
+local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end
+local function handlestanza(session, stanza)
+	if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
+		stanza.attr.xmlns = nil;
+	end
+	-- stanza = session.filter("stanzas/in", stanza);
+	if stanza then
+		return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
+	end
+end
+
+local function new_bidi(origin)
+	local bidi_session, remote_host;
+	origin.log("debug", "Creating bidirectional session wrapper");
+	if origin.direction == "incoming" then -- then we create an "outgoing" bidirectional session
+		local conflicting_session = hosts[origin.to_host].s2sout[origin.from_host]
+		if conflicting_session then
+			conflicting_session.log("info", "We already have an outgoing connection to %s, closing it...", origin.from_host);
+			conflicting_session:close{ condition = "conflict", text = "Replaced by bidirectional stream" }
+			s2smanager.destroy_session(conflicting_session);
+		end
+		remote_host = origin.from_host;
+		bidi_session = s2smanager.new_outgoing(origin.to_host, origin.from_host)
+	else -- outgoing -- then we create an "incoming" bidirectional session
+		remote_host = origin.to_host;
+		bidi_session = s2smanager.new_incoming(origin.conn)
+		bidi_session.to_host = origin.from_host;
+		bidi_session.from_host = origin.to_host;
+		add_filter(origin, "stanzas/in", function(stanza)
+			if stanza.attr.xmlns ~= nil then return stanza end
+			local _, host = jid_split(stanza.attr.from);
+			if host ~= remote_host then return stanza end
+			handlestanza(bidi_session, stanza);
+		end, 1);
+	end
+	origin.bidi_session = bidi_session;
+	bidi_session.sends2s = origin.sends2s;
+	bidi_session.bounce_sendq = noop;
+	bidi_session.notopen = nil;
+	bidi_session.is_bidi = true;
+	bidi_session.bidi_session = false;
+	bidi_session.orig_session = origin;
+	bidi_session.secure = origin.secure;
+	bidi_session.cert_identity_status = origin.cert_identity_status;
+	bidi_session.cert_chain_status = origin.cert_chain_status;
+	bidi_session.close = function(...)
+		return origin.close(...);
+	end
+
+	bidi_session.log("info", "Bidirectional session established");
+	s2smanager.make_authenticated(bidi_session, remote_host);
+	return bidi_session;
+end
+
+-- Incoming s2s
+module:hook("s2s-stream-features", function(event)
+	local origin, features = event.origin, event.features;
+	if not origin.is_bidi and not hosts[module.host].s2sout[origin.from_host] then
+		module:log("debug", "Announcing support for bidirectional streams");
+		features:tag("bidi", { xmlns = xmlns_bidi_feature }):up();
+	end
+end);
+
+module:hook("stanza/urn:xmpp:bidi:bidi", function(event)
+	local origin = event.session or event.origin;
+	if not origin.is_bidi and not origin.bidi_session then
+		module:log("debug", "%s requested bidirectional stream", origin.from_host);
+		origin.do_bidi = true;
+		return true;
+	end
+end);
+
+-- Outgoing s2s
+module:hook("stanza/http://etherx.jabber.org/streams:features", function(event)
+	local origin = event.session or event.origin;
+	if not ( origin.bidi_session or origin.is_bidi or origin.do_bidi)
+	and event.stanza:get_child("bidi", xmlns_bidi_feature) then
+		module:log("debug", "%s supports bidirectional streams", origin.to_host);
+		origin.sends2s(st.stanza("bidi", { xmlns = xmlns_bidi }));
+		origin.do_bidi = true;
+	end
+end, 160);
+
+function enable_bidi(event)
+	local session = event.session;
+	if session.do_bidi and not ( session.is_bidi or session.bidi_session ) then
+		session.do_bidi = nil;
+		new_bidi(session);
+	end
+end
+
+module:hook("s2sin-established", enable_bidi);
+module:hook("s2sout-established", enable_bidi);
+
+function disable_bidi(event)
+	local session = event.session;
+	if session.bidi_session then
+		local bidi_session = session.bidi_session;
+		session.bidi_session, bidi_session.orig_session = nil, nil;
+		session.log("debug", "Tearing down bidirectional stream");
+		s2smanager.destroy_session(bidi_session, event.reason);
+	elseif session.orig_session then
+		local orig_session = session.orig_session;
+		orig_session.bidi_session, session.orig_session = nil, nil;
+		orig_session.log("debug", "Tearing down bidirectional stream");
+		s2smanager.destroy_session(orig_session, event.reason);
+	end
+end
+
+module:hook("s2sin-destroyed", disable_bidi);
+module:hook("s2sout-destroyed", disable_bidi);
+
--- a/mod_carbons/mod_carbons.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_carbons/mod_carbons.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -7,6 +7,7 @@
 local jid_bare = require "util.jid".bare;
 local xmlns_carbons = "urn:xmpp:carbons:2";
 local xmlns_carbons_old = "urn:xmpp:carbons:1";
+local xmlns_carbons_really_old = "urn:xmpp:carbons:0";
 local xmlns_forward = "urn:xmpp:forward:0";
 local full_sessions, bare_sessions = full_sessions, bare_sessions;
 
@@ -27,6 +28,19 @@
 module:hook("iq/self/"..xmlns_carbons_old..":disable", toggle_carbons);
 module:hook("iq/self/"..xmlns_carbons_old..":enable", toggle_carbons);
 
+-- COMPAT :(
+if module:get_option_boolean("carbons_v0") then
+	module:hook("iq/self/"..xmlns_carbons_really_old..":carbons", function(event)
+		local origin, stanza = event.origin, event.stanza;
+		if stanza.attr.type == "set" then
+			local state = stanza.tags[1].attr.mode;
+			origin.want_carbons = state == "enable" and xmlns_carbons_really_old;
+			origin.send(st.reply(stanza));
+			return true;
+		end
+	end);
+end
+
 local function message_handler(event, c2s)
 	local origin, stanza = event.origin, event.stanza;
 	local orig_type = stanza.attr.type;
@@ -76,7 +90,7 @@
 	local copy = st.clone(stanza);
 	copy.attr.xmlns = "jabber:client";
 	local carbon = st.message{ from = bare_jid, type = orig_type, }
-		:tag(c2s and "sent" or "received", { xmlns = xmlns_carbons }):up()
+		:tag(c2s and "sent" or "received", { xmlns = xmlns_carbons })
 			:tag("forwarded", { xmlns = xmlns_forward })
 				:add_child(copy):reset();
 
@@ -86,6 +100,10 @@
 		:tag("forwarded", { xmlns = xmlns_forward })
 			:add_child(copy):reset();
 
+	-- COMPAT
+	local carbon_really_old = st.clone(stanza)
+		:tag(c2s and "sent" or "received", { xmlns = xmlns_carbons_really_old }):up()
+
 	user_sessions = user_sessions and user_sessions.sessions;
 	for _, session in pairs(user_sessions) do
 		-- Carbons are sent to resources that have enabled it
@@ -93,10 +111,14 @@
 		-- but not the resource that sent the message, or the one that it's directed to
 		and session ~= target_session
 		-- and isn't among the top resources that would receive the message per standard routing rules
-		and (c2s or session.priority ~= top_priority) then
+		and (c2s or session.priority ~= top_priority)
+		-- don't send v0 carbons (or copies) for c2s
+		and (not c2s or session.want_carbons ~= xmlns_carbons_really_old) then
 			carbon.attr.to = session.full_jid;
 			module:log("debug", "Sending carbon to %s", session.full_jid);
-			local carbon = session.want_carbons == xmlns_carbons_old and carbon_old or carbon; -- COMPAT
+			local carbon = session.want_carbons == xmlns_carbons_old and carbon_old -- COMPAT
+			or session.want_carbons == xmlns_carbons_really_old and carbon_really_old -- COMPAT
+			or carbon;
 			session.send(carbon);
 		end
 	end
@@ -107,6 +129,7 @@
 end
 
 -- Stanzas sent by local clients
+module:hook("pre-message/host", c2s_message_handler, 1);
 module:hook("pre-message/bare", c2s_message_handler, 1);
 module:hook("pre-message/full", c2s_message_handler, 1);
 -- Stanzas to local clients
@@ -115,3 +138,6 @@
 
 module:add_feature(xmlns_carbons);
 module:add_feature(xmlns_carbons_old);
+if module:get_option_boolean("carbons_v0") then
+	module:add_feature(xmlns_carbons_really_old);
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_carbons_adhoc/mod_carbons_adhoc.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,42 @@
+-- Implement a Adhoc command which will show a user 
+-- the status of carbons generation in regard to his clients
+--
+-- Copyright (C) 2012 Michael Holzt
+--
+-- This file is MIT/X11 licensed.
+
+local st = require "util.stanza";
+local jid_bare = require "util.jid".bare;
+local adhoc_new = module:require "adhoc".new;
+local xmlns_carbons_v2 = "urn:xmpp:carbons:2";
+local xmlns_carbons_v1 = "urn:xmpp:carbons:1";
+local xmlns_carbons_v0 = "urn:xmpp:carbons:0";
+
+local bare_sessions = bare_sessions;
+
+local function adhoc_status(self, data, state)
+	local result;
+
+	local bare_jid = jid_bare(data.from);
+	local user_sessions = bare_sessions[bare_jid];
+
+	local result = "";
+	
+	user_sessions = user_sessions and user_sessions.sessions;
+	for _, session in pairs(user_sessions) do
+		if session.full_jid then
+			result = result .. session.full_jid .. ": " ..
+				( (session.want_carbons == xmlns_carbons_v2 and "v2" ) or
+				  (session.want_carbons == xmlns_carbons_v1 and "v1" ) or
+				  (session.want_carbons == xmlns_carbons_v0 and "v0" ) or
+				  "none" ) .. "\n";
+		end
+	end
+
+	return { info = result, status = "completed" };
+end
+
+local status_desc = adhoc_new("Carbons: Get Status", 
+	"mod_carbons_adhoc#status", adhoc_status);
+
+module:add_item("adhoc", status_desc);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_carbons_copies/mod_carbons_copies.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,63 @@
+-- Send carbons v0 style copies of incoming messages to clients which
+-- are not (yet) capable of Message Carbons (XEP-0280).
+--
+-- This extension integrates with the mod_carbons plugin in such a way
+-- that a client capable of Message Carbons will not get a v0 copy.
+--
+-- This extension can be enabled for all users by default by setting
+-- carbons_copies_default = true.
+--
+-- Alternatively or additionally setting carbons_copies_adhoc = true
+-- will allow the user to enable or disable copies through Adhoc
+-- commands.
+--
+-- Copyright (C) 2012 Michael Holzt
+--
+-- This file is MIT/X11 licensed.
+
+local jid_split = require "util.jid".split;
+local dm_load = require "util.datamanager".load;
+local dm_store = require "util.datamanager".store;  
+local adhoc_new = module:require "adhoc".new;
+local xmlns_carbons_v0 = "urn:xmpp:carbons:0";
+local storename = "mod_carbons_copies";
+
+local function toggle_copies(data, on)
+	local username, hostname, resource = jid_split(data.from);		
+	dm_store(username, hostname, storename, { enabled = on });
+end	
+
+local function adhoc_enable_copies(self, data, state)
+	toggle_copies(data, true);
+	return { info = "Copies are enabled for you now.\nPlease restart/reconnect clients.", status = "completed" };
+end
+
+local function adhoc_disable_copies(self, data, state)
+	toggle_copies(data, false);
+	return { info = "Copies are disabled for you now.\nPlease restart/reconnect clients.", status = "completed" };
+end
+
+module:hook("resource-bind", function(event)
+	local session = event.session;
+	local username, hostname, resource = jid_split(session.full_jid);
+	
+	local store = dm_load(username, hostname, storename) or 
+		{ enabled = 
+		module:get_option_boolean("carbons_copies_default") };
+		
+	if store.enabled then
+		session.want_carbons = xmlns_carbons_v0;
+		module:log("debug", "%s enabling copies", session.full_jid);
+	end
+end);
+	
+-- Adhoc-Support
+if module:get_option_boolean("carbons_copies_adhoc") then
+	local enable_desc = adhoc_new("Carbons: Enable Copies",
+		"mod_carbons_copies#enable", adhoc_enable_copies);
+	local disable_desc = adhoc_new("Carbons: Disable Copies",
+		"mod_carbons_copies#disable", adhoc_disable_copies);
+		 
+	module:add_item("adhoc", enable_desc);
+	module:add_item("adhoc", disable_desc);
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_dir_listing/http_dir_listing/mod_http_dir_listing.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,58 @@
+-- Prosody IM
+-- Copyright (C) 2012 Kim Alvefur
+-- 
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+module:set_global();
+local server = require"net.http.server";
+local lfs = require "lfs";
+local stat = lfs.attributes;
+local build_path = require"socket.url".build_path;
+local base64_encode = require"util.encodings".base64.encode;
+local tag = require"util.stanza".stanza;
+local template = require"util.template";
+
+local function get_resource(resource)
+	local fh = assert(module:load_resource(resource));
+	local data = fh:read"*a";
+	fh:close();
+	return data;
+end
+
+local dir_index_template = template(get_resource("resources/template.html"));
+local style = get_resource("resources/style.css"):gsub("url%((.-)%)", function(url)
+	--module:log("debug", "Inlineing %s", url);
+	return "url(data:image/png;base64,"..base64_encode(get_resource("resources/"..url))..")";
+end);
+
+local function generate_directory_index(path, full_path)
+	local filelist = tag("ul", { class = "filelist" } ):text"\n";
+	if path ~= "/" then
+		filelist:tag("li", { class = "parent directory" })
+			:tag("a", { href = "..", rel = "up" }):text("Parent Directory"):up():up():text"\n"
+	end
+	for file in lfs.dir(full_path) do
+		if file:sub(1,1) ~= "." then
+			local attr = stat(full_path..file) or {};
+			local path = { file };
+			path.is_directory = attr.mode == "directory";
+			filelist:tag("li", { class = attr.mode })
+				:tag("a", { href = build_path(path) }):text(file):up()
+			:up():text"\n";
+		end
+	end
+	return "<!DOCTYPE html>\n"..tostring(dir_index_template.apply{
+		path = path,
+		style = style,
+		filelist = filelist,
+		footer = "Prosody "..prosody.version,
+	});
+end
+
+module:hook_object_event(server, "directory-index", function (event)
+	local ok, data = pcall(generate_directory_index, event.path, event.full_path);
+	if ok then return data end
+	module:log("warn", data);
+end);
Binary file mod_http_dir_listing/http_dir_listing/resources/folder.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_dir_listing/http_dir_listing/resources/style.css	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,10 @@
+
+body{background-color:#eeeeec;font-family:sans-serif;}
+h1{font-size:xx-large;}
+a:link,a:visited{color:#2e3436;text-decoration:none;}
+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);}
+.directory{list-style-image:url(folder.png);}
+.parent{list-style-image:url(user-home.png);}
+footer{margin-top:1ex;font-size:smaller;color:#babdb6;}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_http_dir_listing/http_dir_listing/resources/template.html	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,14 @@
+<html>
+  <head>
+    <title>Index of {path}</title>
+    <meta charset="utf-8"/>
+    <style>{style}</style>
+  </head>
+  <body>
+    <h1>Index of {path}</h1>
+
+		{filelist}
+
+    <footer>{footer}</footer>
+  </body>
+</html>
Binary file mod_http_dir_listing/http_dir_listing/resources/text-x-generic.png has changed
Binary file mod_http_dir_listing/http_dir_listing/resources/user-home.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_incidents_handling/incidents_handling/incidents_handling.lib.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,405 @@
+-- This contains the auxiliary functions for the Incidents Handling module.
+-- (C) 2012-2013, Marco Cirillo (LW.Org)
+
+local pairs, ipairs, os_date, string, table, tonumber = pairs, ipairs, os.date, string, table, tonumber
+
+local dataforms_new = require "util.dataforms".new
+local st = require "util.stanza"
+
+local xmlns_inc = "urn:xmpp:incident:2"
+local xmlns_iodef = "urn:ietf:params:xml:ns:iodef-1.0"
+local my_host = nil
+
+-- // Util and Functions //
+
+local function ft_str()
+	local d = os_date("%FT%T%z"):gsub("^(.*)(%+%d+)", function(dt, z) 
+		if z == "+0000" then return dt.."Z" else return dt..z end
+	end)
+	return d
+end
+
+local function get_incident_layout(i_type)
+	local layout = {
+		title = (i_type == "report" and "Incident report form") or (i_type == "request" and "Request for assistance with incident form"),
+		instructions = "Started/Ended Time, Contacts, Sources and Targets of the attack are mandatory. See RFC 5070 for further format instructions.",
+		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" },
+		
+		{ name = "name", type = "hidden", value = my_host },
+		{ name = "entity", type ="text-single", label = "Remote entity to query" },
+		{ name = "started", type = "text-single", label = "Incident Start Time" },
+		{ name = "ended", type = "text-single", label = "Incident Ended Time" },
+		{ name = "reported", type = "hidden", value = ft_str() },
+		{ name = "description", type = "text-single", label = "Description",
+		  desc = "Description syntax is: <lang (in xml:lang format)> <short description>" },
+		{ name = "contacts", type = "text-multi", label = "Contacts",
+		  desc = "Contacts entries format is: <address> <type> <role> - separated by new lines" },
+		{ name = "related", type = "text-multi", label = "Related Incidents", 
+		  desc = "Related incidents entries format is: <CSIRT's FQDN> <Incident ID> - separated by new lines" },
+		{ name = "impact", type = "text-single", label = "Impact Assessment", 
+		  desc = "Impact assessment format is: <severity> <completion> <type>" },
+		{ name = "sources", type = "text-multi", label = "Attack Sources", 
+		  desc = "Attack sources format is: <address> <category> <count> <count-type>" },
+		{ name = "targets", type = "text-multi", label = "Attack Targets", 
+		  desc = "Attack target format is: <address> <category> <noderole>" }
+	}
+
+	if i_type == "request" then
+		table.insert(layout, { 
+			name = "expectation",
+			type = "list-single",
+			label = "Expected action from remote entity",
+			value = {
+				{ value = "nothing", label = "No action" },
+				{ value = "contact-sender", label = "Contact us, regarding the incident" },
+				{ value = "investigate", label = "Investigate the entities listed into the incident" },
+				{ value = "block-host", label = "Block the involved accounts" },
+				{ value = "other", label = "Other action, filling the description field is required" }
+			}})
+		table.insert(layout, { name = "description", type = "text-single", label = "Description" })
+	end
+
+	return dataforms_new(layout)
+end
+
+local function render_list(incidents)
+	local layout = {
+		title = "Stored Incidents List",
+		instructions = "You can select and view incident reports here, if a followup/response is possible it'll be noted in the step after selection.",
+		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" },
+		{ 
+			name = "ids",
+			type = "list-single",
+			label = "Stored Incidents",
+			value = {}
+		}
+	}
+
+	-- Render stored incidents list
+
+	for id in pairs(incidents) do
+		table.insert(layout[2].value, { value = id, label = id })
+	end
+
+	return dataforms_new(layout)
+end
+
+local function insert_fixed(t, item) table.insert(t, { type = "fixed", value = item }) end
+
+local function render_single(incident)
+	local layout = {
+		title = string.format("Incident ID: %s - Friendly Name: %s", incident.data.id.text, incident.data.id.name),
+		instructions = incident.data.desc.text,
+		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" }
+	}
+
+	insert_fixed(layout, "Start Time: "..incident.data.start_time)
+	insert_fixed(layout, "End Time: "..incident.data.end_time)
+	insert_fixed(layout, "Report Time: "..incident.data.report_time)
+
+	insert_fixed(layout, "Contacts --")
+	for _, contact in ipairs(incident.data.contacts) do
+		insert_fixed(layout, string.format("Role: %s Type: %s", contact.role, contact.type))
+		if contact.jid then insert_fixed(layout, "--> JID: "..contact.jid..(contact.xmlns and ", XMLNS: "..contact.xmlns or "")) end
+		if contact.email then insert_fixed(layout, "--> E-Mail: "..contact.email) end
+		if contact.telephone then insert_fixed(layout, "--> Telephone: "..contact.telephone) end
+		if contact.postaladdr then insert_fixed(layout, "--> Postal Address: "..contact.postaladdr) end
+	end
+
+	insert_fixed(layout, "Related Activity --")	
+	for _, related in ipairs(incident.data.related) do
+		insert_fixed(layout, string.format("Name: %s ID: %s", related.name, related.text))
+	end
+
+	insert_fixed(layout, "Assessment --")
+	insert_fixed(layout, string.format("Language: %s Severity: %s Completion: %s Type: %s",
+		incident.data.assessment.lang, incident.data.assessment.severity, incident.data.assessment.completion, incident.data.assessment.type))
+
+	insert_fixed(layout, "Sources --")
+	for _, source in ipairs(incident.data.event_data.sources) do
+		insert_fixed(layout, string.format("Address: %s Counter: %s", source.address.text, source.counter.value))
+	end
+
+	insert_fixed(layout, "Targets --")
+	for _, target in ipairs(incident.data.event_data.targets) do
+		insert_fixed(layout, string.format("For NodeRole: %s", (target.noderole.cat == "ext-category" and target.noderole.ext) or targets.noderole.cat))
+		for _, address in ipairs(target.addresses) do
+			insert_fixed(layout, string.format("---> Address: %s Type: %s", address.text, (address.cat == "ext-category" and address.ext) or address.cat))
+		end
+	end
+
+	if incident.data.expectation then
+		insert_fixed(layout, "Expected Action: "..incident.data.expectation.action)
+		if incident.data.expectation.desc then
+			insert_fixed(layout, "Expected Action Description: "..incident.data.expectation.desc)
+		end
+	end
+
+	if incident.type == "request" and incident.status == "open" then
+		table.insert(layout, { name = "response-datetime", type = "hidden", value = ft_str() })
+		table.insert(layout, { name = "response", type = "text-single", label = "Respond to the request" })
+	end
+
+	return dataforms_new(layout)
+end
+
+local function get_type(var, typ)
+	if typ == "counter" then
+		local count_type, count_ext = var, nil
+		if count_type ~= "byte" or count_type ~= "packet" or count_type ~= "flow" or count_type ~= "session" or
+		   count_type ~= "alert" or count_type ~= "message" or count_type ~= "event" or count_type ~= "host" or
+		   count_type ~= "site" or count_type ~= "organization" then
+			count_ext = count_type
+			count_type = "ext-type"
+		end
+		return count_type, count_ext
+	elseif typ == "category" then
+		local cat, cat_ext = var, nil
+		if cat ~= "asn" or cat ~= "atm" or cat ~= "e-mail" or cat ~= "ipv4-addr" or
+		   cat ~= "ipv4-net" or cat ~= "ipv4-net-mask" or cat ~= "ipv6-addr" or cat ~= "ipv6-net" or
+		   cat ~= "ipv6-net-mask" or cat ~= "mac" then
+			cat_ext = cat
+			cat = "ext-category"
+		end
+		return cat, cat_ext
+	elseif type == "noderole" then
+		local noderole_ext = nil
+		if cat ~= "client" or cat ~= "server-internal" or cat ~= "server-public" or cat ~= "www" or
+		   cat ~= "mail" or cat ~= "messaging" or cat ~= "streaming" or cat ~= "voice" or
+		   cat ~= "file" or cat ~= "ftp" or cat ~= "p2p" or cat ~= "name" or
+		   cat ~= "directory" or cat ~= "credential" or cat ~= "print" or cat ~= "application" or
+		   cat ~= "database" or cat ~= "infra" or cat ~= "log" then
+			noderole_ext = true
+		end
+		return noderole_ext
+	end
+end
+
+local function do_tag_mapping(tag, object)
+	if tag.name == "IncidentID" then
+		object.id = { text = tag:get_text(), name = tag.attr.name }
+	elseif tag.name == "StartTime" then
+		object.start_time = tag:get_text()
+	elseif tag.name == "EndTime" then
+		object.end_time = tag:get_text()
+	elseif tag.name == "ReportTime" then
+		object.report_time = tag:get_text()
+	elseif tag.name == "Description" then
+		object.desc = { text = tag:get_text(), lang = tag.attr["xml:lang"] }
+	elseif tag.name == "Contact" then
+		local jid = tag:get_child("AdditionalData").tags[1]
+		local email = tag:get_child("Email")
+		local telephone = tag:get_child("Telephone")
+		local postaladdr = tag:get_child("PostalAddress")
+		if not object.contacts then
+			object.contacts = {}
+			object.contacts[1] = {
+				role = tag.attr.role,
+				ext_role = (tag.attr["ext-role"] and true) or nil,
+				type = tag.attr.type,
+				ext_type = (tag.attr["ext-type"] and true) or nil,
+				xmlns = jid.attr.xmlns,
+				jid = jid:get_text(),
+				email = email,
+				telephone = telephone,
+				postaladdr = postaladdr
+			}
+		else
+			object.contacts[#object.contacts + 1] = { 
+				role = tag.attr.role,
+				ext_role = (tag.attr["ext-role"] and true) or nil,
+				type = tag.attr.type,
+				ext_type = (tag.attr["ext-type"] and true) or nil,
+				xmlns = jid.attr.xmlns,
+				jid = jid:get_text(),
+				email = email,
+				telephone = telephone,
+				postaladdr = postaladdr
+			}
+		end
+	elseif tag.name == "RelatedActivity" then
+		object.related = {}
+		for _, t in ipairs(tag.tags) do
+			if tag.name == "IncidentID" then
+				object.related[#object.related + 1] = { text = t:get_text(), name = tag.attr.name }
+			end
+		end
+	elseif tag.name == "Assessment" then
+		local impact = tag:get_child("Impact")
+		object.assessment = { lang = impact.attr.lang, severity = impact.attr.severity, completion = impact.attr.completion, type = impact.attr.type } 
+	elseif tag.name == "EventData" then
+		local source = tag:get_child("Flow").tags[1]
+		local target = tag:get_child("Flow").tags[2]
+		local expectation = tag:get_child("Flow").tags[3]
+		object.event_data = { sources = {}, targets = {} }
+		for _, t in ipairs(source.tags) do
+			local addr = t:get_child("Address")
+			local cntr = t:get_child("Counter")
+			object.event_data.sources[#object.event_data.sources + 1] = {
+				address = { cat = addr.attr.category, ext = addr.attr["ext-category"], text = addr:get_text() },
+				counter = { type = cntr.attr.type, ext_type = cntr.attr["ext-type"], value = cntr:get_text() }
+			}
+		end
+		for _, entry in ipairs(target.tags) do
+			local noderole = { cat = entry:get_child("NodeRole").attr.category, ext = entry:get_child("NodeRole").attr["ext-category"] }
+			local current = #object.event_data.targets + 1
+			object.event_data.targets[current] = { addresses = {}, noderole = noderole }
+			for _, tag in ipairs(entry.tags) do				
+				object.event_data.targets[current].addresses[#object.event_data.targets[current].addresses + 1] = { text = tag:get_text(), cat = tag.attr.category, ext = tag.attr["ext-category"] }
+			end
+		end
+		if expectation then 
+			object.event_data.expectation = { 
+				action = expectation.attr.action,
+				desc = expectation:get_child("Description") and expectation:get_child("Description"):get_text()
+			} 
+		end
+	elseif tag.name == "History" then
+		object.history = {}
+		for _, t in ipairs(tag.tags) do
+			object.history[#object.history + 1] = {
+				action = t.attr.action,
+				date = t:get_child("DateTime"):get_text(),
+				desc = t:get_chilld("Description"):get_text()
+			}
+		end
+	end
+end
+
+local function stanza_parser(stanza)
+	local object = {}
+	
+	if stanza:get_child("report", xmlns_inc) then
+		local report = st.clone(stanza):get_child("report", xmlns_inc):get_child("Incident", xmlns_iodef)
+		for _, tag in ipairs(report.tags) do do_tag_mapping(tag, object) end
+	elseif stanza:get_child("request", xmlns_inc) then
+		local request = st.clone(stanza):get_child("request", xmlns_inc):get_child("Incident", xmlns_iodef)
+		for _, tag in ipairs(request.tags) do do_tag_mapping(tag, object) end
+	elseif stanza:get_child("response", xmlns_inc) then
+		local response = st.clone(stanza):get_child("response", xmlns_inc):get_child("Incident", xmlns_iodef)
+		for _, tag in ipairs(response.tags) do do_tag_mapping(tag, object) end
+	end
+
+	return object
+end
+
+local function stanza_construct(id)
+	if not id then return nil
+	else
+		local object = incidents[id].data
+		local s_type = incidents[id].type
+		local stanza = st.iq():tag(s_type or "report", { xmlns = xmlns_inc })
+		stanza:tag("Incident", { xmlns = xmlns_iodef, purpose = incidents[id].purpose })
+			:tag("IncidentID", { name = object.id.name }):text(object.id.text):up()
+			:tag("StartTime"):text(object.start_time):up()
+			:tag("EndTime"):text(object.end_time):up()
+			:tag("ReportTime"):text(object.report_time):up()
+			:tag("Description", { ["xml:lang"] = object.desc.lang }):text(object.desc.text):up():up();
+		
+		local incident = stanza:get_child(s_type, xmlns_inc):get_child("Incident", xmlns_iodef)		
+
+		for _, contact in ipairs(object.contacts) do
+			incident:tag("Contact", { role = (contact.ext_role and "ext-role") or contact.role,
+						  ["ext-role"] = (contact.ext_role and contact.role) or nil,
+						  type = (contact.ext_type and "ext-type") or contact.type,
+						  ["ext-type"] = (contact.ext_type and contact.type) or nil })
+				:tag("Email"):text(contact.email):up()
+				:tag("Telephone"):text(contact.telephone):up()
+				:tag("PostalAddress"):text(contact.postaladdr):up()
+				:tag("AdditionalData")
+					:tag("jid", { xmlns = contact.xmlns }):text(contact.jid):up():up():up()
+	
+		end
+
+		incident:tag("RelatedActivity"):up();
+
+		for _, related in ipairs(object.related) do
+			incident:get_child("RelatedActivity")			
+				:tag("IncidentID", { name = related.name }):text(related.text):up();
+		end
+
+		incident:tag("Assessment")
+			:tag("Impact", { 
+				lang = object.assessment.lang,
+				severity = object.assessment.severity,
+				completion = object.assessment.completion,
+				type = object.assessment.type
+			}):up():up();
+
+		incident:tag("EventData")
+			:tag("Flow")
+				:tag("System", { category = "source" }):up()
+				:tag("System", { category = "target" }):up():up():up();
+
+		local e_data = incident:get_child("EventData")
+
+		local sources = e_data:get_child("Flow").tags[1]
+		local targets = e_data:get_child("Flow").tags[2]
+
+		for _, source in ipairs(object.event_data.sources) do
+			sources:tag("Node")
+				:tag("Address", { category = source.address.cat, ["ext-category"] = source.address.ext })
+					:text(source.address.text):up()
+				:tag("Counter", { type = source.counter.type, ["ext-type"] = source.counter.ext_type })
+					:text(source.counter.value):up():up();
+		end
+
+		for _, target in ipairs(object.event_data.targets) do
+			targets:tag("Node"):up() ; local node = targets.tags[#targets.tags]
+			for _, address in ipairs(target.addresses) do
+				node:tag("Address", { category = address.cat, ["ext-category"] = address.ext }):text(address.text):up();
+			end
+			node:tag("NodeRole", { category = target.noderole.cat, ["ext-category"] = target.noderole.ext }):up();
+		end
+
+		if object.event_data.expectation then
+			e_data:tag("Expectation", { action = object.event_data.expectation.action }):up();
+			if object.event_data.expectation.desc then
+				local expectation = e_data:get_child("Expectation")
+				expectation:tag("Description"):text(object.event_data.expectation.desc):up();
+			end
+		end
+
+		if object.history then
+			local history = incident:tag("History"):up();
+			
+			for _, item in ipairs(object.history) do
+				history:tag("HistoryItem", { action = item.action })
+					:tag("DateTime"):text(item.date):up()
+					:tag("Description"):text(item.desc):up():up();
+			end	
+		end
+
+		-- Sanitize contact empty tags
+		for _, tag in ipairs(incident) do
+			if tag.name == "Contact" then
+				for i, check in ipairs(tag) do
+					if (check.name == "Email" or check.name == "PostalAddress" or check.name == "Telephone") and
+					   not check:get_text() then
+						table.remove(tag, i) 
+					end
+				end	
+			end
+		end
+
+		if s_type == "request" then stanza.attr.type = "get"
+		elseif s_type == "response" then stanza.attr.type = "set"
+		else stanza.attr.type = "set" end 
+
+		return stanza
+	end
+end 
+
+
+_M = {} -- wraps methods into the library.
+_M.ft_str = ft_str
+_M.get_incident_layout = get_incident_layout
+_M.render_list = render_list
+_M.render_single = render_single
+_M.get_type = get_type
+_M.stanza_parser = stanza_parser
+_M.stanza_construct = stanza_construct
+_M.set_my_host = function(host) my_host = host end
+
+return _M
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_incidents_handling/incidents_handling/mod_incidents_handling.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,352 @@
+-- This plugin implements XEP-268 (Incidents Handling)
+-- (C) 2012-2013, Marco Cirillo (LW.Org)
+
+-- Note: Only part of the IODEF specifications are supported.
+
+module:depends("adhoc")
+
+local datamanager = require "util.datamanager"
+local dataforms_new = require "util.dataforms".new
+local st = require "util.stanza"
+local id_gen = require "util.uuid".generate
+
+local pairs, os_time = pairs, os.time
+
+local xmlns_inc = "urn:xmpp:incident:2"
+local xmlns_iodef = "urn:ietf:params:xml:ns:iodef-1.0"
+
+local my_host = module:get_host()
+local ih_lib = module:require("incidents_handling")
+ih_lib.set_my_host(my_host)
+incidents = {}
+
+local expire_time = module:get_option_number("incidents_expire_time", 0)
+
+-- Incidents Table Methods
+
+local _inc_mt = {} ; _inc_mt.__index = _inc_mt
+
+function _inc_mt:init()
+	self:clean() ; self:save()			
+end
+
+function _inc_mt:clean()
+	if expire_time > 0 then
+		for id, incident in pairs(self) do
+			if ((os_time() - incident.time) > expire_time) and incident.status ~= "open" then
+				incident = nil
+			end
+		end
+	end
+end
+
+function _inc_mt:save()
+	if not datamanager.store("incidents", my_host, "incidents_store", incidents) then
+		module:log("error", "Failed to save the incidents store!")
+	end
+end
+
+function _inc_mt:add(stanza, report)
+	local data = ih_lib.stanza_parser(stanza)
+	local new_object = {
+		time = os_time(),
+		status = (not report and "open") or nil,
+		data = data
+	}
+
+	self[data.id.text] = new_object
+	self:clean() ; self:save()
+end
+
+function _inc_mt:new_object(fields, formtype)
+	local start_time, end_time, report_time = fields.started, fields.ended, fields.reported
+
+	local _desc, _contacts, _related, _impact, _sources, _targets = fields.description, fields.contacts, fields.related, fields.impact, fields.sources, fields.targets
+	local fail = false
+
+	local _lang, _dtext = _desc:match("^(%a%a)%s(.*)$")
+	if not _lang or not _dtext then return false end
+	local desc = { text = _dtext, lang = _lang }
+
+	local contacts = {}	
+	for contact in _contacts:gmatch("[%w%p]+%s[%w%p]+%s[%w%p]+") do
+		local address, atype, role = contact:match("^([%w%p]+)%s([%w%p]+)%s([%w%p]+)$")
+		if not address or not atype or not role then fail = true ; break end
+		contacts[#contacts + 1] = {
+			role = role,
+			ext_role = (role ~= "creator" or role ~= "admin" or role ~= "tech" or role ~= "irt" or role ~= "cc" and true) or nil,
+			type = atype,
+			ext_type = (atype ~= "person" or atype ~= "organization" and true) or nil,
+			jid = (atype == "jid" and address) or nil,
+			email = (atype == "email" and address) or nil,
+			telephone = (atype == "telephone" and address) or nil,
+			postaladdr = (atype == "postaladdr" and address) or nil
+		}
+	end
+
+	local related = {}
+	if _related then
+		for related in _related:gmatch("[%w%p]+%s[%w%p]+") do
+			local fqdn, id = related:match("^([%w%p]+)%s([%w%p]+)$")
+			if fqdn and id then related[#related + 1] = { text = id, name = fqdn } end
+		end
+	end
+
+	local _severity, _completion, _type = _impact:match("^([%w%p]+)%s([%w%p]+)%s([%w%p]+)$")
+	local assessment = { lang = "en", severity = _severity, completion = _completion, type = _type }
+
+	local sources = {}
+	for source in _sources:gmatch("[%w%p]+%s[%w%p]+%s[%d]+%s[%w%p]+") do
+		local address, cat, count, count_type = source:match("^([%w%p]+)%s([%w%p]+)%s(%d+)%s([%w%p]+)$")
+		if not address or not cat or not count or not count_type then fail = true ; break end
+		local cat, cat_ext = ih_lib.get_type(cat, "category")
+		local count_type, count_ext = ih_lib.get_type(count_type, "counter")
+
+		sources[#sources + 1] = {
+			address = { cat = cat, ext = cat_ext, text = address },
+			counter = { type = count_type, ext_type = count_ext, value = count }
+		}
+	end
+
+	local targets, _preprocess = {}, {}
+	for target in _targets:gmatch("[%w%p]+%s[%w%p]+%s[%w%p]+") do
+		local address, cat, noderole, noderole_ext
+		local address, cat, noderole = target:match("^([%w%p]+)%s([%w%p]+)%s([%w%p]+)$")
+		if not address or not cat or not noderole then fail = true ; break end
+		cat, cat_ext = ih_lib.get_type(cat, "category")
+		noderole_ext = ih_lib.get_type(cat, "noderole")
+
+		if not _preprocess[noderole] then _preprocess[noderole] = { addresses = {}, ext = noderole_ext } end
+		
+		_preprocess[noderole].addresses[#_preprocess[noderole].addresses + 1] = {
+			text = address, cat = cat, ext = cat_ext
+		}
+	end
+	for noderole, data in pairs(_preprocess) do
+		local nr_cat = (data.ext and "ext-category") or noderole
+		local nr_ext = (data.ext and noderole) or nil
+		targets[#targets + 1] = { addresses = data.addresses, noderole = { cat = nr_cat, ext = nr_ext } }
+	end
+
+	local new_object = {}
+	if not fail then
+		new_object["time"] = os_time()
+		new_object["status"] = (formtype == "request" and "open") or nil
+		new_object["type"] = formtype
+		new_object["data"] = {
+			id = { text = id_gen(), name = my_host },
+			start_time = start_time,
+			end_time = end_time,
+			report_time = report_time,
+			desc = desc,
+			contacts = contacts,
+			related = related,
+			assessment = assessment,
+			event_data = { sources = sources, targets = targets }
+		}
+		
+		self[new_object.data.id.text] = new_object
+		self:clean() ; self:save()
+		return new_object.data.id.text
+	else return false end
+end
+
+-- // Handler Functions //
+
+local function report_handler(event)
+	local origin, stanza = event.origin, event.stanza
+
+	incidents:add(stanza, true)
+	return origin.send(st.reply(stanza))
+end
+
+local function inquiry_handler(event)
+	local origin, stanza = event.origin, event.stanza
+
+	local inc_id = stanza:get_child("inquiry", xmlns_inc):get_child("Incident", xmlns_iodef):get_child("IncidentID"):get_text()
+	if incidents[inc_id] then
+		module:log("debug", "Server %s queried for incident %s which we know about, sending it", stanza.attr.from, inc_id)
+		local report_iq = stanza_construct(incidents[inc_id])
+		report_iq.attr.from = stanza.attr.to
+		report_iq.attr.to = stanza.attr.from
+		report_iq.attr.type = "set"
+
+		origin.send(st.reply(stanza))
+		origin.send(report_iq)
+		return true
+	else
+		module:log("error", "Server %s queried for incident %s but we don't know about it", stanza.attr.from, inc_id)
+		origin.send(st.error_reply(stanza, "cancel", "item-not-found")) ; return true
+	end	
+end
+
+local function request_handler(event)
+	local origin, stanza = event.origin, event.stanza
+
+	local req_id = stanza:get_child("request", xmlns_inc):get_child("Incident", xmlns_iodef):get_child("IncidentID"):get_text()
+	if not incidents[req_id] then
+		origin.send(st.error_reply(stanza, "cancel", "item-not-found")) ; return true
+	else
+		origin.send(st.reply(stanza)) ; return true
+	end
+end
+
+local function response_handler(event)
+	local origin, stanza = event.origin, event.stanza
+
+	local res_id = stanza:get_child("response", xmlns_inc):get_child("Incident", xmlns_iodef):get_child("IncidentID"):get_text()
+	if incidents[res_id] then
+		incidents[res_id] = nil
+		incidents:add(stanza, true)
+		origin.send(st.reply(stanza)) ; return true
+	else
+		origin.send(st.error_reply(stanza, "cancel", "item-not-found")) ; return true
+	end
+end
+
+local function results_handler(event) return true end -- TODO results handling
+
+-- // Adhoc Commands //
+
+local function list_incidents_command_handler(self, data, state)
+	local list_incidents_layout = ih_lib.render_list(incidents)
+
+	if state then
+		if state.step == 1 then
+			if data.action == "cancel" then 
+				return { status = "canceled" }
+			elseif data.action == "prev" then
+				return { status = "executing", actions = { "next", default = "next" }, form = list_incidents_layout }, {}
+			end
+
+			local single_incident_layout = state.form_layout
+			local fields = single_incident_layout:data(data.form)
+
+			if fields.response then
+				incidents[state.id].status = "closed"
+
+				local iq_send = ih_lib.stanza_construct(incidents[state.id])
+				module:send(iq_send)
+				return { status = "completed", info = "Response sent." }
+			else
+				return { status = "completed" }
+			end
+		else
+			if data.action == "cancel" then return { status = "canceled" } end
+			local fields = list_incidents_layout:data(data.form)
+
+			if fields.ids then
+				local single_incident_layout = ih_lib.render_single(incidents[fields.ids])
+				return { status = "executing", actions = { "prev", "complete", default = "complete" }, form = single_incident_layout }, { step = 1, form_layout = single_incident_layout, id = fields.ids }
+			else
+				return { status = "completed", error = { message = "You need to select the report ID to continue." } }
+			end
+		end
+	else
+		return { status = "executing", actions = { "next", default = "next" }, form = list_incidents_layout }, {}
+	end
+end
+
+local function send_inquiry_command_handler(self, data, state)
+	local send_inquiry_layout = dataforms_new{
+		title = "Send an inquiry about an incident report to a host";
+		instructions = "Please specify both the server host and the incident ID.";
+
+		{ name = "FORM_TYPE", type = "hidden", value = "http://jabber.org/protocol/commands" };
+		{ name = "server", type = "text-single", label = "Server to inquiry" };
+		{ name = "hostname", type = "text-single", label = "Involved incident host" };
+		{ name = "id", type = "text-single", label = "Incident ID" };
+	}
+
+	if state then
+		if data.action == "cancel" then return { status = "canceled" } end
+		local fields = send_inquiry_layout:data(data.form)
+
+		if not fields.hostname or not fields.id or not fields.server then
+			return { status = "completed", error = { message = "You must supply the server to quest, the involved incident host and the incident ID." } }
+		else
+			local iq_send = st.iq({ from = my_host, to = fields.server, type = "get" })
+						:tag("inquiry", { xmlns = xmlns_inc })
+							:tag("Incident", { xmlns = xmlns_iodef, purpose = "traceback" })
+								:tag("IncidentID", { name = data.hostname }):text(fields.id):up():up():up()
+
+			module:log("debug", "Sending incident inquiry to %s", fields.server)
+			module:send(iq_send)
+			return { status = "completed", info = "Inquiry sent, if an answer can be obtained from the remote server it'll be listed between incidents." }
+		end
+	else
+		return { status = "executing", form = send_inquiry_layout }, "executing"
+	end
+end
+
+local function rr_command_handler(self, data, state, formtype)
+	local send_layout = ih_lib.get_incident_layout(formtype)
+	local err_no_fields = { status = "completed", error = { message = "You need to fill all fields, except the eventual related incident." } }
+	local err_proc = { status = "completed", error = { message = "There was an error processing your request, check out the syntax" } }
+
+	if state then
+		if data.action == "cancel" then return { status = "canceled" } end
+		local fields = send_layout:data(data.form)
+			
+		if fields.started and fields.ended and fields.reported and fields.description and fields.contacts and
+		   fields.impact and fields.sources and fields.targets and fields.entity then
+			if formtype == "request" and not fields.expectation then return err_no_fields end
+			local id = incidents:new_object(fields, formtype)
+			if not id then return err_proc end
+
+			local stanza = ih_lib.stanza_construct(id)
+			stanza.attr.from = my_host
+			stanza.attr.to = fields.entity
+			module:log("debug","Sending incident %s stanza to: %s", formtype, stanza.attr.to)
+			module:send(stanza)
+
+			return { status = "completed", info = string.format("Incident %s sent to %s.", formtype, fields.entity) }
+		else
+			return err_no_fields
+		end	   
+	else
+		return { status = "executing", form = send_layout }, "executing"
+	end
+end
+
+local function send_report_command_handler(self, data, state)
+	return rr_command_handler(self, data, state, "report")
+end
+
+local function send_request_command_handler(self, data, state)
+	return rr_command_handler(self, data, state, "request")
+end
+
+local adhoc_new = module:require "adhoc".new
+local list_incidents_descriptor = adhoc_new("List Incidents", xmlns_inc.."#list", list_incidents_command_handler, "admin")
+local send_inquiry_descriptor = adhoc_new("Send Incident Inquiry", xmlns_inc.."#send_inquiry", send_inquiry_command_handler, "admin")
+local send_report_descriptor = adhoc_new("Send Incident Report", xmlns_inc.."#send_report", send_report_command_handler, "admin")
+local send_request_descriptor = adhoc_new("Send Incident Request", xmlns_inc.."#send_request", send_request_command_handler, "admin")
+module:provides("adhoc", list_incidents_descriptor)
+module:provides("adhoc", send_inquiry_descriptor)
+module:provides("adhoc", send_report_descriptor)
+module:provides("adhoc", send_request_descriptor)
+
+-- // Hooks //
+
+module:hook("iq-set/host/urn:xmpp:incident:2:report", report_handler)
+module:hook("iq-get/host/urn:xmpp:incident:2:inquiry", inquiry_handler)
+module:hook("iq-get/host/urn:xmpp:incident:2:request", request_handler)
+module:hook("iq-set/host/urn:xmpp:incident:2:response", response_handler)
+module:hook("iq-result/host/urn:xmpp:incident:2", results_handler)
+
+-- // Module Methods //
+
+module.load = function()
+	if datamanager.load("incidents", my_host, "incidents_store") then incidents = datamanager.load("incidents", my_host, "incidents_store") end
+	setmetatable(incidents, _inc_mt) ; incidents:init()
+end
+
+module.save = function()
+	return { incidents = incidents }
+end
+
+module.restore = function(data)
+	incidents = data.incidents or {}
+	setmetatable(incidents, _inc_mt) ; incidents:init()		
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_last_offline/mod_last_offline.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,33 @@
+local datamanager = require "util.datamanager";	
+local jid_split = require "util.jid".split;
+local time = os.time;
+local NULL = {};
+local host = module.host;
+
+module:hook("resource-unbind", function(event)
+	local session = event.session;
+	if session.username then
+		datamanager.store(session.username, host, "last_online", {
+			timestamp = time(),
+		});
+	end
+end);
+
+local function offline_stamp(event)
+	local stanza = event.stanza;
+	local node, to_host = jid_split(stanza.attr.from);
+	if to_host == host and event.origin == hosts[host] and stanza.attr.type == "unavailable" then
+		local timestamp = (datamanager.load(node, host, "last_online") or NULL).timestamp;
+		if timestamp then
+			stanza:tag("delay", {
+				xmlns = "urn:xmpp:delay",
+				from = host,
+				stamp = datetime.datetime(timestamp),
+			}):up();
+		end
+	end
+end
+
+module:hook("pre-presence/bare", offline_stamp);
+module:hook("pre-presence/full", offline_stamp);
+
--- a/mod_lib_ldap/README.md	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/README.md	Tue Mar 12 12:10:25 2013 +0000
@@ -12,7 +12,7 @@
 With that note in mind, you need to set 'allow\_unencrypted\_plain\_auth' to true in your configuration if
 you want to use LDAP authentication.
 
-To enable LDAP authentication, set 'authentication' to 'ldap' in your configuration file.
+To enable LDAP authentication, set 'authentication' to 'ldap2' in your configuration file.
 See also http://prosody.im/doc/authentication.
 
 # LDAP Storage
--- a/mod_lib_ldap/dev/posix-users.ldif	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/dev/posix-users.ldif	Tue Mar 12 12:10:25 2013 +0000
@@ -19,6 +19,10 @@
 ou: Users
 objectclass: organizationalUnit
 
+dn: ou=Admins,ou=Users,dc=example,dc=com
+ou: Admins
+objectclass: organizationalUnit
+
 dn: uid=one,ou=Users,dc=example,dc=com
 objectclass: posixAccount
 objectclass: person
@@ -212,6 +216,28 @@
  K7j+qA4/I0UUNElHxV4s0HW/D81vBcP54IeNWiYZP5ehNeM3NrP5rFImYZ6gUUVDirmsJNKxGsE6
  9YZB/wABNTJkcEEfWiigpM6rwgyR6vDLJ/q4zvb6Dmiiioe5dj//2Q==
 
+dn: uid=six,ou=Admins,ou=Users,dc=example,dc=com
+objectclass: posixAccount
+objectclass: person
+uid: six
+uidNumber: 1005
+gidNumber: 1005
+sn: Testerson
+cn: Admin Testerson
+userPassword: 123456
+homeDirectory: /home/six
+
+dn: uid=seven,ou=Users,dc=example,dc=com
+objectclass: posixAccount
+objectclass: person
+uid: seven
+uidNumber: 1006
+gidNumber: 1006
+sn: User
+cn: Invalid User
+userPassword: 1234567
+homeDirectory: /home/seven
+
 dn: cn=Everyone,ou=Groups,dc=example,dc=com
 objectclass: posixGroup
 cn: Everyone
--- a/mod_lib_ldap/dev/prosody-posix-ldap.cfg.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/dev/prosody-posix-ldap.cfg.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -1,6 +1,6 @@
 -- Use Include 'prosody-posix-ldap.cfg.lua' from prosody.cfg.lua to include this file
-authentication = 'ldap' -- Indicate that we want to use LDAP for authentication
-storage        = 'ldap' -- Indicate that we want to use LDAP for roster/vcard storage
+authentication = 'ldap2' -- Indicate that we want to use LDAP for authentication
+storage        = 'ldap'  -- Indicate that we want to use LDAP for roster/vcard storage
 
 ldap = {
     hostname      = 'localhost',                    -- LDAP server location
@@ -8,10 +8,10 @@
     bind_password = 'prosody',                      -- Bind password (optional if anonymous bind is supported)
 
     user = {
-      basedn        = 'ou=Users,dc=example,dc=com', -- The base DN where user records can be found
-      filter        = 'objectClass=posixAccount',   -- Filter expression to find user records under basedn
-      usernamefield = 'uid',                        -- The field that contains the user's ID (this will be the username portion of the JID)
-      namefield     = 'cn',                         -- The field that contains the user's full name (this will be the alias found in the roster)
+      basedn        = 'ou=Users,dc=example,dc=com',                  -- The base DN where user records can be found
+      filter        = '(&(objectClass=posixAccount)(!(uid=seven)))', -- Filter expression to find user records under basedn
+      usernamefield = 'uid',                                         -- The field that contains the user's ID (this will be the username portion of the JID)
+      namefield     = 'cn',                                          -- The field that contains the user's full name (this will be the alias found in the roster)
     },
 
     groups = {
--- a/mod_lib_ldap/dev/t/00-login.t	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/dev/t/00-login.t	Tue Mar 12 12:10:25 2013 +0000
@@ -11,9 +11,10 @@
     'three',
     'four',
     'five',
+    'six',
 );
 
-plan tests => scalar(@users) + 2;
+plan tests => scalar(@users) + 3;
 
 foreach my $username (@users) {
     my $conn = TestConnection->new($username);
@@ -23,7 +24,7 @@
     });
 
     my $error = $conn->cond->recv;
-    ok(! $error) or diag($error);
+    ok(! $error) or diag("$username login failed: $error");
 }
 
 do {
@@ -38,7 +39,7 @@
 };
 
 do {
-    my $conn = TestConnection->new('six', password => '12345');
+    my $conn = TestConnection->new('invalid', password => '12345');
 
     $conn->reg_cb(session_ready => sub {
         $conn->cond->send;
@@ -47,3 +48,14 @@
     my $error = $conn->cond->recv;
     ok($error);
 };
+
+do {
+    my $conn = TestConnection->new('seven', password => '1234567');
+
+    $conn->reg_cb(session_ready => sub {
+        $conn->cond->send;
+    });
+
+    my $error = $conn->cond->recv;
+    ok($error);
+};
--- a/mod_lib_ldap/dev/t/TestConnection.pm	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/dev/t/TestConnection.pm	Tue Mar 12 12:10:25 2013 +0000
@@ -14,6 +14,8 @@
     three => '34512',
     four  => '45123',
     five  => '51234',
+    six   => '123456',
+    seven => '1234567',
 );
 
 sub new {
--- a/mod_lib_ldap/ldap.lib.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_lib_ldap/ldap.lib.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -177,7 +177,27 @@
 
 -- XXX consider renaming this...it doesn't bind the current connection
 function _M.bind(username, password)
-    local who = format('%s=%s,%s', params.user.usernamefield, username, params.user.basedn);
+    local conn   = _M.getconnection();
+    local filter = format('%s=%s', params.user.usernamefield, username);
+
+    if filter then
+        filter = _M.filter.combine_and(filter, params.user.filter);
+    end
+
+    local who = _M.singlematch {
+        attrs     = params.user.usernamefield,
+        base      = params.user.basedn,
+        filter    = filter,
+    };
+
+    if who then
+        who = who.dn;
+        module:log('debug', '_M.bind - who: %s', who);
+    else
+        module:log('debug', '_M.bind - no DN found for username = %s', username);
+        return nil, format('no DN found for username = %s', username);
+    end
+
     local conn, err = ldap.open_simple(params.hostname, who, password, params.use_tls);
 
     if conn then
@@ -192,9 +212,10 @@
     local ld = _M.getconnection();
 
     query.sizelimit = 1;
-    query.scope     = 'onelevel';
+    query.scope     = 'subtree';
 
     for dn, attribs in ld:search(query) do
+        attribs.dn = dn;
         return attribs;
     end
 end
--- a/mod_mam/mod_mam.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_mam/mod_mam.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -23,6 +23,7 @@
 local tostring = tostring;
 local time_now = os.time;
 local m_min = math.min;
+local t_insert = table.insert;
 local timestamp, timestamp_parse = require "util.datetime".datetime, require "util.datetime".parse;
 local uuid = require "util.uuid".generate;
 local default_max_items, max_max_items = 20, module:get_option_number("max_archive_query_results", 50);
@@ -165,9 +166,12 @@
 		local first, last, index;
 		local n = 0;
 		local start = qset and qset.index or 1;
+		local results = {};
+		-- An empty <before/> means: give the last n items. So we loop backwards.
+		local reverse = qset and qset.before or false;
 
 		module:log("debug", "Loaded %d items, about to filter", #data);
-		for i=start,#data do
+		for i=(reverse and #data or start),(reverse and start or #data),(reverse and -1 or 1) do
 			local item = data[i];
 			local when, with, resource = item.when, item.with, item.resource;
 			local id = item.id;
@@ -195,7 +199,11 @@
 				local orig_stanza = st.deserialize(item.stanza);
 				orig_stanza.attr.xmlns = "jabber:client";
 				fwd_st:add_child(orig_stanza);
-				origin.send(fwd_st);
+				if reverse then
+					t_insert(results, 1, fwd_st);
+				else
+					results[#results + 1] = fwd_st;
+				end
 				if not first then
 					index = i;
 					first = id;
@@ -219,13 +227,16 @@
 				break
 			end
 		end
+		for _,v in pairs(results) do
+			origin.send(v);
+		end
 		-- That's all folks!
 		module:log("debug", "Archive query %s completed", tostring(qid));
 
 		local reply = st.reply(stanza);
 		if last then
 			-- This is a bit redundant, isn't it?
-			reply:query(xmlns_mam):add_child(rsm.generate{first = first, last = last, count = n});
+			reply:query(xmlns_mam):add_child(rsm.generate{first = (reverse and last or first), last = (reverse and first or last), count = n});
 		end
 		origin.send(reply);
 		return true
--- a/mod_register_json/mod_register_json.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_register_json/mod_register_json.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -69,7 +69,6 @@
 		return http_response(event, 400, "JSON Decoding failed.")
 	else
 		-- Decode JSON data and check that all bits are there else throw an error
-		req_body = json_decode(body)
 		if req_body["username"] == nil or req_body["password"] == nil or req_body["host"] == nil or req_body["ip"] == nil then
 			module:log("debug", "%s supplied an insufficent number of elements or wrong elements for the JSON registration", user)
 			return http_response(event, 400, "Invalid syntax.")
@@ -86,7 +85,7 @@
 			-- And nodeprep the username
 			local username = nodeprep(req_body["username"])
 			if not username then
-				module:log("debug", "%s supplied an username containing invalid characters: %s", user, username)
+				module:log("debug", "An username containing invalid characters was supplied: %s", user)
 				return http_response(event, 406, "Supplied username contains invalid characters, see RFC 6122.")
 			else
 				if not usermanager.user_exists(username, req_body["host"]) then
--- a/mod_s2s_never_encrypt_blacklist/mod_s2s_never_encrypt_blacklist.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_s2s_never_encrypt_blacklist/mod_s2s_never_encrypt_blacklist.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -1,18 +1,24 @@
 -- Filter out servers which gets choppy and buggy when it comes to starttls.
 
-local bad_servers = module:get_option_set("tls_s2s_blacklist")
-local bad_servers_ip = module:get_option_set("tls_s2s_blacklist_ip")
+local bad_servers = module:get_option_set("tls_s2s_blacklist", {})
+local bad_servers_ip = module:get_option_set("tls_s2s_blacklist_ip", {})
+local libev = module:get_option_boolean("use_libevent")
 
 local function disable_tls_for_baddies_in(event)
-	if bad_servers:contains(event.origin.to_host) or bad_servers_ip:contains(event.origin.conn:ip())
-		then event.origin.conn.starttls = nil end
+	local session = event.origin
+	if bad_servers:contains(session.from_host) or bad_servers_ip:contains(session.conn:ip()) then 
+		module:log("debug", "disabling tls on incoming stream from %s...", tostring(session.from_host));
+		if libev then session.conn.starttls = false; else session.conn.starttls = nil; end
+	end
 end
 
 local function disable_tls_for_baddies_out(event)
-	if bad_servers:contains(event.origin.from_host) or bad_servers_ip:contains(event.origin.conn:ip())
-		then event.origin.conn.starttls = nil end
+	local session = event.origin
+	if bad_servers:contains(session.to_host) then
+		module:log("debug", "disabling tls on outgoing stream from %s...", tostring(session.to_host));
+		if libev then session.conn.starttls = false; else session.conn.starttls = nil; end
+	end
 end
 
-module:hook("s2s-stream-features", disable_tls_for_baddies_out, 10)
-module:hook("stanza/http://etherx.jabber.org/streams:features", disable_tls_for_baddies_in, 510)
-
+module:hook("s2s-stream-features", disable_tls_for_baddies_in, 600)
+module:hook("stanza/http://etherx.jabber.org/streams:features", disable_tls_for_baddies_out, 600)
--- a/mod_service_directories/mod_service_directories.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_service_directories/mod_service_directories.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -14,6 +14,7 @@
 local adhoc_new = module:require "adhoc".new;
 local to_ascii = require "util.encodings".idna.to_ascii;
 local nameprep = require "util.encodings".stringprep.nameprep;
+local dataforms_new = require "util.dataforms".new;
 local pairs, ipairs = pairs, ipairs;
 local module = module;
 local hosts = hosts;
@@ -79,7 +80,7 @@
 -- Admin ad-hoc command to subscribe
 
 local function add_contact_handler(self, data, state)
-	local layout = {
+	local layout = dataforms_new{
 		title = "Adding a Server Buddy";
 		instructions = "Fill out this form to add a \"server buddy\".";
 
@@ -92,7 +93,7 @@
 	elseif data.action == "canceled" then
 		return { status = "canceled" };
 	else
-		local fields = layout:data(data);
+		local fields = layout:data(data.form);
 		local peerjid = nameprep(fields.peerjid);
 		if not peerjid or peerjid == "" or #peerjid > 1023 or not to_ascii(peerjid) then
 			return { status = "completed", error = { message = "Invalid JID" } };
--- a/mod_smacks/mod_smacks.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_smacks/mod_smacks.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -83,6 +83,11 @@
 			
 			queue[#queue+1] = cached_stanza;
 		end
+		if session.hibernating then
+			-- The session is hibernating, no point in sending the stanza
+			-- over a dead connection.  It will be delivered upon resumption.
+			return true;
+		end
 		local ok, err = _send(stanza);
 		if ok and #queue > max_unacked_stanzas and not session.awaiting_ack and attr and not attr.xmlns then
 			session.awaiting_ack = true;
@@ -203,7 +208,7 @@
 
 module:hook("pre-resource-unbind", function (event)
 	local session, err = event.session, event.error;
-	if session.smacks and err ~= "session closed" then
+	if session.smacks and err then
 		if not session.resumption_token then
 			local queue = session.outgoing_stanza_queue;
 			if #queue > 0 then
@@ -244,6 +249,14 @@
 end);
 
 module:hook_stanza(xmlns_sm, "resume", function (session, stanza)
+	if session.full_jid then
+		session.log("debug", "Tried to resume after resource binding");
+		session.send(st.stanza("failed", sm_attr)
+			:tag("unexpected-request", { xmlns = xmlns_errors })
+		);
+		return true;
+	end
+
 	local id = stanza.attr.previd;
 	local original_session = session_registry[id];
 	if not original_session then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_strict_https/mod_strict_https.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,44 @@
+-- HTTP Strict Transport Security
+-- https://tools.ietf.org/html/rfc6797
+
+module:set_global();
+
+local http_server = require "net.http.server";
+
+local hsts_header = module:get_option_string("hsts_header", "max-age=31556952"); -- This means "Don't even try to access without HTTPS for a year"
+
+local _old_send_response;
+local _old_fire_event;
+
+local modules = {};
+
+function module.load()
+	_old_send_response = http_server.send_response;
+	function http_server.send_response(response, body)
+		response.headers.strict_transport_security = hsts_header;
+		return _old_send_response(response, body);
+	end
+
+	_old_fire_event = http_server._events.fire_event;
+	function http_server._events.fire_event(event, payload)
+		local request = payload.request;
+		local host = event:match("^[A-Z]+ ([^/]+)");
+		local module = modules[host];
+		if module and not request.secure then
+			payload.response.headers.location = module:http_url(request.path);
+			return 301;
+		end
+		return _old_fire_event(event, payload);
+	end
+end
+function module.unload()
+	http_server.send_response = _old_send_response;
+	http_server._events.fire_event = _old_fire_event;
+end
+function module.add_host(module)
+	local http_host = module:get_option_string("http_host", module.host);
+	modules[http_host] = module;
+	function module.unload()
+		modules[http_host] = nil;
+	end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_uptime_presence/mod_uptime_presence.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -0,0 +1,17 @@
+local st = require"util.stanza";
+local datetime = require"util.datetime";
+
+local presence = st.presence({ from = module.host })
+	:tag("delay", { xmlns = "urn:xmpp:delay",
+		stamp = datetime.datetime(prosody.start_time) });
+
+module:hook("presence/host", function(event)
+	local stanza = event.stanza;
+	if stanza.attr.type == "probe" then
+		presence.attr.id = stanza.attr.id;
+		presence.attr.to = stanza.attr.from;
+		module:send(presence);
+		return true;
+	end
+end, 10);
+
--- a/mod_vjud/mod_vjud.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_vjud/mod_vjud.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -6,6 +6,8 @@
 local jid_split = require "util.jid".prepped_split;
 local vcard = module:require "vcard";
 local rawget, rawset = rawget, rawset;
+local s_lower = string.lower;
+local s_find = string.find;
 
 local st = require "util.stanza";
 local template = require "util.template";
@@ -28,6 +30,8 @@
 </item>
 ]];
 
+local search_mode = module:get_option_string("vjud_mode", "opt-in");
+local allow_remote = module:get_option_boolean("allow_remote_searches", search_mode ~= "all");
 local base_host = module:get_option_string("vjud_search_domain",
 	module:get_host_type() == "component"
 		and module.host:gsub("^[^.]+%.","")
@@ -36,23 +40,6 @@
 module:depends"disco";
 module:add_feature("jabber:iq:search");
 
-local opted_in;
-function module.load()
-	opted_in = dm_load(nil, module.host, "user_index") or {};
-end
-function module.unload()
-	dm_store(nil, module.host, "user_index", opted_in);
-end
-
-local opt_in_layout = dataforms_new{
-	title = "Search settings";
-	instructions = "Do you want to appear in search results?";
-	{
-		name = "searchable",
-		label = "Appear in search results?",
-		type = "boolean",
-	},
-};
 local vCard_mt = {
 	__index = function(t, k)
 		if type(k) ~= "string" then return nil end
@@ -77,21 +64,32 @@
 
 local at_host = "@"..base_host;
 
+local users; -- The user iterator
+
 module:hook("iq/host/jabber:iq:search:query", function(event)
 	local origin, stanza = event.origin, event.stanza;
 
+	if not (allow_remote or origin.type == "c2s") then
+		origin.send(st.error_reply(stanza, "cancel", "not-allowed"))
+		return true;
+	end
+
 	if stanza.attr.type == "get" then
 		origin.send(st.reply(stanza):add_child(get_reply));
 	else -- type == "set"
 		local query = stanza.tags[1];
 		local first, last, nick, email =
-			(query:get_child_text"first" or false),
-			(query:get_child_text"last" or false),
-			(query:get_child_text"nick" or false),
-			(query:get_child_text"email" or false);
+			s_lower(query:get_child_text"first" or ""),
+			s_lower(query:get_child_text"last" or ""),
+			s_lower(query:get_child_text"nick" or ""),
+			s_lower(query:get_child_text"email" or "");
 
+		first = #first >= 2 and first;
+		last  = #last  >= 2 and last;
+		nick  = #nick  >= 2 and nick;
+		email = #email >= 2 and email;
 		if not ( first or last or nick or email ) then
-			origin.send(st.error_reply(stanza, "modify", "not-acceptable", "All fields were empty"));
+			origin.send(st.error_reply(stanza, "modify", "not-acceptable", "All fields were empty or too short"));
 			return true;
 		end
 
@@ -110,13 +108,13 @@
 				});
 			end
 		else
-			for username in pairs(opted_in) do
+			for username in users() do
 				local vCard = get_user_vcard(username);
-				if vCard and (
-				(vCard.N and vCard.N[2] == first) or
-				(vCard.N and vCard.N[1] == last) or
-				(vCard.NICKNAME and vCard.NICKNAME[1] == nick) or
-				(vCard.EMAIL and vCard.EMAIL[1] == email)) then
+				if vCard
+				and ((first and vCard.N and s_find(s_lower(vCard.N[2]), first, nil, true))
+				or (last and vCard.N and s_find(s_lower(vCard.N[1]), last, nil, true))
+				or (nick and vCard.NICKNAME and s_find(s_lower(vCard.NICKNAME[1]), nick, nil, true))
+				or (email and vCard.EMAIL and s_find(s_lower(vCard.EMAIL[1]), email, nil, true))) then
 					reply:add_child(item_template.apply{
 						jid = username..at_host;
 						first = vCard.N and vCard.N[2] or nil;
@@ -132,30 +130,55 @@
 	return true;
 end);
 
-local function opt_in_handler(self, data, state)
-	local username, hostname = jid_split(data.from);
-	if state then -- the second return value
-		if data.action == "cancel" then
-			return { status = "canceled" };
-		end
-
-		if not username or not hostname or hostname ~= base_host then
-			return { status = "error", error = { type = "cancel",
-				condition = "forbidden", message = "Invalid user or hostname." } };
-		end
+if search_mode == "all" then
+	function users()
+		return usermanager.users(base_host);
+	end
+else -- if "opt-in", default
+	local opted_in;
+	function module.load()
+		opted_in = dm_load(nil, module.host, "user_index") or {};
+	end
+	function module.unload()
+		dm_store(nil, module.host, "user_index", opted_in);
+	end
+	function users()
+		return pairs(opted_in);
+	end
+	local opt_in_layout = dataforms_new{
+		title = "Search settings";
+		instructions = "Do you want to appear in search results?";
+		{
+			name = "searchable",
+			label = "Appear in search results?",
+			type = "boolean",
+		},
+	};
+	local function opt_in_handler(self, data, state)
+		local username, hostname = jid_split(data.from);
+		if state then -- the second return value
+			if data.action == "cancel" then
+				return { status = "canceled" };
+			end
 
-		local fields = opt_in_layout:data(data.form);
-		opted_in[username] = fields.searchable or nil
+			if not username or not hostname or hostname ~= base_host then
+				return { status = "error", error = { type = "cancel",
+				condition = "forbidden", message = "Invalid user or hostname." } };
+			end
 
-		return { status = "completed" }
-	else -- No state, send the form.
-		return { status = "executing", actions  = { "complete" },
+			local fields = opt_in_layout:data(data.form);
+			opted_in[username] = fields.searchable or nil
+
+			return { status = "completed" }
+		else -- No state, send the form.
+			return { status = "executing", actions  = { "complete" },
 			form = { layout = opt_in_layout, values = { searchable = opted_in[username] } } }, true;
+		end
 	end
-end
 
-local adhoc_new = module:require "adhoc".new;
-local adhoc_vjudsetup = adhoc_new("Search settings", "vjudsetup", opt_in_handler);--, "self");-- and nil);
-module:depends"adhoc";
-module:provides("adhoc", adhoc_vjudsetup);
+	local adhoc_new = module:require "adhoc".new;
+	local adhoc_vjudsetup = adhoc_new("Search settings", "vjudsetup", opt_in_handler);--, "self");-- and nil);
+	module:depends"adhoc";
+	module:provides("adhoc", adhoc_vjudsetup);
 
+end
--- a/mod_websocket/mod_websocket.lua	Thu Nov 22 18:59:10 2012 +0000
+++ b/mod_websocket/mod_websocket.lua	Tue Mar 12 12:10:25 2013 +0000
@@ -1,6 +1,4 @@
 -- Prosody IM
--- Copyright (C) 2008-2010 Matthew Wild
--- Copyright (C) 2008-2010 Waqas Hussain
 -- Copyright (C) 2012 Florian Zeitz
 --
 -- This project is MIT/X11 licensed. Please see the
@@ -9,28 +7,19 @@
 
 module:set_global();
 
-local add_task = require "util.timer".add_task;
-local new_xmpp_stream = require "util.xmppstream".new;
-local nameprep = require "util.encodings".stringprep.nameprep;
-local sessionmanager = require "core.sessionmanager";
-local st = require "util.stanza";
-local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
-local uuid_generate = require "util.uuid".generate;
+local add_filter = require "util.filters".add_filter;
 local sha1 = require "util.hashes".sha1;
 local base64 = require "util.encodings".base64.encode;
-local band = require "bit".band;
-local bxor = require "bit".bxor;
-
-local xpcall, tostring, type = xpcall, tostring, type;
-local traceback = debug.traceback;
+local softreq = require "util.dependencies".softreq;
+local portmanager = require "core.portmanager";
 
-local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
-
-local log = module._log;
-
-local c2s_timeout = module:get_option_number("c2s_timeout");
-local stream_close_timeout = module:get_option_number("c2s_close_timeout", 5);
-local opt_keepalives = module:get_option_boolean("tcp_keepalives", false);
+local bit;
+pcall(function() bit = require"bit"; end);
+bit = bit or softreq"bit32"
+if not bit then module:log("error", "No bit module found. Either LuaJIT 2, lua-bitop or Lua 5.2 is required"); end
+local band = bit.band;
+local bxor = bit.bxor;
+local rshift = bit.rshift;
 
 local cross_domain = module:get_option("cross_domain_websocket");
 if cross_domain then
@@ -44,11 +33,9 @@
 	end
 end
 
-local sessions = module:shared("sessions");
-local core_process_stanza = prosody.core_process_stanza;
-
-local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza };
-local listener = {};
+module:depends("c2s")
+local sessions = module:shared("c2s/sessions");
+local c2s_listener = portmanager.get_service("c2s").listener;
 
 -- Websocket helpers
 local function parse_frame(frame)
@@ -118,11 +105,11 @@
 		result = result .. string.char(length);
 	elseif length <= 0xFFFF then -- 2-byte length
 		result = result .. string.char(126);
-		result = result .. string.char(length/0x100) .. string.char(length%0x100);
+		result = result .. string.char(rshift(length, 8)) .. string.char(length%0x100);
 	else -- 8-byte length
 		result = result .. string.char(127);
 		for i = 7, 0, -1 do
-			result = result .. string.char(( length / (2^(8*i)) ) % 0x100);
+			result = result .. string.char(rshift(length, 8*i) % 0x100);
 		end
 	end
 
@@ -131,198 +118,33 @@
 	return result;
 end
 
---- Stream events handlers
-local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
-local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
-
-function stream_callbacks.streamopened(session, attr)
-	local send = session.send;
-	session.host = nameprep(attr.to);
-	if not session.host then
-		session:close{ condition = "improper-addressing",
-			text = "A valid 'to' attribute is required on stream headers" };
-		return;
-	end
-	session.version = tonumber(attr.version) or 0;
-	session.streamid = uuid_generate();
-	(session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host);
+--- Filter stuff
+function handle_request(event, path)
+	local request, response = event.request, event.response;
+	local conn = response.conn;
 
-	if not hosts[session.host] then
-		-- We don't serve this host...
-		session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)};
-		return;
-	end
-
-	-- COMPAT: Some current client implementations need this to be self-closing
-	if session.self_closing_stream then
-		send("<?xml version='1.0'?>"..tostring(st.stanza("stream:stream", {
-			xmlns = 'jabber:client', ["xmlns:stream"] = 'http://etherx.jabber.org/streams';
-			id = session.streamid, from = session.host, version = '1.0', ["xml:lang"] = 'en' })));
-	else
-		send("<?xml version='1.0'?>"..st.stanza("stream:stream", {
-			xmlns = 'jabber:client', ["xmlns:stream"] = 'http://etherx.jabber.org/streams';
-			id = session.streamid, from = session.host, version = '1.0', ["xml:lang"] = 'en' }):top_tag());
-	end
-
-	(session.log or log)("debug", "Sent reply <stream:stream> to client");
-	session.notopen = nil;
-
-	-- If session.secure is *false* (not nil) then it means we /were/ encrypting
-	-- since we now have a new stream header, session is secured
-	if session.secure == false then
-		session.secure = true;
+	if not request.headers.sec_websocket_key then
+		response.headers.content_type = "text/html";
+		return [[<!DOCTYPE html><html><head><title>Websocket</title></head><body>
+			<p>It works! Now point your WebSocket client to this URL to connect to Prosody.</p>
+			</body></html>]];
 	end
 
-	local features = st.stanza("stream:features");
-	hosts[session.host].events.fire_event("stream-features", { origin = session, features = features });
-	module:fire_event("stream-features", session, features);
-
-	send(features);
-end
-
-function stream_callbacks.streamclosed(session)
-	session.log("debug", "Received </stream:stream>");
-	session:close(false);
-end
-
-function stream_callbacks.error(session, error, data)
-	if error == "no-stream" then
-		session.log("debug", "Invalid opening stream header");
-		session:close("invalid-namespace");
-	elseif error == "parse-error" then
-		(session.log or log)("debug", "Client XML parse error: %s", tostring(data));
-		session:close("not-well-formed");
-	elseif error == "stream-error" then
-		local condition, text = "undefined-condition";
-		for child in data:children() do
-			if child.attr.xmlns == xmlns_xmpp_streams then
-				if child.name ~= "text" then
-					condition = child.name;
-				else
-					text = child:get_text();
-				end
-				if condition ~= "undefined-condition" and text then
-					break;
-				end
-			end
-		end
-		text = condition .. (text and (" ("..text..")") or "");
-		session.log("info", "Session closed by remote with error: %s", text);
-		session:close(nil, text);
-	end
-end
-
-local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end
-function stream_callbacks.handlestanza(session, stanza)
-	stanza = session.filter("stanzas/in", stanza);
-	if stanza then
-		return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
-	end
-end
+	local wants_xmpp = false;
+	(request.headers.sec_websocket_protocol or ""):gsub("([^,]*),?", function (proto)
+		if proto == "xmpp" then wants_xmpp = true; end
+	end);
 
---- Session methods
-local function session_close(session, reason)
-	local log = session.log or log;
-	if session.conn then
-		if session.notopen then
-			-- COMPAT: Some current client implementations need this to be self-closing
-			if session.self_closing_stream then
-				session.send("<?xml version='1.0'?>"..tostring(st.stanza("stream:stream", default_stream_attr)));
-			else
-				session.send("<?xml version='1.0'?>"..st.stanza("stream:stream", default_stream_attr):top_tag());
-			end
-		end
-		if reason then -- nil == no err, initiated by us, false == initiated by client
-			if type(reason) == "string" then -- assume stream error
-				log("debug", "Disconnecting client, <stream:error> is: %s", reason);
-				session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
-			elseif type(reason) == "table" then
-				if reason.condition then
-					local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
-					if reason.text then
-						stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
-					end
-					if reason.extra then
-						stanza:add_child(reason.extra);
-					end
-					log("debug", "Disconnecting client, <stream:error> is: %s", tostring(stanza));
-					session.send(stanza);
-				elseif reason.name then -- a stanza
-					log("debug", "Disconnecting client, <stream:error> is: %s", tostring(reason));
-					session.send(reason);
-				end
-			end
-		end
-		session.send("</stream:stream>");
-		function session.send() return false; end
-
-		local reason = (reason and (reason.text or reason.condition)) or reason;
-		session.log("info", "c2s stream for %s closed: %s", session.full_jid or ("<"..session.ip..">"), reason or "session closed");
-
-		-- Authenticated incoming stream may still be sending us stanzas, so wait for </stream:stream> from remote
-		local conn = session.conn;
-		if reason == nil and not session.notopen and session.type == "c2s" then
-			-- Grace time to process data from authenticated cleanly-closed stream
-			add_task(stream_close_timeout, function ()
-				if not session.destroyed then
-					session.log("warn", "Failed to receive a stream close response, closing connection anyway...");
-					sm_destroy_session(session, reason);
-					conn:close();
-				end
-			end);
-		else
-			sm_destroy_session(session, reason);
-			conn:close();
-		end
-	end
-end
-
-module:hook_global("user-deleted", function(event)
-	local username, host = event.username, event.host;
-	local user = hosts[host].sessions[username];
-	if user and user.sessions then
-		for jid, session in pairs(user.sessions) do
-			session:close{ condition = "not-authorized", text = "Account deleted" };
-		end
-	end
-end, 200);
-
---- Port listener
-function listener.onconnect(conn)
-	local session = sm_new_session(conn);
-	sessions[conn] = session;
-
-	session.log("info", "Client connected");
-
-	-- Client is using legacy SSL (otherwise mod_tls sets this flag)
-	if conn:ssl() then
-		session.secure = true;
-	end
-
-	if opt_keepalives then
-		conn:setoption("keepalive", opt_keepalives);
-	end
-
-	session.close = session_close;
-
-	session.conn.starttls = nil;
-
-	local stream = new_xmpp_stream(session, stream_callbacks);
-	session.stream = stream;
-	session.notopen = true;
-
-	function session.reset_stream()
-		session.notopen = true;
-		session.stream:reset();
+	if not wants_xmpp then
+		return 501;
 	end
 
 	local function websocket_close(code, message)
-		local data = string.char(code/0x100) .. string.char(code%0x100) .. message;
+		local data = string.char(rshift(code, 8)) .. string.char(code%0x100) .. message;
 		conn:write(build_frame({opcode = 0x8, FIN = true, data = data}));
 		conn:close();
 	end
 
-	local filter = session.filter;
 	local dataBuffer;
 	local function handle_frame(frame)
 		module:log("debug", "Websocket received: %s (%i bytes)", frame.data, #frame.data);
@@ -365,115 +187,58 @@
 			dataBuffer = frame.data;
 		elseif frame.opcode == 0x2 then -- Binary frame
 			websocket_close(1003, "Only text frames are supported");
-			return false;
+			return;
 		elseif frame.opcode == 0x8 then -- Close request
 			websocket_close(1000, "Goodbye");
-			return false;
+			return;
 		elseif frame.opcode == 0x9 then -- Ping frame
 			frame.opcode = 0xA;
 			conn:write(build_frame(frame));
-			return true;
+			return "";
 		else
 			log("warn", "Received frame with unsupported opcode %i", frame.opcode);
-			return true;
+			return "";
 		end
 
 		if frame.FIN then
 			data = dataBuffer;
 			dataBuffer = nil;
 
-			-- COMPAT: Some current client implementations send a self-closing <stream:stream>
-			data, session.self_closing_stream = data:gsub("^(<stream:stream.*)/>$", "%1>");
-			session.self_closing_stream = (session.self_closing_stream == 1)
-
-			data = filter("bytes/in", data);
-			if data then
-				local ok, err = stream:feed(data);
-				if ok then return; end
-				log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
-				session:close("not-well-formed");
-			end
+			return data;
 		end
-		return true;
+		return "";
 	end
 
+	conn:setlistener(c2s_listener);
+	c2s_listener.onconnect(conn);
+
 	local frameBuffer = "";
-	function session.data(data)
+	add_filter(sessions[conn], "bytes/in", function(data)
+		local cache = "";
 		frameBuffer = frameBuffer .. data;
 		local frame, length = parse_frame(frameBuffer);
 
 		while frame do
 			frameBuffer = frameBuffer:sub(length + 1);
-			if not handle_frame(frame) then return; end
+			local result = handle_frame(frame);
+			if not result then return; end
+			cache = cache .. result;
 			frame, length = parse_frame(frameBuffer);
 		end
-	end
-
-	function session.send(s)
-		conn:write(build_frame({ FIN = true, opcode = 0x01, data = tostring(s)}));
-	end
-
-	if c2s_timeout then
-		add_task(c2s_timeout, function ()
-			if session.type == "c2s_unauthed" then
-				session:close("connection-timeout");
-			end
-		end);
-	end
-
-	session.dispatch_stanza = stream_callbacks.handlestanza;
-end
+		return cache;
 
-function listener.onincoming(conn, data)
-	local session = sessions[conn];
-	if session then
-		session.data(data);
-	else
-		listener.onconnect(conn, data);
-		session = sessions[conn];
-		session.data(data);
-	end
-end
-
-function listener.ondisconnect(conn, err)
-	local session = sessions[conn];
-	if session then
-		(session.log or log)("info", "Client disconnected: %s", err or "connection closed");
-		sm_destroy_session(session, err);
-		sessions[conn]  = nil;
-	end
-end
-
-function listener.associate_session(conn, session)
-	sessions[conn] = session;
-end
-
-function handle_request(event, path)
-	local request, response = event.request, event.response;
-
-	if not request.headers.sec_websocket_key then
-		response.headers.content_type = "text/html";
-		return [[<!DOCTYPE html><html><head><title>Websocket</title></head><body>
-			<p>It works! Now point your WebSocket client to this URL to connect to Prosody.</p>
-			</body></html>]];
-	end
-
-	local wants_xmpp = false;
-	(request.headers.sec_websocket_protocol or ""):gsub("([^,]*),?", function (proto)
-		if proto == "xmpp" then wants_xmpp = true; end
 	end);
 
-	if not wants_xmpp then
-		return 501;
-	end
+	add_filter(sessions[conn], "bytes/out", function(data)
+		return build_frame({ FIN = true, opcode = 0x01, data = tostring(data)});
+	end);
 
-	response.conn:setlistener(listener);
 	response.status = "101 Switching Protocols";
-	response.headers.Upgrade = "websocket";
-	response.headers.Connection = "Upgrade";
-	response.headers.Sec_WebSocket_Accept = base64(sha1(request.headers.sec_websocket_key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
-	response.headers.Sec_WebSocket_Protocol = "xmpp";
-	response.headers.Access_Control_Allow_Origin = cross_domain;
+	response.headers.upgrade = "websocket";
+	response.headers.connection = "Upgrade";
+	response.headers.sec_webSocket_accept = base64(sha1(request.headers.sec_websocket_key .. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
+	response.headers.sec_webSocket_protocol = "xmpp";
+	response.headers.access_control_allow_origin = cross_domain;
 
 	return "";
 end
@@ -481,7 +246,8 @@
 function module.add_host(module)
 	module:depends("http");
 	module:provides("http", {
-		name = "xmpp-websocket";
+		name = "websocket";
+		default_path = "xmpp-websocket";
 		route = {
 			["GET"] = handle_request;
 			["GET /"] = handle_request;