1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
|
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.makepylon(pylonname, conf, cq, network)
local self = {
pylonname = pylonname,
cq = cq,
network = network,
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 = ([[
<?xml version='1.0'?><stream:stream from='%s' to='%s' version='1.0' xml:lang='en' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>]]):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 = ([[<stream:stream to='%s' xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams'>]]):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('<handshake/>',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.network:post(self.pylonname, THE_MUC, {
body = body,
source = '[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 ch, msg in self.inbox:iter() do
pprint(ch,msg)
ensure_joined(THE_MUC, msg.source, msg.source)
local user = msg.source: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())
|