|
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 module:set_global(); |
|
10 |
|
11 local new_xmpp_stream = require "util.xmppstream".new; |
|
12 local nameprep = require "util.encodings".stringprep.nameprep; |
|
13 local portmanager = require "core.portmanager"; |
|
14 local sessionmanager = require "core.sessionmanager"; |
|
15 local st = require "util.stanza"; |
|
16 local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; |
|
17 local uuid_generate = require "util.uuid".generate; |
|
18 |
|
19 local xpcall, tostring, type = xpcall, tostring, type; |
|
20 local format = string.format; |
|
21 local traceback = debug.traceback; |
|
22 |
|
23 local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams"; |
|
24 |
|
25 local log = module._log; |
|
26 |
|
27 local opt_keepalives = module:get_option_boolean("tcp_keepalives", false); |
|
28 |
|
29 local sessions = module:shared("sessions"); |
|
30 |
|
31 local stream_callbacks = { default_ns = "jabber:client", handlestanza = core_process_stanza }; |
|
32 local listener = { default_port = 5222, default_mode = "*a" }; |
|
33 |
|
34 --- Stream events handlers |
|
35 local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; |
|
36 local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" }; |
|
37 |
|
38 function stream_callbacks.streamopened(session, attr) |
|
39 local send = session.send; |
|
40 session.host = attr.to; |
|
41 if not session.host then |
|
42 session:close{ condition = "improper-addressing", |
|
43 text = "A 'to' attribute is required on stream headers" }; |
|
44 return; |
|
45 end |
|
46 session.host = nameprep(session.host); |
|
47 session.version = tonumber(attr.version) or 0; |
|
48 session.streamid = uuid_generate(); |
|
49 (session.log or session)("debug", "Client sent opening <stream:stream> to %s", session.host); |
|
50 |
|
51 if not hosts[session.host] then |
|
52 -- We don't serve this host... |
|
53 session:close{ condition = "host-unknown", text = "This server does not serve "..tostring(session.host)}; |
|
54 return; |
|
55 end |
|
56 |
|
57 send("<?xml version='1.0'?>"); |
|
58 send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0' xml:lang='en'>", session.streamid, session.host)); |
|
59 |
|
60 (session.log or log)("debug", "Sent reply <stream:stream> to client"); |
|
61 session.notopen = nil; |
|
62 |
|
63 -- If session.secure is *false* (not nil) then it means we /were/ encrypting |
|
64 -- since we now have a new stream header, session is secured |
|
65 if session.secure == false then |
|
66 session.secure = true; |
|
67 end |
|
68 |
|
69 local features = st.stanza("stream:features"); |
|
70 hosts[session.host].events.fire_event("stream-features", { origin = session, features = features }); |
|
71 module:fire_event("stream-features", session, features); |
|
72 |
|
73 send(features); |
|
74 end |
|
75 |
|
76 function stream_callbacks.streamclosed(session) |
|
77 session.log("debug", "Received </stream:stream>"); |
|
78 session:close(); |
|
79 end |
|
80 |
|
81 function stream_callbacks.error(session, error, data) |
|
82 if error == "no-stream" then |
|
83 session.log("debug", "Invalid opening stream header"); |
|
84 session:close("invalid-namespace"); |
|
85 elseif error == "parse-error" then |
|
86 (session.log or log)("debug", "Client XML parse error: %s", tostring(data)); |
|
87 session:close("not-well-formed"); |
|
88 elseif error == "stream-error" then |
|
89 local condition, text = "undefined-condition"; |
|
90 for child in data:children() do |
|
91 if child.attr.xmlns == xmlns_xmpp_streams then |
|
92 if child.name ~= "text" then |
|
93 condition = child.name; |
|
94 else |
|
95 text = child:get_text(); |
|
96 end |
|
97 if condition ~= "undefined-condition" and text then |
|
98 break; |
|
99 end |
|
100 end |
|
101 end |
|
102 text = condition .. (text and (" ("..text..")") or ""); |
|
103 session.log("info", "Session closed by remote with error: %s", text); |
|
104 session:close(nil, text); |
|
105 end |
|
106 end |
|
107 |
|
108 local function handleerr(err) log("error", "Traceback[c2s]: %s: %s", tostring(err), traceback()); end |
|
109 function stream_callbacks.handlestanza(session, stanza) |
|
110 stanza = session.filter("stanzas/in", stanza); |
|
111 if stanza then |
|
112 return xpcall(function () return core_process_stanza(session, stanza) end, handleerr); |
|
113 end |
|
114 end |
|
115 |
|
116 --- Session methods |
|
117 local function session_close(session, reason) |
|
118 local log = session.log or log; |
|
119 if session.conn then |
|
120 if session.notopen then |
|
121 session.send("<?xml version='1.0'?>"); |
|
122 session.send(st.stanza("stream:stream", default_stream_attr):top_tag()); |
|
123 end |
|
124 if reason then |
|
125 if type(reason) == "string" then -- assume stream error |
|
126 log("info", "Disconnecting client, <stream:error> is: %s", reason); |
|
127 session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' })); |
|
128 elseif type(reason) == "table" then |
|
129 if reason.condition then |
|
130 local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up(); |
|
131 if reason.text then |
|
132 stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); |
|
133 end |
|
134 if reason.extra then |
|
135 stanza:add_child(reason.extra); |
|
136 end |
|
137 log("info", "Disconnecting client, <stream:error> is: %s", tostring(stanza)); |
|
138 session.send(stanza); |
|
139 elseif reason.name then -- a stanza |
|
140 log("info", "Disconnecting client, <stream:error> is: %s", tostring(reason)); |
|
141 session.send(reason); |
|
142 end |
|
143 end |
|
144 end |
|
145 session.send("</stream:stream>"); |
|
146 session.conn:close(); |
|
147 listener.ondisconnect(session.conn, (reason and (reason.text or reason.condition)) or reason or "session closed"); |
|
148 end |
|
149 end |
|
150 |
|
151 --- Port listener |
|
152 function listener.onconnect(conn) |
|
153 local session = sm_new_session(conn); |
|
154 sessions[conn] = session; |
|
155 |
|
156 session.log("info", "Client connected"); |
|
157 |
|
158 -- Client is using legacy SSL (otherwise mod_tls sets this flag) |
|
159 if conn:ssl() then |
|
160 session.secure = true; |
|
161 end |
|
162 |
|
163 if opt_keepalives then |
|
164 conn:setoption("keepalive", opt_keepalives); |
|
165 end |
|
166 |
|
167 session.close = session_close; |
|
168 |
|
169 local stream = new_xmpp_stream(session, stream_callbacks); |
|
170 session.stream = stream; |
|
171 session.notopen = true; |
|
172 |
|
173 function session.reset_stream() |
|
174 session.notopen = true; |
|
175 session.stream:reset(); |
|
176 end |
|
177 |
|
178 local filter = session.filter; |
|
179 function session.data(data) |
|
180 data = filter("bytes/in", data); |
|
181 if data then |
|
182 local ok, err = stream:feed(data); |
|
183 if ok then return; end |
|
184 log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_")); |
|
185 session:close("not-well-formed"); |
|
186 end |
|
187 end |
|
188 |
|
189 session.dispatch_stanza = stream_callbacks.handlestanza; |
|
190 end |
|
191 |
|
192 function listener.onincoming(conn, data) |
|
193 local session = sessions[conn]; |
|
194 if session then |
|
195 session.data(data); |
|
196 end |
|
197 end |
|
198 |
|
199 function listener.ondisconnect(conn, err) |
|
200 local session = sessions[conn]; |
|
201 if session then |
|
202 (session.log or log)("info", "Client disconnected: %s", err); |
|
203 sm_destroy_session(session, err); |
|
204 sessions[conn] = nil; |
|
205 session = nil; |
|
206 end |
|
207 end |
|
208 |
|
209 function listener.associate_session(conn, session) |
|
210 sessions[conn] = session; |
|
211 end |
|
212 |
|
213 portmanager.register_service("c2s", { |
|
214 listener = listener; |
|
215 default_port = 5222; |
|
216 encryption = "starttls"; |
|
217 }); |
|
218 |
|
219 portmanager.register_service("legacy_ssl", { |
|
220 listener = listener; |
|
221 encryption = "ssl"; |
|
222 }); |
|
223 |
|
224 |