From fb680dde0c80e2f84540f500983fcc9275c64e58 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 9 Jan 2009 16:33:09 +0000 Subject: Numerous BOSH improvements... handle client disconnects, either explicit or implicit through inactivity; allow specifying BOSH default parameters through config; fix to prevent prematurely closing request connections in some cases, before they were replied to --- plugins/mod_bosh.lua | 61 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index b3b4aebb..af482556 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -6,27 +6,29 @@ local init_xmlhandlers = require "core.xmlhandlers" local server = require "net.server"; local httpserver = require "net.httpserver"; local sm = require "core.sessionmanager"; +local sm_destroy_session = sm.destroy_session; local new_uuid = require "util.uuid".generate; local fire_event = require "core.eventmanager".fire_event; local core_process_stanza = core_process_stanza; local st = require "util.stanza"; local log = require "util.logger".init("bosh"); local stream_callbacks = { stream_tag = "http://jabber.org/protocol/httpbind|body" }; - +local config = require "core.configmanager"; local xmlns_bosh = "http://jabber.org/protocol/httpbind"; -- (hard-coded into a literal in session.send) -local BOSH_DEFAULT_HOLD = 1; -local BOSH_DEFAULT_INACTIVITY = 30; -local BOSH_DEFAULT_POLLING = 5; -local BOSH_DEFAULT_REQUESTS = 2; -local BOSH_DEFAULT_MAXPAUSE = 120; +local BOSH_DEFAULT_HOLD = tonumber(config.get("*", "core", "bosh_default_hold")) or 1; +local BOSH_DEFAULT_INACTIVITY = tonumber(config.get("*", "core", "bosh_max_inactivity")) or 30; +local BOSH_DEFAULT_POLLING = tonumber(config.get("*", "core", "bosh_max_polling")) or 5; +local BOSH_DEFAULT_REQUESTS = tonumber(config.get("*", "core", "bosh_max_requests")) or 2; +local BOSH_DEFAULT_MAXPAUSE = tonumber(config.get("*", "core", "bosh_max_pause")) or 300; local t_insert, t_remove, t_concat = table.insert, table.remove, table.concat; local os_time = os.time; local sessions = {}; +local inactive_sessions = {}; -- Sessions which have no open requests --- Used to respond to idle sessions +-- Used to respond to idle sessions (those with waiting requests) local waiting_requests = {}; function on_destroy_request(request) waiting_requests[request] = nil; @@ -34,7 +36,6 @@ end function handle_request(method, body, request) if (not body) or request.method ~= "POST" then - --return { status = "200 OK", headers = { ["Content-Type"] = "text/html" }, body = "You don't look like a BOSH client to me... what do you want?" }; return "You really don't look like a BOSH client to me... what do you want?"; end if not method then @@ -60,19 +61,16 @@ function handle_request(method, body, request) log("debug", "...sending what is in the buffer") session.send(t_concat(session.send_buffer)); session.send_buffer = {}; - return; else -- or an empty response log("debug", "...sending an empty response"); session.send(""); - return; end elseif #session.send_buffer > 0 then log("debug", "Session has data in the send buffer, will send now.."); local resp = t_concat(session.send_buffer); session.send_buffer = {}; session.send(resp); - return; end if not request.destroyed and session.bosh_wait then @@ -86,6 +84,7 @@ function handle_request(method, body, request) end end +local session_close_reply = tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate" })); local function bosh_reset_stream(session) session.notopen = true; end local function bosh_close_stream(session, reason) end @@ -95,14 +94,15 @@ function stream_callbacks.streamopened(request, attr) log("debug", "BOSH body open (sid: %s)", attr.sid); local sid = attr.sid if not sid then + -- New session request -- TODO: Sanity checks here (rid, to, known host, etc.) request.notopen = nil; -- Signals that we accept this opening tag -- New session sid = tostring(new_uuid()); local session = { type = "c2s_unauthed", conn = {}, sid = sid, rid = attr.rid, host = attr.to, bosh_version = attr.ver, bosh_wait = attr.wait, streamid = sid, - bosh_hold = BOSH_DEFAULT_HOLD, - requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream, close = bosh_close_stream }; + bosh_hold = BOSH_DEFAULT_HOLD, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY, + requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream, close = bosh_close_stream, dispatch_stanza = core_process_stanza }; sessions[sid] = session; log("info", "New BOSH session, assigned it sid '%s'", sid); local r, send_buffer = session.requests, session.send_buffer; @@ -133,6 +133,10 @@ function stream_callbacks.streamopened(request, attr) end elseif s ~= "" then log("debug", "Saved to send buffer because there are %d open requests", #r); + if session.bosh_max_inactive and not inactive_sessions[session] then + inactive_sessions[session] = os_time(); + (session.log or log)("debug", "BOSH session marked as inactive at %d", inactive_sessions[session]); + end -- Hmm, no requests are open :( t_insert(session.send_buffer, tostring(s)); log("debug", "There are now %d things in the send_buffer", #session.send_buffer); @@ -145,7 +149,7 @@ function stream_callbacks.streamopened(request, attr) fire_event("stream-features", session, features); --xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh' local response = st.stanza("body", { xmlns = xmlns_bosh, - inactivity = "30", polling = "5", requests = "2", hold = tostring(session.bosh_hold), maxpause = "120", + inactivity = tostring(BOSH_DEFAULT_INACTIVITY), polling = tostring(BOSH_DEFAULT_POLLING), requests = tostring(BOSH_DEFAULT_REQUESTS), hold = tostring(session.bosh_hold), maxpause = "120", sid = sid, ver = '1.6', from = session.host, secure = 'true', ["xmpp:version"] = "1.0", ["xmlns:xmpp"] = "urn:xmpp:xbosh", ["xmlns:stream"] = "http://etherx.jabber.org/streams" }):add_child(features); request:send(tostring(response)); @@ -163,6 +167,25 @@ function stream_callbacks.streamopened(request, attr) return; end + if attr.type == "terminate" then + -- Client wants to end this session + (session.log or log)("info", "BOSH client disconnected"); + for _, held_request in ipairs(session.requests) do + held_request:send(session_close_reply); + held_request:destroy(); + end + sm_destroy_session(session); + sessions[sid] = nil; + request.notopen = nil; + return; + end + + -- If session was inactive, make sure it is now marked as not + if #session.requests == 0 then + (session.log or log)("debug", "BOSH client now active again at %d", os_time()); + inactive_sessions[session] = nil; + end + if session.notopen then local features = st.stanza("stream:features"); fire_event("stream-features", session, features); @@ -200,6 +223,16 @@ function on_timer() end end end + + now = now - 3; + for session, inactive_since in pairs(inactive_sessions) do + if now - inactive_since > session.bosh_max_inactive then + (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now); + sessions[session.sid] = nil; + inactive_sessions[session] = nil; + sm_destroy_session(session, "BOSH client silent for over "..session.bosh_max_inactive.." seconds"); + end + end end httpserver.new{ port = 5280, base = "http-bind", handler = handle_request, ssl = false} -- cgit v1.2.3 From c1e6b9426632394045549a99cac18b11955bb687 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 9 Jan 2009 17:27:53 +0000 Subject: Restore fix for missing last_add on deserialized stanzas. Thanks to tsing for discovering. --- util/stanza.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util/stanza.lua b/util/stanza.lua index 6af7e2b2..c4cecb6f 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -199,6 +199,9 @@ function deserialize(stanza) end end stanza.tags = tags; + if not stanza.last_add then + stanza.last_add = {}; + end end end -- cgit v1.2.3 From d4f24630f3a454a0aecaa17ee2667ad1bce367f5 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 9 Jan 2009 17:36:28 +0000 Subject: Add test for previous commit --- tests/test.lua | 3 ++- tests/test_util_stanza.lua | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 tests/test_util_stanza.lua diff --git a/tests/test.lua b/tests/test.lua index eb209219..bc1e1979 100644 --- a/tests/test.lua +++ b/tests/test.lua @@ -25,7 +25,8 @@ function run_all_tests() dotest "core.stanza_router" dotest "core.s2smanager" dotest "core.configmanager" - + dotest "util.stanza" + dosingletest("test_sasl.lua", "latin1toutf8"); end diff --git a/tests/test_util_stanza.lua b/tests/test_util_stanza.lua new file mode 100644 index 00000000..897dab95 --- /dev/null +++ b/tests/test_util_stanza.lua @@ -0,0 +1,7 @@ + +function deserialize(deserialize, st) + local stanza = st.stanza("message", { a = "a" }); + + local stanza2 = deserialize(st.preserialize(stanza)); + assert_is(stanza2.last_add, "Deserialized stanza is missing last_add for adding child tags"); +end -- cgit v1.2.3 From 365368ef5272c34f83f32a77585e10acf174a175 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 9 Jan 2009 17:44:59 +0000 Subject: Add more tests for util/stanza.lua serialization routines --- tests/test_util_stanza.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_util_stanza.lua b/tests/test_util_stanza.lua index 897dab95..f4c4810a 100644 --- a/tests/test_util_stanza.lua +++ b/tests/test_util_stanza.lua @@ -1,7 +1,20 @@ +function preserialize(preserialize, st) + local stanza = st.stanza("message", { a = "a" }); + local stanza2 = preserialize(stanza); + assert_is(stanza2 and stanza.name, "preserialize returns a stanza"); + assert_is_not(stanza2.tags, "Preserialized stanza has no tag list"); + assert_is_not(stanza2.last_add, "Preserialized stanza has no last_add marker"); + assert_is_not(getmetatable(stanza2), "Preserialized stanza has no metatable"); +end + function deserialize(deserialize, st) local stanza = st.stanza("message", { a = "a" }); local stanza2 = deserialize(st.preserialize(stanza)); + assert_is(stanza2 and stanza.name, "deserialize returns a stanza"); assert_is(stanza2.last_add, "Deserialized stanza is missing last_add for adding child tags"); + assert_table(stanza2.attr, "Deserialized stanza has attributes"); + assert_equal(stanza2.attr.a, "a", "Deserialized stanza retains attributes"); + assert_table(getmetatable(stanza2), "Deserialized stanza has metatable"); end -- cgit v1.2.3 From bc31c214d8c650e506676e620b52720f6df8eb1a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 9 Jan 2009 19:16:47 +0000 Subject: Change default maximum inactivity period to 60s from 30s --- plugins/mod_bosh.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index af482556..c0a2ada5 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -17,7 +17,7 @@ local config = require "core.configmanager"; local xmlns_bosh = "http://jabber.org/protocol/httpbind"; -- (hard-coded into a literal in session.send) local BOSH_DEFAULT_HOLD = tonumber(config.get("*", "core", "bosh_default_hold")) or 1; -local BOSH_DEFAULT_INACTIVITY = tonumber(config.get("*", "core", "bosh_max_inactivity")) or 30; +local BOSH_DEFAULT_INACTIVITY = tonumber(config.get("*", "core", "bosh_max_inactivity")) or 60; local BOSH_DEFAULT_POLLING = tonumber(config.get("*", "core", "bosh_max_polling")) or 5; local BOSH_DEFAULT_REQUESTS = tonumber(config.get("*", "core", "bosh_max_requests")) or 2; local BOSH_DEFAULT_MAXPAUSE = tonumber(config.get("*", "core", "bosh_max_pause")) or 300; -- cgit v1.2.3 From bd17ed3f7d3a20cd94ccadbe98bb057f8b8675fc Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Fri, 9 Jan 2009 19:18:46 +0000 Subject: Implement session:close() for BOSH, and add checking for attempts to connect to hosts we don't serve --- plugins/mod_bosh.lua | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index c0a2ada5..d41cc02e 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -84,9 +84,21 @@ function handle_request(method, body, request) end end -local session_close_reply = tostring(st.stanza("body", { xmlns = xmlns_bosh, type = "terminate" })); + local function bosh_reset_stream(session) session.notopen = true; end -local function bosh_close_stream(session, reason) end + +local session_close_reply = st.stanza("body", { xmlns = xmlns_bosh, type = "terminate" }); +local function bosh_close_stream(session, reason) + (session.log or log)("info", "BOSH client disconnected"); + session_close_reply.attr.condition = reason; + local session_close_reply = tostring(session_close_reply); + for _, held_request in ipairs(session.requests) do + held_request:send(session_close_reply); + held_request:destroy(); + end + sessions[session.sid] = nil; + sm_destroy_session(session); +end function stream_callbacks.streamopened(request, attr) print("Attr:") @@ -95,9 +107,17 @@ function stream_callbacks.streamopened(request, attr) local sid = attr.sid if not sid then -- New session request - -- TODO: Sanity checks here (rid, to, known host, etc.) request.notopen = nil; -- Signals that we accept this opening tag + -- TODO: Sanity checks here (rid, to, known host, etc.) + if not hosts[attr.to] then + -- Unknown host + session_close_reply.attr.condition = "host-unknown"; + request:send(tostring(session_close_reply)); + request.notopen = nil + return; + end + -- New session sid = tostring(new_uuid()); local session = { type = "c2s_unauthed", conn = {}, sid = sid, rid = attr.rid, host = attr.to, bosh_version = attr.ver, bosh_wait = attr.wait, streamid = sid, @@ -169,13 +189,7 @@ function stream_callbacks.streamopened(request, attr) if attr.type == "terminate" then -- Client wants to end this session - (session.log or log)("info", "BOSH client disconnected"); - for _, held_request in ipairs(session.requests) do - held_request:send(session_close_reply); - held_request:destroy(); - end - sm_destroy_session(session); - sessions[sid] = nil; + session:close(); request.notopen = nil; return; end -- cgit v1.2.3 From 937375dda1377019b4f592b6f9cea9103cdbd967 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 11 Jan 2009 06:27:57 +0000 Subject: Add child_with_ns() method to stanza elements, and fix child_with_name() to iterate tags rather than all children --- util/stanza.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/util/stanza.lua b/util/stanza.lua index c4cecb6f..14a8f395 100644 --- a/util/stanza.lua +++ b/util/stanza.lua @@ -87,11 +87,17 @@ function stanza_mt:add_child(child) end function stanza_mt:child_with_name(name) - for _, child in ipairs(self) do + for _, child in ipairs(self.tags) do if child.name == name then return child; end end end +function stanza_mt:child_with_ns(ns) + for _, child in ipairs(self.tags) do + if child.attr.xmlns == ns then return child; end + end +end + function stanza_mt:children() local i = 0; return function (a) -- cgit v1.2.3 From 7bd422b3881a111edbb069435c72225d71264e7a Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 11 Jan 2009 07:09:25 +0000 Subject: Added rate limiting to in-band registration, and added IP [black/white]lists --- plugins/mod_register.lua | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/plugins/mod_register.lua b/plugins/mod_register.lua index 377bf153..250be65d 100644 --- a/plugins/mod_register.lua +++ b/plugins/mod_register.lua @@ -23,6 +23,7 @@ local st = require "util.stanza"; local usermanager_user_exists = require "core.usermanager".user_exists; local usermanager_create_user = require "core.usermanager".create_user; local datamanager_store = require "util.datamanager".store; +local os_time = os.time; module:add_feature("jabber:iq:register"); @@ -93,6 +94,14 @@ module:add_iq_handler("c2s", "jabber:iq:register", function (session, stanza) end; end); +local recent_ips = {}; +local min_seconds_between_registrations = config.get(module.host, "core", "min_seconds_between_registrations"); +local whitelisted_ips = config.get(module.host, "core", "registration_whitelist") or { "127.0.0.1" }; +local blacklisted_ips = config.get(module.host, "core", "registration_blacklist") or {}; + +for _, ip in ipairs(whitelisted_ips) do whitelisted_ips[ip] = true; end +for _, ip in ipairs(blacklisted_ips) do blacklisted_ips[ip] = true; end + module:add_iq_handler("c2s_unauthed", "jabber:iq:register", function (session, stanza) if config.get(module.host, "core", "allow_registration") == false then session.send(st.error_reply(stanza, "cancel", "service-unavailable")); @@ -112,6 +121,26 @@ module:add_iq_handler("c2s_unauthed", "jabber:iq:register", function (session, s local username = query:child_with_name("username"); local password = query:child_with_name("password"); if username and password then + -- Check that the user is not blacklisted or registering too often + if blacklisted_ips[session.ip] then + session.send(st.error_reply(stanza, "cancel", "not-acceptable")); + return; + elseif min_seconds_between_registrations and not whitelisted_ips[session.ip] then + if not recent_ips[session.ip] then + recent_ips[session.ip] = { time = os_time(), count = 1 }; + else + + local ip = recent_ips[session.ip]; + ip.count = ip.count + 1; + + if os_time() - ip.time < min_seconds_between_registrations then + ip.time = os_time(); + session.send(st.error_reply(stanza, "cancel", "not-acceptable")); + return; + end + ip.time = os_time(); + end + end -- FIXME shouldn't use table.concat username = table.concat(username); password = table.concat(password); -- cgit v1.2.3 From 7381cd9804f3a6fa6eaabdf1016115a8c6f8d46c Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Sun, 11 Jan 2009 07:15:42 +0000 Subject: Add option to in-band registration to allow only whitelisted IPs to register --- plugins/mod_register.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/mod_register.lua b/plugins/mod_register.lua index 250be65d..44bbf700 100644 --- a/plugins/mod_register.lua +++ b/plugins/mod_register.lua @@ -96,6 +96,7 @@ end); local recent_ips = {}; local min_seconds_between_registrations = config.get(module.host, "core", "min_seconds_between_registrations"); +local whitelist_only = config.get(module.host, "core", "whitelist_registration_only"); local whitelisted_ips = config.get(module.host, "core", "registration_whitelist") or { "127.0.0.1" }; local blacklisted_ips = config.get(module.host, "core", "registration_blacklist") or {}; @@ -122,7 +123,7 @@ module:add_iq_handler("c2s_unauthed", "jabber:iq:register", function (session, s local password = query:child_with_name("password"); if username and password then -- Check that the user is not blacklisted or registering too often - if blacklisted_ips[session.ip] then + if blacklisted_ips[session.ip] or (whitelist_only and not whitelisted_ips[session.ip]) then session.send(st.error_reply(stanza, "cancel", "not-acceptable")); return; elseif min_seconds_between_registrations and not whitelisted_ips[session.ip] then -- cgit v1.2.3 From f5412a5f91b0a211bd74f708e417f7a9de2c338f Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 12 Jan 2009 02:57:49 +0000 Subject: BOSH: Fix for error when closed session was in inactive_sessions list --- plugins/mod_bosh.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index d41cc02e..2fa73fa3 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -240,11 +240,15 @@ function on_timer() now = now - 3; for session, inactive_since in pairs(inactive_sessions) do - if now - inactive_since > session.bosh_max_inactive then - (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now); - sessions[session.sid] = nil; + if session.bosh_max_inactive then + if now - inactive_since > session.bosh_max_inactive then + (session.log or log)("debug", "BOSH client inactive too long, destroying session at %d", now); + sessions[session.sid] = nil; + inactive_sessions[session] = nil; + sm_destroy_session(session, "BOSH client silent for over "..session.bosh_max_inactive.." seconds"); + end + elseif not session.type then inactive_sessions[session] = nil; - sm_destroy_session(session, "BOSH client silent for over "..session.bosh_max_inactive.." seconds"); end end end -- cgit v1.2.3 From 7583e84ee13d63eecf25c9902e5cf4cdf7574192 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 12 Jan 2009 02:59:00 +0000 Subject: BOSH: Make previous fix a bit more efficient --- plugins/mod_bosh.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index 2fa73fa3..2cf35524 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -247,7 +247,7 @@ function on_timer() inactive_sessions[session] = nil; sm_destroy_session(session, "BOSH client silent for over "..session.bosh_max_inactive.." seconds"); end - elseif not session.type then + else inactive_sessions[session] = nil; end end -- cgit v1.2.3 From c273f4c8905b6d605b788e92ae145b5ba0efc2c7 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 12 Jan 2009 02:59:45 +0000 Subject: Set session.ip to the IP address of connecting clients --- core/sessionmanager.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index 963de7ce..f04ca29c 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -58,6 +58,7 @@ function new_session(conn) log("info", "open sessions now: ".. open_sessions); local w = conn.write; session.send = function (t) w(tostring(t)); end + session.ip = conn.ip(); return session; end -- cgit v1.2.3 From ea643d3c907ab9787b2e16961cc03c6ddf58e19e Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 12 Jan 2009 03:27:18 +0000 Subject: modulemanager: Change pairs() to ipairs() to allow ordered module loading --- core/modulemanager.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/modulemanager.lua b/core/modulemanager.lua index 7c1bc0d8..3481aac6 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -68,11 +68,11 @@ function load_modules_for_host(host) local disabled_set = {}; if modules_enabled then if modules_disabled then - for _, module in pairs(modules_disabled) do + for _, module in ipairs(modules_disabled) do disabled_set[module] = true; end end - for _, module in pairs(modules_enabled) do + for _, module in ipairs(modules_enabled) do if not disabled_set[module] then load(host, module); end -- cgit v1.2.3 From 05fe5fb8ffc714ff8b996c0f31dd830193688290 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 12 Jan 2009 03:29:05 +0000 Subject: mod_httpserver: Add require 'net.httpserver' --- plugins/mod_httpserver.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mod_httpserver.lua b/plugins/mod_httpserver.lua index 02a9fd78..2bcdab43 100644 --- a/plugins/mod_httpserver.lua +++ b/plugins/mod_httpserver.lua @@ -1,4 +1,6 @@ +local httpserver = require "net.httpserver"; + local open = io.open; local t_concat = table.concat; -- cgit v1.2.3 From e50d22fcc44c67d513e6b527305134831bdd327b Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 12 Jan 2009 04:02:29 +0000 Subject: core.httpserver: Rename request.responseheaders to the more logical request.headers --- net/httpserver.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/httpserver.lua b/net/httpserver.lua index 3a3c34b4..b9ac7971 100644 --- a/net/httpserver.lua +++ b/net/httpserver.lua @@ -126,7 +126,7 @@ local function request_reader(request, data, startpos) end if request.state == "body" then log("debug", "Reading body...") - if not request.body then request.body = {}; request.havebodylength, request.bodylength = 0, tonumber(request.responseheaders["content-length"]); end + if not request.body then request.body = {}; request.havebodylength, request.bodylength = 0, tonumber(request.headers["content-length"]); end if startpos then data = data:sub(startpos, -1) end @@ -141,7 +141,7 @@ local function request_reader(request, data, startpos) elseif request.state == "headers" then log("debug", "Reading headers...") local pos = startpos; - local headers = request.responseheaders or {}; + local headers = request.headers or {}; for line in data:gmatch("(.-)\r\n") do startpos = (startpos or 1) + #line + 2; local k, v = line:match("(%S+): (.+)"); @@ -149,7 +149,7 @@ local function request_reader(request, data, startpos) headers[k:lower()] = v; -- log("debug", "Header: "..k:lower().." = "..v); elseif #line == 0 then - request.responseheaders = headers; + request.headers = headers; break; else log("debug", "Unhandled header line: "..line); -- cgit v1.2.3 From 9ffcaaa1f99a185bec4160399d0b8f52a70a7527 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 12 Jan 2009 04:05:10 +0000 Subject: Add core.actions for managing server 'actions'; and make modulemanager register actions 'load' and 'unload' --- core/actions.lua | 19 +++++++++++++++++++ core/modulemanager.lua | 15 ++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 core/actions.lua diff --git a/core/actions.lua b/core/actions.lua new file mode 100644 index 00000000..d0be5aeb --- /dev/null +++ b/core/actions.lua @@ -0,0 +1,19 @@ + +local actions = {}; + +function register(path, t) + local curr = actions; + for comp in path:gmatch("([^/]+)/") do + if curr[comp] == nil then + curr[comp] = {}; + end + curr = curr[comp]; + if type(curr) ~= "table" then + return nil, "path-taken"; + end + end + curr[path:match("/([^/]+)$")] = t; + return true; +end + +return { actions = actions, register= register }; \ No newline at end of file diff --git a/core/modulemanager.lua b/core/modulemanager.lua index 3481aac6..6ef39e82 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -27,7 +27,7 @@ local addDiscoInfoHandler = require "core.discomanager".addDiscoInfoHandler; local eventmanager = require "core.eventmanager"; local config = require "core.configmanager"; local multitable_new = require "util.multitable".new; - +local register_actions = require "core.actions".register; local loadfile, pcall = loadfile, pcall; local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv; @@ -254,4 +254,17 @@ end -------------------------------------------------------------------- +local actions = {}; + +function actions.load(params) + --return true, "Module loaded ("..params.module.." on "..params.host..")"; + return load(params.host, params.module); +end + +function actions.unload(params) + return unload(params.host, params.module); +end + +register_actions("/modules", actions); + return _M; -- cgit v1.2.3 From 1980849de01d6eac16e59e8d6986e7d1796c73fe Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 12 Jan 2009 04:09:02 +0000 Subject: Add mod_actions_http for executing actions through HTTP --- plugins/mod_actions_http.lua | 76 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 plugins/mod_actions_http.lua diff --git a/plugins/mod_actions_http.lua b/plugins/mod_actions_http.lua new file mode 100644 index 00000000..327bd670 --- /dev/null +++ b/plugins/mod_actions_http.lua @@ -0,0 +1,76 @@ + +local httpserver = require "net.httpserver"; +local t_concat, t_insert = table.concat, table.insert; + +local log = log; + +local response_404 = { status = "404 Not Found", body = "

No such action

Sorry, I don't have the action you requested" }; + +local control = require "core.actions".actions; + + +local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = string.char(tonumber("0x"..k)); return t[k]; end }); + +local function urldecode(s) + return s and (s:gsub("+", " "):gsub("%%([a-fA-F0-9][a-fA-F0-9])", urlcodes)); +end + +local function query_to_table(query) + if type(query) == "string" and #query > 0 then + if query:match("=") then + local params = {}; + for k, v in query:gmatch("&?([^=%?]+)=([^&%?]+)&?") do + if k and v then + params[urldecode(k)] = urldecode(v); + end + end + return params; + else + return urldecode(query); + end + end +end + + + +local http_path = { http_base }; +local function handle_request(method, body, request) + local path = request.url.path:gsub("^/[^/]+/", ""); + + local curr = control; + + for comp in path:gmatch("([^/]+)") do + curr = curr[comp]; + if not curr then + return response_404; + end + end + + if type(curr) == "table" then + local s = {}; + for k,v in pairs(curr) do + t_insert(s, tostring(k)); + t_insert(s, " = "); + if type(v) == "function" then + t_insert(s, "action") + else + t_insert(s, tostring(v)); + end + t_insert(s, "\n"); + end + return t_concat(s); + elseif type(curr) == "function" then + local params = query_to_table(request.url.query); + params.host = request.headers.host:gsub(":%d+", ""); + local ok, ret1, ret2 = pcall(curr, params); + if not ok then + return "EPIC FAIL: "..tostring(ret1); + elseif not ret1 then + return "FAIL: "..tostring(ret2); + else + return "OK: "..tostring(ret2); + end + end +end + +httpserver.new{ port = 5280, base = "control", handler = handle_request, ssl = false } \ No newline at end of file -- cgit v1.2.3 From 393b7385885eb8ec8e9e93daf31a365480b127a9 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 12 Jan 2009 04:13:05 +0000 Subject: mod_actions_http: Show tables as 'list's --- plugins/mod_actions_http.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/mod_actions_http.lua b/plugins/mod_actions_http.lua index 327bd670..43370a41 100644 --- a/plugins/mod_actions_http.lua +++ b/plugins/mod_actions_http.lua @@ -53,6 +53,8 @@ local function handle_request(method, body, request) t_insert(s, " = "); if type(v) == "function" then t_insert(s, "action") + elseif type(v) == "table" then + t_insert(s, "list"); else t_insert(s, tostring(v)); end -- cgit v1.2.3 From 8b979ccaf40ae38dbeaa5e7a4edb2e6d760fdffa Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Tue, 13 Jan 2009 05:55:31 +0000 Subject: BOSH: Allow BOSH servers to be configured through config file --- plugins/mod_bosh.lua | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index 2cf35524..b5951e96 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -253,5 +253,17 @@ function on_timer() end end -httpserver.new{ port = 5280, base = "http-bind", handler = handle_request, ssl = false} +local ports = config.get(module.host, "core", "bosh_ports") or { 5280 }; +for _, options in ipairs(ports) do + local port, base, ssl, interface = 5280, "http-bind", false, nil; + if type(options) == "number" then + port = options; + elseif type(options) == "table" then + port, base, ssl, interface = options.port or 5280, options.path or "http-bind", options.ssl or false, options.interface; + elseif type(options) == "string" then + base = options; + end + httpserver.new{ port = port, base = base, handler = handle_request, ssl = ssl } +end + server.addtimer(on_timer); -- cgit v1.2.3