Changeset

399:82ad158714e5

Merge with Zash
author Matthew Wild <mwild1@gmail.com>
date Tue, 12 Jan 2016 13:14:36 +0000
parents 378:6042c938e369 (current diff) 398:b4ce2e524ed8 (diff)
children 400:0db9cb909cf1 401:7be4ebefd1f4
files init.lua squishy
diffstat 30 files changed, 373 insertions(+), 207 deletions(-) [+]
line wrap: on
line diff
--- a/init.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/init.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -25,7 +25,7 @@
 	for i=1,select("#", ...) do
 		local ok, err = pcall(require, "verse."..select(i,...));
 		if not ok then
-			error("Verse connection module not found: verse."..select(i,...).."\n"..err);
+			error("Verse connection module not found: verse."..select(i,...)..err);
 		end
 	end
 	return verse;
@@ -119,6 +119,7 @@
 	-- Create and initiate connection
 	local conn = socket.tcp()
 	conn:settimeout(0);
+	conn:setoption("keepalive", true);
 	local success, err = conn:connect(connect_host, connect_port);
 	
 	if not success and err ~= "timeout" then
--- a/libs/adhoc.lib.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/libs/adhoc.lib.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -12,7 +12,7 @@
 
 local _M = {};
 
-function _cmdtag(desc, status, sessionid, action)
+local function _cmdtag(desc, status, sessionid, action)
 	local cmd = st.stanza("command", { xmlns = xmlns_cmd, node = desc.node, status = status });
 	if sessionid then cmd.attr.sessionid = sessionid; end
 	if action then cmd.attr.action = action; end
@@ -35,6 +35,7 @@
 	local data, state = command:handler(dataIn, states[sessionid]);
 	states[sessionid] = state;
 	local stanza = st.reply(stanza);
+	local cmdtag;
 	if data.status == "completed" then
 		states[sessionid] = nil;
 		cmdtag = command:cmdtag("completed", sessionid);
--- a/libs/encodings.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/libs/encodings.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -7,6 +7,6 @@
 module "encodings"
 
 stringprep = {};
-base64 = { encode = mime.b64, decode = not_impl }; --mime.unb64 is buggy with \0
+base64 = { encode = mime.b64, decode = mime.unb64 };
 
 return _M;
--- a/libs/hashes.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/libs/hashes.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -1,3 +1,58 @@
-local sha1 = require "util.sha1";
+local have_luacrypto, crypto = pcall(require, "crypto");
+
+if have_luacrypto then
+	local hashes = {};
+
+	local digest = crypto.digest;
+	local function gethash(algo)
+		return function (string, hex)
+			return digest(algo, string, not hex);
+		end
+	end
+
+	local hmac = crypto.hmac.digest;
+	local function gethmac(algo)
+		return function (key, message, hex)
+			return hmac(algo, message, key, not hex);
+		end
+	end
+
+	local hash_algos = { "md5", "sha1", "sha256", "sha512" };
+
+	for _, hash_algo in ipairs(hash_algos) do
+		hashes[hash_algo] = gethash(hash_algo);
+		hashes["hmac_"..hash_algo] = gethmac(hash_algo);
+	end
 
-return { sha1 = sha1.sha1 };
+	return hashes;
+else
+	local sha1 = require"util.sha1".sha1;
+	local bxor = require"bit".bxor;
+
+	local s_rep = string.rep;
+	local s_char = string.char;
+	local s_byte = string.byte;
+	local t_concat = table.concat;
+
+	local function hmac_sha1(key, message, hexres)
+		if #key > 64 then
+			key = sha1(key);
+		elseif #key < 64 then
+			key = key .. s_rep("\0", 64 - #key);
+		end
+		local o_key_pad, i_key_pad = {}, {}
+		for i = 1, 64 do
+			local b = s_byte(key, i)
+			o_key_pad[i] = s_char(bxor(b, 0x5c));
+			i_key_pad[i] = s_char(bxor(b, 0x36));
+		end
+		o_key_pad = t_concat(o_key_pad);
+		i_key_pad = t_concat(i_key_pad);
+		return sha1(o_key_pad .. sha1(i_key_pad .. message), hexres);
+	end
+
+	return {
+		sha1 = sha1;
+		hmac_sha1 = hmac_sha1;
+	};
+end
--- a/plugins/adhoc.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/adhoc.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -25,15 +25,15 @@
 			return callback(command_list);
 		end);
 	end
-	
+
 	function stream:execute_command(jid, command, callback)
 		local cmd = setmetatable({
 			stream = stream, jid = jid,
-			command = command, callback = callback 
+			command = command, callback = callback
 		}, command_mt);
 		return cmd:execute();
 	end
-	
+
 	-- ACL checker for commands we provide
 	local function has_affiliation(jid, aff)
 		if not(aff) or aff == "user" then return true; end
@@ -42,31 +42,31 @@
 		end
 		-- TODO: Support 'roster', etc.
 	end
-	
+
 	function stream:add_adhoc_command(name, node, handler, permission)
 		commands[node] = adhoc.new(name, node, handler, permission);
 		stream:add_disco_item({ jid = stream.jid, node = node, name = name }, xmlns_commands);
 		return commands[node];
 	end
-	
+
 	local function handle_command(stanza)
 		local command_tag = stanza.tags[1];
 		local node = command_tag.attr.node;
-		
+
 		local handler = commands[node];
 		if not handler then return; end
-		
+
 		if not has_affiliation(stanza.attr.from, handler.permission) then
 			stream:send(verse.error_reply(stanza, "auth", "forbidden", "You don't have permission to execute this command"):up()
 			:add_child(handler:cmdtag("canceled")
 				:tag("note", {type="error"}):text("You don't have permission to execute this command")));
 			return true
 		end
-		
+
 		-- User has permission now execute the command
 		return adhoc.handle_cmd(handler, { send = function (d) return stream:send(d) end }, stanza);
 	end
-	
+
 	stream:hook("iq/"..xmlns_commands, function (stanza)
 		local type = stanza.attr.type;
 		local name = stanza.tags[1].name;
@@ -106,9 +106,9 @@
 			node = self.command,
 			sessionid = self.sessionid
 		});
-	
+
 	if form then iq:add_child(form); end
-	
+
 	self.stream:send_iq(iq, function (result)
 		self:_process_response(result);
 	end);
--- a/plugins/archive.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/archive.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -27,7 +27,7 @@
 		local query_st = st.iq{ type="set", to = where }
 			:tag("query", { xmlns = xmlns_mam, queryid = queryid });
 
-		
+
 		local qstart, qend = tonumber(query_params["start"]), tonumber(query_params["end"]);
 		query_params["start"] = qstart and datetime(qstart);
 		query_params["end"] = qend and datetime(qend);
--- a/plugins/compression.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/compression.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -1,6 +1,6 @@
 -- Copyright (C) 2009-2010 Matthew Wild
 -- Copyright (C) 2009-2010 Tobias Markmann
--- 
+--
 -- This project is MIT/X11 licensed. Please see the
 -- COPYING file in the source package for more information.
 --
@@ -53,7 +53,7 @@
 				return;
 			end
 			session.conn:write(compressed);
-		end;	
+		end;
 end
 
 -- setup decompression for a stream
@@ -101,16 +101,16 @@
 			-- create deflate and inflate streams
 			local deflate_stream = get_deflate_stream(stream);
 			if not deflate_stream then return end
-			
+
 			local inflate_stream = get_inflate_stream(stream);
 			if not inflate_stream then return end
-			
+
 			-- setup compression for stream.w
 			setup_compression(stream, deflate_stream);
-				
+
 			-- setup decompression for stream.data
 			setup_decompression(stream, inflate_stream);
-			
+
 			stream.compressed = true;
 			stream:reopen();
 		elseif stanza.name == "failure" then
--- a/plugins/disco.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/disco.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -1,14 +1,14 @@
 -- Verse XMPP Library
 -- Copyright (C) 2010 Hubert Chathi <hubert@uhoreg.ca>
 -- Copyright (C) 2010 Matthew Wild <mwild1@gmail.com>
--- 
+--
 -- This project is MIT/X11 licensed. Please see the
 -- COPYING file in the source package for more information.
 --
 
 local verse = require "verse";
 local b64 = require("mime").b64;
-local sha1 = require("util.sha1").sha1;
+local sha1 = require("util.hashes").sha1;
 
 local xmlns_caps = "http://jabber.org/protocol/caps";
 local xmlns_disco = "http://jabber.org/protocol/disco";
@@ -118,7 +118,7 @@
 			})
 		end
 	})
-	
+
 	function stream:set_identity(identity, node)
 		self.disco.info[node or false].identities = { identity };
 		stream:resend_presence();
@@ -135,7 +135,7 @@
 		self.disco.info[node or false].features[feature] = true;
 		stream:resend_presence();
 	end
-	
+
 	function stream:remove_disco_feature(feature, node)
 		local feature = feature.var or feature;
 		self.disco.info[node or false].features[feature] = nil;
@@ -181,13 +181,13 @@
 		end
 		return cached_disco.features[feature] or false;
 	end
-	
+
 	function stream:get_local_services(category, type)
 		local host_disco = self.disco.cache[self.host];
 		if not(host_disco) or not(host_disco.items) then
 			return nil, "no-cache";
 		end
-		
+
 		local results = {};
 		for _, service in ipairs(host_disco.items) do
 			if self:jid_has_identity(service.jid, category, type) then
@@ -196,7 +196,7 @@
 		end
 		return results;
 	end
-	
+
 	function stream:disco_local_services(callback)
 		self:disco_items(self.host, nil, function (items)
 			if not items then
@@ -209,7 +209,7 @@
 					return callback(items);
 				end
 			end
-			
+
 			for _, item in ipairs(items) do
 				if item.jid then
 					n_items = n_items + 1;
@@ -221,7 +221,7 @@
 			end
 		end);
 	end
-	
+
 	function stream:disco_info(jid, node, callback)
 		local disco_request = verse.iq({ to = jid, type = "get" })
 			:tag("query", { xmlns = xmlns_disco_info, node = node });
@@ -229,9 +229,9 @@
 			if result.attr.type == "error" then
 				return callback(nil, result:get_error());
 			end
-			
+
 			local identities, features = {}, {};
-			
+
 			for tag in result:get_child("query", xmlns_disco_info):childtags() do
 				if tag.name == "identity" then
 					identities[tag.attr.category.."/"..tag.attr.type] = tag.attr.name or true;
@@ -239,7 +239,7 @@
 					features[tag.attr.var] = true;
 				end
 			end
-			
+
 
 			if not self.disco.cache[jid] then
 				self.disco.cache[jid] = { nodes = {} };
@@ -258,7 +258,7 @@
 			return callback(self.disco.cache[jid]);
 		end);
 	end
-	
+
 	function stream:disco_items(jid, node, callback)
 		local disco_request = verse.iq({ to = jid, type = "get" })
 			:tag("query", { xmlns = xmlns_disco_items, node = node });
@@ -276,11 +276,11 @@
 					});
 				end
 			end
-			
+
 			if not self.disco.cache[jid] then
 				self.disco.cache[jid] = { nodes = {} };
 			end
-			
+
 			if node then
 				if not self.disco.cache[jid].nodes[node] then
 					self.disco.cache[jid].nodes[node] = { nodes = {} };
@@ -292,7 +292,7 @@
 			return callback(disco_items);
 		end);
 	end
-	
+
 	stream:hook("iq/"..xmlns_disco_info, function (stanza)
 		local query = stanza.tags[1];
 		if stanza.attr.type == 'get' and query.name == "query" then
@@ -337,7 +337,7 @@
 			return true
 		end
 	end);
-	
+
 	local initial_disco_started;
 	stream:hook("ready", function ()
 		if initial_disco_started then return; end
@@ -358,7 +358,7 @@
 		end);
 		return true;
 	end, 50);
-	
+
 	stream:hook("presence-out", function (presence)
 		if not presence:get_child("c", xmlns_caps) then
 			presence:reset():add_child(stream:caps()):reset();
--- a/plugins/groupchat.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/groupchat.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -11,7 +11,7 @@
 function verse.plugins.groupchat(stream)
 	stream:add_plugin("presence")
 	stream.rooms = {};
-	
+
 	stream:hook("stanza", function (stanza)
 		local room_jid = jid.bare(stanza.attr.from);
 		if not room_jid then return end
@@ -37,7 +37,7 @@
 			return ret or (stanza.name == "message") or nil;
 		end
 	end, 500);
-	
+
 	function stream:join_room(jid, nick, opts)
 		if not nick then
 			return false, "no nickname supplied"
--- a/plugins/jingle.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/jingle.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -1,5 +1,4 @@
 local verse = require "verse";
-local sha1 = require "util.sha1".sha1;
 local timer = require "util.timer";
 local uuid_generate = require "util.uuid".generate;
 
@@ -16,7 +15,7 @@
 	stream:hook("ready", function ()
 		stream:add_disco_feature(xmlns_jingle);
 	end, 10);
-	
+
 	function stream:jingle(to)
 		return verse.eventable(setmetatable(base or {
 			role = "initiator";
@@ -25,20 +24,20 @@
 			stream = stream;
 		}, jingle_mt));
 	end
-	
+
 	function stream:register_jingle_transport(transport)
 		-- transport is a function that receives a
 		-- <transport> element, and returns a connection
 		-- We wait for 'connected' on that connection,
 		-- and use :send() and 'incoming-raw'.
 	end
-	
+
 	function stream:register_jingle_content_type(content)
 		-- Call content() for every 'incoming-raw'?
 		-- I think content() returns the object we return
 		-- on jingle:accept()
 	end
-	
+
 	local function handle_incoming_jingle(stanza)
 		local jingle_tag = stanza:get_child("jingle", xmlns_jingle);
 		local sid = jingle_tag.attr.sid;
@@ -57,21 +56,21 @@
 			stream:send(reply);
 			return;
 		end
-		
+
 		-- Ok, session-initiate, new session
-		
+
 		-- Create new Jingle object
 		local sid = jingle_tag.attr.sid;
-		
+
 		local jingle = verse.eventable{
 			role = "receiver";
 			peer = stanza.attr.from;
 			sid = sid;
 			stream = stream;
 		};
-		
+
 		setmetatable(jingle, jingle_mt);
-		
+
 		local content_tag;
 		local content, transport;
 		for tag in jingle_tag:childtags() do
@@ -84,10 +83,10 @@
 			 			content = desc_handler;
 			 		end
 			 	end
-				
+
 				local transport_tag = tag:child_with_name("transport");
 				local transport_xmlns = transport_tag.attr.xmlns;
-				
+
 				transport = stream:event("jingle/transport/"..transport_xmlns, jingle, transport_tag);
 				if content and transport then
 					content_tag = tag;
@@ -100,23 +99,23 @@
 			stream:send(verse.error_reply(stanza, "cancel", "feature-not-implemented", "The specified content is not supported"));
 			return true;
 		end
-		
+
 		if not transport then
 			-- FIXME: Refuse session, no transport
 			stream:send(verse.error_reply(stanza, "cancel", "feature-not-implemented", "The specified transport is not supported"));
 			return true;
 		end
-		
+
 		stream:send(verse.reply(stanza));
-		
+
 		jingle.content_tag = content_tag;
 		jingle.creator, jingle.name = content_tag.attr.creator, content_tag.attr.name;
 		jingle.content, jingle.transport = content, transport;
-		
+
 		function jingle:decline()
 			-- FIXME: Decline session
 		end
-		
+
 		stream:hook("jingle/"..sid, function (stanza)
 			if stanza.attr.from ~= jingle.peer then
 				return false;
@@ -124,11 +123,11 @@
 			local jingle_tag = stanza:get_child("jingle", xmlns_jingle);
 			return jingle:handle_command(jingle_tag);
 		end);
-		
+
 		stream:event("jingle", jingle);
 		return true;
 	end
-	
+
 	function jingle_mt:handle_command(jingle_tag)
 		local action = jingle_tag.attr.action;
 		stream:debug("Handling Jingle command: %s", action);
@@ -166,7 +165,7 @@
 			self.stream:send_iq(stanza, callback);
 		end
 	end
-		
+
 	function jingle_mt:accept(options)
 		local accept_stanza = verse.iq({ to = self.peer, type = "set" })
 			:tag("jingle", {
@@ -176,13 +175,13 @@
 				responder = stream.jid,
 			})
 				:tag("content", { creator = self.creator, name = self.name });
-		
+
 		local content_accept_tag = self.content:generate_accept(self.content_tag:child_with_name("description"), options);
 		accept_stanza:add_child(content_accept_tag);
-		
+
 		local transport_accept_tag = self.transport:generate_accept(self.content_tag:child_with_name("transport"), options);
 		accept_stanza:add_child(transport_accept_tag);
-		
+
 		local jingle = self;
 		stream:send_iq(accept_stanza, function (result)
 			if result.attr.type == "error" then
@@ -197,7 +196,7 @@
 			end);
 		end);
 	end
-	
+
 
 	stream:hook("iq/"..xmlns_jingle, handle_incoming_jingle);
 	return true;
@@ -207,26 +206,26 @@
 	local session_initiate = verse.iq({ to = self.peer, type = "set" })
 		:tag("jingle", { xmlns = xmlns_jingle, action = "session-initiate",
 			initiator = self.stream.jid, sid = self.sid });
-	
+
 	-- Content tag
 	session_initiate:tag("content", { creator = self.role, name = name });
-	
+
 	-- Need description element from someone who can turn 'content' into XML
 	local description = self.stream:event("jingle/describe/"..name, content);
-	
+
 	if not description then
 		return false, "Unknown content type";
 	end
-	
+
 	session_initiate:add_child(description);
-	
+
 	-- FIXME: Sort transports by 1) recipient caps 2) priority (SOCKS vs IBB, etc.)
 	-- Fixed to s5b in the meantime
 	local transport = self.stream:event("jingle/transport/".."urn:xmpp:jingle:transports:s5b:1", self);
 	self.transport = transport;
-	
+
 	session_initiate:add_child(transport:generate_initiate());
-	
+
 	self.stream:debug("Hooking %s", "jingle/"..self.sid);
 	self.stream:hook("jingle/"..self.sid, function (stanza)
 		if stanza.attr.from ~= self.peer then
@@ -235,7 +234,7 @@
 		local jingle_tag = stanza:get_child("jingle", xmlns_jingle);
 		return self:handle_command(jingle_tag)
 	end);
-	
+
 	self.stream:send_iq(session_initiate, function (result)
 		if result.attr.type == "error" then
 			self.state = "terminated";
--- a/plugins/jingle_ft.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/jingle_ft.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -3,16 +3,15 @@
 
 local dirsep = package.config:sub(1,1);
 
-local xmlns_jingle_ft = "urn:xmpp:jingle:apps:file-transfer:1";
-local xmlns_si_file_transfer = "http://jabber.org/protocol/si/profile/file-transfer";
+local xmlns_jingle_ft = "urn:xmpp:jingle:apps:file-transfer:4";
 
 function verse.plugins.jingle_ft(stream)
 	stream:hook("ready", function ()
 		stream:add_disco_feature(xmlns_jingle_ft);
 	end, 10);
-	
+
 	local ft_content = { type = "file" };
-	
+
 	function ft_content:generate_accept(description, options)
 		if options and options.save_file then
 			self.jingle:hook("connected", function ()
@@ -20,21 +19,23 @@
 				self.jingle:set_sink(sink);
 			end);
 		end
-		
+
 		return description;
 	end
-	
+
 	local ft_mt = { __index = ft_content };
 	stream:hook("jingle/content/"..xmlns_jingle_ft, function (jingle, description_tag)
-		local file_tag = description_tag:get_child("offer"):get_child("file", xmlns_si_file_transfer);
+		local file_tag = description_tag:get_child("file");
 		local file = {
-			name = file_tag.attr.name;
-			size = tonumber(file_tag.attr.size);
+			name = file_tag:get_child_text("name");
+			size = tonumber(file_tag:get_child_text("size"));
+			desc = file_tag:get_child_text("desc");
+			date = file_tag:get_child_text("date");
 		};
-		
+
 		return setmetatable({ jingle = jingle, file = file }, ft_mt);
 	end);
-	
+
 	stream:hook("jingle/describe/file", function (file_info)
 		-- Return <description/>
 		local date;
@@ -42,25 +43,23 @@
 			date = os.date("!%Y-%m-%dT%H:%M:%SZ", file_info.timestamp);
 		end
 		return verse.stanza("description", { xmlns = xmlns_jingle_ft })
-			:tag("offer")
-				:tag("file", { xmlns = xmlns_si_file_transfer,
-					name = file_info.filename, -- Mandatory
-					size = file_info.size, -- Mandatory
-					date = date,
-					hash = file_info.hash,
-				})
-					:tag("desc"):text(file_info.description or "");
+			:tag("file")
+				:tag("name"):text(file_info.filename):up()
+				:tag("size"):text(tostring(file_info.size)):up()
+				:tag("date"):text(date):up()
+				:tag("desc"):text(file_info.description):up()
+			:up();
 	end);
 
 	function stream:send_file(to, filename)
 		local file, err = io.open(filename);
 		if not file then return file, err; end
-		
+
 		local file_size = file:seek("end", 0);
 		file:seek("set", 0);
-		
+
 		local source = ltn12.source.file(file);
-		
+
 		local jingle = self:jingle(to);
 		jingle:offer("file", {
 			filename = filename:match("[^"..dirsep.."]+$");
--- a/plugins/jingle_s5b.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/jingle_s5b.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -2,7 +2,7 @@
 
 local xmlns_s5b = "urn:xmpp:jingle:transports:s5b:1";
 local xmlns_bytestreams = "http://jabber.org/protocol/bytestreams";
-local sha1 = require "util.sha1".sha1;
+local sha1 = require "util.hashes".sha1;
 local uuid_generate = require "util.uuid".generate;
 
 local function negotiate_socks5(conn, hash)
@@ -12,7 +12,7 @@
 	end
 	local function receive_connection_response(data)
 		conn:unhook("incoming-raw", receive_connection_response);
-		
+
 		if data:sub(1, 2) ~= "\005\000" then
 			return conn:event("error", "connection-failure");
 		end
@@ -47,7 +47,7 @@
 	--Attempt to connect to the next host
 	local function attempt_next_streamhost(event)
 		if event then
-			return callback(nil, event.reason); 
+			return callback(nil, event.reason);
 		end
 		-- First connect, or the last connect failed
 		if conn.current_host < #conn.streamhosts then
@@ -58,7 +58,7 @@
 				conn.streamhosts[conn.current_host].port
 			);
 			if not ok then
-				conn:debug("Error connecting to proxy (%s:%s): %s", 
+				conn:debug("Error connecting to proxy (%s:%s): %s",
 					conn.streamhosts[conn.current_host].host,
 					conn.streamhosts[conn.current_host].port,
 					err
@@ -90,7 +90,7 @@
 	end, 10);
 
 	local s5b = {};
-	
+
 	function s5b:generate_initiate()
 		self.s5b_sid = uuid_generate();
 		local transport = verse.stanza("transport", { xmlns = xmlns_s5b,
@@ -104,13 +104,13 @@
 		stream:debug("Have %d proxies", p)
 		return transport;
 	end
-	
+
 	function s5b:generate_accept(initiate_transport)
 		local candidates = {};
 		self.s5b_peer_candidates = candidates;
 		self.s5b_mode = initiate_transport.attr.mode or "tcp";
 		self.s5b_sid = initiate_transport.attr.sid or self.jingle.sid;
-		
+
 		-- Import the list of candidates the initiator offered us
 		for candidate in initiate_transport:childtags() do
 			--if candidate.attr.jid == "asterix4@jabber.lagaule.org/Gajim"
@@ -125,21 +125,21 @@
 				};
 			--end
 		end
-		
+
 		-- Import our own candidates
 		-- TODO ^
 		local transport = verse.stanza("transport", { xmlns = xmlns_s5b });
 		return transport;
 	end
-	
+
 	function s5b:connect(callback)
 		stream:warn("Connecting!");
-		
+
 		local streamhost_array = {};
 		for cid, streamhost in pairs(self.s5b_peer_candidates or {}) do
 			streamhost_array[#streamhost_array+1] = streamhost;
 		end
-		
+
 		if #streamhost_array > 0 then
 			self.connecting_peer_candidates = true;
 			local function onconnect(streamhost, conn)
@@ -156,7 +156,7 @@
 			self.onconnect_callback = callback;
 		end
 	end
-	
+
 	function s5b:info_received(jingle_tag)
 		stream:warn("Info received");
 		local content_tag = jingle_tag:child_with_name("content");
@@ -171,7 +171,7 @@
 						self.jingle.stream:send_iq(verse.iq({ to = streamhost.jid, type = "set" })
 							:tag("query", { xmlns = xmlns_bytestreams, sid = self.s5b_sid })
 								:tag("activate"):text(self.jingle.peer), function (result)
-							
+
 							if result.attr.type == "result" then
 								self.jingle:send_command("transport-info", verse.stanza("content", content_tag.attr)
 									:tag("transport", { xmlns = xmlns_s5b, sid = self.s5b_sid })
@@ -184,7 +184,7 @@
 						end);
 					end
 				end
-				
+
 				-- FIXME: Another assumption that cid==jid, and that it was our candidate
 				self.jingle.stream:debug("CID: %s", self.jingle.stream.proxy65.available_streamhosts[candidate_used.attr.cid]);
 				local streamhost_array = {
@@ -198,13 +198,13 @@
 			self.onconnect_callback(self.conn);
 		end
 	end
-	
+
 	function s5b:disconnect()
 		if self.conn then
 			self.conn:close();
 		end
 	end
-	
+
 	function s5b:handle_accepted(jingle_tag)
 	end
 
--- a/plugins/legacy.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/legacy.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -4,7 +4,7 @@
 local xmlns_auth = "jabber:iq:auth";
 
 function verse.plugins.legacy(stream)
-	function handle_auth_form(result)
+	local function handle_auth_form(result)
 		local query = result:get_child("query", xmlns_auth);
 		if result.attr.type ~= "result" or not query then
 			local type, cond, text = result:get_error();
@@ -51,14 +51,13 @@
 			end
 		end);
 	end
-	
-	function handle_opened(attr)
+
+	local function handle_opened(attr)
 		if not attr.version then
 			stream:send_iq(verse.iq({type="get"})
 				:tag("query", { xmlns = "jabber:iq:auth" })
 					:tag("username"):text(stream.username),
 				handle_auth_form);
-				
 		end
 	end
 	stream:hook("opened", handle_opened);
--- a/plugins/pep.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/pep.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -7,11 +7,11 @@
 	stream:add_plugin("disco");
 	stream:add_plugin("pubsub");
 	stream.pep = {};
-	
+
 	stream:hook("pubsub/event", function(event)
 		return stream:event("pep/"..event.node, { from = event.from, item = event.item.tags[1] } );
 	end);
-	
+
 	function stream:hook_pep(node, callback, priority)
 		local handlers = stream.events._handlers["pep/"..node];
 		if not(handlers) or #handlers == 0 then
@@ -19,7 +19,7 @@
 		end
 		stream:hook("pep/"..node, callback, priority);
 	end
-	
+
 	function stream:unhook_pep(node, callback)
 		stream:unhook("pep/"..node, callback);
 		local handlers = stream.events._handlers["pep/"..node];
@@ -27,7 +27,7 @@
 			stream:remove_disco_feature(node.."+notify");
 		end
 	end
-	
+
 	function stream:publish_pep(item, node)
 		return stream.pubsub:service(nil):node(node or item.attr.xmlns):publish(nil, nil, item)
 	end
--- a/plugins/ping.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/ping.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -1,11 +1,12 @@
 local verse = require "verse";
+local gettime = require"socket".gettime;
 
 local xmlns_ping = "urn:xmpp:ping";
 
 function verse.plugins.ping(stream)
 	function stream:ping(jid, callback)
-		local t = socket.gettime();
-		stream:send_iq(verse.iq{ to = jid, type = "get" }:tag("ping", { xmlns = xmlns_ping }), 
+		local t = gettime();
+		stream:send_iq(verse.iq{ to = jid, type = "get" }:tag("ping", { xmlns = xmlns_ping }),
 			function (reply)
 				if reply.attr.type == "error" then
 					local type, condition, text = reply:get_error();
@@ -14,7 +15,7 @@
 						return;
 					end
 				end
-				callback(socket.gettime()-t, jid);
+				callback(gettime()-t, jid);
 			end);
 	end
 	stream:hook("iq/"..xmlns_ping, function(stanza)
--- a/plugins/private.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/private.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -18,7 +18,7 @@
 		end
 		self:send_iq(iq, callback);
 	end
-	
+
 	function stream:private_get(name, xmlns, callback)
 		self:send_iq(verse.iq({type="get"})
 			:tag("query", { xmlns = xmlns_private })
--- a/plugins/proxy65.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/proxy65.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -1,6 +1,6 @@
-local events = require "util.events";
+local verse =	require "verse";
 local uuid = require "util.uuid";
-local sha1 = require "util.sha1";
+local sha1 = require "util.hashes".sha1;
 
 local proxy65_mt = {};
 proxy65_mt.__index = proxy65_mt;
@@ -19,12 +19,12 @@
 			outstanding_proxies = outstanding_proxies + 1;
 			stream:send_iq(verse.iq({ to = service.jid, type = "get" })
 				:tag("query", { xmlns = xmlns_bytestreams }), function (result)
-				
+
 				outstanding_proxies = outstanding_proxies - 1;
 				if result.attr.type == "result" then
 					local streamhost = result:get_child("query", xmlns_bytestreams)
 						:get_child("streamhost").attr;
-					
+
 					stream.proxy65.available_streamhosts[streamhost.jid] = {
 						jid = streamhost.jid;
 						host = streamhost.host;
@@ -43,14 +43,14 @@
 			streamhosts = {},
 			current_host = 0;
 		});
-		
+
 		-- Parse hosts from request
 		for tag in request.tags[1]:childtags() do
 			if tag.name == "streamhost" then
-				table.insert(conn.streamhosts, tag.attr);	
+				table.insert(conn.streamhosts, tag.attr);
 			end
 		end
-		
+
 		--Attempt to connect to the next host
 		local function attempt_next_streamhost()
 			-- First connect, or the last connect failed
@@ -68,7 +68,7 @@
 			stream:send(verse.error_reply(request, "cancel", "item-not-found"));
 			-- Let disconnected event fall through to user handlers...
 		end
-		
+
 		function conn:accept()
 			conn:hook("disconnected", attempt_next_streamhost, 100);
 			-- When this event fires, we're connected to a streamhost
@@ -94,14 +94,14 @@
 		target_jid = target_jid;
 		bytestream_sid = uuid.generate();
 	});
-	
+
 	local request = verse.iq{type="set", to = target_jid}
 		:tag("query", { xmlns = xmlns_bytestreams, mode = "tcp", sid = conn.bytestream_sid });
 	for _, proxy in ipairs(proxies or self.proxies) do
 		request:tag("streamhost", proxy):up();
 	end
-	
-	
+
+
 	self.stream:send_iq(request, function (reply)
 		if reply.attr.type == "error" then
 			local type, condition, text = reply:get_error();
@@ -109,9 +109,9 @@
 		else
 			-- Target connected to streamhost, connect ourselves
 			local streamhost_used = reply.tags[1]:get_child("streamhost-used");
-			if not streamhost_used then
+			-- if not streamhost_used then
 				--FIXME: Emit error
-			end
+			-- end
 			conn.streamhost_jid = streamhost_used.attr.jid;
 			local host, port;
 			for _, proxy in ipairs(proxies or self.proxies) do
@@ -120,24 +120,23 @@
 					break;
 				end
 			end
-			if not (host and port) then
+			-- if not (host and port) then
 				--FIXME: Emit error
-			end
-			
+			-- end
+
 			conn:connect(host, port);
 
 			local function handle_proxy_connected()
 				conn:unhook("connected", handle_proxy_connected);
 				-- Both of us connected, tell proxy to activate connection
-				local request = verse.iq{to = conn.streamhost_jid, type="set"}
+				local activate_request = verse.iq{to = conn.streamhost_jid, type="set"}
 					:tag("query", { xmlns = xmlns_bytestreams, sid = conn.bytestream_sid })
 						:tag("activate"):text(target_jid);
-				self.stream:send_iq(request, function (reply)
-					if reply.attr.type == "result" then
+				self.stream:send_iq(activate_request, function (activated)
+					if activated.attr.type == "result" then
 						-- Connection activated, ready to use
 						conn:event("connected", conn);
-					else
-						--FIXME: Emit error
+					-- else --FIXME: Emit error
 					end
 				end);
 				return true;
@@ -151,14 +150,14 @@
 end
 
 function negotiate_socks5(stream, conn, sid, requester_jid, target_jid)
-	local hash = sha1.sha1(sid..requester_jid..target_jid);
+	local hash = sha1(sid..requester_jid..target_jid);
 	local function suppress_connected()
 		conn:unhook("connected", suppress_connected);
 		return true;
 	end
 	local function receive_connection_response(data)
 		conn:unhook("incoming-raw", receive_connection_response);
-		
+
 		if data:sub(1, 2) ~= "\005\000" then
 			return conn:event("error", "connection-failure");
 		end
--- a/plugins/pubsub.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/pubsub.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -1,12 +1,11 @@
 local verse = require "verse";
-local jid_bare = require "util.jid".bare;
 
 local t_insert = table.insert;
 
 local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
 local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
 local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
-local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";
+-- local xmlns_pubsub_errors = "http://jabber.org/protocol/pubsub#errors";
 
 local pubsub = {};
 local pubsub_mt = { __index = pubsub };
@@ -213,7 +212,7 @@
 	if options ~= nil then
 		error("Subscription configuration is not implemented yet.");
 	end
-	self.stream:send_iq(pubsub_iq("set", self.service, nil, "subscribe", self.node, jid, id)
+	self.stream:send_iq(pubsub_iq("set", self.service, nil, "subscribe", self.node, jid)
 	, callback);
 end
 
--- a/plugins/roster.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/roster.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -24,7 +24,7 @@
 	local function item_lua2xml(item_table)
 		local xml_item = verse.stanza("item", { xmlns = xmlns_roster });
 		for k, v in pairs(item_table) do
-			if k ~= "groups" then 
+			if k ~= "groups" then
 				xml_item.attr[k] = v;
 			else
 				for i = 1,#v do
@@ -39,7 +39,6 @@
 		local item_table = { };
 		local groups = {};
 		item_table.groups = groups;
-		local jid = xml_item.attr.jid;
 
 		for k, v in pairs(xml_item.attr) do
 			if k ~= "xmlns" then
@@ -75,8 +74,7 @@
 			if reply.attr.type == "result" then
 				callback(true);
 			else
-				local type, condition, text = reply:get_error();
-				callback(nil, { type, condition, text });
+				callback(nil, reply);
 			end
 		end);
 	end
@@ -94,8 +92,7 @@
 				if reply.attr.type == "result" then
 					callback(true);
 				else
-					local type, condition, text = reply:get_error();
-					callback(nil, { type, condition, text });
+					callback(nil, reply);
 				end
 			end);
 	end
@@ -126,8 +123,7 @@
 					end
 					callback(roster);
 				else
-					local type, condition, text = stanza:get_error();
-					callback(nil, { type, condition, text }); --FIXME
+					callback(nil, result);
 				end
 			end);
 	end
--- a/plugins/sasl.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/sasl.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -1,4 +1,4 @@
--- local verse = require"verse";
+local verse = require"verse";
 local base64, unbase64 = require "mime".b64, require"mime".unb64;
 local xmlns_sasl = "urn:ietf:params:xml:ns:xmpp-sasl";
 
@@ -48,7 +48,7 @@
 		stream:send(auth_stanza);
 		return true;
 	end
-	
+
 	local function handle_sasl(sasl_stanza)
 		if sasl_stanza.name == "failure" then
 			local err = sasl_stanza.tags[1];
@@ -71,10 +71,10 @@
 		end
 		return true;
 	end
-	
+
 	stream:hook("stream-features", handle_features, 300);
 	stream:hook("stream/"..xmlns_sasl, handle_sasl);
-	
+
 	return true;
 end
 
--- a/plugins/session.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/session.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -3,7 +3,7 @@
 local xmlns_session = "urn:ietf:params:xml:ns:xmpp-session";
 
 function verse.plugins.session(stream)
-	
+
 	local function handle_features(features)
 		local session_feature = features:get_child("session", xmlns_session);
 		if session_feature and not session_feature:get_child("optional") then
@@ -14,7 +14,6 @@
 						if reply.attr.type == "result" then
 							stream:event("session-success");
 						elseif reply.attr.type == "error" then
-							local err = reply:child_with_name("error");
 							local type, condition, text = reply:get_error();
 							stream:event("session-failure", { error = condition, text = text, type = type });
 						end
@@ -25,6 +24,6 @@
 		end
 	end
 	stream:hook("stream-features", handle_features);
-	
+
 	return true;
 end
--- a/plugins/smacks.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/smacks.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -1,5 +1,5 @@
 local verse = require "verse";
-local now = socket.gettime;
+local now = require"socket".gettime;
 
 local xmlns_sm = "urn:xmpp:sm:2";
 
@@ -9,10 +9,10 @@
 	local last_ack = 0;
 	local last_stanza_time = now();
 	local timer_active;
-	
+
 	-- State for incoming stanzas
 	local handled_stanza_count = 0;
-	
+
 	-- Catch incoming stanzas
 	local function incoming_stanza(stanza)
 		if stanza.attr.xmlns == "jabber:client" or not stanza.attr.xmlns then
@@ -22,7 +22,7 @@
 	end
 
 	-- Catch outgoing stanzas
-	function outgoing_stanza(stanza)
+	local function outgoing_stanza(stanza)
 		-- NOTE: This will not behave nice if stanzas are serialized before this point
 		if stanza.name and not stanza.attr.xmlns then
 			-- serialize stanzas in order to bypass this on resumption
@@ -59,14 +59,14 @@
 			end);
 			return true;
 		end
-	end	
+	end
 
 	-- Graceful shutdown
 	local function on_close()
 		stream.resumption_token = nil;
 		stream:unhook("disconnected", on_disconnect);
 	end
-	
+
 	local function handle_sm_command(stanza)
 		if stanza.name == "r" then -- Request for acks for stanzas we received
 			stream:debug("Ack requested... acking %d handled stanzas", handled_stanza_count);
--- a/plugins/tls.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/tls.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -18,7 +18,7 @@
 	local function handle_tls(tls_status)
 		if tls_status.name == "proceed" then
 			stream:debug("Server says proceed, handshake starting...");
-			stream.conn:starttls({mode="client", protocol="sslv23", options="no_sslv2"}, true);
+			stream.conn:starttls(stream.ssl or {mode="client", protocol="sslv23", options="no_sslv2",capath="/etc/ssl/certs"}, true);
 		end
 	end
 	local function handle_status(new_status)
@@ -31,6 +31,6 @@
 	stream:hook("stream-features", handle_features, 400);
 	stream:hook("stream/"..xmlns_tls, handle_tls);
 	stream:hook("status", handle_status, 400);
-	
+
 	return true;
 end
--- a/plugins/vcard.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/vcard.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -7,8 +7,7 @@
 	function stream:get_vcard(jid, callback) --jid = nil for self
 		stream:send_iq(verse.iq({to = jid, type="get"})
 			:tag("vCard", {xmlns=xmlns_vcard}), callback and function(stanza)
-				local lCard, xCard;
-				vCard = stanza:get_child("vCard", xmlns_vcard);
+				local vCard = stanza:get_child("vCard", xmlns_vcard);
 				if stanza.attr.type == "result" and vCard then
 					vCard = vcard.from_xep54(vCard)
 					callback(vCard)
--- a/plugins/vcard_update.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/vcard_update.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -1,16 +1,9 @@
 local verse = require "verse";
 
-local xmlns_vcard, xmlns_vcard_update = "vcard-temp", "vcard-temp:x:update";
+-- local xmlns_vcard = "vcard-temp";
+local xmlns_vcard_update = "vcard-temp:x:update";
 
--- MMMmmmm.. hacky
-local ok, fun = pcall(function() return require("util.hashes").sha1; end);
-if not ok then
-	ok, fun = pcall(function() return require("util.sha1").sha1; end);
-	if not ok then
-		error("Could not find a sha1()")
-	end
-end
-local sha1 = fun;
+local sha1 = require("util.hashes").sha1;
 
 local ok, fun = pcall(function()
 	local unb64 = require("util.encodings").base64.decode;
@@ -32,7 +25,7 @@
 
 	local x_vcard_update;
 
-	function update_vcard_photo(vCard) 
+	local function update_vcard_photo(vCard)
 		local data;
 		for i=1,#vCard do
 			if vCard[i].name == "PHOTO" then
@@ -51,10 +44,10 @@
 		end
 	end
 
-	local _set_vcard = stream.set_vcard;
 
 	--[[ TODO Complete this, it's probably broken.
 	-- Maybe better to hook outgoing stanza?
+	local _set_vcard = stream.set_vcard;
 	function stream:set_vcard(vCard, callback)
 		_set_vcard(vCard, function(event, ...)
 			if event.attr.type == "result" then
@@ -71,7 +64,7 @@
 	--]]
 
 	local initial_vcard_fetch_started;
-	stream:hook("ready", function(event)
+	stream:hook("ready", function()
 		if initial_vcard_fetch_started then return; end
 		initial_vcard_fetch_started = true;
 		-- if stream:jid_supports(nil, xmlns_vcard) then TODO this, correctly
--- a/plugins/version.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/plugins/version.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -26,11 +26,11 @@
 		stream:send(reply);
 		return true;
 	end);
-	
+
 	function stream:query_version(target_jid, callback)
-		callback = callback or function (version) return stream:event("version/response", version); end
-		stream:send_iq(verse.iq({ type = "get", to = target_jid })
-			:tag("query", { xmlns = xmlns_version }), 
+		callback = callback or function (version) return self:event("version/response", version); end
+		self:send_iq(verse.iq({ type = "get", to = target_jid })
+			:tag("query", { xmlns = xmlns_version }),
 			function (reply)
 				if reply.attr.type == "result" then
 					local query = reply:get_child("query", xmlns_version);
--- a/squishy	Sat Jan 09 11:03:30 2016 +0000
+++ b/squishy	Tue Jan 12 13:14:36 2016 +0000
@@ -6,11 +6,9 @@
 Module "util.sha1"		"util/sha1.lua"
 Module "lib.adhoc"              "libs/adhoc.lib.lua"
 
-AutoFetchURL("http://hg.prosody.im/prosody-modules/raw-file/tip/mod_mam/?");
-Module "util.rsm"       "rsm.lib.lua"
 -- Prosody libraries
 if not GetOption("prosody") then
-	AutoFetchURL "http://hg.prosody.im/0.9/raw-file/tip/?"
+	AutoFetchURL "http://hg.prosody.im/0.9/raw-file/0.9.9/?"
 else
 	AutoFetchURL(GetOption("prosody").."/?")
 end
@@ -30,7 +28,11 @@
 Module "util.vcard"		"util/vcard.lua"
 Module "util.logger"		"util/logger.lua"
 Module "util.datetime"		"util/datetime.lua"
-Module "util.ip"		"util/ip.lua"
+Module "util.json"		"util/json.lua"
+Module "util.xml"		"util/xml.lua"
+Module "util.rsm"       "util/rsm.lua"
+Module "util.random"       "util/random.lua"
+Module "util.ip"       "util/ip.lua"
 
 Module "util.sasl.scram" "util/sasl/scram.lua"
 Module "util.sasl.plain" "util/sasl/plain.lua"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/random.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -0,0 +1,43 @@
+-- Prosody IM
+-- Copyright (C) 2008-2014 Matthew Wild
+-- Copyright (C) 2008-2014 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local tostring = tostring;
+local os_time = os.time;
+local os_clock = os.clock;
+local ceil = math.ceil;
+local H = require "util.hashes".sha1;
+
+local last_uniq_time = 0;
+local function uniq_time()
+	local new_uniq_time = os_time();
+	if last_uniq_time >= new_uniq_time then new_uniq_time = last_uniq_time + 1; end
+	last_uniq_time = new_uniq_time;
+	return new_uniq_time;
+end
+
+local function new_random(x)
+	return H(x..os_clock()..tostring({}));
+end
+
+local buffer = new_random(uniq_time());
+
+local function seed(x)
+	buffer = new_random(buffer..x);
+end
+
+local function bytes(n)
+	if #buffer < n+4 then seed(uniq_time()); end
+	local r = buffer:sub(1, n);
+	buffer = buffer:sub(n+1);
+	return r;
+end
+
+return {
+	seed = seed;
+	bytes = bytes;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/rsm.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -0,0 +1,87 @@
+local stanza = require"util.stanza".stanza;
+local tostring, tonumber = tostring, tonumber;
+local type = type;
+local pairs = pairs;
+
+local xmlns_rsm = 'http://jabber.org/protocol/rsm';
+
+local element_parsers = {};
+
+do
+	local parsers = element_parsers;
+	local function xs_int(st)
+		return tonumber((st:get_text()));
+	end
+	local function xs_string(st)
+		return st:get_text();
+	end
+
+	parsers.after = xs_string;
+	parsers.before = function(st)
+			local text = st:get_text();
+			return text == "" or text;
+		end;
+	parsers.max = xs_int;
+	parsers.index = xs_int;
+
+	parsers.first = function(st)
+			return { index = tonumber(st.attr.index); st:get_text() };
+		end;
+	parsers.last = xs_string;
+	parsers.count = xs_int;
+end
+
+local element_generators = setmetatable({
+	first = function(st, data)
+		if type(data) == "table" then
+			st:tag("first", { index = data.index }):text(data[1]):up();
+		else
+			st:tag("first"):text(tostring(data)):up();
+		end
+	end;
+	before = function(st, data)
+		if data == true then
+			st:tag("before"):up();
+		else
+			st:tag("before"):text(tostring(data)):up();
+		end
+	end
+}, {
+	__index = function(_, name)
+		return function(st, data)
+			st:tag(name):text(tostring(data)):up();
+		end
+	end;
+});
+
+
+local function parse(set)
+	local rs = {};
+	for tag in set:childtags() do
+		local name = tag.name;
+		local parser = name and element_parsers[name];
+		if parser then
+			rs[name] = parser(tag);
+		end
+	end
+	return rs;
+end
+
+local function generate(t)
+	local st = stanza("set", { xmlns = xmlns_rsm });
+	for k,v in pairs(t) do
+		if element_parsers[k] then
+			element_generators[k](st, v);
+		end
+	end
+	return st;
+end
+
+local function get(st)
+	local set = st:get_child("set", xmlns_rsm);
+	if set and #set.tags > 0 then
+		return parse(set);
+	end
+end
+
+return { parse = parse, generate = generate, get = get };
--- a/util/sasl/scram.lua	Sat Jan 09 11:03:30 2016 +0000
+++ b/util/sasl/scram.lua	Tue Jan 12 13:14:36 2016 +0000
@@ -1,7 +1,8 @@
 
 local base64, unbase64 = require "mime".b64, require"mime".unb64;
-local crypto = require"crypto";
+local hashes = require"util.hashes";
 local bit = require"bit";
+local random = require"util.random";
 
 local tonumber = tonumber;
 local char, byte = string.char, string.byte;
@@ -14,14 +15,7 @@
 	end));
 end
 
-local function H(str)
-	return crypto.digest("sha1", str, true);
-end
-
-local _hmac_digest = crypto.hmac.digest;
-local function HMAC(key, str)
-	return _hmac_digest("sha1", str, key, true);
-end
+local H, HMAC = hashes.sha1, hashes.hmac_sha1;
 
 local function Hi(str, salt, i)
 	local U = HMAC(str, salt .. "\0\0\0\1");
@@ -43,7 +37,7 @@
 
 local function scram(stream, name)
 	local username = "n=" .. value_safe(stream.username);
-	local c_nonce = base64(crypto.rand.bytes(15));
+	local c_nonce = base64(random.bytes(15));
 	local our_nonce = "r=" .. c_nonce;
 	local client_first_message_bare = username .. "," .. our_nonce;
 	local cbind_data = "";