diff options
-rw-r--r-- | core/modulemanager.lua | 42 | ||||
-rw-r--r-- | core/servermanager.lua | 14 | ||||
-rw-r--r-- | core/sessionmanager.lua | 72 | ||||
-rw-r--r-- | core/usermanager.lua | 2 | ||||
-rw-r--r-- | core/xmlhandlers.lua | 32 | ||||
-rw-r--r-- | main.lua | 12 | ||||
-rw-r--r-- | plugins/mod_legacyauth.lua | 2 | ||||
-rw-r--r-- | plugins/mod_saslauth.lua | 66 | ||||
-rw-r--r-- | util/import.lua | 13 | ||||
-rw-r--r-- | util/logger.lua | 2 | ||||
-rw-r--r-- | util/sasl.lua | 2 | ||||
-rw-r--r-- | util/uuid.lua | 9 |
12 files changed, 224 insertions, 44 deletions
diff --git a/core/modulemanager.lua b/core/modulemanager.lua index ad92b41b..6d803a26 100644 --- a/core/modulemanager.lua +++ b/core/modulemanager.lua @@ -4,6 +4,7 @@ local log = require "util.logger".init("modulemanager") local loadfile, pcall = loadfile, pcall; local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv; local pairs, ipairs = pairs, ipairs; +local t_insert = table.insert; local type = type; local tostring, print = tostring, print; @@ -18,6 +19,7 @@ local handlers = {}; local modulehelpers = setmetatable({}, { __index = _G }); function modulehelpers.add_iq_handler(origin_type, xmlns, handler) + if not (origin_type and handler and xmlns) then return false; end handlers[origin_type] = handlers[origin_type] or {}; handlers[origin_type].iq = handlers[origin_type].iq or {}; if not handlers[origin_type].iq[xmlns] then @@ -29,17 +31,19 @@ function modulehelpers.add_iq_handler(origin_type, xmlns, handler) end end -function modulehelpers.add_handler(origin_type, tag, handler) +function modulehelpers.add_handler(origin_type, tag, xmlns, handler) + if not (origin_type and tag and xmlns and handler) then return false; end handlers[origin_type] = handlers[origin_type] or {}; if not handlers[origin_type][tag] then - handlers[origin_type][tag]= handler; + handlers[origin_type][tag] = handlers[origin_type][tag] or {}; + handlers[origin_type][tag][xmlns]= handler; handler_info[handler] = getfenv(2).module; log("debug", "mod_%s now handles tag '%s'", getfenv(2).module.name, tag); elseif handler_info[handlers[origin_type][tag]] then log("warning", "mod_%s wants to handle tag '%s' but mod_%s already handles that", getfenv(2).module.name, tag, handler_info[handlers[origin_type][tag]].module.name); end end - + function loadall() load("saslauth"); load("legacyauth"); @@ -79,14 +83,38 @@ function handle_stanza(origin, stanza) end end - --FIXME: All iq's must be replied to, here we should return service-unavailable I think elseif handlers[origin_type] then local handler = handlers[origin_type][name]; if handler then - log("debug", "Passing stanza to mod_%s", handler_info[handler].name); - return handler(origin, stanza) or true; + handler = handler[xmlns]; + if handler then + log("debug", "Passing stanza to mod_%s", handler_info[handler].name); + return handler(origin, stanza) or true; + end end end - log("debug", "Stanza unhandled by any modules"); + log("debug", "Stanza unhandled by any modules, xmlns: %s", stanza.attr.xmlns); return false; -- we didn't handle it end + +do + local event_handlers = {}; + + function modulehelpers.add_event_hook(name, handler) + if not event_handlers[name] then + event_handlers[name] = {}; + end + t_insert(event_handlers[name] , handler); + end + + function fire_event(name, ...) + local event_handlers = event_handlers[name]; + if event_handlers then + for name, handler in ipairs(event_handlers) do + handler(...); + end + end + end +end + +return _M; diff --git a/core/servermanager.lua b/core/servermanager.lua index 02ccab53..c1e075db 100644 --- a/core/servermanager.lua +++ b/core/servermanager.lua @@ -1,8 +1,20 @@ +local st = require "util.stanza"; +local send = require "core.sessionmanager".send_to_session; +local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas'; + require "modulemanager" -- Handle stanzas that were addressed to the server (whether they came from c2s, s2s, etc.) function handle_stanza(origin, stanza) -- Use plugins - return modulemanager.handle_stanza(origin, stanza); + if not modulemanager.handle_stanza(origin, stanza) then + if stanza.name == "iq" then + local reply = st.reply(stanza); + reply.attr.type = "error"; + reply:tag("error", { type = "cancel" }) + :tag("service-unavailable", { xmlns = xmlns_stanzas }); + send(origin, reply); + end + end end diff --git a/core/sessionmanager.lua b/core/sessionmanager.lua index e89de262..3366120e 100644 --- a/core/sessionmanager.lua +++ b/core/sessionmanager.lua @@ -1,22 +1,62 @@ -local tostring = tostring; - -local print = print; +local tonumber, tostring = tonumber, tostring; +local ipairs, pairs, print= ipairs, pairs, print; +local collectgarbage = collectgarbage; +local m_random = import("math", "random"); +local format = import("string", "format"); local hosts = hosts; +local sessions = sessions; +local modulemanager = require "core.modulemanager"; local log = require "util.logger".init("sessionmanager"); +local error = error; +local uuid_generate = require "util.uuid".uuid_generate; + +local newproxy = newproxy; +local getmetatable = getmetatable; module "sessionmanager" function new_session(conn) local session = { conn = conn, notopen = true, priority = 0, type = "c2s_unauthed" }; + if true then + session.trace = newproxy(true); + getmetatable(session.trace).__gc = function () print("Session got collected") end; + end local w = conn.write; session.send = function (t) w(tostring(t)); end return session; end function destroy_session(session) + if not (session and session.disconnect) then return; end + log("debug", "Destroying session..."); + session.disconnect(); + if session.username then + if session.resource then + hosts[session.host].sessions[session.username].sessions[session.resource] = nil; + end + local nomore = true; + for res, ssn in pairs(hosts[session.host].sessions[session.username]) do + nomore = false; + end + if nomore then + hosts[session.host].sessions[session.username] = nil; + end + end + session.conn = nil; + session.disconnect = nil; + for k in pairs(session) do + if k ~= "trace" then + session[k] = nil; + end + end + collectgarbage("collect"); + collectgarbage("collect"); + collectgarbage("collect"); + collectgarbage("collect"); + collectgarbage("collect"); end function send_to_session(session, data) @@ -30,12 +70,13 @@ function make_authenticated(session, username) if session.type == "c2s_unauthed" then session.type = "c2s"; end + return true; end function bind_resource(session, resource) if not session.username then return false, "auth"; end if session.resource then return false, "constraint"; end -- We don't support binding multiple resources - resource = resource or math.random(100000, 99999999); -- FIXME: Clearly we have issues :) + resource = resource or uuid_generate(); --FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing if not hosts[session.host].sessions[session.username] then @@ -54,4 +95,27 @@ function bind_resource(session, resource) return true; end +function streamopened(session, attr) + local send = session.send; + session.host = attr.to or error("Client failed to specify destination hostname"); + session.version = tonumber(attr.version) or 0; + session.streamid = m_random(1000000, 99999999); + print(session, session.host, "Client opened stream"); + send("<?xml version='1.0'?>"); + send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0'>", session.streamid, session.host)); + + local features = {}; + modulemanager.fire_event("stream-features", session, features); + + send("<stream:features>"); + + for _, feature in ipairs(features) do + send_to_session(session, tostring(feature)); + end + + send("</stream:features>"); + log("info", "Stream opened successfully"); + session.notopen = nil; +end + return _M;
\ No newline at end of file diff --git a/core/usermanager.lua b/core/usermanager.lua index a67ad368..0f303b24 100644 --- a/core/usermanager.lua +++ b/core/usermanager.lua @@ -1,10 +1,12 @@ require "util.datamanager" local datamanager = datamanager; +local log = require "util.logger".init("usermanager"); module "usermanager" function validate_credentials(host, username, password) + log("debug", "User '%s' is being validated", username); local credentials = datamanager.load(username, host, "accounts") or {}; if password == credentials.password then return true; end return false; diff --git a/core/xmlhandlers.lua b/core/xmlhandlers.lua index b6050c5a..ebc8f91d 100644 --- a/core/xmlhandlers.lua +++ b/core/xmlhandlers.lua @@ -1,4 +1,5 @@ +local sessionmanager_streamopened = require "core.sessionmanager".streamopened; require "util.stanza" local st = stanza; @@ -9,6 +10,7 @@ local t_insert = table.insert; local t_remove = table.remove; local t_concat = table.concat; local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end +local sm_destroy_session = import("core.sessionmanager", "destroy_session"); local error = error; @@ -27,7 +29,6 @@ function init_xmlhandlers(session) local stanza function xml_handlers:StartElement(name, attr) - log("info", "xmlhandlers", "Start element: " .. name); if stanza and #chardata > 0 then -- We have some character data in the buffer stanza:text(t_concat(chardata)); @@ -37,24 +38,7 @@ function init_xmlhandlers(session) if not stanza then if session.notopen then if name == "stream" then - session.host = attr.to or error("Client failed to specify destination hostname"); - session.version = attr.version or 0; - session.streamid = m_random(1000000, 99999999); - print(session, session.host, "Client opened stream"); - send("<?xml version='1.0'?>"); - send(format("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='%s' from='%s' version='1.0'>", session.streamid, session.host)); - send("<stream:features>"); - if not session.username then - send("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"); - send("<mechanism>PLAIN</mechanism>"); - send("</mechanisms>"); - else - send("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><required/></bind>"); - end - --send [[<register xmlns="http://jabber.org/features/iq-register"/> ]] - send("</stream:features>"); - log("info", "core", "Stream opened successfully"); - session.notopen = nil; + sessionmanager_streamopened(session, attr); return; end error("Client failed to open stream successfully"); @@ -77,7 +61,15 @@ function init_xmlhandlers(session) end function xml_handlers:EndElement(name) curr_ns,name = name:match("^(.+):(%w+)$"); - if (not stanza) or #stanza.last_add < 0 or (#stanza.last_add > 0 and name ~= stanza.last_add[#stanza.last_add].name) then error("XML parse error in client stream"); end + if (not stanza) or #stanza.last_add < 0 or (#stanza.last_add > 0 and name ~= stanza.last_add[#stanza.last_add].name) then + if name == "stream" then + log("debug", "Stream closed"); + sm_destroy_session(session); + return; + else + error("XML parse error in client stream"); + end + end if stanza and #chardata > 0 then -- We have some character data in the buffer stanza:text(t_concat(chardata)); @@ -13,6 +13,7 @@ dofile "lxmppd.cfg" sessions = {}; +require "util.import" require "core.stanza_dispatch" require "core.xmlhandlers" require "core.rostermanager" @@ -24,6 +25,7 @@ require "core.stanza_router" require "net.connhandlers" require "util.stanza" require "util.jid" + -- Locals for faster access -- local t_insert = table.insert; @@ -31,6 +33,7 @@ local t_concat = table.concat; local t_concatall = function (t, sep) local tt = {}; for _, s in ipairs(t) do t_insert(tt, tostring(s)); end return t_concat(tt, sep); end local m_random = math.random; local format = string.format; +local sm_new_session, sm_destroy_session = sessionmanager.new_session, sessionmanager.destroy_session; --import("core.sessionmanager", "new_session", "destroy_session"); local st = stanza; ------------------------------ @@ -46,7 +49,7 @@ function handler(conn, data, err) local session = sessions[conn]; if not session then - sessions[conn] = sessionmanager.new_session(conn); + sessions[conn] = sm_new_session(conn); session = sessions[conn]; -- Logging functions -- @@ -73,11 +76,8 @@ function handler(conn, data, err) pres:tag("status"):text("Disconnected: "..err); session.stanza_dispatch(pres); end - if session.username then - hosts[session.host].sessions[session.username] = nil; - end session = nil; - print("Disconnected: "..err); + print("Disconnected: "..tostring(err)); collectgarbage("collect"); end end @@ -89,7 +89,7 @@ function handler(conn, data, err) end function disconnect(conn, err) - sessions[conn].disconnect(err); + sm_destroy_session(sessions[conn]); sessions[conn] = nil; end diff --git a/plugins/mod_legacyauth.lua b/plugins/mod_legacyauth.lua index 7a205e5b..8ac10bd4 100644 --- a/plugins/mod_legacyauth.lua +++ b/plugins/mod_legacyauth.lua @@ -38,7 +38,7 @@ add_iq_handler("c2s_unauthed", "jabber:iq:auth", reply:tag("error", { code = "401", type = "auth" }) :tag("not-authorized", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" }); end - dispatch_stanza(reply); + send(session, reply); return true; end end diff --git a/plugins/mod_saslauth.lua b/plugins/mod_saslauth.lua index dc6f3645..8ef1e09d 100644 --- a/plugins/mod_saslauth.lua +++ b/plugins/mod_saslauth.lua @@ -1,19 +1,22 @@ local st = require "util.stanza"; local send = require "core.sessionmanager".send_to_session; +local sm_bind_resource = require "core.sessionmanager".bind_resource; local usermanager_validate_credentials = require "core.usermanager".validate_credentials; -local t_concat = table.concat; +local t_concat, t_insert = table.concat, table.insert; local tostring = tostring; local log = require "util.logger".init("mod_saslauth"); local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl'; +local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind'; +local xmlns_stanzas ='urn:ietf:params:xml:ns:xmpp-stanzas'; local new_connhandler = require "net.connhandlers".new; local new_sasl = require "util.sasl".new; -add_handler("c2s_unauthed", "auth", +add_handler("c2s_unauthed", "auth", xmlns_sasl, function (session, stanza) if not session.sasl_handler then session.sasl_handler = new_sasl(stanza.attr.mechanism, @@ -30,6 +33,7 @@ add_handler("c2s_unauthed", "auth", local success, err = sessionmanager.make_authenticated(session, username); if not success then sessionmanager.destroy_session(session); + return; end session.sasl_handler = nil; session.connhandler = new_connhandler("xmpp-client", session); @@ -50,4 +54,60 @@ add_handler("c2s_unauthed", "auth", error("Client tried to negotiate SASL again", 0); end - end);
\ No newline at end of file + end); + +add_event_hook("stream-features", + function (session, features) + if not session.username then + t_insert(features, "<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>"); + t_insert(features, "<mechanism>PLAIN</mechanism>"); + t_insert(features, "</mechanisms>"); + else + t_insert(features, "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><required/></bind>"); + t_insert(features, "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>"); + end + --send [[<register xmlns="http://jabber.org/features/iq-register"/> ]] + end); + +add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-bind", + function (session, stanza) + log("debug", "Client tried to bind to a resource"); + local resource; + if stanza.attr.type == "set" then + local bind = stanza.tags[1]; + + if bind and bind.attr.xmlns == xmlns_bind then + resource = bind:child_with_name("resource"); + if resource then + resource = resource[1]; + end + end + end + local success, err = sm_bind_resource(session, resource); + if not success then + local reply = st.reply(stanza); + reply.attr.type = "error"; + if err == "conflict" then + reply:tag("error", { type = "modify" }) + :tag("conflict", { xmlns = xmlns_stanzas }); + elseif err == "constraint" then + reply:tag("error", { type = "cancel" }) + :tag("resource-constraint", { xmlns = xmlns_stanzas }); + elseif err == "auth" then + reply:tag("error", { type = "cancel" }) + :tag("not-allowed", { xmlns = xmlns_stanzas }); + end + send(session, reply); + else + local reply = st.reply(stanza); + reply:tag("bind", { xmlns = xmlns_bind}) + :tag("jid"):text(session.full_jid); + send(session, reply); + end + end); + +add_iq_handler("c2s", "urn:ietf:params:xml:ns:xmpp-session", + function (session, stanza) + log("debug", "Client tried to bind to a resource"); + send(session, st.reply(stanza)); + end); diff --git a/util/import.lua b/util/import.lua new file mode 100644 index 00000000..6aab0d45 --- /dev/null +++ b/util/import.lua @@ -0,0 +1,13 @@ + +local t_insert = table.insert; +function import(module, ...) + local m = package.loaded[module] or require(module); + if type(m) == "table" and ... then + local ret = {}; + for _, f in ipairs{...} do + t_insert(ret, m[f]); + end + return unpack(ret); + end + return m; +end diff --git a/util/logger.lua b/util/logger.lua index 3d672e94..623ceb67 100644 --- a/util/logger.lua +++ b/util/logger.lua @@ -9,7 +9,7 @@ function init(name) name = nil; -- While this line is not commented, will automatically fill in file/line number info return function (level, message, ...) if not name then - local inf = debug.getinfo(2, 'Snl'); + local inf = debug.getinfo(3, 'Snl'); level = level .. ","..tostring(inf.short_src):match("[^/]*$")..":"..inf.currentline; end if ... then diff --git a/util/sasl.lua b/util/sasl.lua index 515bcf8a..dbd6326a 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -19,7 +19,7 @@ local function new_plain(onAuth, onSuccess, onFail, onWrite) local authorization = s_match(response, "([^&%z]+)") local authentication = s_match(response, "%z([^&%z]+)%z") local password = s_match(response, "%z[^&%z]+%z([^&%z]+)") - if self.onAuth(authorization, password) == true then + if self.onAuth(authentication, password) == true then self.onWrite(st.stanza("success", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"})) self.onSuccess(authentication) else diff --git a/util/uuid.lua b/util/uuid.lua new file mode 100644 index 00000000..489522aa --- /dev/null +++ b/util/uuid.lua @@ -0,0 +1,9 @@ + +local m_random = math.random; +module "uuid" + +function uuid_generate() + return m_random(0, 99999999); +end + +return _M;
\ No newline at end of file |