Mercurial > nix
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={["'"]="'",["\""]=""",["<"]="<",[">"]=">",["&"]="&"}; 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;