summaryrefslogtreecommitdiff
path: root/xmpp/pylon.lua
blob: 90a155ab713f449bd52f04794b46a96e42c706df (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
local socket = require'cqueues.socket'
local class = require'r.class'

local Channel = require'channel'
local BasePylon = require 'pylon'
local xml = require'xmpp.xml'
local X,xmlify = xml.X, xml.xmlify
local base64 = require'xmpp.base64'
local sha1 = require'xmpp.sha1'

local Xmpp = class.extend(BasePylon)

function Xmpp.init(self)
	self:_check_fields"server component component_secret"

	self.mucs = {}
end

-- only use on local connections!
function Xmpp._connect(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)

	-- 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')
	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)

	for busname, bus in pairs(self.wilson.busses) do
		for _, channel in ipairs(bus) do
			if channel.pylon == self then
				self:add_channel(channel)
			end
		end
	end

	return sock
end

function Xmpp.add_channel(self, channel)
	local muc = { nick_to_user={}, user_to_nick={} }
	self.mucs[channel.descriptor] = muc
	local bot = {} ; self.bot = bot
	bot.user, bot.nick, bot.user_jid, bot.nick_jid = self:ensure_and_get_user(channel.descriptor, "wilson")
end

function Xmpp.ensure_and_get_user(self, muc_jid, msg_sender, source_pylon)
	local muc = assert(self.mucs[muc_jid])
	local nick = msg_sender
	if source_pylon then nick = nick.."["..source_pylon.shortname.."]" end
	local user = muc.nick_to_user[nick]
	if user then
		local user_jid = user..'@'..self.component
		local nick_jid = muc_jid..'/'..nick
		return user, nick, user_jid, nick_jid
	end

	-- join up new user

	user = nick:gsub("[^a-zA-Z0-9%.]","."):match("^%.*(.-)%.*$")
	if source_pylon then
		while muc.user_to_nick[user.."."..source_pylon.name] do user = user..'-' end
		user = user.."."..source_pylon.name
	else
		while muc.user_to_nick[user] do user = user..'-' end
	end
	muc.nick_to_user[nick] = user
	muc.user_to_nick[user] = nick

	local user_jid = user..'@'..self.component
	local nick_jid = muc_jid..'/'..nick

	self.log("Joining up new user "..user_jid.." to "..nick_jid)

	self.sock:write(xmlify(
		X.presence{from=user_jid, to=nick_jid,
			X.x{xmlns='http://jabber.org/protocol/muc',
				X.history{maxstanzas='0'}}}))

	return user, nick, user_jid, nick_jid
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
		self.log:debug(xmlify(x))
		if type(x) == 'string' then
			self.log:warn("x was a string: ("..x..")")
			goto next
		end
		local body = x'body' and x'body'[1]
		if x.label == 'message' then
			local from_bare_jid, from_nick
			if x.xarg.from then from_bare_jid, from_nick = x.xarg.from:match"(.*)/(.*)" end
			local muc = self.mucs[from_bare_jid] ; if muc then
				if
					x.xarg.to == 'wilson@'..self.component
					and body
					and from_nick and not muc.nick_to_user[from_nick]
				then
					self.log:proto('<',from_bare_jid,from_nick,body)
					self.wilson:deliver({
						source_channel = Channel(self, from_bare_jid),
						body = body,
						sender = from_nick,
					})
				end
			end
		end
		::next::
	end
end

function Xmpp.sending(self)
	for dest_channel, message in self.inbox:iter() do
		self.log:proto('>',dest_channel,message.sender,message.body)
		local user, nick, user_jid, nick_jid
			= self:ensure_and_get_user(dest_channel.descriptor, message.sender, message.source_channel.pylon)
		self.sock:write(xmlify(
			X.message{to=dest_channel.descriptor, type='groupchat', from=user_jid,
				X.body{message.body}}))
	end
end

return Xmpp