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