local cqueues = require'cqueues' local cqaux = require'cqueues.auxlib' local socket = require'cqueues.socket' local xml = require'xml' local X = xml.X local xmlify = xml.xmlify local pprint=require'pprint' local Queue = require'queue' local base64 = require'base64' local sha1 = require'sha1' local Xmpp = {} local function make_auth(authz, authn, password) -- sasl plain (RFC4616) return base64.encode(authz..'\0'..authn..'\0'..password) end function Xmpp.make(wilson, conf) local self = { name = conf.name, wilson = wilson, inbox = Queue.make(), } local function conf_var(name) assert(conf[name] ~= nil, 'missing conf field '..name) self[name] = conf[name] end conf_var 'jid' conf_var 'server' conf_var 'resource' conf_var 'component' conf_var 'component_secret' setmetatable(self, {__index=Xmpp}) return self end function Xmpp._connect_c2s(self) local sock = assert(socket.connect(self.server, 5222)) self.sock = sock sock:setmode('bn','bn') local start = ([[ ]]):format(self.jid, self.server) -- state of the art xml parser local function check_and_send(test, text) local x = sock:read('-2048') assert(x:find(test)) if text then sock:write(text) end end local function ietf_urn(v) return 'urn:ietf:params:xml:ns:xmpp-'..v end sock:write(start) check_and_send('starttls', xmlify(X.starttls{xmlns=ietf_urn"tls"})) check_and_send('proceed', nil) sock:starttls() sock:write(start) local auth = make_auth('', self.jid:match"(.*)@", self.password) check_and_send('PLAIN', xmlify(X.auth{xmlns=ietf_urn"sasl", mechanism='PLAIN', auth})) check_and_send('success',start) check_and_send('bind', xmlify(X.iq{type='set', id='aaaa', X.bind{xmlns=ietf_urn"bind", X.resource{self.resource}}})) check_and_send('jid',X.presence{X.show{'chat'}}) return sock end -- this sucks! no tls! no security! only use on local connections! function Xmpp._connect_component(self) local sock = assert(socket.connect(self.server, 5347)) self.sock = sock sock:setmode('bn','bn') -- yes, our component name goes in the 'to' field. don't ask me why local start = ([[]]):format(self.component) print(start) -- state of the art xml parser local function check_and_send(test, text) local x = sock:read('-2048') assert(x:find(test)) if text then sock:write(text) end end sock:write(start) local streamhead = sock:read('-2048') print('streamhead', streamhead) assert(streamhead:find'accept') local streamid = streamhead:match"id='(.-)'" sock:write(xmlify(X.handshake{sha1.sha1(streamid..self.component_secret)})) check_and_send('',nil) return sock end local THE_MUC = 'd@conference.ubq323.website' function Xmpp.run(self) self:_connect_component() self.cq:wrap(self.recving, self) self.cq:wrap(self.sending, self) end function Xmpp.recving(self) local function getmore() local function t(...) pprint(...) return ... end return t(self.sock:read'-2048') end for x in xml.stanzae(getmore) do pprint(x) print(xmlify(x)) local body = x'body' and x'body'[1] if x.label == 'message' then local fr = x.xarg.from local t = x.xarg.to if body and fr:match"/" and t == 'wilson@'..self.component then self.wilson:deliver(Channel(self, THE_MUC), { body = body, sender = '[x]'..x.xarg.from:match("/(.*)") }) end end end end function Xmpp.sending(self) local users_inuse = {} local nicks_inuse = {} local function ensure_joined(muc,user,nick) if nicks_inuse[nick] then return end user = user:gsub("[^a-zA-Z0-9%.]","."):match("^%.*(.-)%.*$") while users_inuse[user] do user = user..'-' end local jid = user..'@'..self.component local mucjid = muc..'/'..nick nicks_inuse[nick] = true users_inuse[user] = true self.sock:write(xmlify( X.presence{from=jid, to=mucjid, X.x{xmlns='http://jabber.org/protocol/muc', X.history{maxstanzas='0'}}})) end ensure_joined(THE_MUC, 'wilson', 'wilson') for dest_channel, message in self.inbox:iter() do pprint(dest_channel, message) ensure_joined(THE_MUC, message.sender, message.sender) local user = message.sender:gsub("[^a-zA-Z0-9%.]","."):match("^%.*(.-)%.*$") local jid = user..'@'..self.component self.sock:write(xmlify( X.message{to=THE_MUC, type='groupchat', from=jid, X.body{msg.body}})) end end return Xmpp -- local cq = cqueues.new() -- local conf = { -- jid='wilson@ubq323.website', -- server='ubq323.website', -- password='gregory<3', -- resource='cheese', -- } -- local dummy_network = { -- post = function(self, pylonname, channel, message) -- pprint(pylonname, channel, message) -- end -- } -- local pylon = Xmpp.makepylon('xmpptest',conf, cq, dummy_network) -- pylon:run() -- pprint('peas', cq:loop())