view verse.lua @ 0:0d4d8b432980 default tip master

Initial commit.
author Kooda <kooda@upyum.com>
date Fri, 27 Aug 2010 17:00:32 +0200
parents
children
line wrap: on
line source

package.preload['util.encodings']=(function(...)
local function e()
error("Encoding function not implemented");
end
module"encodings"
stringprep={};
base64={encode=e,decode=e};
return _M;
end)
package.preload['util.logger']=(function(...)
local o=print
local i,t=select,tostring;
module"logger"
local function e(o,...)
local e,a=0,#arg;
return(o:gsub("%%(.)",function(o)if o~="%"and e<=a then e=e+1;return t(arg[e]);end end));
end
local function n(a,...)
local e,o=0,i('#',...);
local i={...};
return(a:gsub("%%(.)",function(a)if e<=o then e=e+1;return t(i[e]);end end));
end
function init(e)
return function(e,t,...)
o(e,n(t,...));
end
end
return _M;
end)
package.preload['util.xstanza']=(function(...)
local e=getmetatable(require"util.stanza".stanza());
local i="urn:ietf:params:xml:ns:xmpp-stanzas";
function e:get_error()
local o,t,a;
local e=self:get_child("error");
if not e then
return nil,nil;
end
o=e.attr.type;
for e in e:children()do
if e.attr.xmlns==i then
if e.name=="text"then
a=e:get_text();
else
t=e.name;
end
if t and a then
break;
end
end
end
return o,t,a;
end
end)
package.preload['util.stanza']=(function(...)
local e=table.insert;
local t=table.concat;
local r=table.remove;
local y=table.concat;
local s=string.format;
local w=string.match;
local l=tostring;
local d=setmetatable;
local p=getmetatable;
local i=pairs;
local n=ipairs;
local o=type;
local t=next;
local t=print;
local t=unpack;
local m=string.gsub;
local t=string.char;
local u=string.find;
local t=os;
local c=not t.getenv("WINDIR");
local h,a;
if c then
local t,e=pcall(require,"util.termcolours");
if t then
h,a=e.getstyle,e.getstring;
else
c=nil;
end
end
local f="urn:ietf:params:xml:ns:xmpp-stanzas";
module"stanza"
stanza_mt={__type="stanza"};
stanza_mt.__index=stanza_mt;
function stanza(t,e)
local e={name=t,attr=e or{},tags={},last_add={}};
return d(e,stanza_mt);
end
function stanza_mt:query(e)
return self:tag("query",{xmlns=e});
end
function stanza_mt:body(e,t)
return self:tag("body",t):text(e);
end
function stanza_mt:tag(a,t)
local t=stanza(a,t);
(self.last_add[#self.last_add]or self):add_direct_child(t);
e(self.last_add,t);
return self;
end
function stanza_mt:text(e)
(self.last_add[#self.last_add]or self):add_direct_child(e);
return self;
end
function stanza_mt:up()
r(self.last_add);
return self;
end
function stanza_mt:reset()
local e=self.last_add;
for t=1,#e do
e[t]=nil;
end
return self;
end
function stanza_mt:add_direct_child(t)
if o(t)=="table"then
e(self.tags,t);
end
e(self,t);
end
function stanza_mt:add_child(e)
(self.last_add[#self.last_add]or self):add_direct_child(e);
return self;
end
function stanza_mt:get_child(a,t)
for o,e in n(self.tags)do
if(not a or e.name==a)
and((not t and self.attr.xmlns==e.attr.xmlns)
or e.attr.xmlns==t)then
return e;
end
end
end
function stanza_mt:child_with_name(t)
for a,e in n(self.tags)do
if e.name==t then return e;end
end
end
function stanza_mt:child_with_ns(t)
for a,e in n(self.tags)do
if e.attr.xmlns==t then return e;end
end
end
function stanza_mt:children()
local e=0;
return function(t)
e=e+1
local e=t[e]
if e then return e;end
end,self,e;
end
function stanza_mt:childtags()
local e=0;
return function(t)
e=e+1
local e=self.tags[e]
if e then return e;end
end,self.tags[1],e;
end
local r
do
local e={["'"]="&apos;",["\""]="&quot;",["<"]="&lt;",[">"]="&gt;",["&"]="&amp;"};
function r(t)return(m(t,"['&<>\"]",e));end
_M.xml_escape=r;
end
local function m(o,t,h,a,r)
local n=0;
local s=o.name
e(t,"<"..s);
for o,i in i(o.attr)do
if u(o,"\1",1,true)then
local o,s=w(o,"^([^\1]*)\1?(.*)$");
n=n+1;
e(t," xmlns:ns"..n.."='"..a(o).."' ".."ns"..n..":"..s.."='"..a(i).."'");
elseif not(o=="xmlns"and i==r)then
e(t," "..o.."='"..a(i).."'");
end
end
local i=#o;
if i==0 then
e(t,"/>");
else
e(t,">");
for i=1,i do
local i=o[i];
if i.name then
h(i,t,h,a,o.attr.xmlns);
else
e(t,a(i));
end
end
e(t,"</"..s..">");
end
end
function stanza_mt.__tostring(t)
local e={};
m(t,e,m,r,nil);
return y(e);
end
function stanza_mt.top_tag(t)
local e="";
if t.attr then
for t,a in i(t.attr)do if o(t)=="string"then e=e..s(" %s='%s'",t,r(l(a)));end end
end
return s("<%s%s>",t.name,e);
end
function stanza_mt.get_text(e)
if#e.tags==0 then
return y(e);
end
end
function stanza_mt.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 e in e:children()do
if e.attr.xmlns==f 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 or"";
end
function stanza_mt.__add(e,t)
return e:add_direct_child(t);
end
do
local e=0;
function new_id()
e=e+1;
return"lx"..e;
end
end
function preserialize(a)
local t={name=a.name,attr=a.attr};
for i,a in n(a)do
if o(a)=="table"then
e(t,preserialize(a));
else
e(t,a);
end
end
return t;
end
function deserialize(t)
if t then
local a=t.attr;
for e=1,#a do a[e]=nil;end
local s={};
for e in i(a)do
if u(e,"|",1,true)and not u(e,"\1",1,true)then
local o,t=w(e,"^([^|]+)|(.+)$");
s[o.."\1"..t]=a[e];
a[e]=nil;
end
end
for e,t in i(s)do
a[e]=t;
end
d(t,stanza_mt);
for t,e in n(t)do
if o(e)=="table"then
deserialize(e);
end
end
if not t.tags then
local a={};
for n,i in n(t)do
if o(i)=="table"then
e(a,i);
end
end
t.tags=a;
if not t.last_add then
t.last_add={};
end
end
end
return t;
end
function clone(n)
local t={};
local function a(e)
if o(e)~="table"then
return e;
elseif t[e]then
return t[e];
end
local o={};
t[e]=o;
for t,e in i(e)do
o[a(t)]=a(e);
end
return d(o,p(e));
end
return a(n)
end
function message(t,e)
if not e then
return stanza("message",t);
else
return stanza("message",t):tag("body"):text(e);
end
end
function iq(e)
if e and not e.id then e.id=new_id();end
return stanza("iq",e or{id=new_id()});
end
function reply(e)
return stanza(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 a={xmlns=f};
function error_reply(e,o,i,t)
local e=reply(e);
e.attr.type="error";
e:tag("error",{type=o})
:tag(i,a):up();
if(t)then e:tag("text",a):text(t):up();end
return e;
end
end
function presence(e)
return stanza("presence",e);
end
if c then
local d=h("yellow");
local u=h("red");
local t=h("red");
local e=h("magenta");
local h=" "..a(d,"%s")..a(e,"=")..a(u,"'%s'");
local d=a(e,"<")..a(t,"%s").."%s"..a(e,">");
local u=d.."%s"..a(e,"</")..a(t,"%s")..a(e,">");
function stanza_mt.pretty_print(t)
local e="";
for a,t in n(t)do
if o(t)=="string"then
e=e..r(t);
else
e=e..t:pretty_print();
end
end
local a="";
if t.attr then
for e,t in i(t.attr)do if o(e)=="string"then a=a..s(h,e,l(t));end end
end
return s(u,t.name,a,e,t.name);
end
function stanza_mt.pretty_top_tag(t)
local e="";
if t.attr then
for t,a in i(t.attr)do if o(t)=="string"then e=e..s(h,t,l(a));end end
end
return s(d,t.name,e);
end
else
stanza_mt.pretty_print=stanza_mt.__tostring;
stanza_mt.pretty_top_tag=stanza_mt.top_tag;
end
return _M;
end)
package.preload['util.timer']=(function(...)
local d=require"net.server".addtimer;
local i=require"net.server".event;
local l=require"net.server".event_base;
local n=os.time;
local s=table.insert;
local e=table.remove;
local e,h=ipairs,pairs;
local r=type;
local o={};
local a={};
module"timer"
local t;
if not i then
function t(e,o)
local t=n();
e=e+t;
if e>=t then
s(a,{e,o});
else
o();
end
end
d(function()
local e=n();
if#a>0 then
for t,e in h(a)do
s(o,e);
end
a={};
end
for n,a in h(o)do
local i,a=a[1],a[2];
if i<=e then
o[n]=nil;
local e=a(e);
if r(e)=="number"then t(e,a);end
end
end
end);
else
local o=(i.core and i.core.LEAVE)or-1;
function t(a,t)
local e;
e=l:addevent(nil,0,function()
local t=t();
if t then
return 0,t;
elseif e then
return o;
end
end
,a);
end
end
add_task=t;
return _M;
end)
package.preload['util.termcolours']=(function(...)
local a,o=table.concat,table.insert;
local e,i=string.char,string.format;
local s=ipairs;
module"termcolours"
local h={
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 n=e(27).."[%sm%s"..e(27).."[0m";
function getstring(e,t)
if e then
return i(n,e,t);
else
return t;
end
end
function getstyle(...)
local e,t={...},{};
for a,e in s(e)do
e=h[e];
if e then
o(t,e);
end
end
return a(t,";");
end
return _M;
end)
package.preload['util.uuid']=(function(...)
local e=math.random;
local n=tostring;
local e=os.time;
local i=os.clock;
local a=require"util.hashes".sha1;
module"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 a(e..i()..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.server']=(function(...)
local r=function(e)
return _G[e]
end
local ee=function(e)
for t,a in pairs(e)do
e[t]=nil
end
end
local O,e=require("util.logger").init("socket"),table.concat;
local i=function(...)return O("debug",e{...});end
local se=function(...)return O("warn",e{...});end
local e=collectgarbage
local ne=1
local R=r"type"
local q=r"pairs"
local oe=r"ipairs"
local s=r"tostring"
local e=r"collectgarbage"
local a=r"os"
local o=r"table"
local t=r"string"
local e=r"coroutine"
local Y=a.time
local H=a.difftime
local ae=o.concat
local a=o.remove
local te=t.len
local ue=t.sub
local ce=e.wrap
local me=e.yield
local g=r"ssl"
local L=r"socket"or require"socket"
local ie=(g and g.wrap)
local de=L.bind
local le=L.sleep
local fe=L.select
local e=(g and g.newcontext)
local M
local G
local B
local P
local V
local Q
local K
local X
local J
local Z
local C
local l
local he
local e
local N
local re
local v
local h
local S
local d
local n
local _
local p
local f
local c
local a
local o
local y
local I
local U
local A
local T
local F
local u
local E
local z
local j
local x
local k
local D
local W
local b
v={}
h={}
d={}
S={}
n={}
p={}
f={}
_={}
a=0
o=0
y=0
I=0
U=0
A=1
T=0
E=51e3*1024
z=25e3*1024
j=12e5
x=6e4
k=6*60*60
D=false
b=1e3
_maxsslhandshake=30
J=function(u,t,w,c,y,f,m)
m=m or b
local r=0
local p,e=u.onconnect or u.onincoming,u.ondisconnect
local v=t.accept
local e={}
e.shutdown=function()end
e.ssl=function()
return f~=nil
end
e.sslctx=function()
return f
end
e.remove=function()
r=r-1
end
e.close=function()
for a,e in q(n)do
if e.serverport==c then
e.disconnect(e,"server closed")
e:close(true)
end
end
t:close()
o=l(d,t,o)
a=l(h,t,a)
n[t]=nil
e=nil
t=nil
i"server.lua: closed server handler and removed sockets from list"
end
e.ip=function()
return w
end
e.serverport=function()
return c
end
e.socket=function()
return t
end
e.readbuffer=function()
if r>m then
i("server.lua: refused new client connection: server full")
return false
end
local t,a=v(t)
if t then
local a,o=t:getpeername()
t:settimeout(0)
local e,n,t=N(e,u,t,a,c,o,y,f)
if t then
return false
end
r=r+1
i("server.lua: accepted new client connection from ",s(a),":",s(o)," to ",s(c))
return p(e)
elseif a then
i("server.lua: error with new client connection: ",s(a))
return false
end
end
return e
end
N=function(V,e,t,W,B,F,T,q)
t:settimeout(0)
local w
local x
local k
local j
local C=e.onincoming
local R=e.onstatus
local v=e.ondisconnect
local L=e.ondrain
local y={}
local r=0
local Y
local H
local O
local m=0
local b=false
local A=false
local S,N=0,0
local E=E
local z=z
local e=y
e.dispatch=function()
return C
end
e.disconnect=function()
return v
end
e.setlistener=function(a,t)
C=t.onincoming
v=t.ondisconnect
R=t.onstatus
L=t.ondrain
end
e.getstats=function()
return N,S
end
e.ssl=function()
return j
end
e.sslctx=function()
return q
end
e.send=function(n,i,o,a)
return w(t,i,o,a)
end
e.receive=function(o,a)
return x(t,o,a)
end
e.shutdown=function(a)
return k(t,a)
end
e.setoption=function(i,o,a)
if t.setoption then
return t:setoption(o,a);
end
return false,"setoption not implemented";
end
e.close=function(u,s)
if not e then return true;end
a=l(h,t,a)
p[e]=nil
if r~=0 then
if not(s or H)then
e.sendbuffer()
if r~=0 then
if e then
e.write=nil
end
Y=true
return false
end
else
w(t,ae(y,"",1,r),1,m)
end
end
if t then
c=k and k(t)
t:close()
o=l(d,t,o)
n[t]=nil
t=nil
else
i"server.lua: socket already closed"
end
if e then
f[e]=nil
_[e]=nil
e=nil
end
if V then
V.remove()
end
i"server.lua: closed client handler and removed socket from list"
return true
end
e.ip=function()
return W
end
e.serverport=function()
return B
end
e.clientport=function()
return F
end
local _=function(i,a)
m=m+te(a)
if m>E then
_[e]="send buffer exceeded"
e.write=P
return false
elseif t and not d[t]then
o=addsocket(d,t,o)
end
r=r+1
y[r]=a
if e then
f[e]=f[e]or u
end
return true
end
e.write=_
e.bufferqueue=function(t)
return y
end
e.socket=function(a)
return t
end
e.set_mode=function(a,t)
T=t or T
return T
end
e.set_send=function(a,t)
w=t or w
return w
end
e.bufferlen=function(o,a,t)
E=t or E
z=a or z
return m,z,E
end
e.lock_read=function(i,o)
if o==true then
local o=a
a=l(h,t,a)
p[e]=nil
if a~=o then
b=true
end
elseif o==false then
if b then
b=false
a=addsocket(h,t,a)
p[e]=u
end
end
return b
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=P
local a=o
o=l(d,t,o)
f[e]=nil
if o~=a then
A=true
end
elseif a==false then
e.write=_
if A then
A=false
_("")
end
end
return b,A
end
local z=function()
local o,t,a=x(t,T)
if not t or(t=="wantread"or t=="timeout")then
local o=o or a or""
local a=te(o)
if a>z then
v(e,"receive buffer exceeded")
e:close(true)
return false
end
local a=a*ne
N=N+a
U=U+a
p[e]=u
return C(e,o,t)
else
i("server.lua: client ",s(W),":",s(F)," read error: ",s(t))
H=true
v(e,t)
c=e and e:close()
return false
end
end
local f=function()
local b,a,h,n,p;
local p;
if t then
n=ae(y,"",1,r)
b,a,h=w(t,n,1,m)
p=(b or h or 0)*ne
S=S+p
I=I+p
c=D and ee(y)
else
b,a,p=false,"closed",0;
end
if b then
r=0
m=0
o=l(d,t,o)
f[e]=nil
if L then
L(e)
end
c=O and e:starttls(nil)
c=Y and e:close()
return true
elseif h and(a=="timeout"or a=="wantwrite")then
n=ue(n,h+1,m)
y[1]=n
r=1
m=m-h
f[e]=u
return true
else
i("server.lua: client ",s(W),":",s(F)," write error: ",s(a))
H=true
v(e,a)
c=e and e:close()
return false
end
end
local u;
function e.set_sslctx(n,t)
j=true
q=t;
local m
local r
u=ce(function(t)
local n
for u=1,_maxsslhandshake do
o=(m and l(d,t,o))or o
a=(r and l(h,t,a))or a
r,m=nil,nil
c,n=t:dohandshake()
if not n then
i("server.lua: ssl handshake done")
e.readbuffer=z
e.sendbuffer=f
c=R and R(e,"ssl-handshake-complete")
a=addsocket(h,t,a)
return true
else
i("server.lua: error during ssl handshake: ",s(n))
if n=="wantwrite"and not m then
o=addsocket(d,t,o)
m=true
elseif n=="wantread"and not r then
a=addsocket(h,t,a)
r=true
else
break;
end
me()
end
end
v(e,"ssl handshake failed")
c=e and e:close(true)
return false
end
)
end
if g then
if q then
e:set_sslctx(q);
i("server.lua: ","starting ssl handshake")
local a
t,a=ie(t,q)
if a then
i("server.lua: ssl error: ",s(a))
return nil,nil,a
end
t:settimeout(0)
e.readbuffer=u
e.sendbuffer=u
u(t)
if not t then
return nil,nil,"ssl handshake failed";
end
else
local c;
e.starttls=function(f,m)
if m then
c=m;
e:set_sslctx(c);
end
if r>0 then
i"server.lua: we need to do tls, but delaying until send buffer empty"
O=true
return
end
i("server.lua: attempting to start tls on "..s(t))
local m,r=t
t,r=ie(t,c)
if r then
i("server.lua: error while starting tls on client: ",s(r))
return nil,r
end
t:settimeout(0)
w=t.send
x=t.receive
k=M
n[t]=e
a=addsocket(h,t,a)
a=l(h,m,a)
o=l(d,m,o)
n[m]=nil
e.starttls=nil
O=nil
j=true
e.readbuffer=u
e.sendbuffer=u
u(t)
end
e.readbuffer=z
e.sendbuffer=f
end
else
e.readbuffer=z
e.sendbuffer=f
end
w=t.send
x=t.receive
k=(j and M)or t.shutdown
n[t]=e
a=addsocket(h,t,a)
return e,t
end
M=function()
end
P=function()
return false
end
addsocket=function(t,a,e)
if not t[a]then
e=e+1
t[e]=a
t[a]=e
end
return e;
end
l=function(e,a,t)
local i=e[a]
if i then
e[a]=nil
local o=e[t]
e[t]=nil
if o~=a then
e[o]=i
e[i]=o
end
return t-1
end
return t
end
C=function(e)
o=l(d,e,o)
a=l(h,e,a)
n[e]=nil
e:close()
end
local function l(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
end
K=function(o,e,d,l,r)
local t
if R(d)~="table"then
t="invalid listener table"
end
if R(e)~="number"or not(e>=0 and e<=65535)then
t="invalid port"
elseif v[e]then
t="listeners on port '"..e.."' already exist"
elseif r and not g then
t="luasec not found"
end
if t then
se("server.lua, port ",e,": ",t)
return nil,t
end
o=o or"*"
local t,s=de(o,e)
if s then
se("server.lua, port ",e,": ",s)
return nil,s
end
local s,d=J(d,t,o,e,l,r,b)
if not s then
t:close()
return nil,d
end
t:settimeout(0)
a=addsocket(h,t,a)
v[e]=s
n[t]=s
i("server.lua: new "..(r and"ssl "or"").."server listener on '",o,":",e,"'")
return s
end
X=function(e)
return v[e];
end
he=function(e)
local t=v[e]
if not t then
return nil,"no server found on port '"..s(e).."'"
end
t:close()
v[e]=nil
return true
end
Q=function()
for e,t in q(n)do
t:close()
n[e]=nil
end
a=0
o=0
y=0
v={}
h={}
d={}
S={}
n={}
end
Z=function()
return A,T,E,z,j,x,k,D,b,_maxsslhandshake
end
re=function(e)
if R(e)~="table"then
return nil,"invalid settings table"
end
A=tonumber(e.timeout)or A
T=tonumber(e.sleeptime)or T
E=tonumber(e.maxsendlen)or E
z=tonumber(e.maxreadlen)or z
j=tonumber(e.checkinterval)or j
x=tonumber(e.sendtimeout)or x
k=tonumber(e.readtimeout)or k
D=e.cleanqueue
b=e._maxclientsperserver or b
_maxsslhandshake=e._maxsslhandshake or _maxsslhandshake
return true
end
V=function(e)
if R(e)~="function"then
return nil,"invalid listener function"
end
y=y+1
S[y]=e
return true
end
B=function()
return U,I,a,o,y
end
local e=true;
setquitting=function(t)
e=not t;
return;
end
G=function()
while e do
local a,e,t=fe(h,d,A)
for e,t in oe(e)do
local e=n[t]
if e then
e.sendbuffer()
else
C(t)
i"server.lua: found no handler and closed socket (writelist)"
end
end
for t,e in oe(a)do
local t=n[e]
if t then
t.readbuffer()
else
C(e)
i"server.lua: found no handler and closed socket (readlist)"
end
end
for e,t in q(_)do
e.disconnect()(e,t)
e:close(true)
end
ee(_)
u=Y()
if H(u-W)>=1 then
for e=1,y do
S[e](u)
end
W=u
end
le(T)
end
return"quitting"
end
local function h()
return"select";
end
local i=function(t,i,s,a,e,h)
local e=N(nil,a,t,i,s,"clientport",e,h)
n[t]=e
o=addsocket(d,t,o)
if a.onconnect then
local t=e.sendbuffer;
e.sendbuffer=function()
a.onconnect(e);
e.sendbuffer=t;
if#e:bufferqueue()>0 then
return t();
end
end
end
return e,t
end
local a=function(o,a,n,s,h)
local t,e=L.tcp()
if e then
return nil,e
end
t:settimeout(0)
c,e=t:connect(o,a)
if e then
local e=i(t,o,a,n)
else
N(nil,n,t,o,a,"clientport",s,h)
end
end
r"setmetatable"(n,{__mode="k"})
r"setmetatable"(p,{__mode="k"})
r"setmetatable"(f,{__mode="k"})
W=Y()
F=Y()
V(function()
local e=H(u-F)
if e>j then
F=u
for e,t in q(f)do
if H(u-t)>x then
e.disconnect()(e,"send timeout")
e:close(true)
end
end
for e,t in q(p)do
if H(u-t)>k then
e.disconnect()(e,"read timeout")
e:close()
end
end
end
end
)
local function t(e)
local t=O;
if e then
O=e;
end
return t;
end
return{
addclient=a,
wrapclient=i,
loop=G,
link=l,
stats=B,
closeall=Q,
addtimer=V,
addserver=K,
getserver=X,
setlogger=t,
getsettings=Z,
setquitting=setquitting,
removeserver=he,
get_backend=h,
changesettings=re,
}
end)
package.preload['core.xmlhandlers']=(function(...)
require"util.stanza"
local y=stanza;
local u=tostring;
local w=table.insert;
local l=table.concat;
local r=require"util.logger".init("xmlhandlers");
local s=error;
module"xmlhandlers"
local f={
["http://www.w3.org/XML/1998/namespace"]="xml";
};
local i="http://etherx.jabber.org/streams";
local t="\1";
local h="^([^"..t.."]*)"..t.."?(.*)$";
function init_xmlhandlers(a,e)
local o={};
local n={};
local r=a.log or r;
local r=e.streamopened;
local d=e.streamclosed;
local s=e.error or function(t,e)s("XML stream error: "..u(e));end;
local m=e.handlestanza;
local i=e.stream_ns or i;
local c=i..t..(e.stream_tag or"stream");
local p=i..t..(e.error_tag or"error");
local u=e.default_ns;
local e;
function n:StartElement(n,t)
if e and#o>0 then
e:text(l(o));
o={};
end
local o,i=n:match(h);
if i==""then
o,i="",o;
end
if o~=u then
t.xmlns=o;
end
for e=1,#t do
local a=t[e];
t[e]=nil;
local e,o=a:match(h);
if o~=""then
e=f[e];
if e then
t[e..":"..o]=t[a];
t[a]=nil;
end
end
end
if not e then
if a.notopen then
if n==c then
if r then
r(a,t);
end
else
s(a,"no-stream");
end
return;
end
if o=="jabber:client"and i~="iq"and i~="presence"and i~="message"then
s(a,"invalid-top-level-element");
end
e=y.stanza(i,t);
else
t.xmlns=nil;
if o~=u then
t.xmlns=o;
end
e:tag(i,t);
end
end
function n:CharacterData(t)
if e then
w(o,t);
end
end
function n:EndElement(t)
if e then
if#o>0 then
e:text(l(o));
o={};
end
if#e.last_add==0 then
if t~=p then
m(a,e);
else
s(a,"stream-error",e);
end
e=nil;
else
e:up();
end
else
if t==c then
if d then
d(a);
end
else
local t,e=t:match(h);
if e==""then
t,e="",t;
end
s(a,"parse-error","unexpected-element-close",e);
end
e,o=nil,{};
end
end
return n;
end
return init_xmlhandlers;
end)
package.preload['util.jid']=(function(...)
local t=string.match;
local h=require"util.encodings".stringprep.nodeprep;
local s=require"util.encodings".stringprep.nameprep;
local n=require"util.encodings".stringprep.resourceprep;
module"jid"
local function a(e)
if not e then return;end
local o,a=t(e,"^([^@]+)@()");
local a,i=t(e,"^([^@/]+)()",a)
if o and not a then return nil,nil,nil;end
local t=t(e,"^/(.+)$",i);
if(not a)or((not t)and#e>=i)then return nil,nil,nil;end
return o,a,t;
end
split=a;
function bare(e)
local t,e=a(e);
if t and e then
return t.."@"..e;
end
return e;
end
local function o(e)
local t,e,a=a(e);
if e then
e=s(e);
if not e then return;end
if t then
t=h(t);
if not t then return;end
end
if a then
a=n(a);
if not a then return;end
end
return t,e,a;
end
end
prepped_split=o;
function prep(e)
local t,e,a=o(e);
if e then
if t then
e=t.."@"..e;
end
if a then
e=e.."/"..a;
end
end
return e;
end
function join(t,e,a)
if t and e and a then
return t.."@"..e.."/"..a;
elseif t and e then
return t.."@"..e;
elseif e and a then
return e.."/"..a;
elseif e then
return e;
end
return nil;
end
function compare(t,e)
local n,i,o=a(t);
local t,a,e=a(e);
if((t~=nil and t==n)or t==nil)and
((a~=nil and a==i)or a==nil)and
((e~=nil and e==o)or e==nil)then
return true
end
return false
end
return _M;
end)
package.preload['util.events']=(function(...)
local r=ipairs;
local i=pairs;
local s=table.insert;
local h=table.sort;
local d=select;
module"events"
function new()
local o={};
local t={};
local a={};
local function n(o)
local a=a[o];
local e=t[o];
if e then
for t=#e,1,-1 do e[t]=nil;end
else e={};t[o]=e;end
for t in i(a)do
s(e,t);
end
h(e,function(t,e)return a[t]>a[e];end);
end;
local function s(t,o,i)
local e=a[t];
if e then
e[o]=i or 0;
else
e={[o]=i or 0};
a[t]=e;
end
n(t);
end;
local function h(t,o)
local e=a[t];
if e then
e[o]=nil;
n(t);
end
end;
local function e(e)
for e,t in i(e)do
s(e,t);
end
end;
local function e(e)
for t,e in i(e)do
h(t,e);
end
end;
local function n(a)
local e=t[a];
if not e then e={};t[a]=e;end
local e=function(...)
for t=1,#e do
local e=e[t](...);
if e~=nil then return e;end
end
end;
o[a]=e;
return e;
end;
local function i(e)
return o[e]or n(e);
end;
local function n(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;
local function l(e,...)
local a=i(e);
local t={...};
local e={};
return function(...)
for t,o in r(t)do e[o]=d(t,...);end
a(e);
end;
end;
return{
add_handler=s;
remove_handler=h;
add_plugin=add_plugin;
remove_plugin=remove_plugin;
get_dispatcher=i;
fire_event=n;
get_named_arg_dispatcher=l;
_dispatchers=o;
_handlers=t;
_event_map=a;
};
end
return _M;
end)
package.preload['util.sha1']=(function(...)
local u=string.len
local a=string.char
local k=string.byte
local g=string.sub
local m=math.floor
local t=require"bit"
local b=t.bnot
local e=t.band
local p=t.bor
local n=t.bxor
local o=t.lshift
local i=t.rshift
local h,r,d,l,c
local function y(e,t)
return o(e,t)+i(e,32-t)
end
local function s(i)
local t,o
local t=""
for n=1,8 do
o=e(i,15)
if(o<10)then
t=a(o+48)..t
else
t=a(o+87)..t
end
i=m(i/16)
end
return t
end
local function j(t)
local i,o
local n=""
i=u(t)*8
t=t..a(128)
o=56-e(u(t),63)
if(o<0)then
o=o+64
end
for e=1,o do
t=t..a(0)
end
for t=1,8 do
n=a(e(i,255))..n
i=m(i/256)
end
return t..n
end
local function q(w)
local s,t,i,o,f,u,m,v
local a,a
local a={}
while(w~="")do
for e=0,15 do
a[e]=0
for t=1,4 do
a[e]=a[e]*256+k(w,e*4+t)
end
end
for e=16,79 do
a[e]=y(n(n(a[e-3],a[e-8]),n(a[e-14],a[e-16])),1)
end
s=h
t=r
i=d
o=l
f=c
for r=0,79 do
if(r<20)then
u=p(e(t,i),e(b(t),o))
m=1518500249
elseif(r<40)then
u=n(n(t,i),o)
m=1859775393
elseif(r<60)then
u=p(p(e(t,i),e(t,o)),e(i,o))
m=2400959708
else
u=n(n(t,i),o)
m=3395469782
end
v=y(s,5)+u+f+m+a[r]
f=o
o=i
i=y(t,30)
t=s
s=v
end
h=e(h+s,4294967295)
r=e(r+t,4294967295)
d=e(d+i,4294967295)
l=e(l+o,4294967295)
c=e(c+f,4294967295)
w=g(w,65)
end
end
local function a(e,t)
e=j(e)
h=1732584193
r=4023233417
d=2562383102
l=271733878
c=3285377520
q(e)
local e=s(h)..s(r)..s(d)
..s(l)..s(c);
if t then
return e;
else
return e:gsub("..",function(e)
return string.char(tonumber(e,16));
end);
end
end
_G.sha1={sha1=a};
return _G.sha1;
end)
package.preload['verse.plugins.tls']=(function(...)
local o=require"util.stanza";
local t="urn:ietf:params:xml:ns:xmpp-tls";
function verse.plugins.tls(e)
local function a(a)
if e.authenticated then return;end
if a:get_child("starttls",t)and e.conn.starttls then
e:debug("Negotiating TLS...");
e:send(o.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({mode="client",protocol="sslv23",options="no_sslv2"},true);
end
end
local function i(t)
if t=="ssl-handshake-complete"then
e.secure=true;
e:debug("Re-opening stream...");
e:reopen();
end
end
e:hook("stream-features",a,400);
e:hook("stream/"..t,o);
e:hook("status",i,400);
return true;
end
end)
package.preload['verse.plugins.sasl']=(function(...)
local o=require"util.stanza";
local e=require"util.xstanza";
local t=require"mime".b64;
local a="urn:ietf:params:xml:ns:xmpp-sasl";
function verse.plugins.sasl(e)
local function i(i)
if e.authenticated then return;end
e:debug("Authenticating with SASL...");
local t=t("\0"..e.username.."\0"..e.password);
e:debug("Selecting PLAIN mechanism...");
local a=o.stanza("auth",{xmlns=a,mechanism="PLAIN"});
if t then
a:text(t);
end
e:send(a);
return true;
end
local function o(t)
if t.name=="success"then
e.authenticated=true;
e:event("authentication-success");
elseif t.name=="failure"then
local t=t.tags[1];
e:event("authentication-failure",{condition=t.name});
end
e:reopen();
return true;
end
e:hook("stream-features",i,300);
e:hook("stream/"..a,o);
return true;
end
end)
package.preload['verse.plugins.bind']=(function(...)
local t=require"util.stanza";
local a="urn:ietf:params:xml:ns:xmpp-bind";
function verse.plugins.bind(e)
local function i(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("jid")
:get_text();
e.username,e.host,e.resource=jid.split(t);
e.jid,e.bound=t,true;
e:event("bind-success",full_jid);
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",i,200);
return true;
end
end)
package.preload['verse.plugins.version']=(function(...)
local t="jabber:iq:version";
local function a(e,t)
e.name=t.name;
e.version=t.version;
e.platform=t.platform;
end
function verse.plugins.version(e)
e.version={set=a};
e:hook("iq/"..t,function(a)
if a.attr.type~="get"then return;end
local t=verse.reply(a)
:tag("query",{xmlns=t});
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(o,a)
a=a or function(t)return e:event("version/response",t);end
e:send_iq(verse.iq({type="get",to=o})
:tag("query",{xmlns=t}),
function(o)
local e=o:get_child("query",t);
if o.attr.type=="result"then
local o=e:get_child("name");
local t=e:get_child("version");
local e=e:get_child("os");
a({
name=o and o:get_text()or nil;
version=t and t:get_text()or nil;
platform=e and e:get_text()or nil;
});
else
local e,t,o=o:get_error();
a({
error=true;
condition=t;
text=o;
type=e;
});
end
end);
end
return true;
end
end)
package.preload['verse.plugins.ping']=(function(...)
require"util.xstanza";
local o="urn:xmpp:ping";
function verse.plugins.ping(t)
function t:ping(e,a)
local n=socket.gettime();
t:send_iq(verse.iq{to=e,type="get"}:tag("ping",{xmlns=o}),
function(t)
if t.attr.type=="error"then
local i,t,o=t:get_error();
if t~="service-unavailable"and t~="feature-not-implemented"then
a(nil,e,{type=i,condition=t,text=o});
return;
end
end
a(socket.gettime()-n,e);
end);
end
return true;
end
end)
package.preload['verse.plugins.session']=(function(...)
local i=require"util.stanza";
local a="urn:ietf:params:xml:ns:xmpp-session";
function verse.plugins.session(e)
local function o(t)
local t=t:get_child("session",a);
if t and not t:get_child("optional")then
local function o(t)
e:debug("Establishing Session...");
e:send_iq(i.iq({type="set"}):tag("session",{xmlns=a}),
function(t)
if t.attr.type=="result"then
e:event("session-success");
elseif t.attr.type=="error"then
local a=t:child_with_name("error");
local a,t,o=t:get_error();
e:event("session-failure",{error=t,text=o,type=a});
end
end);
return true;
end
e:hook("bind-success",o);
end
end
e:hook("stream-features",o);
return true;
end
end)
package.preload['verse.plugins.compression']=(function(...)
local t=require"util.stanza";
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 i=9;
local function s(o)
local i,e=pcall(e.deflate,i);
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 h(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 n(e,o)
function e:send(a)
local o,a,i=pcall(o,tostring(a),'sync');
if o==false then
e:close({
condition="undefined-condition";
text=a;
extra=t.stanza("failure",{xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
});
e:warn("Compressed send failed: %s",tostring(a));
return;
end
e.conn:write(a);
end;
end
local function i(e,a)
local o=e.data
e.data=function(i,n)
e:debug("Decompressing data...");
local n,a,s=pcall(a,n);
if n==false then
e:close({
condition="undefined-condition";
text=a;
extra=t.stanza("failure",{xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed");
});
stream:warn("%s",tostring(a));
return;
end
return o(i,a);
end;
end
function verse.plugins.compression(e)
local function r(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(t)
if t.name=="compressed"then
e:debug("Activating compression...")
local t=s(e);
if not t then return end
local a=h(e);
if not a then return end
n(e,t);
i(e,a);
e.compressed=true;
e:reopen();
elseif t.name=="failure"then
e:warn("Failed to establish compression");
end
end
e:hook("stream-features",r,250);
e:hook("stream/"..a,o);
end
end)
package.preload['verse.plugins.blocking']=(function(...)
local a="urn:xmpp:blocking";
function verse.plugins.blocking(e)
e.blocking={};
function e.blocking:block_jid(o,t)
e:send_iq(verse.iq{type="set"}
:tag("block",{xmlns=a})
:tag("item",{jid=o})
,function()return t and t(true);end
,function()return t and t(false);end
);
end
function e.blocking:unblock_jid(o,t)
e:send_iq(verse.iq{type="set"}
:tag("unblock",{xmlns=a})
:tag("item",{jid=o})
,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(verse.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(verse.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.proxy65']=(function(...)
local e=require"util.events";
local h=require"util.uuid";
local r=require"util.sha1";
local i={};
i.__index=i;
local o="http://jabber.org/protocol/bytestreams";
local n;
function verse.plugins.proxy65(t)
t.proxy65=setmetatable({stream=t},i);
t.proxy65.available_streamhosts={};
local e=0;
t:hook("disco/service-discovered/proxy",function(a)
if a.type=="bytestreams"then
e=e+1;
t:send_iq(verse.iq({to=a.jid,type="get"})
:tag("query",{xmlns=o}),function(a)
e=e-1;
if a.attr.type=="result"then
local e=a:get_child("query",o)
: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/"..o,function(a)
local e=verse.new(nil,{
initiator_jid=a.attr.from,
streamhosts={},
current_host=0;
});
for t in a.tags[1]:childtags()do
if t.name=="streamhost"then
table.insert(e.streamhosts,t.attr);
end
end
local function o()
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
);
n(t,e,a.tags[1].attr.sid,a.attr.from,t.jid);
return true;
end
e:unhook("disconnected",o);
t:send(verse.error_reply(a,"cancel","item-not-found"));
end
function e:accept()
e:hook("disconnected",o,100);
e:hook("connected",function()
e:unhook("disconnected",o);
local e=verse.reply(a)
:tag("query",a.tags[1].attr)
:tag("streamhost-used",{jid=e.streamhosts[e.current_host].jid});
t:send(e);
end,100);
o();
end
function e:refuse()
end
t:event("proxy65/request",e);
end);
end
function i:new(t,s)
local e=verse.new(nil,{
target_jid=t;
bytestream_sid=h.generate();
});
local a=verse.iq{type="set",to=t}
:tag("query",{xmlns=o,mode="tcp",sid=e.bytestream_sid});
for t,e in ipairs(s or self.proxies)do
a:tag("streamhost",e):up();
end
self.stream:send_iq(a,function(a)
if a.attr.type=="error"then
local a,o,t=a:get_error();
e:event("connection-failed",{conn=e,type=a,condition=o,text=t});
else
local a=a.tags[1]:get_child("streamhost-used");
if not a then
end
e.streamhost_jid=a.attr.jid;
local a,i;
for o,t in ipairs(s or self.proxies)do
if t.jid==e.streamhost_jid then
a,i=t.host,t.port;
break;
end
end
if not(a and i)then
end
e:connect(a,i);
local function a()
e:unhook("connected",a);
local t=verse.iq{to=e.streamhost_jid,type="set"}
:tag("query",{xmlns=o,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);
else
end
end);
return true;
end
e:hook("connected",a,100);
n(self.stream,e,e.bytestream_sid,self.stream.jid,t);
end
end);
return e;
end
function n(i,e,a,t,o)
local t=r.sha1(a..t..o);
local function a()
e:unhook("connected",a);
return true;
end
local function o(t)
e:unhook("incoming-raw",o);
if t:sub(1,2)~="\005\000"then
return e:event("error","connection-failure");
end
e:event("connected");
return true;
end
local function i(a)
e:unhook("incoming-raw",i);
if a~="\005\000"then
local t="version-mismatch";
if a:sub(1,1)=="\005"then
t="authentication-failure";
end
return e:event("error",t);
end
e:send(string.char(5,1,0,3,#t)..t.."\0\0");
e:hook("incoming-raw",o,100);
return true;
end
e:hook("connected",a,200);
e:hook("incoming-raw",i,100);
e:send("\005\001\000");
end
end)
package.preload['verse.plugins.jingle']=(function(...)
local e=require"util.sha1".sha1;
local n=require"util.stanza";
local e=require"util.timer";
local a=require"util.uuid".generate;
local o="urn:xmpp:jingle:1";
local h="urn:xmpp:jingle:errors:1";
local t={};
t.__index=t;
local e={};
local e={};
function verse.plugins.jingle(e)
e:hook("ready",function()
e:add_disco_feature(o);
end,10);
function e:jingle(o)
return verse.eventable(setmetatable(base or{
role="initiator";
peer=o;
sid=a();
stream=e;
},t));
end
function e:register_jingle_transport(e)
end
function e:register_jingle_content_type(e)
end
local function u(i)
local r=i:get_child("jingle",o);
local a=r.attr.sid;
local s=r.attr.action;
local a=e:event("jingle/"..a,i);
if a==true then
e:send(verse.reply(i));
return true;
end
if s~="session-initiate"then
local t=n.error_reply(i,"cancel","item-not-found")
:tag("unknown-session",{xmlns=h}):up();
e:send(t);
return;
end
local l=r.attr.sid;
local a=verse.eventable{
role="receiver";
peer=i.attr.from;
sid=l;
stream=e;
};
setmetatable(a,t);
local s;
local d,h;
for t in r:childtags()do
if t.name=="content"and t.attr.xmlns==o then
local i=t:child_with_name("description");
local o=i.attr.xmlns;
if o then
local e=e:event("jingle/content/"..o,a,i);
if e then
d=e;
end
end
local o=t:child_with_name("transport");
local i=o.attr.xmlns;
h=e:event("jingle/transport/"..i,a,o);
if d and h then
s=t;
break;
end
end
end
if not d then
e:send(n.error_reply(i,"cancel","feature-not-implemented","The specified content is not supported"));
return;
end
if not h then
e:send(n.error_reply(i,"cancel","feature-not-implemented","The specified transport is not supported"));
return;
end
e:send(n.reply(i));
a.content_tag=s;
a.creator,a.name=s.attr.creator,s.attr.name;
a.content,a.transport=d,h;
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",o);
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,e,t)
local e=n.iq({to=self.peer,type="set"})
:tag("jingle",{
xmlns=o,
sid=self.sid,
action=a,
initiator=self.role=="initiator"and self.stream.jid or nil,
responder=self.role=="responder"and self.jid or nil,
})
:tag("content",{creator=self.creator,name=self.name})
:add_child(e);
if not t then
self.stream:send(e);
else
self.stream:send_iq(e,t);
end
end
function t:accept(a)
local t=n.iq({to=self.peer,type="set"})
:tag("jingle",{
xmlns=o,
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/"..o,u);
return true;
end
function t:offer(t,a)
local e=n.iq({to=self.peer,type="set"})
:tag("jingle",{xmlns=o,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",o);
return self:handle_command(e)
end);
self.stream:send_iq(e,function(e)
if e.type=="error"then
self.state="terminated";
local e,a,t=e:get_error();
return self:event("error",{type=e,condition=a,text=t});
end
end);
self.state="pending";
end
function t:terminate(e)
local e=verse.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.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)
print("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 o=require"ltn12";
local s=package.config:sub(1,1);
local a="urn:xmpp:jingle:apps:file-transfer:1";
local i="http://jabber.org/protocol/si/profile/file-transfer";
function verse.plugins.jingle_ft(t)
t:hook("ready",function()
t:add_disco_feature(a);
end,10);
local n={name="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("offer"):get_child("file",i);
local e={
name=e.attr.name;
size=tonumber(e.attr.size);
};
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 verse.stanza("description",{xmlns=a})
:tag("offer")
:tag("file",{xmlns=i,
name=e.filename,
size=e.size,
date=t,
hash=e.hash,
})
:tag("desc"):text(e.description or"");
end);
function t:send_file(a,t)
local e,i=io.open(t);
if not e then return e,i;end
local i=e:seek("end",0);
e:seek("set",0);
local o=o.source.file(e);
local e=c:jingle(a);
e:offer("file",{
filename=t:match("[^"..s.."]+$");
size=i;
});
e:hook("connected",function()
e:set_source(o,true);
end);
return e;
end
end
end)
package.preload['verse.plugins.jingle_s5b']=(function(...)
local a="urn:xmpp:jingle:transports:s5b:1";
local s=require"util.sha1".sha1;
local r=require"util.uuid".generate;
local function h(e,i)
local function n()
e:unhook("connected",n);
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 t(a)
e:unhook("incoming-raw",t);
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,#i)..i.."\0\0");
e:hook("incoming-raw",o,100);
return true;
end
e:hook("connected",n,200);
e:hook("incoming-raw",t,100);
e:send("\005\001\000");
end
local function i(a,e,i)
local e=verse.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
h(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 verse.plugins.jingle_s5b(e)
e:hook("ready",function()
e:add_disco_feature(a);
end,10);
local t={};
function t:generate_initiate()
self.s5b_sid=r();
local a=verse.stanza("transport",{xmlns=a,
mode="tcp",sid=self.s5b_sid});
local t=0;
for o,i in pairs(e.proxy65.available_streamhosts)do
t=t+1;
a:tag("candidate",{jid=o,host=i.host,
port=i.port,cid=o,priority=t,type="proxy"}):up();
end
e:debug("Have %d proxies",t)
return a;
end
function t:generate_accept(e)
local t={};
self.s5b_peer_candidates=t;
self.s5b_mode=e.attr.mode or"tcp";
self.s5b_sid=e.attr.sid or self.jingle.sid;
for e in e:childtags()do
t[e.attr.cid]={
type=e.attr.type;
jid=e.attr.jid;
host=e.attr.host;
port=tonumber(e.attr.port)or 0;
priority=tonumber(e.attr.priority)or 0;
cid=e.attr.cid;
};
end
local e=verse.stanza("transport",{xmlns=a});
return e;
end
function t:connect(o)
e:warn("Connecting!");
local t={};
for a,e in pairs(self.s5b_peer_candidates or{})do
t[#t+1]=e;
end
if#t>0 then
self.connecting_peer_candidates=true;
local function n(t,e)
self.jingle:send_command("transport-info",verse.stanza("transport",{xmlns=a,sid=self.s5b_sid})
:tag("candidate-used",{cid=t.cid}));
self.onconnect_callback=o;
self.conn=e;
end
local e=s(self.s5b_sid..self.peer..e.jid,true);
i(n,t,e);
else
e:warn("Actually, I'm going to wait for my peer to tell me its streamhost...");
self.onconnect_callback=o;
end
end
function t:info_received(t)
e:warn("Info received");
local o=t:child_with_name("content"):child_with_name("transport");
if o:get_child("candidate-used")and not self.connecting_peer_candidates then
local t=o:child_with_name("candidate-used");
if t then
local function n(o,e)
if self.jingle.role=="initiator"then
self.jingle.stream:send_iq(verse.iq({to=o.jid,type="set"})
:tag("query",{xmlns=xmlns_bytestreams,sid=self.s5b_sid})
:tag("activate"):text(self.jingle.peer),function(o)
if o.attr.type=="result"then
self.jingle:send_command("transport-info",verse.stanza("transport",{xmlns=a,sid=self.s5b_sid})
:tag("activated",{cid=t.attr.cid}));
self.conn=e;
self.onconnect_callback(e);
else
self.jingle.stream:error("Failed to activate bytestream");
end
end);
end
end
self.jingle.stream:debug("CID: %s",self.jingle.stream.proxy65.available_streamhosts[t.attr.cid]);
local t={
self.jingle.stream.proxy65.available_streamhosts[t.attr.cid];
};
local e=s(self.s5b_sid..e.jid..self.peer,true);
i(n,t,e);
end
elseif o:get_child("activated")then
self.onconnect_callback(self.conn);
end
end
function t:disconnect()
if self.conn then
self.conn:close();
end
end
function t:handle_accepted(e)
end
local t={__index=t};
e:hook("jingle/transport/"..a,function(e)
return setmetatable({
role=e.role,
peer=e.peer,
stream=e.stream,
jingle=e,
},t);
end);
end
end)
package.preload['verse.plugins.disco']=(function(...)
local a=require"util.stanza"
local o=require("mime").b64
local i=require("util.sha1").sha1
local e="http://jabber.org/protocol/disco";
local r=e.."#info";
local s=e.."#items";
function verse.plugins.disco(e)
e.disco={cache={},info={}}
e.disco.info.identities={
{category='client',type='pc',name='Verse'},
}
e.disco.info.features={
{var='http://jabber.org/protocol/caps'},
{var='http://jabber.org/protocol/disco#info'},
{var='http://jabber.org/protocol/disco#items'},
}
e.disco.items={}
e.disco.nodes={}
e.caps={}
e.caps.node='http://code.matthewwild.co.uk/verse/'
local function n(t,e)
if t.category<e.category then
return true;
elseif e.category<t.category then
return false;
end
if t.type<e.type then
return true;
elseif e.type<t.type then
return false;
end
if(not t['xml:lang']and e['xml:lang'])or
(e['xml:lang']and t['xml:lang']<e['xml:lang'])then
return true
end
return false
end
local function t(t,e)
return t.var<e.var
end
local function h()
table.sort(e.disco.info.identities,n)
table.sort(e.disco.info.features,t)
local t=''
for a,e in pairs(e.disco.info.identities)do
t=t..string.format(
'%s/%s/%s/%s',e.category,e.type,
e['xml:lang']or'',e.name or''
)..'<'
end
for a,e in pairs(e.disco.info.features)do
t=t..e.var..'<'
end
return(o(i(t)))
end
setmetatable(e.caps,{
__call=function(...)
local t=h()
return a.stanza('c',{
xmlns='http://jabber.org/protocol/caps',
hash='sha-1',
node=e.caps.node,
ver=t
})
end
})
function e:add_disco_feature(e)
table.insert(self.disco.info.features,{var=e});
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(o,a)
local e=self.disco.cache[self.host];
if not(e)or not(e.items)then
return nil,"no-cache";
end
local t={};
for i,e in ipairs(e.items)do
if self:jid_has_identity(e.jid,o,a)then
table.insert(t,e.jid);
end
end
return t;
end
function e:disco_local_services(a)
self:disco_items(self.host,nil,function(t)
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,n)
local a=verse.iq({to=e,type="get"})
:tag("query",{xmlns=r,node=t});
self:send_iq(a,function(a)
if a.attr.type=="error"then
return n(nil,a:get_error());
end
local o,i={},{};
for e in a:get_child("query",r):childtags()do
if e.name=="identity"then
o[e.attr.category.."/"..e.attr.type]=e.attr.name or true;
elseif e.name=="feature"then
i[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.nodes[t]then
self.disco.cache.nodes[t]={nodes={}};
end
self.disco.cache[e].nodes[t].identities=o;
self.disco.cache[e].nodes[t].features=i;
else
self.disco.cache[e].identities=o;
self.disco.cache[e].features=i;
end
return n(self.disco.cache[e]);
end);
end
function e:disco_items(a,t,i)
local o=verse.iq({to=a,type="get"})
:tag("query",{xmlns=s,node=t});
self:send_iq(o,function(o)
if o.attr.type=="error"then
return i(nil,o:get_error());
end
local e={};
for t in o:get_child("query",s):childtags()do
if t.name=="item"then
table.insert(e,{
name=t.attr.name;
jid=t.attr.jid;
});
end
end
if not self.disco.cache[a]then
self.disco.cache[a]={nodes={}};
end
if t then
if not self.disco.cache.nodes[t]then
self.disco.cache.nodes[t]={nodes={}};
end
self.disco.cache.nodes[t].items=e;
else
self.disco.cache[a].items=e;
end
return i(e);
end);
end
e:hook("iq/http://jabber.org/protocol/disco#info",function(t)
if t.attr.type=='get'then
local o=t:child_with_name('query')
if not o then return;end
local s
local i
if o.attr.node then
local h=h()
local n=e.disco.nodes[o.attr.node]
if n and n.info then
s=n.info.identities or{}
i=n.info.identities or{}
elseif o.attr.node==e.caps.node..'#'..h then
s=e.disco.info.identities
i=e.disco.info.features
else
local t=a.stanza('iq',{
to=t.attr.from,
from=t.attr.to,
id=t.attr.id,
type='error'
})
t:tag('query',{xmlns='http://jabber.org/protocol/disco#info'}):reset()
t:tag('error',{type='cancel'}):tag(
'item-not-found',{xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'}
)
e:send(t)
return true
end
else
s=e.disco.info.identities
i=e.disco.info.features
end
local o=a.stanza('query',{
xmlns='http://jabber.org/protocol/disco#info',
node=o.attr.node
})
for t,e in pairs(s)do
o:tag('identity',e):reset()
end
for a,t in pairs(i)do
o:tag('feature',t):reset()
end
e:send(a.stanza('iq',{
to=t.attr.from,
from=t.attr.to,
id=t.attr.id,
type='result'
}):add_child(o))
return true
end
end);
e:hook("iq/http://jabber.org/protocol/disco#items",function(t)
if t.attr.type=='get'then
local o=t:child_with_name('query')
if not o then return;end
local i
if o.attr.node then
local o=e.disco.nodes[o.attr.node]
if o then
i=o.items or{}
else
local t=a.stanza('iq',{
to=t.attr.from,
from=t.attr.to,
id=t.attr.id,
type='error'
})
t:tag('query',{xmlns='http://jabber.org/protocol/disco#items'}):reset()
t:tag('error',{type='cancel'}):tag(
'item-not-found',{xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'}
)
e:send(t)
return true
end
else
i=e.disco.items
end
local o=a.stanza('query',{
xmlns='http://jabber.org/protocol/disco#items',
node=o.attr.node
})
for a,t in pairs(i)do
o:tag('item',t):reset()
end
e:send(a.stanza('iq',{
to=t.attr.from,
from=t.attr.to,
id=t.attr.id,
type='result'
}):add_child(o))
return true
end
end);
e:hook("ready",function()
e:disco_local_services(function(t)
for a,t in ipairs(t)do
for a in pairs(e.disco.cache[t.jid].identities)do
local a,o=a:match("^(.*)/(.*)$");
e:event("disco/service-discovered/"..a,{
type=o,jid=t.jid;
});
end
end
end);
end,5);
end
end)
package.preload['verse.client']=(function(...)
local t=require"verse";
local a=t.stream_mt;
local h=require"util.jid".split;
local s=require"lxp";
local o=require"util.stanza";
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"core.xmlhandlers";
local n="http://etherx.jabber.org/streams";
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)
return e:event("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 a:reset()
local e=s.new(r(self,i),"\1");
self.parser=e;
self.notopen=true;
return true;
end
function a:connect_client(e,o)
self.jid,self.password=e,o;
self.username,self.host,self.resource=h(e);
self:add_plugin("tls");
self:add_plugin("sasl");
self:add_plugin("bind");
self:add_plugin("session");
function self.data(t,e)
local t,o=self.parser:parse(e);
if t then return;end
a:debug("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)
if e.attr.xmlns==nil or e.attr.xmlns=="jabber:client"then
if e.name=="iq"and(e.attr.type=="get"or e.attr.type=="set")then
local a=e.tags[1]and e.tags[1].attr.xmlns;
if a then
ret=self:event("iq/"..a,e);
if not ret then
ret=self:event("iq",e);
end
end
if ret==nil then
self:send(t.error_reply(e,"cancel","service-unavailable"));
return true;
end
else
ret=self:event(e.name,e);
end
end
return ret;
end,-1);
local function e()
self:event("ready");
end
self:hook("session-success",e,-1)
self:hook("bind-success",e,-1);
local e=self.close;
function self:close(t)
if not self.notopen then
self:send("</stream:stream>");
end
return e(self);
end
self:connect(self.connect_host or self.host,self.connect_port or 5222);
self:reopen();
end
function a:reopen()
self:reset();
self:send(o.stanza("stream:stream",{to=self.host,["xmlns:stream"]='http://etherx.jabber.org/streams',
xmlns="jabber:client",version="1.0"}):top_tag());
end
function a:send_iq(e,a)
local t=self:new_id();
self.tracked_iqs[t]=a;
e.attr.id=t;
self:send(e);
end
function a:new_id()
self.curr_id=self.curr_id+1;
return tostring(self.curr_id);
end
end)
pcall(require,"luarocks.require");
pcall(require,"ssl");
local a=require"net.server";
local n=require"util.events";
module("verse",package.seeall);
local t=_M;
local e={};
e.__index=e;
stream_mt=e;
t.plugins={};
function t.new(o,a)
local e=setmetatable(a or{},e);
e.id=tostring(e):match("%x*$");
e:set_logger(o,true);
e.events=n.new();
return e;
end
t.add_task=require"util.timer".add_task;
function t.loop()
return a.loop();
end
function t.quit()
return a.setquitting(true);
end
t.logger=logger.init;
function t.set_logger(e)
a.setlogger(e);
end
function e:connect(e,t)
e=e or"localhost";
t=tonumber(t)or 5222;
local i=socket.tcp()
i:settimeout(0);
local n,o=i:connect(e,t);
if not n and o~="timeout"then
self:warn("connect() to %s:%d failed: %s",e,t,o);
return false,o;
end
local e=a.wrapclient(i,e,t,new_listener(self),"*a");
if not e then
return false,o;
end
self.conn=e;
local t,a=e.write,tostring;
self.send=function(i,o)return t(e,a(o));end
return true;
end
function e:close()
local e=self.conn.disconnect();
self.conn:close();
e(conn,reason);
end
function e:debug(...)
if self.logger and self.log.debug then
return self.logger("debug",...);
end
end
function e:warn(...)
if self.logger and self.log.warn then
return self.logger("warn",...);
end
end
function e:error(...)
if self.logger and self.log.error then
return self.logger("error",...);
end
end
function e:set_logger(t,e)
local a=self.logger;
if t then
self.logger=t;
end
if e then
if e==true then
e={"debug","info","warn","error"};
end
self.log={};
for t,e in ipairs(e)do
self.log[e]=true;
end
end
return a;
end
function stream_mt:set_log_levels(e)
self:set_logger(nil,e);
end
function e:event(e,...)
self:debug("Firing event: "..tostring(e));
return self.events.fire_event(e,...);
end
function e:hook(e,...)
return self.events.add_handler(e,...);
end
function e:unhook(e,t)
return self.events.remove_handler(e,t);
end
function t.eventable(t)
t.events=n.new();
t.hook,t.unhook=e.hook,e.unhook;
local e=t.events.fire_event;
function t:event(t,...)
return e(t,...);
end
return t;
end
function e:add_plugin(e)
if require("verse.plugins."..e)then
local a,t=t.plugins[e](self);
if a then
self:debug("Loaded %s plugin",e);
else
self:warn("Failed to load %s plugin: %s",e,t);
end
end
return self;
end
function new_listener(e)
local t={};
function t.onconnect(a)
e.connected=true;
e.send=function(t,e)t:debug("Sending data: "..tostring(e));return a:write(tostring(e));end;
e:event("connected");
end
function t.onincoming(a,t)
e:event("incoming-raw",t);
end
function t.ondisconnect(a,t)
e.connected=false;
e:event("disconnected",{reason=t});
end
function t.ondrain(t)
e:event("drained");
end
function t.onstatus(a,t)
e:event("status",t);
end
return t;
end
local e=require"util.logger".init("verse");
return t;