net/xmppserver_listener.lua

1
-- Prosody IM
2
-- Copyright (C) 2008-2010 Matthew Wild
3
-- Copyright (C) 2008-2010 Waqas Hussain
4
-- 
5
-- This project is MIT/X11 licensed. Please see the
6
-- COPYING file in the source package for more information.
7
--
8
 
9
 
10
local tostring = tostring;
11
local type = type;
12
local xpcall = xpcall;
13
local s_format = string.format;
14
local traceback = debug.traceback;
15
 
16
local logger = require "logger";
17
local log = logger.init("xmppserver_listener");
18
local st = require "util.stanza";
19
local connlisteners_register = require "net.connlisteners".register;
20
local new_xmpp_stream = require "util.xmppstream".new;
21
local s2s_new_incoming = require "core.s2smanager".new_incoming;
22
local s2s_streamopened = require "core.s2smanager".streamopened;
23
local s2s_streamclosed = require "core.s2smanager".streamclosed;
24
local s2s_destroy_session = require "core.s2smanager".destroy_session;
25
local s2s_attempt_connect = require "core.s2smanager".attempt_connection;
26
local stream_callbacks = { default_ns = "jabber:server",
27
		streamopened = s2s_streamopened, streamclosed = s2s_streamclosed, handlestanza =  core_process_stanza };
28
 
29
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
30
 
31
function stream_callbacks.error(session, error, data)
32
	if error == "no-stream" then
33
		session:close("invalid-namespace");
34
	elseif error == "parse-error" then
35
		session.log("debug", "Server-to-server XML parse error: %s", tostring(error));
36
		session:close("not-well-formed");
37
	elseif error == "stream-error" then
38
		local condition, text = "undefined-condition";
39
		for child in data:children() do
40
			if child.attr.xmlns == xmlns_xmpp_streams then
41
				if child.name ~= "text" then
42
					condition = child.name;
43
				else
44
					text = child:get_text();
45
				end
46
				if condition ~= "undefined-condition" and text then
47
					break;
48
				end
49
			end
50
		end
51
		text = condition .. (text and (" ("..text..")") or "");
52
		session.log("info", "Session closed by remote with error: %s", text);
53
		session:close(nil, text);
54
	end
55
end
56
 
57
local function handleerr(err) log("error", "Traceback[s2s]: %s: %s", tostring(err), traceback()); end
58
function stream_callbacks.handlestanza(session, stanza)
59
	if stanza.attr.xmlns == "jabber:client" then --COMPAT: Prosody pre-0.6.2 may send jabber:client
60
		stanza.attr.xmlns = nil;
61
	end
62
	stanza = session.filter("stanzas/in", stanza);
63
	if stanza then
64
		return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
65
	end
66
end
67
 
68
local sessions = {};
69
local xmppserver = { default_port = 5269, default_mode = "*a" };
70
 
71
-- These are session methods --
72
 
73
local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
74
local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
75
local function session_close(session, reason, remote_reason)
76
	local log = session.log or log;
77
	if session.conn then
78
		if session.notopen then
79
			session.sends2s("<?xml version='1.0'?>");
80
			session.sends2s(st.stanza("stream:stream", default_stream_attr):top_tag());
81
		end
82
		if reason then
83
			if type(reason) == "string" then -- assume stream error
84
				log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, reason);
85
				session.sends2s(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
86
			elseif type(reason) == "table" then
87
				if reason.condition then
88
					local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
89
					if reason.text then
90
						stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
91
					end
92
					if reason.extra then
93
						stanza:add_child(reason.extra);
94
					end
95
					log("info", "Disconnecting %s[%s], <stream:error> is: %s", session.host or "(unknown host)", session.type, tostring(stanza));
96
					session.sends2s(stanza);
97
				elseif reason.name then -- a stanza
98
					log("info", "Disconnecting %s->%s[%s], <stream:error> is: %s", session.from_host or "(unknown host)", session.to_host or "(unknown host)", session.type, tostring(reason));
99
					session.sends2s(reason);
100
				end
101
			end
102
		end
103
		session.sends2s("</stream:stream>");
104
		if session.notopen or not session.conn:close() then
105
			session.conn:close(true); -- Force FIXME: timer?
106
		end
107
		session.conn:close();
108
		xmppserver.ondisconnect(session.conn, remote_reason or (reason and (reason.text or reason.condition)) or reason or "stream closed");
109
	end
110
end
111
 
112
 
113
-- End of session methods --
114
 
115
local function initialize_session(session)
116
	local stream = new_xmpp_stream(session, stream_callbacks);
117
	session.stream = stream;
118
	
119
	session.notopen = true;
120
		
121
	function session.reset_stream()
122
		session.notopen = true;
123
		session.stream:reset();
124
	end
125
	
126
	local filter = session.filter;
127
	function session.data(data)
128
		data = filter("bytes/in", data);
129
		if data then
130
			local ok, err = stream:feed(data);
131
			if ok then return; end
132
			(session.log or log)("warn", "Received invalid XML: %s", data);
133
			(session.log or log)("warn", "Problem was: %s", err);
134
			session:close("not-well-formed");
135
		end
136
	end
137
 
138
	session.close = session_close;
139
	local handlestanza = stream_callbacks.handlestanza;
140
	function session.dispatch_stanza(session, stanza)
141
		return handlestanza(session, stanza);
142
	end
143
end
144
 
145
function xmppserver.onconnect(conn)
146
	if not sessions[conn] then -- May be an existing outgoing session
147
		local session = s2s_new_incoming(conn);
148
		sessions[conn] = session;
149
	
150
		-- Logging functions --
151
		local conn_name = "s2sin"..tostring(conn):match("[a-f0-9]+$");
152
		session.log = logger.init(conn_name);
153
		
154
		session.log("info", "Incoming s2s connection");
155
		
156
		initialize_session(session);
157
	end
158
end
159
 
160
function xmppserver.onincoming(conn, data)
161
	local session = sessions[conn];
162
	if session then
163
		session.data(data);
164
	end
165
end
166
	
167
function xmppserver.onstatus(conn, status)
168
	if status == "ssl-handshake-complete" then
169
		local session = sessions[conn];
170
		if session and session.direction == "outgoing" then
171
			local to_host, from_host = session.to_host, session.from_host;
172
			session.log("debug", "Sending stream header...");
173
			session.sends2s(s_format([[<stream:stream xmlns='jabber:server' xmlns:db='jabber:server:dialback' xmlns:stream='http://etherx.jabber.org/streams' from='%s' to='%s' version='1.0'>]], from_host, to_host));
174
		end
175
	end
176
end
177
 
178
function xmppserver.ondisconnect(conn, err)
179
	local session = sessions[conn];
180
	if session then
181
		if err and err ~= "closed" and session.srv_hosts then
182
			(session.log or log)("debug", "s2s connection attempt failed: %s", err);
183
			if s2s_attempt_connect(session, err) then
184
				(session.log or log)("debug", "...so we're going to try another target");
185
				return; -- Session lives for now
186
			end
187
		end
188
		(session.log or log)("info", "s2s disconnected: %s->%s (%s)", tostring(session.from_host), tostring(session.to_host), tostring(err or "closed"));
189
		s2s_destroy_session(session, err);
190
		sessions[conn]  = nil;
191
		session = nil;
192
	end
193
end
194
 
195
function xmppserver.register_outgoing(conn, session)
196
	session.direction = "outgoing";
197
	sessions[conn] = session;
198
	
199
	initialize_session(session);
200
end
201
 
202
connlisteners_register("xmppserver", xmppserver);
203
 
204
 
205
-- We need to perform some initialisation when a connection is created
206
-- We also need to perform that same initialisation at other points (SASL, TLS, ...)
207
 
208
-- ...and we need to handle data
209
-- ...and record all sessions associated with connections