Software /
code /
prosody-modules
Changeset
461:bbea8081c865
Revert various changes accidentally included in previous commit
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sat, 29 Oct 2011 13:34:15 +0200 |
parents | 460:9bb9343f3c7a |
children | 462:f28a3f260fc2 |
files | mod_auth_sql/mod_auth_sql.lua mod_data_access/mod_data_access.lua mod_ircd/mod_ircd_broke.lua mod_ircd/squishy mod_pubsub_feed/mod_pubsub_feed.lua |
diffstat | 5 files changed, 52 insertions(+), 5477 deletions(-) [+] |
line wrap: on
line diff
--- a/mod_auth_sql/mod_auth_sql.lua Mon Oct 24 00:20:51 2011 +0000 +++ b/mod_auth_sql/mod_auth_sql.lua Sat Oct 29 13:34:15 2011 +0200 @@ -7,16 +7,11 @@ local new_sasl = require "util.sasl".new; local nodeprep = require "util.encodings".stringprep.nodeprep; local DBI = require "DBI" -local crypt = require "crypt"; local connection; local params = module:get_option("sql"); -local host = module.host; -local realm = module:get_option_string("realm", host); -local mitm_mode = module:get_option_boolean("mitm_mode"); local resolve_relative_path = require "core.configmanager".resolve_relative_path; -local datamanager = require "util.datamanager"; local function test_connection() if not connection then return nil; end @@ -77,7 +72,7 @@ end local function get_password(username) - local stmt, err = getsql("SELECT `password` FROM `users` WHERE `email`=?", username .. "@" .. realm); + local stmt, err = getsql("SELECT `password` FROM `authreg` WHERE `username`=? AND `realm`=?", username, module.host); if stmt then for row in stmt:rows(true) do return row.password; @@ -85,56 +80,38 @@ end end + provider = { name = "sql" }; function provider.test_password(username, password) - local local_data = datamanager.load(username, realm, "accounts") or {}; - if data.password == password then return true end - local dirty; - local hash = data.crypted_password; - if not hash then - hash = get_password(username); - if hash then - data.crypted_password = hash; - dirty = true; - else - return false - end - end - local ok = password and crypt(password, hash) == password; - if ok and mitm_mode then - local_data.password = password; - dirty = true - end - if dirty then - datamanager.store(username, realm, "accounts", local_data); - end - return ok + return password and get_password(username) == password; end function provider.get_password(username) - return nil, "Getting password is not supported."; + return get_password(username); end function provider.set_password(username, password) return nil, "Setting password is not supported."; end function provider.user_exists(username) - return datamanager.load(username, realm, "accounts") or get_password(username) and true; + return get_password(username) and true; end function provider.create_user(username, password) return nil, "Account creation/modification not supported."; end function provider.get_sasl_handler() local profile = { - plain_test = function(sasl, username, password, realm) + plain = function(sasl, username, realm) local prepped_username = nodeprep(username); if not prepped_username then module:log("debug", "NODEprep failed on username: %s", username); - return nil; + return "", nil; end - return provider.test_password(prepped_username, password); + local password = get_password(prepped_username); + if not password then return "", nil; end + return password, true; end }; - return new_sasl(host, profile); + return new_sasl(module.host, profile); end module:add_item("auth-provider", provider);
--- a/mod_data_access/mod_data_access.lua Mon Oct 24 00:20:51 2011 +0000 +++ b/mod_data_access/mod_data_access.lua Sat Oct 29 13:34:15 2011 +0200 @@ -1,19 +1,26 @@ -- HTTP Access to datamanager -- By Kim Alvefur <zash@zash.se> -local t_concat = table.concat; local jid_prep = require "util.jid".prep; local jid_split = require "util.jid".split; local um_test_pw = require "core.usermanager".test_password; local is_admin = require "core.usermanager".is_admin local dm_load = require "util.datamanager".load; -local dm_store = require "util.datamanager".store; local dm_list_load = require "util.datamanager".list_load; -local dm_list_append = require "util.datamanager".list_append; local b64_decode = require "util.encodings".base64.decode; -local http = require "net.http"; -local urldecode = http.urldecode; -local urlencode = http.urlencode; +--local urldecode = require "net.http".urldecode; +--[[local urlparams = --require "net.http".getQueryParams or whatever MattJ names it +function(s) + if not s:match("=") then return urldecode(s); end + local r = {} + s:gsub("([^=&]*)=([^&]*)", function(k,v) + r[ urldecode(k) ] = urldecode(v); + return nil + end) + return r +end; +--]] + local function http_response(code, message, extra_headers) local response = { status = code .. " " .. message; @@ -26,29 +33,15 @@ lua = require "util.serialization".serialize, json = require "util.json".encode }; -local decoders = { - lua = require "util.serialization".deserialize, - json = require "util.json".decode, -}; -local content_type_map = { - ["text/x-lua"] = "lua"; lua = "text/x-lua"; - ["application/json"] = "json"; json = "application/json"; -} --[[ encoders.xml = function(data) return "<?xml version='1.0' encoding='utf-8'?><todo:write-this-serializer/>"; end --]] -local allowed_methods = { - GET = true, "GET", - PUT = true, "PUT", - POST = true, "POST", -} - local function handle_request(method, body, request) - if not allowed_methods[method] then - return http_response(405, "Method Not Allowed", {["Allow"] = t_concat(allowed_methods, ", ")}); - end + if request.method ~= "GET" then + return http_response(405, "Method Not Allowed", {["Allow"] = "GET"}); + end -- TODO Maybe PUT? if not request.headers["authorization"] then return http_response(401, "Unauthorized", @@ -85,59 +78,29 @@ return http_response(404, "Not Found"); end - local p_host, p_user, p_store, p_type = unpack(path); - - if not p_store or not p_store:match("^[%a_]+$") then - return http_response(404, "Not Found"); - end - if user_host ~= path[1] or user_node ~= path[2] then -- To only give admins acces to anything, move the inside of this block after authz - module:log("debug", "%s wants access to %s@%s[%s], is admin?", user, p_user, p_host, p_store) - if not is_admin(user, p_host) then + module:log("debug", "%s wants access to %s@%s[%s], is admin?", user, path[2], path[1], path[3]) + if not is_admin(user, path[1]) then return http_response(403, "Forbidden"); end end - if method == "GET" then - local data = dm_load(p_user, p_host, p_store); - - data = data or dm_load_list(p_user, p_host, p_store); + local data = dm_load(path[2], path[1], path[3]); + + data = data or dm_list_load(path[2], path[1], path[3]); - --TODO Use the Accept header - content_type = p_type or "json"; - if data and encoders[content_type] then - return { - status = "200 OK", - body = encoders[content_type](data) .. "\n", - headers = {["content-type"] = content_type_map[content_type].."; charset=utf-8"} - }; - else - return http_response(404, "Not Found"); - end - else -- POST or PUT - if not body then - return http_response(400, "Bad Request") - end - local content_type, content = request.headers["content-type"], body; - content_type = content_type and content_type_map[content_type] - module:log("debug", "%s: %s", content_type, tostring(content)); - content = content_type and decoders[content_type] and decoders[content_type](content); - module:log("debug", "%s: %s", type(content), tostring(content)); - if not content then - return http_response(400, "Bad Request") - end - local ok, err - if method == "PUT" then - ok, err = dm_store(p_user, p_host, p_store, content); - elseif method == "POST" then - ok, err = dm_list_append(p_user, p_host, p_store, content); - end - if ok then - return http_response(201, "Created", { Location = t_concat({"/data",p_host,p_user,p_store}, "/") }); - else - return { status = "500 Internal Server Error", body = err } - end + if data and encoders[path[4] or "json"] then + return { + status = "200 OK", + body = encoders[path[4] or "json"](data) .. "\n", + headers = {["content-type"] = "text/plain; charset=utf-8"} + --headers = {["content-type"] = encoders[data[4] or "json"].mime .. "; charset=utf-8"} + -- FIXME a little nicer that the above + -- Also, would be cooler to use the Accept header, but parsing it ... + }; + else + return http_response(404, "Not Found"); end end
--- a/mod_ircd/mod_ircd_broke.lua Mon Oct 24 00:20:51 2011 +0000 +++ b/mod_ircd/mod_ircd_broke.lua Sat Oct 29 13:34:15 2011 +0200 @@ -1,5321 +1,3 @@ -package.preload['verse'] = (function (...) -package.preload['util.encodings'] = (function (...) -local function not_impl() - error("Function not implemented"); -end - -local mime = require "mime"; - -module "encodings" - -stringprep = {}; -base64 = { encode = mime.b64, decode = not_impl }; --mime.unb64 is buggy with \0 - -return _M; - end) -package.preload['util.hashes'] = (function (...) -local sha1 = require "util.sha1"; - -return { sha1 = sha1.sha1 }; - end) -package.preload['util.logger'] = (function (...) -local select, tostring = select, tostring; -local io_write = function(...) return io.stderr:write(...) end; -module "logger" - -local function format(format, ...) - local n, maxn = 0, #arg; - return (format:gsub("%%(.)", function (c) if c ~= "%" and n <= maxn then n = n + 1; return tostring(arg[n]); end end)); -end - -local function format(format, ...) - local n, maxn = 0, select('#', ...); - local arg = { ... }; - return (format:gsub("%%(.)", function (c) if n <= maxn then n = n + 1; return tostring(arg[n]); end end)); -end - -function init(name) - return function (level, message, ...) - io_write(level, "\t", format(message, ...), "\n"); - end -end - -return _M; - end) -package.preload['util.sha1'] = (function (...) -------------------------------------------------- ---- *** SHA-1 algorithm for Lua *** --- -------------------------------------------------- ---- Author: Martin Huesser --- ---- Date: 2008-06-16 --- ---- License: You may use this code in your --- ---- projects as long as this header --- ---- stays intact. --- -------------------------------------------------- - -local strlen = string.len -local strchar = string.char -local strbyte = string.byte -local strsub = string.sub -local floor = math.floor -local bit = require "bit" -local bnot = bit.bnot -local band = bit.band -local bor = bit.bor -local bxor = bit.bxor -local shl = bit.lshift -local shr = bit.rshift -local h0, h1, h2, h3, h4 - -------------------------------------------------- - -local function LeftRotate(val, nr) - return shl(val, nr) + shr(val, 32 - nr) -end - -------------------------------------------------- - -local function ToHex(num) - local i, d - local str = "" - for i = 1, 8 do - d = band(num, 15) - if (d < 10) then - str = strchar(d + 48) .. str - else - str = strchar(d + 87) .. str - end - num = floor(num / 16) - end - return str -end - -------------------------------------------------- - -local function PreProcess(str) - local bitlen, i - local str2 = "" - bitlen = strlen(str) * 8 - str = str .. strchar(128) - i = 56 - band(strlen(str), 63) - if (i < 0) then - i = i + 64 - end - for i = 1, i do - str = str .. strchar(0) - end - for i = 1, 8 do - str2 = strchar(band(bitlen, 255)) .. str2 - bitlen = floor(bitlen / 256) - end - return str .. str2 -end - -------------------------------------------------- - -local function MainLoop(str) - local a, b, c, d, e, f, k, t - local i, j - local w = {} - while (str ~= "") do - for i = 0, 15 do - w[i] = 0 - for j = 1, 4 do - w[i] = w[i] * 256 + strbyte(str, i * 4 + j) - end - end - for i = 16, 79 do - w[i] = LeftRotate(bxor(bxor(w[i - 3], w[i - 8]), bxor(w[i - 14], w[i - 16])), 1) - end - a = h0 - b = h1 - c = h2 - d = h3 - e = h4 - for i = 0, 79 do - if (i < 20) then - f = bor(band(b, c), band(bnot(b), d)) - k = 1518500249 - elseif (i < 40) then - f = bxor(bxor(b, c), d) - k = 1859775393 - elseif (i < 60) then - f = bor(bor(band(b, c), band(b, d)), band(c, d)) - k = 2400959708 - else - f = bxor(bxor(b, c), d) - k = 3395469782 - end - t = LeftRotate(a, 5) + f + e + k + w[i] - e = d - d = c - c = LeftRotate(b, 30) - b = a - a = t - end - h0 = band(h0 + a, 4294967295) - h1 = band(h1 + b, 4294967295) - h2 = band(h2 + c, 4294967295) - h3 = band(h3 + d, 4294967295) - h4 = band(h4 + e, 4294967295) - str = strsub(str, 65) - end -end - -------------------------------------------------- - -local function sha1(str, hexres) - str = PreProcess(str) - h0 = 1732584193 - h1 = 4023233417 - h2 = 2562383102 - h3 = 0271733878 - h4 = 3285377520 - MainLoop(str) - local hex = ToHex(h0)..ToHex(h1)..ToHex(h2) - ..ToHex(h3)..ToHex(h4); - if hexres then - return hex; - else - return (hex:gsub("..", function (byte) - return string.char(tonumber(byte, 16)); - end)); - end -end - -_G.sha1 = {sha1 = sha1}; -return _G.sha1; - -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- - end) -package.preload['lib.adhoc'] = (function (...) --- Copyright (C) 2009-2010 Florian Zeitz --- --- This file is MIT/X11 licensed. Please see the --- COPYING file in the source package for more information. --- - -local st, uuid = require "util.stanza", require "util.uuid"; - -local xmlns_cmd = "http://jabber.org/protocol/commands"; - -local states = {} - -local _M = {}; - -function _cmdtag(desc, status, sessionid, action) - local cmd = st.stanza("command", { xmlns = xmlns_cmd, node = desc.node, status = status }); - if sessionid then cmd.attr.sessionid = sessionid; end - if action then cmd.attr.action = action; end - - return cmd; -end - -function _M.new(name, node, handler, permission) - return { name = name, node = node, handler = handler, cmdtag = _cmdtag, permission = (permission or "user") }; -end - -function _M.handle_cmd(command, origin, stanza) - local sessionid = stanza.tags[1].attr.sessionid or uuid.generate(); - local dataIn = {}; - dataIn.to = stanza.attr.to; - dataIn.from = stanza.attr.from; - dataIn.action = stanza.tags[1].attr.action or "execute"; - dataIn.form = stanza.tags[1]:child_with_ns("jabber:x:data"); - - local data, state = command:handler(dataIn, states[sessionid]); - states[sessionid] = state; - local stanza = st.reply(stanza); - if data.status == "completed" then - states[sessionid] = nil; - cmdtag = command:cmdtag("completed", sessionid); - elseif data.status == "canceled" then - states[sessionid] = nil; - cmdtag = command:cmdtag("canceled", sessionid); - elseif data.status == "error" then - states[sessionid] = nil; - stanza = st.error_reply(stanza, data.error.type, data.error.condition, data.error.message); - origin.send(stanza); - return true; - else - cmdtag = command:cmdtag("executing", sessionid); - end - - for name, content in pairs(data) do - if name == "info" then - cmdtag:tag("note", {type="info"}):text(content):up(); - elseif name == "warn" then - cmdtag:tag("note", {type="warn"}):text(content):up(); - elseif name == "error" then - cmdtag:tag("note", {type="error"}):text(content.message):up(); - elseif name =="actions" then - local actions = st.stanza("actions"); - for _, action in ipairs(content) do - if (action == "prev") or (action == "next") or (action == "complete") then - actions:tag(action):up(); - else - module:log("error", 'Command "'..command.name.. - '" at node "'..command.node..'" provided an invalid action "'..action..'"'); - end - end - cmdtag:add_child(actions); - elseif name == "form" then - cmdtag:add_child((content.layout or content):form(content.values)); - elseif name == "result" then - cmdtag:add_child((content.layout or content):form(content.values, "result")); - elseif name == "other" then - cmdtag:add_child(content); - end - end - stanza:add_child(cmdtag); - origin.send(stanza); - - return true; -end - -return _M; - end) -package.preload['util.stanza'] = (function (...) --- 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. --- - - -local t_insert = table.insert; -local t_concat = table.concat; -local t_remove = table.remove; -local t_concat = table.concat; -local s_format = string.format; -local s_match = string.match; -local tostring = tostring; -local setmetatable = setmetatable; -local getmetatable = getmetatable; -local pairs = pairs; -local ipairs = ipairs; -local type = type; -local next = next; -local print = print; -local unpack = unpack; -local s_gsub = string.gsub; -local s_char = string.char; -local s_find = string.find; -local os = os; - -local do_pretty_printing = not os.getenv("WINDIR"); -local getstyle, getstring; -if do_pretty_printing then - local ok, termcolours = pcall(require, "util.termcolours"); - if ok then - getstyle, getstring = termcolours.getstyle, termcolours.getstring; - else - do_pretty_printing = nil; - end -end - -local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas"; - -module "stanza" - -stanza_mt = { __type = "stanza" }; -stanza_mt.__index = stanza_mt; -local stanza_mt = stanza_mt; - -function stanza(name, attr) - local stanza = { name = name, attr = attr or {}, tags = {} }; - return setmetatable(stanza, stanza_mt); -end -local stanza = stanza; - -function stanza_mt:query(xmlns) - return self:tag("query", { xmlns = xmlns }); -end - -function stanza_mt:body(text, attr) - return self:tag("body", attr):text(text); -end - -function stanza_mt:tag(name, attrs) - local s = stanza(name, attrs); - local last_add = self.last_add; - if not last_add then last_add = {}; self.last_add = last_add; end - (last_add[#last_add] or self):add_direct_child(s); - t_insert(last_add, s); - return self; -end - -function stanza_mt:text(text) - local last_add = self.last_add; - (last_add and last_add[#last_add] or self):add_direct_child(text); - return self; -end - -function stanza_mt:up() - local last_add = self.last_add; - if last_add then t_remove(last_add); end - return self; -end - -function stanza_mt:reset() - self.last_add = nil; - return self; -end - -function stanza_mt:add_direct_child(child) - if type(child) == "table" then - t_insert(self.tags, child); - end - t_insert(self, child); -end - -function stanza_mt:add_child(child) - local last_add = self.last_add; - (last_add and last_add[#last_add] or self):add_direct_child(child); - return self; -end - -function stanza_mt:get_child(name, xmlns) - for _, child in ipairs(self.tags) do - if (not name or child.name == name) - and ((not xmlns and self.attr.xmlns == child.attr.xmlns) - or child.attr.xmlns == xmlns) then - - return child; - end - end -end - -function stanza_mt:get_child_text(name, xmlns) - local tag = self:get_child(name, xmlns); - if tag then - return tag:get_text(); - end - return nil; -end - -function stanza_mt:child_with_name(name) - for _, child in ipairs(self.tags) do - if child.name == name then return child; end - end -end - -function stanza_mt:child_with_ns(ns) - for _, child in ipairs(self.tags) do - if child.attr.xmlns == ns then return child; end - end -end - -function stanza_mt:children() - local i = 0; - return function (a) - i = i + 1 - return a[i]; - end, self, i; -end - -function stanza_mt:childtags(name, xmlns) - xmlns = xmlns or self.attr.xmlns; - local tags = self.tags; - local start_i, max_i = 1, #tags; - return function () - for i = start_i, max_i do - local v = tags[i]; - if (not name or v.name == name) - and (not xmlns or xmlns == v.attr.xmlns) then - start_i = i+1; - return v; - end - end - end; -end - -function stanza_mt:maptags(callback) - local tags, curr_tag = self.tags, 1; - local n_children, n_tags = #self, #tags; - - local i = 1; - while curr_tag <= n_tags do - if self[i] == tags[curr_tag] then - local ret = callback(self[i]); - if ret == nil then - t_remove(self, i); - t_remove(tags, curr_tag); - n_children = n_children - 1; - n_tags = n_tags - 1; - else - self[i] = ret; - tags[i] = ret; - end - i = i + 1; - curr_tag = curr_tag + 1; - end - end - return self; -end - -local xml_escape -do - local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; - function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end - _M.xml_escape = xml_escape; -end - -local function _dostring(t, buf, self, xml_escape, parentns) - local nsid = 0; - local name = t.name - t_insert(buf, "<"..name); - for k, v in pairs(t.attr) do - if s_find(k, "\1", 1, true) then - local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$"); - nsid = nsid + 1; - t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'"); - elseif not(k == "xmlns" and v == parentns) then - t_insert(buf, " "..k.."='"..xml_escape(v).."'"); - end - end - local len = #t; - if len == 0 then - t_insert(buf, "/>"); - else - t_insert(buf, ">"); - for n=1,len do - local child = t[n]; - if child.name then - self(child, buf, self, xml_escape, t.attr.xmlns); - else - t_insert(buf, xml_escape(child)); - end - end - t_insert(buf, "</"..name..">"); - end -end -function stanza_mt.__tostring(t) - local buf = {}; - _dostring(t, buf, _dostring, xml_escape, nil); - return t_concat(buf); -end - -function stanza_mt.top_tag(t) - local attr_string = ""; - if t.attr then - for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(" %s='%s'", k, xml_escape(tostring(v))); end end - end - return s_format("<%s%s>", t.name, attr_string); -end - -function stanza_mt.get_text(t) - if #t.tags == 0 then - return t_concat(t); - end -end - -function stanza_mt.get_error(stanza) - local type, condition, text; - - local error_tag = stanza:get_child("error"); - if not error_tag then - return nil, nil, nil; - end - type = error_tag.attr.type; - - for child in error_tag:childtags() do - if child.attr.xmlns == xmlns_stanzas then - if not text and child.name == "text" then - text = child:get_text(); - elseif not condition then - condition = child.name; - end - if condition and text then - break; - end - end - end - return type, condition or "undefined-condition", text; -end - -function stanza_mt.__add(s1, s2) - return s1:add_direct_child(s2); -end - - -do - local id = 0; - function new_id() - id = id + 1; - return "lx"..id; - end -end - -function preserialize(stanza) - local s = { name = stanza.name, attr = stanza.attr }; - for _, child in ipairs(stanza) do - if type(child) == "table" then - t_insert(s, preserialize(child)); - else - t_insert(s, child); - end - end - return s; -end - -function deserialize(stanza) - -- Set metatable - if stanza then - local attr = stanza.attr; - for i=1,#attr do attr[i] = nil; end - local attrx = {}; - for att in pairs(attr) do - if s_find(att, "|", 1, true) and not s_find(att, "\1", 1, true) then - local ns,na = s_match(att, "^([^|]+)|(.+)$"); - attrx[ns.."\1"..na] = attr[att]; - attr[att] = nil; - end - end - for a,v in pairs(attrx) do - attr[a] = v; - end - setmetatable(stanza, stanza_mt); - for _, child in ipairs(stanza) do - if type(child) == "table" then - deserialize(child); - end - end - if not stanza.tags then - -- Rebuild tags - local tags = {}; - for _, child in ipairs(stanza) do - if type(child) == "table" then - t_insert(tags, child); - end - end - stanza.tags = tags; - end - end - - return stanza; -end - -local function _clone(stanza) - local attr, tags = {}, {}; - for k,v in pairs(stanza.attr) do attr[k] = v; end - local new = { name = stanza.name, attr = attr, tags = tags }; - for i=1,#stanza do - local child = stanza[i]; - if child.name then - child = _clone(child); - t_insert(tags, child); - end - t_insert(new, child); - end - return setmetatable(new, stanza_mt); -end -clone = _clone; - -function message(attr, body) - if not body then - return stanza("message", attr); - else - return stanza("message", attr):tag("body"):text(body):up(); - end -end -function iq(attr) - if attr and not attr.id then attr.id = new_id(); end - return stanza("iq", attr or { id = new_id() }); -end - -function reply(orig) - return stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) }); -end - -do - local xmpp_stanzas_attr = { xmlns = xmlns_stanzas }; - function error_reply(orig, type, condition, message) - local t = reply(orig); - t.attr.type = "error"; - t:tag("error", {type = type}) --COMPAT: Some day xmlns:stanzas goes here - :tag(condition, xmpp_stanzas_attr):up(); - if (message) then t:tag("text", xmpp_stanzas_attr):text(message):up(); end - return t; -- stanza ready for adding app-specific errors - end -end - -function presence(attr) - return stanza("presence", attr); -end - -if do_pretty_printing then - local style_attrk = getstyle("yellow"); - local style_attrv = getstyle("red"); - local style_tagname = getstyle("red"); - local style_punc = getstyle("magenta"); - - local attr_format = " "..getstring(style_attrk, "%s")..getstring(style_punc, "=")..getstring(style_attrv, "'%s'"); - local top_tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">"); - --local tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">").."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">"); - local tag_format = top_tag_format.."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">"); - function stanza_mt.pretty_print(t) - local children_text = ""; - for n, child in ipairs(t) do - if type(child) == "string" then - children_text = children_text .. xml_escape(child); - else - children_text = children_text .. child:pretty_print(); - end - end - - local attr_string = ""; - if t.attr then - for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end - end - return s_format(tag_format, t.name, attr_string, children_text, t.name); - end - - function stanza_mt.pretty_top_tag(t) - local attr_string = ""; - if t.attr then - for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end - end - return s_format(top_tag_format, t.name, attr_string); - end -else - -- Sorry, fresh out of colours for you guys ;) - stanza_mt.pretty_print = stanza_mt.__tostring; - stanza_mt.pretty_top_tag = stanza_mt.top_tag; -end - -return _M; - end) -package.preload['util.timer'] = (function (...) --- 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. --- - - -local ns_addtimer = require "net.server".addtimer; -local event = require "net.server".event; -local event_base = require "net.server".event_base; - -local math_min = math.min -local math_huge = math.huge -local get_time = require "socket".gettime; -local t_insert = table.insert; -local t_remove = table.remove; -local ipairs, pairs = ipairs, pairs; -local type = type; - -local data = {}; -local new_data = {}; - -module "timer" - -local _add_task; -if not event then - function _add_task(delay, func) - local current_time = get_time(); - delay = delay + current_time; - if delay >= current_time then - t_insert(new_data, {delay, func}); - else - func(); - end - end - - ns_addtimer(function() - local current_time = get_time(); - if #new_data > 0 then - for _, d in pairs(new_data) do - t_insert(data, d); - end - new_data = {}; - end - - local next_time = math_huge; - for i, d in pairs(data) do - local t, func = d[1], d[2]; - if t <= current_time then - data[i] = nil; - local r = func(current_time); - if type(r) == "number" then - _add_task(r, func); - next_time = math_min(next_time, r); - end - else - next_time = math_min(next_time, t - current_time); - end - end - return next_time; - end); -else - local EVENT_LEAVE = (event.core and event.core.LEAVE) or -1; - function _add_task(delay, func) - local event_handle; - event_handle = event_base:addevent(nil, 0, function () - local ret = func(); - if ret then - return 0, ret; - elseif event_handle then - return EVENT_LEAVE; - end - end - , delay); - end -end - -add_task = _add_task; - -return _M; - end) -package.preload['util.termcolours'] = (function (...) --- 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. --- - - -local t_concat, t_insert = table.concat, table.insert; -local char, format = string.char, string.format; -local ipairs = ipairs; -local io_write = io.write; - -local windows; -if os.getenv("WINDIR") then - windows = require "util.windows"; -end -local orig_color = windows and windows.get_consolecolor and windows.get_consolecolor(); - -module "termcolours" - -local stylemap = { - 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 winstylemap = { - ["0"] = orig_color, -- reset - ["1"] = 7+8, -- bold - ["1;33"] = 2+4+8, -- bold yellow - ["1;31"] = 4+8 -- bold red -} - -local fmt_string = char(0x1B).."[%sm%s"..char(0x1B).."[0m"; -function getstring(style, text) - if style then - return format(fmt_string, style, text); - else - return text; - end -end - -function getstyle(...) - local styles, result = { ... }, {}; - for i, style in ipairs(styles) do - style = stylemap[style]; - if style then - t_insert(result, style); - end - end - return t_concat(result, ";"); -end - -local last = "0"; -function setstyle(style) - style = style or "0"; - if style ~= last then - io_write("\27["..style.."m"); - last = style; - end -end - -if windows then - function setstyle(style) - style = style or "0"; - if style ~= last then - windows.set_consolecolor(winstylemap[style] or orig_color); - last = style; - end - end - if not orig_color then - function setstyle(style) end - end -end - -return _M; - end) -package.preload['util.uuid'] = (function (...) --- 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. --- - - -local m_random = math.random; -local tostring = tostring; -local os_time = os.time; -local os_clock = os.clock; -local sha1 = require "util.hashes".sha1; - -module "uuid" - -local last_uniq_time = 0; -local function uniq_time() - local new_uniq_time = os_time(); - if last_uniq_time >= new_uniq_time then new_uniq_time = last_uniq_time + 1; end - last_uniq_time = new_uniq_time; - return new_uniq_time; -end - -local function new_random(x) - return sha1(x..os_clock()..tostring({}), true); -end - -local buffer = new_random(uniq_time()); -local function _seed(x) - buffer = new_random(buffer..x); -end -local function get_nibbles(n) - if #buffer < n then _seed(uniq_time()); end - local r = buffer:sub(0, n); - buffer = buffer:sub(n+1); - return r; -end -local function get_twobits() - return ("%x"):format(get_nibbles(1):byte() % 4 + 8); -end - -function generate() - -- generate RFC 4122 complaint UUIDs (version 4 - random) - return get_nibbles(8).."-"..get_nibbles(4).."-4"..get_nibbles(3).."-"..(get_twobits())..get_nibbles(3).."-"..get_nibbles(12); -end -seed = _seed; - -return _M; - end) -package.preload['net.dns'] = (function (...) --- Prosody IM --- This file is included with Prosody IM. It has modifications, --- which are hereby placed in the public domain. - - --- todo: quick (default) header generation --- todo: nxdomain, error handling --- todo: cache results of encodeName - - --- reference: http://tools.ietf.org/html/rfc1035 --- reference: http://tools.ietf.org/html/rfc1876 (LOC) - - -local socket = require "socket"; -local timer = require "util.timer"; - -local _, windows = pcall(require, "util.windows"); -local is_windows = (_ and windows) or os.getenv("WINDIR"); - -local coroutine, io, math, string, table = - coroutine, io, math, string, table; - -local ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, select, type= - ipairs, next, pairs, print, setmetatable, tostring, assert, error, unpack, select, type; - -local ztact = { -- public domain 20080404 lua@ztact.com - get = function(parent, ...) - local len = select('#', ...); - for i=1,len do - parent = parent[select(i, ...)]; - if parent == nil then break; end - end - return parent; - end; - set = function(parent, ...) - local len = select('#', ...); - local key, value = select(len-1, ...); - local cutpoint, cutkey; - - for i=1,len-2 do - local key = select (i, ...) - local child = parent[key] - - if value == nil then - if child == nil then - return; - elseif next(child, next(child)) then - cutpoint = nil; cutkey = nil; - elseif cutpoint == nil then - cutpoint = parent; cutkey = key; - end - elseif child == nil then - child = {}; - parent[key] = child; - end - parent = child - end - - if value == nil and cutpoint then - cutpoint[cutkey] = nil; - else - parent[key] = value; - return value; - end - end; -}; -local get, set = ztact.get, ztact.set; - -local default_timeout = 15; - --------------------------------------------------- module dns -module('dns') -local dns = _M; - - --- dns type & class codes ------------------------------ dns type & class codes - - -local append = table.insert - - -local function highbyte(i) -- - - - - - - - - - - - - - - - - - - highbyte - return (i-(i%0x100))/0x100; -end - - -local function augment (t) -- - - - - - - - - - - - - - - - - - - - augment - local a = {}; - for i,s in pairs(t) do - a[i] = s; - a[s] = s; - a[string.lower(s)] = s; - end - return a; -end - - -local function encode (t) -- - - - - - - - - - - - - - - - - - - - - encode - local code = {}; - for i,s in pairs(t) do - local word = string.char(highbyte(i), i%0x100); - code[i] = word; - code[s] = word; - code[string.lower(s)] = word; - end - return code; -end - - -dns.types = { - 'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR', 'NULL', 'WKS', - 'PTR', 'HINFO', 'MINFO', 'MX', 'TXT', - [ 28] = 'AAAA', [ 29] = 'LOC', [ 33] = 'SRV', - [252] = 'AXFR', [253] = 'MAILB', [254] = 'MAILA', [255] = '*' }; - - -dns.classes = { 'IN', 'CS', 'CH', 'HS', [255] = '*' }; - - -dns.type = augment (dns.types); -dns.class = augment (dns.classes); -dns.typecode = encode (dns.types); -dns.classcode = encode (dns.classes); - - - -local function standardize(qname, qtype, qclass) -- - - - - - - standardize - if string.byte(qname, -1) ~= 0x2E then qname = qname..'.'; end - qname = string.lower(qname); - return qname, dns.type[qtype or 'A'], dns.class[qclass or 'IN']; -end - - -local function prune(rrs, time, soft) -- - - - - - - - - - - - - - - prune - time = time or socket.gettime(); - for i,rr in pairs(rrs) do - if rr.tod then - -- rr.tod = rr.tod - 50 -- accelerated decripitude - rr.ttl = math.floor(rr.tod - time); - if rr.ttl <= 0 then - table.remove(rrs, i); - return prune(rrs, time, soft); -- Re-iterate - end - elseif soft == 'soft' then -- What is this? I forget! - assert(rr.ttl == 0); - rrs[i] = nil; - end - end -end - - --- metatables & co. ------------------------------------------ metatables & co. - - -local resolver = {}; -resolver.__index = resolver; - -resolver.timeout = default_timeout; - -local function default_rr_tostring(rr) - local rr_val = rr.type and rr[rr.type:lower()]; - if type(rr_val) ~= "string" then - return "<UNKNOWN RDATA TYPE>"; - end - return rr_val; -end - -local special_tostrings = { - LOC = resolver.LOC_tostring; - MX = function (rr) - return string.format('%2i %s', rr.pref, rr.mx); - end; - SRV = function (rr) - local s = rr.srv; - return string.format('%5d %5d %5d %s', s.priority, s.weight, s.port, s.target); - end; -}; - -local rr_metatable = {}; -- - - - - - - - - - - - - - - - - - - rr_metatable -function rr_metatable.__tostring(rr) - local rr_string = (special_tostrings[rr.type] or default_rr_tostring)(rr); - return string.format('%2s %-5s %6i %-28s %s', rr.class, rr.type, rr.ttl, rr.name, rr_string); -end - - -local rrs_metatable = {}; -- - - - - - - - - - - - - - - - - - rrs_metatable -function rrs_metatable.__tostring(rrs) - local t = {}; - for i,rr in pairs(rrs) do - append(t, tostring(rr)..'\n'); - end - return table.concat(t); -end - - -local cache_metatable = {}; -- - - - - - - - - - - - - - - - cache_metatable -function cache_metatable.__tostring(cache) - local time = socket.gettime(); - local t = {}; - for class,types in pairs(cache) do - for type,names in pairs(types) do - for name,rrs in pairs(names) do - prune(rrs, time); - append(t, tostring(rrs)); - end - end - end - return table.concat(t); -end - - -function resolver:new() -- - - - - - - - - - - - - - - - - - - - - resolver - local r = { active = {}, cache = {}, unsorted = {} }; - setmetatable(r, resolver); - setmetatable(r.cache, cache_metatable); - setmetatable(r.unsorted, { __mode = 'kv' }); - return r; -end - - --- packet layer -------------------------------------------------- packet layer - - -function dns.random(...) -- - - - - - - - - - - - - - - - - - - dns.random - math.randomseed(math.floor(10000*socket.gettime())); - dns.random = math.random; - return dns.random(...); -end - - -local function encodeHeader(o) -- - - - - - - - - - - - - - - encodeHeader - o = o or {}; - o.id = o.id or dns.random(0, 0xffff); -- 16b (random) id - - o.rd = o.rd or 1; -- 1b 1 recursion desired - o.tc = o.tc or 0; -- 1b 1 truncated response - o.aa = o.aa or 0; -- 1b 1 authoritative response - o.opcode = o.opcode or 0; -- 4b 0 query - -- 1 inverse query - -- 2 server status request - -- 3-15 reserved - o.qr = o.qr or 0; -- 1b 0 query, 1 response - - o.rcode = o.rcode or 0; -- 4b 0 no error - -- 1 format error - -- 2 server failure - -- 3 name error - -- 4 not implemented - -- 5 refused - -- 6-15 reserved - o.z = o.z or 0; -- 3b 0 resvered - o.ra = o.ra or 0; -- 1b 1 recursion available - - o.qdcount = o.qdcount or 1; -- 16b number of question RRs - o.ancount = o.ancount or 0; -- 16b number of answers RRs - o.nscount = o.nscount or 0; -- 16b number of nameservers RRs - o.arcount = o.arcount or 0; -- 16b number of additional RRs - - -- string.char() rounds, so prevent roundup with -0.4999 - local header = string.char( - highbyte(o.id), o.id %0x100, - o.rd + 2*o.tc + 4*o.aa + 8*o.opcode + 128*o.qr, - o.rcode + 16*o.z + 128*o.ra, - highbyte(o.qdcount), o.qdcount %0x100, - highbyte(o.ancount), o.ancount %0x100, - highbyte(o.nscount), o.nscount %0x100, - highbyte(o.arcount), o.arcount %0x100 - ); - - return header, o.id; -end - - -local function encodeName(name) -- - - - - - - - - - - - - - - - encodeName - local t = {}; - for part in string.gmatch(name, '[^.]+') do - append(t, string.char(string.len(part))); - append(t, part); - end - append(t, string.char(0)); - return table.concat(t); -end - - -local function encodeQuestion(qname, qtype, qclass) -- - - - encodeQuestion - qname = encodeName(qname); - qtype = dns.typecode[qtype or 'a']; - qclass = dns.classcode[qclass or 'in']; - return qname..qtype..qclass; -end - - -function resolver:byte(len) -- - - - - - - - - - - - - - - - - - - - - byte - len = len or 1; - local offset = self.offset; - local last = offset + len - 1; - if last > #self.packet then - error(string.format('out of bounds: %i>%i', last, #self.packet)); - end - self.offset = offset + len; - return string.byte(self.packet, offset, last); -end - - -function resolver:word() -- - - - - - - - - - - - - - - - - - - - - - word - local b1, b2 = self:byte(2); - return 0x100*b1 + b2; -end - - -function resolver:dword () -- - - - - - - - - - - - - - - - - - - - - dword - local b1, b2, b3, b4 = self:byte(4); - --print('dword', b1, b2, b3, b4); - return 0x1000000*b1 + 0x10000*b2 + 0x100*b3 + b4; -end - - -function resolver:sub(len) -- - - - - - - - - - - - - - - - - - - - - - sub - len = len or 1; - local s = string.sub(self.packet, self.offset, self.offset + len - 1); - self.offset = self.offset + len; - return s; -end - - -function resolver:header(force) -- - - - - - - - - - - - - - - - - - header - local id = self:word(); - --print(string.format(':header id %x', id)); - if not self.active[id] and not force then return nil; end - - local h = { id = id }; - - local b1, b2 = self:byte(2); - - h.rd = b1 %2; - h.tc = b1 /2%2; - h.aa = b1 /4%2; - h.opcode = b1 /8%16; - h.qr = b1 /128; - - h.rcode = b2 %16; - h.z = b2 /16%8; - h.ra = b2 /128; - - h.qdcount = self:word(); - h.ancount = self:word(); - h.nscount = self:word(); - h.arcount = self:word(); - - for k,v in pairs(h) do h[k] = v-v%1; end - - return h; -end - - -function resolver:name() -- - - - - - - - - - - - - - - - - - - - - - name - local remember, pointers = nil, 0; - local len = self:byte(); - local n = {}; - while len > 0 do - if len >= 0xc0 then -- name is "compressed" - pointers = pointers + 1; - if pointers >= 20 then error('dns error: 20 pointers'); end; - local offset = ((len-0xc0)*0x100) + self:byte(); - remember = remember or self.offset; - self.offset = offset + 1; -- +1 for lua - else -- name is not compressed - append(n, self:sub(len)..'.'); - end - len = self:byte(); - end - self.offset = remember or self.offset; - return table.concat(n); -end - - -function resolver:question() -- - - - - - - - - - - - - - - - - - question - local q = {}; - q.name = self:name(); - q.type = dns.type[self:word()]; - q.class = dns.class[self:word()]; - return q; -end - - -function resolver:A(rr) -- - - - - - - - - - - - - - - - - - - - - - - - A - local b1, b2, b3, b4 = self:byte(4); - rr.a = string.format('%i.%i.%i.%i', b1, b2, b3, b4); -end - -function resolver:AAAA(rr) - local addr = {}; - for i = 1, rr.rdlength, 2 do - local b1, b2 = self:byte(2); - table.insert(addr, ("%02x%02x"):format(b1, b2)); - end - rr.aaaa = table.concat(addr, ":"); -end - -function resolver:CNAME(rr) -- - - - - - - - - - - - - - - - - - - - CNAME - rr.cname = self:name(); -end - - -function resolver:MX(rr) -- - - - - - - - - - - - - - - - - - - - - - - MX - rr.pref = self:word(); - rr.mx = self:name(); -end - - -function resolver:LOC_nibble_power() -- - - - - - - - - - LOC_nibble_power - local b = self:byte(); - --print('nibbles', ((b-(b%0x10))/0x10), (b%0x10)); - return ((b-(b%0x10))/0x10) * (10^(b%0x10)); -end - - -function resolver:LOC(rr) -- - - - - - - - - - - - - - - - - - - - - - LOC - rr.version = self:byte(); - if rr.version == 0 then - rr.loc = rr.loc or {}; - rr.loc.size = self:LOC_nibble_power(); - rr.loc.horiz_pre = self:LOC_nibble_power(); - rr.loc.vert_pre = self:LOC_nibble_power(); - rr.loc.latitude = self:dword(); - rr.loc.longitude = self:dword(); - rr.loc.altitude = self:dword(); - end -end - - -local function LOC_tostring_degrees(f, pos, neg) -- - - - - - - - - - - - - - f = f - 0x80000000; - if f < 0 then pos = neg; f = -f; end - local deg, min, msec; - msec = f%60000; - f = (f-msec)/60000; - min = f%60; - deg = (f-min)/60; - return string.format('%3d %2d %2.3f %s', deg, min, msec/1000, pos); -end - - -function resolver.LOC_tostring(rr) -- - - - - - - - - - - - - LOC_tostring - local t = {}; - - --[[ - for k,name in pairs { 'size', 'horiz_pre', 'vert_pre', 'latitude', 'longitude', 'altitude' } do - append(t, string.format('%4s%-10s: %12.0f\n', '', name, rr.loc[name])); - end - --]] - - append(t, string.format( - '%s %s %.2fm %.2fm %.2fm %.2fm', - LOC_tostring_degrees (rr.loc.latitude, 'N', 'S'), - LOC_tostring_degrees (rr.loc.longitude, 'E', 'W'), - (rr.loc.altitude - 10000000) / 100, - rr.loc.size / 100, - rr.loc.horiz_pre / 100, - rr.loc.vert_pre / 100 - )); - - return table.concat(t); -end - - -function resolver:NS(rr) -- - - - - - - - - - - - - - - - - - - - - - - NS - rr.ns = self:name(); -end - - -function resolver:SOA(rr) -- - - - - - - - - - - - - - - - - - - - - - SOA -end - - -function resolver:SRV(rr) -- - - - - - - - - - - - - - - - - - - - - - SRV - rr.srv = {}; - rr.srv.priority = self:word(); - rr.srv.weight = self:word(); - rr.srv.port = self:word(); - rr.srv.target = self:name(); -end - -function resolver:PTR(rr) - rr.ptr = self:name(); -end - -function resolver:TXT(rr) -- - - - - - - - - - - - - - - - - - - - - - TXT - rr.txt = self:sub (self:byte()); -end - - -function resolver:rr() -- - - - - - - - - - - - - - - - - - - - - - - - rr - local rr = {}; - setmetatable(rr, rr_metatable); - rr.name = self:name(self); - rr.type = dns.type[self:word()] or rr.type; - rr.class = dns.class[self:word()] or rr.class; - rr.ttl = 0x10000*self:word() + self:word(); - rr.rdlength = self:word(); - - if rr.ttl <= 0 then - rr.tod = self.time + 30; - else - rr.tod = self.time + rr.ttl; - end - - local remember = self.offset; - local rr_parser = self[dns.type[rr.type]]; - if rr_parser then rr_parser(self, rr); end - self.offset = remember; - rr.rdata = self:sub(rr.rdlength); - return rr; -end - - -function resolver:rrs (count) -- - - - - - - - - - - - - - - - - - - - - rrs - local rrs = {}; - for i = 1,count do append(rrs, self:rr()); end - return rrs; -end - - -function resolver:decode(packet, force) -- - - - - - - - - - - - - - decode - self.packet, self.offset = packet, 1; - local header = self:header(force); - if not header then return nil; end - local response = { header = header }; - - response.question = {}; - local offset = self.offset; - for i = 1,response.header.qdcount do - append(response.question, self:question()); - end - response.question.raw = string.sub(self.packet, offset, self.offset - 1); - - if not force then - if not self.active[response.header.id] or not self.active[response.header.id][response.question.raw] then - return nil; - end - end - - response.answer = self:rrs(response.header.ancount); - response.authority = self:rrs(response.header.nscount); - response.additional = self:rrs(response.header.arcount); - - return response; -end - - --- socket layer -------------------------------------------------- socket layer - - -resolver.delays = { 1, 3 }; - - -function resolver:addnameserver(address) -- - - - - - - - - - addnameserver - self.server = self.server or {}; - append(self.server, address); -end - - -function resolver:setnameserver(address) -- - - - - - - - - - setnameserver - self.server = {}; - self:addnameserver(address); -end - - -function resolver:adddefaultnameservers() -- - - - - adddefaultnameservers - if is_windows then - if windows and windows.get_nameservers then - for _, server in ipairs(windows.get_nameservers()) do - self:addnameserver(server); - end - end - if not self.server or #self.server == 0 then - -- TODO log warning about no nameservers, adding opendns servers as fallback - self:addnameserver("208.67.222.222"); - self:addnameserver("208.67.220.220"); - end - else -- posix - local resolv_conf = io.open("/etc/resolv.conf"); - if resolv_conf then - for line in resolv_conf:lines() do - line = line:gsub("#.*$", "") - :match('^%s*nameserver%s+(.*)%s*$'); - if line then - line:gsub("%f[%d.](%d+%.%d+%.%d+%.%d+)%f[^%d.]", function (address) - self:addnameserver(address) - end); - end - end - end - if not self.server or #self.server == 0 then - -- TODO log warning about no nameservers, adding localhost as the default nameserver - self:addnameserver("127.0.0.1"); - end - end -end - - -function resolver:getsocket(servernum) -- - - - - - - - - - - - - getsocket - self.socket = self.socket or {}; - self.socketset = self.socketset or {}; - - local sock = self.socket[servernum]; - if sock then return sock; end - - local err; - sock, err = socket.udp(); - if not sock then - return nil, err; - end - if self.socket_wrapper then sock = self.socket_wrapper(sock, self); end - sock:settimeout(0); - -- todo: attempt to use a random port, fallback to 0 - sock:setsockname('*', 0); - sock:setpeername(self.server[servernum], 53); - self.socket[servernum] = sock; - self.socketset[sock] = servernum; - return sock; -end - -function resolver:voidsocket(sock) - if self.socket[sock] then - self.socketset[self.socket[sock]] = nil; - self.socket[sock] = nil; - elseif self.socketset[sock] then - self.socket[self.socketset[sock]] = nil; - self.socketset[sock] = nil; - end -end - -function resolver:socket_wrapper_set(func) -- - - - - - - socket_wrapper_set - self.socket_wrapper = func; -end - - -function resolver:closeall () -- - - - - - - - - - - - - - - - - - closeall - for i,sock in ipairs(self.socket) do - self.socket[i] = nil; - self.socketset[sock] = nil; - sock:close(); - end -end - - -function resolver:remember(rr, type) -- - - - - - - - - - - - - - remember - --print ('remember', type, rr.class, rr.type, rr.name) - local qname, qtype, qclass = standardize(rr.name, rr.type, rr.class); - - if type ~= '*' then - type = qtype; - local all = get(self.cache, qclass, '*', qname); - --print('remember all', all); - if all then append(all, rr); end - end - - self.cache = self.cache or setmetatable({}, cache_metatable); - local rrs = get(self.cache, qclass, type, qname) or - set(self.cache, qclass, type, qname, setmetatable({}, rrs_metatable)); - append(rrs, rr); - - if type == 'MX' then self.unsorted[rrs] = true; end -end - - -local function comp_mx(a, b) -- - - - - - - - - - - - - - - - - - - comp_mx - return (a.pref == b.pref) and (a.mx < b.mx) or (a.pref < b.pref); -end - - -function resolver:peek (qname, qtype, qclass) -- - - - - - - - - - - - peek - qname, qtype, qclass = standardize(qname, qtype, qclass); - local rrs = get(self.cache, qclass, qtype, qname); - if not rrs then return nil; end - if prune(rrs, socket.gettime()) and qtype == '*' or not next(rrs) then - set(self.cache, qclass, qtype, qname, nil); - return nil; - end - if self.unsorted[rrs] then table.sort (rrs, comp_mx); end - return rrs; -end - - -function resolver:purge(soft) -- - - - - - - - - - - - - - - - - - - purge - if soft == 'soft' then - self.time = socket.gettime(); - for class,types in pairs(self.cache or {}) do - for type,names in pairs(types) do - for name,rrs in pairs(names) do - prune(rrs, self.time, 'soft') - end - end - end - else self.cache = {}; end -end - - -function resolver:query(qname, qtype, qclass) -- - - - - - - - - - -- query - qname, qtype, qclass = standardize(qname, qtype, qclass) - - if not self.server then self:adddefaultnameservers(); end - - local question = encodeQuestion(qname, qtype, qclass); - local peek = self:peek (qname, qtype, qclass); - if peek then return peek; end - - local header, id = encodeHeader(); - --print ('query id', id, qclass, qtype, qname) - local o = { - packet = header..question, - server = self.best_server, - delay = 1, - retry = socket.gettime() + self.delays[1] - }; - - -- remember the query - self.active[id] = self.active[id] or {}; - self.active[id][question] = o; - - -- remember which coroutine wants the answer - local co = coroutine.running(); - if co then - set(self.wanted, qclass, qtype, qname, co, true); - --set(self.yielded, co, qclass, qtype, qname, true); - end - - local conn, err = self:getsocket(o.server) - if not conn then - return nil, err; - end - conn:send (o.packet) - - if timer and self.timeout then - local num_servers = #self.server; - local i = 1; - timer.add_task(self.timeout, function () - if get(self.wanted, qclass, qtype, qname, co) then - if i < num_servers then - i = i + 1; - self:servfail(conn); - o.server = self.best_server; - conn, err = self:getsocket(o.server); - if conn then - conn:send(o.packet); - return self.timeout; - end - end - -- Tried everything, failed - self:cancel(qclass, qtype, qname, co, true); - end - end) - end - return true; -end - -function resolver:servfail(sock) - -- Resend all queries for this server - - local num = self.socketset[sock] - - -- Socket is dead now - self:voidsocket(sock); - - -- Find all requests to the down server, and retry on the next server - self.time = socket.gettime(); - for id,queries in pairs(self.active) do - for question,o in pairs(queries) do - if o.server == num then -- This request was to the broken server - o.server = o.server + 1 -- Use next server - if o.server > #self.server then - o.server = 1; - end - - o.retries = (o.retries or 0) + 1; - if o.retries >= #self.server then - --print('timeout'); - queries[question] = nil; - else - local _a = self:getsocket(o.server); - if _a then _a:send(o.packet); end - end - end - end - end - - if num == self.best_server then - self.best_server = self.best_server + 1; - if self.best_server > #self.server then - -- Exhausted all servers, try first again - self.best_server = 1; - end - end -end - -function resolver:settimeout(seconds) - self.timeout = seconds; -end - -function resolver:receive(rset) -- - - - - - - - - - - - - - - - - receive - --print('receive'); print(self.socket); - self.time = socket.gettime(); - rset = rset or self.socket; - - local response; - for i,sock in pairs(rset) do - - if self.socketset[sock] then - local packet = sock:receive(); - if packet then - response = self:decode(packet); - if response and self.active[response.header.id] - and self.active[response.header.id][response.question.raw] then - --print('received response'); - --self.print(response); - - for j,rr in pairs(response.answer) do - if rr.name:sub(-#response.question[1].name, -1) == response.question[1].name then - self:remember(rr, response.question[1].type) - end - end - - -- retire the query - local queries = self.active[response.header.id]; - queries[response.question.raw] = nil; - - if not next(queries) then self.active[response.header.id] = nil; end - if not next(self.active) then self:closeall(); end - - -- was the query on the wanted list? - local q = response.question[1]; - local cos = get(self.wanted, q.class, q.type, q.name); - if cos then - for co in pairs(cos) do - set(self.yielded, co, q.class, q.type, q.name, nil); - if coroutine.status(co) == "suspended" then coroutine.resume(co); end - end - set(self.wanted, q.class, q.type, q.name, nil); - end - end - end - end - end - - return response; -end - - -function resolver:feed(sock, packet, force) - --print('receive'); print(self.socket); - self.time = socket.gettime(); - - local response = self:decode(packet, force); - if response and self.active[response.header.id] - and self.active[response.header.id][response.question.raw] then - --print('received response'); - --self.print(response); - - for j,rr in pairs(response.answer) do - self:remember(rr, response.question[1].type); - end - - -- retire the query - local queries = self.active[response.header.id]; - queries[response.question.raw] = nil; - if not next(queries) then self.active[response.header.id] = nil; end - if not next(self.active) then self:closeall(); end - - -- was the query on the wanted list? - local q = response.question[1]; - if q then - local cos = get(self.wanted, q.class, q.type, q.name); - if cos then - for co in pairs(cos) do - set(self.yielded, co, q.class, q.type, q.name, nil); - if coroutine.status(co) == "suspended" then coroutine.resume(co); end - end - set(self.wanted, q.class, q.type, q.name, nil); - end - end - end - - return response; -end - -function resolver:cancel(qclass, qtype, qname, co, call_handler) - local cos = get(self.wanted, qclass, qtype, qname); - if cos then - if call_handler then - coroutine.resume(co); - end - cos[co] = nil; - end -end - -function resolver:pulse() -- - - - - - - - - - - - - - - - - - - - - pulse - --print(':pulse'); - while self:receive() do end - if not next(self.active) then return nil; end - - self.time = socket.gettime(); - for id,queries in pairs(self.active) do - for question,o in pairs(queries) do - if self.time >= o.retry then - - o.server = o.server + 1; - if o.server > #self.server then - o.server = 1; - o.delay = o.delay + 1; - end - - if o.delay > #self.delays then - --print('timeout'); - queries[question] = nil; - if not next(queries) then self.active[id] = nil; end - if not next(self.active) then return nil; end - else - --print('retry', o.server, o.delay); - local _a = self.socket[o.server]; - if _a then _a:send(o.packet); end - o.retry = self.time + self.delays[o.delay]; - end - end - end - end - - if next(self.active) then return true; end - return nil; -end - - -function resolver:lookup(qname, qtype, qclass) -- - - - - - - - - - lookup - self:query (qname, qtype, qclass) - while self:pulse() do - local recvt = {} - for i, s in ipairs(self.socket) do - recvt[i] = s - end - socket.select(recvt, nil, 4) - end - --print(self.cache); - return self:peek(qname, qtype, qclass); -end - -function resolver:lookupex(handler, qname, qtype, qclass) -- - - - - - - - - - lookup - return self:peek(qname, qtype, qclass) or self:query(qname, qtype, qclass); -end - -function resolver:tohostname(ip) - return dns.lookup(ip:gsub("(%d+)%.(%d+)%.(%d+)%.(%d+)", "%4.%3.%2.%1.in-addr.arpa."), "PTR"); -end - ---print ---------------------------------------------------------------- print - - -local hints = { -- - - - - - - - - - - - - - - - - - - - - - - - - - - hints - 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 = dns.type, - class = dns.class -}; - - -local function hint(p, s) -- - - - - - - - - - - - - - - - - - - - - - hint - return (hints[s] and hints[s][p[s]]) or ''; -end - - -function resolver.print(response) -- - - - - - - - - - - - - resolver.print - for s,s in pairs { 'id', 'qr', 'opcode', 'aa', 'tc', 'rd', 'ra', 'z', - 'rcode', 'qdcount', 'ancount', 'nscount', 'arcount' } do - print( string.format('%-30s', 'header.'..s), response.header[s], hint(response.header, s) ); - end - - for i,question in ipairs(response.question) do - print(string.format ('question[%i].name ', i), question.name); - print(string.format ('question[%i].type ', i), question.type); - print(string.format ('question[%i].class ', i), question.class); - end - - local common = { name=1, type=1, class=1, ttl=1, rdlength=1, rdata=1 }; - local tmp; - for s,s in pairs({'answer', 'authority', 'additional'}) do - for i,rr in pairs(response[s]) do - for j,t in pairs({ 'name', 'type', 'class', 'ttl', 'rdlength' }) do - tmp = string.format('%s[%i].%s', s, i, t); - print(string.format('%-30s', tmp), rr[t], hint(rr, t)); - end - for j,t in pairs(rr) do - if not common[j] then - tmp = string.format('%s[%i].%s', s, i, j); - print(string.format('%-30s %s', tostring(tmp), tostring(t))); - end - end - end - end -end - - --- module api ------------------------------------------------------ module api - - -function dns.resolver () -- - - - - - - - - - - - - - - - - - - - - resolver - -- this function seems to be redundant with resolver.new () - - local r = { active = {}, cache = {}, unsorted = {}, wanted = {}, yielded = {}, best_server = 1 }; - setmetatable (r, resolver); - setmetatable (r.cache, cache_metatable); - setmetatable (r.unsorted, { __mode = 'kv' }); - return r; -end - -local _resolver = dns.resolver(); -dns._resolver = _resolver; - -function dns.lookup(...) -- - - - - - - - - - - - - - - - - - - - - lookup - return _resolver:lookup(...); -end - -function dns.tohostname(...) - return _resolver:tohostname(...); -end - -function dns.purge(...) -- - - - - - - - - - - - - - - - - - - - - - purge - return _resolver:purge(...); -end - -function dns.peek(...) -- - - - - - - - - - - - - - - - - - - - - - - peek - return _resolver:peek(...); -end - -function dns.query(...) -- - - - - - - - - - - - - - - - - - - - - - query - return _resolver:query(...); -end - -function dns.feed(...) -- - - - - - - - - - - - - - - - - - - - - - - feed - return _resolver:feed(...); -end - -function dns.cancel(...) -- - - - - - - - - - - - - - - - - - - - - - cancel - return _resolver:cancel(...); -end - -function dns.settimeout(...) - return _resolver:settimeout(...); -end - -function dns.socket_wrapper_set(...) -- - - - - - - - - socket_wrapper_set - return _resolver:socket_wrapper_set(...); -end - -return dns; - end) -package.preload['net.adns'] = (function (...) --- 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. --- - -local server = require "net.server"; -local dns = require "net.dns"; - -local log = require "util.logger".init("adns"); - -local t_insert, t_remove = table.insert, table.remove; -local coroutine, tostring, pcall = coroutine, tostring, pcall; - -local function dummy_send(sock, data, i, j) return (j-i)+1; end - -module "adns" - -function lookup(handler, qname, qtype, qclass) - return coroutine.wrap(function (peek) - if peek then - log("debug", "Records for %s already cached, using those...", qname); - handler(peek); - return; - end - log("debug", "Records for %s not in cache, sending query (%s)...", qname, tostring(coroutine.running())); - local ok, err = dns.query(qname, qtype, qclass); - if ok then - coroutine.yield({ qclass or "IN", qtype or "A", qname, coroutine.running()}); -- Wait for reply - log("debug", "Reply for %s (%s)", qname, tostring(coroutine.running())); - end - if ok then - ok, err = pcall(handler, dns.peek(qname, qtype, qclass)); - else - log("error", "Error sending DNS query: %s", err); - ok, err = pcall(handler, nil, err); - end - if not ok then - log("error", "Error in DNS response handler: %s", tostring(err)); - end - end)(dns.peek(qname, qtype, qclass)); -end - -function cancel(handle, call_handler, reason) - log("warn", "Cancelling DNS lookup for %s", tostring(handle[3])); - dns.cancel(handle[1], handle[2], handle[3], handle[4], call_handler); -end - -function new_async_socket(sock, resolver) - local peername = "<unknown>"; - local listener = {}; - local handler = {}; - function listener.onincoming(conn, data) - if data then - dns.feed(handler, data); - end - end - function listener.ondisconnect(conn, err) - if err then - log("warn", "DNS socket for %s disconnected: %s", peername, err); - local servers = resolver.server; - if resolver.socketset[conn] == resolver.best_server and resolver.best_server == #servers then - log("error", "Exhausted all %d configured DNS servers, next lookup will try %s again", #servers, servers[1]); - end - - resolver:servfail(conn); -- Let the magic commence - end - end - handler = server.wrapclient(sock, "dns", 53, listener); - if not handler then - log("warn", "handler is nil"); - end - - handler.settimeout = function () end - handler.setsockname = function (_, ...) return sock:setsockname(...); end - handler.setpeername = function (_, ...) peername = (...); local ret = sock:setpeername(...); _:set_send(dummy_send); return ret; end - handler.connect = function (_, ...) return sock:connect(...) end - --handler.send = function (_, data) _:write(data); return _.sendbuffer and _.sendbuffer(); end - handler.send = function (_, data) - local getpeername = sock.getpeername; - log("debug", "Sending DNS query to %s", (getpeername and getpeername(sock)) or "<unconnected>"); - return sock:send(data); - end - return handler; -end - -dns.socket_wrapper_set(new_async_socket); - -return _M; - end) -package.preload['net.server'] = (function (...) --- --- server.lua by blastbeat of the luadch project --- Re-used here under the MIT/X Consortium License --- --- Modifications (C) 2008-2010 Matthew Wild, Waqas Hussain --- - --- // wrapping luadch stuff // -- - -local use = function( what ) - return _G[ what ] -end -local clean = function( tbl ) - for i, k in pairs( tbl ) do - tbl[ i ] = nil - end -end - -local log, table_concat = require ("util.logger").init("socket"), table.concat; -local out_put = function (...) return log("debug", table_concat{...}); end -local out_error = function (...) return log("warn", table_concat{...}); end -local mem_free = collectgarbage - -----------------------------------// DECLARATION //-- - ---// constants //-- - -local STAT_UNIT = 1 -- byte - ---// lua functions //-- - -local type = use "type" -local pairs = use "pairs" -local ipairs = use "ipairs" -local tonumber = use "tonumber" -local tostring = use "tostring" -local collectgarbage = use "collectgarbage" - ---// lua libs //-- - -local os = use "os" -local table = use "table" -local string = use "string" -local coroutine = use "coroutine" - ---// lua lib methods //-- - -local os_difftime = os.difftime -local math_min = math.min -local math_huge = math.huge -local table_concat = table.concat -local table_remove = table.remove -local string_len = string.len -local string_sub = string.sub -local coroutine_wrap = coroutine.wrap -local coroutine_yield = coroutine.yield - ---// extern libs //-- - -local luasec = use "ssl" -local luasocket = use "socket" or require "socket" -local luasocket_gettime = luasocket.gettime - ---// extern lib methods //-- - -local ssl_wrap = ( luasec and luasec.wrap ) -local socket_bind = luasocket.bind -local socket_sleep = luasocket.sleep -local socket_select = luasocket.select -local ssl_newcontext = ( luasec and luasec.newcontext ) - ---// functions //-- - -local id -local loop -local stats -local idfalse -local addtimer -local closeall -local addsocket -local addserver -local getserver -local wrapserver -local getsettings -local closesocket -local removesocket -local removeserver -local changetimeout -local wrapconnection -local changesettings - ---// tables //-- - -local _server -local _readlist -local _timerlist -local _sendlist -local _socketlist -local _closelist -local _readtimes -local _writetimes - ---// simple data types //-- - -local _ -local _readlistlen -local _sendlistlen -local _timerlistlen - -local _sendtraffic -local _readtraffic - -local _selecttimeout -local _sleeptime - -local _starttime -local _currenttime - -local _maxsendlen -local _maxreadlen - -local _checkinterval -local _sendtimeout -local _readtimeout - -local _cleanqueue - -local _timer - -local _maxclientsperserver - -local _maxsslhandshake - -----------------------------------// DEFINITION //-- - -_server = { } -- key = port, value = table; list of listening servers -_readlist = { } -- array with sockets to read from -_sendlist = { } -- arrary with sockets to write to -_timerlist = { } -- array of timer functions -_socketlist = { } -- key = socket, value = wrapped socket (handlers) -_readtimes = { } -- key = handler, value = timestamp of last data reading -_writetimes = { } -- key = handler, value = timestamp of last data writing/sending -_closelist = { } -- handlers to close - -_readlistlen = 0 -- length of readlist -_sendlistlen = 0 -- length of sendlist -_timerlistlen = 0 -- lenght of timerlist - -_sendtraffic = 0 -- some stats -_readtraffic = 0 - -_selecttimeout = 1 -- timeout of socket.select -_sleeptime = 0 -- time to wait at the end of every loop - -_maxsendlen = 51000 * 1024 -- max len of send buffer -_maxreadlen = 25000 * 1024 -- max len of read buffer - -_checkinterval = 1200000 -- interval in secs to check idle clients -_sendtimeout = 60000 -- allowed send idle time in secs -_readtimeout = 6 * 60 * 60 -- allowed read idle time in secs - -_cleanqueue = false -- clean bufferqueue after using - -_maxclientsperserver = 1000 - -_maxsslhandshake = 30 -- max handshake round-trips - -----------------------------------// PRIVATE //-- - -wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxconnections ) -- this function wraps a server - - maxconnections = maxconnections or _maxclientsperserver - - local connections = 0 - - local dispatch, disconnect = listeners.onconnect or listeners.onincoming, listeners.ondisconnect - - local accept = socket.accept - - --// public methods of the object //-- - - local handler = { } - - handler.shutdown = function( ) end - - handler.ssl = function( ) - return sslctx ~= nil - end - handler.sslctx = function( ) - return sslctx - end - handler.remove = function( ) - connections = connections - 1 - end - handler.close = function( ) - for _, handler in pairs( _socketlist ) do - if handler.serverport == serverport then - handler.disconnect( handler, "server closed" ) - handler:close( true ) - end - end - socket:close( ) - _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) - _readlistlen = removesocket( _readlist, socket, _readlistlen ) - _socketlist[ socket ] = nil - handler = nil - socket = nil - --mem_free( ) - out_put "server.lua: closed server handler and removed sockets from list" - end - handler.ip = function( ) - return ip - end - handler.serverport = function( ) - return serverport - end - handler.socket = function( ) - return socket - end - handler.readbuffer = function( ) - if connections > maxconnections then - out_put( "server.lua: refused new client connection: server full" ) - return false - end - local client, err = accept( socket ) -- try to accept - if client then - local ip, clientport = client:getpeername( ) - client:settimeout( 0 ) - local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx ) -- wrap new client socket - if err then -- error while wrapping ssl socket - return false - end - connections = connections + 1 - out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport)) - return dispatch( handler ) - elseif err then -- maybe timeout or something else - out_put( "server.lua: error with new client connection: ", tostring(err) ) - return false - end - end - return handler -end - -wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx ) -- this function wraps a client to a handler object - - socket:settimeout( 0 ) - - --// local import of socket methods //-- - - local send - local receive - local shutdown - - --// private closures of the object //-- - - local ssl - - local dispatch = listeners.onincoming - local status = listeners.onstatus - local disconnect = listeners.ondisconnect - local drain = listeners.ondrain - - local bufferqueue = { } -- buffer array - local bufferqueuelen = 0 -- end of buffer array - - local toclose - local fatalerror - local needtls - - local bufferlen = 0 - - local noread = false - local nosend = false - - local sendtraffic, readtraffic = 0, 0 - - local maxsendlen = _maxsendlen - local maxreadlen = _maxreadlen - - --// public methods of the object //-- - - local handler = bufferqueue -- saves a table ^_^ - - handler.dispatch = function( ) - return dispatch - end - handler.disconnect = function( ) - return disconnect - end - handler.setlistener = function( self, listeners ) - dispatch = listeners.onincoming - disconnect = listeners.ondisconnect - status = listeners.onstatus - drain = listeners.ondrain - end - handler.getstats = function( ) - return readtraffic, sendtraffic - end - handler.ssl = function( ) - return ssl - end - handler.sslctx = function ( ) - return sslctx - end - handler.send = function( _, data, i, j ) - return send( socket, data, i, j ) - end - handler.receive = function( pattern, prefix ) - return receive( socket, pattern, prefix ) - end - handler.shutdown = function( pattern ) - return shutdown( socket, pattern ) - end - handler.setoption = function (self, option, value) - if socket.setoption then - return socket:setoption(option, value); - end - return false, "setoption not implemented"; - end - handler.close = function( self, forced ) - if not handler then return true; end - _readlistlen = removesocket( _readlist, socket, _readlistlen ) - _readtimes[ handler ] = nil - if bufferqueuelen ~= 0 then - if not ( forced or fatalerror ) then - handler.sendbuffer( ) - if bufferqueuelen ~= 0 then -- try again... - if handler then - handler.write = nil -- ... but no further writing allowed - end - toclose = true - return false - end - else - send( socket, table_concat( bufferqueue, "", 1, bufferqueuelen ), 1, bufferlen ) -- forced send - end - end - if socket then - _ = shutdown and shutdown( socket ) - socket:close( ) - _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) - _socketlist[ socket ] = nil - socket = nil - else - out_put "server.lua: socket already closed" - end - if handler then - _writetimes[ handler ] = nil - _closelist[ handler ] = nil - handler = nil - end - if server then - server.remove( ) - end - out_put "server.lua: closed client handler and removed socket from list" - return true - end - handler.ip = function( ) - return ip - end - handler.serverport = function( ) - return serverport - end - handler.clientport = function( ) - return clientport - end - local write = function( self, data ) - bufferlen = bufferlen + string_len( data ) - if bufferlen > maxsendlen then - _closelist[ handler ] = "send buffer exceeded" -- cannot close the client at the moment, have to wait to the end of the cycle - handler.write = idfalse -- dont write anymore - return false - elseif socket and not _sendlist[ socket ] then - _sendlistlen = addsocket(_sendlist, socket, _sendlistlen) - end - bufferqueuelen = bufferqueuelen + 1 - bufferqueue[ bufferqueuelen ] = data - if handler then - _writetimes[ handler ] = _writetimes[ handler ] or _currenttime - end - return true - end - handler.write = write - handler.bufferqueue = function( self ) - return bufferqueue - end - handler.socket = function( self ) - return socket - end - handler.set_mode = function( self, new ) - pattern = new or pattern - return pattern - end - handler.set_send = function ( self, newsend ) - send = newsend or send - return send - end - handler.bufferlen = function( self, readlen, sendlen ) - maxsendlen = sendlen or maxsendlen - maxreadlen = readlen or maxreadlen - return bufferlen, maxreadlen, maxsendlen - end - --TODO: Deprecate - handler.lock_read = function (self, switch) - if switch == true then - local tmp = _readlistlen - _readlistlen = removesocket( _readlist, socket, _readlistlen ) - _readtimes[ handler ] = nil - if _readlistlen ~= tmp then - noread = true - end - elseif switch == false then - if noread then - noread = false - _readlistlen = addsocket(_readlist, socket, _readlistlen) - _readtimes[ handler ] = _currenttime - end - end - return noread - end - handler.pause = function (self) - return self:lock_read(true); - end - handler.resume = function (self) - return self:lock_read(false); - end - handler.lock = function( self, switch ) - handler.lock_read (switch) - if switch == true then - handler.write = idfalse - local tmp = _sendlistlen - _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) - _writetimes[ handler ] = nil - if _sendlistlen ~= tmp then - nosend = true - end - elseif switch == false then - handler.write = write - if nosend then - nosend = false - write( "" ) - end - end - return noread, nosend - end - local _readbuffer = function( ) -- this function reads data - local buffer, err, part = receive( socket, pattern ) -- receive buffer with "pattern" - if not err or (err == "wantread" or err == "timeout") then -- received something - local buffer = buffer or part or "" - local len = string_len( buffer ) - if len > maxreadlen then - disconnect( handler, "receive buffer exceeded" ) - handler:close( true ) - return false - end - local count = len * STAT_UNIT - readtraffic = readtraffic + count - _readtraffic = _readtraffic + count - _readtimes[ handler ] = _currenttime - --out_put( "server.lua: read data '", buffer:gsub("[^%w%p ]", "."), "', error: ", err ) - return dispatch( handler, buffer, err ) - else -- connections was closed or fatal error - out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " read error: ", tostring(err) ) - fatalerror = true - disconnect( handler, err ) - _ = handler and handler:close( ) - return false - end - end - local _sendbuffer = function( ) -- this function sends data - local succ, err, byte, buffer, count; - local count; - if socket then - buffer = table_concat( bufferqueue, "", 1, bufferqueuelen ) - succ, err, byte = send( socket, buffer, 1, bufferlen ) - count = ( succ or byte or 0 ) * STAT_UNIT - sendtraffic = sendtraffic + count - _sendtraffic = _sendtraffic + count - _ = _cleanqueue and clean( bufferqueue ) - --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) ) - else - succ, err, count = false, "closed", 0; - end - if succ then -- sending succesful - bufferqueuelen = 0 - bufferlen = 0 - _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) -- delete socket from writelist - _writetimes[ handler ] = nil - if drain then - drain(handler) - end - _ = needtls and handler:starttls(nil) - _ = toclose and handler:close( ) - return true - elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write - buffer = string_sub( buffer, byte + 1, bufferlen ) -- new buffer - bufferqueue[ 1 ] = buffer -- insert new buffer in queue - bufferqueuelen = 1 - bufferlen = bufferlen - byte - _writetimes[ handler ] = _currenttime - return true - else -- connection was closed during sending or fatal error - out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " write error: ", tostring(err) ) - fatalerror = true - disconnect( handler, err ) - _ = handler and handler:close( ) - return false - end - end - - -- Set the sslctx - local handshake; - function handler.set_sslctx(self, new_sslctx) - ssl = true - sslctx = new_sslctx; - local wrote - local read - handshake = coroutine_wrap( function( client ) -- create handshake coroutine - local err - for i = 1, _maxsslhandshake do - _sendlistlen = ( wrote and removesocket( _sendlist, client, _sendlistlen ) ) or _sendlistlen - _readlistlen = ( read and removesocket( _readlist, client, _readlistlen ) ) or _readlistlen - read, wrote = nil, nil - _, err = client:dohandshake( ) - if not err then - out_put( "server.lua: ssl handshake done" ) - handler.readbuffer = _readbuffer -- when handshake is done, replace the handshake function with regular functions - handler.sendbuffer = _sendbuffer - _ = status and status( handler, "ssl-handshake-complete" ) - _readlistlen = addsocket(_readlist, client, _readlistlen) - return true - else - if err == "wantwrite" and not wrote then - _sendlistlen = addsocket(_sendlist, client, _sendlistlen) - wrote = true - elseif err == "wantread" and not read then - _readlistlen = addsocket(_readlist, client, _readlistlen) - read = true - else - out_put( "server.lua: ssl handshake error: ", tostring(err) ) - break; - end - --coroutine_yield( handler, nil, err ) -- handshake not finished - coroutine_yield( ) - end - end - disconnect( handler, "ssl handshake failed" ) - _ = handler and handler:close( true ) -- forced disconnect - return false -- handshake failed - end - ) - end - if luasec then - if sslctx then -- ssl? - handler:set_sslctx(sslctx); - out_put("server.lua: ", "starting ssl handshake") - local err - socket, err = ssl_wrap( socket, sslctx ) -- wrap socket - if err then - out_put( "server.lua: ssl error: ", tostring(err) ) - --mem_free( ) - return nil, nil, err -- fatal error - end - socket:settimeout( 0 ) - handler.readbuffer = handshake - handler.sendbuffer = handshake - handshake( socket ) -- do handshake - if not socket then - return nil, nil, "ssl handshake failed"; - end - else - local sslctx; - handler.starttls = function( self, _sslctx) - if _sslctx then - sslctx = _sslctx; - handler:set_sslctx(sslctx); - end - if bufferqueuelen > 0 then - out_put "server.lua: we need to do tls, but delaying until send buffer empty" - needtls = true - return - end - out_put( "server.lua: attempting to start tls on " .. tostring( socket ) ) - local oldsocket, err = socket - socket, err = ssl_wrap( socket, sslctx ) -- wrap socket - --out_put( "server.lua: sslwrapped socket is " .. tostring( socket ) ) - if err then - out_put( "server.lua: error while starting tls on client: ", tostring(err) ) - return nil, err -- fatal error - end - - socket:settimeout( 0 ) - - -- add the new socket to our system - - send = socket.send - receive = socket.receive - shutdown = id - - _socketlist[ socket ] = handler - _readlistlen = addsocket(_readlist, socket, _readlistlen) - - -- remove traces of the old socket - - _readlistlen = removesocket( _readlist, oldsocket, _readlistlen ) - _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen ) - _socketlist[ oldsocket ] = nil - - handler.starttls = nil - needtls = nil - - -- Secure now - ssl = true - - handler.readbuffer = handshake - handler.sendbuffer = handshake - handshake( socket ) -- do handshake - end - handler.readbuffer = _readbuffer - handler.sendbuffer = _sendbuffer - end - else - handler.readbuffer = _readbuffer - handler.sendbuffer = _sendbuffer - end - send = socket.send - receive = socket.receive - shutdown = ( ssl and id ) or socket.shutdown - - _socketlist[ socket ] = handler - _readlistlen = addsocket(_readlist, socket, _readlistlen) - return handler, socket -end - -id = function( ) -end - -idfalse = function( ) - return false -end - -addsocket = function( list, socket, len ) - if not list[ socket ] then - len = len + 1 - list[ len ] = socket - list[ socket ] = len - end - return len; -end - -removesocket = function( list, socket, len ) -- this function removes sockets from a list ( copied from copas ) - local pos = list[ socket ] - if pos then - list[ socket ] = nil - local last = list[ len ] - list[ len ] = nil - if last ~= socket then - list[ last ] = pos - list[ pos ] = last - end - return len - 1 - end - return len -end - -closesocket = function( socket ) - _sendlistlen = removesocket( _sendlist, socket, _sendlistlen ) - _readlistlen = removesocket( _readlist, socket, _readlistlen ) - _socketlist[ socket ] = nil - socket:close( ) - --mem_free( ) -end - -local function link(sender, receiver, buffersize) - local sender_locked; - local _sendbuffer = receiver.sendbuffer; - function receiver.sendbuffer() - _sendbuffer(); - if sender_locked and receiver.bufferlen() < buffersize then - sender:lock_read(false); -- Unlock now - sender_locked = nil; - end - end - - local _readbuffer = sender.readbuffer; - function sender.readbuffer() - _readbuffer(); - if not sender_locked and receiver.bufferlen() >= buffersize then - sender_locked = true; - sender:lock_read(true); - end - end -end - -----------------------------------// PUBLIC //-- - -addserver = function( addr, port, listeners, pattern, sslctx ) -- this function provides a way for other scripts to reg a server - local err - if type( listeners ) ~= "table" then - err = "invalid listener table" - end - if type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then - err = "invalid port" - elseif _server[ addr..":"..port ] then - err = "listeners on '[" .. addr .. "]:" .. port .. "' already exist" - elseif sslctx and not luasec then - err = "luasec not found" - end - if err then - out_error( "server.lua, [", addr, "]:", port, ": ", err ) - return nil, err - end - addr = addr or "*" - local server, err = socket_bind( addr, port ) - if err then - out_error( "server.lua, [", addr, "]:", port, ": ", err ) - return nil, err - end - local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, _maxclientsperserver ) -- wrap new server socket - if not handler then - server:close( ) - return nil, err - end - server:settimeout( 0 ) - _readlistlen = addsocket(_readlist, server, _readlistlen) - _server[ addr..":"..port ] = handler - _socketlist[ server ] = handler - out_put( "server.lua: new "..(sslctx and "ssl " or "").."server listener on '[", addr, "]:", port, "'" ) - return handler -end - -getserver = function ( addr, port ) - return _server[ addr..":"..port ]; -end - -removeserver = function( addr, port ) - local handler = _server[ addr..":"..port ] - if not handler then - return nil, "no server found on '[" .. addr .. "]:" .. tostring( port ) .. "'" - end - handler:close( ) - _server[ addr..":"..port ] = nil - return true -end - -closeall = function( ) - for _, handler in pairs( _socketlist ) do - handler:close( ) - _socketlist[ _ ] = nil - end - _readlistlen = 0 - _sendlistlen = 0 - _timerlistlen = 0 - _server = { } - _readlist = { } - _sendlist = { } - _timerlist = { } - _socketlist = { } - --mem_free( ) -end - -getsettings = function( ) - return _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver, _maxsslhandshake -end - -changesettings = function( new ) - if type( new ) ~= "table" then - return nil, "invalid settings table" - end - _selecttimeout = tonumber( new.timeout ) or _selecttimeout - _sleeptime = tonumber( new.sleeptime ) or _sleeptime - _maxsendlen = tonumber( new.maxsendlen ) or _maxsendlen - _maxreadlen = tonumber( new.maxreadlen ) or _maxreadlen - _checkinterval = tonumber( new.checkinterval ) or _checkinterval - _sendtimeout = tonumber( new.sendtimeout ) or _sendtimeout - _readtimeout = tonumber( new.readtimeout ) or _readtimeout - _cleanqueue = new.cleanqueue - _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver - _maxsslhandshake = new._maxsslhandshake or _maxsslhandshake - return true -end - -addtimer = function( listener ) - if type( listener ) ~= "function" then - return nil, "invalid listener function" - end - _timerlistlen = _timerlistlen + 1 - _timerlist[ _timerlistlen ] = listener - return true -end - -stats = function( ) - return _readtraffic, _sendtraffic, _readlistlen, _sendlistlen, _timerlistlen -end - -local quitting; - -setquitting = function (quit) - quitting = not not quit; -end - -loop = function(once) -- this is the main loop of the program - if quitting then return "quitting"; end - if once then quitting = "once"; end - local next_timer_time = math_huge; - repeat - local read, write, err = socket_select( _readlist, _sendlist, math_min(_selecttimeout, next_timer_time) ) - for i, socket in ipairs( write ) do -- send data waiting in writequeues - local handler = _socketlist[ socket ] - if handler then - handler.sendbuffer( ) - else - closesocket( socket ) - out_put "server.lua: found no handler and closed socket (writelist)" -- this should not happen - end - end - for i, socket in ipairs( read ) do -- receive data - local handler = _socketlist[ socket ] - if handler then - handler.readbuffer( ) - else - closesocket( socket ) - out_put "server.lua: found no handler and closed socket (readlist)" -- this can happen - end - end - for handler, err in pairs( _closelist ) do - handler.disconnect( )( handler, err ) - handler:close( true ) -- forced disconnect - end - clean( _closelist ) - _currenttime = luasocket_gettime( ) - if _currenttime - _timer >= math_min(next_timer_time, 1) then - next_timer_time = math_huge; - for i = 1, _timerlistlen do - local t = _timerlist[ i ]( _currenttime ) -- fire timers - if t then next_timer_time = math_min(next_timer_time, t); end - end - _timer = _currenttime - else - next_timer_time = next_timer_time - (_currenttime - _timer); - end - socket_sleep( _sleeptime ) -- wait some time - --collectgarbage( ) - until quitting; - if once and quitting == "once" then quitting = nil; return; end - return "quitting" -end - -step = function () - return loop(true); -end - -local function get_backend() - return "select"; -end - ---// EXPERIMENTAL //-- - -local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx ) - local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx ) - _socketlist[ socket ] = handler - _sendlistlen = addsocket(_sendlist, socket, _sendlistlen) - if listeners.onconnect then - -- When socket is writeable, call onconnect - local _sendbuffer = handler.sendbuffer; - handler.sendbuffer = function () - handler.sendbuffer = _sendbuffer; - listeners.onconnect(handler); - -- If there was data with the incoming packet, handle it now. - if #handler:bufferqueue() > 0 then - return _sendbuffer(); - end - end - end - return handler, socket -end - -local addclient = function( address, port, listeners, pattern, sslctx ) - local client, err = luasocket.tcp( ) - if err then - return nil, err - end - client:settimeout( 0 ) - _, err = client:connect( address, port ) - if err then -- try again - local handler = wrapclient( client, address, port, listeners ) - else - wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx ) - end -end - ---// EXPERIMENTAL //-- - -----------------------------------// BEGIN //-- - -use "setmetatable" ( _socketlist, { __mode = "k" } ) -use "setmetatable" ( _readtimes, { __mode = "k" } ) -use "setmetatable" ( _writetimes, { __mode = "k" } ) - -_timer = luasocket_gettime( ) -_starttime = luasocket_gettime( ) - -addtimer( function( ) - local difftime = os_difftime( _currenttime - _starttime ) - if difftime > _checkinterval then - _starttime = _currenttime - for handler, timestamp in pairs( _writetimes ) do - if os_difftime( _currenttime - timestamp ) > _sendtimeout then - --_writetimes[ handler ] = nil - handler.disconnect( )( handler, "send timeout" ) - handler:close( true ) -- forced disconnect - end - end - for handler, timestamp in pairs( _readtimes ) do - if os_difftime( _currenttime - timestamp ) > _readtimeout then - --_readtimes[ handler ] = nil - handler.disconnect( )( handler, "read timeout" ) - handler:close( ) -- forced disconnect? - end - end - end - end -) - -local function setlogger(new_logger) - local old_logger = log; - if new_logger then - log = new_logger; - end - return old_logger; -end - -----------------------------------// PUBLIC INTERFACE //-- - -return { - - addclient = addclient, - wrapclient = wrapclient, - - loop = loop, - link = link, - step = step, - stats = stats, - closeall = closeall, - addtimer = addtimer, - addserver = addserver, - getserver = getserver, - setlogger = setlogger, - getsettings = getsettings, - setquitting = setquitting, - removeserver = removeserver, - get_backend = get_backend, - changesettings = changesettings, -} - end) -package.preload['util.xmppstream'] = (function (...) --- 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. --- - - -local lxp = require "lxp"; -local st = require "util.stanza"; -local stanza_mt = st.stanza_mt; - -local tostring = tostring; -local t_insert = table.insert; -local t_concat = table.concat; -local t_remove = table.remove; -local setmetatable = setmetatable; - -local default_log = require "util.logger".init("xmppstream"); - --- COMPAT: w/LuaExpat 1.1.0 -local lxp_supports_doctype = pcall(lxp.new, { StartDoctypeDecl = false }); - -if not lxp_supports_doctype then - default_log("warn", "The version of LuaExpat on your system leaves Prosody " - .."vulnerable to denial-of-service attacks. You should upgrade to " - .."LuaExpat 1.1.1 or higher as soon as possible. See " - .."http://prosody.im/doc/depends#luaexpat for more information."); -end - -local error = error; - -module "xmppstream" - -local new_parser = lxp.new; - -local ns_prefixes = { - ["http://www.w3.org/XML/1998/namespace"] = "xml"; -}; - -local xmlns_streams = "http://etherx.jabber.org/streams"; - -local ns_separator = "\1"; -local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$"; - -_M.ns_separator = ns_separator; -_M.ns_pattern = ns_pattern; - -function new_sax_handlers(session, stream_callbacks) - local xml_handlers = {}; - - local log = session.log or default_log; - - local cb_streamopened = stream_callbacks.streamopened; - local cb_streamclosed = stream_callbacks.streamclosed; - local cb_error = stream_callbacks.error or function(session, e) error("XML stream error: "..tostring(e)); end; - local cb_handlestanza = stream_callbacks.handlestanza; - - local stream_ns = stream_callbacks.stream_ns or xmlns_streams; - local stream_tag = stream_callbacks.stream_tag or "stream"; - if stream_ns ~= "" then - stream_tag = stream_ns..ns_separator..stream_tag; - end - local stream_error_tag = stream_ns..ns_separator..(stream_callbacks.error_tag or "error"); - - local stream_default_ns = stream_callbacks.default_ns; - - local stack = {}; - local chardata, stanza = {}; - local non_streamns_depth = 0; - function xml_handlers:StartElement(tagname, attr) - if stanza and #chardata > 0 then - -- We have some character data in the buffer - t_insert(stanza, t_concat(chardata)); - chardata = {}; - end - local curr_ns,name = tagname:match(ns_pattern); - if name == "" then - curr_ns, name = "", curr_ns; - end - - if curr_ns ~= stream_default_ns or non_streamns_depth > 0 then - attr.xmlns = curr_ns; - non_streamns_depth = non_streamns_depth + 1; - end - - -- FIXME !!!!! - for i=1,#attr do - local k = attr[i]; - attr[i] = nil; - local ns, nm = k:match(ns_pattern); - if nm ~= "" then - ns = ns_prefixes[ns]; - if ns then - attr[ns..":"..nm] = attr[k]; - attr[k] = nil; - end - end - end - - if not stanza then --if we are not currently inside a stanza - if session.notopen then - if tagname == stream_tag then - non_streamns_depth = 0; - if cb_streamopened then - cb_streamopened(session, attr); - end - else - -- Garbage before stream? - cb_error(session, "no-stream"); - end - return; - end - if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then - cb_error(session, "invalid-top-level-element"); - end - - stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt); - else -- we are inside a stanza, so add a tag - t_insert(stack, stanza); - local oldstanza = stanza; - stanza = setmetatable({ name = name, attr = attr, tags = {} }, stanza_mt); - t_insert(oldstanza, stanza); - t_insert(oldstanza.tags, stanza); - end - end - function xml_handlers:CharacterData(data) - if stanza then - t_insert(chardata, data); - end - end - function xml_handlers:EndElement(tagname) - if non_streamns_depth > 0 then - non_streamns_depth = non_streamns_depth - 1; - end - if stanza then - if #chardata > 0 then - -- We have some character data in the buffer - t_insert(stanza, t_concat(chardata)); - chardata = {}; - end - -- Complete stanza - if #stack == 0 then - if tagname ~= stream_error_tag then - cb_handlestanza(session, stanza); - else - cb_error(session, "stream-error", stanza); - end - stanza = nil; - else - stanza = t_remove(stack); - end - else - if tagname == stream_tag then - if cb_streamclosed then - cb_streamclosed(session); - end - else - local curr_ns,name = tagname:match(ns_pattern); - if name == "" then - curr_ns, name = "", curr_ns; - end - cb_error(session, "parse-error", "unexpected-element-close", name); - end - stanza, chardata = nil, {}; - stack = {}; - end - end - - local function restricted_handler(parser) - cb_error(session, "parse-error", "restricted-xml", "Restricted XML, see RFC 6120 section 11.1."); - if not parser.stop or not parser:stop() then - error("Failed to abort parsing"); - end - end - - if lxp_supports_doctype then - xml_handlers.StartDoctypeDecl = restricted_handler; - end - xml_handlers.Comment = restricted_handler; - xml_handlers.ProcessingInstruction = restricted_handler; - - local function reset() - stanza, chardata = nil, {}; - stack = {}; - end - - local function set_session(stream, new_session) - session = new_session; - log = new_session.log or default_log; - end - - return xml_handlers, { reset = reset, set_session = set_session }; -end - -function new(session, stream_callbacks) - local handlers, meta = new_sax_handlers(session, stream_callbacks); - local parser = new_parser(handlers, ns_separator); - local parse = parser.parse; - - return { - reset = function () - parser = new_parser(handlers, ns_separator); - parse = parser.parse; - meta.reset(); - end, - feed = function (self, data) - return parse(parser, data); - end, - set_session = meta.set_session; - }; -end - -return _M; - end) -package.preload['util.jid'] = (function (...) --- 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. --- - - - -local match = string.match; -local nodeprep = require "util.encodings".stringprep.nodeprep; -local nameprep = require "util.encodings".stringprep.nameprep; -local resourceprep = require "util.encodings".stringprep.resourceprep; - -module "jid" - -local function _split(jid) - if not jid then return; end - local node, nodepos = match(jid, "^([^@/]+)@()"); - local host, hostpos = match(jid, "^([^@/]+)()", nodepos) - if node and not host then return nil, nil, nil; end - local resource = match(jid, "^/(.+)$", hostpos); - if (not host) or ((not resource) and #jid >= hostpos) then return nil, nil, nil; end - return node, host, resource; -end -split = _split; - -function bare(jid) - local node, host = _split(jid); - if node and host then - return node.."@"..host; - end - return host; -end - -local function _prepped_split(jid) - local node, host, resource = _split(jid); - if host then - host = nameprep(host); - if not host then return; end - if node then - node = nodeprep(node); - if not node then return; end - end - if resource then - resource = resourceprep(resource); - if not resource then return; end - end - return node, host, resource; - end -end -prepped_split = _prepped_split; - -function prep(jid) - local node, host, resource = _prepped_split(jid); - if host then - if node then - host = node .. "@" .. host; - end - if resource then - host = host .. "/" .. resource; - end - end - return host; -end - -function join(node, host, resource) - if node and host and resource then - return node.."@"..host.."/"..resource; - elseif node and host then - return node.."@"..host; - elseif host and resource then - return host.."/"..resource; - elseif host then - return host; - end - return nil; -- Invalid JID -end - -function compare(jid, acl) - -- compare jid to single acl rule - -- TODO compare to table of rules? - local jid_node, jid_host, jid_resource = _split(jid); - local acl_node, acl_host, acl_resource = _split(acl); - if ((acl_node ~= nil and acl_node == jid_node) or acl_node == nil) and - ((acl_host ~= nil and acl_host == jid_host) or acl_host == nil) and - ((acl_resource ~= nil and acl_resource == jid_resource) or acl_resource == nil) then - return true - end - return false -end - -return _M; - end) -package.preload['util.events'] = (function (...) --- 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. --- - - -local pairs = pairs; -local t_insert = table.insert; -local t_sort = table.sort; -local setmetatable = setmetatable; -local next = next; - -module "events" - -function new() - local handlers = {}; - local event_map = {}; - local function _rebuild_index(handlers, event) - local _handlers = event_map[event]; - if not _handlers or next(_handlers) == nil then return; end - local index = {}; - for handler in pairs(_handlers) do - t_insert(index, handler); - end - t_sort(index, function(a, b) return _handlers[a] > _handlers[b]; end); - handlers[event] = index; - return index; - end; - setmetatable(handlers, { __index = _rebuild_index }); - local function add_handler(event, handler, priority) - local map = event_map[event]; - if map then - map[handler] = priority or 0; - else - map = {[handler] = priority or 0}; - event_map[event] = map; - end - handlers[event] = nil; - end; - local function remove_handler(event, handler) - local map = event_map[event]; - if map then - map[handler] = nil; - handlers[event] = nil; - if next(map) == nil then - event_map[event] = nil; - end - end - end; - local function add_handlers(handlers) - for event, handler in pairs(handlers) do - add_handler(event, handler); - end - end; - local function remove_handlers(handlers) - for event, handler in pairs(handlers) do - remove_handler(event, handler); - end - end; - local function fire_event(event, ...) - local h = handlers[event]; - if h then - for i=1,#h do - local ret = h[i](...); - if ret ~= nil then return ret; end - end - end - end; - return { - add_handler = add_handler; - remove_handler = remove_handler; - add_handlers = add_handlers; - remove_handlers = remove_handlers; - fire_event = fire_event; - _handlers = handlers; - _event_map = event_map; - }; -end - -return _M; - end) -package.preload['util.dataforms'] = (function (...) --- 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. --- - -local setmetatable = setmetatable; -local pairs, ipairs = pairs, ipairs; -local tostring, type = tostring, type; -local t_concat = table.concat; -local st = require "util.stanza"; - -module "dataforms" - -local xmlns_forms = 'jabber:x:data'; - -local form_t = {}; -local form_mt = { __index = form_t }; - -function new(layout) - return setmetatable(layout, form_mt); -end - -function form_t.form(layout, data, formtype) - local form = st.stanza("x", { xmlns = xmlns_forms, type = formtype or "form" }); - if layout.title then - form:tag("title"):text(layout.title):up(); - end - if layout.instructions then - form:tag("instructions"):text(layout.instructions):up(); - end - for n, field in ipairs(layout) do - local field_type = field.type or "text-single"; - -- Add field tag - form:tag("field", { type = field_type, var = field.name, label = field.label }); - - local value = (data and data[field.name]) or field.value; - - if value then - -- Add value, depending on type - if field_type == "hidden" then - if type(value) == "table" then - -- Assume an XML snippet - form:tag("value") - :add_child(value) - :up(); - else - form:tag("value"):text(tostring(value)):up(); - end - elseif field_type == "boolean" then - form:tag("value"):text((value and "1") or "0"):up(); - elseif field_type == "fixed" then - - elseif field_type == "jid-multi" then - for _, jid in ipairs(value) do - form:tag("value"):text(jid):up(); - end - elseif field_type == "jid-single" then - form:tag("value"):text(value):up(); - elseif field_type == "text-single" or field_type == "text-private" then - form:tag("value"):text(value):up(); - elseif field_type == "text-multi" then - -- Split into multiple <value> tags, one for each line - for line in value:gmatch("([^\r\n]+)\r?\n*") do - form:tag("value"):text(line):up(); - end - elseif field_type == "list-single" then - local has_default = false; - if type(value) == "string" then - form:tag("value"):text(value):up(); - else - for _, val in ipairs(value) do - if type(val) == "table" then - form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up(); - if val.default and (not has_default) then - form:tag("value"):text(val.value):up(); - has_default = true; - end - else - form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up(); - end - end - end - elseif field_type == "list-multi" then - for _, val in ipairs(value) do - if type(val) == "table" then - form:tag("option", { label = val.label }):tag("value"):text(val.value):up():up(); - if val.default then - form:tag("value"):text(val.value):up(); - end - else - form:tag("option", { label= val }):tag("value"):text(tostring(val)):up():up(); - end - end - end - end - - if field.required then - form:tag("required"):up(); - end - - -- Jump back up to list of fields - form:up(); - end - return form; -end - -local field_readers = {}; - -function form_t.data(layout, stanza) - local data = {}; - - for field_tag in stanza:childtags() do - local field_type; - for n, field in ipairs(layout) do - if field.name == field_tag.attr.var then - field_type = field.type; - break; - end - end - - local reader = field_readers[field_type]; - if reader then - data[field_tag.attr.var] = reader(field_tag); - end - - end - return data; -end - -field_readers["text-single"] = - function (field_tag) - local value = field_tag:child_with_name("value"); - if value then - return value[1]; - end - end - -field_readers["text-private"] = - field_readers["text-single"]; - -field_readers["jid-single"] = - field_readers["text-single"]; - -field_readers["jid-multi"] = - function (field_tag) - local result = {}; - for value_tag in field_tag:childtags() do - if value_tag.name == "value" then - result[#result+1] = value_tag[1]; - end - end - return result; - end - -field_readers["text-multi"] = - function (field_tag) - local result = {}; - for value_tag in field_tag:childtags() do - if value_tag.name == "value" then - result[#result+1] = value_tag[1]; - end - end - return t_concat(result, "\n"); - end - -field_readers["list-single"] = - field_readers["text-single"]; - -field_readers["list-multi"] = - function (field_tag) - local result = {}; - for value_tag in field_tag:childtags() do - if value_tag.name == "value" then - result[#result+1] = value_tag[1]; - end - end - return result; - end - -field_readers["boolean"] = - function (field_tag) - local value = field_tag:child_with_name("value"); - if value then - if value[1] == "1" or value[1] == "true" then - return true; - else - return false; - end - end - end - -field_readers["hidden"] = - function (field_tag) - local value = field_tag:child_with_name("value"); - if value then - return value[1]; - end - end - -return _M; - - ---[=[ - -Layout: -{ - - title = "MUC Configuration", - instructions = [[Use this form to configure options for this MUC room.]], - - { name = "FORM_TYPE", type = "hidden", required = true }; - { name = "field-name", type = "field-type", required = false }; -} - - ---]=] - end) -package.preload['util.serialization'] = (function (...) --- 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. --- - -local string_rep = string.rep; -local type = type; -local tostring = tostring; -local t_insert = table.insert; -local t_concat = table.concat; -local error = error; -local pairs = pairs; -local next = next; - -local loadstring = loadstring; -local setfenv = setfenv; -local pcall = pcall; - -local debug_traceback = debug.traceback; -local log = require "util.logger".init("serialization"); -module "serialization" - -local indent = function(i) - return string_rep("\t", i); -end -local function basicSerialize (o) - if type(o) == "number" or type(o) == "boolean" then - -- no need to check for NaN, as that's not a valid table index - if o == 1/0 then return "(1/0)"; - elseif o == -1/0 then return "(-1/0)"; - else return tostring(o); end - else -- assume it is a string -- FIXME make sure it's a string. throw an error otherwise. - return (("%q"):format(tostring(o)):gsub("\\\n", "\\n")); - end -end -local function _simplesave(o, ind, t, func) - if type(o) == "number" then - if o ~= o then func(t, "(0/0)"); - elseif o == 1/0 then func(t, "(1/0)"); - elseif o == -1/0 then func(t, "(-1/0)"); - else func(t, tostring(o)); end - elseif type(o) == "string" then - func(t, (("%q"):format(o):gsub("\\\n", "\\n"))); - elseif type(o) == "table" then - if next(o) ~= nil then - func(t, "{\n"); - for k,v in pairs(o) do - func(t, indent(ind)); - func(t, "["); - func(t, basicSerialize(k)); - func(t, "] = "); - if ind == 0 then - _simplesave(v, 0, t, func); - else - _simplesave(v, ind+1, t, func); - end - func(t, ";\n"); - end - func(t, indent(ind-1)); - func(t, "}"); - else - func(t, "{}"); - end - elseif type(o) == "boolean" then - func(t, (o and "true" or "false")); - else - log("error", "cannot serialize a %s: %s", type(o), debug_traceback()) - func(t, "nil"); - end -end - -function append(t, o) - _simplesave(o, 1, t, t.write or t_insert); - return t; -end - -function serialize(o) - return t_concat(append({}, o)); -end - -function deserialize(str) - if type(str) ~= "string" then return nil; end - str = "return "..str; - local f, err = loadstring(str, "@data"); - if not f then return nil, err; end - setfenv(f, {}); - local success, ret = pcall(f); - if not success then return nil, ret; end - return ret; -end - -return _M; - end) -package.preload['verse.plugins.presence'] = (function (...) -function verse.plugins.presence(stream) - stream.last_presence = nil; - - stream:hook("presence-out", function (presence) - if not presence.attr.to then - stream.last_presence = presence; -- Cache non-directed presence - end - end, 1); - - function stream:resend_presence() - if last_presence then - stream:send(last_presence); - end - end - - function stream:set_status(opts) - local p = verse.presence(); - if type(opts) == "table" then - if opts.show then - p:tag("show"):text(opts.show):up(); - end - if opts.prio then - p:tag("priority"):text(tostring(opts.prio)):up(); - end - if opts.msg then - p:tag("status"):text(opts.msg):up(); - end - end - -- TODO maybe use opts as prio if it's a int, - -- or as show or status if it's a string? - - stream:send(p); - end -end - end) -package.preload['verse.plugins.groupchat'] = (function (...) -local events = require "events"; - -local room_mt = {}; -room_mt.__index = room_mt; - -local xmlns_delay = "urn:xmpp:delay"; -local xmlns_muc = "http://jabber.org/protocol/muc"; - -function verse.plugins.groupchat(stream) - stream:add_plugin("presence") - stream.rooms = {}; - - stream:hook("stanza", function (stanza) - local room_jid = jid.bare(stanza.attr.from); - if not room_jid then return end - local room = stream.rooms[room_jid] - if not room and stanza.attr.to and room_jid then - room = stream.rooms[stanza.attr.to.." "..room_jid] - end - if room and room.opts.source and stanza.attr.to ~= room.opts.source then return end - if room then - local nick = select(3, jid.split(stanza.attr.from)); - local body = stanza:get_child("body"); - local delay = stanza:get_child("delay", xmlns_delay); - local event = { - room_jid = room_jid; - room = room; - sender = room.occupants[nick]; - nick = nick; - body = (body and body:get_text()) or nil; - stanza = stanza; - delay = (delay and delay.attr.stamp); - }; - local ret = room:event(stanza.name, event); - return ret or (stanza.name == "message") or nil; - end - end, 500); - - function stream:join_room(jid, nick, opts) - if not nick then - return false, "no nickname supplied" - end - opts = opts or {}; - local room = setmetatable({ - stream = stream, jid = jid, nick = nick, - subject = nil, - occupants = {}, - opts = opts, - events = events.new() - }, room_mt); - if opts.source then - self.rooms[opts.source.." "..jid] = room; - else - self.rooms[jid] = room; - end - local occupants = room.occupants; - room:hook("presence", function (presence) - local nick = presence.nick or nick; - if not occupants[nick] and presence.stanza.attr.type ~= "unavailable" then - occupants[nick] = { - nick = nick; - jid = presence.stanza.attr.from; - presence = presence.stanza; - }; - local x = presence.stanza:get_child("x", xmlns_muc .. "#user"); - if x then - local x_item = x:get_child("item"); - if x_item and x_item.attr then - occupants[nick].real_jid = x_item.attr.jid; - occupants[nick].affiliation = x_item.attr.affiliation; - occupants[nick].role = x_item.attr.role; - end - --TODO Check for status 100? - end - if nick == room.nick then - room.stream:event("groupchat/joined", room); - else - room:event("occupant-joined", occupants[nick]); - end - elseif occupants[nick] and presence.stanza.attr.type == "unavailable" then - if nick == room.nick then - room.stream:event("groupchat/left", room); - if room.opts.source then - self.rooms[room.opts.source.." "..jid] = nil; - else - self.rooms[jid] = nil; - end - else - occupants[nick].presence = presence.stanza; - room:event("occupant-left", occupants[nick]); - occupants[nick] = nil; - end - end - end); - room:hook("message", function(msg) - local subject = msg.stanza:get_child_text("subject"); - if not subject then return end - subject = #subject > 0 and subject or nil; - if subject ~= room.subject then - local old_subject = room.subject; - room.subject = subject; - return self:event("subject-changed", { from = old_subject, to = subject, by = msg.sender }); - end - end, 2000); - local join_st = verse.presence():tag("x",{xmlns = xmlns_muc}):reset(); - self:event("pre-groupchat/joining", join_st); - room:send(join_st) - self:event("groupchat/joining", room); - return room; - end - - stream:hook("presence-out", function(presence) - if not presence.attr.to then - for _, room in pairs(stream.rooms) do - room:send(presence); - end - presence.attr.to = nil; - end - end); -end - -function room_mt:send(stanza) - if stanza.name == "message" and not stanza.attr.type then - stanza.attr.type = "groupchat"; - end - if stanza.name == "presence" then - stanza.attr.to = self.jid .."/"..self.nick; - end - if stanza.attr.type == "groupchat" or not stanza.attr.to then - stanza.attr.to = self.jid; - end - if self.opts.source then - stanza.attr.from = self.opts.source - end - self.stream:send(stanza); -end - -function room_mt:send_message(text) - self:send(verse.message():tag("body"):text(text)); -end - -function room_mt:set_subject(text) - self:send(verse.message():tag("subject"):text(text)); -end - -function room_mt:leave(message) - self.stream:event("groupchat/leaving", self); - self:send(verse.presence({type="unavailable"})); -end - -function room_mt:admin_set(nick, what, value, reason) - self:send(verse.iq({type="set"}) - :query(xmlns_muc .. "#admin") - :tag("item", {nick = nick, [what] = value}) - :tag("reason"):text(reason or "")); -end - -function room_mt:set_role(nick, role, reason) - self:admin_set(nick, "role", role, reason); -end - -function room_mt:set_affiliation(nick, affiliation, reason) - self:admin_set(nick, "affiliation", affiliation, reason); -end - -function room_mt:kick(nick, reason) - self:set_role(nick, "none", reason); -end - -function room_mt:ban(nick, reason) - self:set_affiliation(nick, "outcast", reason); -end - -function room_mt:event(name, arg) - self.stream:debug("Firing room event: %s", name); - return self.events.fire_event(name, arg); -end - -function room_mt:hook(name, callback, priority) - return self.events.add_handler(name, callback, priority); -end - end) -package.preload['net.httpclient_listener'] = (function (...) --- 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. --- - -local log = require "util.logger".init("httpclient_listener"); - -local connlisteners_register = require "net.connlisteners".register; - -local requests = {}; -- Open requests -local buffers = {}; -- Buffers of partial lines - -local httpclient = { default_port = 80, default_mode = "*a" }; - -function httpclient.onincoming(conn, data) - local request = requests[conn]; - - if not request then - log("warn", "Received response from connection %s with no request attached!", tostring(conn)); - return; - end - - if data and request.reader then - request:reader(data); - end -end - -function httpclient.ondisconnect(conn, err) - local request = requests[conn]; - if request and err ~= "closed" then - request:reader(nil); - end - requests[conn] = nil; -end - -function httpclient.register_request(conn, req) - log("debug", "Attaching request %s to connection %s", tostring(req.id or req), tostring(conn)); - requests[conn] = req; -end - -connlisteners_register("httpclient", httpclient); - end) -package.preload['net.connlisteners'] = (function (...) --- 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. --- - - - -local listeners_dir = (CFG_SOURCEDIR or ".").."/net/"; -local server = require "net.server"; -local log = require "util.logger".init("connlisteners"); -local tostring = tostring; -local type = type -local ipairs = ipairs - -local dofile, xpcall, error = - dofile, xpcall, error - -local debug_traceback = debug.traceback; - -module "connlisteners" - -local listeners = {}; - -function register(name, listener) - if listeners[name] and listeners[name] ~= listener then - log("debug", "Listener %s is already registered, not registering any more", name); - return false; - end - listeners[name] = listener; - log("debug", "Registered connection listener %s", name); - return true; -end - -function deregister(name) - listeners[name] = nil; -end - -function get(name) - local h = listeners[name]; - if not h then - local ok, ret = xpcall(function() dofile(listeners_dir..name:gsub("[^%w%-]", "_").."_listener.lua") end, debug_traceback); - if not ok then - log("error", "Error while loading listener '%s': %s", tostring(name), tostring(ret)); - return nil, ret; - end - h = listeners[name]; - end - return h; -end - -function start(name, udata) - local h, err = get(name); - if not h then - error("No such connection module: "..name.. (err and (" ("..err..")") or ""), 0); - end - - local interfaces = (udata and udata.interface) or h.default_interface or "*"; - if type(interfaces) == "string" then interfaces = {interfaces}; end - local port = (udata and udata.port) or h.default_port or error("Can't start listener "..name.." because no port was specified, and it has no default port", 0); - local mode = (udata and udata.mode) or h.default_mode or 1; - local ssl = (udata and udata.ssl) or nil; - local autossl = udata and udata.type == "ssl"; - - if autossl and not ssl then - return nil, "no ssl context"; - end - - ok, err = true, {}; - for _, interface in ipairs(interfaces) do - local handler - handler, err[interface] = server.addserver(interface, port, h, mode, autossl and ssl or nil); - ok = ok and handler; - end - - return ok, err; -end - -return _M; - end) -package.preload['util.httpstream'] = (function (...) - -local coroutine = coroutine; -local tonumber = tonumber; - -local deadroutine = coroutine.create(function() end); -coroutine.resume(deadroutine); - -module("httpstream") - -local function parser(success_cb, parser_type, options_cb) - local data = coroutine.yield(); - local function readline() - local pos = data:find("\r\n", nil, true); - while not pos do - data = data..coroutine.yield(); - pos = data:find("\r\n", nil, true); - end - local r = data:sub(1, pos-1); - data = data:sub(pos+2); - return r; - end - local function readlength(n) - while #data < n do - data = data..coroutine.yield(); - end - local r = data:sub(1, n); - data = data:sub(n + 1); - return r; - end - local function readheaders() - local headers = {}; -- read headers - while true do - local line = readline(); - if line == "" then break; end -- headers done - local key, val = line:match("^([^%s:]+): *(.*)$"); - if not key then coroutine.yield("invalid-header-line"); end -- TODO handle multi-line and invalid headers - key = key:lower(); - headers[key] = headers[key] and headers[key]..","..val or val; - end - return headers; - end - - if not parser_type or parser_type == "server" then - while true do - -- read status line - local status_line = readline(); - local method, path, httpversion = status_line:match("^(%S+)%s+(%S+)%s+HTTP/(%S+)$"); - if not method then coroutine.yield("invalid-status-line"); end - path = path:gsub("^//+", "/"); -- TODO parse url more - local headers = readheaders(); - - -- read body - local len = tonumber(headers["content-length"]); - len = len or 0; -- TODO check for invalid len - local body = readlength(len); - - success_cb({ - method = method; - path = path; - httpversion = httpversion; - headers = headers; - body = body; - }); - end - elseif parser_type == "client" then - while true do - -- read status line - local status_line = readline(); - local httpversion, status_code, reason_phrase = status_line:match("^HTTP/(%S+)%s+(%d%d%d)%s+(.*)$"); - status_code = tonumber(status_code); - if not status_code then coroutine.yield("invalid-status-line"); end - local headers = readheaders(); - - -- read body - local have_body = not - ( (options_cb and options_cb().method == "HEAD") - or (status_code == 204 or status_code == 304 or status_code == 301) - or (status_code >= 100 and status_code < 200) ); - - local body; - if have_body then - local len = tonumber(headers["content-length"]); - if headers["transfer-encoding"] == "chunked" then - body = ""; - while true do - local chunk_size = readline():match("^%x+"); - if not chunk_size then coroutine.yield("invalid-chunk-size"); end - chunk_size = tonumber(chunk_size, 16) - if chunk_size == 0 then break; end - body = body..readlength(chunk_size); - if readline() ~= "" then coroutine.yield("invalid-chunk-ending"); end - end - local trailers = readheaders(); - elseif len then -- TODO check for invalid len - body = readlength(len); - else -- read to end - repeat - local newdata = coroutine.yield(); - data = data..newdata; - until newdata == ""; - body, data = data, ""; - end - end - - success_cb({ - code = status_code; - httpversion = httpversion; - headers = headers; - body = body; - -- COMPAT the properties below are deprecated - responseversion = httpversion; - responseheaders = headers; - }); - end - else coroutine.yield("unknown-parser-type"); end -end - -function new(success_cb, error_cb, parser_type, options_cb) - local co = coroutine.create(parser); - coroutine.resume(co, success_cb, parser_type, options_cb) - return { - feed = function(self, data) - if not data then - if parser_type == "client" then coroutine.resume(co, ""); end - co = deadroutine; - return error_cb(); - end - local success, result = coroutine.resume(co, data); - if result then - co = deadroutine; - return error_cb(result); - end - end; - }; -end - -return _M; - end) -package.preload['net.http'] = (function (...) --- 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. --- - - -local socket = require "socket" -local mime = require "mime" -local url = require "socket.url" -local httpstream_new = require "util.httpstream".new; - -local server = require "net.server" - -local connlisteners_get = require "net.connlisteners".get; -local listener = connlisteners_get("httpclient") or error("No httpclient listener!"); - -local t_insert, t_concat = table.insert, table.concat; -local pairs, ipairs = pairs, ipairs; -local tonumber, tostring, xpcall, select, debug_traceback, char, format = - tonumber, tostring, xpcall, select, debug.traceback, string.char, string.format; - -local log = require "util.logger".init("http"); - -module "http" - -function urlencode(s) return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end)); end -function urldecode(s) return s and (s:gsub("%%(%x%x)", function (c) return char(tonumber(c,16)); end)); end - -local function _formencodepart(s) - return s and (s:gsub("%W", function (c) - if c ~= " " then - return format("%%%02x", c:byte()); - else - return "+"; - end - end)); -end -function formencode(form) - local result = {}; - for _, field in ipairs(form) do - t_insert(result, _formencodepart(field.name).."=".._formencodepart(field.value)); - end - return t_concat(result, "&"); -end - -local function request_reader(request, data, startpos) - if not request.parser then - local function success_cb(r) - if request.callback then - for k,v in pairs(r) do request[k] = v; end - request.callback(r.body, r.code, request); - request.callback = nil; - end - destroy_request(request); - end - local function error_cb(r) - if request.callback then - request.callback(r or "connection-closed", 0, request); - request.callback = nil; - end - destroy_request(request); - end - local function options_cb() - return request; - end - request.parser = httpstream_new(success_cb, error_cb, "client", options_cb); - end - request.parser:feed(data); -end - -local function handleerr(err) log("error", "Traceback[http]: %s: %s", tostring(err), debug_traceback()); end -function request(u, ex, callback) - local req = url.parse(u); - - if not (req and req.host) then - callback(nil, 0, req); - return nil, "invalid-url"; - end - - if not req.path then - req.path = "/"; - end - - local custom_headers, body; - local default_headers = { ["Host"] = req.host, ["User-Agent"] = "Prosody XMPP Server" } - - - if req.userinfo then - default_headers["Authorization"] = "Basic "..mime.b64(req.userinfo); - end - - if ex then - custom_headers = ex.headers; - req.onlystatus = ex.onlystatus; - body = ex.body; - if body then - req.method = "POST "; - default_headers["Content-Length"] = tostring(#body); - default_headers["Content-Type"] = "application/x-www-form-urlencoded"; - end - if ex.method then req.method = ex.method; end - end - - req.handler, req.conn = server.wrapclient(socket.tcp(), req.host, req.port or 80, listener, "*a"); - req.write = function (...) return req.handler:write(...); end - req.conn:settimeout(0); - local ok, err = req.conn:connect(req.host, req.port or 80); - if not ok and err ~= "timeout" then - callback(nil, 0, req); - return nil, err; - end - - local request_line = { req.method or "GET", " ", req.path, " HTTP/1.1\r\n" }; - - if req.query then - t_insert(request_line, 4, "?"); - t_insert(request_line, 5, req.query); - end - - req.write(t_concat(request_line)); - local t = { [2] = ": ", [4] = "\r\n" }; - if custom_headers then - for k, v in pairs(custom_headers) do - t[1], t[3] = k, v; - req.write(t_concat(t)); - default_headers[k] = nil; - end - end - - for k, v in pairs(default_headers) do - t[1], t[3] = k, v; - req.write(t_concat(t)); - default_headers[k] = nil; - end - req.write("\r\n"); - - if body then - req.write(body); - end - - req.callback = function (content, code, request) log("debug", "Calling callback, status %s", code or "---"); return select(2, xpcall(function () return callback(content, code, request) end, handleerr)); end - req.reader = request_reader; - req.state = "status"; - - listener.register_request(req.handler, req); - - return req; -end - -function destroy_request(request) - if request.conn then - request.conn = nil; - request.handler:close() - listener.ondisconnect(request.handler, "closed"); - end -end - -_M.urlencode = urlencode; - -return _M; - end) -package.preload['verse.bosh'] = (function (...) - -local new_xmpp_stream = require "util.xmppstream".new; -local st = require "util.stanza"; -require "net.httpclient_listener"; -- Required for net.http to work -local http = require "net.http"; - -local stream_mt = setmetatable({}, { __index = verse.stream_mt }); -stream_mt.__index = stream_mt; - -local xmlns_stream = "http://etherx.jabber.org/streams"; -local xmlns_bosh = "http://jabber.org/protocol/httpbind"; - -local reconnect_timeout = 5; - -function verse.new_bosh(logger, url) - local stream = { - bosh_conn_pool = {}; - bosh_waiting_requests = {}; - bosh_rid = math.random(1,999999); - bosh_outgoing_buffer = {}; - bosh_url = url; - conn = {}; - }; - function stream:reopen() - self.bosh_need_restart = true; - self:flush(); - end - local conn = verse.new(logger, stream); - return setmetatable(conn, stream_mt); -end - -function stream_mt:connect() - self:_send_session_request(); -end - -function stream_mt:send(data) - self:debug("Putting into BOSH send buffer: %s", tostring(data)); - self.bosh_outgoing_buffer[#self.bosh_outgoing_buffer+1] = st.clone(data); - self:flush(); --TODO: Optimize by doing this on next tick (give a chance for data to buffer) -end - -function stream_mt:flush() - if self.connected - and #self.bosh_waiting_requests < self.bosh_max_requests - and (#self.bosh_waiting_requests == 0 - or #self.bosh_outgoing_buffer > 0 - or self.bosh_need_restart) then - self:debug("Flushing..."); - local payload = self:_make_body(); - local buffer = self.bosh_outgoing_buffer; - for i, stanza in ipairs(buffer) do - payload:add_child(stanza); - buffer[i] = nil; - end - self:_make_request(payload); - else - self:debug("Decided not to flush."); - end -end - -function stream_mt:_make_request(payload) - local request, err = http.request(self.bosh_url, { body = tostring(payload) }, function (response, code, request) - if code ~= 0 then - self.inactive_since = nil; - return self:_handle_response(response, code, request); - end - - -- Connection issues, we need to retry this request - local time = os.time(); - if not self.inactive_since then - self.inactive_since = time; -- So we know when it is time to give up - elseif time - 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 - (time - self.inactive_since), reconnect_timeout); - end - - -- Set up reconnect timer - timer.add_task(reconnect_timeout, function () - self:debug("Retrying request..."); - -- Remove old request - for i, waiting_request in ipairs(self.bosh_waiting_requests) do - if waiting_request == request then - table.remove(self.bosh_waiting_requests, i); - break; - end - end - self:_make_request(payload); - end); - end); - if request then - table.insert(self.bosh_waiting_requests, request); - else - self:warn("Request failed instantly: %s", err); - end -end - -function stream_mt:_disconnected() - self.connected = nil; - self:event("disconnected"); -end - -function stream_mt:_send_session_request() - local body = self:_make_body(); - - -- XEP-0124 - body.attr.hold = "1"; - body.attr.wait = "60"; - body.attr["xml:lang"] = "en"; - body.attr.ver = "1.6"; - - -- XEP-0206 - body.attr.from = self.jid; - body.attr.to = self.host; - body.attr.secure = 'true'; - - http.request(self.bosh_url, { body = tostring(body) }, function (response, code) - if code == 0 then - -- Failed to connect - return self:_disconnected(); - end - -- Handle session creation response - local payload = self:_parse_response(response) - if not payload then - self:warn("Invalid session creation response"); - self:_disconnected(); - return; - end - self.bosh_sid = payload.attr.sid; -- Session id - self.bosh_wait = tonumber(payload.attr.wait); -- How long the server may hold connections for - self.bosh_hold = tonumber(payload.attr.hold); -- How many connections the server may hold - self.bosh_max_inactivity = tonumber(payload.attr.inactivity); -- Max amount of time with no connections - self.bosh_max_requests = tonumber(payload.attr.requests) or self.bosh_hold; -- Max simultaneous requests we can make - self.connected = true; - self:event("connected"); - self:_handle_response_payload(payload); - end); -end - -function stream_mt:_handle_response(response, code, request) - if self.bosh_waiting_requests[1] ~= request then - self:warn("Server replied to request that wasn't the oldest"); - for i, waiting_request in ipairs(self.bosh_waiting_requests) do - if waiting_request == request then - self.bosh_waiting_requests[i] = nil; - break; - end - end - else - table.remove(self.bosh_waiting_requests, 1); - end - local payload = self:_parse_response(response); - if payload then - self:_handle_response_payload(payload); - end - self:flush(); -end - -function stream_mt:_handle_response_payload(payload) - for stanza in payload:childtags() do - if stanza.attr.xmlns == xmlns_stream then - self:event("stream-"..stanza.name, stanza); - elseif stanza.attr.xmlns then - self:event("stream/"..stanza.attr.xmlns, stanza); - else - self:event("stanza", stanza); - end - end - if payload.attr.type == "terminate" then - self:_disconnected({reason = payload.attr.condition}); - end -end - -local stream_callbacks = { - stream_ns = "http://jabber.org/protocol/httpbind", stream_tag = "body", - default_ns = "jabber:client", - streamopened = function (session, attr) session.notopen = nil; session.payload = verse.stanza("body", attr); return true; end; - handlestanza = function (session, stanza) session.payload:add_child(stanza); end; -}; -function stream_mt:_parse_response(response) - self:debug("Parsing response: %s", response); - if response == nil then - self:debug("%s", debug.traceback()); - self:_disconnected(); - return; - end - local session = { notopen = true, log = self.log }; - local stream = new_xmpp_stream(session, stream_callbacks); - stream:feed(response); - return session.payload; -end - -function stream_mt:_make_body() - self.bosh_rid = self.bosh_rid + 1; - local body = verse.stanza("body", { - xmlns = xmlns_bosh; - 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; - body.attr.restart = 'true'; - end - return body; -end - end) -package.preload['bit'] = (function (...) --- 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. --- - - -local type = type; -local tonumber = tonumber; -local setmetatable = setmetatable; -local error = error; -local tostring = tostring; -local print = print; - -local xor_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=0;[18]=3;[19]=2;[20]=5;[21]=4;[22]=7;[23]=6;[24]=9;[25]=8;[26]=11;[27]=10;[28]=13;[29]=12;[30]=15;[31]=14;[32]=2;[33]=3;[34]=0;[35]=1;[36]=6;[37]=7;[38]=4;[39]=5;[40]=10;[41]=11;[42]=8;[43]=9;[44]=14;[45]=15;[46]=12;[47]=13;[48]=3;[49]=2;[50]=1;[51]=0;[52]=7;[53]=6;[54]=5;[55]=4;[56]=11;[57]=10;[58]=9;[59]=8;[60]=15;[61]=14;[62]=13;[63]=12;[64]=4;[65]=5;[66]=6;[67]=7;[68]=0;[69]=1;[70]=2;[71]=3;[72]=12;[73]=13;[74]=14;[75]=15;[76]=8;[77]=9;[78]=10;[79]=11;[80]=5;[81]=4;[82]=7;[83]=6;[84]=1;[85]=0;[86]=3;[87]=2;[88]=13;[89]=12;[90]=15;[91]=14;[92]=9;[93]=8;[94]=11;[95]=10;[96]=6;[97]=7;[98]=4;[99]=5;[100]=2;[101]=3;[102]=0;[103]=1;[104]=14;[105]=15;[106]=12;[107]=13;[108]=10;[109]=11;[110]=8;[111]=9;[112]=7;[113]=6;[114]=5;[115]=4;[116]=3;[117]=2;[118]=1;[119]=0;[120]=15;[121]=14;[122]=13;[123]=12;[124]=11;[125]=10;[126]=9;[127]=8;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=0;[137]=1;[138]=2;[139]=3;[140]=4;[141]=5;[142]=6;[143]=7;[144]=9;[145]=8;[146]=11;[147]=10;[148]=13;[149]=12;[150]=15;[151]=14;[152]=1;[153]=0;[154]=3;[155]=2;[156]=5;[157]=4;[158]=7;[159]=6;[160]=10;[161]=11;[162]=8;[163]=9;[164]=14;[165]=15;[166]=12;[167]=13;[168]=2;[169]=3;[170]=0;[171]=1;[172]=6;[173]=7;[174]=4;[175]=5;[176]=11;[177]=10;[178]=9;[179]=8;[180]=15;[181]=14;[182]=13;[183]=12;[184]=3;[185]=2;[186]=1;[187]=0;[188]=7;[189]=6;[190]=5;[191]=4;[192]=12;[193]=13;[194]=14;[195]=15;[196]=8;[197]=9;[198]=10;[199]=11;[200]=4;[201]=5;[202]=6;[203]=7;[204]=0;[205]=1;[206]=2;[207]=3;[208]=13;[209]=12;[210]=15;[211]=14;[212]=9;[213]=8;[214]=11;[215]=10;[216]=5;[217]=4;[218]=7;[219]=6;[220]=1;[221]=0;[222]=3;[223]=2;[224]=14;[225]=15;[226]=12;[227]=13;[228]=10;[229]=11;[230]=8;[231]=9;[232]=6;[233]=7;[234]=4;[235]=5;[236]=2;[237]=3;[238]=0;[239]=1;[240]=15;[241]=14;[242]=13;[243]=12;[244]=11;[245]=10;[246]=9;[247]=8;[248]=7;[249]=6;[250]=5;[251]=4;[252]=3;[253]=2;[254]=1;[255]=0;}; -local or_map = {[0]=0;[1]=1;[2]=2;[3]=3;[4]=4;[5]=5;[6]=6;[7]=7;[8]=8;[9]=9;[10]=10;[11]=11;[12]=12;[13]=13;[14]=14;[15]=15;[16]=1;[17]=1;[18]=3;[19]=3;[20]=5;[21]=5;[22]=7;[23]=7;[24]=9;[25]=9;[26]=11;[27]=11;[28]=13;[29]=13;[30]=15;[31]=15;[32]=2;[33]=3;[34]=2;[35]=3;[36]=6;[37]=7;[38]=6;[39]=7;[40]=10;[41]=11;[42]=10;[43]=11;[44]=14;[45]=15;[46]=14;[47]=15;[48]=3;[49]=3;[50]=3;[51]=3;[52]=7;[53]=7;[54]=7;[55]=7;[56]=11;[57]=11;[58]=11;[59]=11;[60]=15;[61]=15;[62]=15;[63]=15;[64]=4;[65]=5;[66]=6;[67]=7;[68]=4;[69]=5;[70]=6;[71]=7;[72]=12;[73]=13;[74]=14;[75]=15;[76]=12;[77]=13;[78]=14;[79]=15;[80]=5;[81]=5;[82]=7;[83]=7;[84]=5;[85]=5;[86]=7;[87]=7;[88]=13;[89]=13;[90]=15;[91]=15;[92]=13;[93]=13;[94]=15;[95]=15;[96]=6;[97]=7;[98]=6;[99]=7;[100]=6;[101]=7;[102]=6;[103]=7;[104]=14;[105]=15;[106]=14;[107]=15;[108]=14;[109]=15;[110]=14;[111]=15;[112]=7;[113]=7;[114]=7;[115]=7;[116]=7;[117]=7;[118]=7;[119]=7;[120]=15;[121]=15;[122]=15;[123]=15;[124]=15;[125]=15;[126]=15;[127]=15;[128]=8;[129]=9;[130]=10;[131]=11;[132]=12;[133]=13;[134]=14;[135]=15;[136]=8;[137]=9;[138]=10;[139]=11;[140]=12;[141]=13;[142]=14;[143]=15;[144]=9;[145]=9;[146]=11;[147]=11;[148]=13;[149]=13;[150]=15;[151]=15;[152]=9;[153]=9;[154]=11;[155]=11;[156]=13;[157]=13;[158]=15;[159]=15;[160]=10;[161]=11;[162]=10;[163]=11;[164]=14;[165]=15;[166]=14;[167]=15;[168]=10;[169]=11;[170]=10;[171]=11;[172]=14;[173]=15;[174]=14;[175]=15;[176]=11;[177]=11;[178]=11;[179]=11;[180]=15;[181]=15;[182]=15;[183]=15;[184]=11;[185]=11;[186]=11;[187]=11;[188]=15;[189]=15;[190]=15;[191]=15;[192]=12;[193]=13;[194]=14;[195]=15;[196]=12;[197]=13;[198]=14;[199]=15;[200]=12;[201]=13;[202]=14;[203]=15;[204]=12;[205]=13;[206]=14;[207]=15;[208]=13;[209]=13;[210]=15;[211]=15;[212]=13;[213]=13;[214]=15;[215]=15;[216]=13;[217]=13;[218]=15;[219]=15;[220]=13;[221]=13;[222]=15;[223]=15;[224]=14;[225]=15;[226]=14;[227]=15;[228]=14;[229]=15;[230]=14;[231]=15;[232]=14;[233]=15;[234]=14;[235]=15;[236]=14;[237]=15;[238]=14;[239]=15;[240]=15;[241]=15;[242]=15;[243]=15;[244]=15;[245]=15;[246]=15;[247]=15;[248]=15;[249]=15;[250]=15;[251]=15;[252]=15;[253]=15;[254]=15;[255]=15;}; -local and_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=0;[9]=0;[10]=0;[11]=0;[12]=0;[13]=0;[14]=0;[15]=0;[16]=0;[17]=1;[18]=0;[19]=1;[20]=0;[21]=1;[22]=0;[23]=1;[24]=0;[25]=1;[26]=0;[27]=1;[28]=0;[29]=1;[30]=0;[31]=1;[32]=0;[33]=0;[34]=2;[35]=2;[36]=0;[37]=0;[38]=2;[39]=2;[40]=0;[41]=0;[42]=2;[43]=2;[44]=0;[45]=0;[46]=2;[47]=2;[48]=0;[49]=1;[50]=2;[51]=3;[52]=0;[53]=1;[54]=2;[55]=3;[56]=0;[57]=1;[58]=2;[59]=3;[60]=0;[61]=1;[62]=2;[63]=3;[64]=0;[65]=0;[66]=0;[67]=0;[68]=4;[69]=4;[70]=4;[71]=4;[72]=0;[73]=0;[74]=0;[75]=0;[76]=4;[77]=4;[78]=4;[79]=4;[80]=0;[81]=1;[82]=0;[83]=1;[84]=4;[85]=5;[86]=4;[87]=5;[88]=0;[89]=1;[90]=0;[91]=1;[92]=4;[93]=5;[94]=4;[95]=5;[96]=0;[97]=0;[98]=2;[99]=2;[100]=4;[101]=4;[102]=6;[103]=6;[104]=0;[105]=0;[106]=2;[107]=2;[108]=4;[109]=4;[110]=6;[111]=6;[112]=0;[113]=1;[114]=2;[115]=3;[116]=4;[117]=5;[118]=6;[119]=7;[120]=0;[121]=1;[122]=2;[123]=3;[124]=4;[125]=5;[126]=6;[127]=7;[128]=0;[129]=0;[130]=0;[131]=0;[132]=0;[133]=0;[134]=0;[135]=0;[136]=8;[137]=8;[138]=8;[139]=8;[140]=8;[141]=8;[142]=8;[143]=8;[144]=0;[145]=1;[146]=0;[147]=1;[148]=0;[149]=1;[150]=0;[151]=1;[152]=8;[153]=9;[154]=8;[155]=9;[156]=8;[157]=9;[158]=8;[159]=9;[160]=0;[161]=0;[162]=2;[163]=2;[164]=0;[165]=0;[166]=2;[167]=2;[168]=8;[169]=8;[170]=10;[171]=10;[172]=8;[173]=8;[174]=10;[175]=10;[176]=0;[177]=1;[178]=2;[179]=3;[180]=0;[181]=1;[182]=2;[183]=3;[184]=8;[185]=9;[186]=10;[187]=11;[188]=8;[189]=9;[190]=10;[191]=11;[192]=0;[193]=0;[194]=0;[195]=0;[196]=4;[197]=4;[198]=4;[199]=4;[200]=8;[201]=8;[202]=8;[203]=8;[204]=12;[205]=12;[206]=12;[207]=12;[208]=0;[209]=1;[210]=0;[211]=1;[212]=4;[213]=5;[214]=4;[215]=5;[216]=8;[217]=9;[218]=8;[219]=9;[220]=12;[221]=13;[222]=12;[223]=13;[224]=0;[225]=0;[226]=2;[227]=2;[228]=4;[229]=4;[230]=6;[231]=6;[232]=8;[233]=8;[234]=10;[235]=10;[236]=12;[237]=12;[238]=14;[239]=14;[240]=0;[241]=1;[242]=2;[243]=3;[244]=4;[245]=5;[246]=6;[247]=7;[248]=8;[249]=9;[250]=10;[251]=11;[252]=12;[253]=13;[254]=14;[255]=15;} - -local not_map = {[0]=15;[1]=14;[2]=13;[3]=12;[4]=11;[5]=10;[6]=9;[7]=8;[8]=7;[9]=6;[10]=5;[11]=4;[12]=3;[13]=2;[14]=1;[15]=0;}; -local rshift1_map = {[0]=0;[1]=0;[2]=1;[3]=1;[4]=2;[5]=2;[6]=3;[7]=3;[8]=4;[9]=4;[10]=5;[11]=5;[12]=6;[13]=6;[14]=7;[15]=7;}; -local rshift1carry_map = {[0]=0;[1]=8;[2]=0;[3]=8;[4]=0;[5]=8;[6]=0;[7]=8;[8]=0;[9]=8;[10]=0;[11]=8;[12]=0;[13]=8;[14]=0;[15]=8;}; -local lshift1_map = {[0]=0;[1]=2;[2]=4;[3]=6;[4]=8;[5]=10;[6]=12;[7]=14;[8]=0;[9]=2;[10]=4;[11]=6;[12]=8;[13]=10;[14]=12;[15]=14;}; -local lshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=1;[9]=1;[10]=1;[11]=1;[12]=1;[13]=1;[14]=1;[15]=1;}; -local arshift1carry_map = {[0]=0;[1]=0;[2]=0;[3]=0;[4]=0;[5]=0;[6]=0;[7]=0;[8]=8;[9]=8;[10]=8;[11]=8;[12]=8;[13]=8;[14]=8;[15]=8;}; - -module "bit" - -local bit_mt = {__tostring = function(t) return ("%x%x%x%x%x%x%x%x"):format(t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8]); end}; -local function do_bop(a, b, op) - return setmetatable({ - op[a[1]*16+b[1]]; - op[a[2]*16+b[2]]; - op[a[3]*16+b[3]]; - op[a[4]*16+b[4]]; - op[a[5]*16+b[5]]; - op[a[6]*16+b[6]]; - op[a[7]*16+b[7]]; - op[a[8]*16+b[8]]; - }, bit_mt); -end -local function do_uop(a, op) - return setmetatable({ - op[a[1]]; - op[a[2]]; - op[a[3]]; - op[a[4]]; - op[a[5]]; - op[a[6]]; - op[a[7]]; - op[a[8]]; - }, bit_mt); -end - -function bxor(a, b) return do_bop(a, b, xor_map); end -function bor(a, b) return do_bop(a, b, or_map); end -function band(a, b) return do_bop(a, b, and_map); end - -function bnot(a) return do_uop(a, not_map); end -local function _rshift1(t) - local carry = 0; - for i=1,8 do - local t_i = rshift1_map[t[i]] + carry; - carry = rshift1carry_map[t[i]]; - t[i] = t_i; - end -end -function rshift(a, i) - local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]}; - for n = 1,i do _rshift1(t); end - return setmetatable(t, bit_mt); -end -local function _arshift1(t) - local carry = arshift1carry_map[t[1]]; - for i=1,8 do - local t_i = rshift1_map[t[i]] + carry; - carry = rshift1carry_map[t[i]]; - t[i] = t_i; - end -end -function arshift(a, i) - local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]}; - for n = 1,i do _arshift1(t); end - return setmetatable(t, bit_mt); -end -local function _lshift1(t) - local carry = 0; - for i=8,1,-1 do - local t_i = lshift1_map[t[i]] + carry; - carry = lshift1carry_map[t[i]]; - t[i] = t_i; - end -end -function lshift(a, i) - local t = {a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]}; - for n = 1,i do _lshift1(t); end - return setmetatable(t, bit_mt); -end - -local function _cast(a) - if type(a) == "number" then a = ("%x"):format(a); - elseif type(a) == "table" then return a; - elseif type(a) ~= "string" then error("string expected, got "..type(a), 2); end - local t = {0,0,0,0,0,0,0,0}; - a = "00000000"..a; - a = a:sub(-8); - for i = 1,8 do - t[i] = tonumber(a:sub(i,i), 16) or error("Number format error", 2); - end - return setmetatable(t, bit_mt); -end - -local function wrap1(f) - return function(a, ...) - if type(a) ~= "table" then a = _cast(a); end - a = f(a, ...); - a = tonumber(tostring(a), 16); - if a > 0x7fffffff then a = a - 1 - 0xffffffff; end - return a; - end; -end -local function wrap2(f) - return function(a, b, ...) - if type(a) ~= "table" then a = _cast(a); end - if type(b) ~= "table" then b = _cast(b); end - a = f(a, b, ...); - a = tonumber(tostring(a), 16); - if a > 0x7fffffff then a = a - 1 - 0xffffffff; end - return a; - end; -end - -bxor = wrap2(bxor); -bor = wrap2(bor); -band = wrap2(band); -bnot = wrap1(bnot); -lshift = wrap1(lshift); -rshift = wrap1(rshift); -arshift = wrap1(arshift); -cast = wrap1(_cast); - -bits = 32; - -return _M; - end) -package.preload['verse.client'] = (function (...) -local verse = require "verse"; -local stream = verse.stream_mt; - -local jid_split = require "util.jid".split; -local adns = require "net.adns"; -local lxp = require "lxp"; -local st = require "util.stanza"; - --- Shortcuts to save having to load util.stanza -verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply = - st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply; - -local new_xmpp_stream = require "util.xmppstream".new; - -local xmlns_stream = "http://etherx.jabber.org/streams"; - -local function compare_srv_priorities(a,b) - return a.priority < b.priority or (a.priority == b.priority and a.weight > b.weight); -end - -local stream_callbacks = { - stream_ns = xmlns_stream, - stream_tag = "stream", - default_ns = "jabber:client" }; - -function stream_callbacks.streamopened(stream, attr) - stream.stream_id = attr.id; - if not stream:event("opened", attr) then - stream.notopen = nil; - end - return true; -end - -function stream_callbacks.streamclosed(stream) - return stream:event("closed"); -end - -function stream_callbacks.handlestanza(stream, stanza) - if stanza.attr.xmlns == xmlns_stream then - return stream:event("stream-"..stanza.name, stanza); - elseif stanza.attr.xmlns then - return stream:event("stream/"..stanza.attr.xmlns, stanza); - end - - return stream:event("stanza", stanza); -end - -function stream:reset() - if self.stream then - self.stream:reset(); - else - self.stream = new_xmpp_stream(self, stream_callbacks); - end - self.notopen = true; - return true; -end - -function stream:connect_client(jid, pass) - self.jid, self.password = jid, pass; - self.username, self.host, self.resource = jid_split(jid); - - -- Required XMPP features - self:add_plugin("tls"); - self:add_plugin("sasl"); - self:add_plugin("bind"); - self:add_plugin("session"); - - function self.data(conn, data) - local ok, err = self.stream:feed(data); - if ok then return; end - self:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data: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 (data) return self.data(self.conn, data); end); - - self.curr_id = 0; - - self.tracked_iqs = {}; - self:hook("stanza", function (stanza) - local id, type = stanza.attr.id, stanza.attr.type; - if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then - self.tracked_iqs[id](stanza); - self.tracked_iqs[id] = nil; - return true; - end - end); - - self:hook("stanza", function (stanza) - if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then - if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then - local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; - if xmlns then - ret = self:event("iq/"..xmlns, stanza); - if not ret then - ret = self:event("iq", stanza); - end - end - if ret == nil then - self:send(verse.error_reply(stanza, "cancel", "service-unavailable")); - return true; - end - else - ret = self:event(stanza.name, stanza); - end - end - return ret; - end, -1); - - self:hook("outgoing", function (data) - if data.name then - self:event("stanza-out", data); - end - end); - - self:hook("stanza-out", function (stanza) - if not stanza.attr.xmlns then - self:event(stanza.name.."-out", stanza); - end - end); - - local function stream_ready() - self:event("ready"); - end - self:hook("session-success", stream_ready, -1) - self:hook("bind-success", stream_ready, -1); - - local _base_close = self.close; - function self:close(reason) - if not self.notopen then - self:send("</stream:stream>"); - end - return _base_close(self); - end - - local function start_connect() - -- Initialise connection - self:connect(self.connect_host or self.host, self.connect_port or 5222); - end - - if not (self.connect_host or self.connect_port) then - -- Look up SRV records - adns.lookup(function (answer) - if answer then - local srv_hosts = {}; - self.srv_hosts = srv_hosts; - for _, record in ipairs(answer) do - table.insert(srv_hosts, record.srv); - end - table.sort(srv_hosts, compare_srv_priorities); - - local srv_choice = srv_hosts[1]; - self.srv_choice = 1; - if srv_choice then - self.connect_host, self.connect_port = srv_choice.target, srv_choice.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 srv_choice = srv_hosts[self.srv_choice]; - self.connect_host, self.connect_port = srv_choice.target, srv_choice.port; - start_connect(); - return true; - end - end, 1000); - - self:hook("connected", function () - self.srv_hosts = nil; - end, 1000); - end - start_connect(); - end, "_xmpp-client._tcp."..(self.host)..".", "SRV"); - else - start_connect(); - end -end - -function stream:reopen() - self:reset(); - self:send(st.stanza("stream:stream", { to = self.host, ["xmlns:stream"]='http://etherx.jabber.org/streams', - xmlns = "jabber:client", version = "1.0" }):top_tag()); -end - -function stream:send_iq(iq, callback) - local id = self:new_id(); - self.tracked_iqs[id] = callback; - iq.attr.id = id; - self:send(iq); -end - -function stream:new_id() - self.curr_id = self.curr_id + 1; - return tostring(self.curr_id); -end - end) -package.preload['verse.component'] = (function (...) -local verse = require "verse"; -local stream = verse.stream_mt; - -local jid_split = require "util.jid".split; -local lxp = require "lxp"; -local st = require "util.stanza"; -local sha1 = require "util.sha1".sha1; - --- Shortcuts to save having to load util.stanza -verse.message, verse.presence, verse.iq, verse.stanza, verse.reply, verse.error_reply = - st.message, st.presence, st.iq, st.stanza, st.reply, st.error_reply; - -local new_xmpp_stream = require "util.xmppstream".new; - -local xmlns_stream = "http://etherx.jabber.org/streams"; -local xmlns_component = "jabber:component:accept"; - -local stream_callbacks = { - stream_ns = xmlns_stream, - stream_tag = "stream", - default_ns = xmlns_component }; - -function stream_callbacks.streamopened(stream, attr) - stream.stream_id = attr.id; - if not stream:event("opened", attr) then - stream.notopen = nil; - end - return true; -end - -function stream_callbacks.streamclosed(stream) - return stream:event("closed"); -end - -function stream_callbacks.handlestanza(stream, stanza) - if stanza.attr.xmlns == xmlns_stream then - return stream:event("stream-"..stanza.name, stanza); - elseif stanza.attr.xmlns or stanza.name == "handshake" then - return stream:event("stream/"..(stanza.attr.xmlns or xmlns_component), stanza); - end - - return stream:event("stanza", stanza); -end - -function stream:reset() - if self.stream then - self.stream:reset(); - else - self.stream = new_xmpp_stream(self, stream_callbacks); - end - self.notopen = true; - return true; -end - -function stream:connect_component(jid, pass) - self.jid, self.password = jid, pass; - self.username, self.host, self.resource = jid_split(jid); - - function self.data(conn, data) - local ok, err = self.stream:feed(data); - if ok then return; end - stream:debug("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " ")); - stream:close("xml-not-well-formed"); - end - - self:hook("incoming-raw", function (data) return self.data(self.conn, data); end); - - self.curr_id = 0; - - self.tracked_iqs = {}; - self:hook("stanza", function (stanza) - local id, type = stanza.attr.id, stanza.attr.type; - if id and stanza.name == "iq" and (type == "result" or type == "error") and self.tracked_iqs[id] then - self.tracked_iqs[id](stanza); - self.tracked_iqs[id] = nil; - return true; - end - end); - - self:hook("stanza", function (stanza) - if stanza.attr.xmlns == nil or stanza.attr.xmlns == "jabber:client" then - if stanza.name == "iq" and (stanza.attr.type == "get" or stanza.attr.type == "set") then - local xmlns = stanza.tags[1] and stanza.tags[1].attr.xmlns; - if xmlns then - ret = self:event("iq/"..xmlns, stanza); - if not ret then - ret = self:event("iq", stanza); - end - end - if ret == nil then - self:send(verse.error_reply(stanza, "cancel", "service-unavailable")); - return true; - end - else - ret = self:event(stanza.name, stanza); - end - end - return ret; - end, -1); - - self:hook("opened", function (attr) - print(self.jid, self.stream_id, attr.id); - local token = sha1(self.stream_id..pass, true); - - self:send(st.stanza("handshake", { xmlns = xmlns_component }):text(token)); - self:hook("stream/"..xmlns_component, function (stanza) - if stanza.name == "handshake" then - self:event("authentication-success"); - end - end); - end); - - local function stream_ready() - self:event("ready"); - end - self:hook("authentication-success", stream_ready, -1); - - -- Initialise connection - self:connect(self.connect_host or self.host, self.connect_port or 5347); - self:reopen(); -end - -function stream:reopen() - self:reset(); - self:send(st.stanza("stream:stream", { to = self.host, ["xmlns:stream"]='http://etherx.jabber.org/streams', - xmlns = xmlns_component, version = "1.0" }):top_tag()); -end - -function stream:close(reason) - if not self.notopen then - self:send("</stream:stream>"); - end - local on_disconnect = self.conn.disconnect(); - self.conn:close(); - on_disconnect(conn, reason); -end - -function stream:send_iq(iq, callback) - local id = self:new_id(); - self.tracked_iqs[id] = callback; - iq.attr.id = id; - self:send(iq); -end - -function stream:new_id() - self.curr_id = self.curr_id + 1; - return tostring(self.curr_id); -end - end) - --- Use LuaRocks if available -pcall(require, "luarocks.require"); - --- Load LuaSec if available -pcall(require, "ssl"); - -local server = require "net.server"; -local events = require "util.events"; - -module("verse", package.seeall); -local verse = _M; -_M.server = server; - -local stream = {}; -stream.__index = stream; -stream_mt = stream; - -verse.plugins = {}; - -function verse.new(logger, base) - local t = setmetatable(base or {}, stream); - t.id = tostring(t):match("%x*$"); - t:set_logger(logger, true); - t.events = events.new(); - t.plugins = {}; - return t; -end - -verse.add_task = require "util.timer".add_task; - -verse.logger = logger.init; -verse.log = verse.logger("verse"); - -function verse.set_logger(logger) - verse.log = logger; - server.setlogger(logger); -end - -function verse.filter_log(levels, logger) - local level_set = {}; - for _, level in ipairs(levels) do - level_set[level] = true; - end - return function (level, name, ...) - if level_set[level] then - return logger(level, name, ...); - end - end; -end - -local function error_handler(err) - verse.log("error", "Error: %s", err); - verse.log("error", "Traceback: %s", debug.traceback()); -end - -function verse.set_error_handler(new_error_handler) - error_handler = new_error_handler; -end - -function verse.loop() - return xpcall(server.loop, error_handler); -end - -function verse.step() - return xpcall(server.step, error_handler); -end - -function verse.quit() - return server.setquitting(true); -end - -function stream:connect(connect_host, connect_port) - connect_host = connect_host or "localhost"; - connect_port = tonumber(connect_port) or 5222; - - -- Create and initiate connection - local conn = socket.tcp() - conn:settimeout(0); - local success, err = conn:connect(connect_host, connect_port); - - if not success and err ~= "timeout" then - self:warn("connect() to %s:%d failed: %s", connect_host, connect_port, err); - return self:event("disconnected", { reason = err }) or false, err; - end - - local conn = server.wrapclient(conn, connect_host, connect_port, new_listener(self), "*a"); - if not conn then - self:warn("connection initialisation failed: %s", err); - return self:event("disconnected", { reason = err }) or false, err; - end - - self.conn = conn; - self.send = function (stream, data) - self:event("outgoing", data); - data = tostring(data); - self:event("outgoing-raw", data); - return conn:write(data); - end; - return true; -end - -function stream:close() - if not self.conn then - verse.log("error", "Attempt to close disconnected connection - possibly a bug"); - return; - end - local on_disconnect = self.conn.disconnect(); - self.conn:close(); - on_disconnect(conn, reason); -end - --- Logging functions -function stream:debug(...) - if self.logger and self.log.debug then - return self.logger("debug", ...); - end -end - -function stream:warn(...) - if self.logger and self.log.warn then - return self.logger("warn", ...); - end -end - -function stream:error(...) - if self.logger and self.log.error then - return self.logger("error", ...); - end -end - -function stream:set_logger(logger, levels) - local old_logger = self.logger; - if logger then - self.logger = logger; - end - if levels then - if levels == true then - levels = { "debug", "info", "warn", "error" }; - end - self.log = {}; - for _, level in ipairs(levels) do - self.log[level] = true; - end - end - return old_logger; -end - -function stream_mt:set_log_levels(levels) - self:set_logger(nil, levels); -end - --- Event handling -function stream:event(name, ...) - self:debug("Firing event: "..tostring(name)); - return self.events.fire_event(name, ...); -end - -function stream:hook(name, ...) - return self.events.add_handler(name, ...); -end - -function stream:unhook(name, handler) - return self.events.remove_handler(name, handler); -end - -function verse.eventable(object) - object.events = events.new(); - object.hook, object.unhook = stream.hook, stream.unhook; - local fire_event = object.events.fire_event; - function object:event(name, ...) - return fire_event(name, ...); - end - return object; -end - -function stream:add_plugin(name) - if self.plugins[name] then return true; end - if require("verse.plugins."..name) then - local ok, err = verse.plugins[name](self); - if ok ~= false then - self:debug("Loaded %s plugin", name); - self.plugins[name] = true; - else - self:warn("Failed to load %s plugin: %s", name, err); - end - end - return self; -end - --- Listener factory -function new_listener(stream) - local conn_listener = {}; - - function conn_listener.onconnect(conn) - stream.connected = true; - stream:event("connected"); - end - - function conn_listener.onincoming(conn, data) - stream:event("incoming-raw", data); - end - - function conn_listener.ondisconnect(conn, err) - stream.connected = false; - stream:event("disconnected", { reason = err }); - end - - function conn_listener.ondrain(conn) - stream:event("drained"); - end - - function conn_listener.onstatus(conn, new_status) - stream:event("status", new_status); - end - - return conn_listener; -end - - -local log = require "util.logger".init("verse"); - -return verse; - end) -- README -- Squish verse into this dir, then squish them into one, which you move -- and rename to mod_ircd.lua in your prosody modules/plugins dir. @@ -5499,9 +181,6 @@ --FIXME PM's probably won't work end end); - room:hook("subject-changed", function(changed) - session.send((":%s TOPIC %s :%s"):format(changed.by, channel, changed.to or "")); - end); end c:hook("groupchat/joined", function(room) @@ -5561,23 +240,6 @@ end end -function commands.TOPIC(session, message) - if not message then return end - local channel, topic = message:match("^(%S+) :(.*)$"); - if not channel then - channel = message:match("^(%S+)"); - end - if not channel then return end - local room = session.rooms[channel]; - if topic then - room:set_subject(topic) - else - session.send((":%s TOPIC %s :%s"):format(session.host, channel, room.subject or "")); - -- first should be who set it, but verse doesn't provide that yet, so we'll - -- just say it was the server - end -end - function commands.PING(session, server) session.send(":"..session.host..": PONG "..server); end @@ -5612,16 +274,11 @@ --c:send(data) end -local function desetup() - require "net.connlisteners".deregister("irc"); -end - --c:hook("ready", function () require "net.connlisteners".register("irc", irc_listener); require "net.connlisteners".start("irc"); --end); -module:hook("module-unloaded", desetup) --print("Starting loop...") --verse.loop()
--- a/mod_ircd/squishy Mon Oct 24 00:20:51 2011 +0000 +++ b/mod_ircd/squishy Sat Oct 29 13:34:15 2011 +0200 @@ -1,6 +1,6 @@ -Output "mod_ircd.lua" +Output "mod_ircd.out.lua" -- In theory, you should be able to leave all but verses groupchat and presence plugins Module "verse" "verse.lua" -Main "mod_ircd.in.lua" +Main "mod_ircd.lua"
--- a/mod_pubsub_feed/mod_pubsub_feed.lua Mon Oct 24 00:20:51 2011 +0000 +++ b/mod_pubsub_feed/mod_pubsub_feed.lua Sat Oct 29 13:34:15 2011 +0200 @@ -38,33 +38,19 @@ local urldecode = http.urldecode; local urlencode = http.urlencode; -local feed_list = {}; -local refresh_interval; - --- Dynamicaly reloadable config. -local function update_config() local config = module:get_option("feeds") or { planet_jabber = "http://planet.jabber.org/atom.xml"; prosody_blog = "http://blog.prosody.im/feed/atom.xml"; }; - refresh_interval = module:get_option_number("feed_pull_interval", 15) * 60; - local new_feed_list; +local refresh_interval = module:get_option_number("feed_pull_interval", 15) * 60; +local use_pubsubhubub = module:get_option_boolean("use_pubsubhubub", true); -- HTTP by default or not? +local httphost = module:get_option_string("pubsubhubub_httphost", module.host); -- If module.host IN A doesn't point to this server, use this to override. +local feed_list = { } for node, url in pairs(config) do - local new_feed_list[node] = true; - if not feed_list[node] then feed_list[node] = { url = url; node = node; last_update = 0 }; - else - feed_list[node].url = url; end - end - for node in pairs(feed_list) do - if not new_feed_list[node] then - feed_list[node] = nil; - end - end -end -update_config(); -module:hook("config-reloaded", update_config); +-- TODO module:hook("config-reloaded", above loop); +-- Also, keeping it somewhere persistent in order to avoid duplicated publishes? -- Used to kill the timer local module_unloaded = false; @@ -72,13 +58,6 @@ module_unloaded = true; end --- Config stuff that can't be reloaded, since it would need to re-bind HTTP stuff. - --- If module.host IN A doesn't point to this server, use this to override. -local httphost = module:get_option_string("pubsubhubub_httphost", module.host); --- HTTP by default or not? -local use_pubsubhubub = module:get_option_boolean("use_pubsubhubub", true); - -- Thanks to Maranda for this local port, base, ssl = 5280, "callback", false; local ports = module:get_option("feeds_ports") or { port = port, base = base, ssl = ssl }; @@ -190,13 +169,12 @@ end function refresh_feeds() - local now = time(); if module_unloaded then return end --module:log("debug", "Refreshing feeds"); for node, item in pairs(feed_list) do --FIXME Don't fetch feeds which have a subscription -- Otoho, what if the subscription expires or breaks? - if item.last_update + refresh_interval < now then + if item.last_update + refresh_interval < time() then module:log("debug", "checking %s", item.node); fetch(item, update_entry); end