verse.lua

Sun, 30 Dec 2018 09:43:36 +0000

author
Matthew Wild <mwild1@gmail.com>
date
Sun, 30 Dec 2018 09:43:36 +0000
changeset 164
14500a149b31
parent 103
9e0e56393978
child 177
3c19b67a1f0f
permissions
-rw-r--r--

client: Ignore timeout timer if we received a stanza

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