# HG changeset patch # User Matthew Wild # Date 1607015127 0 # Node ID 550f506de75ae11a3b17fe904454866c20aa10fa Initial commit diff -r 000000000000 -r 550f506de75a main.lua --- /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(""); + 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(""); + 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)); diff -r 000000000000 -r 550f506de75a util/async.lua --- /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; +}; diff -r 000000000000 -r 550f506de75a util/id.lua --- /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; +} diff -r 000000000000 -r 550f506de75a util/logger.lua --- /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; +}; diff -r 000000000000 -r 550f506de75a util/promise.lua --- /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; +} diff -r 000000000000 -r 550f506de75a util/random.lua --- /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"; +}; diff -r 000000000000 -r 550f506de75a util/xpcall.lua --- /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; +}; diff -r 000000000000 -r 550f506de75a verse.lua --- /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={["'"]="'",["\""]=""",["<"]="<",[">"]=">",["&"]="&"}; +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,""); +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,""); +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"";end +local e={}; +for t in t:gmatch("[^;]+")do +n(e,u[h(t)]); +end +return""; +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"; +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#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=""; +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 +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 ..."); +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_requests0 +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.prioritye.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(""); +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(""); +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(""); +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;