diff options
-rw-r--r-- | core/modulemanager.lua | 2 | ||||
-rw-r--r-- | net/http.lua | 1 | ||||
-rw-r--r-- | net/httpserver.lua | 3 | ||||
-rw-r--r-- | net/xmppcomponent_listener.lua | 171 | ||||
-rw-r--r-- | plugins/mod_bosh.lua | 7 | ||||
-rw-r--r-- | plugins/mod_component.lua | 158 | ||||
-rwxr-xr-x | prosody | 1 | ||||
-rw-r--r-- | util/multitable.lua | 4 | ||||
-rw-r--r-- | util/serialization.lua | 4 |
9 files changed, 190 insertions, 161 deletions
diff --git a/core/modulemanager.lua b/core/modulemanager.lua index 2cba50ac..6dbc8c53 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -28,7 +28,7 @@ local type = type; local next = next; local rawget = rawget; -local tostring, print = tostring, print; +local tostring = tostring; -- We need this to let modules access the real global namespace local _G = _G; diff --git a/net/http.lua b/net/http.lua index 24828ffe..b39cc6fb 100644 --- a/net/http.lua +++ b/net/http.lua @@ -15,7 +15,6 @@ local tonumber, tostring, pairs, xpcall, select, debug_traceback, char = local log = require "util.logger".init("http"); local print = function () end -local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end }); local urlencode = function (s) return s and (s:gsub("%W", function (c) return string.format("%%%02x", c:byte()); end)); end module "http" diff --git a/net/httpserver.lua b/net/httpserver.lua index 0ecf84ea..12328b29 100644 --- a/net/httpserver.lua +++ b/net/httpserver.lua @@ -8,10 +8,9 @@ local connlisteners_get = require "net.connlisteners".get; local listener; local t_insert, t_concat = table.insert, table.concat; -local s_match, s_gmatch, s_char = string.match, string.gmatch; +local s_match, s_gmatch = string.match, string.gmatch; local tonumber, tostring, pairs = tonumber, tostring, pairs; -local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = s_char(tonumber("0x"..k)); return t[k]; end }); local urlencode = function (s) return s and (s:gsub("%W", function (c) return string.format("%%%x", c:byte()); end)); end local log = require "util.logger".init("httpserver"); diff --git a/net/xmppcomponent_listener.lua b/net/xmppcomponent_listener.lua new file mode 100644 index 00000000..03675e19 --- /dev/null +++ b/net/xmppcomponent_listener.lua @@ -0,0 +1,171 @@ +-- Prosody IM v0.4 +-- Copyright (C) 2008-2009 Matthew Wild +-- Copyright (C) 2008-2009 Waqas Hussain +-- +-- This project is MIT/X11 licensed. Please see the +-- COPYING file in the source package for more information. +-- + + +local hosts = _G.hosts; + +local t_concat = table.concat; + +local lxp = require "lxp"; +local logger = require "util.logger"; +local config = require "core.configmanager"; +local eventmanager = require "core.eventmanager"; +local connlisteners = require "net.connlisteners"; +local cm_register_component = require "core.componentmanager".register_component; +local cm_deregister_component = require "core.componentmanager".deregister_component; +local uuid_gen = require "util.uuid".generate; +local sha1 = require "util.hashes".sha1; +local st = require "util.stanza"; +local init_xmlhandlers = require "core.xmlhandlers"; + +local sessions = {}; + +local log = logger.init("componentlistener"); + +local component_listener = { default_port = 5347; default_mode = "*a"; default_interface = config.get("*", "core", "component_interface") or "127.0.0.1" }; + +local xmlns_component = 'jabber:component:accept'; + +--- Callbacks/data for xmlhandlers to handle streams for us --- + +local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams|stream", default_ns = xmlns_component }; + +function stream_callbacks.error(session, error, data, data2) + log("warn", "Error processing component stream: "..tostring(error)); + if error == "no-stream" then + session:close("invalid-namespace"); + elseif error == "xml-parse-error" and data == "unexpected-element-close" then + session.log("warn", "Unexpected close of '%s' tag", data2); + session:close("xml-not-well-formed"); + else + session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(error)); + session:close("xml-not-well-formed"); + end +end + +function stream_callbacks.streamopened(session, attr) + if config.get(attr.to, "core", "component_module") ~= "component" then + -- Trying to act as a component domain which + -- hasn't been configured + session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" }; + return; + end + + -- Store the original host (this is used for config, etc.) + session.user = attr.to; + -- Set the host for future reference + session.host = config.get(attr.to, "core", "component_address") or attr.to; + -- Note that we don't create the internal component + -- until after the external component auths successfully + + session.streamid = uuid_gen(); + session.notopen = nil; + + session.send(st.stanza("stream:stream", { xmlns=xmlns_component, + ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag()); + +end + +function stream_callbacks.streamclosed(session) + session.send("</stream:stream>"); + session.notopen = true; +end + +local core_process_stanza = core_process_stanza; + +function stream_callbacks.handlestanza(session, stanza) + -- Namespaces are icky. + if not stanza.attr.xmlns and stanza.name == "handshake" then + stanza.attr.xmlns = xmlns_component; + end + return core_process_stanza(session, stanza); +end + +--- Closing a component connection +local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; +local function session_close(session, reason) + local log = session.log or log; + if session.conn then + if reason then + if type(reason) == "string" then -- assume stream error + log("info", "Disconnecting component, <stream:error> is: %s", reason); + session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' })); + elseif type(reason) == "table" then + if reason.condition then + local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up(); + if reason.text then + stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); + end + if reason.extra then + stanza:add_child(reason.extra); + end + log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza)); + session.send(stanza); + elseif reason.name then -- a stanza + log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason)); + session.send(reason); + end + end + end + session.send("</stream:stream>"); + session.conn.close(); + component_listener.disconnect(session.conn, "stream error"); + end +end + +--- Component connlistener +function component_listener.listener(conn, data) + local session = sessions[conn]; + if not session then + local _send = conn.write; + session = { type = "component", conn = conn, send = function (data) return _send(tostring(data)); end }; + sessions[conn] = session; + + -- Logging functions -- + + local conn_name = "jcp"..tostring(conn):match("[a-f0-9]+$"); + session.log = logger.init(conn_name); + session.close = session_close; + + session.log("info", "Incoming Jabber component connection"); + + local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "|"); + session.parser = parser; + + session.notopen = true; + + function session.data(conn, data) + local ok, err = parser:parse(data); + if ok then return; end + session:close("xml-not-well-formed"); + end + + session.dispatch_stanza = stream_callbacks.handlestanza; + + end + if data then + session.data(conn, data); + end +end + +function component_listener.disconnect(conn, err) + local session = sessions[conn]; + if session then + (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err)); + if session.host then + log("debug", "Deregistering component"); + cm_deregister_component(session.host); + hosts[session.host].connected = nil; + end + sessions[conn] = nil; + session = nil; + collectgarbage("collect"); + end +end + +connlisteners.register('xmppcomponent', component_listener); diff --git a/plugins/mod_bosh.lua b/plugins/mod_bosh.lua index 653af8bb..46e9a95f 100644 --- a/plugins/mod_bosh.lua +++ b/plugins/mod_bosh.lua @@ -12,7 +12,8 @@ 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 logger = require "util.logger"; +local log = logger.init("mod_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) @@ -137,8 +138,10 @@ function stream_callbacks.streamopened(request, attr) sid = 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, bosh_max_inactive = BOSH_DEFAULT_INACTIVITY, - requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream, close = bosh_close_stream, dispatch_stanza = core_process_stanza }; + requests = { }, send_buffer = {}, reset_stream = bosh_reset_stream, close = bosh_close_stream, + dispatch_stanza = core_process_stanza, log = logger.init("bosh"..sid) }; sessions[sid] = session; + log("info", "New BOSH session, assigned it sid '%s'", sid); local r, send_buffer = session.requests, session.send_buffer; local response = { headers = default_headers } diff --git a/plugins/mod_component.lua b/plugins/mod_component.lua index e5ea47a2..fa2c166f 100644 --- a/plugins/mod_component.lua +++ b/plugins/mod_component.lua @@ -33,74 +33,18 @@ local component_listener = { default_port = 5347; default_mode = "*a"; default_i local xmlns_component = 'jabber:component:accept'; ---- Callbacks/data for xmlhandlers to handle streams for us --- - -local stream_callbacks = { stream_tag = "http://etherx.jabber.org/streams|stream", default_ns = xmlns_component }; - -function stream_callbacks.error(session, error, data, data2) - log("warn", "Error processing component stream: "..tostring(error)); - if error == "no-stream" then - session:close("invalid-namespace"); - elseif error == "xml-parse-error" and data == "unexpected-element-close" then - session.log("debug", "Unexpected close of '%s' tag", data2); - session:close("xml-not-well-formed"); - else - session.log("debug", "External component %s XML parse error: %s", tostring(session.host), tostring(error)); - print(data, debug.traceback()) - session:close("xml-not-well-formed"); - end -end - -function stream_callbacks.streamopened(session, attr) - if config.get(attr.to, "core", "component_module") ~= "component" then - -- Trying to act as a component domain which - -- hasn't been configured - session:close{ condition = "host-unknown", text = tostring(attr.to).." does not match any configured external components" }; - return; - end - - -- Store the original host (this is used for config, etc.) - session.user = attr.to; - -- Set the host for future reference - session.host = config.get(attr.to, "core", "component_address") or attr.to; - -- Note that we don't create the internal component - -- until after the external component auths successfully - - session.streamid = uuid_gen(); - session.notopen = nil; - - session.send(st.stanza("stream:stream", { xmlns=xmlns_component, - ["xmlns:stream"]='http://etherx.jabber.org/streams', id=session.streamid, from=session.host }):top_tag()); - -end - -function stream_callbacks.streamclosed(session) - session.send("</stream:stream>"); - session.notopen = true; -end - -local core_process_stanza = core_process_stanza; - -function stream_callbacks.handlestanza(session, stanza) - -- Namespaces are icky. - log("warn", "Handing stanza with name %s", stanza.name); - if stanza.name ~= "handshake" then - return core_process_stanza(session, stanza); - else - handle_component_auth(session, stanza); - end -end - --- Handle authentication attempts by components function handle_component_auth(session, stanza) + log("info", "Handling component auth"); if (not session.host) or #stanza.tags > 0 then + (session.log or log)("warn", "Component handshake invalid"); session:close("not-authorized"); return; end local secret = config.get(session.user, "core", "component_secret"); if not secret then - log("warn", "Component attempted to identify as %s, but component_password is not set", session.user); + (session.log or log)("warn", "Component attempted to identify as %s, but component_password is not set", session.user); session:close("not-authorized"); return; end @@ -108,18 +52,21 @@ function handle_component_auth(session, stanza) local supplied_token = t_concat(stanza); local calculated_token = sha1(session.streamid..secret, true); if supplied_token:lower() ~= calculated_token:lower() then + log("info", "Component for %s authentication failed", session.host); session:close{ condition = "not-authorized", text = "Given token does not match calculated token" }; return; end -- Authenticated now + log("info", "Component authenticated: %s", session.host); -- If component not already created for this host, create one now if not hosts[session.host].connected then local send = session.send; session.component_session = cm_register_component(session.host, function (_, data) return send(data); end); hosts[session.host].connected = true; + log("info", "Component successfully registered"); else log("error", "Multiple components bound to the same address, first one wins (TODO: Implement stanza distribution)"); end @@ -129,96 +76,3 @@ function handle_component_auth(session, stanza) end module:add_handler("component", "handshake", xmlns_component, handle_component_auth); - ---- Closing a component connection -local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'}; -local function session_close(session, reason) - local log = session.log or log; - if session.conn then - if reason then - if type(reason) == "string" then -- assume stream error - log("info", "Disconnecting component, <stream:error> is: %s", reason); - session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' })); - elseif type(reason) == "table" then - if reason.condition then - local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up(); - if reason.text then - stanza:tag("text", stream_xmlns_attr):text(reason.text):up(); - end - if reason.extra then - stanza:add_child(reason.extra); - end - log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza)); - session.send(stanza); - elseif reason.name then -- a stanza - log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason)); - session.send(reason); - end - end - end - session.send("</stream:stream>"); - session.conn.close(); - component_listener.disconnect(session.conn, "stream error"); - end -end - ---- Component connlistener -function component_listener.listener(conn, data) - local session = sessions[conn]; - if not session then - local _send = conn.write; - session = { type = "component", conn = conn, send = function (data) return _send(tostring(data)); end }; - sessions[conn] = session; - - -- Logging functions -- - - local conn_name = "xep114-"..tostring(conn):match("[a-f0-9]+$"); - session.log = logger.init(conn_name); - session.close = session_close; - - session.log("info", "Incoming XEP-0114 connection"); - - local parser = lxp.new(init_xmlhandlers(session, stream_callbacks), "|"); - session.parser = parser; - - session.notopen = true; - - function session.data(conn, data) - local ok, err = parser:parse(data); - if ok then return; end - session:close("xml-not-well-formed"); - end - - session.dispatch_stanza = stream_callbacks.handlestanza; - - end - if data then - session.data(conn, data); - end -end - -function component_listener.disconnect(conn, err) - local session = sessions[conn]; - if session then - (session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err)); - if session.host then - log("debug", "deregistering component"); - cm_deregister_component(session.host); - hosts[session.host].connected = nil; - end - sessions[conn] = nil; - session = nil; - collectgarbage("collect"); - end -end - -connlisteners.register('component', component_listener); - -module:add_event_hook("server-started", - function () - if _G.net_activate_ports then - _G.net_activate_ports("component", "component", {5437}, "tcp"); - else - error("No net_activate_ports: Using an incompatible version of Prosody?"); - end - end); @@ -156,6 +156,7 @@ end net_activate_ports("c2s", "xmppclient", {5222}, (global_ssl_ctx and "tls") or "tcp"); net_activate_ports("s2s", "xmppserver", {5269}, "tcp"); +net_activate_ports("component", "xmppcomponent", {}, "tcp"); net_activate_ports("legacy_ssl", "xmppclient", {}, "ssl"); if cl.get("console") then diff --git a/util/multitable.lua b/util/multitable.lua index c0bb2205..d1858d8f 100644 --- a/util/multitable.lua +++ b/util/multitable.lua @@ -56,7 +56,7 @@ local function r(t, n, _end, ...) return; end if k then - v = t[k]; + local v = t[k]; if v then r(v, n+1, _end, ...); if not next(v) then @@ -96,7 +96,7 @@ local function s(t, n, results, _end, ...) return; end if k then - v = t[k]; + local v = t[k]; if v then s(v, n+1, results, _end, ...); end diff --git a/util/serialization.lua b/util/serialization.lua index bff0f306..41d963e9 100644 --- a/util/serialization.lua +++ b/util/serialization.lua @@ -14,6 +14,8 @@ local t_concat = table.concat; local error = error; local pairs = pairs; +local debug_traceback = debug.traceback; +local log = require "util.logger".init("serialization"); module "serialization" local indent = function(i) @@ -50,7 +52,7 @@ local function _simplesave(o, ind, t, func) elseif type(o) == "boolean" then func(t, (o and "true" or "false")); else - error("cannot serialize a " .. type(o)) + log("warn", "cannot serialize a %s: %s", type(o), debug_traceback()) end end |