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