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 });