summaryrefslogtreecommitdiff
path: root/xmpp.lua
blob: 24a5a3cebf2e7a93d778ac9d17c9a5c66754c6a3 (plain)
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())