verse.lua

Thu, 23 Mar 2023 12:14:53 +0000

author
Matthew Wild <mwild1@gmail.com>
date
Thu, 23 Mar 2023 12:14:53 +0000
changeset 172
2c17151ed21b
parent 103
9e0e56393978
child 177
3c19b67a1f0f
permissions
-rw-r--r--

client: Fix timeout handling

Previously, the timeout handler would fire an error that would get caught and
logged by the timer code. However that error never reached the upper levels of
scansion, leading to the whole thing just hanging.

Now we just trigger resumption of the async runner, and throw the error from
there if we haven't received the stanza yet.

With this change, timeouts are now correctly handled and reported as failures.

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

mercurial