net/xmppclient_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
 
11
local logger = require "logger";
12
local log = logger.init("xmppclient_listener");
13
local new_xmpp_stream = require "util.xmppstream".new;
14
 
15
local connlisteners_register = require "net.connlisteners".register;
16
 
17
local sessionmanager = require "core.sessionmanager";
18
local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session;
19
local sm_streamopened = sessionmanager.streamopened;
20
local sm_streamclosed = sessionmanager.streamclosed;
21
local st = require "util.stanza";
22
local xpcall = xpcall;
23
local tostring = tostring;
24
local type = type;
25
local traceback = debug.traceback;
26
 
27
local config = require "core.configmanager";
28
local opt_keepalives = config.get("*", "core", "tcp_keepalives");
29
 
30
local stream_callbacks = { default_ns = "jabber:client",
31
		streamopened = sm_streamopened, streamclosed = sm_streamclosed, handlestanza = core_process_stanza };
32
 
33
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
34
 
35
function stream_callbacks.error(session, error, data)
36
	if error == "no-stream" then
37
		session.log("debug", "Invalid opening stream header");
38
		session:close("invalid-namespace");
39
	elseif error == "parse-error" then
40
		(session.log or log)("debug", "Client XML parse error: %s", tostring(data));
41
		session:close("not-well-formed");
42
	elseif error == "stream-error" then
43
		local condition, text = "undefined-condition";
44
		for child in data:children() do
45
			if child.attr.xmlns == xmlns_xmpp_streams then
46
				if child.name ~= "text" then
47
					condition = child.name;
48
				else
49
					text = child:get_text();
50
				end
51
				if condition ~= "undefined-condition" and text then
52
					break;
53
				end
54
			end
55
		end
56
		text = condition .. (text and (" ("..text..")") or "");
57
		session.log("info", "Session closed by remote with error: %s", text);
58
		session:close(nil, text);
59
	end
60
end
61
 
62
local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end
63
function stream_callbacks.handlestanza(session, stanza)
64
	stanza = session.filter("stanzas/in", stanza);
65
	if stanza then
66
		return xpcall(function () return core_process_stanza(session, stanza) end, handleerr);
67
	end
68
end
69
 
70
local sessions = {};
71
local xmppclient = { default_port = 5222, default_mode = "*a" };
72
 
73
-- These are session methods --
74
 
75
local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
76
local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
77
local function session_close(session, reason)
78
	local log = session.log or log;
79
	if session.conn then
80
		if session.notopen then
81
			session.send("<?xml version='1.0'?>");
82
			session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
83
		end
84
		if reason then
85
			if type(reason) == "string" then -- assume stream error
86
				log("info", "Disconnecting client, <stream:error> is: %s", reason);
87
				session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
88
			elseif type(reason) == "table" then
89
				if reason.condition then
90
					local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
91
					if reason.text then
92
						stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
93
					end
94
					if reason.extra then
95
						stanza:add_child(reason.extra);
96
					end
97
					log("info", "Disconnecting client, <stream:error> is: %s", tostring(stanza));
98
					session.send(stanza);
99
				elseif reason.name then -- a stanza
100
					log("info", "Disconnecting client, <stream:error> is: %s", tostring(reason));
101
					session.send(reason);
102
				end
103
			end
104
		end
105
		session.send("</stream:stream>");
106
		session.conn:close();
107
		xmppclient.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed");
108
	end
109
end
110
 
111
 
112
-- End of session methods --
113
 
114
function xmppclient.onconnect(conn)
115
	local session = sm_new_session(conn);
116
	sessions[conn] = session;
117
	
118
	session.log("info", "Client connected");
119
	
120
	-- Client is using legacy SSL (otherwise mod_tls sets this flag)
121
	if conn:ssl() then
122
		session.secure = true;
123
	end
124
	
125
	if opt_keepalives ~= nil then
126
		conn:setoption("keepalive", opt_keepalives);
127
	end
128
	
129
	session.close = session_close;
130
	
131
	local stream = new_xmpp_stream(session, stream_callbacks);
132
	session.stream = stream;
133
	
134
	session.notopen = true;
135
	
136
	function session.reset_stream()
137
		session.notopen = true;
138
		session.stream:reset();
139
	end
140
	
141
	local filter = session.filter;
142
	function session.data(data)
143
		data = filter("bytes/in", data);
144
		if data then
145
			local ok, err = stream:feed(data);
146
			if ok then return; end
147
			log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
148
			session:close("not-well-formed");
149
		end
150
	end
151
	
152
	local handlestanza = stream_callbacks.handlestanza;
153
	function session.dispatch_stanza(session, stanza)
154
		return handlestanza(session, stanza);
155
	end
156
end
157
 
158
function xmppclient.onincoming(conn, data)
159
	local session = sessions[conn];
160
	if session then
161
		session.data(data);
162
	end
163
end
164
	
165
function xmppclient.ondisconnect(conn, err)
166
	local session = sessions[conn];
167
	if session then
168
		(session.log or log)("info", "Client disconnected: %s", err);
169
		sm_destroy_session(session, err);
170
		sessions[conn]  = nil;
171
		session = nil;
172
	end
173
end
174
 
175
function xmppclient.associate_session(conn, session)
176
	sessions[conn] = session;
177
end
178
 
179
connlisteners_register("xmppclient", xmppclient);