# HG changeset patch # User Kooda # Date 1282921232 -7200 # Node ID 0d4d8b43298097d96424c32a00d6c92356a7c3d8 Initial commit. diff --git a/config.lua b/config.lua new file mode 100644 --- /dev/null +++ b/config.lua @@ -0,0 +1,21 @@ +jid = "bot@upyum.com"; +password = "bot"; +nickname = "Nix-ng"; +debug = true; +mode="normal" -- Ne sert à rien… + +-- Les admins ne sont rien pour l'instant. +admins = { + "kooda@upyum.com", + "apteno@im.apteno.fr" +}; + +muc1 = { + jid = "test1@muc.upyum.com"; + -- password = "blabla"; +}; + +muc2 = { + jid = "test2@muc.upyum.com"; + -- password = "blabla"; +}; diff --git a/libs/utils.lua b/libs/utils.lua new file mode 100644 --- /dev/null +++ b/libs/utils.lua @@ -0,0 +1,54 @@ +-- +-------------------------------------------------------------------------------- +-- FILE: utils.lua +-- USAGE: ./utils.lua +-- DESCRIPTION: A couple of functions +-- OPTIONS: --- +-- REQUIREMENTS: --- +-- BUGS: --- +-- NOTES: --- +-- AUTHOR: Adrien Ramos (M), +-- COMPANY: +-- VERSION: 1.0 +-- CREATED: 07/08/2010 23:40:24 CEST +-- REVISION: --- +-------------------------------------------------------------------------------- +-- + +local verse = require("verse"); +local st = require("util.stanza"); + +-- Lit le fichier de configuration (par défaut config.lua) et stocke la configuration +-- dans la table config +function config_load(filename) + local filename = filename or "config.lua" + -- Config loading + local chunk, err = loadfile(filename); + if not chunk then + print("File or syntax error:", err); + return 1; + end + + local config = {}; + setfenv(chunk, setmetatable(config, {__index = _G})); + local ok, err = pcall(chunk); + if not ok then + print("Error while processing config:", err); + return 1; + end + setmetatable(config, nil) + return config; +end + +function room_join(stream, room, nick) + if not nick or not room or not stream then + error("room_join needs the following arguments : stream room nick"); + end + s = st.presence{from=stream.username.."@"..stream.host.."/"..stream.resource, to = room.jid.."/"..nick}; + s:tag("x", {xmlns="http://jabber.org/protocol/muc"}); + if room.password then + s:tag("password"):text(room.password); + end + print("XML OUT:", s); + stream:send(s); +end diff --git a/tiny-nix.lua b/tiny-nix.lua new file mode 100755 --- /dev/null +++ b/tiny-nix.lua @@ -0,0 +1,58 @@ +require("libs.utils"); +local verse = require("verse"); +require("verse.client"); + +local config = config_load(); +room_map = { + [config.muc1.jid] = config.muc2.jid; + [config.muc2.jid] = config.muc1.jid; +}; +local bot = verse.new(); +local occupants = { + [config.muc1.jid] = {}; + [config.muc2.jid] = {}; +}; + +function new_clone(table, room, nick, presence) + table[room][nick] = verse.new(); + table[room][nick]:connect_client(config.jid.."/"..nick, config.password); + table[room][nick]:hook("ready", function() + table[room][nick]:send(presence); + end); +end; + +function stanza_split(stanza) + return jid.bare(stanza.attr.from), + room_map[jid.bare(stanza.attr.from)], + select(3, jid.split(stanza.attr.from)); +end + +bot:hook("ready", function() + room_join(bot, config.muc1, config.nickname); + room_join(bot, config.muc2, config.nickname); +end); + +bot:hook("stanza", function(stanza) + local room_from, room_to, nick = stanza_split(stanza); + if not room_from or not room_to or not nick or nick == config.nickname then return; end; + if occupants[room_from][nick] then return; end; + if stanza.name == "message" and stanza.attr.type ~= "groupchat" then return; end; + + local reply = stanza; reply.attr.from = nil; + reply.attr.to = room_to; + if stanza.name == "presence" then + reply.attr.to = room_to.."/"..nick; + if not occupants[room_to][nick] then new_clone(occupants, room_to, nick, reply); end; + if stanza.attr.type == "unavailable" then + occupants[room_to][nick]:send(reply); + occupants[room_to][nick]:close(); + occupants[room_to][nick] = nil; + occupants[room_from][nick] = nil; + return; + end + end + if occupants[room_to][nick] then occupants[room_to][nick]:send(reply); end; +end); + +bot:connect_client(config.jid, config.password); +verse.loop(); diff --git a/verse.lua b/verse.lua new file mode 100644 --- /dev/null +++ b/verse.lua @@ -0,0 +1,3207 @@ +package.preload['util.encodings']=(function(...) +local function e() +error("Encoding function not implemented"); +end +module"encodings" +stringprep={}; +base64={encode=e,decode=e}; +return _M; +end) +package.preload['util.logger']=(function(...) +local o=print +local i,t=select,tostring; +module"logger" +local function e(o,...) +local e,a=0,#arg; +return(o:gsub("%%(.)",function(o)if o~="%"and e<=a then e=e+1;return t(arg[e]);end end)); +end +local function n(a,...) +local e,o=0,i('#',...); +local i={...}; +return(a:gsub("%%(.)",function(a)if e<=o then e=e+1;return t(i[e]);end end)); +end +function init(e) +return function(e,t,...) +o(e,n(t,...)); +end +end +return _M; +end) +package.preload['util.xstanza']=(function(...) +local e=getmetatable(require"util.stanza".stanza()); +local i="urn:ietf:params:xml:ns:xmpp-stanzas"; +function e:get_error() +local o,t,a; +local e=self:get_child("error"); +if not e then +return nil,nil; +end +o=e.attr.type; +for e in e:children()do +if e.attr.xmlns==i then +if e.name=="text"then +a=e:get_text(); +else +t=e.name; +end +if t and a then +break; +end +end +end +return o,t,a; +end +end) +package.preload['util.stanza']=(function(...) +local e=table.insert; +local t=table.concat; +local r=table.remove; +local y=table.concat; +local s=string.format; +local w=string.match; +local l=tostring; +local d=setmetatable; +local p=getmetatable; +local i=pairs; +local n=ipairs; +local o=type; +local t=next; +local t=print; +local t=unpack; +local m=string.gsub; +local t=string.char; +local u=string.find; +local t=os; +local c=not t.getenv("WINDIR"); +local h,a; +if c then +local t,e=pcall(require,"util.termcolours"); +if t then +h,a=e.getstyle,e.getstring; +else +c=nil; +end +end +local f="urn:ietf:params:xml:ns:xmpp-stanzas"; +module"stanza" +stanza_mt={__type="stanza"}; +stanza_mt.__index=stanza_mt; +function stanza(t,e) +local e={name=t,attr=e or{},tags={},last_add={}}; +return d(e,stanza_mt); +end +function stanza_mt:query(e) +return self:tag("query",{xmlns=e}); +end +function stanza_mt:body(e,t) +return self:tag("body",t):text(e); +end +function stanza_mt:tag(a,t) +local t=stanza(a,t); +(self.last_add[#self.last_add]or self):add_direct_child(t); +e(self.last_add,t); +return self; +end +function stanza_mt:text(e) +(self.last_add[#self.last_add]or self):add_direct_child(e); +return self; +end +function stanza_mt:up() +r(self.last_add); +return self; +end +function stanza_mt:reset() +local e=self.last_add; +for t=1,#e do +e[t]=nil; +end +return self; +end +function stanza_mt:add_direct_child(t) +if o(t)=="table"then +e(self.tags,t); +end +e(self,t); +end +function stanza_mt:add_child(e) +(self.last_add[#self.last_add]or self):add_direct_child(e); +return self; +end +function stanza_mt:get_child(a,t) +for o,e in n(self.tags)do +if(not a or e.name==a) +and((not t and self.attr.xmlns==e.attr.xmlns) +or e.attr.xmlns==t)then +return e; +end +end +end +function stanza_mt:child_with_name(t) +for a,e in n(self.tags)do +if e.name==t then return e;end +end +end +function stanza_mt:child_with_ns(t) +for a,e in n(self.tags)do +if e.attr.xmlns==t then return e;end +end +end +function stanza_mt:children() +local e=0; +return function(t) +e=e+1 +local e=t[e] +if e then return e;end +end,self,e; +end +function stanza_mt:childtags() +local e=0; +return function(t) +e=e+1 +local e=self.tags[e] +if e then return e;end +end,self.tags[1],e; +end +local r +do +local e={["'"]="'",["\""]=""",["<"]="<",[">"]=">",["&"]="&"}; +function r(t)return(m(t,"['&<>\"]",e));end +_M.xml_escape=r; +end +local function m(o,t,h,a,r) +local n=0; +local s=o.name +e(t,"<"..s); +for o,i in i(o.attr)do +if u(o,"\1",1,true)then +local o,s=w(o,"^([^\1]*)\1?(.*)$"); +n=n+1; +e(t," xmlns:ns"..n.."='"..a(o).."' ".."ns"..n..":"..s.."='"..a(i).."'"); +elseif not(o=="xmlns"and i==r)then +e(t," "..o.."='"..a(i).."'"); +end +end +local i=#o; +if i==0 then +e(t,"/>"); +else +e(t,">"); +for i=1,i do +local i=o[i]; +if i.name then +h(i,t,h,a,o.attr.xmlns); +else +e(t,a(i)); +end +end +e(t,""); +end +end +function stanza_mt.__tostring(t) +local e={}; +m(t,e,m,r,nil); +return y(e); +end +function stanza_mt.top_tag(t) +local e=""; +if t.attr then +for t,a in i(t.attr)do if o(t)=="string"then e=e..s(" %s='%s'",t,r(l(a)));end end +end +return s("<%s%s>",t.name,e); +end +function stanza_mt.get_text(e) +if#e.tags==0 then +return y(e); +end +end +function stanza_mt.get_error(e) +local o,a,t; +local e=e:get_child("error"); +if not e then +return nil,nil,nil; +end +o=e.attr.type; +for e in e:children()do +if e.attr.xmlns==f then +if not t and e.name=="text"then +t=e:get_text(); +elseif not a then +a=e.name; +end +if a and t then +break; +end +end +end +return o,a or"undefined-condition",t or""; +end +function stanza_mt.__add(e,t) +return e:add_direct_child(t); +end +do +local e=0; +function new_id() +e=e+1; +return"lx"..e; +end +end +function preserialize(a) +local t={name=a.name,attr=a.attr}; +for i,a in n(a)do +if o(a)=="table"then +e(t,preserialize(a)); +else +e(t,a); +end +end +return t; +end +function deserialize(t) +if t then +local a=t.attr; +for e=1,#a do a[e]=nil;end +local s={}; +for e in i(a)do +if u(e,"|",1,true)and not u(e,"\1",1,true)then +local o,t=w(e,"^([^|]+)|(.+)$"); +s[o.."\1"..t]=a[e]; +a[e]=nil; +end +end +for e,t in i(s)do +a[e]=t; +end +d(t,stanza_mt); +for t,e in n(t)do +if o(e)=="table"then +deserialize(e); +end +end +if not t.tags then +local a={}; +for n,i in n(t)do +if o(i)=="table"then +e(a,i); +end +end +t.tags=a; +if not t.last_add then +t.last_add={}; +end +end +end +return t; +end +function clone(n) +local t={}; +local function a(e) +if o(e)~="table"then +return e; +elseif t[e]then +return t[e]; +end +local o={}; +t[e]=o; +for t,e in i(e)do +o[a(t)]=a(e); +end +return d(o,p(e)); +end +return a(n) +end +function message(t,e) +if not e then +return stanza("message",t); +else +return stanza("message",t):tag("body"):text(e); +end +end +function iq(e) +if e and not e.id then e.id=new_id();end +return stanza("iq",e or{id=new_id()}); +end +function reply(e) +return stanza(e.name,e.attr and{to=e.attr.from,from=e.attr.to,id=e.attr.id,type=((e.name=="iq"and"result")or e.attr.type)}); +end +do +local a={xmlns=f}; +function error_reply(e,o,i,t) +local e=reply(e); +e.attr.type="error"; +e:tag("error",{type=o}) +:tag(i,a):up(); +if(t)then e:tag("text",a):text(t):up();end +return e; +end +end +function presence(e) +return stanza("presence",e); +end +if c then +local d=h("yellow"); +local u=h("red"); +local t=h("red"); +local e=h("magenta"); +local h=" "..a(d,"%s")..a(e,"=")..a(u,"'%s'"); +local d=a(e,"<")..a(t,"%s").."%s"..a(e,">"); +local u=d.."%s"..a(e,""); +function stanza_mt.pretty_print(t) +local e=""; +for a,t in n(t)do +if o(t)=="string"then +e=e..r(t); +else +e=e..t:pretty_print(); +end +end +local a=""; +if t.attr then +for e,t in i(t.attr)do if o(e)=="string"then a=a..s(h,e,l(t));end end +end +return s(u,t.name,a,e,t.name); +end +function stanza_mt.pretty_top_tag(t) +local e=""; +if t.attr then +for t,a in i(t.attr)do if o(t)=="string"then e=e..s(h,t,l(a));end end +end +return s(d,t.name,e); +end +else +stanza_mt.pretty_print=stanza_mt.__tostring; +stanza_mt.pretty_top_tag=stanza_mt.top_tag; +end +return _M; +end) +package.preload['util.timer']=(function(...) +local d=require"net.server".addtimer; +local i=require"net.server".event; +local l=require"net.server".event_base; +local n=os.time; +local s=table.insert; +local e=table.remove; +local e,h=ipairs,pairs; +local r=type; +local o={}; +local a={}; +module"timer" +local t; +if not i then +function t(e,o) +local t=n(); +e=e+t; +if e>=t then +s(a,{e,o}); +else +o(); +end +end +d(function() +local e=n(); +if#a>0 then +for t,e in h(a)do +s(o,e); +end +a={}; +end +for n,a in h(o)do +local i,a=a[1],a[2]; +if i<=e then +o[n]=nil; +local e=a(e); +if r(e)=="number"then t(e,a);end +end +end +end); +else +local o=(i.core and i.core.LEAVE)or-1; +function t(a,t) +local e; +e=l:addevent(nil,0,function() +local t=t(); +if t then +return 0,t; +elseif e then +return o; +end +end +,a); +end +end +add_task=t; +return _M; +end) +package.preload['util.termcolours']=(function(...) +local a,o=table.concat,table.insert; +local e,i=string.char,string.format; +local s=ipairs; +module"termcolours" +local h={ +reset=0;bright=1,dim=2,underscore=4,blink=5,reverse=7,hidden=8; +black=30;red=31;green=32;yellow=33;blue=34;magenta=35;cyan=36;white=37; +["black background"]=40;["red background"]=41;["green background"]=42;["yellow background"]=43;["blue background"]=44;["magenta background"]=45;["cyan background"]=46;["white background"]=47; +bold=1,dark=2,underline=4,underlined=4,normal=0; +} +local n=e(27).."[%sm%s"..e(27).."[0m"; +function getstring(e,t) +if e then +return i(n,e,t); +else +return t; +end +end +function getstyle(...) +local e,t={...},{}; +for a,e in s(e)do +e=h[e]; +if e then +o(t,e); +end +end +return a(t,";"); +end +return _M; +end) +package.preload['util.uuid']=(function(...) +local e=math.random; +local n=tostring; +local e=os.time; +local i=os.clock; +local a=require"util.hashes".sha1; +module"uuid" +local t=0; +local function o() +local e=e(); +if t>=e then e=t+1;end +t=e; +return e; +end +local function t(e) +return a(e..i()..n({}),true); +end +local e=t(o()); +local function a(a) +e=t(e..a); +end +local function t(t) +if#em then +i("server.lua: refused new client connection: server full") +return false +end +local t,a=v(t) +if t then +local a,o=t:getpeername() +t:settimeout(0) +local e,n,t=N(e,u,t,a,c,o,y,f) +if t then +return false +end +r=r+1 +i("server.lua: accepted new client connection from ",s(a),":",s(o)," to ",s(c)) +return p(e) +elseif a then +i("server.lua: error with new client connection: ",s(a)) +return false +end +end +return e +end +N=function(V,e,t,W,B,F,T,q) +t:settimeout(0) +local w +local x +local k +local j +local C=e.onincoming +local R=e.onstatus +local v=e.ondisconnect +local L=e.ondrain +local y={} +local r=0 +local Y +local H +local O +local m=0 +local b=false +local A=false +local S,N=0,0 +local E=E +local z=z +local e=y +e.dispatch=function() +return C +end +e.disconnect=function() +return v +end +e.setlistener=function(a,t) +C=t.onincoming +v=t.ondisconnect +R=t.onstatus +L=t.ondrain +end +e.getstats=function() +return N,S +end +e.ssl=function() +return j +end +e.sslctx=function() +return q +end +e.send=function(n,i,o,a) +return w(t,i,o,a) +end +e.receive=function(o,a) +return x(t,o,a) +end +e.shutdown=function(a) +return k(t,a) +end +e.setoption=function(i,o,a) +if t.setoption then +return t:setoption(o,a); +end +return false,"setoption not implemented"; +end +e.close=function(u,s) +if not e then return true;end +a=l(h,t,a) +p[e]=nil +if r~=0 then +if not(s or H)then +e.sendbuffer() +if r~=0 then +if e then +e.write=nil +end +Y=true +return false +end +else +w(t,ae(y,"",1,r),1,m) +end +end +if t then +c=k and k(t) +t:close() +o=l(d,t,o) +n[t]=nil +t=nil +else +i"server.lua: socket already closed" +end +if e then +f[e]=nil +_[e]=nil +e=nil +end +if V then +V.remove() +end +i"server.lua: closed client handler and removed socket from list" +return true +end +e.ip=function() +return W +end +e.serverport=function() +return B +end +e.clientport=function() +return F +end +local _=function(i,a) +m=m+te(a) +if m>E then +_[e]="send buffer exceeded" +e.write=P +return false +elseif t and not d[t]then +o=addsocket(d,t,o) +end +r=r+1 +y[r]=a +if e then +f[e]=f[e]or u +end +return true +end +e.write=_ +e.bufferqueue=function(t) +return y +end +e.socket=function(a) +return t +end +e.set_mode=function(a,t) +T=t or T +return T +end +e.set_send=function(a,t) +w=t or w +return w +end +e.bufferlen=function(o,a,t) +E=t or E +z=a or z +return m,z,E +end +e.lock_read=function(i,o) +if o==true then +local o=a +a=l(h,t,a) +p[e]=nil +if a~=o then +b=true +end +elseif o==false then +if b then +b=false +a=addsocket(h,t,a) +p[e]=u +end +end +return b +end +e.pause=function(t) +return t:lock_read(true); +end +e.resume=function(t) +return t:lock_read(false); +end +e.lock=function(i,a) +e.lock_read(a) +if a==true then +e.write=P +local a=o +o=l(d,t,o) +f[e]=nil +if o~=a then +A=true +end +elseif a==false then +e.write=_ +if A then +A=false +_("") +end +end +return b,A +end +local z=function() +local o,t,a=x(t,T) +if not t or(t=="wantread"or t=="timeout")then +local o=o or a or"" +local a=te(o) +if a>z then +v(e,"receive buffer exceeded") +e:close(true) +return false +end +local a=a*ne +N=N+a +U=U+a +p[e]=u +return C(e,o,t) +else +i("server.lua: client ",s(W),":",s(F)," read error: ",s(t)) +H=true +v(e,t) +c=e and e:close() +return false +end +end +local f=function() +local b,a,h,n,p; +local p; +if t then +n=ae(y,"",1,r) +b,a,h=w(t,n,1,m) +p=(b or h or 0)*ne +S=S+p +I=I+p +c=D and ee(y) +else +b,a,p=false,"closed",0; +end +if b then +r=0 +m=0 +o=l(d,t,o) +f[e]=nil +if L then +L(e) +end +c=O and e:starttls(nil) +c=Y and e:close() +return true +elseif h and(a=="timeout"or a=="wantwrite")then +n=ue(n,h+1,m) +y[1]=n +r=1 +m=m-h +f[e]=u +return true +else +i("server.lua: client ",s(W),":",s(F)," write error: ",s(a)) +H=true +v(e,a) +c=e and e:close() +return false +end +end +local u; +function e.set_sslctx(n,t) +j=true +q=t; +local m +local r +u=ce(function(t) +local n +for u=1,_maxsslhandshake do +o=(m and l(d,t,o))or o +a=(r and l(h,t,a))or a +r,m=nil,nil +c,n=t:dohandshake() +if not n then +i("server.lua: ssl handshake done") +e.readbuffer=z +e.sendbuffer=f +c=R and R(e,"ssl-handshake-complete") +a=addsocket(h,t,a) +return true +else +i("server.lua: error during ssl handshake: ",s(n)) +if n=="wantwrite"and not m then +o=addsocket(d,t,o) +m=true +elseif n=="wantread"and not r then +a=addsocket(h,t,a) +r=true +else +break; +end +me() +end +end +v(e,"ssl handshake failed") +c=e and e:close(true) +return false +end +) +end +if g then +if q then +e:set_sslctx(q); +i("server.lua: ","starting ssl handshake") +local a +t,a=ie(t,q) +if a then +i("server.lua: ssl error: ",s(a)) +return nil,nil,a +end +t:settimeout(0) +e.readbuffer=u +e.sendbuffer=u +u(t) +if not t then +return nil,nil,"ssl handshake failed"; +end +else +local c; +e.starttls=function(f,m) +if m then +c=m; +e:set_sslctx(c); +end +if r>0 then +i"server.lua: we need to do tls, but delaying until send buffer empty" +O=true +return +end +i("server.lua: attempting to start tls on "..s(t)) +local m,r=t +t,r=ie(t,c) +if r then +i("server.lua: error while starting tls on client: ",s(r)) +return nil,r +end +t:settimeout(0) +w=t.send +x=t.receive +k=M +n[t]=e +a=addsocket(h,t,a) +a=l(h,m,a) +o=l(d,m,o) +n[m]=nil +e.starttls=nil +O=nil +j=true +e.readbuffer=u +e.sendbuffer=u +u(t) +end +e.readbuffer=z +e.sendbuffer=f +end +else +e.readbuffer=z +e.sendbuffer=f +end +w=t.send +x=t.receive +k=(j and M)or t.shutdown +n[t]=e +a=addsocket(h,t,a) +return e,t +end +M=function() +end +P=function() +return false +end +addsocket=function(t,a,e) +if not t[a]then +e=e+1 +t[e]=a +t[a]=e +end +return e; +end +l=function(e,a,t) +local i=e[a] +if i then +e[a]=nil +local o=e[t] +e[t]=nil +if o~=a then +e[o]=i +e[i]=o +end +return t-1 +end +return t +end +C=function(e) +o=l(d,e,o) +a=l(h,e,a) +n[e]=nil +e:close() +end +local function l(e,t,o) +local a; +local i=t.sendbuffer; +function t.sendbuffer() +i(); +if a and t.bufferlen()=o then +a=true; +e:lock_read(true); +end +end +end +K=function(o,e,d,l,r) +local t +if R(d)~="table"then +t="invalid listener table" +end +if R(e)~="number"or not(e>=0 and e<=65535)then +t="invalid port" +elseif v[e]then +t="listeners on port '"..e.."' already exist" +elseif r and not g then +t="luasec not found" +end +if t then +se("server.lua, port ",e,": ",t) +return nil,t +end +o=o or"*" +local t,s=de(o,e) +if s then +se("server.lua, port ",e,": ",s) +return nil,s +end +local s,d=J(d,t,o,e,l,r,b) +if not s then +t:close() +return nil,d +end +t:settimeout(0) +a=addsocket(h,t,a) +v[e]=s +n[t]=s +i("server.lua: new "..(r and"ssl "or"").."server listener on '",o,":",e,"'") +return s +end +X=function(e) +return v[e]; +end +he=function(e) +local t=v[e] +if not t then +return nil,"no server found on port '"..s(e).."'" +end +t:close() +v[e]=nil +return true +end +Q=function() +for e,t in q(n)do +t:close() +n[e]=nil +end +a=0 +o=0 +y=0 +v={} +h={} +d={} +S={} +n={} +end +Z=function() +return A,T,E,z,j,x,k,D,b,_maxsslhandshake +end +re=function(e) +if R(e)~="table"then +return nil,"invalid settings table" +end +A=tonumber(e.timeout)or A +T=tonumber(e.sleeptime)or T +E=tonumber(e.maxsendlen)or E +z=tonumber(e.maxreadlen)or z +j=tonumber(e.checkinterval)or j +x=tonumber(e.sendtimeout)or x +k=tonumber(e.readtimeout)or k +D=e.cleanqueue +b=e._maxclientsperserver or b +_maxsslhandshake=e._maxsslhandshake or _maxsslhandshake +return true +end +V=function(e) +if R(e)~="function"then +return nil,"invalid listener function" +end +y=y+1 +S[y]=e +return true +end +B=function() +return U,I,a,o,y +end +local e=true; +setquitting=function(t) +e=not t; +return; +end +G=function() +while e do +local a,e,t=fe(h,d,A) +for e,t in oe(e)do +local e=n[t] +if e then +e.sendbuffer() +else +C(t) +i"server.lua: found no handler and closed socket (writelist)" +end +end +for t,e in oe(a)do +local t=n[e] +if t then +t.readbuffer() +else +C(e) +i"server.lua: found no handler and closed socket (readlist)" +end +end +for e,t in q(_)do +e.disconnect()(e,t) +e:close(true) +end +ee(_) +u=Y() +if H(u-W)>=1 then +for e=1,y do +S[e](u) +end +W=u +end +le(T) +end +return"quitting" +end +local function h() +return"select"; +end +local i=function(t,i,s,a,e,h) +local e=N(nil,a,t,i,s,"clientport",e,h) +n[t]=e +o=addsocket(d,t,o) +if a.onconnect then +local t=e.sendbuffer; +e.sendbuffer=function() +a.onconnect(e); +e.sendbuffer=t; +if#e:bufferqueue()>0 then +return t(); +end +end +end +return e,t +end +local a=function(o,a,n,s,h) +local t,e=L.tcp() +if e then +return nil,e +end +t:settimeout(0) +c,e=t:connect(o,a) +if e then +local e=i(t,o,a,n) +else +N(nil,n,t,o,a,"clientport",s,h) +end +end +r"setmetatable"(n,{__mode="k"}) +r"setmetatable"(p,{__mode="k"}) +r"setmetatable"(f,{__mode="k"}) +W=Y() +F=Y() +V(function() +local e=H(u-F) +if e>j then +F=u +for e,t in q(f)do +if H(u-t)>x then +e.disconnect()(e,"send timeout") +e:close(true) +end +end +for e,t in q(p)do +if H(u-t)>k then +e.disconnect()(e,"read timeout") +e:close() +end +end +end +end +) +local function t(e) +local t=O; +if e then +O=e; +end +return t; +end +return{ +addclient=a, +wrapclient=i, +loop=G, +link=l, +stats=B, +closeall=Q, +addtimer=V, +addserver=K, +getserver=X, +setlogger=t, +getsettings=Z, +setquitting=setquitting, +removeserver=he, +get_backend=h, +changesettings=re, +} +end) +package.preload['core.xmlhandlers']=(function(...) +require"util.stanza" +local y=stanza; +local u=tostring; +local w=table.insert; +local l=table.concat; +local r=require"util.logger".init("xmlhandlers"); +local s=error; +module"xmlhandlers" +local f={ +["http://www.w3.org/XML/1998/namespace"]="xml"; +}; +local i="http://etherx.jabber.org/streams"; +local t="\1"; +local h="^([^"..t.."]*)"..t.."?(.*)$"; +function init_xmlhandlers(a,e) +local o={}; +local n={}; +local r=a.log or r; +local r=e.streamopened; +local d=e.streamclosed; +local s=e.error or function(t,e)s("XML stream error: "..u(e));end; +local m=e.handlestanza; +local i=e.stream_ns or i; +local c=i..t..(e.stream_tag or"stream"); +local p=i..t..(e.error_tag or"error"); +local u=e.default_ns; +local e; +function n:StartElement(n,t) +if e and#o>0 then +e:text(l(o)); +o={}; +end +local o,i=n:match(h); +if i==""then +o,i="",o; +end +if o~=u then +t.xmlns=o; +end +for e=1,#t do +local a=t[e]; +t[e]=nil; +local e,o=a:match(h); +if o~=""then +e=f[e]; +if e then +t[e..":"..o]=t[a]; +t[a]=nil; +end +end +end +if not e then +if a.notopen then +if n==c then +if r then +r(a,t); +end +else +s(a,"no-stream"); +end +return; +end +if o=="jabber:client"and i~="iq"and i~="presence"and i~="message"then +s(a,"invalid-top-level-element"); +end +e=y.stanza(i,t); +else +t.xmlns=nil; +if o~=u then +t.xmlns=o; +end +e:tag(i,t); +end +end +function n:CharacterData(t) +if e then +w(o,t); +end +end +function n:EndElement(t) +if e then +if#o>0 then +e:text(l(o)); +o={}; +end +if#e.last_add==0 then +if t~=p then +m(a,e); +else +s(a,"stream-error",e); +end +e=nil; +else +e:up(); +end +else +if t==c then +if d then +d(a); +end +else +local t,e=t:match(h); +if e==""then +t,e="",t; +end +s(a,"parse-error","unexpected-element-close",e); +end +e,o=nil,{}; +end +end +return n; +end +return init_xmlhandlers; +end) +package.preload['util.jid']=(function(...) +local t=string.match; +local h=require"util.encodings".stringprep.nodeprep; +local s=require"util.encodings".stringprep.nameprep; +local n=require"util.encodings".stringprep.resourceprep; +module"jid" +local function a(e) +if not e then return;end +local o,a=t(e,"^([^@]+)@()"); +local a,i=t(e,"^([^@/]+)()",a) +if o and not a then return nil,nil,nil;end +local t=t(e,"^/(.+)$",i); +if(not a)or((not t)and#e>=i)then return nil,nil,nil;end +return o,a,t; +end +split=a; +function bare(e) +local t,e=a(e); +if t and e then +return t.."@"..e; +end +return e; +end +local function o(e) +local t,e,a=a(e); +if e then +e=s(e); +if not e then return;end +if t then +t=h(t); +if not t then return;end +end +if a then +a=n(a); +if not a then return;end +end +return t,e,a; +end +end +prepped_split=o; +function prep(e) +local t,e,a=o(e); +if e then +if t then +e=t.."@"..e; +end +if a then +e=e.."/"..a; +end +end +return e; +end +function join(t,e,a) +if t and e and a then +return t.."@"..e.."/"..a; +elseif t and e then +return t.."@"..e; +elseif e and a then +return e.."/"..a; +elseif e then +return e; +end +return nil; +end +function compare(t,e) +local n,i,o=a(t); +local t,a,e=a(e); +if((t~=nil and t==n)or t==nil)and +((a~=nil and a==i)or a==nil)and +((e~=nil and e==o)or e==nil)then +return true +end +return false +end +return _M; +end) +package.preload['util.events']=(function(...) +local r=ipairs; +local i=pairs; +local s=table.insert; +local h=table.sort; +local d=select; +module"events" +function new() +local o={}; +local t={}; +local a={}; +local function n(o) +local a=a[o]; +local e=t[o]; +if e then +for t=#e,1,-1 do e[t]=nil;end +else e={};t[o]=e;end +for t in i(a)do +s(e,t); +end +h(e,function(t,e)return a[t]>a[e];end); +end; +local function s(t,o,i) +local e=a[t]; +if e then +e[o]=i or 0; +else +e={[o]=i or 0}; +a[t]=e; +end +n(t); +end; +local function h(t,o) +local e=a[t]; +if e then +e[o]=nil; +n(t); +end +end; +local function e(e) +for e,t in i(e)do +s(e,t); +end +end; +local function e(e) +for t,e in i(e)do +h(t,e); +end +end; +local function n(a) +local e=t[a]; +if not e then e={};t[a]=e;end +local e=function(...) +for t=1,#e do +local e=e[t](...); +if e~=nil then return e;end +end +end; +o[a]=e; +return e; +end; +local function i(e) +return o[e]or n(e); +end; +local function n(e,...) +local e=t[e]; +if e then +for t=1,#e do +local e=e[t](...); +if e~=nil then return e;end +end +end +end; +local function l(e,...) +local a=i(e); +local t={...}; +local e={}; +return function(...) +for t,o in r(t)do e[o]=d(t,...);end +a(e); +end; +end; +return{ +add_handler=s; +remove_handler=h; +add_plugin=add_plugin; +remove_plugin=remove_plugin; +get_dispatcher=i; +fire_event=n; +get_named_arg_dispatcher=l; +_dispatchers=o; +_handlers=t; +_event_map=a; +}; +end +return _M; +end) +package.preload['util.sha1']=(function(...) +local u=string.len +local a=string.char +local k=string.byte +local g=string.sub +local m=math.floor +local t=require"bit" +local b=t.bnot +local e=t.band +local p=t.bor +local n=t.bxor +local o=t.lshift +local i=t.rshift +local h,r,d,l,c +local function y(e,t) +return o(e,t)+i(e,32-t) +end +local function s(i) +local t,o +local t="" +for n=1,8 do +o=e(i,15) +if(o<10)then +t=a(o+48)..t +else +t=a(o+87)..t +end +i=m(i/16) +end +return t +end +local function j(t) +local i,o +local n="" +i=u(t)*8 +t=t..a(128) +o=56-e(u(t),63) +if(o<0)then +o=o+64 +end +for e=1,o do +t=t..a(0) +end +for t=1,8 do +n=a(e(i,255))..n +i=m(i/256) +end +return t..n +end +local function q(w) +local s,t,i,o,f,u,m,v +local a,a +local a={} +while(w~="")do +for e=0,15 do +a[e]=0 +for t=1,4 do +a[e]=a[e]*256+k(w,e*4+t) +end +end +for e=16,79 do +a[e]=y(n(n(a[e-3],a[e-8]),n(a[e-14],a[e-16])),1) +end +s=h +t=r +i=d +o=l +f=c +for r=0,79 do +if(r<20)then +u=p(e(t,i),e(b(t),o)) +m=1518500249 +elseif(r<40)then +u=n(n(t,i),o) +m=1859775393 +elseif(r<60)then +u=p(p(e(t,i),e(t,o)),e(i,o)) +m=2400959708 +else +u=n(n(t,i),o) +m=3395469782 +end +v=y(s,5)+u+f+m+a[r] +f=o +o=i +i=y(t,30) +t=s +s=v +end +h=e(h+s,4294967295) +r=e(r+t,4294967295) +d=e(d+i,4294967295) +l=e(l+o,4294967295) +c=e(c+f,4294967295) +w=g(w,65) +end +end +local function a(e,t) +e=j(e) +h=1732584193 +r=4023233417 +d=2562383102 +l=271733878 +c=3285377520 +q(e) +local e=s(h)..s(r)..s(d) +..s(l)..s(c); +if t then +return e; +else +return e:gsub("..",function(e) +return string.char(tonumber(e,16)); +end); +end +end +_G.sha1={sha1=a}; +return _G.sha1; +end) +package.preload['verse.plugins.tls']=(function(...) +local o=require"util.stanza"; +local t="urn:ietf:params:xml:ns:xmpp-tls"; +function verse.plugins.tls(e) +local function a(a) +if e.authenticated then return;end +if a:get_child("starttls",t)and e.conn.starttls then +e:debug("Negotiating TLS..."); +e:send(o.stanza("starttls",{xmlns=t})); +return true; +elseif not e.conn.starttls and not e.secure then +e:warn("SSL libary (LuaSec) not loaded, so TLS not available"); +elseif not e.secure then +e:debug("Server doesn't offer TLS :("); +end +end +local function o(t) +if t.name=="proceed"then +e:debug("Server says proceed, handshake starting..."); +e.conn:starttls({mode="client",protocol="sslv23",options="no_sslv2"},true); +end +end +local function i(t) +if t=="ssl-handshake-complete"then +e.secure=true; +e:debug("Re-opening stream..."); +e:reopen(); +end +end +e:hook("stream-features",a,400); +e:hook("stream/"..t,o); +e:hook("status",i,400); +return true; +end +end) +package.preload['verse.plugins.sasl']=(function(...) +local o=require"util.stanza"; +local e=require"util.xstanza"; +local t=require"mime".b64; +local a="urn:ietf:params:xml:ns:xmpp-sasl"; +function verse.plugins.sasl(e) +local function i(i) +if e.authenticated then return;end +e:debug("Authenticating with SASL..."); +local t=t("\0"..e.username.."\0"..e.password); +e:debug("Selecting PLAIN mechanism..."); +local a=o.stanza("auth",{xmlns=a,mechanism="PLAIN"}); +if t then +a:text(t); +end +e:send(a); +return true; +end +local function o(t) +if t.name=="success"then +e.authenticated=true; +e:event("authentication-success"); +elseif t.name=="failure"then +local t=t.tags[1]; +e:event("authentication-failure",{condition=t.name}); +end +e:reopen(); +return true; +end +e:hook("stream-features",i,300); +e:hook("stream/"..a,o); +return true; +end +end) +package.preload['verse.plugins.bind']=(function(...) +local t=require"util.stanza"; +local a="urn:ietf:params:xml:ns:xmpp-bind"; +function verse.plugins.bind(e) +local function i(o) +if e.bound then return;end +e:debug("Binding resource..."); +e:send_iq(t.iq({type="set"}):tag("bind",{xmlns=a}):tag("resource"):text(e.resource), +function(t) +if t.attr.type=="result"then +local t=t +:get_child("bind",a) +:get_child("jid") +:get_text(); +e.username,e.host,e.resource=jid.split(t); +e.jid,e.bound=t,true; +e:event("bind-success",full_jid); +elseif t.attr.type=="error"then +local a=t:child_with_name("error"); +local o,a,t=t:get_error(); +e:event("bind-failure",{error=a,text=t,type=o}); +end +end); +end +e:hook("stream-features",i,200); +return true; +end +end) +package.preload['verse.plugins.version']=(function(...) +local t="jabber:iq:version"; +local function a(e,t) +e.name=t.name; +e.version=t.version; +e.platform=t.platform; +end +function verse.plugins.version(e) +e.version={set=a}; +e:hook("iq/"..t,function(a) +if a.attr.type~="get"then return;end +local t=verse.reply(a) +:tag("query",{xmlns=t}); +if e.version.name then +t:tag("name"):text(tostring(e.version.name)):up(); +end +if e.version.version then +t:tag("version"):text(tostring(e.version.version)):up() +end +if e.version.platform then +t:tag("os"):text(e.version.platform); +end +e:send(t); +return true; +end); +function e:query_version(o,a) +a=a or function(t)return e:event("version/response",t);end +e:send_iq(verse.iq({type="get",to=o}) +:tag("query",{xmlns=t}), +function(o) +local e=o:get_child("query",t); +if o.attr.type=="result"then +local o=e:get_child("name"); +local t=e:get_child("version"); +local e=e:get_child("os"); +a({ +name=o and o:get_text()or nil; +version=t and t:get_text()or nil; +platform=e and e:get_text()or nil; +}); +else +local e,t,o=o:get_error(); +a({ +error=true; +condition=t; +text=o; +type=e; +}); +end +end); +end +return true; +end +end) +package.preload['verse.plugins.ping']=(function(...) +require"util.xstanza"; +local o="urn:xmpp:ping"; +function verse.plugins.ping(t) +function t:ping(e,a) +local n=socket.gettime(); +t:send_iq(verse.iq{to=e,type="get"}:tag("ping",{xmlns=o}), +function(t) +if t.attr.type=="error"then +local i,t,o=t:get_error(); +if t~="service-unavailable"and t~="feature-not-implemented"then +a(nil,e,{type=i,condition=t,text=o}); +return; +end +end +a(socket.gettime()-n,e); +end); +end +return true; +end +end) +package.preload['verse.plugins.session']=(function(...) +local i=require"util.stanza"; +local a="urn:ietf:params:xml:ns:xmpp-session"; +function verse.plugins.session(e) +local function o(t) +local t=t:get_child("session",a); +if t and not t:get_child("optional")then +local function o(t) +e:debug("Establishing Session..."); +e:send_iq(i.iq({type="set"}):tag("session",{xmlns=a}), +function(t) +if t.attr.type=="result"then +e:event("session-success"); +elseif t.attr.type=="error"then +local a=t:child_with_name("error"); +local a,t,o=t:get_error(); +e:event("session-failure",{error=t,text=o,type=a}); +end +end); +return true; +end +e:hook("bind-success",o); +end +end +e:hook("stream-features",o); +return true; +end +end) +package.preload['verse.plugins.compression']=(function(...) +local t=require"util.stanza"; +local e=require"zlib"; +local a="http://jabber.org/features/compress" +local a="http://jabber.org/protocol/compress" +local o="http://etherx.jabber.org/streams"; +local i=9; +local function s(o) +local i,e=pcall(e.deflate,i); +if i==false then +local t=t.stanza("failure",{xmlns=a}):tag("setup-failed"); +o:send(t); +o:error("Failed to create zlib.deflate filter: %s",tostring(e)); +return +end +return e +end +local function h(o) +local i,e=pcall(e.inflate); +if i==false then +local t=t.stanza("failure",{xmlns=a}):tag("setup-failed"); +o:send(t); +o:error("Failed to create zlib.inflate filter: %s",tostring(e)); +return +end +return e +end +local function n(e,o) +function e:send(a) +local o,a,i=pcall(o,tostring(a),'sync'); +if o==false then +e:close({ +condition="undefined-condition"; +text=a; +extra=t.stanza("failure",{xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed"); +}); +e:warn("Compressed send failed: %s",tostring(a)); +return; +end +e.conn:write(a); +end; +end +local function i(e,a) +local o=e.data +e.data=function(i,n) +e:debug("Decompressing data..."); +local n,a,s=pcall(a,n); +if n==false then +e:close({ +condition="undefined-condition"; +text=a; +extra=t.stanza("failure",{xmlns="http://jabber.org/protocol/compress"}):tag("processing-failed"); +}); +stream:warn("%s",tostring(a)); +return; +end +return o(i,a); +end; +end +function verse.plugins.compression(e) +local function r(o) +if not e.compressed then +local o=o:child_with_name("compression"); +if o then +for o in o:children()do +local o=o[1] +if o=="zlib"then +e:send(t.stanza("compress",{xmlns=a}):tag("method"):text("zlib")) +e:debug("Enabled compression using zlib.") +return true; +end +end +session:debug("Remote server supports no compression algorithm we support.") +end +end +end +local function o(t) +if t.name=="compressed"then +e:debug("Activating compression...") +local t=s(e); +if not t then return end +local a=h(e); +if not a then return end +n(e,t); +i(e,a); +e.compressed=true; +e:reopen(); +elseif t.name=="failure"then +e:warn("Failed to establish compression"); +end +end +e:hook("stream-features",r,250); +e:hook("stream/"..a,o); +end +end) +package.preload['verse.plugins.blocking']=(function(...) +local a="urn:xmpp:blocking"; +function verse.plugins.blocking(e) +e.blocking={}; +function e.blocking:block_jid(o,t) +e:send_iq(verse.iq{type="set"} +:tag("block",{xmlns=a}) +:tag("item",{jid=o}) +,function()return t and t(true);end +,function()return t and t(false);end +); +end +function e.blocking:unblock_jid(o,t) +e:send_iq(verse.iq{type="set"} +:tag("unblock",{xmlns=a}) +:tag("item",{jid=o}) +,function()return t and t(true);end +,function()return t and t(false);end +); +end +function e.blocking:unblock_all_jids(t) +e:send_iq(verse.iq{type="set"} +:tag("unblock",{xmlns=a}) +,function()return t and t(true);end +,function()return t and t(false);end +); +end +function e.blocking:get_blocked_jids(t) +e:send_iq(verse.iq{type="get"} +:tag("blocklist",{xmlns=a}) +,function(e) +local a=e:get_child("blocklist",a); +if not a then return t and t(false);end +local e={}; +for t in a:childtags()do +e[#e+1]=t.attr.jid; +end +return t and t(e); +end +,function(e)return t and t(false);end +); +end +end +end) +package.preload['verse.plugins.proxy65']=(function(...) +local e=require"util.events"; +local h=require"util.uuid"; +local r=require"util.sha1"; +local i={}; +i.__index=i; +local o="http://jabber.org/protocol/bytestreams"; +local n; +function verse.plugins.proxy65(t) +t.proxy65=setmetatable({stream=t},i); +t.proxy65.available_streamhosts={}; +local e=0; +t:hook("disco/service-discovered/proxy",function(a) +if a.type=="bytestreams"then +e=e+1; +t:send_iq(verse.iq({to=a.jid,type="get"}) +:tag("query",{xmlns=o}),function(a) +e=e-1; +if a.attr.type=="result"then +local e=a:get_child("query",o) +:get_child("streamhost").attr; +t.proxy65.available_streamhosts[e.jid]={ +jid=e.jid; +host=e.host; +port=tonumber(e.port); +}; +end +if e==0 then +t:event("proxy65/discovered-proxies",t.proxy65.available_streamhosts); +end +end); +end +end); +t:hook("iq/"..o,function(a) +local e=verse.new(nil,{ +initiator_jid=a.attr.from, +streamhosts={}, +current_host=0; +}); +for t in a.tags[1]:childtags()do +if t.name=="streamhost"then +table.insert(e.streamhosts,t.attr); +end +end +local function o() +if e.current_host<#e.streamhosts then +e.current_host=e.current_host+1; +e:connect( +e.streamhosts[e.current_host].host, +e.streamhosts[e.current_host].port +); +n(t,e,a.tags[1].attr.sid,a.attr.from,t.jid); +return true; +end +e:unhook("disconnected",o); +t:send(verse.error_reply(a,"cancel","item-not-found")); +end +function e:accept() +e:hook("disconnected",o,100); +e:hook("connected",function() +e:unhook("disconnected",o); +local e=verse.reply(a) +:tag("query",a.tags[1].attr) +:tag("streamhost-used",{jid=e.streamhosts[e.current_host].jid}); +t:send(e); +end,100); +o(); +end +function e:refuse() +end +t:event("proxy65/request",e); +end); +end +function i:new(t,s) +local e=verse.new(nil,{ +target_jid=t; +bytestream_sid=h.generate(); +}); +local a=verse.iq{type="set",to=t} +:tag("query",{xmlns=o,mode="tcp",sid=e.bytestream_sid}); +for t,e in ipairs(s or self.proxies)do +a:tag("streamhost",e):up(); +end +self.stream:send_iq(a,function(a) +if a.attr.type=="error"then +local a,o,t=a:get_error(); +e:event("connection-failed",{conn=e,type=a,condition=o,text=t}); +else +local a=a.tags[1]:get_child("streamhost-used"); +if not a then +end +e.streamhost_jid=a.attr.jid; +local a,i; +for o,t in ipairs(s or self.proxies)do +if t.jid==e.streamhost_jid then +a,i=t.host,t.port; +break; +end +end +if not(a and i)then +end +e:connect(a,i); +local function a() +e:unhook("connected",a); +local t=verse.iq{to=e.streamhost_jid,type="set"} +:tag("query",{xmlns=o,sid=e.bytestream_sid}) +:tag("activate"):text(t); +self.stream:send_iq(t,function(t) +if t.attr.type=="result"then +e:event("connected",e); +else +end +end); +return true; +end +e:hook("connected",a,100); +n(self.stream,e,e.bytestream_sid,self.stream.jid,t); +end +end); +return e; +end +function n(i,e,a,t,o) +local t=r.sha1(a..t..o); +local function a() +e:unhook("connected",a); +return true; +end +local function o(t) +e:unhook("incoming-raw",o); +if t:sub(1,2)~="\005\000"then +return e:event("error","connection-failure"); +end +e:event("connected"); +return true; +end +local function i(a) +e:unhook("incoming-raw",i); +if a~="\005\000"then +local t="version-mismatch"; +if a:sub(1,1)=="\005"then +t="authentication-failure"; +end +return e:event("error",t); +end +e:send(string.char(5,1,0,3,#t)..t.."\0\0"); +e:hook("incoming-raw",o,100); +return true; +end +e:hook("connected",a,200); +e:hook("incoming-raw",i,100); +e:send("\005\001\000"); +end +end) +package.preload['verse.plugins.jingle']=(function(...) +local e=require"util.sha1".sha1; +local n=require"util.stanza"; +local e=require"util.timer"; +local a=require"util.uuid".generate; +local o="urn:xmpp:jingle:1"; +local h="urn:xmpp:jingle:errors:1"; +local t={}; +t.__index=t; +local e={}; +local e={}; +function verse.plugins.jingle(e) +e:hook("ready",function() +e:add_disco_feature(o); +end,10); +function e:jingle(o) +return verse.eventable(setmetatable(base or{ +role="initiator"; +peer=o; +sid=a(); +stream=e; +},t)); +end +function e:register_jingle_transport(e) +end +function e:register_jingle_content_type(e) +end +local function u(i) +local r=i:get_child("jingle",o); +local a=r.attr.sid; +local s=r.attr.action; +local a=e:event("jingle/"..a,i); +if a==true then +e:send(verse.reply(i)); +return true; +end +if s~="session-initiate"then +local t=n.error_reply(i,"cancel","item-not-found") +:tag("unknown-session",{xmlns=h}):up(); +e:send(t); +return; +end +local l=r.attr.sid; +local a=verse.eventable{ +role="receiver"; +peer=i.attr.from; +sid=l; +stream=e; +}; +setmetatable(a,t); +local s; +local d,h; +for t in r:childtags()do +if t.name=="content"and t.attr.xmlns==o then +local i=t:child_with_name("description"); +local o=i.attr.xmlns; +if o then +local e=e:event("jingle/content/"..o,a,i); +if e then +d=e; +end +end +local o=t:child_with_name("transport"); +local i=o.attr.xmlns; +h=e:event("jingle/transport/"..i,a,o); +if d and h then +s=t; +break; +end +end +end +if not d then +e:send(n.error_reply(i,"cancel","feature-not-implemented","The specified content is not supported")); +return; +end +if not h then +e:send(n.error_reply(i,"cancel","feature-not-implemented","The specified transport is not supported")); +return; +end +e:send(n.reply(i)); +a.content_tag=s; +a.creator,a.name=s.attr.creator,s.attr.name; +a.content,a.transport=d,h; +function a:decline() +end +e:hook("jingle/"..l,function(e) +if e.attr.from~=a.peer then +return false; +end +local e=e:get_child("jingle",o); +return a:handle_command(e); +end); +e:event("jingle",a); +return true; +end +function t:handle_command(a) +local t=a.attr.action; +e:debug("Handling Jingle command: %s",t); +if t=="session-terminate"then +self:destroy(); +elseif t=="session-accept"then +self:handle_accepted(a); +elseif t=="transport-info"then +e:debug("Handling transport-info"); +self.transport:info_received(a); +elseif t=="transport-replace"then +e:error("Peer wanted to swap transport, not implemented"); +else +e:warn("Unhandled Jingle command: %s",t); +return nil; +end +return true; +end +function t:send_command(a,e,t) +local e=n.iq({to=self.peer,type="set"}) +:tag("jingle",{ +xmlns=o, +sid=self.sid, +action=a, +initiator=self.role=="initiator"and self.stream.jid or nil, +responder=self.role=="responder"and self.jid or nil, +}) +:tag("content",{creator=self.creator,name=self.name}) +:add_child(e); +if not t then +self.stream:send(e); +else +self.stream:send_iq(e,t); +end +end +function t:accept(a) +local t=n.iq({to=self.peer,type="set"}) +:tag("jingle",{ +xmlns=o, +sid=self.sid, +action="session-accept", +responder=e.jid, +}) +:tag("content",{creator=self.creator,name=self.name}); +local o=self.content:generate_accept(self.content_tag:child_with_name("description"),a); +t:add_child(o); +local a=self.transport:generate_accept(self.content_tag:child_with_name("transport"),a); +t:add_child(a); +local a=self; +e:send_iq(t,function(t) +if t.attr.type=="error"then +local a,t,a=t:get_error(); +e:error("session-accept rejected: %s",t); +return false; +end +a.transport:connect(function(t) +e:warn("CONNECTED (receiver)!!!"); +a.state="active"; +a:event("connected",t); +end); +end); +end +e:hook("iq/"..o,u); +return true; +end +function t:offer(t,a) +local e=n.iq({to=self.peer,type="set"}) +:tag("jingle",{xmlns=o,action="session-initiate", +initiator=self.stream.jid,sid=self.sid}); +e:tag("content",{creator=self.role,name=t}); +local t=self.stream:event("jingle/describe/"..t,a); +if not t then +return false,"Unknown content type"; +end +e:add_child(t); +local t=self.stream:event("jingle/transport/".."urn:xmpp:jingle:transports:s5b:1",self); +self.transport=t; +e:add_child(t:generate_initiate()); +self.stream:debug("Hooking %s","jingle/"..self.sid); +self.stream:hook("jingle/"..self.sid,function(e) +if e.attr.from~=self.peer then +return false; +end +local e=e:get_child("jingle",o); +return self:handle_command(e) +end); +self.stream:send_iq(e,function(e) +if e.type=="error"then +self.state="terminated"; +local e,a,t=e:get_error(); +return self:event("error",{type=e,condition=a,text=t}); +end +end); +self.state="pending"; +end +function t:terminate(e) +local e=verse.stanza("reason"):tag(e or"success"); +self:send_command("session-terminate",e,function(e) +self.state="terminated"; +self.transport:disconnect(); +self:destroy(); +end); +end +function t:destroy() +self.stream:unhook("jingle/"..self.sid,self.handle_command); +end +function t:handle_accepted(e) +local e=e:child_with_name("transport"); +self.transport:handle_accepted(e); +self.transport:connect(function(e) +print("CONNECTED (initiator)!") +self.state="active"; +self:event("connected",e); +end); +end +function t:set_source(a,o) +local function t() +local e,i=a(); +if e and e~=""then +self.transport.conn:send(e); +elseif e==""then +return t(); +elseif e==nil then +if o then +self:terminate(); +end +self.transport.conn:unhook("drained",t); +a=nil; +end +end +self.transport.conn:hook("drained",t); +t(); +end +function t:set_sink(t) +self.transport.conn:hook("incoming-raw",t); +self.transport.conn:hook("disconnected",function(e) +self.stream:debug("Closing sink..."); +local e=e.reason; +if e=="closed"then e=nil;end +t(nil,e); +end); +end +end) +package.preload['verse.plugins.jingle_ft']=(function(...) +local o=require"ltn12"; +local s=package.config:sub(1,1); +local a="urn:xmpp:jingle:apps:file-transfer:1"; +local i="http://jabber.org/protocol/si/profile/file-transfer"; +function verse.plugins.jingle_ft(t) +t:hook("ready",function() +t:add_disco_feature(a); +end,10); +local n={name="file"}; +function n:generate_accept(t,e) +if e and e.save_file then +self.jingle:hook("connected",function() +local e=o.sink.file(io.open(e.save_file,"w+")); +self.jingle:set_sink(e); +end); +end +return t; +end +local n={__index=n}; +t:hook("jingle/content/"..a,function(t,e) +local e=e:get_child("offer"):get_child("file",i); +local e={ +name=e.attr.name; +size=tonumber(e.attr.size); +}; +return setmetatable({jingle=t,file=e},n); +end); +t:hook("jingle/describe/file",function(e) +local t; +if e.timestamp then +t=os.date("!%Y-%m-%dT%H:%M:%SZ",e.timestamp); +end +return verse.stanza("description",{xmlns=a}) +:tag("offer") +:tag("file",{xmlns=i, +name=e.filename, +size=e.size, +date=t, +hash=e.hash, +}) +:tag("desc"):text(e.description or""); +end); +function t:send_file(a,t) +local e,i=io.open(t); +if not e then return e,i;end +local i=e:seek("end",0); +e:seek("set",0); +local o=o.source.file(e); +local e=c:jingle(a); +e:offer("file",{ +filename=t:match("[^"..s.."]+$"); +size=i; +}); +e:hook("connected",function() +e:set_source(o,true); +end); +return e; +end +end +end) +package.preload['verse.plugins.jingle_s5b']=(function(...) +local a="urn:xmpp:jingle:transports:s5b:1"; +local s=require"util.sha1".sha1; +local r=require"util.uuid".generate; +local function h(e,i) +local function n() +e:unhook("connected",n); +return true; +end +local function o(t) +e:unhook("incoming-raw",o); +if t:sub(1,2)~="\005\000"then +return e:event("error","connection-failure"); +end +e:event("connected"); +return true; +end +local function t(a) +e:unhook("incoming-raw",t); +if a~="\005\000"then +local t="version-mismatch"; +if a:sub(1,1)=="\005"then +t="authentication-failure"; +end +return e:event("error",t); +end +e:send(string.char(5,1,0,3,#i)..i.."\0\0"); +e:hook("incoming-raw",o,100); +return true; +end +e:hook("connected",n,200); +e:hook("incoming-raw",t,100); +e:send("\005\001\000"); +end +local function i(a,e,i) +local e=verse.new(nil,{ +streamhosts=e, +current_host=0; +}); +local function t(o) +if o then +return a(nil,o.reason); +end +if e.current_host<#e.streamhosts then +e.current_host=e.current_host+1; +e:debug("Attempting to connect to "..e.streamhosts[e.current_host].host..":"..e.streamhosts[e.current_host].port.."..."); +local t,a=e:connect( +e.streamhosts[e.current_host].host, +e.streamhosts[e.current_host].port +); +if not t then +e:debug("Error connecting to proxy (%s:%s): %s", +e.streamhosts[e.current_host].host, +e.streamhosts[e.current_host].port, +a +); +else +e:debug("Connecting..."); +end +h(e,i); +return true; +end +e:unhook("disconnected",t); +return a(nil); +end +e:hook("disconnected",t,100); +e:hook("connected",function() +e:unhook("disconnected",t); +a(e.streamhosts[e.current_host],e); +end,100); +t(); +return e; +end +function verse.plugins.jingle_s5b(e) +e:hook("ready",function() +e:add_disco_feature(a); +end,10); +local t={}; +function t:generate_initiate() +self.s5b_sid=r(); +local a=verse.stanza("transport",{xmlns=a, +mode="tcp",sid=self.s5b_sid}); +local t=0; +for o,i in pairs(e.proxy65.available_streamhosts)do +t=t+1; +a:tag("candidate",{jid=o,host=i.host, +port=i.port,cid=o,priority=t,type="proxy"}):up(); +end +e:debug("Have %d proxies",t) +return a; +end +function t:generate_accept(e) +local t={}; +self.s5b_peer_candidates=t; +self.s5b_mode=e.attr.mode or"tcp"; +self.s5b_sid=e.attr.sid or self.jingle.sid; +for e in e:childtags()do +t[e.attr.cid]={ +type=e.attr.type; +jid=e.attr.jid; +host=e.attr.host; +port=tonumber(e.attr.port)or 0; +priority=tonumber(e.attr.priority)or 0; +cid=e.attr.cid; +}; +end +local e=verse.stanza("transport",{xmlns=a}); +return e; +end +function t:connect(o) +e:warn("Connecting!"); +local t={}; +for a,e in pairs(self.s5b_peer_candidates or{})do +t[#t+1]=e; +end +if#t>0 then +self.connecting_peer_candidates=true; +local function n(t,e) +self.jingle:send_command("transport-info",verse.stanza("transport",{xmlns=a,sid=self.s5b_sid}) +:tag("candidate-used",{cid=t.cid})); +self.onconnect_callback=o; +self.conn=e; +end +local e=s(self.s5b_sid..self.peer..e.jid,true); +i(n,t,e); +else +e:warn("Actually, I'm going to wait for my peer to tell me its streamhost..."); +self.onconnect_callback=o; +end +end +function t:info_received(t) +e:warn("Info received"); +local o=t:child_with_name("content"):child_with_name("transport"); +if o:get_child("candidate-used")and not self.connecting_peer_candidates then +local t=o:child_with_name("candidate-used"); +if t then +local function n(o,e) +if self.jingle.role=="initiator"then +self.jingle.stream:send_iq(verse.iq({to=o.jid,type="set"}) +:tag("query",{xmlns=xmlns_bytestreams,sid=self.s5b_sid}) +:tag("activate"):text(self.jingle.peer),function(o) +if o.attr.type=="result"then +self.jingle:send_command("transport-info",verse.stanza("transport",{xmlns=a,sid=self.s5b_sid}) +:tag("activated",{cid=t.attr.cid})); +self.conn=e; +self.onconnect_callback(e); +else +self.jingle.stream:error("Failed to activate bytestream"); +end +end); +end +end +self.jingle.stream:debug("CID: %s",self.jingle.stream.proxy65.available_streamhosts[t.attr.cid]); +local t={ +self.jingle.stream.proxy65.available_streamhosts[t.attr.cid]; +}; +local e=s(self.s5b_sid..e.jid..self.peer,true); +i(n,t,e); +end +elseif o:get_child("activated")then +self.onconnect_callback(self.conn); +end +end +function t:disconnect() +if self.conn then +self.conn:close(); +end +end +function t:handle_accepted(e) +end +local t={__index=t}; +e:hook("jingle/transport/"..a,function(e) +return setmetatable({ +role=e.role, +peer=e.peer, +stream=e.stream, +jingle=e, +},t); +end); +end +end) +package.preload['verse.plugins.disco']=(function(...) +local a=require"util.stanza" +local o=require("mime").b64 +local i=require("util.sha1").sha1 +local e="http://jabber.org/protocol/disco"; +local r=e.."#info"; +local s=e.."#items"; +function verse.plugins.disco(e) +e.disco={cache={},info={}} +e.disco.info.identities={ +{category='client',type='pc',name='Verse'}, +} +e.disco.info.features={ +{var='http://jabber.org/protocol/caps'}, +{var='http://jabber.org/protocol/disco#info'}, +{var='http://jabber.org/protocol/disco#items'}, +} +e.disco.items={} +e.disco.nodes={} +e.caps={} +e.caps.node='http://code.matthewwild.co.uk/verse/' +local function n(t,e) +if t.category"); +end +return e(self); +end +self:connect(self.connect_host or self.host,self.connect_port or 5222); +self:reopen(); +end +function a:reopen() +self:reset(); +self:send(o.stanza("stream:stream",{to=self.host,["xmlns:stream"]='http://etherx.jabber.org/streams', +xmlns="jabber:client",version="1.0"}):top_tag()); +end +function a:send_iq(e,a) +local t=self:new_id(); +self.tracked_iqs[t]=a; +e.attr.id=t; +self:send(e); +end +function a:new_id() +self.curr_id=self.curr_id+1; +return tostring(self.curr_id); +end +end) +pcall(require,"luarocks.require"); +pcall(require,"ssl"); +local a=require"net.server"; +local n=require"util.events"; +module("verse",package.seeall); +local t=_M; +local e={}; +e.__index=e; +stream_mt=e; +t.plugins={}; +function t.new(o,a) +local e=setmetatable(a or{},e); +e.id=tostring(e):match("%x*$"); +e:set_logger(o,true); +e.events=n.new(); +return e; +end +t.add_task=require"util.timer".add_task; +function t.loop() +return a.loop(); +end +function t.quit() +return a.setquitting(true); +end +t.logger=logger.init; +function t.set_logger(e) +a.setlogger(e); +end +function e:connect(e,t) +e=e or"localhost"; +t=tonumber(t)or 5222; +local i=socket.tcp() +i:settimeout(0); +local n,o=i:connect(e,t); +if not n and o~="timeout"then +self:warn("connect() to %s:%d failed: %s",e,t,o); +return false,o; +end +local e=a.wrapclient(i,e,t,new_listener(self),"*a"); +if not e then +return false,o; +end +self.conn=e; +local t,a=e.write,tostring; +self.send=function(i,o)return t(e,a(o));end +return true; +end +function e:close() +local e=self.conn.disconnect(); +self.conn:close(); +e(conn,reason); +end +function e:debug(...) +if self.logger and self.log.debug then +return self.logger("debug",...); +end +end +function e:warn(...) +if self.logger and self.log.warn then +return self.logger("warn",...); +end +end +function e:error(...) +if self.logger and self.log.error then +return self.logger("error",...); +end +end +function e:set_logger(t,e) +local a=self.logger; +if t then +self.logger=t; +end +if e then +if e==true then +e={"debug","info","warn","error"}; +end +self.log={}; +for t,e in ipairs(e)do +self.log[e]=true; +end +end +return a; +end +function stream_mt:set_log_levels(e) +self:set_logger(nil,e); +end +function e:event(e,...) +self:debug("Firing event: "..tostring(e)); +return self.events.fire_event(e,...); +end +function e:hook(e,...) +return self.events.add_handler(e,...); +end +function e:unhook(e,t) +return self.events.remove_handler(e,t); +end +function t.eventable(t) +t.events=n.new(); +t.hook,t.unhook=e.hook,e.unhook; +local e=t.events.fire_event; +function t:event(t,...) +return e(t,...); +end +return t; +end +function e:add_plugin(e) +if require("verse.plugins."..e)then +local a,t=t.plugins[e](self); +if a then +self:debug("Loaded %s plugin",e); +else +self:warn("Failed to load %s plugin: %s",e,t); +end +end +return self; +end +function new_listener(e) +local t={}; +function t.onconnect(a) +e.connected=true; +e.send=function(t,e)t:debug("Sending data: "..tostring(e));return a:write(tostring(e));end; +e:event("connected"); +end +function t.onincoming(a,t) +e:event("incoming-raw",t); +end +function t.ondisconnect(a,t) +e.connected=false; +e:event("disconnected",{reason=t}); +end +function t.ondrain(t) +e:event("drained"); +end +function t.onstatus(a,t) +e:event("status",t); +end +return t; +end +local e=require"util.logger".init("verse"); +return t;