Initial commit default tip

Thu, 03 Dec 2020 17:05:27 +0000

author
Matthew Wild <mwild1@gmail.com>
date
Thu, 03 Dec 2020 17:05:27 +0000
changeset 0
550f506de75a

Initial commit

main.lua file | annotate | diff | comparison | revisions
util/async.lua file | annotate | diff | comparison | revisions
util/id.lua file | annotate | diff | comparison | revisions
util/logger.lua file | annotate | diff | comparison | revisions
util/promise.lua file | annotate | diff | comparison | revisions
util/random.lua file | annotate | diff | comparison | revisions
util/xpcall.lua file | annotate | diff | comparison | revisions
verse.lua file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.lua	Thu Dec 03 17:05:27 2020 +0000
@@ -0,0 +1,141 @@
+-- Admin credentials
+local jid, password = "user@example.com", "secret"";
+
+-- Domain that users are on
+local domain = "example.com";
+
+-- Number of connections to the XMPP server
+local max_workers = 20;
+
+local input_filename = assert(arg[1], "No user file specified");
+
+local verse = require "verse".init("client");
+local async = require "util.async";
+local promise = require "util.promise";
+
+local function fs_encode(s)
+	return (s:gsub("%W", function (c)
+		return ("%02x"):format(c:byte());
+	end));
+end
+
+local function get_user_pep_nodes(c, username)
+	return promise.new(function (resolve, reject)
+		local request = verse.iq({ type = "get", to = username.."@"..domain })
+			:tag("query", { xmlns = "http://jabber.org/protocol/disco#items" });
+		c:send_iq(request, function (response)
+			if response.attr.type == "error" then
+				return reject(response);
+			end
+			local nodes = {};
+			for item in response.tags[1]:childtags("item") do
+				table.insert(nodes, item.attr.node);
+			end
+			resolve(nodes);
+		end);
+	end);
+end
+
+local function get_user_pep_data(c, username, node)
+	return promise.new(function (resolve, reject)
+		local request = verse.iq({ type = "get", to = username.."@"..domain })
+			:tag("pubsub", { xmlns = "http://jabber.org/protocol/pubsub" })
+				:tag("items", { node = node });
+		c:send_iq(request, function (response)
+			if response.attr.type == "error" then
+				return reject(response);
+			end
+			resolve(response
+				:get_child("pubsub", "http://jabber.org/protocol/pubsub")
+					:get_child("items")
+			);
+		end);
+	end);
+end
+
+
+local function connect_clients(n_clients)
+	local client_promises = {};
+	for i = 1, n_clients do
+		local c = verse.new();
+		client_promises[i] = promise.new(function (resolve, reject)
+			c:hook("ready", function ()
+				resolve(c);
+			end);
+			c:hook("disconnected", reject);
+			c:connect_client(jid, password);
+		end);
+	end
+	return promise.all(client_promises);
+end
+
+local function run_worker(client, users_file)
+	return promise.new(function (resolve, reject)
+		local processed = 0;
+		local line = users_file:read("*l");
+		if not line then resolve(processed); end
+		client:onready(function (self)
+				processed = processed + 1;
+				local new_line = users_file:read("*l");
+				if new_line then
+					self:run(new_line);
+				else
+					resolve(processed);
+					return;
+				end
+			end)
+			:onerror(reject)
+			:run(line);
+	end);
+end
+connect_clients(max_workers):next(function (connections)
+	local clients = {};
+	for i, conn_ in ipairs(connections) do
+		local conn = conn_;
+		clients[i] = async.runner(function (username)
+			--print("Getting nodes for "..username.."...");
+			local nodes, err = async.wait_for(get_user_pep_nodes(conn, username));
+			if not nodes then
+				print("Error:", err);
+				return;
+			end
+			local f = io.open(fs_encode(username)..".xml", "w+");
+			f:write("<pep>");
+			for _, node_name in ipairs(nodes) do
+				local data = async.wait_for(get_user_pep_data(conn, username, node_name));
+				f:write(tostring(data));
+			end
+			f:write("</pep>");
+			f:close();
+		end);
+	end
+	return clients;
+end):next(function (clients)
+	local users_file = assert(io.open(input_filename));
+
+	local workers = {};
+	for i = 1, #clients do
+		workers[i] = run_worker(clients[i], users_file);
+	end
+	promise.all(workers):next(function (status)
+		local total = 0;
+		for i, processed in ipairs(status) do
+			print(("Worker %d processed %d users"):format(i, processed));
+			total = total + processed;
+		end
+		print(("Total: %d users processed"):format(total));
+		verse.quit();
+	end, function (err)
+		print("Fatal error:", err);
+		os.exit(1);
+	end);
+
+end):catch(function (err)
+	print("Initialization error:", err);
+	os.exit(1);
+end);
+
+print("Starting loop...")
+local start_time = os.time();
+verse.loop()
+print(("All done in %d seconds!"):format(os.time()-start_time));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/async.lua	Thu Dec 03 17:05:27 2020 +0000
@@ -0,0 +1,275 @@
+local logger = require "util.logger";
+local log = logger.init("util.async");
+local new_id = require "util.id".short;
+local xpcall = require "util.xpcall".xpcall;
+
+local function checkthread()
+	local thread, main = coroutine.running();
+	if not thread or main then
+		error("Not running in an async context, see https://prosody.im/doc/developers/util/async");
+	end
+	return thread;
+end
+
+local function runner_from_thread(thread)
+	local level = 0;
+	-- Find the 'level' of the top-most function (0 == current level, 1 == caller, ...)
+	while debug.getinfo(thread, level, "") do level = level + 1; end
+	local name, runner = debug.getlocal(thread, level-1, 1);
+	if name ~= "self" or type(runner) ~= "table" or runner.thread ~= thread then
+		return nil;
+	end
+	return runner;
+end
+
+local function call_watcher(runner, watcher_name, ...)
+	local watcher = runner.watchers[watcher_name];
+	if not watcher then
+		return false;
+	end
+	runner:log("debug", "Calling '%s' watcher", watcher_name);
+	local ok, err = xpcall(watcher, debug.traceback, runner, ...);
+	if not ok then
+		runner:log("error", "Error in '%s' watcher: %s", watcher_name, err);
+		return nil, err;
+	end
+	return true;
+end
+
+local function runner_continue(thread)
+	-- ASSUMPTION: runner is in 'waiting' state (but we don't have the runner to know for sure)
+	if coroutine.status(thread) ~= "suspended" then -- This should suffice
+		log("error", "unexpected async state: thread not suspended");
+		return false;
+	end
+	local ok, state, runner = coroutine.resume(thread);
+	if not ok then
+		local err = state;
+		-- Running the coroutine failed, which means we have to find the runner manually,
+		-- in order to inform the error handler
+		runner = runner_from_thread(thread);
+		if not runner then
+			log("error", "unexpected async state: unable to locate runner during error handling");
+			return false;
+		end
+		call_watcher(runner, "error", debug.traceback(thread, err));
+		runner.state = "ready";
+		return runner:run();
+	elseif state == "ready" then
+		-- If state is 'ready', it is our responsibility to update runner.state from 'waiting'.
+		-- We also have to :run(), because the queue might have further items that will not be
+		-- processed otherwise. FIXME: It's probably best to do this in a nexttick (0 timer).
+		runner.state = "ready";
+		runner:run();
+	end
+	return true;
+end
+
+local function waiter(num)
+	local thread = checkthread();
+	num = num or 1;
+	local waiting;
+	return function ()
+		if num == 0 then return; end -- already done
+		waiting = true;
+		coroutine.yield("wait");
+	end, function ()
+		num = num - 1;
+		if num == 0 and waiting then
+			runner_continue(thread);
+		elseif num < 0 then
+			error("done() called too many times");
+		end
+	end;
+end
+
+local function guarder()
+	local guards = {};
+	local default_id = {};
+	return function (id, func)
+		id = id or default_id;
+		local thread = checkthread();
+		local guard = guards[id];
+		if not guard then
+			guard = {};
+			guards[id] = guard;
+			log("debug", "New guard!");
+		else
+			table.insert(guard, thread);
+			log("debug", "Guarded. %d threads waiting.", #guard)
+			coroutine.yield("wait");
+		end
+		local function exit()
+			local next_waiting = table.remove(guard, 1);
+			if next_waiting then
+				log("debug", "guard: Executing next waiting thread (%d left)", #guard)
+				runner_continue(next_waiting);
+			else
+				log("debug", "Guard off duty.")
+				guards[id] = nil;
+			end
+		end
+		if func then
+			func();
+			exit();
+			return;
+		end
+		return exit;
+	end;
+end
+
+local runner_mt = {};
+runner_mt.__index = runner_mt;
+
+local function runner_create_thread(func, self)
+	local thread = coroutine.create(function (self) -- luacheck: ignore 432/self
+		while true do
+			func(coroutine.yield("ready", self));
+		end
+	end);
+	debug.sethook(thread, debug.gethook());
+	assert(coroutine.resume(thread, self)); -- Start it up, it will return instantly to wait for the first input
+	return thread;
+end
+
+local function default_error_watcher(runner, err)
+	runner:log("error", "Encountered error: %s", err);
+	error(err);
+end
+local function default_func(f) f(); end
+local function runner(func, watchers, data)
+	local id = new_id();
+	local _log = logger.init("runner" .. id);
+	return setmetatable({ func = func or default_func, thread = false, state = "ready", notified_state = "ready",
+		queue = {}, watchers = watchers or { error = default_error_watcher }, data = data, id = id, _log = _log; }
+	, runner_mt);
+end
+
+-- Add a task item for the runner to process
+function runner_mt:run(input)
+	if input ~= nil then
+		table.insert(self.queue, input);
+		--self:log("debug", "queued new work item, %d items queued", #self.queue);
+	end
+	if self.state ~= "ready" then
+		-- The runner is busy. Indicate that the task item has been
+		-- queued, and return information about the current runner state
+		return true, self.state, #self.queue;
+	end
+
+	local q, thread = self.queue, self.thread;
+	if not thread or coroutine.status(thread) == "dead" then
+		--luacheck: ignore 143/coroutine
+		if thread and coroutine.close then
+			coroutine.close(thread);
+		end
+		self:log("debug", "creating new coroutine");
+		-- Create a new coroutine for this runner
+		thread = runner_create_thread(self.func, self);
+		self.thread = thread;
+	end
+
+	-- Process task item(s) while the queue is not empty, and we're not blocked
+	local n, state, err = #q, self.state, nil;
+	self.state = "running";
+	--self:log("debug", "running main loop");
+	while n > 0 and state == "ready" and not err do
+		local consumed;
+		-- Loop through queue items, and attempt to run them
+		for i = 1,n do
+			local queued_input = q[i];
+			local ok, new_state = coroutine.resume(thread, queued_input);
+			if not ok then
+				-- There was an error running the coroutine, save the error, mark runner as ready to begin again
+				consumed, state, err = i, "ready", debug.traceback(thread, new_state);
+				self.thread = nil;
+				break;
+			elseif new_state == "wait" then
+				 -- Runner is blocked on waiting for a task item to complete
+				consumed, state = i, "waiting";
+				break;
+			end
+		end
+		-- Loop ended - either queue empty because all tasks passed without blocking (consumed == nil)
+		-- or runner is blocked/errored, and consumed will contain the number of tasks processed so far
+		if not consumed then consumed = n; end
+		-- Remove consumed items from the queue array
+		if q[n+1] ~= nil then
+			n = #q;
+		end
+		for i = 1, n do
+			q[i] = q[consumed+i];
+		end
+		n = #q;
+	end
+	-- Runner processed all items it can, so save current runner state
+	self.state = state;
+	if err or state ~= self.notified_state then
+		self:log("debug", "changed state from %s to %s", self.notified_state, err and ("error ("..state..")") or state);
+		if err then
+			state = "error"
+		else
+			self.notified_state = state;
+		end
+		local handler = self.watchers[state];
+		if handler then handler(self, err); end
+	end
+	if n > 0 then
+		return self:run();
+	end
+	return true, state, n;
+end
+
+-- Add a task item to the queue without invoking the runner, even if it is idle
+function runner_mt:enqueue(input)
+	table.insert(self.queue, input);
+	self:log("debug", "queued new work item, %d items queued", #self.queue);
+	return self;
+end
+
+function runner_mt:log(level, fmt, ...)
+	return self._log(level, fmt, ...);
+end
+
+function runner_mt:onready(f)
+	self.watchers.ready = f;
+	return self;
+end
+
+function runner_mt:onwaiting(f)
+	self.watchers.waiting = f;
+	return self;
+end
+
+function runner_mt:onerror(f)
+	self.watchers.error = f;
+	return self;
+end
+
+local function ready()
+	return pcall(checkthread);
+end
+
+local function wait_for(promise)
+	local async_wait, async_done = waiter();
+	local ret, err = nil, nil;
+	promise:next(
+		function (r) ret = r; end,
+		function (e) err = e; end)
+		:finally(async_done);
+	async_wait();
+	if ret then
+		return ret;
+	else
+		return nil, err;
+	end
+end
+
+return {
+	ready = ready;
+	waiter = waiter;
+	guarder = guarder;
+	runner = runner;
+	wait = wait_for; -- COMPAT w/trunk pre-0.12
+	wait_for = wait_for;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/id.lua	Thu Dec 03 17:05:27 2020 +0000
@@ -0,0 +1,26 @@
+-- Prosody IM
+-- Copyright (C) 2008-2017 Matthew Wild
+-- Copyright (C) 2008-2017 Waqas Hussain
+-- Copyright (C) 2008-2017 Kim Alvefur
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+local s_gsub = string.gsub;
+local random_bytes = require "util.random".bytes;
+local base64_encode = require "util.encodings".base64.encode;
+
+local b64url = { ["+"] = "-", ["/"] = "_", ["="] = "" };
+local function b64url_random(len)
+	return (s_gsub(base64_encode(random_bytes(len)), "[+/=]", b64url));
+end
+
+return {
+	short =  function () return b64url_random(6); end;
+	medium = function () return b64url_random(12); end;
+	long =   function () return b64url_random(24); end;
+	custom = function (size)
+		return function () return b64url_random(size); end;
+	end;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/logger.lua	Thu Dec 03 17:05:27 2020 +0000
@@ -0,0 +1,90 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+-- luacheck: ignore 213/level
+
+local pairs = pairs;
+local ipairs = ipairs;
+local require = require;
+
+local _ENV = nil;
+-- luacheck: std none
+
+local level_sinks = {};
+
+local make_logger;
+
+local function init(name)
+	local log_debug = make_logger(name, "debug");
+	local log_info = make_logger(name, "info");
+	local log_warn = make_logger(name, "warn");
+	local log_error = make_logger(name, "error");
+
+	return function (level, message, ...)
+			if level == "debug" then
+				return log_debug(message, ...);
+			elseif level == "info" then
+				return log_info(message, ...);
+			elseif level == "warn" then
+				return log_warn(message, ...);
+			elseif level == "error" then
+				return log_error(message, ...);
+			end
+		end
+end
+
+function make_logger(source_name, level)
+	local level_handlers = level_sinks[level];
+	if not level_handlers then
+		level_handlers = {};
+		level_sinks[level] = level_handlers;
+	end
+
+	local logger = function (message, ...)
+		for i = 1,#level_handlers do
+			level_handlers[i](source_name, level, message, ...);
+		end
+	end
+
+	return logger;
+end
+
+local function reset()
+	for level, handler_list in pairs(level_sinks) do
+		-- Clear all handlers for this level
+		for i = 1, #handler_list do
+			handler_list[i] = nil;
+		end
+	end
+end
+
+local function add_level_sink(level, sink_function)
+	if not level_sinks[level] then
+		level_sinks[level] = { sink_function };
+	else
+		level_sinks[level][#level_sinks[level] + 1 ] = sink_function;
+	end
+end
+
+local function add_simple_sink(simple_sink_function, levels)
+	local format = require "util.format".format;
+	local function sink_function(name, level, msg, ...)
+		return simple_sink_function(name, level, format(msg, ...));
+	end
+	for _, level in ipairs(levels or {"debug", "info", "warn", "error"}) do
+		add_level_sink(level, sink_function);
+	end
+end
+
+return {
+	init = init;
+	make_logger = make_logger;
+	reset = reset;
+	add_level_sink = add_level_sink;
+	add_simple_sink = add_simple_sink;
+	new = make_logger;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/promise.lua	Thu Dec 03 17:05:27 2020 +0000
@@ -0,0 +1,177 @@
+local promise_methods = {};
+local promise_mt = { __name = "promise", __index = promise_methods };
+
+local xpcall = require "util.xpcall".xpcall;
+
+function promise_mt:__tostring()
+	return  "promise (" .. (self._state or "invalid") .. ")";
+end
+
+local function is_promise(o)
+	local mt = getmetatable(o);
+	return mt == promise_mt;
+end
+
+local function wrap_handler(f, resolve, reject, default)
+	if not f then
+		return default;
+	end
+	return function (param)
+		local ok, ret = xpcall(f, debug.traceback, param);
+		if ok then
+			resolve(ret);
+		else
+			reject(ret);
+		end
+		return true;
+	end;
+end
+
+local function next_pending(self, on_fulfilled, on_rejected, resolve, reject)
+	table.insert(self._pending_on_fulfilled, wrap_handler(on_fulfilled, resolve, reject, resolve));
+	table.insert(self._pending_on_rejected, wrap_handler(on_rejected, resolve, reject, reject));
+end
+
+local function next_fulfilled(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_rejected
+	wrap_handler(on_fulfilled, resolve, reject, resolve)(promise.value);
+end
+
+local function next_rejected(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_fulfilled
+	wrap_handler(on_rejected, resolve, reject, reject)(promise.reason);
+end
+
+local function promise_settle(promise, new_state, new_next, cbs, value)
+	if promise._state ~= "pending" then
+		return;
+	end
+	promise._state = new_state;
+	promise._next = new_next;
+	for _, cb in ipairs(cbs) do
+		cb(value);
+	end
+	-- No need to keep references to callbacks
+	promise._pending_on_fulfilled = nil;
+	promise._pending_on_rejected = nil;
+	return true;
+end
+
+local function new_resolve_functions(p)
+	local resolved = false;
+	local function _resolve(v)
+		if resolved then return; end
+		resolved = true;
+		if is_promise(v) then
+			v:next(new_resolve_functions(p));
+		elseif promise_settle(p, "fulfilled", next_fulfilled, p._pending_on_fulfilled, v) then
+			p.value = v;
+		end
+
+	end
+	local function _reject(e)
+		if resolved then return; end
+		resolved = true;
+		if promise_settle(p, "rejected", next_rejected, p._pending_on_rejected, e) then
+			p.reason = e;
+		end
+	end
+	return _resolve, _reject;
+end
+
+local function new(f)
+	local p = setmetatable({ _state = "pending", _next = next_pending, _pending_on_fulfilled = {}, _pending_on_rejected = {} }, promise_mt);
+	if f then
+		local resolve, reject = new_resolve_functions(p);
+		local ok, ret = xpcall(f, debug.traceback, resolve, reject);
+		if not ok and p._state == "pending" then
+			reject(ret);
+		end
+	end
+	return p;
+end
+
+local function all(promises)
+	return new(function (resolve, reject)
+		local count, total, results = 0, #promises, {};
+		for i = 1, total do
+			promises[i]:next(function (v)
+				results[i] = v;
+				count = count + 1;
+				if count == total then
+					resolve(results);
+				end
+			end, reject);
+		end
+	end);
+end
+
+local function all_settled(promises)
+	return new(function (resolve)
+		local count, total, results = 0, #promises, {};
+		for i = 1, total do
+			promises[i]:next(function (v)
+				results[i] = { status = "fulfilled", value = v };
+				count = count + 1;
+				if count == total then
+					resolve(results);
+				end
+			end, function (e)
+				results[i] = { status = "rejected", reason = e };
+				count = count + 1;
+				if count == total then
+					resolve(results);
+				end
+			end);
+		end
+	end);
+end
+
+local function race(promises)
+	return new(function (resolve, reject)
+		for i = 1, #promises do
+			promises[i]:next(resolve, reject);
+		end
+	end);
+end
+
+local function resolve(v)
+	return new(function (_resolve)
+		_resolve(v);
+	end);
+end
+
+local function reject(v)
+	return new(function (_, _reject)
+		_reject(v);
+	end);
+end
+
+local function try(f)
+	return resolve():next(function () return f(); end);
+end
+
+function promise_methods:next(on_fulfilled, on_rejected)
+	return new(function (resolve, reject) --luacheck: ignore 431/resolve 431/reject
+		self:_next(on_fulfilled, on_rejected, resolve, reject);
+	end);
+end
+
+function promise_methods:catch(on_rejected)
+	return self:next(nil, on_rejected);
+end
+
+function promise_methods:finally(on_finally)
+	local function _on_finally(value) on_finally(); return value; end
+	local function _on_catch_finally(err) on_finally(); return reject(err); end
+	return self:next(_on_finally, _on_catch_finally);
+end
+
+return {
+	new = new;
+	resolve = resolve;
+	reject = reject;
+	all = all;
+	all_settled = all_settled;
+	race = race;
+	try = try;
+	is_promise = is_promise;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/random.lua	Thu Dec 03 17:05:27 2020 +0000
@@ -0,0 +1,35 @@
+-- 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 ok, crand = pcall(require, "util.crand");
+if ok then return crand; end
+
+local urandom, urandom_err = io.open("/dev/urandom", "r");
+
+local function bytes(n)
+	local data, err = urandom:read(n);
+	if not data then
+		if err then
+			error("Unable to retrieve data from secure random number generator (/dev/urandom): "..tostring(err));
+		else
+			error("Secure random number generator (/dev/urandom) returned an end-of-file condition");
+		end
+	end
+	return data;
+end
+
+if not urandom then
+	function bytes()
+		error("Unable to obtain a secure random number generator, please see https://prosody.im/doc/random ("..urandom_err..")");
+	end
+end
+
+return {
+	bytes = bytes;
+	_source = "/dev/urandom";
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util/xpcall.lua	Thu Dec 03 17:05:27 2020 +0000
@@ -0,0 +1,9 @@
+local xpcall = xpcall;
+
+if select(2, xpcall(function (x) return x end, function () end,  "test")) ~= "test" then
+	xpcall = require"util.compat".xpcall;
+end
+
+return {
+	xpcall = xpcall;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/verse.lua	Thu Dec 03 17:05:27 2020 +0000
@@ -0,0 +1,9185 @@
+package.preload['util.encodings']=(function(...)
+local _ENV=_ENV;
+local function a(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local function e()
+error("Function not implemented");
+end
+local e=require"mime";
+a"encodings"
+idna={};
+stringprep={};
+base64={encode=e.b64,decode=e.unb64};
+utf8={
+valid=(utf8 and utf8.len)and function(e)return not not utf8.len(e);end or function()return true;end;
+};
+return _M;
+end)
+package.preload['util.hashes']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local function e(t,e)
+error("Hash method "..e.." not available",2);
+end
+local e=setmetatable({},{__index=e});
+local function t(e,a)
+local e,o=pcall(require,e);
+if e then a(o);end
+end
+t("bgcrypto.md5",function(t)
+e.md5=t.digest;
+e.hmac_md5=t.hmac.digest;
+end);
+t("bgcrypto.sha1",function(t)
+e.sha1=t.digest;
+e.hmac_sha1=t.hmac.digest;
+e.scram_Hi_sha1=function(a,o,e)return t.pbkdf2(a,o,e,20);end;
+end);
+t("bgcrypto.sha256",function(t)
+e.sha256=t.digest;
+e.hmac_sha256=t.hmac.digest;
+end);
+t("bgcrypto.sha512",function(t)
+e.sha512=t.digest;
+e.hmac_sha512=t.hmac.digest;
+end);
+t("sha1",function(t)
+e.sha1=function(e,a)
+if a then
+return t.sha1(e);
+else
+return(t.binary(e));
+end
+end;
+end);
+return e;
+end)
+package.preload['lib.adhoc']=(function(...)
+local _ENV=_ENV;
+local function d(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local s,i=require"util.stanza",require"util.uuid";
+local r="http://jabber.org/protocol/commands";
+local n={}
+local h={};
+local function o(e,o,a,t)
+local e=s.stanza("command",{xmlns=r,node=e.node,status=o});
+if a then e.attr.sessionid=a;end
+if t then e.attr.action=t;end
+return e;
+end
+function h.new(t,i,e,a)
+return{name=t,node=i,handler=e,cmdtag=o,permission=(a or"user")};
+end
+function h.handle_cmd(o,h,t)
+local e=t.tags[1].attr.sessionid or i.generate();
+local a={};
+a.to=t.attr.to;
+a.from=t.attr.from;
+a.action=t.tags[1].attr.action or"execute";
+a.form=t.tags[1]:child_with_ns("jabber:x:data");
+local a,i=o:handler(a,n[e]);
+n[e]=i;
+local i=s.reply(t);
+local t;
+if a.status=="completed"then
+n[e]=nil;
+t=o:cmdtag("completed",e);
+elseif a.status=="canceled"then
+n[e]=nil;
+t=o:cmdtag("canceled",e);
+elseif a.status=="error"then
+n[e]=nil;
+i=s.error_reply(i,a.error.type,a.error.condition,a.error.message);
+h.send(i);
+return true;
+else
+t=o:cmdtag("executing",e);
+end
+for a,e in pairs(a)do
+if a=="info"then
+t:tag("note",{type="info"}):text(e):up();
+elseif a=="warn"then
+t:tag("note",{type="warn"}):text(e):up();
+elseif a=="error"then
+t:tag("note",{type="error"}):text(e.message):up();
+elseif a=="actions"then
+local a=s.stanza("actions");
+for i,e in ipairs(e)do
+if(e=="prev")or(e=="next")or(e=="complete")then
+a:tag(e):up();
+else
+d:log("error",'Command "'..o.name..
+'" at node "'..o.node..'" provided an invalid action "'..e..'"');
+end
+end
+t:add_child(a);
+elseif a=="form"then
+t:add_child((e.layout or e):form(e.values));
+elseif a=="result"then
+t:add_child((e.layout or e):form(e.values,"result"));
+elseif a=="other"then
+t:add_child(e);
+end
+end
+i:add_child(t);
+h.send(i);
+return true;
+end
+return h;
+end)
+package.preload['util.stanza']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local i=error;
+local t=table.insert;
+local l=table.remove;
+local p=table.concat;
+local r=string.format;
+local c=string.match;
+local f=tostring;
+local w=setmetatable;
+local q=getmetatable;
+local n=pairs;
+local s=ipairs;
+local a=type;
+local j=string.gsub;
+local m=string.sub;
+local u=string.find;
+local e=os;
+local h=require"util.encodings".utf8.valid;
+local y=not e.getenv("WINDIR");
+local d,o;
+if y then
+local t,e=pcall(require,"util.termcolours");
+if t then
+d,o=e.getstyle,e.getstring;
+else
+y=nil;
+end
+end
+local v="urn:ietf:params:xml:ns:xmpp-stanzas";
+local _ENV=nil;
+local e={__name="stanza"};
+e.__index=e;
+local function g(e,t)
+if a(e)~="string"then
+i("invalid "..t.." name: expected string, got "..a(e));
+elseif#e==0 then
+i("invalid "..t.." name: empty string");
+elseif u(e,"[<>& '\"]")then
+i("invalid "..t.." name: contains invalid characters");
+elseif not h(e)then
+i("invalid "..t.." name: contains invalid utf8");
+end
+end
+local function b(e,t)
+if a(e)~="string"then
+i("invalid "..t.." value: expected string, got "..a(e));
+elseif not h(e)then
+i("invalid "..t.." value: contains invalid utf8");
+end
+end
+local function k(e)
+if e~=nil then
+if a(e)~="table"then
+i("invalid attributes, expected table got "..a(e));
+end
+for t,e in n(e)do
+g(t,"attribute");
+b(e,"attribute");
+if a(e)~="string"then
+i("invalid attribute value for '"..t.."': expected string, got "..a(e));
+elseif not h(e)then
+i("invalid attribute value for '"..t.."': contains invalid utf8");
+end
+end
+end
+end
+local function h(t,a,o)
+g(t,"tag");
+k(a);
+local t={name=t,attr=a or{},namespaces=o,tags={}};
+return w(t,e);
+end
+local function g(t)
+return q(t)==e;
+end
+function e:query(e)
+return self:tag("query",{xmlns=e});
+end
+function e:body(t,e)
+return self:tag("body",e):text(t);
+end
+function e:text_tag(o,a,t,e)
+return self:tag(o,t,e):text(a):up();
+end
+function e:tag(e,a,o)
+local a=h(e,a,o);
+local e=self.last_add;
+if not e then e={};self.last_add=e;end
+(e[#e]or self):add_direct_child(a);
+t(e,a);
+return self;
+end
+function e:text(t)
+if t~=nil and t~=""then
+local e=self.last_add;
+(e and e[#e]or self):add_direct_child(t);
+end
+return self;
+end
+function e:up()
+local e=self.last_add;
+if e then l(e);end
+return self;
+end
+function e:reset()
+self.last_add=nil;
+return self;
+end
+function e:add_direct_child(e)
+if g(e)then
+t(self.tags,e);
+t(self,e);
+else
+b(e,"text");
+t(self,e);
+end
+end
+function e:add_child(t)
+local e=self.last_add;
+(e and e[#e]or self):add_direct_child(t);
+return self;
+end
+function e:remove_children(a,e)
+e=e or self.attr.xmlns;
+return self:maptags(function(t)
+if(not a or t.name==a)and t.attr.xmlns==e then
+return nil;
+end
+return t;
+end);
+end
+function e:get_child(a,t)
+for o,e in s(self.tags)do
+if(not a or e.name==a)
+and((not t and self.attr.xmlns==e.attr.xmlns)
+or e.attr.xmlns==t)then
+return e;
+end
+end
+end
+function e:get_child_text(t,e)
+local e=self:get_child(t,e);
+if e then
+return e:get_text();
+end
+return nil;
+end
+function e:child_with_name(t)
+for a,e in s(self.tags)do
+if e.name==t then return e;end
+end
+end
+function e:child_with_ns(t)
+for a,e in s(self.tags)do
+if e.attr.xmlns==t then return e;end
+end
+end
+function e:children()
+local e=0;
+return function(t)
+e=e+1
+return t[e];
+end,self,e;
+end
+function e:childtags(t,a)
+local e=self.tags;
+local o,i=1,#e;
+return function()
+for i=o,i do
+local e=e[i];
+if(not t or e.name==t)
+and((not a and self.attr.xmlns==e.attr.xmlns)
+or e.attr.xmlns==a)then
+o=i+1;
+return e;
+end
+end
+end;
+end
+function e:maptags(h)
+local o,t=self.tags,1;
+local n,a=#self,#o;
+local s=n+1;
+local e=1;
+while t<=a and a>0 do
+if self[e]==o[t]then
+local i=h(self[e]);
+if i==nil then
+l(self,e);
+l(o,t);
+n=n-1;
+a=a-1;
+e=e-1;
+t=t-1;
+else
+self[e]=i;
+o[t]=i;
+end
+t=t+1;
+end
+e=e+1;
+if e>s then
+i("Invalid stanza state! Please report this error.");
+end
+end
+return self;
+end
+function e:find(a)
+local e=1;
+local s=#a+1;
+repeat
+local o,t,i;
+local n=m(a,e,e);
+if n=="@"then
+return self.attr[m(a,e+1)];
+elseif n=="{"then
+o,e=c(a,"^([^}]+)}()",e+1);
+end
+t,i,e=c(a,"^([^@/#]*)([/#]?)()",e);
+t=t~=""and t or nil;
+if e==s then
+if i=="#"then
+return self:get_child_text(t,o);
+end
+return self:get_child(t,o);
+end
+self=self:get_child(t,o);
+until not self
+end
+local i={["'"]="&apos;",["\""]="&quot;",["<"]="&lt;",[">"]="&gt;",["&"]="&amp;"};
+local function l(e)return(j(e,"['&<>\"]",i));end
+local function m(o,e,s,a,r)
+local i=0;
+local h=o.name
+t(e,"<"..h);
+for o,n in n(o.attr)do
+if u(o,"\1",1,true)then
+local o,s=c(o,"^([^\1]*)\1?(.*)$");
+i=i+1;
+t(e," xmlns:ns"..i.."='"..a(o).."' ".."ns"..i..":"..s.."='"..a(n).."'");
+elseif not(o=="xmlns"and n==r)then
+t(e," "..o.."='"..a(n).."'");
+end
+end
+local i=#o;
+if i==0 then
+t(e,"/>");
+else
+t(e,">");
+for i=1,i do
+local i=o[i];
+if i.name then
+s(i,e,s,a,o.attr.xmlns);
+else
+t(e,a(i));
+end
+end
+t(e,"</"..h..">");
+end
+end
+function e.__tostring(t)
+local e={};
+m(t,e,m,l,nil);
+return p(e);
+end
+function e.top_tag(e)
+local t="";
+if e.attr then
+for e,o in n(e.attr)do if a(e)=="string"then t=t..r(" %s='%s'",e,l(f(o)));end end
+end
+return r("<%s%s>",e.name,t);
+end
+function e.get_text(e)
+if#e.tags==0 then
+return p(e);
+end
+end
+function e.get_error(a)
+local o,t,e;
+local a=a:get_child("error");
+if not a then
+return nil,nil,nil;
+end
+o=a.attr.type;
+for o,a in s(a.tags)do
+if a.attr.xmlns==v then
+if not e and a.name=="text"then
+e=a:get_text();
+elseif not t then
+t=a.name;
+end
+if t and e then
+break;
+end
+end
+end
+return o,t or"undefined-condition",e;
+end
+local function m(o)
+local i={name=o.name,attr=o.attr};
+for o,e in s(o)do
+if a(e)=="table"then
+t(i,m(e));
+else
+t(i,e);
+end
+end
+return i;
+end
+e.__freeze=m;
+local function p(o)
+if o then
+local i=o.attr;
+for e=1,#i do i[e]=nil;end
+local h={};
+for e in n(i)do
+if u(e,"|",1,true)and not u(e,"\1",1,true)then
+local a,t=c(e,"^([^|]+)|(.+)$");
+h[a.."\1"..t]=i[e];
+i[e]=nil;
+end
+end
+for e,t in n(h)do
+i[e]=t;
+end
+w(o,e);
+for t,e in s(o)do
+if a(e)=="table"then
+p(e);
+end
+end
+if not o.tags then
+local e={};
+for n,i in s(o)do
+if a(i)=="table"then
+t(e,i);
+end
+end
+o.tags=e;
+end
+end
+return o;
+end
+local function u(a)
+local i,h={},{};
+for t,e in n(a.attr)do i[t]=e;end
+local s,o=a.namespaces;
+if s then
+o={};
+for e,t in n(s)do o[e]=t;end
+end
+local o={name=a.name,attr=i,namespaces=o,tags=h};
+for e=1,#a do
+local e=a[e];
+if e.name then
+e=u(e);
+t(h,e);
+end
+t(o,e);
+end
+return w(o,e);
+end
+local function w(t,e)
+if not e then
+return h("message",t);
+else
+return h("message",t):tag("body"):text(e):up();
+end
+end
+local function b(e)
+if not(e and e.id)then
+end
+return h("iq",e);
+end
+local function c(e)
+return h(e.name,
+e.attr and{
+to=e.attr.from,
+from=e.attr.to,
+id=e.attr.id,
+type=((e.name=="iq"and"result")or e.attr.type)
+});
+end
+local t={xmlns=v};
+local function v(e,i,o,a)
+local e=c(e);
+e.attr.type="error";
+e:tag("error",{type=i})
+:tag(o,t):up();
+if a then e:tag("text",t):text(a):up();end
+return e;
+end
+local function k(e)
+return h("presence",e);
+end
+if y then
+local i=d("yellow");
+local u=d("red");
+local h=d("red");
+local t=d("magenta");
+local i=" "..o(i,"%s")..o(t,"=")..o(u,"'%s'");
+local d=o(t,"<")..o(h,"%s").."%s"..o(t,">");
+local h=d.."%s"..o(t,"</")..o(h,"%s")..o(t,">");
+function e.pretty_print(t)
+local e="";
+for o,t in s(t)do
+if a(t)=="string"then
+e=e..l(t);
+else
+e=e..t:pretty_print();
+end
+end
+local o="";
+if t.attr then
+for e,t in n(t.attr)do if a(e)=="string"then o=o..r(i,e,f(t));end end
+end
+return r(h,t.name,o,e,t.name);
+end
+function e.pretty_top_tag(t)
+local e="";
+if t.attr then
+for t,o in n(t.attr)do if a(t)=="string"then e=e..r(i,t,f(o));end end
+end
+return r(d,t.name,e);
+end
+else
+e.pretty_print=e.__tostring;
+e.pretty_top_tag=e.top_tag;
+end
+return{
+stanza_mt=e;
+stanza=h;
+is_stanza=g;
+preserialize=m;
+deserialize=p;
+clone=u;
+message=w;
+iq=b;
+reply=c;
+error_reply=v;
+presence=k;
+xml_escape=l;
+};
+end)
+package.preload['util.timer']=(function(...)
+local _ENV=_ENV;
+local function u(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local o=require"net.server";
+local h=math.min
+local l=math.huge
+local i=require"socket".gettime;
+local s=table.insert;
+local d=pairs;
+local r=type;
+local n={};
+local a={};
+u"timer"
+local e;
+if not o.event then
+function e(t,n)
+local i=i();
+t=t+i;
+if t>=i then
+s(a,{t,n});
+else
+local t=n(i);
+if t and r(t)=="number"then
+return e(t,n);
+end
+end
+end
+o._addtimer(function()
+local o=i();
+if#a>0 then
+for t,e in d(a)do
+s(n,e);
+end
+a={};
+end
+local t=l;
+for d,a in d(n)do
+local s,i=a[1],a[2];
+if s<=o then
+n[d]=nil;
+local a=i(o);
+if r(a)=="number"then
+e(a,i);
+t=h(t,a);
+end
+else
+t=h(t,s-o);
+end
+end
+return t;
+end);
+else
+local t=o.event;
+local n=o.event_base;
+local o=(t.core and t.core.LEAVE)or-1;
+function e(a,e)
+local t;
+t=n:addevent(nil,0,function()
+local e=e(i());
+if e then
+return 0,e;
+elseif t then
+return o;
+end
+end
+,a);
+end
+end
+add_task=e;
+return _M;
+end)
+package.preload['util.termcolours']=(function(...)
+local _ENV=_ENV;
+local function s(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local i,n=table.concat,table.insert;
+local t,a=string.char,string.format;
+local h=tonumber;
+local r=ipairs;
+local d=io.write;
+local e;
+if os.getenv("WINDIR")then
+e=require"util.windows";
+end
+local o=e and e.get_consolecolor and e.get_consolecolor();
+s"termcolours"
+local s={
+reset=0;bright=1,dim=2,underscore=4,blink=5,reverse=7,hidden=8;
+black=30;red=31;green=32;yellow=33;blue=34;magenta=35;cyan=36;white=37;
+["black background"]=40;["red background"]=41;["green background"]=42;["yellow background"]=43;["blue background"]=44;["magenta background"]=45;["cyan background"]=46;["white background"]=47;
+bold=1,dark=2,underline=4,underlined=4,normal=0;
+}
+local l={
+["0"]=o,
+["1"]=7+8,
+["1;33"]=2+4+8,
+["1;31"]=4+8
+}
+local u={
+[1]="font-weight: bold",[2]="opacity: 0.5",[4]="text-decoration: underline",[8]="visibility: hidden",
+[30]="color:black",[31]="color:red",[32]="color:green",[33]="color:#FFD700",
+[34]="color:blue",[35]="color: magenta",[36]="color:cyan",[37]="color: white",
+[40]="background-color:black",[41]="background-color:red",[42]="background-color:green",
+[43]="background-color:yellow",[44]="background-color:blue",[45]="background-color: magenta",
+[46]="background-color:cyan",[47]="background-color: white";
+};
+local c=t(27).."[%sm%s"..t(27).."[0m";
+function getstring(t,e)
+if t then
+return a(c,t,e);
+else
+return e;
+end
+end
+function getstyle(...)
+local e,t={...},{};
+for a,e in r(e)do
+e=s[e];
+if e then
+n(t,e);
+end
+end
+return i(t,";");
+end
+local a="0";
+function setstyle(e)
+e=e or"0";
+if e~=a then
+d("\27["..e.."m");
+a=e;
+end
+end
+if e then
+function setstyle(t)
+t=t or"0";
+if t~=a then
+e.set_consolecolor(l[t]or o);
+a=t;
+end
+end
+if not o then
+function setstyle(e)end
+end
+end
+local function a(t)
+if t=="0"then return"</span>";end
+local e={};
+for t in t:gmatch("[^;]+")do
+n(e,u[h(t)]);
+end
+return"</span><span style='"..i(e,";").."'>";
+end
+function tohtml(e)
+return e:gsub("\027%[(.-)m",a);
+end
+return _M;
+end)
+package.preload['util.uuid']=(function(...)
+local _ENV=_ENV;
+local function a(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local i=tostring;
+local e=os.time;
+local o=os.clock;
+local n=require"util.hashes".sha1;
+a"uuid"
+local t=0;
+local function a()
+local e=e();
+if t>=e then e=t+1;end
+t=e;
+return e;
+end
+local function t(e)
+return n(e..o()..i({}),true);
+end
+local e=t(a());
+local function o(a)
+e=t(e..a);
+end
+local function t(t)
+if#e<t then o(a());end
+local a=e:sub(0,t);
+e=e:sub(t+1);
+return a;
+end
+local function e()
+return("%x"):format(t(1):byte()%4+8);
+end
+function generate()
+return t(8).."-"..t(4).."-4"..t(3).."-"..(e())..t(3).."-"..t(12);
+end
+seed=o;
+return _M;
+end)
+package.preload['net.dns']=(function(...)
+local _ENV=_ENV;
+local function c(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local n=require"socket";
+local j=require"util.timer";
+local e,y=pcall(require,"util.windows");
+local E=(e and y)or os.getenv("WINDIR");
+local u,_,v,a,i=
+coroutine,io,math,string,table;
+local f,s,o,m,r,b,z,x,t,e,q=
+ipairs,next,pairs,print,setmetatable,tostring,assert,error,unpack,select,type;
+local e={
+get=function(t,...)
+local a=e('#',...);
+for a=1,a do
+t=t[e(a,...)];
+if t==nil then break;end
+end
+return t;
+end;
+set=function(a,...)
+local i=e('#',...);
+local h,o=e(i-1,...);
+local t,n;
+for i=1,i-2 do
+local i=e(i,...)
+local e=a[i]
+if o==nil then
+if e==nil then
+return;
+elseif s(e,s(e))then
+t=nil;n=nil;
+elseif t==nil then
+t=a;n=i;
+end
+elseif e==nil then
+e={};
+a[i]=e;
+end
+a=e
+end
+if o==nil and t then
+t[n]=nil;
+else
+a[h]=o;
+return o;
+end
+end;
+};
+local d,l=e.get,e.set;
+local k=15;
+c('dns')
+local t=_M;
+local h=i.insert
+local function c(e)
+return(e-(e%256))/256;
+end
+local function p(e)
+local t={};
+for o,e in o(e)do
+t[o]=e;
+t[e]=e;
+t[a.lower(e)]=e;
+end
+return t;
+end
+local function w(i)
+local e={};
+for t,i in o(i)do
+local o=a.char(c(t),t%256);
+e[t]=o;
+e[i]=o;
+e[a.lower(i)]=o;
+end
+return e;
+end
+t.types={
+'A','NS','MD','MF','CNAME','SOA','MB','MG','MR','NULL','WKS',
+'PTR','HINFO','MINFO','MX','TXT',
+[28]='AAAA',[29]='LOC',[33]='SRV',
+[252]='AXFR',[253]='MAILB',[254]='MAILA',[255]='*'};
+t.classes={'IN','CS','CH','HS',[255]='*'};
+t.type=p(t.types);
+t.class=p(t.classes);
+t.typecode=w(t.types);
+t.classcode=w(t.classes);
+local function g(e,o,i)
+if a.byte(e,-1)~=46 then e=e..'.';end
+e=a.lower(e);
+return e,t.type[o or'A'],t.class[i or'IN'];
+end
+local function p(t,a,o)
+a=a or n.gettime();
+for n,e in f(t)do
+if e.tod then
+e.ttl=v.floor(e.tod-a);
+if e.ttl<=0 then
+t[e[e.type:lower()]]=nil;
+i.remove(t,n);
+return p(t,a,o);
+end
+elseif o=='soft'then
+z(e.ttl==0);
+t[e[e.type:lower()]]=nil;
+i.remove(t,n);
+end
+end
+end
+local e={};
+e.__index=e;
+e.timeout=k;
+local function k(e)
+local e=e.type and e[e.type:lower()];
+if q(e)~="string"then
+return"<UNKNOWN RDATA TYPE>";
+end
+return e;
+end
+local w={
+LOC=e.LOC_tostring;
+MX=function(e)
+return a.format('%2i %s',e.pref,e.mx);
+end;
+SRV=function(e)
+local e=e.srv;
+return a.format('%5d %5d %5d %s',e.priority,e.weight,e.port,e.target);
+end;
+};
+local q={};
+function q.__tostring(e)
+local t=(w[e.type]or k)(e);
+return a.format('%2s %-5s %6i %-28s %s',e.class,e.type,e.ttl,e.name,t);
+end
+local k={};
+function k.__tostring(t)
+local e={};
+for a,t in f(t)do
+h(e,b(t)..'\n');
+end
+return i.concat(e);
+end
+local w={};
+function w.__tostring(t)
+local a=n.gettime();
+local e={};
+for i,t in o(t)do
+for i,t in o(t)do
+for o,t in o(t)do
+p(t,a);
+h(e,b(t));
+end
+end
+end
+return i.concat(e);
+end
+function e:new()
+local t={active={},cache={},unsorted={}};
+r(t,e);
+r(t.cache,w);
+r(t.unsorted,{__mode='kv'});
+return t;
+end
+function t.random(...)
+v.randomseed(v.floor(1e4*n.gettime())%2147483648);
+t.random=v.random;
+return t.random(...);
+end
+local function v(e)
+e=e or{};
+e.id=e.id or t.random(0,65535);
+e.rd=e.rd or 1;
+e.tc=e.tc or 0;
+e.aa=e.aa or 0;
+e.opcode=e.opcode or 0;
+e.qr=e.qr or 0;
+e.rcode=e.rcode or 0;
+e.z=e.z or 0;
+e.ra=e.ra or 0;
+e.qdcount=e.qdcount or 1;
+e.ancount=e.ancount or 0;
+e.nscount=e.nscount or 0;
+e.arcount=e.arcount or 0;
+local t=a.char(
+c(e.id),e.id%256,
+e.rd+2*e.tc+4*e.aa+8*e.opcode+128*e.qr,
+e.rcode+16*e.z+128*e.ra,
+c(e.qdcount),e.qdcount%256,
+c(e.ancount),e.ancount%256,
+c(e.nscount),e.nscount%256,
+c(e.arcount),e.arcount%256
+);
+return t,e.id;
+end
+local function c(t)
+local e={};
+for t in a.gmatch(t,'[^.]+')do
+h(e,a.char(a.len(t)));
+h(e,t);
+end
+h(e,a.char(0));
+return i.concat(e);
+end
+local function z(o,a,e)
+o=c(o);
+a=t.typecode[a or'a'];
+e=t.classcode[e or'in'];
+return o..a..e;
+end
+function e:byte(e)
+e=e or 1;
+local o=self.offset;
+local t=o+e-1;
+if t>#self.packet then
+x(a.format('out of bounds: %i>%i',t,#self.packet));
+end
+self.offset=o+e;
+return a.byte(self.packet,o,t);
+end
+function e:word()
+local e,t=self:byte(2);
+return 256*e+t;
+end
+function e:dword()
+local t,e,a,o=self:byte(4);
+return 16777216*t+65536*e+256*a+o;
+end
+function e:sub(e)
+e=e or 1;
+local t=a.sub(self.packet,self.offset,self.offset+e-1);
+self.offset=self.offset+e;
+return t;
+end
+function e:header(t)
+local e=self:word();
+if not self.active[e]and not t then return nil;end
+local e={id=e};
+local t,a=self:byte(2);
+e.rd=t%2;
+e.tc=t/2%2;
+e.aa=t/4%2;
+e.opcode=t/8%16;
+e.qr=t/128;
+e.rcode=a%16;
+e.z=a/16%8;
+e.ra=a/128;
+e.qdcount=self:word();
+e.ancount=self:word();
+e.nscount=self:word();
+e.arcount=self:word();
+for a,t in o(e)do e[a]=t-t%1;end
+return e;
+end
+function e:name()
+local t,a=nil,0;
+local e=self:byte();
+local o={};
+if e==0 then return"."end
+while e>0 do
+if e>=192 then
+a=a+1;
+if a>=20 then x('dns error: 20 pointers');end;
+local e=((e-192)*256)+self:byte();
+t=t or self.offset;
+self.offset=e+1;
+else
+h(o,self:sub(e)..'.');
+end
+e=self:byte();
+end
+self.offset=t or self.offset;
+return i.concat(o);
+end
+function e:question()
+local e={};
+e.name=self:name();
+e.type=t.type[self:word()];
+e.class=t.class[self:word()];
+return e;
+end
+function e:A(i)
+local o,e,t,n=self:byte(4);
+i.a=a.format('%i.%i.%i.%i',o,e,t,n);
+end
+function e:AAAA(a)
+local e={};
+for t=1,a.rdlength,2 do
+local a,t=self:byte(2);
+i.insert(e,("%02x%02x"):format(a,t));
+end
+e=i.concat(e,":"):gsub("%f[%x]0+(%x)","%1");
+local t={};
+for e in e:gmatch(":[0:]+:")do
+i.insert(t,e)
+end
+if#t==0 then
+a.aaaa=e;
+return
+elseif#t>1 then
+i.sort(t,function(t,e)return#t>#e end);
+end
+a.aaaa=e:gsub(t[1],"::",1):gsub("^0::","::"):gsub("::0$","::");
+end
+function e:CNAME(e)
+e.cname=self:name();
+end
+function e:MX(e)
+e.pref=self:word();
+e.mx=self:name();
+end
+function e:LOC_nibble_power()
+local e=self:byte();
+return((e-(e%16))/16)*(10^(e%16));
+end
+function e:LOC(e)
+e.version=self:byte();
+if e.version==0 then
+e.loc=e.loc or{};
+e.loc.size=self:LOC_nibble_power();
+e.loc.horiz_pre=self:LOC_nibble_power();
+e.loc.vert_pre=self:LOC_nibble_power();
+e.loc.latitude=self:dword();
+e.loc.longitude=self:dword();
+e.loc.altitude=self:dword();
+end
+end
+local function c(e,i,t)
+e=e-2147483648;
+if e<0 then i=t;e=-e;end
+local n,o,t;
+t=e%6e4;
+e=(e-t)/6e4;
+o=e%60;
+n=(e-o)/60;
+return a.format('%3d %2d %2.3f %s',n,o,t/1e3,i);
+end
+function e.LOC_tostring(e)
+local t={};
+h(t,a.format(
+'%s    %s    %.2fm %.2fm %.2fm %.2fm',
+c(e.loc.latitude,'N','S'),
+c(e.loc.longitude,'E','W'),
+(e.loc.altitude-1e7)/100,
+e.loc.size/100,
+e.loc.horiz_pre/100,
+e.loc.vert_pre/100
+));
+return i.concat(t);
+end
+function e:NS(e)
+e.ns=self:name();
+end
+function e:SOA(e)
+end
+function e:SRV(e)
+e.srv={};
+e.srv.priority=self:word();
+e.srv.weight=self:word();
+e.srv.port=self:word();
+e.srv.target=self:name();
+end
+function e:PTR(e)
+e.ptr=self:name();
+end
+function e:TXT(e)
+e.txt=self:sub(self:byte());
+end
+function e:rr()
+local e={};
+r(e,q);
+e.name=self:name(self);
+e.type=t.type[self:word()]or e.type;
+e.class=t.class[self:word()]or e.class;
+e.ttl=65536*self:word()+self:word();
+e.rdlength=self:word();
+if e.ttl<=0 then
+e.tod=self.time+30;
+else
+e.tod=self.time+e.ttl;
+end
+local a=self.offset;
+local t=self[t.type[e.type]];
+if t then t(self,e);end
+self.offset=a;
+e.rdata=self:sub(e.rdlength);
+return e;
+end
+function e:rrs(t)
+local e={};
+for t=1,t do h(e,self:rr());end
+return e;
+end
+function e:decode(t,o)
+self.packet,self.offset=t,1;
+local t=self:header(o);
+if not t then return nil;end
+local t={header=t};
+t.question={};
+local i=self.offset;
+for e=1,t.header.qdcount do
+h(t.question,self:question());
+end
+t.question.raw=a.sub(self.packet,i,self.offset-1);
+if not o then
+if not self.active[t.header.id]or not self.active[t.header.id][t.question.raw]then
+self.active[t.header.id]=nil;
+return nil;
+end
+end
+t.answer=self:rrs(t.header.ancount);
+t.authority=self:rrs(t.header.nscount);
+t.additional=self:rrs(t.header.arcount);
+return t;
+end
+e.delays={1,3};
+function e:addnameserver(e)
+self.server=self.server or{};
+h(self.server,e);
+end
+function e:setnameserver(e)
+self.server={};
+self:addnameserver(e);
+end
+function e:adddefaultnameservers()
+if E then
+if y and y.get_nameservers then
+for t,e in f(y.get_nameservers())do
+self:addnameserver(e);
+end
+end
+if not self.server or#self.server==0 then
+self:addnameserver("208.67.222.222");
+self:addnameserver("208.67.220.220");
+end
+else
+local e=_.open("/etc/resolv.conf");
+if e then
+for e in e:lines()do
+e=e:gsub("#.*$","")
+:match('^%s*nameserver%s+(.*)%s*$');
+if e then
+e:gsub("%f[%d.](%d+%.%d+%.%d+%.%d+)%f[^%d.]",function(e)
+self:addnameserver(e)
+end);
+end
+end
+end
+if not self.server or#self.server==0 then
+self:addnameserver("127.0.0.1");
+end
+end
+end
+function e:getsocket(a)
+self.socket=self.socket or{};
+self.socketset=self.socketset or{};
+local e=self.socket[a];
+if e then return e;end
+local o,t;
+e,t=n.udp();
+if e and self.socket_wrapper then e,t=self.socket_wrapper(e,self);end
+if not e then
+return nil,t;
+end
+e:settimeout(0);
+self.socket[a]=e;
+self.socketset[e]=a;
+o,t=e:setsockname('*',0);
+if not o then return self:servfail(e,t);end
+o,t=e:setpeername(self.server[a],53);
+if not o then return self:servfail(e,t);end
+return e;
+end
+function e:voidsocket(e)
+if self.socket[e]then
+self.socketset[self.socket[e]]=nil;
+self.socket[e]=nil;
+elseif self.socketset[e]then
+self.socket[self.socketset[e]]=nil;
+self.socketset[e]=nil;
+end
+e:close();
+end
+function e:socket_wrapper_set(e)
+self.socket_wrapper=e;
+end
+function e:closeall()
+for t,e in f(self.socket)do
+self.socket[t]=nil;
+self.socketset[e]=nil;
+e:close();
+end
+end
+function e:remember(e,t)
+local a,o,i=g(e.name,e.type,e.class);
+if t~='*'then
+t=o;
+local t=d(self.cache,i,'*',a);
+if t then h(t,e);end
+end
+self.cache=self.cache or r({},w);
+local a=d(self.cache,i,t,a)or
+l(self.cache,i,t,a,r({},k));
+if not a[e[o:lower()]]then
+a[e[o:lower()]]=true;
+h(a,e);
+end
+if t=='MX'then self.unsorted[a]=true;end
+end
+local function c(t,e)
+return(t.pref==e.pref)and(t.mx<e.mx)or(t.pref<e.pref);
+end
+function e:peek(o,t,a,h)
+o,t,a=g(o,t,a);
+local e=d(self.cache,a,t,o);
+if not e then
+if h then if h<=0 then return end else h=3 end
+e=d(self.cache,a,"CNAME",o);
+if not(e and e[1])then return end
+return self:peek(e[1].cname,t,a,h-1);
+end
+if p(e,n.gettime())and t=='*'or not s(e)then
+l(self.cache,a,t,o,nil);
+return nil;
+end
+if self.unsorted[e]then i.sort(e,c);self.unsorted[e]=nil;end
+return e;
+end
+function e:purge(e)
+if e=='soft'then
+self.time=n.gettime();
+for t,e in o(self.cache or{})do
+for t,e in o(e)do
+for t,e in o(e)do
+p(e,self.time,'soft')
+end
+end
+end
+else self.cache=r({},w);end
+end
+function e:query(a,t,e)
+a,t,e=g(a,t,e)
+local s=u.running();
+local o=d(self.wanted,e,t,a);
+if s and o then
+l(self.wanted,e,t,a,s,true);
+return true;
+end
+if not self.server then self:adddefaultnameservers();end
+local h=z(a,t,e);
+local o=self:peek(a,t,e);
+if o then return o;end
+local o,i=v();
+local o={
+packet=o..h,
+server=self.best_server,
+delay=1,
+retry=n.gettime()+self.delays[1]
+};
+self.active[i]=self.active[i]or{};
+self.active[i][h]=o;
+if s then
+l(self.wanted,e,t,a,s,true);
+end
+local i,h=self:getsocket(o.server)
+if not i then
+return nil,h;
+end
+i:send(o.packet)
+if j and self.timeout then
+local r=#self.server;
+local n=1;
+j.add_task(self.timeout,function()
+if d(self.wanted,e,t,a,s)then
+if n<r then
+n=n+1;
+self:servfail(i);
+o.server=self.best_server;
+i,h=self:getsocket(o.server);
+if i then
+i:send(o.packet);
+return self.timeout;
+end
+end
+self:cancel(e,t,a);
+end
+end)
+end
+return true;
+end
+function e:servfail(t,i)
+local h=self.socketset[t]
+t=self:voidsocket(t);
+self.time=n.gettime();
+for n,a in o(self.active)do
+for o,e in o(a)do
+if e.server==h then
+e.server=e.server+1
+if e.server>#self.server then
+e.server=1;
+end
+e.retries=(e.retries or 0)+1;
+if e.retries>=#self.server then
+a[o]=nil;
+else
+t,i=self:getsocket(e.server);
+if t then t:send(e.packet);end
+end
+end
+end
+if s(a)==nil then
+self.active[n]=nil;
+end
+end
+if h==self.best_server then
+self.best_server=self.best_server+1;
+if self.best_server>#self.server then
+self.best_server=1;
+end
+end
+return t,i;
+end
+function e:settimeout(e)
+self.timeout=e;
+end
+function e:receive(t)
+self.time=n.gettime();
+t=t or self.socket;
+local e;
+for a,t in o(t)do
+if self.socketset[t]then
+local t=t:receive();
+if t then
+e=self:decode(t);
+if e and self.active[e.header.id]
+and self.active[e.header.id][e.question.raw]then
+for a,t in o(e.answer)do
+self:remember(t,e.question[1].type)
+end
+local t=self.active[e.header.id];
+t[e.question.raw]=nil;
+if not s(t)then self.active[e.header.id]=nil;end
+if not s(self.active)then self:closeall();end
+local e=e.question[1];
+local t=d(self.wanted,e.class,e.type,e.name);
+if t then
+for e in o(t)do
+if u.status(e)=="suspended"then u.resume(e);end
+end
+l(self.wanted,e.class,e.type,e.name,nil);
+end
+end
+end
+end
+end
+return e;
+end
+function e:feed(a,t,e)
+self.time=n.gettime();
+local e=self:decode(t,e);
+if e and self.active[e.header.id]
+and self.active[e.header.id][e.question.raw]then
+for a,t in o(e.answer)do
+self:remember(t,e.question[1].type);
+end
+local t=self.active[e.header.id];
+t[e.question.raw]=nil;
+if not s(t)then self.active[e.header.id]=nil;end
+if not s(self.active)then self:closeall();end
+local e=e.question[1];
+if e then
+local t=d(self.wanted,e.class,e.type,e.name);
+if t then
+for e in o(t)do
+if u.status(e)=="suspended"then u.resume(e);end
+end
+l(self.wanted,e.class,e.type,e.name,nil);
+end
+end
+end
+return e;
+end
+function e:cancel(e,t,a)
+local i=d(self.wanted,e,t,a);
+if i then
+for e in o(i)do
+if u.status(e)=="suspended"then u.resume(e);end
+end
+l(self.wanted,e,t,a,nil);
+end
+end
+function e:pulse()
+while self:receive()do end
+if not s(self.active)then return nil;end
+self.time=n.gettime();
+for i,t in o(self.active)do
+for a,e in o(t)do
+if self.time>=e.retry then
+e.server=e.server+1;
+if e.server>#self.server then
+e.server=1;
+e.delay=e.delay+1;
+end
+if e.delay>#self.delays then
+t[a]=nil;
+if not s(t)then self.active[i]=nil;end
+if not s(self.active)then return nil;end
+else
+local t=self.socket[e.server];
+if t then t:send(e.packet);end
+e.retry=self.time+self.delays[e.delay];
+end
+end
+end
+end
+if s(self.active)then return true;end
+return nil;
+end
+function e:lookup(o,a,t)
+self:query(o,a,t)
+while self:pulse()do
+local e={}
+for a,t in f(self.socket)do
+e[a]=t
+end
+n.select(e,nil,4)
+end
+return self:peek(o,a,t);
+end
+function e:lookupex(o,e,t,a)
+return self:peek(e,t,a)or self:query(e,t,a);
+end
+function e:tohostname(e)
+return t.lookup(e:gsub("(%d+)%.(%d+)%.(%d+)%.(%d+)","%4.%3.%2.%1.in-addr.arpa."),"PTR");
+end
+local i={
+qr={[0]='query','response'},
+opcode={[0]='query','inverse query','server status request'},
+aa={[0]='non-authoritative','authoritative'},
+tc={[0]='complete','truncated'},
+rd={[0]='recursion not desired','recursion desired'},
+ra={[0]='recursion not available','recursion available'},
+z={[0]='(reserved)'},
+rcode={[0]='no error','format error','server failure','name error','not implemented'},
+type=t.type,
+class=t.class
+};
+local function s(t,e)
+return(i[e]and i[e][t[e]])or'';
+end
+function e.print(t)
+for o,e in o{'id','qr','opcode','aa','tc','rd','ra','z',
+'rcode','qdcount','ancount','nscount','arcount'}do
+m(a.format('%-30s','header.'..e),t.header[e],s(t.header,e));
+end
+for t,e in f(t.question)do
+m(a.format('question[%i].name         ',t),e.name);
+m(a.format('question[%i].type         ',t),e.type);
+m(a.format('question[%i].class        ',t),e.class);
+end
+local r={name=1,type=1,class=1,ttl=1,rdlength=1,rdata=1};
+local e;
+for i,n in o({'answer','authority','additional'})do
+for h,i in o(t[n])do
+for o,t in o({'name','type','class','ttl','rdlength'})do
+e=a.format('%s[%i].%s',n,h,t);
+m(a.format('%-30s',e),i[t],s(i,t));
+end
+for t,o in o(i)do
+if not r[t]then
+e=a.format('%s[%i].%s',n,h,t);
+m(a.format('%-30s  %s',b(e),b(o)));
+end
+end
+end
+end
+end
+function t.resolver()
+local t={active={},cache={},unsorted={},wanted={},best_server=1};
+r(t,e);
+r(t.cache,w);
+r(t.unsorted,{__mode='kv'});
+return t;
+end
+local e=t.resolver();
+t._resolver=e;
+function t.lookup(...)
+return e:lookup(...);
+end
+function t.tohostname(...)
+return e:tohostname(...);
+end
+function t.purge(...)
+return e:purge(...);
+end
+function t.peek(...)
+return e:peek(...);
+end
+function t.query(...)
+return e:query(...);
+end
+function t.feed(...)
+return e:feed(...);
+end
+function t.cancel(...)
+return e:cancel(...);
+end
+function t.settimeout(...)
+return e:settimeout(...);
+end
+function t.cache()
+return e.cache;
+end
+function t.socket_wrapper_set(...)
+return e:socket_wrapper_set(...);
+end
+return t;
+end)
+package.preload['net.adns']=(function(...)
+local _ENV=_ENV;
+local function o(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local c=require"net.server";
+local a=require"net.dns";
+local e=require"util.logger".init("adns");
+local t,t=table.insert,table.remove;
+local n,s,l=coroutine,tostring,pcall;
+local function u(a,a,t,e)return(e-t)+1;end
+o"adns"
+function lookup(d,t,h,r)
+return n.wrap(function(o)
+if o then
+e("debug","Records for %s already cached, using those...",t);
+d(o);
+return;
+end
+e("debug","Records for %s not in cache, sending query (%s)...",t,s(n.running()));
+local i,o=a.query(t,h,r);
+if i then
+n.yield({r or"IN",h or"A",t,n.running()});
+e("debug","Reply for %s (%s)",t,s(n.running()));
+end
+if i then
+i,o=l(d,a.peek(t,h,r));
+else
+e("error","Error sending DNS query: %s",o);
+i,o=l(d,nil,o);
+end
+if not i then
+e("error","Error in DNS response handler: %s",s(o));
+end
+end)(a.peek(t,h,r));
+end
+function cancel(t,o,i)
+e("warn","Cancelling DNS lookup for %s",s(t[3]));
+a.cancel(t[1],t[2],t[3],t[4],o);
+end
+function new_async_socket(i,o)
+local n="<unknown>";
+local s={};
+local t={};
+local h;
+function s.onincoming(o,e)
+if e then
+a.feed(t,e);
+end
+end
+function s.ondisconnect(i,a)
+if a then
+e("warn","DNS socket for %s disconnected: %s",n,a);
+local t=o.server;
+if o.socketset[i]==o.best_server and o.best_server==#t then
+e("error","Exhausted all %d configured DNS servers, next lookup will try %s again",#t,t[1]);
+end
+o:servfail(i);
+end
+end
+t,h=c.wrapclient(i,"dns",53,s);
+if not t then
+return nil,h;
+end
+t.settimeout=function()end
+t.setsockname=function(e,...)return i:setsockname(...);end
+t.setpeername=function(o,...)n=(...);local e,a=i:setpeername(...);o:set_send(u);return e,a;end
+t.connect=function(e,...)return i:connect(...)end
+t.send=function(a,t)
+e("debug","Sending DNS query to %s",n);
+return i:send(t);
+end
+return t;
+end
+a.socket_wrapper_set(new_async_socket);
+return _M;
+end)
+package.preload['net.server']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local m=function(e)
+return _G[e]
+end
+local W,e=require("util.logger").init("socket"),table.concat;
+local n=function(...)return W("debug",e{...});end
+local E=function(...)return W("warn",e{...});end
+local ue=1
+local f=m"type"
+local q=m"pairs"
+local me=m"ipairs"
+local y=m"tonumber"
+local u=m"tostring"
+local t=m"table"
+local a=m"string"
+local e=m"coroutine"
+local Y=math.min
+local ce=math.huge
+local fe=t.concat
+local de=t.insert
+local ye=a.sub
+local we=e.wrap
+local pe=e.yield
+local I,e=pcall(require,"ssl")
+local b=m"socket"or require"socket"
+local P=b.gettime
+local le=b.dns.getaddrinfo
+local ve=(I and e.wrap)
+local se=b.bind
+local be=b.select
+local O
+local Z
+local ie
+local V
+local B
+local l
+local ne
+local X
+local oe
+local te
+local ae
+local J
+local s
+local he
+local ee
+local re
+local p
+local i
+local C
+local r
+local h
+local A
+local v
+local w
+local k
+local z
+local a
+local o
+local g
+local F
+local M
+local T
+local j
+local R
+local Q
+local d
+local S
+local L
+local U
+local D
+local H
+local _
+local x
+local N
+p={}
+i={}
+r={}
+C={}
+h={}
+v={}
+w={}
+A={}
+k={}
+a=0
+o=0
+g=0
+F=0
+M=0
+T=1
+j=128
+R=10
+S=51e3*1024
+L=25e3*1024
+U=30
+D=6e4
+H=14*60
+local e=package.config:sub(1,1)=="\\"
+x=(e and math.huge)or b._SETSIZE or 1024
+_=b._SETSIZE or 1024
+N=30
+te=function(y,t,m,c,g,w)
+if t:getfd()>=x then
+E("server.lua: Disallowed FD number: "..t:getfd())
+t:close()
+return nil,"fd-too-large"
+end
+local f=0
+local v,e=y.onconnect,y.ondisconnect
+local b=t.accept
+local e={}
+e.shutdown=function()end
+e.ssl=function()
+return w~=nil
+end
+e.sslctx=function()
+return w
+end
+e.remove=function()
+f=f-1
+if e then
+e.resume()
+end
+end
+e.close=function()
+t:close()
+o=s(r,t,o)
+a=s(i,t,a)
+p[m..":"..c]=nil;
+h[t]=nil
+e=nil
+t=nil
+n"server.lua: closed server handler and removed sockets from list"
+end
+e.pause=function(o)
+if not e.paused then
+a=s(i,t,a)
+if o then
+h[t]=nil
+t:close()
+t=nil;
+end
+e.paused=true;
+n("server.lua: server [",m,"]:",c," paused")
+end
+end
+e.resume=function()
+if e.paused then
+if not t then
+t=se(m,c,j);
+t:settimeout(0)
+end
+a=l(i,t,a)
+h[t]=e
+k[e]=nil
+e.paused=false;
+n("server.lua: server [",m,"]:",c," resumed")
+end
+end
+e.ip=function()
+return m
+end
+e.serverport=function()
+return c
+end
+e.socket=function()
+return t
+end
+e.readbuffer=function()
+if a>=_ or o>=_ then
+e.pause()
+k[e]=d
+n("server.lua: refused new client connection: server full")
+return false
+end
+local t,o=b(t)
+if t then
+local o,a=t:getpeername()
+local e,i,t=ee(e,y,t,o,c,a,g,w)
+if t then
+return false
+end
+f=f+1
+n("server.lua: accepted new client connection from ",u(o),":",u(a)," to ",u(c))
+if v and not w then
+return v(e);
+end
+return;
+elseif o then
+n("server.lua: error with new client connection: ",u(o))
+e.pause()
+k[e]=d
+return false
+end
+end
+return e
+end
+ee=function(b,f,t,R,X,D,H,q)
+if t:getfd()>=x then
+E("server.lua: Disallowed FD number: "..t:getfd())
+t:close()
+if b then
+k[b]=d
+b.pause()
+end
+return nil,nil,"fd-too-large"
+end
+t:settimeout(0)
+local y
+local T
+local g
+local K
+local G=f.onincoming
+local B=f.onstatus
+local k=f.ondisconnect
+local W=f.ondrain
+local J=f.onreadtimeout;
+local C=f.ondetach
+local p={}
+local c=0
+local Q
+local U
+local m=0
+local j=false
+local E=false
+local Y,P=0,0
+local x=S
+local _=L
+local e=p
+e.dispatch=function()
+return G
+end
+e.disconnect=function()
+return k
+end
+e.onreadtimeout=J;
+e.setlistener=function(a,t)
+if C then
+C(a)
+end
+G=t.onincoming
+k=t.ondisconnect
+B=t.onstatus
+W=t.ondrain
+e.onreadtimeout=t.onreadtimeout
+C=t.ondetach
+end
+e.getstats=function()
+return P,Y
+end
+e.ssl=function()
+return K
+end
+e.sslctx=function()
+return q
+end
+e.send=function(n,a,o,i)
+return y(t,a,o,i)
+end
+e.receive=function(o,a)
+return T(t,o,a)
+end
+e.shutdown=function(a)
+return g(t,a)
+end
+e.setoption=function(i,a,o)
+if t.setoption then
+return t:setoption(a,o);
+end
+return false,"setoption not implemented";
+end
+e.force_close=function(t,a)
+if c~=0 then
+n("server.lua: discarding unwritten data for ",u(R),":",u(D))
+c=0;
+end
+return t:close(a);
+end
+e.close=function(l,d)
+if not e then return true;end
+a=s(i,t,a)
+v[e]=nil
+if c~=0 then
+e.sendbuffer()
+if c~=0 then
+if e then
+e.write=nil
+end
+Q=true
+return false
+end
+end
+if t then
+z=g and g(t)
+t:close()
+o=s(r,t,o)
+h[t]=nil
+t=nil
+else
+n"server.lua: socket already closed"
+end
+if e then
+w[e]=nil
+A[e]=nil
+local t=e;
+e=nil
+if k then
+k(t,d or false);
+k=nil
+end
+end
+if b then
+b.remove()
+end
+n"server.lua: closed client handler and removed socket from list"
+return true
+end
+e.server=function()
+return b
+end
+e.ip=function()
+return R
+end
+e.serverport=function()
+return X
+end
+e.clientport=function()
+return D
+end
+e.port=e.clientport
+local b=function(i,a)
+if not e then return false end
+m=m+#a
+if m>x then
+A[e]="send buffer exceeded"
+e.write=V
+return false
+elseif t and not r[t]then
+o=l(r,t,o)
+end
+c=c+1
+p[c]=a
+if e then
+w[e]=w[e]or d
+end
+return true
+end
+e.write=b
+e.bufferqueue=function(t)
+return p
+end
+e.socket=function(a)
+return t
+end
+e.set_mode=function(a,t)
+H=t or H
+return H
+end
+e.set_send=function(a,t)
+y=t or y
+return y
+end
+e.bufferlen=function(o,a,t)
+x=t or x
+_=a or _
+return m,_,x
+end
+e.lock_read=function(n,o)
+if o==true then
+local o=a
+a=s(i,t,a)
+v[e]=nil
+if a~=o then
+j=true
+end
+elseif o==false then
+if j then
+j=false
+a=l(i,t,a)
+v[e]=d
+end
+end
+return j
+end
+e.pause=function(t)
+return t:lock_read(true);
+end
+e.resume=function(t)
+return t:lock_read(false);
+end
+e.lock=function(i,a)
+e.lock_read(a)
+if a==true then
+e.write=V
+local a=o
+o=s(r,t,o)
+w[e]=nil
+if o~=a then
+E=true
+end
+elseif a==false then
+e.write=b
+if E then
+E=false
+b("")
+end
+end
+return j,E
+end
+local v=function()
+local a,t,o=T(t,H)
+if not t or(t=="wantread"or t=="timeout")then
+local o=a or o or""
+local a=#o
+if a>_ then
+e:close("receive buffer exceeded")
+return false
+end
+local a=a*ue
+P=P+a
+M=M+a
+v[e]=d
+return G(e,o,t)
+else
+n("server.lua: client ",u(R),":",u(D)," read error: ",u(t))
+z=e and e:force_close(t)
+return false
+end
+end
+local p=function()
+local f,a,h,i,l;
+if t then
+i=fe(p,"",1,c)
+f,a,h=y(t,i,1,m)
+l=(f or h or 0)*ue
+Y=Y+l
+F=F+l
+for e=c,1,-1 do
+p[e]=nil
+end
+else
+f,a,l=false,"unexpected close",0;
+end
+if f then
+c=0
+m=0
+o=s(r,t,o)
+w[e]=nil
+if W then
+W(e)
+end
+z=U and e:starttls(nil)
+z=Q and e:force_close()
+return true
+elseif h and(a=="timeout"or a=="wantwrite")then
+i=ye(i,h+1,m)
+p[1]=i
+c=1
+m=m-h
+w[e]=d
+return true
+else
+n("server.lua: client ",u(R),":",u(D)," write error: ",u(a))
+z=e and e:force_close(a)
+return false
+end
+end
+local d;
+function e.set_sslctx(w,t)
+q=t;
+local u,m
+d=we(function(h)
+local t
+for d=1,N do
+o=(m and s(r,h,o))or o
+a=(u and s(i,h,a))or a
+u,m=nil,nil
+d,t=h:dohandshake()
+if not t then
+n("server.lua: ssl handshake done")
+e.readbuffer=v
+e.sendbuffer=p
+d=B and B(e,"ssl-handshake-complete")
+if w.autostart_ssl and f.onconnect then
+f.onconnect(w);
+if c~=0 then
+o=l(r,h,o)
+end
+end
+a=l(i,h,a)
+return true
+else
+if t=="wantwrite"then
+o=l(r,h,o)
+m=true
+elseif t=="wantread"then
+a=l(i,h,a)
+u=true
+else
+break;
+end
+t=nil;
+pe()
+end
+end
+t="ssl handshake error: "..(t or"handshake too long");
+n("server.lua: ",t);
+z=e and e:force_close(t)
+return false,t
+end
+)
+end
+if I then
+e.starttls=function(f,m)
+if m then
+e:set_sslctx(m);
+end
+if c>0 then
+n"server.lua: we need to do tls, but delaying until send buffer empty"
+U=true
+return
+end
+n("server.lua: attempting to start tls on "..u(t))
+local m,c=t
+t,c=ve(t,q)
+if not t then
+n("server.lua: error while starting tls on client: ",u(c or"unknown error"))
+return nil,c
+end
+t:settimeout(0)
+y=t.send
+T=t.receive
+g=O
+h[t]=e
+a=l(i,t,a)
+a=s(i,m,a)
+o=s(r,m,o)
+h[m]=nil
+e.starttls=nil
+U=nil
+K=true
+e.readbuffer=d
+e.sendbuffer=d
+return d(t)
+end
+end
+e.readbuffer=v
+e.sendbuffer=p
+y=t.send
+T=t.receive
+g=(K and O)or t.shutdown
+h[t]=e
+a=l(i,t,a)
+if q and I then
+n"server.lua: auto-starting ssl negotiation..."
+e.autostart_ssl=true;
+local t,e=e:starttls(q);
+if t==false then
+return nil,nil,e
+end
+end
+return e,t
+end
+O=function()
+end
+V=function()
+return false
+end
+l=function(t,a,e)
+if not t[a]then
+e=e+1
+t[e]=a
+t[a]=e
+end
+return e;
+end
+s=function(e,o,t)
+local i=e[o]
+if i then
+e[o]=nil
+local a=e[t]
+e[t]=nil
+if a~=o then
+e[a]=i
+e[i]=a
+end
+return t-1
+end
+return t
+end
+J=function(e)
+o=s(r,e,o)
+a=s(i,e,a)
+h[e]=nil
+e:close()
+end
+local function z(e,a,o)
+local t;
+local i=a.sendbuffer;
+function a.sendbuffer()
+i();
+if t and a.bufferlen()<o then
+e:lock_read(false);
+t=nil;
+end
+end
+local i=e.readbuffer;
+function e.readbuffer()
+i();
+if not t and a.bufferlen()>=o then
+t=true;
+e:lock_read(true);
+end
+end
+e:set_mode("*a");
+end
+ne=function(e,t,d,u,r)
+e=e or"*"
+local o
+if f(d)~="table"then
+o="invalid listener table"
+elseif f(e)~="string"then
+o="invalid address"
+elseif f(t)~="number"or not(t>=0 and t<=65535)then
+o="invalid port"
+elseif p[e..":"..t]then
+o="listeners on '["..e.."]:"..t.."' already exist"
+elseif r and not I then
+o="luasec not found"
+end
+if o then
+E("server.lua, [",e,"]:",t,": ",o)
+return nil,o
+end
+local o,s=se(e,t,j)
+if s then
+E("server.lua, [",e,"]:",t,": ",s)
+return nil,s
+end
+local s,d=te(d,o,e,t,u,r)
+if not s then
+o:close()
+return nil,d
+end
+o:settimeout(0)
+a=l(i,o,a)
+p[e..":"..t]=s
+h[o]=s
+n("server.lua: new "..(r and"ssl "or"").."server listener on '[",e,"]:",t,"'")
+return s
+end
+oe=function(e,t)
+return p[e..":"..t];
+end
+he=function(e,t)
+local a=p[e..":"..t]
+if not a then
+return nil,"no server found on '["..e.."]:"..u(t).."'"
+end
+a:close()
+p[e..":"..t]=nil
+return true
+end
+B=function()
+for e,t in q(h)do
+t:close()
+h[e]=nil
+end
+a=0
+o=0
+g=0
+p={}
+i={}
+r={}
+C={}
+h={}
+end
+ae=function()
+return{
+select_timeout=T;
+tcp_backlog=j;
+max_send_buffer_size=S;
+max_receive_buffer_size=L;
+select_idle_check_interval=U;
+send_timeout=D;
+read_timeout=H;
+max_connections=_;
+max_ssl_handshake_roundtrips=N;
+highest_allowed_fd=x;
+accept_retry_interval=R;
+}
+end
+re=function(e)
+if f(e)~="table"then
+return nil,"invalid settings table"
+end
+T=y(e.select_timeout)or T
+S=y(e.max_send_buffer_size)or S
+L=y(e.max_receive_buffer_size)or L
+U=y(e.select_idle_check_interval)or U
+j=y(e.tcp_backlog)or j
+D=y(e.send_timeout)or D
+H=y(e.read_timeout)or H
+R=y(e.accept_retry_interval)or R
+_=e.max_connections or _
+N=e.max_ssl_handshake_roundtrips or N
+x=e.highest_allowed_fd or x
+return true
+end
+X=function(e)
+if f(e)~="function"then
+return nil,"invalid listener function"
+end
+g=g+1
+C[g]=e
+return true
+end
+local u do
+local o={};
+local t={};
+function u(e,a)
+local o=P();
+e=e+o;
+if e>=o then
+de(t,{e,a});
+else
+local e=a(o);
+if e and f(e)=="number"then
+return u(e,a);
+end
+end
+end
+X(function(a)
+if#t>0 then
+for a,e in q(t)do
+de(o,e);
+end
+t={};
+end
+local e=ce;
+for s,t in q(o)do
+local i,n=t[1],t[2];
+if i<=a then
+o[s]=nil;
+local t=n(a);
+if f(t)=="number"then
+u(t,n);
+e=Y(e,t);
+end
+else
+e=Y(e,i-a);
+end
+end
+return e;
+end);
+end
+ie=function()
+return M,F,a,o,g
+end
+local e;
+local function c(t)
+e=t;
+end
+Z=function(t)
+if e then return"quitting";end
+if t then e="once";end
+d=P()
+repeat
+local t=ce;
+for e=1,g do
+local e=C[e](d)
+if e then t=Y(t,e);end
+end
+local t,a,o=be(i,r,Y(T,t))
+for t,e in me(t)do
+local t=h[e]
+if t then
+t.readbuffer()
+else
+J(e)
+n"server.lua: found no handler and closed socket (readlist)"
+end
+end
+for e,t in me(a)do
+local e=h[t]
+if e then
+e.sendbuffer()
+else
+J(t)
+n"server.lua: found no handler and closed socket (writelist)"
+end
+end
+for e,t in q(A)do
+e.disconnect()(e,t)
+e:force_close()
+A[e]=nil;
+end
+d=P()
+if d-Q>U then
+Q=d
+for e,t in q(w)do
+if d-t>D then
+e.disconnect()(e,"send timeout")
+e:force_close()
+end
+end
+for e,t in q(v)do
+if d-t>H then
+if not(e.onreadtimeout)or e:onreadtimeout()~=true then
+e.disconnect()(e,"read timeout")
+e:close()
+else
+v[e]=d
+end
+end
+end
+end
+for e,t in q(k)do
+if d-t>R then
+k[e]=nil;
+e.resume();
+end
+end
+until e;
+if e=="once"then e=nil;return;end
+B();
+return"quitting"
+end
+local function y()
+return Z(true);
+end
+local function g()
+return"select";
+end
+local n=function(t,d,u,n,e,s)
+local e,t,d=ee(nil,n,t,d,u,"clientport",e,s)
+if not e then return nil,d end
+h[t]=e
+if not s then
+a=l(i,t,a)
+o=l(r,t,o)
+if n.onconnect then
+local t=e.sendbuffer;
+e.sendbuffer=function()
+e.sendbuffer=t;
+n.onconnect(e);
+return t();
+end
+end
+end
+return e,t
+end
+local b=function(a,t,s,r,i,o)
+local e
+if f(s)~="table"then
+e="invalid listener table"
+elseif f(a)~="string"then
+e="invalid address"
+elseif f(t)~="number"or not(t>=0 and t<=65535)then
+e="invalid port"
+elseif i and not I then
+e="luasec not found"
+end
+if le and not o then
+local e,t=le(a)
+if not e then return nil,t end
+if e[1]and e[1].family=="inet6"then
+o="tcp6"
+end
+end
+local o=b[o or"tcp"]
+if f(o)~="function"then
+e="invalid socket type"
+end
+if e then
+E("server.lua, addclient: ",e)
+return nil,e
+end
+local o,e=o()
+if e then
+return nil,e
+end
+o:settimeout(0)
+local h,e=o:connect(a,t)
+if h or e=="timeout"or e=="Operation already in progress"then
+return n(o,a,t,s,r,i)
+else
+return nil,e
+end
+end
+local p=function(e)
+local e=e.conn;
+o=s(r,e,o)
+a=s(i,e,a)
+h[e]=nil
+end;
+local t=function(t,n,d)
+local e=t.conn
+h[e]=t
+if n~=nil then
+if n then
+a=l(i,e,a)
+else
+o=s(r,e,o)
+end
+end
+if d~=nil then
+if d then
+o=l(r,e,o)
+else
+a=s(i,e,a)
+end
+end
+end
+local a=function(e,a,i)
+local o=e
+if f(e)=="number"then
+o={getfd=function()return e;end}
+end
+local e={
+conn=o;
+readbuffer=a or O;
+sendbuffer=i or O;
+close=p;
+setflags=t;
+};
+t(e,a,i)
+return e
+end
+m"setmetatable"(h,{__mode="k"})
+m"setmetatable"(v,{__mode="k"})
+m"setmetatable"(w,{__mode="k"})
+Q=P()
+local function t(e)
+local t=W;
+if e then
+W=e;
+end
+return t;
+end
+return{
+_addtimer=X,
+add_task=u;
+addclient=b,
+wrapclient=n,
+watchfd=a,
+loop=Z,
+link=z,
+step=y,
+stats=ie,
+closeall=B,
+addserver=ne,
+getserver=oe,
+setlogger=t,
+getsettings=ae,
+setquitting=c,
+removeserver=he,
+get_backend=g,
+changesettings=re,
+}
+end)
+package.preload['util.xmppstream']=(function(...)
+local _ENV=_ENV;
+local function o(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local e=require"lxp";
+local t=require"util.stanza";
+local g=t.stanza_mt;
+local c=error;
+local t=tostring;
+local d=table.insert;
+local p=table.concat;
+local k=table.remove;
+local v=setmetatable;
+local q=pcall(e.new,{StartDoctypeDecl=false});
+local x=pcall(e.new,{XmlDecl=false});
+local a=not not e.new({}).getcurrentbytecount;
+local j=1024*1024*10;
+o"xmppstream"
+local b=e.new;
+local z={
+["http://www.w3.org/XML/1998/namespace\1lang"]="xml:lang";
+["http://www.w3.org/XML/1998/namespace\1space"]="xml:space";
+["http://www.w3.org/XML/1998/namespace\1base"]="xml:base";
+["http://www.w3.org/XML/1998/namespace\1id"]="xml:id";
+};
+local o="http://etherx.jabber.org/streams";
+local r="\1";
+local y="^([^"..r.."]*)"..r.."?(.*)$";
+_M.ns_separator=r;
+_M.ns_pattern=y;
+local function h()end
+function new_sax_handlers(n,e,s)
+local i={};
+local w=e.streamopened;
+local f=e.streamclosed;
+local l=e.error or function(o,a,e)c("XML stream error: "..t(a)..(e and": "..t(e)or""),2);end;
+local b=e.handlestanza;
+s=s or h;
+local t=e.stream_ns or o;
+local m=e.stream_tag or"stream";
+if t~=""then
+m=t..r..m;
+end
+local j=t..r..(e.error_tag or"error");
+local _=e.default_ns;
+local u={};
+local h,e={};
+local t=0;
+local r=0;
+function i:StartElement(c,o)
+if e and#h>0 then
+d(e,p(h));
+h={};
+end
+local h,i=c:match(y);
+if i==""then
+h,i="",h;
+end
+if h~=_ or r>0 then
+o.xmlns=h;
+r=r+1;
+end
+for t=1,#o do
+local e=o[t];
+o[t]=nil;
+local t=z[e];
+if t then
+o[t]=o[e];
+o[e]=nil;
+end
+end
+if not e then
+if a then
+t=self:getcurrentbytecount();
+end
+if n.notopen then
+if c==m then
+r=0;
+if w then
+if a then
+s(t);
+t=0;
+end
+w(n,o);
+end
+else
+l(n,"no-stream",c);
+end
+return;
+end
+if h=="jabber:client"and i~="iq"and i~="presence"and i~="message"then
+l(n,"invalid-top-level-element");
+end
+e=v({name=i,attr=o,tags={}},g);
+else
+if a then
+t=t+self:getcurrentbytecount();
+end
+d(u,e);
+local t=e;
+e=v({name=i,attr=o,tags={}},g);
+d(t,e);
+d(t.tags,e);
+end
+end
+if x then
+function i:XmlDecl(e,e,e)
+if a then
+s(self:getcurrentbytecount());
+end
+end
+end
+function i:StartCdataSection()
+if a then
+if e then
+t=t+self:getcurrentbytecount();
+else
+s(self:getcurrentbytecount());
+end
+end
+end
+function i:EndCdataSection()
+if a then
+if e then
+t=t+self:getcurrentbytecount();
+else
+s(self:getcurrentbytecount());
+end
+end
+end
+function i:CharacterData(o)
+if e then
+if a then
+t=t+self:getcurrentbytecount();
+end
+d(h,o);
+elseif a then
+s(self:getcurrentbytecount());
+end
+end
+function i:EndElement(o)
+if a then
+t=t+self:getcurrentbytecount()
+end
+if r>0 then
+r=r-1;
+end
+if e then
+if#h>0 then
+d(e,p(h));
+h={};
+end
+if#u==0 then
+if a then
+s(t);
+end
+t=0;
+if o~=j then
+b(n,e);
+else
+l(n,"stream-error",e);
+end
+e=nil;
+else
+e=k(u);
+end
+else
+if f then
+f(n);
+end
+end
+end
+local function a(e)
+l(n,"parse-error","restricted-xml","Restricted XML, see RFC 6120 section 11.1.");
+if not e.stop or not e:stop()then
+c("Failed to abort parsing");
+end
+end
+if q then
+i.StartDoctypeDecl=a;
+end
+i.Comment=a;
+i.ProcessingInstruction=a;
+local function a()
+e,h,t=nil,{},0;
+u={};
+end
+local function t(t,e)
+n=e;
+end
+return i,{reset=a,set_session=t};
+end
+function new(i,n,t)
+local e=0;
+local o;
+if a then
+function o(a)
+e=e-a;
+end
+t=t or j;
+elseif t then
+c("Stanza size limits are not supported on this version of LuaExpat")
+end
+local i,s=new_sax_handlers(i,n,o);
+local o=b(i,r,false);
+local n=o.parse;
+return{
+reset=function()
+o=b(i,r,false);
+n=o.parse;
+e=0;
+s.reset();
+end,
+feed=function(s,i)
+if a then
+e=e+#i;
+end
+local o,i=n(o,i);
+if a and e>t then
+return nil,"stanza-too-large";
+end
+return o,i;
+end,
+set_session=s.set_session;
+};
+end
+return _M;
+end)
+package.preload['util.jid']=(function(...)
+local _ENV=_ENV;
+local function a(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local t,h=string.match,string.sub;
+local r=require"util.encodings".stringprep.nodeprep;
+local d=require"util.encodings".stringprep.nameprep;
+local l=require"util.encodings".stringprep.resourceprep;
+local n={
+[" "]="\\20";['"']="\\22";
+["&"]="\\26";["'"]="\\27";
+["/"]="\\2f";[":"]="\\3a";
+["<"]="\\3c";[">"]="\\3e";
+["@"]="\\40";["\\"]="\\5c";
+};
+local s={};
+for t,e in pairs(n)do s[e]=t;end
+a"jid"
+local function a(e)
+if not e then return;end
+local i,a=t(e,"^([^@/]+)@()");
+local a,o=t(e,"^([^@/]+)()",a)
+if i and not a then return nil,nil,nil;end
+local t=t(e,"^/(.+)$",o);
+if(not a)or((not t)and#e>=o)then return nil,nil,nil;end
+return i,a,t;
+end
+split=a;
+function bare(e)
+local t,e=a(e);
+if t and e then
+return t.."@"..e;
+end
+return e;
+end
+local function o(e)
+local t,e,a=a(e);
+if e then
+if h(e,-1,-1)=="."then
+e=h(e,1,-2);
+end
+e=d(e);
+if not e then return;end
+if t then
+t=r(t);
+if not t then return;end
+end
+if a then
+a=l(a);
+if not a then return;end
+end
+return t,e,a;
+end
+end
+prepped_split=o;
+function prep(e)
+local t,e,a=o(e);
+if e then
+if t then
+e=t.."@"..e;
+end
+if a then
+e=e.."/"..a;
+end
+end
+return e;
+end
+function join(t,e,a)
+if t and e and a then
+return t.."@"..e.."/"..a;
+elseif t and e then
+return t.."@"..e;
+elseif e and a then
+return e.."/"..a;
+elseif e then
+return e;
+end
+return nil;
+end
+function compare(t,e)
+local n,o,i=a(t);
+local a,t,e=a(e);
+if((a~=nil and a==n)or a==nil)and
+((t~=nil and t==o)or t==nil)and
+((e~=nil and e==i)or e==nil)then
+return true
+end
+return false
+end
+function escape(e)return e and(e:gsub(".",n));end
+function unescape(e)return e and(e:gsub("\\%x%x",s));end
+return _M;
+end)
+package.preload['util.events']=(function(...)
+local _ENV=_ENV;
+local function a(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local i=pairs;
+local o=table.insert;
+local n=table.sort;
+local s=setmetatable;
+local h=next;
+a"events"
+function new()
+local t={};
+local e={};
+local function r(s,a)
+local e=e[a];
+if not e or h(e)==nil then return;end
+local t={};
+for e in i(e)do
+o(t,e);
+end
+n(t,function(a,t)return e[a]>e[t];end);
+s[a]=t;
+return t;
+end;
+s(t,{__index=r});
+local function s(o,n,i)
+local a=e[o];
+if a then
+a[n]=i or 0;
+else
+a={[n]=i or 0};
+e[o]=a;
+end
+t[o]=nil;
+end;
+local function n(o,i)
+local a=e[o];
+if a then
+a[i]=nil;
+t[o]=nil;
+if h(a)==nil then
+e[o]=nil;
+end
+end
+end;
+local function h(e)
+for t,e in i(e)do
+s(t,e);
+end
+end;
+local function o(e)
+for e,t in i(e)do
+n(e,t);
+end
+end;
+local function a(e,...)
+local e=t[e];
+if e then
+for t=1,#e do
+local e=e[t](...);
+if e~=nil then return e;end
+end
+end
+end;
+return{
+add_handler=s;
+remove_handler=n;
+add_handlers=h;
+remove_handlers=o;
+fire_event=a;
+_handlers=t;
+_event_map=e;
+};
+end
+return _M;
+end)
+package.preload['util.dataforms']=(function(...)
+local _ENV=_ENV;
+local function o(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local a=setmetatable;
+local e,i=pairs,ipairs;
+local n,s,c=tostring,type,next;
+local r=table.concat;
+local u=require"util.stanza";
+local d=require"util.jid".prep;
+o"dataforms"
+local l='jabber:x:data';
+local h={};
+local t={__index=h};
+function new(e)
+return a(e,t);
+end
+function from_stanza(e)
+local o={
+title=e:get_child_text("title");
+instructions=e:get_child_text("instructions");
+};
+for e in e:childtags("field")do
+local a={
+name=e.attr.var;
+label=e.attr.label;
+type=e.attr.type;
+required=e:get_child("required")and true or nil;
+value=e:get_child_text("value");
+};
+o[#o+1]=a;
+if a.type then
+local t={};
+if a.type:match"list%-"then
+for e in e:childtags("option")do
+t[#t+1]={label=e.attr.label,value=e:get_child_text("value")};
+end
+for e in e:childtags("value")do
+t[#t+1]={label=e.attr.label,value=e:get_text(),default=true};
+end
+elseif a.type:match"%-multi"then
+for e in e:childtags("value")do
+t[#t+1]=e.attr.label and{label=e.attr.label,value=e:get_text()}or e:get_text();
+end
+if a.type=="text-multi"then
+a.value=r(t,"\n");
+else
+a.value=t;
+end
+end
+end
+end
+return new(o);
+end
+function h.form(t,h,e)
+local e=u.stanza("x",{xmlns=l,type=e or"form"});
+if t.title then
+e:tag("title"):text(t.title):up();
+end
+if t.instructions then
+e:tag("instructions"):text(t.instructions):up();
+end
+for t,o in i(t)do
+local a=o.type or"text-single";
+e:tag("field",{type=a,var=o.name,label=o.label});
+local t=(h and h[o.name])or o.value;
+if t then
+if a=="hidden"then
+if s(t)=="table"then
+e:tag("value")
+:add_child(t)
+:up();
+else
+e:tag("value"):text(n(t)):up();
+end
+elseif a=="boolean"then
+e:tag("value"):text((t and"1")or"0"):up();
+elseif a=="fixed"then
+elseif a=="jid-multi"then
+for a,t in i(t)do
+e:tag("value"):text(t):up();
+end
+elseif a=="jid-single"then
+e:tag("value"):text(t):up();
+elseif a=="text-single"or a=="text-private"then
+e:tag("value"):text(t):up();
+elseif a=="text-multi"then
+for t in t:gmatch("([^\r\n]+)\r?\n*")do
+e:tag("value"):text(t):up();
+end
+elseif a=="list-single"then
+local a=false;
+for o,t in i(t)do
+if s(t)=="table"then
+e:tag("option",{label=t.label}):tag("value"):text(t.value):up():up();
+if t.default and(not a)then
+e:tag("value"):text(t.value):up();
+a=true;
+end
+else
+e:tag("option",{label=t}):tag("value"):text(n(t)):up():up();
+end
+end
+elseif a=="list-multi"then
+for a,t in i(t)do
+if s(t)=="table"then
+e:tag("option",{label=t.label}):tag("value"):text(t.value):up():up();
+if t.default then
+e:tag("value"):text(t.value):up();
+end
+else
+e:tag("option",{label=t}):tag("value"):text(n(t)):up():up();
+end
+end
+end
+end
+if o.required then
+e:tag("required"):up();
+end
+e:up();
+end
+return e;
+end
+local e={};
+function h.data(t,n)
+local o={};
+local a={};
+for i,t in i(t)do
+local i;
+for e in n:childtags()do
+if t.name==e.attr.var then
+i=e;
+break;
+end
+end
+if not i then
+if t.required then
+a[t.name]="Required value missing";
+end
+else
+local e=e[t.type];
+if e then
+o[t.name],a[t.name]=e(i,t.required);
+end
+end
+end
+if c(a)then
+return o,a;
+end
+return o;
+end
+e["text-single"]=
+function(t,a)
+local t=t:get_child_text("value");
+if t and#t>0 then
+return t
+elseif a then
+return nil,"Required value missing";
+end
+end
+e["text-private"]=
+e["text-single"];
+e["jid-single"]=
+function(t,o)
+local a=t:get_child_text("value")
+local t=d(a);
+if t and#t>0 then
+return t
+elseif a then
+return nil,"Invalid JID: "..a;
+elseif o then
+return nil,"Required value missing";
+end
+end
+e["jid-multi"]=
+function(o,i)
+local a={};
+local t={};
+for e in o:childtags("value")do
+local e=e:get_text();
+local o=d(e);
+a[#a+1]=o;
+if e and not o then
+t[#t+1]=("Invalid JID: "..e);
+end
+end
+if#a>0 then
+return a,(#t>0 and r(t,"\n")or nil);
+elseif i then
+return nil,"Required value missing";
+end
+end
+e["list-multi"]=
+function(a,o)
+local t={};
+for e in a:childtags("value")do
+t[#t+1]=e:get_text();
+end
+return t,(o and#t==0 and"Required value missing"or nil);
+end
+e["text-multi"]=
+function(t,a)
+local t,a=e["list-multi"](t,a);
+if t then
+t=r(t,"\n");
+end
+return t,a;
+end
+e["list-single"]=
+e["text-single"];
+local a={
+["1"]=true,["true"]=true,
+["0"]=false,["false"]=false,
+};
+e["boolean"]=
+function(t,o)
+local t=t:get_child_text("value");
+local a=a[t~=nil and t];
+if a~=nil then
+return a;
+elseif t then
+return nil,"Invalid boolean representation";
+elseif o then
+return nil,"Required value missing";
+end
+end
+e["hidden"]=
+function(e)
+return e:get_child_text("value");
+end
+return _M;
+end)
+package.preload['util.caps']=(function(...)
+local _ENV=_ENV;
+local function a(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local d=require"util.encodings".base64.encode;
+local l=require"util.hashes".sha1;
+local n,s,h=table.insert,table.sort,table.concat;
+local r=ipairs;
+a"caps"
+function calculate_hash(e)
+local i,o,a={},{},{};
+for t,e in r(e)do
+if e.name=="identity"then
+n(i,(e.attr.category or"").."\0"..(e.attr.type or"").."\0"..(e.attr["xml:lang"]or"").."\0"..(e.attr.name or""));
+elseif e.name=="feature"then
+n(o,e.attr.var or"");
+elseif e.name=="x"and e.attr.xmlns=="jabber:x:data"then
+local t={};
+local o;
+for a,e in r(e.tags)do
+if e.name=="field"and e.attr.var then
+local a={};
+for t,e in r(e.tags)do
+e=#e.tags==0 and e:get_text();
+if e then n(a,e);end
+end
+s(a);
+if e.attr.var=="FORM_TYPE"then
+o=a[1];
+elseif#a>0 then
+n(t,e.attr.var.."\0"..h(a,"<"));
+else
+n(t,e.attr.var);
+end
+end
+end
+s(t);
+t=h(t,"<");
+if o then t=o.."\0"..t;end
+n(a,t);
+end
+end
+s(i);
+s(o);
+s(a);
+if#i>0 then i=h(i,"<"):gsub("%z","/").."<";else i="";end
+if#o>0 then o=h(o,"<").."<";else o="";end
+if#a>0 then a=h(a,"<"):gsub("%z","<").."<";else a="";end
+local e=i..o..a;
+local t=d(l(e));
+return t,e;
+end
+return _M;
+end)
+package.preload['util.vcard']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local n=require"util.stanza";
+local a,c=table.insert,table.concat;
+local h=type;
+local e,r,f=next,pairs,ipairs;
+local d,l,u,m;
+local w="\n";
+local i;
+local function e()
+error"Not implemented"
+end
+local function e()
+error"Not implemented"
+end
+local function y(e)
+return e:gsub("[,:;\\]","\\%1"):gsub("\n","\\n");
+end
+local function s(e)
+return e:gsub("\\?[\\nt:;,]",{
+["\\\\"]="\\",
+["\\n"]="\n",
+["\\r"]="\r",
+["\\t"]="\t",
+["\\:"]=":",
+["\\;"]=";",
+["\\,"]=",",
+[":"]="\29",
+[";"]="\30",
+[","]="\31",
+});
+end
+local function p(e)
+local a=n.stanza(e.name,{xmlns="vcard-temp"});
+local t=i[e.name];
+if t=="text"then
+a:text(e[1]);
+elseif h(t)=="table"then
+if t.types and e.TYPE then
+if h(e.TYPE)=="table"then
+for o,t in r(t.types)do
+for o,e in r(e.TYPE)do
+if e:upper()==t then
+a:tag(t):up();
+break;
+end
+end
+end
+else
+a:tag(e.TYPE:upper()):up();
+end
+end
+if t.props then
+for o,t in r(t.props)do
+if e[t]then
+a:tag(t):up();
+end
+end
+end
+if t.value then
+a:tag(t.value):text(e[1]):up();
+elseif t.values then
+local o=t.values;
+local i=o.behaviour=="repeat-last"and o[#o];
+for o=1,#e do
+a:tag(t.values[o]or i):text(e[o]):up();
+end
+end
+end
+return a;
+end
+local function o(t)
+local e=n.stanza("vCard",{xmlns="vcard-temp"});
+for a=1,#t do
+e:add_child(p(t[a]));
+end
+return e;
+end
+function m(e)
+if not e[1]or e[1].name then
+return o(e)
+else
+local t=n.stanza("xCard",{xmlns="vcard-temp"});
+for a=1,#e do
+t:add_child(o(e[a]));
+end
+return t;
+end
+end
+function d(t)
+t=t
+:gsub("\r\n","\n")
+:gsub("\n ","")
+:gsub("\n\n+","\n");
+local h={};
+local e;
+for t in t:gmatch("[^\n]+")do
+local t=s(t);
+local s,t,n=t:match("^([-%a]+)(\30?[^\29]*)\29(.*)$");
+n=n:gsub("\29",":");
+if#t>0 then
+local a={};
+for e,o,i in t:gmatch("\30([^=]+)(=?)([^\30]*)")do
+e=e:upper();
+local t={};
+for e in i:gmatch("[^\31]+")do
+t[#t+1]=e
+t[e]=true;
+end
+if o=="="then
+a[e]=t;
+else
+a[e]=true;
+end
+end
+t=a;
+end
+if s=="BEGIN"and n=="VCARD"then
+e={};
+h[#h+1]=e;
+elseif s=="END"and n=="VCARD"then
+e=nil;
+elseif e and i[s]then
+local o=i[s];
+local i={name=s};
+e[#e+1]=i;
+local s=e;
+e=i;
+if o.types then
+for o,a in f(o.types)do
+local a=a:lower();
+if(t.TYPE and t.TYPE[a]==true)
+or t[a]==true then
+e.TYPE=a;
+end
+end
+end
+if o.props then
+for o,a in f(o.props)do
+if t[a]then
+if t[a]==true then
+e[a]=true;
+else
+for o,t in f(t[a])do
+e[a]=t;
+end
+end
+end
+end
+end
+if o=="text"or o.value then
+a(e,n);
+elseif o.values then
+local t="\30"..n;
+for t in t:gmatch("\30([^\30]*)")do
+a(e,t);
+end
+end
+e=s;
+end
+end
+return h;
+end
+local function n(t)
+local e={};
+for a=1,#t do
+e[a]=y(t[a]);
+end
+e=c(e,";");
+local a="";
+for t,e in r(t)do
+if h(t)=="string"and t~="name"then
+a=a..(";%s=%s"):format(t,h(e)=="table"and c(e,",")or e);
+end
+end
+return("%s%s:%s"):format(t.name,a,e)
+end
+local function o(t)
+local e={};
+a(e,"BEGIN:VCARD")
+for o=1,#t do
+a(e,n(t[o]));
+end
+a(e,"END:VCARD")
+return c(e,w);
+end
+function l(e)
+if e[1]and e[1].name then
+return o(e)
+else
+local a={};
+for t=1,#e do
+a[t]=o(e[t]);
+end
+return c(a,w);
+end
+end
+local function n(o)
+local t=o.name;
+local e=i[t];
+local t={name=t};
+if e=="text"then
+t[1]=o:get_text();
+elseif h(e)=="table"then
+if e.value then
+t[1]=o:get_child_text(e.value)or"";
+elseif e.values then
+local e=e.values;
+if e.behaviour=="repeat-last"then
+for e=1,#o.tags do
+a(t,o.tags[e]:get_text()or"");
+end
+else
+for i=1,#e do
+a(t,o:get_child_text(e[i])or"");
+end
+end
+elseif e.names then
+local e=e.names;
+for a=1,#e do
+if o:get_child(e[a])then
+t[1]=e[a];
+break;
+end
+end
+end
+if e.props_verbatim then
+for a,e in r(e.props_verbatim)do
+t[a]=e;
+end
+end
+if e.types then
+local e=e.types;
+t.TYPE={};
+for i=1,#e do
+if o:get_child(e[i])then
+a(t.TYPE,e[i]:lower());
+end
+end
+if#t.TYPE==0 then
+t.TYPE=nil;
+end
+end
+if e.props then
+local e=e.props;
+for i=1,#e do
+local e=e[i]
+local o=o:get_child_text(e);
+if o then
+t[e]=t[e]or{};
+a(t[e],o);
+end
+end
+end
+else
+return nil
+end
+return t;
+end
+local function o(e)
+local t=e.tags;
+local e={};
+for o=1,#t do
+a(e,n(t[o]));
+end
+return e
+end
+function u(e)
+if e.attr.xmlns~="vcard-temp"then
+return nil,"wrong-xmlns";
+end
+if e.name=="xCard"then
+local t={};
+local e=e.tags;
+for a=1,#e do
+t[a]=o(e[a]);
+end
+return t
+elseif e.name=="vCard"then
+return o(e)
+end
+end
+i={
+VERSION="text",
+FN="text",
+N={
+values={
+"FAMILY",
+"GIVEN",
+"MIDDLE",
+"PREFIX",
+"SUFFIX",
+},
+},
+NICKNAME="text",
+PHOTO={
+props_verbatim={ENCODING={"b"}},
+props={"TYPE"},
+value="BINVAL",
+},
+BDAY="text",
+ADR={
+types={
+"HOME",
+"WORK",
+"POSTAL",
+"PARCEL",
+"DOM",
+"INTL",
+"PREF",
+},
+values={
+"POBOX",
+"EXTADD",
+"STREET",
+"LOCALITY",
+"REGION",
+"PCODE",
+"CTRY",
+}
+},
+LABEL={
+types={
+"HOME",
+"WORK",
+"POSTAL",
+"PARCEL",
+"DOM",
+"INTL",
+"PREF",
+},
+value="LINE",
+},
+TEL={
+types={
+"HOME",
+"WORK",
+"VOICE",
+"FAX",
+"PAGER",
+"MSG",
+"CELL",
+"VIDEO",
+"BBS",
+"MODEM",
+"ISDN",
+"PCS",
+"PREF",
+},
+value="NUMBER",
+},
+EMAIL={
+types={
+"HOME",
+"WORK",
+"INTERNET",
+"PREF",
+"X400",
+},
+value="USERID",
+},
+JABBERID="text",
+MAILER="text",
+TZ="text",
+GEO={
+values={
+"LAT",
+"LON",
+},
+},
+TITLE="text",
+ROLE="text",
+LOGO="copy of PHOTO",
+AGENT="text",
+ORG={
+values={
+behaviour="repeat-last",
+"ORGNAME",
+"ORGUNIT",
+}
+},
+CATEGORIES={
+values="KEYWORD",
+},
+NOTE="text",
+PRODID="text",
+REV="text",
+SORTSTRING="text",
+SOUND="copy of PHOTO",
+UID="text",
+URL="text",
+CLASS={
+names={
+"PUBLIC",
+"PRIVATE",
+"CONFIDENTIAL",
+},
+},
+KEY={
+props={"TYPE"},
+value="CRED",
+},
+DESC="text",
+};
+i.LOGO=i.PHOTO;
+i.SOUND=i.PHOTO;
+return{
+from_text=d;
+to_text=l;
+from_xep54=u;
+to_xep54=m;
+lua_to_text=l;
+lua_to_xep54=m;
+text_to_lua=d;
+text_to_xep54=function(...)return m(d(...));end;
+xep54_to_lua=u;
+xep54_to_text=function(...)return l(u(...))end;
+};
+end)
+package.preload['util.logger']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local e=pcall;
+local e=string.find;
+local e,i,e=ipairs,pairs,setmetatable;
+local a={};
+local e={};
+local t;
+function a.init(e)
+local o=t(e,"debug");
+local a=t(e,"info");
+local i=t(e,"warn");
+local n=t(e,"error");
+return function(e,t,...)
+if e=="debug"then
+return o(t,...);
+elseif e=="info"then
+return a(t,...);
+elseif e=="warn"then
+return i(t,...);
+elseif e=="error"then
+return n(t,...);
+end
+end
+end
+function t(i,a)
+local t=e[a];
+if not t then
+t={};
+e[a]=t;
+end
+local e=function(o,...)
+for e=1,#t do
+t[e](i,a,o,...);
+end
+end
+return e;
+end
+function a.reset()
+for t,e in i(e)do
+for t=1,#e do
+e[t]=nil;
+end
+end
+end
+function a.add_level_sink(t,o)
+if not e[t]then
+e[t]={o};
+else
+e[t][#e[t]+1]=o;
+end
+end
+a.new=t;
+return a;
+end)
+package.preload['util.datetime']=(function(...)
+local _ENV=_ENV;
+local function a(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local e=os.date;
+local i=os.time;
+local u=os.difftime;
+local t=error;
+local r=tonumber;
+a"datetime"
+function date(t)
+return e("!%Y-%m-%d",t);
+end
+function datetime(t)
+return e("!%Y-%m-%dT%H:%M:%SZ",t);
+end
+function time(t)
+return e("!%H:%M:%S",t);
+end
+function legacy(t)
+return e("!%Y%m%dT%H:%M:%S",t);
+end
+function parse(t)
+if t then
+local n,d,l,s,h,a,o;
+n,d,l,s,h,a,o=t:match("^(%d%d%d%d)%-?(%d%d)%-?(%d%d)T(%d%d):(%d%d):(%d%d)%.?%d*([Z+%-]?.*)$");
+if n then
+local u=u(i(e("*t")),i(e("!*t")));
+local t=0;
+if o~=""and o~="Z"then
+local o,a,e=o:match("([+%-])(%d%d):?(%d*)");
+if not o then return;end
+if#e~=2 then e="0";end
+a,e=r(a),r(e);
+t=a*60*60+e*60;
+if o=="-"then t=-t;end
+end
+a=(a+u)-t;
+return i({year=n,month=d,day=l,hour=s,min=h,sec=a,isdst=false});
+end
+end
+end
+return _M;
+end)
+package.preload['util.json']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local y=type;
+local t,p,v,j=table.insert,table.concat,table.remove,table.sort;
+local s=string.char;
+local q,m=tostring,tonumber;
+local u,r=pairs,ipairs;
+local n=next;
+local e=error;
+local e,h,g=newproxy,getmetatable,setmetatable;
+local c=print;
+local a,o=pcall(require,"util.array");
+local f=a and h(o())or{};
+local a={};
+local i=e and e(true)or{};
+if h and h(i)then
+h(i).__tostring=function()return"null";end;
+end
+a.null=i;
+local w={
+["\""]="\\\"",["\\"]="\\\\",["\b"]="\\b",
+["\f"]="\\f",["\n"]="\\n",["\r"]="\\r",["\t"]="\\t"};
+local e={
+["\""]="\"",["\\"]="\\",["/"]="/",
+b="\b",f="\f",n="\n",r="\r",t="\t"};
+for t=0,31 do
+local e=s(t);
+if not w[e]then w[e]=("\\u%.4X"):format(t);end
+end
+local function x(e)
+if e<128 then return s(e);end
+local t=e%64;
+if e<2048 then
+local e=(e-t)/64;
+return s(128+64+e,128+t);
+end
+local a=e%4096;
+local o=(a-t)/64;
+local e=(e-a)/4096;
+return s(128+64+32+e,128+o,128+t);
+end
+local k={
+number=true,
+string=true,
+table=true,
+boolean=true
+};
+local z={
+__array=true;
+__hash=true;
+};
+local o,b,d,l;
+function l(e,a)
+t(a,"\""..(e:gsub(".",w)).."\"");
+end
+function d(a,e)
+t(e,"[");
+if n(a)then
+for i,a in r(a)do
+o(a,e);
+t(e,",");
+end
+v(e);
+end
+t(e,"]");
+end
+function b(c,e)
+local a={};
+local h={};
+local s={};
+for e,t in r(c)do
+a[e]=t;
+end
+for e,t in u(c)do
+local o,n=y(e),y(t);
+if k[n]or t==i then
+if o=="string"and not z[e]then
+s[e]=t;
+elseif(k[o]or e==i)and a[e]==nil then
+h[e]=t;
+end
+end
+end
+if n(h)~=nil or n(s)~=nil or n(a)==nil then
+t(e,"{");
+local c=#e;
+if e.ordered then
+local a={};
+for e in u(s)do
+t(a,e);
+end
+j(a);
+for i,a in r(a)do
+l(a,e);
+t(e,":");
+o(s[a],e);
+t(e,",");
+end
+else
+for i,a in u(s)do
+l(i,e);
+t(e,":");
+o(a,e);
+t(e,",");
+end
+end
+if n(h)~=nil then
+t(e,"\"__hash\":[");
+for i,a in u(h)do
+o(i,e);
+t(e,",");
+o(a,e);
+t(e,",");
+end
+v(e);
+t(e,"]");
+t(e,",");
+end
+if n(a)then
+t(e,"\"__array\":");
+d(a,e);
+t(e,",");
+end
+if c~=#e then v(e);end
+t(e,"}");
+else
+d(a,e);
+end
+end
+function o(e,a)
+local o=y(e);
+if o=="number"then
+t(a,q(e));
+elseif o=="string"then
+l(e,a);
+elseif o=="table"then
+local t=h(e);
+if t==f then
+d(e,a);
+else
+b(e,a);
+end
+elseif o=="boolean"then
+t(a,(e and"true"or"false"));
+else
+t(a,"null");
+end
+end
+function a.encode(t)
+local e={};
+o(t,e);
+return p(e);
+end
+function a.encode_ordered(t)
+local e={ordered=true};
+o(t,e);
+return p(e);
+end
+function a.encode_array(t)
+local e={};
+d(t,e);
+return p(e);
+end
+local function o(t,e)
+return t:find("[^ \t\r\n]",e)or e;
+end
+local function d(e)
+local a=e.__array;
+if a then
+e.__array=nil;
+for o,a in r(a)do
+t(e,a);
+end
+end
+local a=e.__hash;
+if a then
+e.__hash=nil;
+local t;
+for o,a in r(a)do
+if t~=nil then
+e[t]=a;t=nil;
+else
+t=a;
+end
+end
+end
+return e;
+end
+local n,h;
+local function u(t,e)
+local s={};
+while true do
+local a,i;
+e=o(t,e+1);
+if t:byte(e)~=34 then
+if t:byte(e)==125 then return s,e+1;end
+return nil,"key expected";
+end
+a,e=h(t,e);
+if a==nil then return nil,e;end
+e=o(t,e);
+if t:byte(e)~=58 then return nil,"colon expected";end
+i,e=n(t,e+1);
+if i==nil then return nil,e;end
+s[a]=i;
+e=o(t,e);
+local t=t:byte(e);
+if t==125 then return d(s),e+1;end
+if t~=44 then return nil,"object eof";end
+end
+end
+local function l(i,e)
+local s={};
+local h=e;
+while true do
+local a;
+a,e=n(i,e+1);
+if a==nil then
+if i:byte(h+1)==93 then return g(s,f),h+2;end
+return a,e;
+end
+t(s,a);
+e=o(i,e);
+local t=i:byte(e);
+if t==93 then return g(s,f),e+1;end
+if t~=44 then return nil,"array eof";end
+end
+end
+local t;
+local function e(e)
+local t,e=m(e:sub(3,6),16),m(e:sub(9,12),16);
+local e=t*1024+e-56613888;
+local o=e%64;
+e=(e-o)/64;
+local t=e%64;
+e=(e-t)/64;
+local a=e%64;
+e=(e-a)/64;
+return s(240+e,128+a,128+t,128+o);
+end
+local function s(e)
+e=e:match("%x%x%x%x",3);
+if e then
+return x(m(e,16));
+end
+t=true;
+end
+function h(o,e)
+e=e+1;
+local a=o:find("\"",e,true);
+if a then
+local e=o:sub(e,a-1);
+t=nil;
+e=e:gsub("\\u.?.?.?.?",s);
+if t then return nil,"invalid escape";end
+return e,a+1;
+end
+return nil,"string eof";
+end
+local function d(t,e)
+local t=t:match("[0-9%.%-eE%+]+",e);
+return m(t),e+#t;
+end
+local function s(t,e)
+local o,a,t=t:byte(e+1,e+3);
+if o==117 and a==108 and t==108 then
+return i,e+4;
+end
+return nil,"null parse failed";
+end
+local function i(t,e)
+local t,a,o=t:byte(e+1,e+3);
+if t==114 and a==117 and o==101 then
+return true,e+4;
+end
+return nil,"true parse failed";
+end
+local function r(t,e)
+local i,o,a,t=t:byte(e+1,e+4);
+if i==97 and o==108 and a==115 and t==101 then
+return false,e+5;
+end
+return nil,"false parse failed";
+end
+function n(a,t)
+t=o(a,t);
+local e=a:byte(t);
+if e==123 then
+return u(a,t);
+elseif e==91 then
+return l(a,t);
+elseif e==34 then
+return h(a,t);
+elseif e~=nil and e>=48 and e<=57 or e==45 then
+return d(a,t);
+elseif e==110 then
+return s(a,t);
+elseif e==116 then
+return i(a,t);
+elseif e==102 then
+return r(a,t);
+else
+return nil,"value expected";
+end
+end
+local t={
+["\\\""]="\\u0022";
+["\\\\"]="\\u005c";
+["\\/"]="\\u002f";
+["\\b"]="\\u0008";
+["\\f"]="\\u000C";
+["\\n"]="\\u000A";
+["\\r"]="\\u000D";
+["\\t"]="\\u0009";
+["\\u"]="\\u";
+};
+function a.decode(e)
+e=e:gsub("\\.",t)
+local t,a=n(e,1);
+if t==nil then return t,a;end
+if e:find("[^ \t\r\n]",a)then return nil,"garbage at eof";end
+return t;
+end
+function a.test(e)
+local e=a.encode(e);
+local t=a.decode(e);
+local t=a.encode(t);
+if e~=t then
+c("FAILED");
+c("encoded:",e);
+c("recoded:",t);
+else
+c(e);
+end
+return e==t;
+end
+return a;
+end)
+package.preload['util.xml']=(function(...)
+local _ENV=_ENV;
+local function a(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local t=require"util.stanza";
+local n=require"lxp";
+a("xml")
+local e=(function()
+local s={
+["http://www.w3.org/XML/1998/namespace"]="xml";
+};
+local e="\1";
+local i="^([^"..e.."]*)"..e.."?(.*)$";
+return function(h)
+local o={};
+local a=t.stanza("root");
+function o:StartElement(t,e)
+local t,o=t:match(i);
+if o==""then
+t,o="",t;
+end
+if t~=""then
+e.xmlns=t;
+end
+for t=1,#e do
+local a=e[t];
+e[t]=nil;
+local t,o=a:match(i);
+if o~=""then
+t=s[t];
+if t then
+e[t..":"..o]=e[a];
+e[a]=nil;
+end
+end
+end
+a:tag(o,e);
+end
+function o:CharacterData(e)
+a:text(e);
+end
+function o:EndElement(e)
+a:up();
+end
+local n=n.new(o,"\1");
+local e,i,o,t=n:parse(h);
+if e then e,i,o,t=n:parse();end
+if e then
+return a.tags[1];
+else
+return e,i.." (line "..o..", col "..t..")";
+end
+end;
+end)();
+parse=e;
+return _M;
+end)
+package.preload['util.rsm']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local n=require"util.stanza".stanza;
+local a,o=tostring,tonumber;
+local s=type;
+local h=pairs;
+local i='http://jabber.org/protocol/rsm';
+local t={};
+do
+local e=t;
+local function t(e)
+return o((e:get_text()));
+end
+local function a(t)
+return t:get_text();
+end
+e.after=a;
+e.before=function(e)
+local e=e:get_text();
+return e==""or e;
+end;
+e.max=t;
+e.index=t;
+e.first=function(e)
+return{index=o(e.attr.index);e:get_text()};
+end;
+e.last=a;
+e.count=t;
+end
+local r=setmetatable({
+first=function(t,e)
+if s(e)=="table"then
+t:tag("first",{index=e.index}):text(e[1]):up();
+else
+t:tag("first"):text(a(e)):up();
+end
+end;
+before=function(e,t)
+if t==true then
+e:tag("before"):up();
+else
+e:tag("before"):text(a(t)):up();
+end
+end
+},{
+__index=function(e,o)
+return function(t,e)
+t:tag(o):text(a(e)):up();
+end
+end;
+});
+local function o(e)
+local o={};
+for a in e:childtags()do
+local e=a.name;
+local t=e and t[e];
+if t then
+o[e]=t(a);
+end
+end
+return o;
+end
+local function s(e)
+local a=n("set",{xmlns=i});
+for e,o in h(e)do
+if t[e]then
+r[e](a,o);
+end
+end
+return a;
+end
+local function t(e)
+local e=e:get_child("set",i);
+if e and#e.tags>0 then
+return o(e);
+end
+end
+return{parse=o,generate=s,get=t};
+end)
+package.preload['util.random']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local e=io.open("/dev/urandom","r");
+if e then
+return{
+seed=function()end;
+bytes=function(t)return e:read(t);end
+};
+end
+local e=require"crypto"
+return e.rand;
+end)
+package.preload['util.ip']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local o={};
+local i={__index=function(e,t)return(o[t])(e);end,
+__tostring=function(e)return e.addr;end,
+__eq=function(t,e)return t.addr==e.addr;end};
+local n={["0"]="0000",["1"]="0001",["2"]="0010",["3"]="0011",["4"]="0100",["5"]="0101",["6"]="0110",["7"]="0111",["8"]="1000",["9"]="1001",["A"]="1010",["B"]="1011",["C"]="1100",["D"]="1101",["E"]="1110",["F"]="1111"};
+local function e(t,e)
+if not e then
+local a=t:match("^%x+(.)");
+if a==":"or(not(a)and t:sub(1,1)==":")then
+e="IPv6"
+elseif a=="."then
+e="IPv4"
+end
+if not e then
+return nil,"invalid address";
+end
+elseif e~="IPv4"and e~="IPv6"then
+return nil,"invalid protocol";
+end
+local a;
+if e=="IPv6"and t:find('%',1,true)then
+t,a=t:match("^(.-)%%(.*)");
+end
+if e=="IPv6"and t:find('.',1,true)then
+local e;
+t,e=t:gsub(":(%d+)%.(%d+)%.(%d+)%.(%d+)$",function(t,e,o,a)
+return(":%04X:%04X"):format(t*256+e,o*256+a);
+end);
+if e~=1 then return nil,"invalid-address";end
+end
+return setmetatable({addr=t,proto=e,zone=a},i);
+end
+local function i(e)
+local t="";
+local a={};
+if e.proto=="IPv4"then
+e=e.toV4mapped;
+end
+e=(e.addr):upper();
+e:gsub("([^:]*):?",function(e)a[#a+1]=e end);
+if not e:match(":$")then a[#a]=nil;end
+for o,e in ipairs(a)do
+if e:len()==0 and o~=1 and o~=#a then
+for e=1,16*(9-#a)do
+t=t.."0";
+end
+else
+for e=1,4-e:len()do
+t=t.."0000";
+end
+for a=1,e:len()do
+t=t..n[e:sub(a,a)];
+end
+end
+end
+return t;
+end
+local function t(a,t)
+a,t=i(a),i(t);
+for e=1,128 do
+if a:sub(e,e)~=t:sub(e,e)then
+return e-1;
+end
+end
+return 128;
+end
+local function r(t)
+local e={};
+t:gsub("([^.]*).?",function(t)e[#e+1]=tonumber(t)end);
+if e[1]==127 then
+return 2;
+elseif e[1]==169 and e[2]==254 then
+return 2;
+else
+return 14;
+end
+end
+local function h(e)
+if e:match("^[0:]*1$")then
+return 2;
+elseif e:match("^[Ff][Ee][89ABab]")then
+return 2;
+elseif e:match("^[Ff][Ee][CcDdEeFf]")then
+return 5;
+elseif e:match("^[Ff][Ff]")then
+return tonumber("0x"..e:sub(4,4));
+else
+return 14;
+end
+end
+local function i(a)
+if t(a,e("::1","IPv6"))==128 then
+return 0;
+elseif t(a,e("2002::","IPv6"))>=16 then
+return 2;
+elseif t(a,e("2001::","IPv6"))>=32 then
+return 5;
+elseif t(a,e("fc00::","IPv6"))>=7 then
+return 13;
+elseif t(a,e("fec0::","IPv6"))>=10 then
+return 11;
+elseif t(a,e("3ffe::","IPv6"))>=16 then
+return 12;
+elseif t(a,e("::","IPv6"))>=96 then
+return 3;
+elseif t(a,e("::ffff:0:0","IPv6"))>=96 then
+return 4;
+else
+return 1;
+end
+end
+local function n(a)
+if t(a,e("::1","IPv6"))==128 then
+return 50;
+elseif t(a,e("2002::","IPv6"))>=16 then
+return 30;
+elseif t(a,e("2001::","IPv6"))>=32 then
+return 5;
+elseif t(a,e("fc00::","IPv6"))>=7 then
+return 3;
+elseif t(a,e("fec0::","IPv6"))>=10 then
+return 1;
+elseif t(a,e("3ffe::","IPv6"))>=16 then
+return 1;
+elseif t(a,e("::","IPv6"))>=96 then
+return 1;
+elseif t(a,e("::ffff:0:0","IPv6"))>=96 then
+return 35;
+else
+return 40;
+end
+end
+local function s(o)
+local a={};
+local t="::ffff:";
+o:gsub("([^.]*).?",function(e)a[#a+1]=tonumber(e)end);
+t=t..("%02x"):format(a[1]);
+t=t..("%02x"):format(a[2]);
+t=t..":"
+t=t..("%02x"):format(a[3]);
+t=t..("%02x"):format(a[4]);
+return e(t,"IPv6");
+end
+function o:toV4mapped()
+if self.proto~="IPv4"then return nil,"No IPv4 address"end
+local e=s(self.addr);
+self.toV4mapped=e;
+return e;
+end
+function o:label()
+local e;
+if self.proto=="IPv4"then
+e=i(self.toV4mapped);
+else
+e=i(self);
+end
+self.label=e;
+return e;
+end
+function o:precedence()
+local e;
+if self.proto=="IPv4"then
+e=n(self.toV4mapped);
+else
+e=n(self);
+end
+self.precedence=e;
+return e;
+end
+function o:scope()
+local e;
+if self.proto=="IPv4"then
+e=r(self.addr);
+else
+e=h(self.addr);
+end
+self.scope=e;
+return e;
+end
+return{new_ip=e,
+commonPrefixLength=t};
+end)
+package.preload['util.time']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+end)
+package.preload['util.sasl.scram']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local n,m=require"mime".b64,require"mime".unb64;
+local e=require"util.hashes";
+local a=require"bit";
+local s=require"util.random";
+local u=tonumber;
+local o,t=string.char,string.byte;
+local i=string.gsub;
+local a=a.bxor;
+local function c(n,e)
+return(i(n,"()(.)",function(n,i)
+return o(a(t(i),t(e,n)))
+end));
+end
+local w,t=e.sha1,e.hmac_sha1;
+local function y(o,e,i)
+local e=t(o,e.."\0\0\0\1");
+local a=e;
+for i=2,i do
+e=t(o,e);
+a=c(a,e);
+end
+return a;
+end
+local function f(e)
+return e;
+end
+local function a(e)
+return(i(e,"[,=]",{[","]="=2C",["="]="=3D"}));
+end
+local function d(e,o)
+local a="n="..a(e.username);
+local d=n(s.bytes(15));
+local r="r="..d;
+local h=a..","..r;
+local i="";
+local a=e.conn:ssl()and"y"or"n";
+if o=="SCRAM-SHA-1-PLUS"then
+i=e.conn:socket():getfinished();
+a="p=tls-unique";
+end
+local s=a..",,";
+local a=s..h;
+local a,l=coroutine.yield(a);
+if a~="challenge"then return false end
+local a,o,p=l:match("(r=[^,]+),s=([^,]*),i=(%d+)");
+local u=u(p);
+o=m(o);
+if not a or not o or not u then
+return false,"Could not parse server_first_message";
+elseif a:find(d,3,true)~=3 then
+return false,"nonce sent by server does not match our nonce";
+elseif a==r then
+return false,"server did not append s-nonce to nonce";
+end
+local i=s..i;
+local i="c="..n(i);
+local r=i..","..a;
+local a;
+local i;
+local s;
+if e.client_key and e.server_key then
+i=e.client_key;
+s=e.server_key;
+else
+if e.salted_password then
+a=e.salted_password;
+elseif e.password then
+a=y(f(e.password),o,u);
+end
+s=t(a,"Server Key");
+i=t(a,"Client Key");
+end
+local a=w(i);
+local e=h..","..l..","..r;
+local a=t(a,e);
+local a=c(i,a);
+local o=t(s,e);
+local e="p="..n(a);
+local e=r..","..e;
+local t,e=coroutine.yield(e);
+if t~="success"then return false,"success-expected"end
+local e=e:match("v=([^,]+)");
+if m(e)~=o then
+return false,"server signature did not match";
+end
+return true;
+end
+return function(e,t)
+if e.username and(e.password or(e.client_key or e.server_key))then
+if t=="SCRAM-SHA-1"then
+return d,99;
+elseif t=="SCRAM-SHA-1-PLUS"then
+local e=e.conn:ssl()and e.conn:socket();
+if e and e.getfinished then
+return d,100;
+end
+end
+end
+end
+end)
+package.preload['util.sasl.plain']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+return function(e,t)
+if t=="PLAIN"and e.username and e.password then
+return function(e)
+return"success"==coroutine.yield((e.authzid or"").."\0"..e.username.."\0"..e.password);
+end,5;
+end
+end
+end)
+package.preload['util.sasl.anonymous']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+return function(t,e)
+if e=="ANONYMOUS"then
+return function()
+return coroutine.yield()=="success";
+end,0;
+end
+end
+end)
+package.preload['verse.plugins.tls']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local a=require"verse";
+local t="urn:ietf:params:xml:ns:xmpp-tls";
+function a.plugins.tls(e)
+local function i(o)
+if e.authenticated then return;end
+if o:get_child("starttls",t)and e.conn.starttls then
+e:debug("Negotiating TLS...");
+e:send(a.stanza("starttls",{xmlns=t}));
+return true;
+elseif not e.conn.starttls and not e.secure then
+e:warn("SSL library (LuaSec) not loaded, so TLS not available");
+elseif not e.secure then
+e:debug("Server doesn't offer TLS :(");
+end
+end
+local function o(t)
+if t.name=="proceed"then
+e:debug("Server says proceed, handshake starting...");
+e.conn:starttls(e.ssl or{mode="client",protocol="sslv23",options="no_sslv2",capath="/etc/ssl/certs"},true);
+end
+end
+local function a(t)
+if t=="ssl-handshake-complete"then
+e.secure=true;
+e:debug("Re-opening stream...");
+e:reopen();
+end
+end
+e:hook("stream-features",i,400);
+e:hook("stream/"..t,o);
+e:hook("status",a,400);
+return true;
+end
+end)
+package.preload['verse.plugins.sasl']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local n=require"verse";
+local s,h=require"mime".b64,require"mime".unb64;
+local a="urn:ietf:params:xml:ns:xmpp-sasl";
+function n.plugins.sasl(e)
+local function r(t)
+if e.authenticated then return;end
+e:debug("Authenticating with SASL...");
+local t=t:get_child("mechanisms",a);
+if not t then return end
+local o={};
+local i={};
+for t in t:childtags("mechanism")do
+t=t:get_text();
+e:debug("Server offers %s",t);
+if not o[t]then
+local n=t:match("[^-]+");
+local s,a=pcall(require,"util.sasl."..n:lower());
+if s then
+e:debug("Loaded SASL %s module",n);
+o[t],i[t]=a(e,t);
+elseif not tostring(a):match("not found")then
+e:debug("Loading failed: %s",tostring(a));
+end
+end
+end
+local t={};
+for e in pairs(o)do
+table.insert(t,e);
+end
+if not t[1]then
+e:event("authentication-failure",{condition="no-supported-sasl-mechanisms"});
+e:close();
+return;
+end
+table.sort(t,function(e,t)return i[e]>i[t];end);
+local t,i=t[1];
+e:debug("Selecting %s mechanism...",t);
+e.sasl_mechanism=coroutine.wrap(o[t]);
+i=e:sasl_mechanism(t);
+local t=n.stanza("auth",{xmlns=a,mechanism=t});
+if i then
+t:text(s(i));
+end
+e:send(t);
+return true;
+end
+local function i(t)
+if t.name=="failure"then
+local a=t.tags[1];
+local t=t:get_child_text("text");
+e:event("authentication-failure",{condition=a.name,text=t});
+e:close();
+return false;
+end
+local t,o=e.sasl_mechanism(t.name,h(t:get_text()));
+if not t then
+e:event("authentication-failure",{condition=o});
+e:close();
+return false;
+elseif t==true then
+e:event("authentication-success");
+e.authenticated=true
+e:reopen();
+else
+e:send(n.stanza("response",{xmlns=a}):text(s(t)));
+end
+return true;
+end
+e:hook("stream-features",r,300);
+e:hook("stream/"..a,i);
+return true;
+end
+end)
+package.preload['verse.plugins.bind']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local t=require"verse";
+local o=require"util.jid";
+local a="urn:ietf:params:xml:ns:xmpp-bind";
+function t.plugins.bind(e)
+local function i(i)
+if e.bound then return;end
+e:debug("Binding resource...");
+e:send_iq(t.iq({type="set"}):tag("bind",{xmlns=a}):tag("resource"):text(e.resource),
+function(t)
+if t.attr.type=="result"then
+local t=t
+:get_child("bind",a)
+:get_child_text("jid");
+e.username,e.host,e.resource=o.split(t);
+e.jid,e.bound=t,true;
+e:event("bind-success",{jid=t});
+elseif t.attr.type=="error"then
+local a=t:child_with_name("error");
+local t,a,o=t:get_error();
+e:event("bind-failure",{error=a,text=o,type=t});
+end
+end);
+end
+e:hook("stream-features",i,200);
+return true;
+end
+end)
+package.preload['verse.plugins.session']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local a=require"verse";
+local t="urn:ietf:params:xml:ns:xmpp-session";
+function a.plugins.session(e)
+local function i(o)
+local o=o:get_child("session",t);
+if o and not o:get_child("optional")then
+local function o(o)
+e:debug("Establishing Session...");
+e:send_iq(a.iq({type="set"}):tag("session",{xmlns=t}),
+function(t)
+if t.attr.type=="result"then
+e:event("session-success");
+elseif t.attr.type=="error"then
+local t,o,a=t:get_error();
+e:event("session-failure",{error=o,text=a,type=t});
+end
+end);
+return true;
+end
+e:hook("bind-success",o);
+end
+end
+e:hook("stream-features",i);
+return true;
+end
+end)
+package.preload['verse.plugins.legacy']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local i=require"verse";
+local s=require"util.uuid".generate;
+local a="jabber:iq:auth";
+function i.plugins.legacy(e)
+local function n(t)
+local o=t:get_child("query",a);
+if t.attr.type~="result"or not o then
+local t,a,o=t:get_error();
+e:debug("warn","%s %s: %s",t,a,o);
+end
+local t={
+username=e.username;
+password=e.password;
+resource=e.resource or s();
+digest=false,sequence=false,token=false;
+};
+local a=i.iq({to=e.host,type="set"})
+:tag("query",{xmlns=a});
+if#o>0 then
+for o in o:childtags()do
+local o=o.name;
+local i=t[o];
+if i then
+a:tag(o):text(t[o]):up();
+elseif i==nil then
+local t="feature-not-implemented";
+e:event("authentication-failure",{condition=t});
+return false;
+end
+end
+else
+for t,e in pairs(t)do
+if e then
+a:tag(t):text(e):up();
+end
+end
+end
+e:send_iq(a,function(a)
+if a.attr.type=="result"then
+e.resource=t.resource;
+e.jid=t.username.."@"..e.host.."/"..t.resource;
+e:event("authentication-success");
+e:event("bind-success",e.jid);
+else
+local a,t,a=a:get_error();
+e:event("authentication-failure",{condition=t});
+end
+end);
+end
+local function a(t)
+if not t.version then
+e:send_iq(i.iq({type="get"})
+:tag("query",{xmlns="jabber:iq:auth"})
+:tag("username"):text(e.username),
+n);
+end
+end
+e:hook("opened",a);
+end
+end)
+package.preload['verse.plugins.compression']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local t=require"verse";
+local i=require"zlib";
+local e="http://jabber.org/features/compress"
+local a="http://jabber.org/protocol/compress"
+local e="http://etherx.jabber.org/streams";
+local o=9;
+local function s(e)
+local i,o=pcall(i.deflate,o);
+if i==false then
+local t=t.stanza("failure",{xmlns=a}):tag("setup-failed");
+e:send(t);
+e:error("Failed to create zlib.deflate filter: %s",tostring(o));
+return
+end
+return o
+end
+local function n(o)
+local i,e=pcall(i.inflate);
+if i==false then
+local t=t.stanza("failure",{xmlns=a}):tag("setup-failed");
+o:send(t);
+o:error("Failed to create zlib.inflate filter: %s",tostring(e));
+return
+end
+return e
+end
+local function i(e,o)
+function e:send(i)
+local i,o,n=pcall(o,tostring(i),'sync');
+if i==false then
+e:close({
+condition="undefined-condition";
+text=o;
+extra=t.stanza("failure",{xmlns=a}):tag("processing-failed");
+});
+e:warn("Compressed send failed: %s",tostring(o));
+return;
+end
+e.conn:write(o);
+end;
+end
+local function h(e,o)
+local n=e.data
+e.data=function(i,s)
+e:debug("Decompressing data...");
+local s,o,h=pcall(o,s);
+if s==false then
+e:close({
+condition="undefined-condition";
+text=o;
+extra=t.stanza("failure",{xmlns=a}):tag("processing-failed");
+});
+stream:warn("%s",tostring(o));
+return;
+end
+return n(i,o);
+end;
+end
+function t.plugins.compression(e)
+local function d(o)
+if not e.compressed then
+local o=o:child_with_name("compression");
+if o then
+for o in o:children()do
+local o=o[1]
+if o=="zlib"then
+e:send(t.stanza("compress",{xmlns=a}):tag("method"):text("zlib"))
+e:debug("Enabled compression using zlib.")
+return true;
+end
+end
+session:debug("Remote server supports no compression algorithm we support.")
+end
+end
+end
+local function r(o)
+if o.name=="compressed"then
+e:debug("Activating compression...")
+local a=s(e);
+if not a then return end
+local t=n(e);
+if not t then return end
+i(e,a);
+h(e,t);
+e.compressed=true;
+e:reopen();
+elseif o.name=="failure"then
+e:warn("Failed to establish compression");
+end
+end
+e:hook("stream-features",d,250);
+e:hook("stream/"..a,r);
+end
+end)
+package.preload['verse.plugins.smacks']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local n=require"verse";
+local h=require"socket".gettime;
+local s="urn:xmpp:sm:3";
+function n.plugins.smacks(e)
+local t={};
+local a=0;
+local r=h();
+local o;
+local i=0;
+local function l(t)
+if t.attr.xmlns=="jabber:client"or not t.attr.xmlns then
+i=i+1;
+e:debug("Increasing handled stanzas to %d for %s",i,t:top_tag());
+end
+end
+local function u(a)
+if a.name and not a.attr.xmlns then
+t[#t+1]=tostring(a);
+r=h();
+if not o then
+o=true;
+e:debug("Waiting to send ack request...");
+n.add_task(1,function()
+if#t==0 then
+o=false;
+return;
+end
+local a=h()-r;
+if a<1 and#t<10 then
+return 1-a;
+end
+e:debug("Time up, sending <r>...");
+o=false;
+e:send(n.stanza("r",{xmlns=s}));
+end);
+end
+end
+end
+local function h()
+e:debug("smacks: connection lost");
+e.stream_management_supported=nil;
+if e.resumption_token then
+e:debug("smacks: have resumption token, reconnecting in 1s...");
+e.authenticated=nil;
+n.add_task(1,function()
+e:connect(e.connect_host or e.host,e.connect_port or 5222);
+end);
+return true;
+end
+end
+local function d()
+e.resumption_token=nil;
+e:unhook("disconnected",h);
+end
+local function r(o)
+if o.name=="r"then
+e:debug("Ack requested... acking %d handled stanzas",i);
+e:send(n.stanza("a",{xmlns=s,h=tostring(i)}));
+elseif o.name=="a"then
+local o=tonumber(o.attr.h);
+if o>a then
+local i=#t;
+for a=a+1,o do
+table.remove(t,1);
+end
+e:debug("Received ack: New ack: "..o.." Last ack: "..a.." Unacked stanzas now: "..#t.." (was "..i..")");
+a=o;
+else
+e:warn("Received bad ack for "..o.." when last ack was "..a);
+end
+elseif o.name=="enabled"then
+if o.attr.id then
+e.resumption_token=o.attr.id;
+e:hook("closed",d,100);
+e:hook("disconnected",h,100);
+end
+elseif o.name=="resumed"then
+local o=tonumber(o.attr.h);
+if o>a then
+local i=#t;
+for a=a+1,o do
+table.remove(t,1);
+end
+e:debug("Received ack: New ack: "..o.." Last ack: "..a.." Unacked stanzas now: "..#t.." (was "..i..")");
+a=o;
+end
+for a=1,#t do
+e:send(t[a]);
+end
+t={};
+e:debug("Resumed successfully");
+e:event("resumed");
+else
+e:warn("Don't know how to handle "..s.."/"..o.name);
+end
+end
+local function t()
+if not e.smacks then
+e:debug("smacks: sending enable");
+e:send(n.stanza("enable",{xmlns=s,resume="true"}));
+e.smacks=true;
+e:hook("stanza",l);
+e:hook("outgoing",u);
+end
+end
+local function o(a)
+if a:get_child("sm",s)then
+e.stream_management_supported=true;
+if e.smacks and e.bound then
+e:debug("Resuming stream with %d handled stanzas",i);
+e:send(n.stanza("resume",{xmlns=s,
+h=i,previd=e.resumption_token}));
+return true;
+else
+e:hook("bind-success",t,1);
+end
+end
+end
+e:hook("stream-features",o,250);
+e:hook("stream/"..s,r);
+end
+end)
+package.preload['verse.plugins.keepalive']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local t=require"verse";
+function t.plugins.keepalive(e)
+e.keepalive_timeout=e.keepalive_timeout or 300;
+t.add_task(e.keepalive_timeout,function()
+e.conn:write(" ");
+return e.keepalive_timeout;
+end);
+end
+end)
+package.preload['verse.plugins.disco']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local t=require"verse";
+local e=require("mime").b64;
+local e=require("util.hashes").sha1;
+local s=require"util.caps".calculate_hash;
+local n="http://jabber.org/protocol/caps";
+local e="http://jabber.org/protocol/disco";
+local i=e.."#info";
+local o=e.."#items";
+function t.plugins.disco(e)
+e:add_plugin("presence");
+local a={
+__index=function(a,e)
+local t={identities={},features={}};
+if e=="identities"or e=="features"then
+return a[false][e]
+end
+a[e]=t;
+return t;
+end,
+};
+local h={
+__index=function(t,a)
+local e={};
+t[a]=e;
+return e;
+end,
+};
+e.disco={
+cache={},
+info=setmetatable({
+[false]={
+identities={
+{category='client',type='pc',name='Verse'},
+},
+features={
+[n]=true,
+[i]=true,
+[o]=true,
+},
+},
+},a);
+items=setmetatable({[false]={}},h);
+};
+e.caps={}
+e.caps.node='http://code.matthewwild.co.uk/verse/'
+local function h(a)
+local o=e.disco.info[a or false];
+if a and a==e.caps.node.."#"..e.caps.hash then
+o=e.disco.info[false];
+end
+local n,o=o.identities,o.features
+local e=t.stanza("query",{
+xmlns=i,
+node=a,
+});
+for a,t in pairs(n)do
+e:tag('identity',t):up()
+end
+for t in pairs(o)do
+e:tag('feature',{var=t}):up()
+end
+return e;
+end
+setmetatable(e.caps,{
+__call=function(...)
+local a=s(h())
+e.caps.hash=a;
+return t.stanza('c',{
+xmlns=n,
+hash='sha-1',
+node=e.caps.node,
+ver=a
+})
+end
+})
+function e:set_identity(a,t)
+self.disco.info[t or false].identities={a};
+e:resend_presence();
+end
+function e:add_identity(a,t)
+local t=self.disco.info[t or false].identities;
+t[#t+1]=a;
+e:resend_presence();
+end
+function e:add_disco_feature(t,a)
+local t=t.var or t;
+self.disco.info[a or false].features[t]=true;
+e:resend_presence();
+end
+function e:remove_disco_feature(t,a)
+local t=t.var or t;
+self.disco.info[a or false].features[t]=nil;
+e:resend_presence();
+end
+function e:add_disco_item(t,e)
+local e=self.disco.items[e or false];
+e[#e+1]=t;
+end
+function e:remove_disco_item(a,e)
+local e=self.disco.items[e or false];
+for t=#e,1,-1 do
+if e[t]==a then
+table.remove(e,t);
+end
+end
+end
+function e:jid_has_identity(e,a,t)
+local o=self.disco.cache[e];
+if not o then
+return nil,"no-cache";
+end
+local e=self.disco.cache[e].identities;
+if t then
+return e[a.."/"..t]or false;
+end
+for e in pairs(e)do
+if e:match("^(.*)/")==a then
+return true;
+end
+end
+end
+function e:jid_supports(e,t)
+local e=self.disco.cache[e];
+if not e or not e.features then
+return nil,"no-cache";
+end
+return e.features[t]or false;
+end
+function e:get_local_services(o,a)
+local e=self.disco.cache[self.host];
+if not(e)or not(e.items)then
+return nil,"no-cache";
+end
+local t={};
+for i,e in ipairs(e.items)do
+if self:jid_has_identity(e.jid,o,a)then
+table.insert(t,e.jid);
+end
+end
+return t;
+end
+function e:disco_local_services(a)
+self:disco_items(self.host,nil,function(t)
+if not t then
+return a({});
+end
+local e=0;
+local function o()
+e=e-1;
+if e==0 then
+return a(t);
+end
+end
+for a,t in ipairs(t)do
+if t.jid then
+e=e+1;
+self:disco_info(t.jid,nil,o);
+end
+end
+if e==0 then
+return a(t);
+end
+end);
+end
+function e:disco_info(e,a,s)
+local t=t.iq({to=e,type="get"})
+:tag("query",{xmlns=i,node=a});
+self:send_iq(t,function(n)
+if n.attr.type=="error"then
+return s(nil,n:get_error());
+end
+local o,t={},{};
+for e in n:get_child("query",i):childtags()do
+if e.name=="identity"then
+o[e.attr.category.."/"..e.attr.type]=e.attr.name or true;
+elseif e.name=="feature"then
+t[e.attr.var]=true;
+end
+end
+if not self.disco.cache[e]then
+self.disco.cache[e]={nodes={}};
+end
+if a then
+if not self.disco.cache[e].nodes[a]then
+self.disco.cache[e].nodes[a]={nodes={}};
+end
+self.disco.cache[e].nodes[a].identities=o;
+self.disco.cache[e].nodes[a].features=t;
+else
+self.disco.cache[e].identities=o;
+self.disco.cache[e].features=t;
+end
+return s(self.disco.cache[e]);
+end);
+end
+function e:disco_items(a,i,n)
+local t=t.iq({to=a,type="get"})
+:tag("query",{xmlns=o,node=i});
+self:send_iq(t,function(e)
+if e.attr.type=="error"then
+return n(nil,e:get_error());
+end
+local t={};
+for e in e:get_child("query",o):childtags()do
+if e.name=="item"then
+table.insert(t,{
+name=e.attr.name;
+jid=e.attr.jid;
+node=e.attr.node;
+});
+end
+end
+if not self.disco.cache[a]then
+self.disco.cache[a]={nodes={}};
+end
+if i then
+if not self.disco.cache[a].nodes[i]then
+self.disco.cache[a].nodes[i]={nodes={}};
+end
+self.disco.cache[a].nodes[i].items=t;
+else
+self.disco.cache[a].items=t;
+end
+return n(t);
+end);
+end
+e:hook("iq/"..i,function(a)
+local o=a.tags[1];
+if a.attr.type=='get'and o.name=="query"then
+local o=h(o.attr.node);
+local t=t.reply(a):add_child(o);
+e:send(t);
+return true
+end
+end);
+e:hook("iq/"..o,function(i)
+local a=i.tags[1];
+if i.attr.type=='get'and a.name=="query"then
+local n=e.disco.items[a.attr.node or false];
+local t=t.reply(i):tag('query',{
+xmlns=o,
+node=a.attr.node
+})
+for a=1,#n do
+t:tag('item',n[a]):up()
+end
+e:send(t);
+return true
+end
+end);
+local t;
+e:hook("ready",function()
+if t then return;end
+t=true;
+local function o(t)
+local a=e.disco.cache[t];
+if a then
+for a in pairs(a.identities)do
+local o,a=a:match("^(.*)/(.*)$");
+print(t,o,a)
+e:event("disco/service-discovered/"..o,{
+type=a,jid=t;
+});
+end
+end
+end
+e:disco_info(e.host,nil,function()
+o(e.host);
+end);
+e:disco_local_services(function(t)
+for a,t in ipairs(t)do
+o(t.jid);
+end
+e:event("ready");
+end);
+return true;
+end,50);
+e:hook("presence-out",function(t)
+t:remove_children("c",n);
+t:reset():add_child(e:caps()):reset();
+end,10);
+end
+end)
+package.preload['verse.plugins.version']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local o=require"verse";
+local a="jabber:iq:version";
+local function i(t,e)
+t.name=e.name;
+t.version=e.version;
+t.platform=e.platform;
+end
+function o.plugins.version(e)
+e.version={set=i};
+e:hook("iq/"..a,function(t)
+if t.attr.type~="get"then return;end
+local t=o.reply(t)
+:tag("query",{xmlns=a});
+if e.version.name then
+t:tag("name"):text(tostring(e.version.name)):up();
+end
+if e.version.version then
+t:tag("version"):text(tostring(e.version.version)):up()
+end
+if e.version.platform then
+t:tag("os"):text(e.version.platform);
+end
+e:send(t);
+return true;
+end);
+function e:query_version(e,t)
+t=t or function(e)return self:event("version/response",e);end
+self:send_iq(o.iq({type="get",to=e})
+:tag("query",{xmlns=a}),
+function(o)
+if o.attr.type=="result"then
+local e=o:get_child("query",a);
+local o=e and e:get_child_text("name");
+local a=e and e:get_child_text("version");
+local e=e and e:get_child_text("os");
+t({
+name=o;
+version=a;
+platform=e;
+});
+else
+local o,a,e=o:get_error();
+t({
+error=true;
+condition=a;
+text=e;
+type=o;
+});
+end
+end);
+end
+return true;
+end
+end)
+package.preload['verse.plugins.ping']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local a=require"verse";
+local n=require"socket".gettime;
+local i="urn:xmpp:ping";
+function a.plugins.ping(e)
+function e:ping(t,o)
+local s=n();
+e:send_iq(a.iq{to=t,type="get"}:tag("ping",{xmlns=i}),
+function(e)
+if e.attr.type=="error"then
+local a,e,i=e:get_error();
+if e~="service-unavailable"and e~="feature-not-implemented"then
+o(nil,t,{type=a,condition=e,text=i});
+return;
+end
+end
+o(n()-s,t);
+end);
+end
+e:hook("iq/"..i,function(t)
+return e:send(a.reply(t));
+end);
+return true;
+end
+end)
+package.preload['verse.plugins.uptime']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local o=require"verse";
+local t="jabber:iq:last";
+local function a(e,t)
+e.starttime=t.starttime;
+end
+function o.plugins.uptime(e)
+e.uptime={set=a};
+e:hook("iq/"..t,function(a)
+if a.attr.type~="get"then return;end
+local t=o.reply(a)
+:tag("query",{seconds=tostring(os.difftime(os.time(),e.uptime.starttime)),xmlns=t});
+e:send(t);
+return true;
+end);
+function e:query_uptime(i,a)
+a=a or function(t)return e:event("uptime/response",t);end
+e:send_iq(o.iq({type="get",to=i})
+:tag("query",{xmlns=t}),
+function(e)
+local t=e:get_child("query",t);
+if e.attr.type=="result"then
+local e=tonumber(t.attr.seconds);
+a({
+seconds=e or nil;
+});
+else
+local t,e,o=e:get_error();
+a({
+error=true;
+condition=e;
+text=o;
+type=t;
+});
+end
+end);
+end
+return true;
+end
+end)
+package.preload['verse.plugins.blocking']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local a=require"verse";
+local o="urn:xmpp:blocking";
+function a.plugins.blocking(e)
+e.blocking={};
+function e.blocking:block_jid(i,t)
+e:send_iq(a.iq{type="set"}
+:tag("block",{xmlns=o})
+:tag("item",{jid=i})
+,function()return t and t(true);end
+,function()return t and t(false);end
+);
+end
+function e.blocking:unblock_jid(i,t)
+e:send_iq(a.iq{type="set"}
+:tag("unblock",{xmlns=o})
+:tag("item",{jid=i})
+,function()return t and t(true);end
+,function()return t and t(false);end
+);
+end
+function e.blocking:unblock_all_jids(t)
+e:send_iq(a.iq{type="set"}
+:tag("unblock",{xmlns=o})
+,function()return t and t(true);end
+,function()return t and t(false);end
+);
+end
+function e.blocking:get_blocked_jids(t)
+e:send_iq(a.iq{type="get"}
+:tag("blocklist",{xmlns=o})
+,function(e)
+local a=e:get_child("blocklist",o);
+if not a then return t and t(false);end
+local e={};
+for t in a:childtags()do
+e[#e+1]=t.attr.jid;
+end
+return t and t(e);
+end
+,function(e)return t and t(false);end
+);
+end
+end
+end)
+package.preload['verse.plugins.jingle']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local o=require"verse";
+local e=require"util.timer";
+local n=require"util.uuid".generate;
+local i="urn:xmpp:jingle:1";
+local h="urn:xmpp:jingle:errors:1";
+local t={};
+t.__index=t;
+local e={};
+local e={};
+function o.plugins.jingle(e)
+e:hook("ready",function()
+e:add_disco_feature(i);
+end,10);
+function e:jingle(a)
+return o.eventable(setmetatable(base or{
+role="initiator";
+peer=a;
+sid=n();
+stream=e;
+},t));
+end
+function e:register_jingle_transport(e)
+end
+function e:register_jingle_content_type(e)
+end
+local function u(n)
+local d=n:get_child("jingle",i);
+local a=d.attr.sid;
+local s=d.attr.action;
+local a=e:event("jingle/"..a,n);
+if a==true then
+e:send(o.reply(n));
+return true;
+end
+if s~="session-initiate"then
+local t=o.error_reply(n,"cancel","item-not-found")
+:tag("unknown-session",{xmlns=h}):up();
+e:send(t);
+return;
+end
+local l=d.attr.sid;
+local a=o.eventable{
+role="receiver";
+peer=n.attr.from;
+sid=l;
+stream=e;
+};
+setmetatable(a,t);
+local r;
+local h,s;
+for t in d:childtags()do
+if t.name=="content"and t.attr.xmlns==i then
+local o=t:child_with_name("description");
+local i=o.attr.xmlns;
+if i then
+local e=e:event("jingle/content/"..i,a,o);
+if e then
+h=e;
+end
+end
+local o=t:child_with_name("transport");
+local i=o.attr.xmlns;
+s=e:event("jingle/transport/"..i,a,o);
+if h and s then
+r=t;
+break;
+end
+end
+end
+if not h then
+e:send(o.error_reply(n,"cancel","feature-not-implemented","The specified content is not supported"));
+return true;
+end
+if not s then
+e:send(o.error_reply(n,"cancel","feature-not-implemented","The specified transport is not supported"));
+return true;
+end
+e:send(o.reply(n));
+a.content_tag=r;
+a.creator,a.name=r.attr.creator,r.attr.name;
+a.content,a.transport=h,s;
+function a:decline()
+end
+e:hook("jingle/"..l,function(e)
+if e.attr.from~=a.peer then
+return false;
+end
+local e=e:get_child("jingle",i);
+return a:handle_command(e);
+end);
+e:event("jingle",a);
+return true;
+end
+function t:handle_command(a)
+local t=a.attr.action;
+e:debug("Handling Jingle command: %s",t);
+if t=="session-terminate"then
+self:destroy();
+elseif t=="session-accept"then
+self:handle_accepted(a);
+elseif t=="transport-info"then
+e:debug("Handling transport-info");
+self.transport:info_received(a);
+elseif t=="transport-replace"then
+e:error("Peer wanted to swap transport, not implemented");
+else
+e:warn("Unhandled Jingle command: %s",t);
+return nil;
+end
+return true;
+end
+function t:send_command(e,a,t)
+local e=o.iq({to=self.peer,type="set"})
+:tag("jingle",{
+xmlns=i,
+sid=self.sid,
+action=e,
+initiator=self.role=="initiator"and self.stream.jid or nil,
+responder=self.role=="responder"and self.jid or nil,
+}):add_child(a);
+if not t then
+self.stream:send(e);
+else
+self.stream:send_iq(e,t);
+end
+end
+function t:accept(a)
+local t=o.iq({to=self.peer,type="set"})
+:tag("jingle",{
+xmlns=i,
+sid=self.sid,
+action="session-accept",
+responder=e.jid,
+})
+:tag("content",{creator=self.creator,name=self.name});
+local o=self.content:generate_accept(self.content_tag:child_with_name("description"),a);
+t:add_child(o);
+local a=self.transport:generate_accept(self.content_tag:child_with_name("transport"),a);
+t:add_child(a);
+local a=self;
+e:send_iq(t,function(t)
+if t.attr.type=="error"then
+local a,t,a=t:get_error();
+e:error("session-accept rejected: %s",t);
+return false;
+end
+a.transport:connect(function(t)
+e:warn("CONNECTED (receiver)!!!");
+a.state="active";
+a:event("connected",t);
+end);
+end);
+end
+e:hook("iq/"..i,u);
+return true;
+end
+function t:offer(t,a)
+local e=o.iq({to=self.peer,type="set"})
+:tag("jingle",{xmlns=i,action="session-initiate",
+initiator=self.stream.jid,sid=self.sid});
+e:tag("content",{creator=self.role,name=t});
+local t=self.stream:event("jingle/describe/"..t,a);
+if not t then
+return false,"Unknown content type";
+end
+e:add_child(t);
+local t=self.stream:event("jingle/transport/".."urn:xmpp:jingle:transports:s5b:1",self);
+self.transport=t;
+e:add_child(t:generate_initiate());
+self.stream:debug("Hooking %s","jingle/"..self.sid);
+self.stream:hook("jingle/"..self.sid,function(e)
+if e.attr.from~=self.peer then
+return false;
+end
+local e=e:get_child("jingle",i);
+return self:handle_command(e)
+end);
+self.stream:send_iq(e,function(e)
+if e.attr.type=="error"then
+self.state="terminated";
+local t,a,e=e:get_error();
+return self:event("error",{type=t,condition=a,text=e});
+end
+end);
+self.state="pending";
+end
+function t:terminate(e)
+local e=o.stanza("reason"):tag(e or"success");
+self:send_command("session-terminate",e,function(e)
+self.state="terminated";
+self.transport:disconnect();
+self:destroy();
+end);
+end
+function t:destroy()
+self:event("terminated");
+self.stream:unhook("jingle/"..self.sid,self.handle_command);
+end
+function t:handle_accepted(e)
+local e=e:child_with_name("transport");
+self.transport:handle_accepted(e);
+self.transport:connect(function(e)
+self.stream:debug("CONNECTED (initiator)!")
+self.state="active";
+self:event("connected",e);
+end);
+end
+function t:set_source(a,o)
+local function t()
+local e,i=a();
+if e and e~=""then
+self.transport.conn:send(e);
+elseif e==""then
+return t();
+elseif e==nil then
+if o then
+self:terminate();
+end
+self.transport.conn:unhook("drained",t);
+a=nil;
+end
+end
+self.transport.conn:hook("drained",t);
+t();
+end
+function t:set_sink(t)
+self.transport.conn:hook("incoming-raw",t);
+self.transport.conn:hook("disconnected",function(e)
+self.stream:debug("Closing sink...");
+local e=e.reason;
+if e=="closed"then e=nil;end
+t(nil,e);
+end);
+end
+end)
+package.preload['verse.plugins.jingle_ft']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local i=require"verse";
+local o=require"ltn12";
+local s=package.config:sub(1,1);
+local t="urn:xmpp:jingle:apps:file-transfer:4";
+function i.plugins.jingle_ft(e)
+e:hook("ready",function()
+e:add_disco_feature(t);
+end,10);
+local a={type="file"};
+function a:generate_accept(t,e)
+if e and e.save_file then
+self.jingle:hook("connected",function()
+local e=o.sink.file(io.open(e.save_file,"w+"));
+self.jingle:set_sink(e);
+end);
+end
+return t;
+end
+local a={__index=a};
+e:hook("jingle/content/"..t,function(t,e)
+local e=e:get_child("file");
+local e={
+name=e:get_child_text("name");
+size=tonumber(e:get_child_text("size"));
+desc=e:get_child_text("desc");
+date=e:get_child_text("date");
+};
+return setmetatable({jingle=t,file=e},a);
+end);
+e:hook("jingle/describe/file",function(e)
+local a;
+if e.timestamp then
+a=os.date("!%Y-%m-%dT%H:%M:%SZ",e.timestamp);
+end
+return i.stanza("description",{xmlns=t})
+:tag("file")
+:tag("name"):text(e.filename):up()
+:tag("size"):text(tostring(e.size)):up()
+:tag("date"):text(a):up()
+:tag("desc"):text(e.description):up()
+:up();
+end);
+function e:send_file(n,t)
+local e,a=io.open(t);
+if not e then return e,a;end
+local i=e:seek("end",0);
+e:seek("set",0);
+local a=o.source.file(e);
+local e=self:jingle(n);
+e:offer("file",{
+filename=t:match("[^"..s.."]+$");
+size=i;
+});
+e:hook("connected",function()
+e:set_source(a,true);
+end);
+return e;
+end
+end
+end)
+package.preload['verse.plugins.jingle_s5b']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local a=require"verse";
+local o="urn:xmpp:jingle:transports:s5b:1";
+local r="http://jabber.org/protocol/bytestreams";
+local h=require"util.hashes".sha1;
+local s=require"util.uuid".generate;
+local function d(e,n)
+local function i()
+e:unhook("connected",i);
+return true;
+end
+local function o(t)
+e:unhook("incoming-raw",o);
+if t:sub(1,2)~="\005\000"then
+return e:event("error","connection-failure");
+end
+e:event("connected");
+return true;
+end
+local function a(t)
+e:unhook("incoming-raw",a);
+if t~="\005\000"then
+local a="version-mismatch";
+if t:sub(1,1)=="\005"then
+a="authentication-failure";
+end
+return e:event("error",a);
+end
+e:send(string.char(5,1,0,3,#n)..n.."\0\0");
+e:hook("incoming-raw",o,100);
+return true;
+end
+e:hook("connected",i,200);
+e:hook("incoming-raw",a,100);
+e:send("\005\001\000");
+end
+local function n(o,e,i)
+local e=a.new(nil,{
+streamhosts=e,
+current_host=0;
+});
+local function t(a)
+if a then
+return o(nil,a.reason);
+end
+if e.current_host<#e.streamhosts then
+e.current_host=e.current_host+1;
+e:debug("Attempting to connect to "..e.streamhosts[e.current_host].host..":"..e.streamhosts[e.current_host].port.."...");
+local a,t=e:connect(
+e.streamhosts[e.current_host].host,
+e.streamhosts[e.current_host].port
+);
+if not a then
+e:debug("Error connecting to proxy (%s:%s): %s",
+e.streamhosts[e.current_host].host,
+e.streamhosts[e.current_host].port,
+t
+);
+else
+e:debug("Connecting...");
+end
+d(e,i);
+return true;
+end
+e:unhook("disconnected",t);
+return o(nil);
+end
+e:hook("disconnected",t,100);
+e:hook("connected",function()
+e:unhook("disconnected",t);
+o(e.streamhosts[e.current_host],e);
+end,100);
+t();
+return e;
+end
+function a.plugins.jingle_s5b(e)
+e:hook("ready",function()
+e:add_disco_feature(o);
+end,10);
+local t={};
+function t:generate_initiate()
+self.s5b_sid=s();
+local i=a.stanza("transport",{xmlns=o,
+mode="tcp",sid=self.s5b_sid});
+local t=0;
+for a,o in pairs(e.proxy65.available_streamhosts)do
+t=t+1;
+i:tag("candidate",{jid=a,host=o.host,
+port=o.port,cid=a,priority=t,type="proxy"}):up();
+end
+e:debug("Have %d proxies",t)
+return i;
+end
+function t:generate_accept(e)
+local t={};
+self.s5b_peer_candidates=t;
+self.s5b_mode=e.attr.mode or"tcp";
+self.s5b_sid=e.attr.sid or self.jingle.sid;
+for e in e:childtags()do
+t[e.attr.cid]={
+type=e.attr.type;
+jid=e.attr.jid;
+host=e.attr.host;
+port=tonumber(e.attr.port)or 0;
+priority=tonumber(e.attr.priority)or 0;
+cid=e.attr.cid;
+};
+end
+local e=a.stanza("transport",{xmlns=o});
+return e;
+end
+function t:connect(i)
+e:warn("Connecting!");
+local t={};
+for a,e in pairs(self.s5b_peer_candidates or{})do
+t[#t+1]=e;
+end
+if#t>0 then
+self.connecting_peer_candidates=true;
+local function s(e,t)
+self.jingle:send_command("transport-info",a.stanza("content",{creator=self.creator,name=self.name})
+:tag("transport",{xmlns=o,sid=self.s5b_sid})
+:tag("candidate-used",{cid=e.cid}));
+self.onconnect_callback=i;
+self.conn=t;
+end
+local e=h(self.s5b_sid..self.peer..e.jid,true);
+n(s,t,e);
+else
+e:warn("Actually, I'm going to wait for my peer to tell me its streamhost...");
+self.onconnect_callback=i;
+end
+end
+function t:info_received(t)
+e:warn("Info received");
+local s=t:child_with_name("content");
+local i=s:child_with_name("transport");
+if i:get_child("candidate-used")and not self.connecting_peer_candidates then
+local t=i:child_with_name("candidate-used");
+if t then
+local function i(i,e)
+if self.jingle.role=="initiator"then
+self.jingle.stream:send_iq(a.iq({to=i.jid,type="set"})
+:tag("query",{xmlns=r,sid=self.s5b_sid})
+:tag("activate"):text(self.jingle.peer),function(i)
+if i.attr.type=="result"then
+self.jingle:send_command("transport-info",a.stanza("content",s.attr)
+:tag("transport",{xmlns=o,sid=self.s5b_sid})
+:tag("activated",{cid=t.attr.cid}));
+self.conn=e;
+self.onconnect_callback(e);
+else
+self.jingle.stream:error("Failed to activate bytestream");
+end
+end);
+end
+end
+self.jingle.stream:debug("CID: %s",self.jingle.stream.proxy65.available_streamhosts[t.attr.cid]);
+local t={
+self.jingle.stream.proxy65.available_streamhosts[t.attr.cid];
+};
+local e=h(self.s5b_sid..e.jid..self.peer,true);
+n(i,t,e);
+end
+elseif i:get_child("activated")then
+self.onconnect_callback(self.conn);
+end
+end
+function t:disconnect()
+if self.conn then
+self.conn:close();
+end
+end
+function t:handle_accepted(e)
+end
+local t={__index=t};
+e:hook("jingle/transport/"..o,function(e)
+return setmetatable({
+role=e.role,
+peer=e.peer,
+stream=e.stream,
+jingle=e,
+},t);
+end);
+end
+end)
+package.preload['verse.plugins.proxy65']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local a=require"verse";
+local r=require"util.uuid";
+local d=require"util.hashes".sha1;
+local n={};
+n.__index=n;
+local i="http://jabber.org/protocol/bytestreams";
+local s;
+function a.plugins.proxy65(t)
+t.proxy65=setmetatable({stream=t},n);
+t.proxy65.available_streamhosts={};
+local e=0;
+t:hook("disco/service-discovered/proxy",function(o)
+if o.type=="bytestreams"then
+e=e+1;
+t:send_iq(a.iq({to=o.jid,type="get"})
+:tag("query",{xmlns=i}),function(a)
+e=e-1;
+if a.attr.type=="result"then
+local e=a:get_child("query",i)
+:get_child("streamhost").attr;
+t.proxy65.available_streamhosts[e.jid]={
+jid=e.jid;
+host=e.host;
+port=tonumber(e.port);
+};
+end
+if e==0 then
+t:event("proxy65/discovered-proxies",t.proxy65.available_streamhosts);
+end
+end);
+end
+end);
+t:hook("iq/"..i,function(o)
+local e=a.new(nil,{
+initiator_jid=o.attr.from,
+streamhosts={},
+current_host=0;
+});
+for t in o.tags[1]:childtags()do
+if t.name=="streamhost"then
+table.insert(e.streamhosts,t.attr);
+end
+end
+local function i()
+if e.current_host<#e.streamhosts then
+e.current_host=e.current_host+1;
+e:connect(
+e.streamhosts[e.current_host].host,
+e.streamhosts[e.current_host].port
+);
+s(t,e,o.tags[1].attr.sid,o.attr.from,t.jid);
+return true;
+end
+e:unhook("disconnected",i);
+t:send(a.error_reply(o,"cancel","item-not-found"));
+end
+function e:accept()
+e:hook("disconnected",i,100);
+e:hook("connected",function()
+e:unhook("disconnected",i);
+local e=a.reply(o)
+:tag("query",o.tags[1].attr)
+:tag("streamhost-used",{jid=e.streamhosts[e.current_host].jid});
+t:send(e);
+end,100);
+i();
+end
+function e:refuse()
+end
+t:event("proxy65/request",e);
+end);
+end
+function n:new(t,h)
+local e=a.new(nil,{
+target_jid=t;
+bytestream_sid=r.generate();
+});
+local o=a.iq{type="set",to=t}
+:tag("query",{xmlns=i,mode="tcp",sid=e.bytestream_sid});
+for t,e in ipairs(h or self.proxies)do
+o:tag("streamhost",e):up();
+end
+self.stream:send_iq(o,function(o)
+if o.attr.type=="error"then
+local t,o,a=o:get_error();
+e:event("connection-failed",{conn=e,type=t,condition=o,text=a});
+else
+local o=o.tags[1]:get_child("streamhost-used");
+e.streamhost_jid=o.attr.jid;
+local o,n;
+for a,t in ipairs(h or self.proxies)do
+if t.jid==e.streamhost_jid then
+o,n=t.host,t.port;
+break;
+end
+end
+e:connect(o,n);
+local function o()
+e:unhook("connected",o);
+local t=a.iq{to=e.streamhost_jid,type="set"}
+:tag("query",{xmlns=i,sid=e.bytestream_sid})
+:tag("activate"):text(t);
+self.stream:send_iq(t,function(t)
+if t.attr.type=="result"then
+e:event("connected",e);
+end
+end);
+return true;
+end
+e:hook("connected",o,100);
+s(self.stream,e,e.bytestream_sid,self.stream.jid,t);
+end
+end);
+return e;
+end
+function s(i,e,a,t,o)
+local t=d(a..t..o);
+local function a()
+e:unhook("connected",a);
+return true;
+end
+local function o(t)
+e:unhook("incoming-raw",o);
+if t:sub(1,2)~="\005\000"then
+return e:event("error","connection-failure");
+end
+e:event("connected");
+return true;
+end
+local function i(a)
+e:unhook("incoming-raw",i);
+if a~="\005\000"then
+local t="version-mismatch";
+if a:sub(1,1)=="\005"then
+t="authentication-failure";
+end
+return e:event("error",t);
+end
+e:send(string.char(5,1,0,3,#t)..t.."\0\0");
+e:hook("incoming-raw",o,100);
+return true;
+end
+e:hook("connected",a,200);
+e:hook("incoming-raw",i,100);
+e:send("\005\001\000");
+end
+end)
+package.preload['verse.plugins.jingle_ibb']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local e=require"verse";
+local i=require"util.encodings".base64;
+local s=require"util.uuid".generate;
+local n="urn:xmpp:jingle:transports:ibb:1";
+local o="http://jabber.org/protocol/ibb";
+assert(i.encode("This is a test.")=="VGhpcyBpcyBhIHRlc3Qu","Base64 encoding failed");
+assert(i.decode("VGhpcyBpcyBhIHRlc3Qu")=="This is a test.","Base64 decoding failed");
+local t=table.concat
+local a={};
+local t={__index=a};
+local function h(a)
+local t=setmetatable({stream=a},t)
+t=e.eventable(t);
+return t;
+end
+function a:initiate(t,e,a)
+self.block=2048;
+self.stanza=a or'iq';
+self.peer=t;
+self.sid=e or tostring(self):match("%x+$");
+self.iseq=0;
+self.oseq=0;
+local e=function(e)
+return self:feed(e)
+end
+self.feeder=e;
+print("Hooking incoming IQs");
+local t=self.stream;
+t:hook("iq/"..o,e)
+if a=="message"then
+t:hook("message",e)
+end
+end
+function a:open(t)
+self.stream:send_iq(e.iq{to=self.peer,type="set"}
+:tag("open",{
+xmlns=o,
+["block-size"]=self.block,
+sid=self.sid,
+stanza=self.stanza
+})
+,function(e)
+if t then
+if e.attr.type~="error"then
+t(true)
+else
+t(false,e:get_error())
+end
+end
+end);
+end
+function a:send(n)
+local a=self.stanza;
+local t;
+if a=="iq"then
+t=e.iq{type="set",to=self.peer}
+elseif a=="message"then
+t=e.message{to=self.peer}
+end
+local e=self.oseq;
+self.oseq=e+1;
+t:tag("data",{xmlns=o,sid=self.sid,seq=e})
+:text(i.encode(n));
+if a=="iq"then
+self.stream:send_iq(t,function(e)
+self:event(e.attr.type=="result"and"drained"or"error");
+end)
+else
+stream:send(t)
+self:event("drained");
+end
+end
+function a:feed(t)
+if t.attr.from~=self.peer then return end
+local a=t[1];
+if a.attr.sid~=self.sid then return end
+local n;
+if a.name=="open"then
+self:event("connected");
+self.stream:send(e.reply(t))
+return true
+elseif a.name=="data"then
+local o=t:get_child_text("data",o);
+local a=tonumber(a.attr.seq);
+local n=self.iseq;
+if o and a then
+if a~=n then
+self.stream:send(e.error_reply(t,"cancel","not-acceptable","Wrong sequence. Packet lost?"))
+self:close();
+self:event("error");
+return true;
+end
+self.iseq=a+1;
+local a=i.decode(o);
+if self.stanza=="iq"then
+self.stream:send(e.reply(t))
+end
+self:event("incoming-raw",a);
+return true;
+end
+elseif a.name=="close"then
+self.stream:send(e.reply(t))
+self:close();
+return true
+end
+end
+function a:close()
+self.stream:unhook("iq/"..o,self.feeder)
+self:event("disconnected");
+end
+function e.plugins.jingle_ibb(a)
+a:hook("ready",function()
+a:add_disco_feature(n);
+end,10);
+local t={};
+function t:_setup()
+local e=h(self.stream);
+e.sid=self.sid or e.sid;
+e.stanza=self.stanza or e.stanza;
+e.block=self.block or e.block;
+e:initiate(self.peer,self.sid,self.stanza);
+self.conn=e;
+end
+function t:generate_initiate()
+print("ibb:generate_initiate() as "..self.role);
+local t=s();
+self.sid=t;
+self.stanza='iq';
+self.block=2048;
+local e=e.stanza("transport",{xmlns=n,
+sid=self.sid,stanza=self.stanza,["block-size"]=self.block});
+return e;
+end
+function t:generate_accept(t)
+print("ibb:generate_accept() as "..self.role);
+local e=t.attr;
+self.sid=e.sid or self.sid;
+self.stanza=e.stanza or self.stanza;
+self.block=e["block-size"]or self.block;
+self:_setup();
+return t;
+end
+function t:connect(t)
+if not self.conn then
+self:_setup();
+end
+local e=self.conn;
+print("ibb:connect() as "..self.role);
+if self.role=="initiator"then
+e:open(function(a,...)
+assert(a,table.concat({...},", "));
+t(e);
+end);
+else
+t(e);
+end
+end
+function t:info_received(e)
+print("ibb:info_received()");
+end
+function t:disconnect()
+if self.conn then
+self.conn:close()
+end
+end
+function t:handle_accepted(e)end
+local t={__index=t};
+a:hook("jingle/transport/"..n,function(e)
+return setmetatable({
+role=e.role,
+peer=e.peer,
+stream=e.stream,
+jingle=e,
+},t);
+end);
+end
+end)
+package.preload['verse.plugins.pubsub']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local o=require"verse";
+local s=table.insert;
+local i="http://jabber.org/protocol/pubsub";
+local h="http://jabber.org/protocol/pubsub#owner";
+local r="http://jabber.org/protocol/pubsub#event";
+local e={};
+local n={__index=e};
+function o.plugins.pubsub(e)
+e.pubsub=setmetatable({stream=e},n);
+e:hook("message",function(t)
+local a=t.attr.from;
+for t in t:childtags("event",r)do
+local t=t:get_child("items");
+if t then
+local o=t.attr.node;
+for t in t:childtags("item")do
+e:event("pubsub/event",{
+from=a;
+node=o;
+item=t;
+});
+end
+end
+end
+end);
+return true;
+end
+function e:create(e,t,a)
+return self:service(e):node(t):create(nil,a);
+end
+function e:subscribe(a,o,t,e)
+return self:service(a):node(o):subscribe(t,nil,e);
+end
+function e:publish(i,o,a,t,e)
+return self:service(i):node(o):publish(a,nil,t,e);
+end
+local a={};
+local t={__index=a};
+function e:service(e)
+return setmetatable({stream=self.stream,service=e},t)
+end
+local function t(t,n,s,a,r,h,e)
+local t=o.iq{type=t or"get",to=n}
+:tag("pubsub",{xmlns=s or i})
+if a then t:tag(a,{node=r,jid=h});end
+if e then t:tag("item",{id=e~=true and e or nil});end
+return t;
+end
+function a:subscriptions(o)
+self.stream:send_iq(t(nil,self.service,nil,"subscriptions")
+,o and function(a)
+if a.attr.type=="result"then
+local e=a:get_child("pubsub",i);
+local e=e and e:get_child("subscriptions");
+local a={};
+if e then
+for t in e:childtags("subscription")do
+local e=self:node(t.attr.node)
+e.subscription=t;
+e.subscribed_jid=t.attr.jid;
+s(a,e);
+end
+end
+o(a);
+else
+o(false,a:get_error());
+end
+end or nil);
+end
+function a:affiliations(e)
+self.stream:send_iq(t(nil,self.service,nil,"affiliations")
+,e and function(t)
+if t.attr.type=="result"then
+local t=t:get_child("pubsub",i);
+local a=t and t:get_child("affiliations")or{};
+local t={};
+if a then
+for a in a:childtags("affiliation")do
+local e=self:node(a.attr.node)
+e.affiliation=a;
+s(t,e);
+end
+end
+e(t);
+else
+e(false,t:get_error());
+end
+end or nil);
+end
+function a:nodes(a)
+self.stream:disco_items(self.service,nil,function(e,...)
+if e then
+for t=1,#e do
+e[t]=self:node(e[t].node);
+end
+end
+a(e,...)
+end);
+end
+local e={};
+local o={__index=e};
+function a:node(e)
+return setmetatable({stream=self.stream,service=self.service,node=e},o)
+end
+function n:__call(t,e)
+local t=self:service(t);
+return e and t:node(e)or t;
+end
+function e:hook(a,o)
+self._hooks=self._hooks or setmetatable({},{__mode='kv'});
+local function t(e)
+if(not e.service or e.from==self.service)and e.node==self.node then
+return a(e)
+end
+end
+self._hooks[a]=t;
+self.stream:hook("pubsub/event",t,o);
+return t;
+end
+function e:unhook(e)
+if e then
+local e=self._hooks[e];
+self.stream:unhook("pubsub/event",e);
+elseif self._hooks then
+for e in pairs(self._hooks)do
+self.stream:unhook("pubsub/event",e);
+end
+end
+end
+function e:create(a,e)
+if a~=nil then
+error("Not implemented yet.");
+else
+self.stream:send_iq(t("set",self.service,nil,"create",self.node),e);
+end
+end
+function e:configure(e,a)
+if e~=nil then
+error("Not implemented yet.");
+end
+self.stream:send_iq(t("set",self.service,nil,e==nil and"default"or"configure",self.node),a);
+end
+function e:publish(i,a,o,e)
+if a~=nil then
+error("Node configuration is not implemented yet.");
+end
+self.stream:send_iq(t("set",self.service,nil,"publish",self.node,nil,i or true)
+:add_child(o)
+,e);
+end
+function e:subscribe(e,a,o)
+e=e or self.stream.jid;
+if a~=nil then
+error("Subscription configuration is not implemented yet.");
+end
+self.stream:send_iq(t("set",self.service,nil,"subscribe",self.node,e)
+,o);
+end
+function e:subscription(e)
+error("Not implemented yet.");
+end
+function e:affiliation(e)
+error("Not implemented yet.");
+end
+function e:unsubscribe(e,a)
+e=e or self.subscribed_jid or self.stream.jid;
+self.stream:send_iq(t("set",self.service,nil,"unsubscribe",self.node,e)
+,a);
+end
+function e:configure_subscription(e,e)
+error("Not implemented yet.");
+end
+function e:items(a,e)
+if a then
+self.stream:send_iq(t("get",self.service,nil,"items",self.node)
+,e);
+else
+self.stream:disco_items(self.service,self.node,e);
+end
+end
+function e:item(e,a)
+self.stream:send_iq(t("get",self.service,nil,"items",self.node,nil,e)
+,a);
+end
+function e:retract(e,a)
+self.stream:send_iq(t("set",self.service,nil,"retract",self.node,nil,e)
+,a);
+end
+function e:purge(a,e)
+assert(not a,"Not implemented yet.");
+self.stream:send_iq(t("set",self.service,h,"purge",self.node)
+,e);
+end
+function e:delete(a,e)
+assert(not a,"Not implemented yet.");
+self.stream:send_iq(t("set",self.service,h,"delete",self.node)
+,e);
+end
+end)
+package.preload['verse.plugins.pep']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local e=require"verse";
+local t="http://jabber.org/protocol/pubsub";
+local t=t.."#event";
+function e.plugins.pep(e)
+e:add_plugin("disco");
+e:add_plugin("pubsub");
+e.pep={};
+e:hook("pubsub/event",function(t)
+return e:event("pep/"..t.node,{from=t.from,item=t.item.tags[1]});
+end);
+function e:hook_pep(t,o,i)
+local a=e.events._handlers["pep/"..t];
+if not(a)or#a==0 then
+e:add_disco_feature(t.."+notify");
+end
+e:hook("pep/"..t,o,i);
+end
+function e:unhook_pep(t,a)
+e:unhook("pep/"..t,a);
+local a=e.events._handlers["pep/"..t];
+if not(a)or#a==0 then
+e:remove_disco_feature(t.."+notify");
+end
+end
+function e:publish_pep(t,a,o)
+return e.pubsub:service(nil):node(a or t.attr.xmlns):publish(o or"current",nil,t)
+end
+end
+end)
+package.preload['verse.plugins.adhoc']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local o=require"verse";
+local n=require"lib.adhoc";
+local t="http://jabber.org/protocol/commands";
+local s="jabber:x:data";
+local a={};
+a.__index=a;
+local i={};
+function o.plugins.adhoc(e)
+e:add_plugin("disco");
+e:add_disco_feature(t);
+function e:query_commands(a,o)
+e:disco_items(a,t,function(a)
+e:debug("adhoc list returned")
+local t={};
+for o,a in ipairs(a)do
+t[a.node]=a.name;
+end
+e:debug("adhoc calling callback")
+return o(t);
+end);
+end
+function e:execute_command(t,o,i)
+local e=setmetatable({
+stream=e,jid=t,
+command=o,callback=i
+},a);
+return e:execute();
+end
+local function r(t,e)
+if not(e)or e=="user"then return true;end
+if type(e)=="function"then
+return e(t);
+end
+end
+function e:add_adhoc_command(o,a,s,h)
+i[a]=n.new(o,a,s,h);
+e:add_disco_item({jid=e.jid,node=a,name=o},t);
+return i[a];
+end
+local function s(a)
+local t=a.tags[1];
+local t=t.attr.node;
+local t=i[t];
+if not t then return;end
+if not r(a.attr.from,t.permission)then
+e:send(o.error_reply(a,"auth","forbidden","You don't have permission to execute this command"):up()
+:add_child(t:cmdtag("canceled")
+:tag("note",{type="error"}):text("You don't have permission to execute this command")));
+return true
+end
+return n.handle_cmd(t,{send=function(t)return e:send(t)end},a);
+end
+e:hook("iq/"..t,function(e)
+local a=e.attr.type;
+local t=e.tags[1].name;
+if a=="set"and t=="command"then
+return s(e);
+end
+end);
+end
+function a:_process_response(e)
+if e.attr.type=="error"then
+self.status="canceled";
+self.callback(self,{});
+return;
+end
+local e=e:get_child("command",t);
+self.status=e.attr.status;
+self.sessionid=e.attr.sessionid;
+self.form=e:get_child("x",s);
+self.note=e:get_child("note");
+self.callback(self);
+end
+function a:execute()
+local e=o.iq({to=self.jid,type="set"})
+:tag("command",{xmlns=t,node=self.command});
+self.stream:send_iq(e,function(e)
+self:_process_response(e);
+end);
+end
+function a:next(a)
+local e=o.iq({to=self.jid,type="set"})
+:tag("command",{
+xmlns=t,
+node=self.command,
+sessionid=self.sessionid
+});
+if a then e:add_child(a);end
+self.stream:send_iq(e,function(e)
+self:_process_response(e);
+end);
+end
+end)
+package.preload['verse.plugins.presence']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local a=require"verse";
+function a.plugins.presence(t)
+t.last_presence=nil;
+t:hook("presence-out",function(e)
+if not e.attr.to then
+t.last_presence=e;
+end
+end,1);
+function t:resend_presence()
+if self.last_presence then
+t:send(self.last_presence);
+end
+end
+function t:set_status(e)
+local a=a.presence();
+if type(e)=="table"then
+if e.show then
+a:tag("show"):text(e.show):up();
+end
+if e.priority or e.prio then
+a:tag("priority"):text(tostring(e.priority or e.prio)):up();
+end
+if e.status or e.msg then
+a:tag("status"):text(e.status or e.msg):up();
+end
+elseif type(e)=="string"then
+a:tag("status"):text(e):up();
+end
+t:send(a);
+end
+end
+end)
+package.preload['verse.plugins.private']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local t=require"verse";
+local a="jabber:iq:private";
+function t.plugins.private(o)
+function o:private_set(o,i,e,n)
+local t=t.iq({type="set"})
+:tag("query",{xmlns=a});
+if e then
+if e.name==o and e.attr and e.attr.xmlns==i then
+t:add_child(e);
+else
+t:tag(o,{xmlns=i})
+:add_child(e);
+end
+end
+self:send_iq(t,n);
+end
+function o:private_get(e,o,i)
+self:send_iq(t.iq({type="get"})
+:tag("query",{xmlns=a})
+:tag(e,{xmlns=o}),
+function(t)
+if t.attr.type=="result"then
+local t=t:get_child("query",a);
+local e=t:get_child(e,o);
+i(e);
+end
+end);
+end
+end
+end)
+package.preload['verse.plugins.roster']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local i=require"verse";
+local d=require"util.jid".bare;
+local a="jabber:iq:roster";
+local o="urn:xmpp:features:rosterver";
+local n=table.insert;
+function i.plugins.roster(t)
+local s=false;
+local e={
+items={};
+ver="";
+};
+t.roster=e;
+t:hook("stream-features",function(e)
+if e:get_child("ver",o)then
+s=true;
+end
+end);
+local function h(e)
+local t=i.stanza("item",{xmlns=a});
+for a,e in pairs(e)do
+if a~="groups"then
+t.attr[a]=e;
+else
+for a=1,#e do
+t:tag("group"):text(e[a]):up();
+end
+end
+end
+return t;
+end
+local function r(o)
+local e={};
+local a={};
+e.groups=a;
+for t,a in pairs(o.attr)do
+if t~="xmlns"then
+e[t]=a
+end
+end
+for e in o:childtags("group")do
+n(a,e:get_text())
+end
+return e;
+end
+function e:load(t)
+e.ver,e.items=t.ver,t.items;
+end
+function e:dump()
+return{
+ver=e.ver,
+items=e.items,
+};
+end
+function e:add_contact(o,n,s,e)
+local o={jid=o,name=n,groups=s};
+local a=i.iq({type="set"})
+:tag("query",{xmlns=a})
+:add_child(h(o));
+t:send_iq(a,function(t)
+if not e then return end
+if t.attr.type=="result"then
+e(true);
+else
+e(nil,t);
+end
+end);
+end
+function e:delete_contact(o,n)
+o=(type(o)=="table"and o.jid)or o;
+local s={jid=o,subscription="remove"}
+if not e.items[o]then return false,"item-not-found";end
+t:send_iq(i.iq({type="set"})
+:tag("query",{xmlns=a})
+:add_child(h(s)),
+function(e)
+if not n then return end
+if e.attr.type=="result"then
+n(true);
+else
+n(nil,e);
+end
+end);
+end
+local function h(t)
+local t=r(t);
+e.items[t.jid]=t;
+end
+local function r(t)
+local a=e.items[t];
+e.items[t]=nil;
+return a;
+end
+function e:fetch(n)
+t:send_iq(i.iq({type="get"}):tag("query",{xmlns=a,ver=s and e.ver or nil}),
+function(o)
+if o.attr.type=="result"then
+local t=o:get_child("query",a);
+if t then
+e.items={};
+for t in t:childtags("item")do
+h(t)
+end
+e.ver=t.attr.ver or"";
+end
+n(e);
+else
+n(nil,o);
+end
+end);
+end
+t:hook("iq/"..a,function(n)
+local s,o=n.attr.type,n.attr.from;
+if s=="set"and(not o or o==d(t.jid))then
+local s=n:get_child("query",a);
+local o=s and s:get_child("item");
+if o then
+local i,a;
+local n=o.attr.jid;
+if o.attr.subscription=="remove"then
+i="removed"
+a=r(n);
+else
+i=e.items[n]and"changed"or"added";
+h(o)
+a=e.items[n];
+end
+e.ver=s.attr.ver;
+if a then
+t:event("roster/item-"..i,a);
+end
+end
+t:send(i.reply(n))
+return true;
+end
+end);
+end
+end)
+package.preload['verse.plugins.register']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local t=require"verse";
+local o="jabber:iq:register";
+function t.plugins.register(e)
+local function a(i)
+if i:get_child("register","http://jabber.org/features/iq-register")then
+local t=t.iq({to=e.host_,type="set"})
+:tag("query",{xmlns=o})
+:tag("username"):text(e.username):up()
+:tag("password"):text(e.password):up();
+if e.register_email then
+t:tag("email"):text(e.register_email):up();
+end
+e:send_iq(t,function(t)
+if t.attr.type=="result"then
+e:event("registration-success");
+else
+local a,t,o=t:get_error();
+e:debug("Registration failed: %s",t);
+e:event("registration-failure",{type=a,condition=t,text=o});
+end
+end);
+else
+e:debug("In-band registration not offered by server");
+e:event("registration-failure",{condition="service-unavailable"});
+end
+e:unhook("stream-features",a);
+return true;
+end
+e:hook("stream-features",a,310);
+end
+end)
+package.preload['verse.plugins.groupchat']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local i=require"verse";
+local e=require"util.events";
+local n=require"util.jid";
+local a={};
+a.__index=a;
+local h="urn:xmpp:delay";
+local s="http://jabber.org/protocol/muc";
+function i.plugins.groupchat(o)
+o:add_plugin("presence")
+o.rooms={};
+o:hook("stanza",function(e)
+local a=n.bare(e.attr.from);
+if not a then return end
+local t=o.rooms[a]
+if not t and e.attr.to and a then
+t=o.rooms[e.attr.to.." "..a]
+end
+if t and t.opts.source and e.attr.to~=t.opts.source then return end
+if t then
+local o=select(3,n.split(e.attr.from));
+local n=e:get_child_text("body");
+local i=e:get_child("delay",h);
+local a={
+room_jid=a;
+room=t;
+sender=t.occupants[o];
+nick=o;
+body=n;
+stanza=e;
+delay=(i and i.attr.stamp);
+};
+local t=t:event(e.name,a);
+return t or(e.name=="message")or nil;
+end
+end,500);
+function o:join_room(n,h,t,r)
+if not h then
+return false,"no nickname supplied"
+end
+t=t or{};
+local e=setmetatable(i.eventable{
+stream=o,jid=n,nick=h,
+subject=nil,
+occupants={},
+opts=t,
+},a);
+if t.source then
+self.rooms[t.source.." "..n]=e;
+else
+self.rooms[n]=e;
+end
+local a=e.occupants;
+e:hook("presence",function(o)
+local t=o.nick or h;
+if not a[t]and o.stanza.attr.type~="unavailable"then
+a[t]={
+nick=t;
+jid=o.stanza.attr.from;
+presence=o.stanza;
+};
+local o=o.stanza:get_child("x",s.."#user");
+if o then
+local e=o:get_child("item");
+if e and e.attr then
+a[t].real_jid=e.attr.jid;
+a[t].affiliation=e.attr.affiliation;
+a[t].role=e.attr.role;
+end
+end
+if t==e.nick then
+e.stream:event("groupchat/joined",e);
+else
+e:event("occupant-joined",a[t]);
+end
+elseif a[t]and o.stanza.attr.type=="unavailable"then
+if t==e.nick then
+e.stream:event("groupchat/left",e);
+if e.opts.source then
+self.rooms[e.opts.source.." "..n]=nil;
+else
+self.rooms[n]=nil;
+end
+else
+a[t].presence=o.stanza;
+e:event("occupant-left",a[t]);
+a[t]=nil;
+end
+end
+end);
+e:hook("message",function(a)
+local t=a.stanza:get_child_text("subject");
+if not t then return end
+t=#t>0 and t or nil;
+if t~=e.subject then
+local o=e.subject;
+e.subject=t;
+return e:event("subject-changed",{from=o,to=t,by=a.sender,event=a});
+end
+end,2e3);
+local t=i.presence():tag("x",{xmlns=s}):reset();
+if r then
+t:get_child("x",s):tag("password"):text(r):reset();
+end
+self:event("pre-groupchat/joining",t);
+e:send(t)
+self:event("groupchat/joining",e);
+return e;
+end
+o:hook("presence-out",function(e)
+if not e.attr.to then
+for a,t in pairs(o.rooms)do
+t:send(e);
+end
+e.attr.to=nil;
+end
+end);
+end
+function a:send(e)
+if e.name=="message"and not e.attr.type then
+e.attr.type="groupchat";
+end
+if e.name=="presence"then
+e.attr.to=self.jid.."/"..self.nick;
+end
+if e.attr.type=="groupchat"or not e.attr.to then
+e.attr.to=self.jid;
+end
+if self.opts.source then
+e.attr.from=self.opts.source
+end
+self.stream:send(e);
+end
+function a:send_message(e)
+self:send(i.message():tag("body"):text(e));
+end
+function a:set_subject(e)
+self:send(i.message():tag("subject"):text(e));
+end
+function a:leave(e)
+self.stream:event("groupchat/leaving",self);
+local t=i.presence({type="unavailable"});
+if e then
+t:tag("status"):text(e);
+end
+self:send(t);
+end
+function a:admin_set(o,a,t,e)
+self:send(i.iq({type="set"})
+:query(s.."#admin")
+:tag("item",{nick=o,[a]=t})
+:tag("reason"):text(e or""));
+end
+function a:set_role(e,a,t)
+self:admin_set(e,"role",a,t);
+end
+function a:set_affiliation(a,t,e)
+self:admin_set(a,"affiliation",t,e);
+end
+function a:kick(e,t)
+self:set_role(e,"none",t);
+end
+function a:ban(e,t)
+self:set_affiliation(e,"outcast",t);
+end
+end)
+package.preload['verse.plugins.vcard']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local i=require"verse";
+local o=require"util.vcard";
+local e="vcard-temp";
+function i.plugins.vcard(a)
+function a:get_vcard(n,t)
+a:send_iq(i.iq({to=n,type="get"})
+:tag("vCard",{xmlns=e}),t and function(a)
+local e=a:get_child("vCard",e);
+if a.attr.type=="result"and e then
+e=o.from_xep54(e)
+t(e)
+else
+t(false)
+end
+end or nil);
+end
+function a:set_vcard(e,n)
+local t;
+if type(e)=="table"and e.name then
+t=e;
+elseif type(e)=="string"then
+t=o.to_xep54(o.from_text(e)[1]);
+elseif type(e)=="table"then
+t=o.to_xep54(e);
+error("Converting a table to vCard not implemented")
+end
+if not t then return false end
+a:debug("setting vcard to %s",tostring(t));
+a:send_iq(i.iq({type="set"})
+:add_child(t),n);
+end
+end
+end)
+package.preload['verse.plugins.vcard_update']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local n=require"verse";
+local i="vcard-temp:x:update";
+local s=require("util.hashes").sha1;
+local e,t=pcall(function()
+local e=require("util.encodings").base64.decode;
+assert(e("SGVsbG8=")=="Hello")
+return e;
+end);
+if not e then
+e,t=pcall(function()return require("mime").unb64;end);
+if not e then
+error("Could not find a base64 decoder")
+end
+end
+local h=t;
+function n.plugins.vcard_update(e)
+e:add_plugin("vcard");
+e:add_plugin("presence");
+local t;
+local function r(a)
+local o;
+for e=1,#a do
+if a[e].name=="PHOTO"then
+o=a[e][1];
+break
+end
+end
+if o then
+local a=s(h(o),true);
+t=n.stanza("x",{xmlns=i})
+:tag("photo"):text(a);
+e:resend_presence()
+else
+t=nil;
+end
+end
+local a;
+e:hook("ready",function()
+if a then return;end
+a=true;
+e:get_vcard(nil,function(t)
+if t then
+r(t)
+end
+e:event("ready");
+end);
+return true;
+end,3);
+e:hook("presence-out",function(e)
+if t and not e:get_child("x",i)then
+e:add_child(t);
+end
+end,10);
+end
+end)
+package.preload['verse.plugins.carbons']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local a=require"verse";
+local o="urn:xmpp:carbons:2";
+local s="urn:xmpp:forward:0";
+local n=os.time;
+local h=require"util.datetime".parse;
+local r=require"util.jid".bare;
+function a.plugins.carbons(e)
+local t={};
+t.enabled=false;
+e.carbons=t;
+function t:enable(i)
+e:send_iq(a.iq{type="set"}
+:tag("enable",{xmlns=o})
+,function(e)
+local e=e.attr.type=="result";
+if e then
+t.enabled=true;
+end
+if i then
+i(e);
+end
+end or nil);
+end
+function t:disable(i)
+e:send_iq(a.iq{type="set"}
+:tag("disable",{xmlns=o})
+,function(e)
+local e=e.attr.type=="result";
+if e then
+t.enabled=false;
+end
+if i then
+i(e);
+end
+end or nil);
+end
+local i;
+e:hook("bind-success",function()
+i=r(e.jid);
+end);
+e:hook("message",function(a)
+local t=a:get_child(nil,o);
+if a.attr.from==i and t then
+local o=t.name;
+local t=t:get_child("forwarded",s);
+local a=t and t:get_child("message","jabber:client");
+local t=t:get_child("delay","urn:xmpp:delay");
+local t=t and t.attr.stamp;
+t=t and h(t);
+if a then
+return e:event("carbon",{
+dir=o,
+stanza=a,
+timestamp=t or n(),
+});
+end
+end
+end,1);
+end
+end)
+package.preload['verse.plugins.archive']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local o=require"verse";
+local t=require"util.stanza";
+local a="urn:xmpp:mam:2"
+local l="urn:xmpp:forward:0";
+local d="urn:xmpp:delay";
+local i=require"util.uuid".generate;
+local u=require"util.datetime".parse;
+local h=require"util.datetime".datetime;
+local e=require"util.dataforms".new;
+local r=require"util.rsm";
+local c={};
+local m=e{
+{name="FORM_TYPE";type="hidden";value=a;};
+{name="with";type="jid-single";};
+{name="start";type="text-single"};
+{name="end";type="text-single";};
+};
+function o.plugins.archive(o)
+function o:query_archive(o,e,n)
+local i=i();
+local o=t.iq{type="set",to=o}
+:tag("query",{xmlns=a,queryid=i});
+local t,s=tonumber(e["start"]),tonumber(e["end"]);
+e["start"]=t and h(t);
+e["end"]=s and h(s);
+o:add_child(m:form(e,"submit"));
+o:add_child(r.generate(e));
+local t={};
+local function s(e)
+local e=e:get_child("result",a);
+if e and e.attr.queryid==i then
+local a=e:get_child("forwarded",l);
+local o=e.attr.id;
+local e=a:get_child("delay",d);
+local e=e and u(e.attr.stamp)or nil;
+local a=a:get_child("message","jabber:client")
+t[#t+1]={id=o,stamp=e,message=a};
+return true
+end
+end
+self:hook("message",s,1);
+self:send_iq(o,function(e)
+self:unhook("message",s);
+if e.attr.type=="error"then
+self:warn(table.concat({e:get_error()}," "))
+n(false,e:get_error())
+return true;
+end
+local e=e:get_child("fin",a)
+if e then
+local e=r.get(e);
+for a,e in pairs(e or c)do t[a]=e;end
+end
+n(t);
+return true
+end);
+end
+local i={
+always=true,[true]="always",
+never=false,[false]="never",
+roster="roster",
+}
+local function s(t)
+local e={};
+local a=t.attr.default;
+if a then
+e[false]=i[a];
+end
+local a=t:get_child("always");
+if a then
+for t in a:childtags("jid")do
+local t=t:get_text();
+e[t]=true;
+end
+end
+local t=t:get_child("never");
+if t then
+for t in t:childtags("jid")do
+local t=t:get_text();
+e[t]=false;
+end
+end
+return e;
+end
+local function n(o)
+local e
+e,o[false]=o[false],nil;
+if e~=nil then
+e=i[e];
+end
+local a=t.stanza("prefs",{xmlns=a,default=e})
+local e=t.stanza("always");
+local t=t.stanza("never");
+for o,a in pairs(o)do
+(a and e or t):tag("jid"):text(o):up();
+end
+return a:add_child(e):add_child(t);
+end
+function o:archive_prefs_get(o)
+self:send_iq(t.iq{type="get"}:tag("prefs",{xmlns=a}),
+function(e)
+if e and e.attr.type=="result"and e.tags[1]then
+local t=s(e.tags[1]);
+o(t,e);
+else
+o(nil,e);
+end
+end);
+end
+function o:archive_prefs_set(e,a)
+self:send_iq(t.iq{type="set"}:add_child(n(e)),a);
+end
+end
+end)
+package.preload['util.http']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local t,s=string.format,string.char;
+local d,n,o=pairs,ipairs,tonumber;
+local i,h=table.insert,table.concat;
+local function r(e)
+return e and(e:gsub("[^a-zA-Z0-9.~_-]",function(e)return t("%%%02x",e:byte());end));
+end
+local function a(e)
+return e and(e:gsub("%%(%x%x)",function(e)return s(o(e,16));end));
+end
+local function e(e)
+return e and(e:gsub("%W",function(e)
+if e~=" "then
+return t("%%%02x",e:byte());
+else
+return"+";
+end
+end));
+end
+local function s(t)
+local a={};
+if t[1]then
+for o,t in n(t)do
+i(a,e(t.name).."="..e(t.value));
+end
+else
+for o,t in d(t)do
+i(a,e(o).."="..e(t));
+end
+end
+return h(a,"&");
+end
+local function n(e)
+if not e:match("=")then return a(e);end
+local o={};
+for t,e in e:gmatch("([^=&]*)=([^&]*)")do
+t,e=t:gsub("%+","%%20"),e:gsub("%+","%%20");
+t,e=a(t),a(e);
+i(o,{name=t,value=e});
+o[t]=e;
+end
+return o;
+end
+local function t(e,t)
+e=","..e:gsub("[ \t]",""):lower()..",";
+return e:find(","..t:lower()..",",1,true)~=nil;
+end
+return{
+urlencode=r,urldecode=a;
+formencode=s,formdecode=n;
+contains_token=t;
+};
+end)
+package.preload['net.http.parser']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local w=tonumber;
+local a=assert;
+local b=require"socket.url".parse;
+local t=require"util.http".urldecode;
+local function g(e)
+e=t((e:gsub("//+","/")));
+if e:sub(1,1)~="/"then
+e="/"..e;
+end
+local t=0;
+for e in e:gmatch("([^/]+)/")do
+if e==".."then
+t=t-1;
+elseif e~="."then
+t=t+1;
+end
+if t<0 then
+return nil;
+end
+end
+return e;
+end
+local y={};
+function y.new(u,h,e,p)
+local d=true;
+if not e or e=="server"then d=false;else a(e=="client","Invalid parser type");end
+local e="";
+local y,a,r;
+local s=nil;
+local t;
+local o;
+local c;
+local n;
+return{
+feed=function(l,i)
+if n then return nil,"parse has failed";end
+if not i then
+if s and d and not o then
+t.body=e;
+u(t);
+elseif e~=""then
+n=true;return h();
+end
+return;
+end
+e=e..i;
+while#e>0 do
+if s==nil then
+local m=e:find("\r\n\r\n",nil,true);
+if not m then return;end
+local u,r,l,i,v;
+local f;
+local a={};
+for t in e:sub(1,m+1):gmatch("([^\r\n]+)\r\n")do
+if f then
+local e,t=t:match("^([^%s:]+): *(.*)$");
+if not e then n=true;return h("invalid-header-line");end
+e=e:lower();
+a[e]=a[e]and a[e]..","..t or t;
+else
+f=t;
+if d then
+l,i,v=t:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$");
+i=w(i);
+if not i then n=true;return h("invalid-status-line");end
+c=not
+((p and p().method=="HEAD")
+or(i==204 or i==304 or i==301)
+or(i>=100 and i<200));
+else
+u,r,l=t:match("^(%w+) (%S+) HTTP/(1%.[01])$");
+if not u then n=true;return h("invalid-status-line");end
+end
+end
+end
+if not f then n=true;return h("invalid-status-line");end
+y=c and a["transfer-encoding"]=="chunked";
+o=w(a["content-length"]);
+if d then
+if not c then o=0;end
+t={
+code=i;
+httpversion=l;
+headers=a;
+body=c and""or nil;
+responseversion=l;
+responseheaders=a;
+};
+else
+local e;
+if r:byte()==47 then
+local a,t=r:match("([^?]*).?(.*)");
+if t==""then t=nil;end
+e={path=a,query=t};
+else
+e=b(r);
+if not(e and e.path)then n=true;return h("invalid-url");end
+end
+r=g(e.path);
+a.host=e.host or a.host;
+o=o or 0;
+t={
+method=u;
+url=e;
+path=r;
+httpversion=l;
+headers=a;
+body=nil;
+};
+end
+e=e:sub(m+4);
+s=true;
+end
+if s then
+if d then
+if y then
+if not e:find("\r\n",nil,true)then
+return;
+end
+if not a then
+a,r=e:match("^(%x+)[^\r\n]*\r\n()");
+a=a and w(a,16);
+if not a then n=true;return h("invalid-chunk-size");end
+end
+if a==0 and e:find("\r\n\r\n",r-2,true)then
+s,a=nil,nil;
+e=e:gsub("^.-\r\n\r\n","");
+u(t);
+elseif#e-r-2>=a then
+t.body=t.body..e:sub(r,r+(a-1));
+e=e:sub(r+a+2);
+a,r=nil,nil;
+else
+break;
+end
+elseif o and#e>=o then
+if t.code==101 then
+t.body,e=e,"";
+else
+t.body,e=e:sub(1,o),e:sub(o+1);
+end
+s=nil;u(t);
+else
+break;
+end
+elseif#e>=o then
+t.body,e=e:sub(1,o),e:sub(o+1);
+s=nil;u(t);
+else
+break;
+end
+end
+end
+end;
+};
+end
+return y;
+end)
+package.preload['net.http']=(function(...)
+local _ENV=_ENV;
+local function a(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local y=require"socket"
+local q=require"util.encodings".base64.encode;
+local r=require"socket.url"
+local d=require"net.http.parser".new;
+local h=require"util.http";
+local x=pcall(require,"ssl");
+local j=require"net.server"
+local l,o=table.insert,table.concat;
+local m=pairs;
+local v,c,p,b,s=
+tonumber,tostring,xpcall,select,debug.traceback;
+local g,k=assert,error
+local u=require"util.logger".init("http");
+a"http"
+local i={};
+local n={default_port=80,default_mode="*a"};
+function n.onconnect(t)
+local e=i[t];
+local a={e.method or"GET"," ",e.path," HTTP/1.1\r\n"};
+if e.query then
+l(a,4,"?"..e.query);
+end
+t:write(o(a));
+local a={[2]=": ",[4]="\r\n"};
+for e,i in m(e.headers)do
+a[1],a[3]=e,i;
+t:write(o(a));
+end
+t:write("\r\n");
+if e.body then
+t:write(e.body);
+end
+end
+function n.onincoming(a,t)
+local e=i[a];
+if not e then
+u("warn","Received response from connection %s with no request attached!",c(a));
+return;
+end
+if t and e.reader then
+e:reader(t);
+end
+end
+function n.ondisconnect(t,a)
+local e=i[t];
+if e and e.conn then
+e:reader(nil,a);
+end
+i[t]=nil;
+end
+function n.ondetach(e)
+i[e]=nil;
+end
+local function f(e,o,t)
+if not e.parser then
+local function a(t)
+if e.callback then
+e.callback(t or"connection-closed",0,e);
+e.callback=nil;
+end
+destroy_request(e);
+end
+if not o then
+a(t);
+return;
+end
+local function o(t)
+if e.callback then
+e.callback(t.body,t.code,t,e);
+e.callback=nil;
+end
+destroy_request(e);
+end
+local function t()
+return e;
+end
+e.parser=d(o,a,"client",t);
+end
+e.parser:feed(o);
+end
+local function w(e)u("error","Traceback[http]: %s",s(c(e),2));end
+function request(e,t,l)
+local e=r.parse(e);
+if not(e and e.host)then
+l(nil,0,e);
+return nil,"invalid-url";
+end
+if not e.path then
+e.path="/";
+end
+local d,o,s;
+local r,a=e.host,e.port;
+local h=r;
+if(a=="80"and e.scheme=="http")
+or(a=="443"and e.scheme=="https")then
+a=nil;
+elseif a then
+h=h..":"..a;
+end
+o={
+["Host"]=h;
+["User-Agent"]="Prosody XMPP Server";
+};
+if e.userinfo then
+o["Authorization"]="Basic "..q(e.userinfo);
+end
+if t then
+e.onlystatus=t.onlystatus;
+s=t.body;
+if s then
+d="POST";
+o["Content-Length"]=c(#s);
+o["Content-Type"]="application/x-www-form-urlencoded";
+end
+if t.method then d=t.method;end
+if t.headers then
+for t,e in m(t.headers)do
+o[t]=e;
+end
+end
+end
+e.method,e.headers,e.body=d,o,s;
+local o=e.scheme=="https";
+if o and not x then
+k("SSL not available, unable to contact https URL");
+end
+local s=a and v(a)or(o and 443 or 80);
+local a=y.tcp();
+a:settimeout(10);
+local d,h=a:connect(r,s);
+if not d and h~="timeout"then
+l(nil,0,e);
+return nil,h;
+end
+local h=false;
+if o then
+h=t and t.sslctx or{mode="client",protocol="sslv23",options={"no_sslv2","no_sslv3"}};
+end
+e.handler,e.conn=g(j.wrapclient(a,r,s,n,"*a",h));
+e.write=function(...)return e.handler:write(...);end
+e.callback=function(a,t,o,i)u("debug","Calling callback, status %s",t or"---");return b(2,p(function()return l(a,t,o,i)end,w));end
+e.reader=f;
+e.state="status";
+i[e.handler]=e;
+return e;
+end
+function destroy_request(e)
+if e.conn then
+e.conn=nil;
+e.handler:close()
+end
+end
+local e,a=h.urlencode,h.urldecode;
+local t,o=h.formencode,h.formdecode;
+_M.urlencode,_M.urldecode=e,a;
+_M.formencode,_M.formdecode=t,o;
+return _M;
+end)
+package.preload['util.x509']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local i=require"util.encodings".stringprep.nameprep;
+local l=require"util.encodings".idna.to_ascii;
+local d=require"util.encodings".base64;
+local e=require"util.logger".init("x509");
+local c=string.format;
+local _ENV=nil;
+local f="2.5.4.3";
+local r="2.5.29.17";
+local s="1.3.6.1.5.5.7.8.5";
+local h="1.3.6.1.5.5.7.8.7";
+local function n(a,o)
+local t=l(a)
+if t==nil then
+e("info","Host %s failed IDNA ToASCII operation",a)
+return false
+end
+t=t:lower()
+local i=t:gsub("^[^.]+%.","")
+for a=1,#o do
+local a=o[a]
+if t==a:lower()then
+e("debug","Cert dNSName %s matched hostname",a);
+return true
+end
+if a:match("^%*%.")then
+local t=a:gsub("^[^.]+%.","")
+if i==t:lower()then
+e("debug","Cert dNSName %s matched hostname",a);
+return true
+end
+end
+end
+return false
+end
+local function m(a,t)
+local o=i(a)
+for a=1,#t do
+local t=t[a]
+if t:match("[@/]")then
+e("debug","Ignoring xmppAddr %s because it's not a bare domain",t)
+else
+local a=i(t)
+if a==nil then
+e("info","Ignoring xmppAddr %s, failed nameprep!",t)
+else
+if o==a then
+e("debug","Cert xmppAddr %s matched hostname",t)
+return true
+end
+end
+end
+end
+return false
+end
+local function u(t,o,i)
+local a=l(t)
+if a==nil then
+e("info","Host %s failed IDNA ToASCII operation",t);
+return false
+end
+if o:match("^_")==nil then o="_"..o end
+a=a:lower();
+local n=a:gsub("^[^.]+%.","")
+for t=1,#i do
+local i,t=i[t]:match("^(_[^.]+)%.(.*)");
+if o==i then
+if a==t:lower()then
+e("debug","Cert SRVName %s matched hostname",t);
+return true;
+end
+if t:match("^%*%.")then
+local a=t:gsub("^[^.]+%.","")
+if n==a:lower()then
+e("debug","Cert SRVName %s matched hostname",t)
+return true
+end
+end
+if a==t:lower()then
+e("debug","Cert SRVName %s matched hostname",t);
+return true
+end
+end
+end
+return false
+end
+local function l(a,i,o)
+if o.setencode then
+o:setencode("utf8");
+end
+local t=o:extensions()
+if t[r]then
+local e=t[r];
+local t=false
+if e[s]then
+t=true
+if i=="_xmpp-client"or i=="_xmpp-server"then
+if m(a,e[s])then return true end
+end
+end
+if e[h]then
+t=true
+if i and u(a,i,e[h])then return true end
+end
+if e["dNSName"]then
+t=true
+if n(a,e["dNSName"])then return true end
+end
+if e["uniformResourceIdentifier"]then
+t=true
+end
+if t then return false end
+end
+local o=o:subject()
+local t=nil
+for a=1,#o do
+local a=o[a]
+if a["oid"]==f then
+if t then
+e("info","Certificate has multiple common names")
+return false
+end
+t=a["value"];
+end
+end
+if t then
+return n(a,{t})
+end
+return false
+end
+local e="%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n"..
+"([0-9A-Za-z+/=\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-";
+local function a(t)
+local t,e=t:match(e);
+if t and e then
+return d.decode(e),t;
+end
+end
+local o=('.'):rep(64);
+local n="-----BEGIN %s-----\n%s\n-----END %s-----\n"
+local function i(t,e)
+e=e and e:upper()or"CERTIFICATE";
+t=d.encode(t);
+return c(n,e,t:gsub(o,'%0\n',(#t-1)/64),e);
+end
+return{
+verify_identity=l;
+pem2der=a;
+der2pem=i;
+};
+end)
+package.preload['verse.bosh']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local h=require"util.xmppstream".new;
+local i=require"util.stanza";
+require"net.httpclient_listener";
+local t=require"net.http";
+local e=setmetatable({},{__index=verse.stream_mt});
+e.__index=e;
+local s="http://etherx.jabber.org/streams";
+local n="http://jabber.org/protocol/httpbind";
+local o=5;
+function verse.new_bosh(a,t)
+local t={
+bosh_conn_pool={};
+bosh_waiting_requests={};
+bosh_rid=math.random(1,999999);
+bosh_outgoing_buffer={};
+bosh_url=t;
+conn={};
+};
+function t:reopen()
+self.bosh_need_restart=true;
+self:flush();
+end
+local t=verse.new(a,t);
+return setmetatable(t,e);
+end
+function e:connect()
+self:_send_session_request();
+end
+function e:send(e)
+self:debug("Putting into BOSH send buffer: %s",tostring(e));
+self.bosh_outgoing_buffer[#self.bosh_outgoing_buffer+1]=i.clone(e);
+self:flush();
+end
+function e:flush()
+if self.connected
+and#self.bosh_waiting_requests<self.bosh_max_requests
+and(#self.bosh_waiting_requests==0
+or#self.bosh_outgoing_buffer>0
+or self.bosh_need_restart)then
+self:debug("Flushing...");
+local e=self:_make_body();
+local t=self.bosh_outgoing_buffer;
+for o,a in ipairs(t)do
+e:add_child(a);
+t[o]=nil;
+end
+self:_make_request(e);
+else
+self:debug("Decided not to flush.");
+end
+end
+function e:_make_request(a)
+local e,t=t.request(self.bosh_url,{body=tostring(a)},function(i,e,t)
+if e~=0 then
+self.inactive_since=nil;
+return self:_handle_response(i,e,t);
+end
+local e=os.time();
+if not self.inactive_since then
+self.inactive_since=e;
+elseif e-self.inactive_since>self.bosh_max_inactivity then
+return self:_disconnected();
+else
+self:debug("%d seconds left to reconnect, retrying in %d seconds...",
+self.bosh_max_inactivity-(e-self.inactive_since),o);
+end
+timer.add_task(o,function()
+self:debug("Retrying request...");
+for e,a in ipairs(self.bosh_waiting_requests)do
+if a==t then
+table.remove(self.bosh_waiting_requests,e);
+break;
+end
+end
+self:_make_request(a);
+end);
+end);
+if e then
+table.insert(self.bosh_waiting_requests,e);
+else
+self:warn("Request failed instantly: %s",t);
+end
+end
+function e:_disconnected()
+self.connected=nil;
+self:event("disconnected");
+end
+function e:_send_session_request()
+local e=self:_make_body();
+e.attr.hold="1";
+e.attr.wait="60";
+e.attr["xml:lang"]="en";
+e.attr.ver="1.6";
+e.attr.from=self.jid;
+e.attr.to=self.host;
+e.attr.secure='true';
+t.request(self.bosh_url,{body=tostring(e)},function(t,e)
+if e==0 then
+return self:_disconnected();
+end
+local e=self:_parse_response(t)
+if not e then
+self:warn("Invalid session creation response");
+self:_disconnected();
+return;
+end
+self.bosh_sid=e.attr.sid;
+self.bosh_wait=tonumber(e.attr.wait);
+self.bosh_hold=tonumber(e.attr.hold);
+self.bosh_max_inactivity=tonumber(e.attr.inactivity);
+self.bosh_max_requests=tonumber(e.attr.requests)or self.bosh_hold;
+self.connected=true;
+self:event("connected");
+self:_handle_response_payload(e);
+end);
+end
+function e:_handle_response(t,a,e)
+if self.bosh_waiting_requests[1]~=e then
+self:warn("Server replied to request that wasn't the oldest");
+for t,a in ipairs(self.bosh_waiting_requests)do
+if a==e then
+self.bosh_waiting_requests[t]=nil;
+break;
+end
+end
+else
+table.remove(self.bosh_waiting_requests,1);
+end
+local e=self:_parse_response(t);
+if e then
+self:_handle_response_payload(e);
+end
+self:flush();
+end
+function e:_handle_response_payload(t)
+local e=t.tags;
+for t=1,#e do
+local e=e[t];
+if e.attr.xmlns==s then
+self:event("stream-"..e.name,e);
+elseif e.attr.xmlns then
+self:event("stream/"..e.attr.xmlns,e);
+else
+self:event("stanza",e);
+end
+end
+if t.attr.type=="terminate"then
+self:_disconnected({reason=t.attr.condition});
+end
+end
+local a={
+stream_ns="http://jabber.org/protocol/httpbind",stream_tag="body",
+default_ns="jabber:client",
+streamopened=function(e,t)e.notopen=nil;e.payload=verse.stanza("body",t);return true;end;
+handlestanza=function(e,t)e.payload:add_child(t);end;
+};
+function e:_parse_response(e)
+self:debug("Parsing response: %s",e);
+if e==nil then
+self:debug("%s",debug.traceback());
+self:_disconnected();
+return;
+end
+local t={notopen=true,stream=self};
+local a=h(t,a);
+a:feed(e);
+return t.payload;
+end
+function e:_make_body()
+self.bosh_rid=self.bosh_rid+1;
+local e=verse.stanza("body",{
+xmlns=n;
+content="text/xml; charset=utf-8";
+sid=self.bosh_sid;
+rid=self.bosh_rid;
+});
+if self.bosh_need_restart then
+self.bosh_need_restart=nil;
+e.attr.restart='true';
+end
+return e;
+end
+end)
+package.preload['verse.client']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local t=require"verse";
+local o=t.stream_mt;
+local h=require"util.jid".split;
+local s=require"net.adns";
+local a=require"util.stanza";
+t.message,t.presence,t.iq,t.stanza,t.reply,t.error_reply=
+a.message,a.presence,a.iq,a.stanza,a.reply,a.error_reply;
+local d=require"util.xmppstream".new;
+local n="http://etherx.jabber.org/streams";
+local function r(t,e)
+return t.priority<e.priority or(t.priority==e.priority and t.weight>e.weight);
+end
+local i={
+stream_ns=n,
+stream_tag="stream",
+default_ns="jabber:client"};
+function i.streamopened(e,t)
+e.stream_id=t.id;
+if not e:event("opened",t)then
+e.notopen=nil;
+end
+return true;
+end
+function i.streamclosed(e)
+e.notopen=true;
+if not e.closed then
+e:send("</stream:stream>");
+e.closed=true;
+end
+e:event("closed");
+return e:close("stream closed")
+end
+function i.handlestanza(t,e)
+if e.attr.xmlns==n then
+return t:event("stream-"..e.name,e);
+elseif e.attr.xmlns then
+return t:event("stream/"..e.attr.xmlns,e);
+end
+return t:event("stanza",e);
+end
+function i.error(a,t,e)
+if a:event(t,e)==nil then
+if e then
+local t=e:get_child(nil,"urn:ietf:params:xml:ns:xmpp-streams");
+local e=e:get_child_text("text","urn:ietf:params:xml:ns:xmpp-streams");
+error(t.name..(e and": "..e or""));
+else
+error(e and e.name or t or"unknown-error");
+end
+end
+end
+function o:reset()
+if self.stream then
+self.stream:reset();
+else
+self.stream=d(self,i);
+end
+self.notopen=true;
+return true;
+end
+function o:connect_client(e,a)
+self.jid,self.password=e,a;
+self.username,self.host,self.resource=h(e);
+self:add_plugin("tls");
+self:add_plugin("sasl");
+self:add_plugin("bind");
+self:add_plugin("session");
+function self.data(t,e)
+local t,a=self.stream:feed(e);
+if t then return;end
+self:debug("Received invalid XML (%s) %d bytes: %s",tostring(a),#e,e:sub(1,300):gsub("[\r\n]+"," "));
+self:close("xml-not-well-formed");
+end
+self:hook("connected",function()self:reopen();end);
+self:hook("incoming-raw",function(e)return self.data(self.conn,e);end);
+self.curr_id=0;
+self.tracked_iqs={};
+self:hook("stanza",function(e)
+local t,a=e.attr.id,e.attr.type;
+if t and e.name=="iq"and(a=="result"or a=="error")and self.tracked_iqs[t]then
+self.tracked_iqs[t](e);
+self.tracked_iqs[t]=nil;
+return true;
+end
+end);
+self:hook("stanza",function(e)
+local a;
+if e.attr.xmlns==nil or e.attr.xmlns=="jabber:client"then
+if e.name=="iq"and(e.attr.type=="get"or e.attr.type=="set")then
+local o=e.tags[1]and e.tags[1].attr.xmlns;
+if o then
+a=self:event("iq/"..o,e);
+if not a then
+a=self:event("iq",e);
+end
+end
+if a==nil then
+self:send(t.error_reply(e,"cancel","service-unavailable"));
+return true;
+end
+else
+a=self:event(e.name,e);
+end
+end
+return a;
+end,-1);
+self:hook("outgoing",function(e)
+if e.name then
+self:event("stanza-out",e);
+end
+end);
+self:hook("stanza-out",function(e)
+if not e.attr.xmlns then
+self:event(e.name.."-out",e);
+end
+end);
+local function e()
+self:event("ready");
+end
+self:hook("session-success",e,-1)
+self:hook("bind-success",e,-1);
+local e=self.close;
+function self:close(t)
+self.close=e;
+if not self.closed then
+self:send("</stream:stream>");
+self.closed=true;
+else
+return self:close(t);
+end
+end
+local function t()
+self:connect(self.connect_host or self.host,self.connect_port or 5222);
+end
+if not(self.connect_host or self.connect_port)then
+s.lookup(function(a)
+if a then
+local e={};
+self.srv_hosts=e;
+for a,t in ipairs(a)do
+table.insert(e,t.srv);
+end
+table.sort(e,r);
+local a=e[1];
+self.srv_choice=1;
+if a then
+self.connect_host,self.connect_port=a.target,a.port;
+self:debug("Best record found, will connect to %s:%d",self.connect_host or self.host,self.connect_port or 5222);
+end
+self:hook("disconnected",function()
+if self.srv_hosts and self.srv_choice<#self.srv_hosts then
+self.srv_choice=self.srv_choice+1;
+local e=e[self.srv_choice];
+self.connect_host,self.connect_port=e.target,e.port;
+t();
+return true;
+end
+end,1e3);
+self:hook("connected",function()
+self.srv_hosts=nil;
+end,1e3);
+end
+t();
+end,"_xmpp-client._tcp."..(self.host)..".","SRV");
+else
+t();
+end
+end
+function o:reopen()
+self:reset();
+self:send(a.stanza("stream:stream",{to=self.host,["xmlns:stream"]='http://etherx.jabber.org/streams',
+xmlns="jabber:client",version="1.0"}):top_tag());
+end
+function o:send_iq(e,a)
+local t=self:new_id();
+self.tracked_iqs[t]=a;
+e.attr.id=t;
+self:send(e);
+end
+function o:new_id()
+self.curr_id=self.curr_id+1;
+return tostring(self.curr_id);
+end
+end)
+package.preload['verse.component']=(function(...)
+local _ENV=_ENV;
+local function e(t,...)
+local e=package.loaded[t]or _ENV[t]or{_NAME=t};
+package.loaded[t]=e;
+for t=1,select("#",...)do
+(select(t,...))(e);
+end
+_ENV=e;
+_M=e;
+return e;
+end
+local o=require"verse";
+local t=o.stream_mt;
+local d=require"util.jid".split;
+local e=require"lxp";
+local a=require"util.stanza";
+local h=require"util.hashes".sha1;
+o.message,o.presence,o.iq,o.stanza,o.reply,o.error_reply=
+a.message,a.presence,a.iq,a.stanza,a.reply,a.error_reply;
+local r=require"util.xmppstream".new;
+local s="http://etherx.jabber.org/streams";
+local i="jabber:component:accept";
+local n={
+stream_ns=s,
+stream_tag="stream",
+default_ns=i};
+function n.streamopened(e,t)
+e.stream_id=t.id;
+if not e:event("opened",t)then
+e.notopen=nil;
+end
+return true;
+end
+function n.streamclosed(e)
+return e:event("closed");
+end
+function n.handlestanza(t,e)
+if e.attr.xmlns==s then
+return t:event("stream-"..e.name,e);
+elseif e.attr.xmlns or e.name=="handshake"then
+return t:event("stream/"..(e.attr.xmlns or i),e);
+end
+return t:event("stanza",e);
+end
+function t:reset()
+if self.stream then
+self.stream:reset();
+else
+self.stream=r(self,n);
+end
+self.notopen=true;
+return true;
+end
+function t:connect_component(e,n)
+self.jid,self.password=e,n;
+self.username,self.host,self.resource=d(e);
+function self.data(a,e)
+local o,a=self.stream:feed(e);
+if o then return;end
+t:debug("Received invalid XML (%s) %d bytes: %s",tostring(a),#e,e:sub(1,300):gsub("[\r\n]+"," "));
+t:close("xml-not-well-formed");
+end
+self:hook("incoming-raw",function(e)return self.data(self.conn,e);end);
+self.curr_id=0;
+self.tracked_iqs={};
+self:hook("stanza",function(e)
+local t,a=e.attr.id,e.attr.type;
+if t and e.name=="iq"and(a=="result"or a=="error")and self.tracked_iqs[t]then
+self.tracked_iqs[t](e);
+self.tracked_iqs[t]=nil;
+return true;
+end
+end);
+self:hook("stanza",function(e)
+local t;
+if e.attr.xmlns==nil or e.attr.xmlns=="jabber:client"then
+if e.name=="iq"and(e.attr.type=="get"or e.attr.type=="set")then
+local a=e.tags[1]and e.tags[1].attr.xmlns;
+if a then
+t=self:event("iq/"..a,e);
+if not t then
+t=self:event("iq",e);
+end
+end
+if t==nil then
+self:send(o.error_reply(e,"cancel","service-unavailable"));
+return true;
+end
+else
+t=self:event(e.name,e);
+end
+end
+return t;
+end,-1);
+self:hook("opened",function(e)
+print(self.jid,self.stream_id,e.id);
+local e=h(self.stream_id..n,true);
+self:send(a.stanza("handshake",{xmlns=i}):text(e));
+self:hook("stream/"..i,function(e)
+if e.name=="handshake"then
+self:event("authentication-success");
+end
+end);
+end);
+local function e()
+self:event("ready");
+end
+self:hook("authentication-success",e,-1);
+self:connect(self.connect_host or self.host,self.connect_port or 5347);
+self:reopen();
+end
+function t:reopen()
+self:reset();
+self:send(a.stanza("stream:stream",{to=self.jid,["xmlns:stream"]='http://etherx.jabber.org/streams',
+xmlns=i,version="1.0"}):top_tag());
+end
+function t:close(e)
+if not self.notopen then
+self:send("</stream:stream>");
+end
+local t=self.conn.disconnect();
+self.conn:close();
+t(conn,e);
+end
+function t:send_iq(t,a)
+local e=self:new_id();
+self.tracked_iqs[e]=a;
+t.attr.id=e;
+self:send(t);
+end
+function t:new_id()
+self.curr_id=self.curr_id+1;
+return tostring(self.curr_id);
+end
+end)
+pcall(require,"luarocks.require");
+local n=require"socket";
+pcall(require,"ssl");
+local a=require"net.server";
+local s=require"util.events";
+local o=require"util.logger";
+local e={};
+e.server=a;
+local t={};
+t.__index=t;
+e.stream_mt=t;
+e.plugins={};
+function e.init(...)
+for e=1,select("#",...)do
+local t,a=pcall(require,"verse."..select(e,...));
+if not t then
+error("Verse connection module not found: verse."..select(e,...)..a);
+end
+end
+return e;
+end
+local i=0;
+function e.new(o,a)
+local t=setmetatable(a or{},t);
+i=i+1;
+t.id=tostring(i);
+t.logger=o or e.new_logger("stream"..t.id);
+t.events=s.new();
+t.plugins={};
+t.verse=e;
+return t;
+end
+e.add_task=require"util.timer".add_task;
+e.logger=o.init;
+e.new_logger=o.init;
+e.log=e.logger("verse");
+local function i(t,...)
+local e,a,o=0,{...},select('#',...);
+return(t:gsub("%%(.)",function(t)if e<=o then e=e+1;return tostring(a[e]);end end));
+end
+function e.set_log_handler(e,t)
+t=t or{"debug","info","warn","error"};
+o.reset();
+if io.type(e)=="file"then
+local t=e;
+function e(e,a,o)
+t:write(e,"\t",a,"\t",o,"\n");
+end
+end
+if e then
+local function n(o,a,t,...)
+return e(o,a,i(t,...));
+end
+for t,e in ipairs(t)do
+o.add_level_sink(e,n);
+end
+end
+end
+function e._default_log_handler(t,a,o)
+return io.stderr:write(t,"\t",a,"\t",o,"\n");
+end
+e.set_log_handler(e._default_log_handler,{"error"});
+local function o(t)
+e.log("error","Error: %s",t);
+e.log("error","Traceback: %s",debug.traceback());
+end
+function e.set_error_handler(e)
+o=e;
+end
+function e.loop()
+return xpcall(a.loop,o);
+end
+function e.step()
+return xpcall(a.step,o);
+end
+function e.quit()
+return a.setquitting("once");
+end
+function t:listen(o,t)
+o=o or"localhost";
+t=t or 0;
+local e,a=a.addserver(o,t,e.new_listener(self,"server"),"*a");
+if e then
+self:debug("Bound to %s:%s",o,t);
+self.server=e;
+end
+return e,a;
+end
+function t:connect(i,o)
+i=i or"localhost";
+o=tonumber(o)or 5222;
+local n=n.tcp()
+n:settimeout(0);
+n:setoption("keepalive",true);
+local s,t=n:connect(i,o);
+if not s and t~="timeout"then
+self:warn("connect() to %s:%d failed: %s",i,o,t);
+return self:event("disconnected",{reason=t})or false,t;
+end
+local e=a.wrapclient(n,i,o,e.new_listener(self),"*a");
+if not e then
+self:warn("connection initialisation failed: %s",t);
+return self:event("disconnected",{reason=t})or false,t;
+end
+self:set_conn(e);
+return true;
+end
+function t:set_conn(t)
+self.conn=t;
+self.send=function(a,e)
+self:event("outgoing",e);
+e=tostring(e);
+self:event("outgoing-raw",e);
+return t:write(e);
+end;
+end
+function t:close(t)
+if not self.conn then
+e.log("error","Attempt to close disconnected connection - possibly a bug");
+return;
+end
+local e=self.conn.disconnect();
+self.conn:close();
+e(self.conn,t);
+end
+function t:debug(...)
+return self.logger("debug",...);
+end
+function t:info(...)
+return self.logger("info",...);
+end
+function t:warn(...)
+return self.logger("warn",...);
+end
+function t:error(...)
+return self.logger("error",...);
+end
+function t:event(e,...)
+self:debug("Firing event: "..tostring(e));
+return self.events.fire_event(e,...);
+end
+function t:hook(e,...)
+return self.events.add_handler(e,...);
+end
+function t:unhook(e,t)
+return self.events.remove_handler(e,t);
+end
+function e.eventable(e)
+e.events=s.new();
+e.hook,e.unhook=t.hook,t.unhook;
+local t=e.events.fire_event;
+function e:event(e,...)
+return t(e,...);
+end
+return e;
+end
+function t:add_plugin(t)
+if self.plugins[t]then return true;end
+if require("verse.plugins."..t)then
+local e,a=e.plugins[t](self);
+if e~=false then
+self:debug("Loaded %s plugin",t);
+self.plugins[t]=true;
+else
+self:warn("Failed to load %s plugin: %s",t,a);
+end
+end
+return self;
+end
+function e.new_listener(t)
+local a={};
+function a.onconnect(o)
+if t.server then
+local a=e.new();
+o:setlistener(e.new_listener(a));
+a:set_conn(o);
+t:event("connected",{client=a});
+else
+t.connected=true;
+t:event("connected");
+end
+end
+function a.onincoming(a,e)
+t:event("incoming-raw",e);
+end
+function a.ondisconnect(a,e)
+if a~=t.conn then return end
+t.connected=false;
+t:event("disconnected",{reason=e});
+end
+function a.ondrain(e)
+t:event("drained");
+end
+function a.onstatus(a,e)
+t:event("status",e);
+end
+return a;
+end
+return e;

mercurial