Software /
code /
prosody
Comparison
plugins/mod_bosh.lua @ 11200:bf8f2da84007
Merge 0.11->trunk
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Thu, 05 Nov 2020 22:31:25 +0100 |
parent | 11126:cc6b1dab01a2 |
child | 11391:8eff5c744395 |
comparison
equal
deleted
inserted
replaced
11199:6c7c50a4de32 | 11200:bf8f2da84007 |
---|---|
42 -- The maximum amount of time that the server will hold onto a request before replying | 42 -- The maximum amount of time that the server will hold onto a request before replying |
43 -- (the client can set this to a lower value when it connects, if it chooses) | 43 -- (the client can set this to a lower value when it connects, if it chooses) |
44 local bosh_max_wait = module:get_option_number("bosh_max_wait", 120); | 44 local bosh_max_wait = module:get_option_number("bosh_max_wait", 120); |
45 | 45 |
46 local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure"); | 46 local consider_bosh_secure = module:get_option_boolean("consider_bosh_secure"); |
47 local cross_domain = module:get_option("cross_domain_bosh", false); | 47 local cross_domain = module:get_option("cross_domain_bosh"); |
48 | 48 |
49 if cross_domain == true then cross_domain = "*"; end | 49 if cross_domain ~= nil then |
50 if type(cross_domain) == "table" then cross_domain = table.concat(cross_domain, ", "); end | 50 module:log("info", "The 'cross_domain_bosh' option has been deprecated"); |
51 end | |
51 | 52 |
52 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; | 53 local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; |
53 | 54 |
54 -- All sessions, and sessions that have no requests open | 55 -- All sessions, and sessions that have no requests open |
55 local sessions = module:shared("sessions"); | 56 local sessions = module:shared("sessions"); |
56 | 57 |
58 local measure_active = module:measure("active_sessions", "amount"); | |
59 local measure_inactive = module:measure("inactive_sessions", "amount"); | |
60 local report_bad_host = module:measure("bad_host", "rate"); | |
61 local report_bad_sid = module:measure("bad_sid", "rate"); | |
62 local report_new_sid = module:measure("new_sid", "rate"); | |
63 local report_timeout = module:measure("timeout", "rate"); | |
64 | |
65 module:hook("stats-update", function () | |
66 local active = 0; | |
67 local inactive = 0; | |
68 for _, session in pairs(sessions) do | |
69 if #session.requests > 0 then | |
70 active = active + 1; | |
71 else | |
72 inactive = inactive + 1; | |
73 end | |
74 end | |
75 measure_active(active); | |
76 measure_inactive(inactive); | |
77 end); | |
78 | |
57 -- Used to respond to idle sessions (those with waiting requests) | 79 -- Used to respond to idle sessions (those with waiting requests) |
58 function on_destroy_request(request) | 80 function on_destroy_request(request) |
59 log("debug", "Request destroyed: %s", tostring(request)); | 81 log("debug", "Request destroyed: %s", request); |
60 local session = sessions[request.context.sid]; | 82 local session = sessions[request.context.sid]; |
61 if session then | 83 if session then |
62 local requests = session.requests; | 84 local requests = session.requests; |
63 for i, r in ipairs(requests) do | 85 for i, r in ipairs(requests) do |
64 if r == request then | 86 if r == request then |
71 local max_inactive = session.bosh_max_inactive; | 93 local max_inactive = session.bosh_max_inactive; |
72 if max_inactive and #requests == 0 then | 94 if max_inactive and #requests == 0 then |
73 if session.inactive_timer then | 95 if session.inactive_timer then |
74 session.inactive_timer:stop(); | 96 session.inactive_timer:stop(); |
75 end | 97 end |
76 session.inactive_timer = module:add_timer(max_inactive, check_inactive, session, request.context, | 98 session.inactive_timer = module:add_timer(max_inactive, session_timeout, session, request.context, |
77 "BOSH client silent for over "..max_inactive.." seconds"); | 99 "BOSH client silent for over "..max_inactive.." seconds"); |
78 (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive); | 100 (session.log or log)("debug", "BOSH session marked as inactive (for %ds)", max_inactive); |
79 end | 101 end |
80 if session.bosh_wait_timer then | 102 if session.bosh_wait_timer then |
81 session.bosh_wait_timer:stop(); | 103 session.bosh_wait_timer:stop(); |
82 session.bosh_wait_timer = nil; | 104 session.bosh_wait_timer = nil; |
83 end | 105 end |
84 end | 106 end |
85 end | 107 end |
86 | 108 |
87 function check_inactive(now, session, context, reason) -- luacheck: ignore 212/now | 109 function session_timeout(now, session, context, reason) -- luacheck: ignore 212/now |
88 if not session.destroyed then | 110 if not session.destroyed then |
111 report_timeout(); | |
89 sessions[context.sid] = nil; | 112 sessions[context.sid] = nil; |
90 sm_destroy_session(session, reason); | 113 sm_destroy_session(session, reason); |
91 end | 114 end |
92 end | 115 end |
93 | 116 |
94 local function set_cross_domain_headers(response) | |
95 local headers = response.headers; | |
96 headers.access_control_allow_methods = "GET, POST, OPTIONS"; | |
97 headers.access_control_allow_headers = "Content-Type"; | |
98 headers.access_control_max_age = "7200"; | |
99 headers.access_control_allow_origin = cross_domain; | |
100 return response; | |
101 end | |
102 | |
103 function handle_OPTIONS(event) | |
104 if cross_domain and event.request.headers.origin then | |
105 set_cross_domain_headers(event.response); | |
106 end | |
107 return ""; | |
108 end | |
109 | |
110 function handle_POST(event) | 117 function handle_POST(event) |
111 log("debug", "Handling new request %s: %s\n----------", tostring(event.request), tostring(event.request.body)); | 118 log("debug", "Handling new request %s: %s\n----------", event.request, event.request.body); |
112 | 119 |
113 local request, response = event.request, event.response; | 120 local request, response = event.request, event.response; |
114 response.on_destroy = on_destroy_request; | 121 response.on_destroy = on_destroy_request; |
115 local body = request.body; | 122 local body = request.body; |
116 | 123 |
118 local stream = new_xmpp_stream(context, stream_callbacks); | 125 local stream = new_xmpp_stream(context, stream_callbacks); |
119 response.context = context; | 126 response.context = context; |
120 | 127 |
121 local headers = response.headers; | 128 local headers = response.headers; |
122 headers.content_type = "text/xml; charset=utf-8"; | 129 headers.content_type = "text/xml; charset=utf-8"; |
123 | |
124 if cross_domain and request.headers.origin then | |
125 set_cross_domain_headers(response); | |
126 end | |
127 | 130 |
128 -- stream:feed() calls the stream_callbacks, so all stanzas in | 131 -- stream:feed() calls the stream_callbacks, so all stanzas in |
129 -- the body are processed in this next line before it returns. | 132 -- the body are processed in this next line before it returns. |
130 -- In particular, the streamopened() stream callback is where | 133 -- In particular, the streamopened() stream callback is where |
131 -- much of the session logic happens, because it's where we first | 134 -- much of the session logic happens, because it's where we first |
203 -- A response has been sent already, or we're ignoring this request | 206 -- A response has been sent already, or we're ignoring this request |
204 -- (e.g. so a different instance of the module can handle it) | 207 -- (e.g. so a different instance of the module can handle it) |
205 return; | 208 return; |
206 end | 209 end |
207 module:log("warn", "Unable to associate request with a session (incomplete request?)"); | 210 module:log("warn", "Unable to associate request with a session (incomplete request?)"); |
211 report_bad_sid(); | |
208 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 212 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
209 ["xmlns:stream"] = xmlns_streams, condition = "item-not-found" }); | 213 ["xmlns:stream"] = xmlns_streams, condition = "item-not-found" }); |
210 return tostring(close_reply) .. "\n"; | 214 return tostring(close_reply) .. "\n"; |
211 end | 215 end |
212 | 216 |
218 | 222 |
219 local function bosh_reset_stream(session) session.notopen = true; end | 223 local function bosh_reset_stream(session) session.notopen = true; end |
220 | 224 |
221 local stream_xmlns_attr = { xmlns = "urn:ietf:params:xml:ns:xmpp-streams" }; | 225 local stream_xmlns_attr = { xmlns = "urn:ietf:params:xml:ns:xmpp-streams" }; |
222 local function bosh_close_stream(session, reason) | 226 local function bosh_close_stream(session, reason) |
223 (session.log or log)("info", "BOSH client disconnected: %s", tostring((reason and reason.condition or reason) or "session close")); | 227 (session.log or log)("info", "BOSH client disconnected: %s", (reason and reason.condition or reason) or "session close"); |
224 | 228 |
225 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 229 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
226 ["xmlns:stream"] = xmlns_streams }); | 230 ["xmlns:stream"] = xmlns_streams }); |
227 | 231 |
228 | 232 |
243 end | 247 end |
244 elseif reason.name then -- a stanza | 248 elseif reason.name then -- a stanza |
245 close_reply = reason; | 249 close_reply = reason; |
246 end | 250 end |
247 end | 251 end |
248 log("info", "Disconnecting client, <stream:error> is: %s", tostring(close_reply)); | 252 log("info", "Disconnecting client, <stream:error> is: %s", close_reply); |
249 end | 253 end |
250 | 254 |
251 local response_body = tostring(close_reply); | 255 local response_body = tostring(close_reply); |
252 for _, held_request in ipairs(session.requests) do | 256 for _, held_request in ipairs(session.requests) do |
253 held_request:send(response_body); | 257 held_request:send(response_body); |
266 context.rid = rid; | 270 context.rid = rid; |
267 if not sid then | 271 if not sid then |
268 -- New session request | 272 -- New session request |
269 context.notopen = nil; -- Signals that we accept this opening tag | 273 context.notopen = nil; -- Signals that we accept this opening tag |
270 | 274 |
271 local to_host = nameprep(attr.to); | 275 if not attr.to then |
272 if not to_host then | 276 log("debug", "BOSH client tried to connect without specifying a host"); |
273 log("debug", "BOSH client tried to connect to invalid host: %s", tostring(attr.to)); | 277 report_bad_host(); |
274 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 278 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
275 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); | 279 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); |
276 response:send(tostring(close_reply)); | 280 response:send(tostring(close_reply)); |
277 return; | 281 return; |
278 end | 282 end |
279 | 283 |
280 if not prosody.hosts[to_host] then | 284 local to_host = nameprep(attr.to); |
281 log("debug", "BOSH client tried to connect to non-existant host: %s", attr.to); | 285 if not to_host then |
286 log("debug", "BOSH client tried to connect to invalid host: %s", attr.to); | |
287 report_bad_host(); | |
282 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 288 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
283 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); | 289 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); |
284 response:send(tostring(close_reply)); | 290 response:send(tostring(close_reply)); |
285 return; | 291 return; |
286 end | 292 end |
287 | 293 |
288 if prosody.hosts[to_host].type ~= "local" then | 294 if not prosody.hosts[to_host] then |
289 log("debug", "BOSH client tried to connect to %s host: %s", prosody.hosts[to_host].type, attr.to); | 295 log("debug", "BOSH client tried to connect to non-existant host: %s", attr.to); |
296 report_bad_host(); | |
290 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 297 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
291 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); | 298 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); |
292 response:send(tostring(close_reply)); | 299 response:send(tostring(close_reply)); |
293 return; | 300 return; |
294 end | 301 end |
295 | 302 |
303 if prosody.hosts[to_host].type ~= "local" then | |
304 log("debug", "BOSH client tried to connect to %s host: %s", prosody.hosts[to_host].type, attr.to); | |
305 report_bad_host(); | |
306 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | |
307 ["xmlns:stream"] = xmlns_streams, condition = "improper-addressing" }); | |
308 response:send(tostring(close_reply)); | |
309 return; | |
310 end | |
311 | |
296 local wait = tonumber(attr.wait); | 312 local wait = tonumber(attr.wait); |
297 if not rid or (not attr.wait or not wait or wait < 0 or wait % 1 ~= 0) then | 313 if not rid or (not attr.wait or not wait or wait < 0 or wait % 1 ~= 0) then |
298 log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", tostring(attr.rid), tostring(attr.wait)); | 314 log("debug", "BOSH client sent invalid rid or wait attributes: rid=%s, wait=%s", attr.rid, attr.wait); |
299 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", | 315 local close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", |
300 ["xmlns:stream"] = xmlns_streams, condition = "bad-request" }); | 316 ["xmlns:stream"] = xmlns_streams, condition = "bad-request" }); |
301 response:send(tostring(close_reply)); | 317 response:send(tostring(close_reply)); |
302 return; | 318 return; |
303 end | 319 end |
324 | 340 |
325 local filter = initialize_filters(session); | 341 local filter = initialize_filters(session); |
326 | 342 |
327 session.log("debug", "BOSH session created for request from %s", session.ip); | 343 session.log("debug", "BOSH session created for request from %s", session.ip); |
328 log("info", "New BOSH session, assigned it sid '%s'", sid); | 344 log("info", "New BOSH session, assigned it sid '%s'", sid); |
345 report_new_sid(); | |
329 | 346 |
330 module:fire_event("bosh-session", { session = session, request = request }); | 347 module:fire_event("bosh-session", { session = session, request = request }); |
331 | 348 |
332 -- Send creation response | 349 -- Send creation response |
333 local creating_session = true; | 350 local creating_session = true; |
338 if s.attr and not s.attr.xmlns then | 355 if s.attr and not s.attr.xmlns then |
339 s = st.clone(s); | 356 s = st.clone(s); |
340 s.attr.xmlns = "jabber:client"; | 357 s.attr.xmlns = "jabber:client"; |
341 end | 358 end |
342 s = filter("stanzas/out", s); | 359 s = filter("stanzas/out", s); |
343 --log("debug", "Sending BOSH data: %s", tostring(s)); | 360 --log("debug", "Sending BOSH data: %s", s); |
344 if not s then return true end | 361 if not s then return true end |
345 t_insert(session.send_buffer, tostring(s)); | 362 t_insert(session.send_buffer, tostring(s)); |
346 | 363 |
347 local oldest_request = r[1]; | 364 local oldest_request = r[1]; |
348 if oldest_request and not session.bosh_processing then | 365 if oldest_request and not session.bosh_processing then |
378 | 395 |
379 local session = sessions[sid]; | 396 local session = sessions[sid]; |
380 if not session then | 397 if not session then |
381 -- Unknown sid | 398 -- Unknown sid |
382 log("info", "Client tried to use sid '%s' which we don't know about", sid); | 399 log("info", "Client tried to use sid '%s' which we don't know about", sid); |
400 report_bad_sid(); | |
383 response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" }))); | 401 response:send(tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate", condition = "item-not-found" }))); |
384 context.notopen = nil; | 402 context.notopen = nil; |
385 return; | 403 return; |
386 end | 404 end |
387 | 405 |
440 session.send(features); | 458 session.send(features); |
441 session.notopen = nil; | 459 session.notopen = nil; |
442 end | 460 end |
443 end | 461 end |
444 | 462 |
445 local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(tostring(err), 2)); end | 463 local function handleerr(err) log("error", "Traceback[bosh]: %s", traceback(err, 2)); end |
446 | 464 |
447 function runner_callbacks:error(err) -- luacheck: ignore 212/self | 465 function runner_callbacks:error(err) -- luacheck: ignore 212/self |
448 return handleerr(err); | 466 return handleerr(err); |
449 end | 467 end |
450 | 468 |
510 else | 528 else |
511 session:close({ condition = "bad-format", text = "Error processing stream" }); | 529 session:close({ condition = "bad-format", text = "Error processing stream" }); |
512 end | 530 end |
513 end | 531 end |
514 | 532 |
533 local GET_response_body = [[<html><body> | |
534 <p>It works! Now point your BOSH client to this URL to connect to Prosody.</p> | |
535 <p>For more information see <a href="https://prosody.im/doc/setting_up_bosh">Prosody: Setting up BOSH</a>.</p> | |
536 </body></html>]]; | |
537 | |
515 local GET_response = { | 538 local GET_response = { |
516 headers = { | 539 headers = { |
517 content_type = "text/html"; | 540 content_type = "text/html"; |
518 }; | 541 }; |
519 body = [[<html><body> | 542 body = module:get_option_string("bosh_get_response_body", GET_response_body); |
520 <p>It works! Now point your BOSH client to this URL to connect to Prosody.</p> | |
521 <p>For more information see <a href="https://prosody.im/doc/setting_up_bosh">Prosody: Setting up BOSH</a>.</p> | |
522 </body></html>]]; | |
523 }; | 543 }; |
524 | 544 |
525 module:depends("http"); | 545 module:depends("http"); |
526 module:provides("http", { | 546 module:provides("http", { |
527 default_path = "/http-bind"; | 547 default_path = "/http-bind"; |
528 route = { | 548 route = { |
529 ["GET"] = GET_response; | 549 ["GET"] = GET_response; |
530 ["GET /"] = GET_response; | 550 ["GET /"] = GET_response; |
531 ["OPTIONS"] = handle_OPTIONS; | |
532 ["OPTIONS /"] = handle_OPTIONS; | |
533 ["POST"] = handle_POST; | 551 ["POST"] = handle_POST; |
534 ["POST /"] = handle_POST; | 552 ["POST /"] = handle_POST; |
535 }; | 553 }; |
536 }); | 554 }); |