# HG changeset patch # User Matthew Wild # Date 1222906138 -3600 # Node ID 3fdfd6e0cb4e23b916062499a25271d3ce6c6715 # Parent 06eadafafefa31b5273080202b7633d5e9fb720e SASL! (but before you get too excited, no resource binding yet. And yes, there are still plenty of rough edges to the code...) ((eg. must move out of xmlhandlers.lua o_O )) diff -r 06eadafafefa -r 3fdfd6e0cb4e core/modulemanager.lua --- a/core/modulemanager.lua Thu Oct 02 00:00:35 2008 +0100 +++ b/core/modulemanager.lua Thu Oct 02 01:08:58 2008 +0100 @@ -23,19 +23,25 @@ if not handlers[origin_type].iq[xmlns] then handlers[origin_type].iq[xmlns]= handler; handler_info[handler] = getfenv(2).module; - log("debug", "mod_%s now handles iq,%s", getfenv(2).module.name, xmlns); + log("debug", "mod_%s now handles tag 'iq' with query namespace '%s'", getfenv(2).module.name, xmlns); else - log("warning", "mod_%s wants to handle iq,%s but mod_%s already handles that", getfenv(2).module.name, xmlns, handler_info[handlers[origin_type].iq[xmlns]].module.name); + log("warning", "mod_%s wants to handle tag 'iq' with query namespace '%s' but mod_%s already handles that", getfenv(2).module.name, xmlns, handler_info[handlers[origin_type].iq[xmlns]].module.name); end end -function modulehelpers.add_presence_handler(origin_type, handler) -end - -function modulehelpers.add_message_handler(origin_type, handler) +function modulehelpers.add_handler(origin_type, tag, handler) + handlers[origin_type] = handlers[origin_type] or {}; + if not handlers[origin_type][tag] then + handlers[origin_type][tag]= 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"); load("roster"); end @@ -58,9 +64,9 @@ end function handle_stanza(origin, stanza) - local name, origin_type = stanza.name, origin.type; + local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns, origin.type; - if name == "iq" then + if name == "iq" and xmlns == "jabber:client" and handlers[origin_type] then log("debug", "Stanza is an "); local child = stanza.tags[1]; if child then @@ -73,6 +79,13 @@ 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; + end end log("debug", "Stanza unhandled by any modules"); return false; -- we didn't handle it diff -r 06eadafafefa -r 3fdfd6e0cb4e core/sessionmanager.lua --- a/core/sessionmanager.lua Thu Oct 02 00:00:35 2008 +0100 +++ b/core/sessionmanager.lua Thu Oct 02 01:08:58 2008 +0100 @@ -1,6 +1,10 @@ local tostring = tostring; +local print = print; + +local hosts = hosts; + local log = require "util.logger".init("sessionmanager"); module "sessionmanager" @@ -12,9 +16,42 @@ return session; end +function destroy_session(session) +end + function send_to_session(session, data) - log("debug", "Sending...", tostring(data)); + log("debug", "Sending: %s", tostring(data)); session.conn.write(tostring(data)); end +function make_authenticated(session, username) + session.username = username; + session.resource = resource; + if session.type == "c2s_unauthed" then + session.type = "c2s"; + end +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 :) + --FIXME: Randomly-generated resources must be unique per-user, and never conflict with existing + + if not hosts[session.host].sessions[session.username] then + hosts[session.host].sessions[session.username] = { sessions = {} }; + else + if hosts[session.host].sessions[session.username].sessions[resource] then + -- Resource conflict + return false, "conflict"; + end + end + + session.resource = resource; + session.full_jid = session.username .. '@' .. session.host .. '/' .. resource; + hosts[session.host].sessions[session.username].sessions[resource] = session; + + return true; +end + return _M; \ No newline at end of file diff -r 06eadafafefa -r 3fdfd6e0cb4e core/usermanager.lua --- a/core/usermanager.lua Thu Oct 02 00:00:35 2008 +0100 +++ b/core/usermanager.lua Thu Oct 02 01:08:58 2008 +0100 @@ -9,3 +9,5 @@ if password == credentials.password then return true; end return false; end + +return _M; \ No newline at end of file diff -r 06eadafafefa -r 3fdfd6e0cb4e core/xmlhandlers.lua --- a/core/xmlhandlers.lua Thu Oct 02 00:00:35 2008 +0100 +++ b/core/xmlhandlers.lua Thu Oct 02 01:08:58 2008 +0100 @@ -27,6 +27,7 @@ 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)); @@ -41,21 +42,28 @@ session.streamid = m_random(1000000, 99999999); print(session, session.host, "Client opened stream"); send(""); - send(format("", session.streamid, session.host)); - --send(""); - --send("PLAIN"); + send(format("", session.streamid, session.host)); + send(""); + if not session.username then + send(""); + send("PLAIN"); + send(""); + else + send(""); + end --send [[ ]] - --send(""); + send(""); log("info", "core", "Stream opened successfully"); session.notopen = nil; return; end error("Client failed to open stream successfully"); end - if name ~= "iq" and name ~= "presence" and name ~= "message" then + if curr_ns == "jabber:client" and name ~= "iq" and name ~= "presence" and name ~= "message" then error("Client sent invalid top-level stanza"); end - stanza = st.stanza(name, { to = attr.to, type = attr.type, id = attr.id, xmlns = curr_ns }); + attr.xmlns = curr_ns; + stanza = st.stanza(name, attr); --{ to = attr.to, type = attr.type, id = attr.id, xmlns = curr_ns }); curr_tag = stanza; else attr.xmlns = curr_ns; diff -r 06eadafafefa -r 3fdfd6e0cb4e main.lua --- a/main.lua Thu Oct 02 00:00:35 2008 +0100 +++ b/main.lua Thu Oct 02 01:08:58 2008 +0100 @@ -21,6 +21,7 @@ require "core.usermanager" require "core.sessionmanager" require "core.stanza_router" +require "net.connhandlers" require "util.stanza" require "util.jid" @@ -31,7 +32,6 @@ local m_random = math.random; local format = string.format; local st = stanza; -local init_xmlhandlers = xmlhandlers.init_xmlhandlers; ------------------------------ @@ -63,8 +63,8 @@ print("Client connected"); session.stanza_dispatch = function (stanza) return core_process_stanza(session, stanza); end - session.xml_handlers = init_xmlhandlers(session); - session.parser = lxp.new(session.xml_handlers, ":"); + + session.connhandler = connhandlers.new("xmpp-client", session); function session.disconnect(err) if session.last_presence and session.last_presence.attr.type ~= "unavailable" then @@ -82,7 +82,7 @@ end end if data then - session.parser:parse(data); + session.connhandler:data(data); end --log("info", "core", "Client disconnected, connection closed"); diff -r 06eadafafefa -r 3fdfd6e0cb4e net/connhandlers.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/net/connhandlers.lua Thu Oct 02 01:08:58 2008 +0100 @@ -0,0 +1,16 @@ + +local lxp = require "lxp" +local init_xmlhandlers = require "core.xmlhandlers" + +module "connhandlers" + + +function new(name, session) + if name == "xmpp-client" then + local parser = lxp.new(init_xmlhandlers(session), ":"); + local parse = parser.parse; + return { data = function (self, data) return parse(parser, data); end, parser = parser } + end +end + +return _M; \ No newline at end of file diff -r 06eadafafefa -r 3fdfd6e0cb4e plugins/mod_legacyauth.lua --- a/plugins/mod_legacyauth.lua Thu Oct 02 00:00:35 2008 +0100 +++ b/plugins/mod_legacyauth.lua Thu Oct 02 01:08:58 2008 +0100 @@ -21,16 +21,27 @@ require "core.usermanager" if usermanager.validate_credentials(session.host, username, password) then -- Authentication successful! - session.username = username; - session.resource = resource; - session.full_jid = username.."@"..session.host.."/"..session.resource; - if session.type == "c2s_unauthed" then - session.type = "c2s"; + local success, err = sessionmanager.make_authenticated(session, username); + if success then + success, err = sessionmanager.bind_resource(session, resource); + --FIXME: Reply with error + if not success then + local reply = st.reply(stanza); + reply.attr.type = "error"; + if err == "conflict" then + reply:tag("error", { code = "409", type = "cancel" }) + :tag("conflict", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" }); + elseif err == "constraint" then + reply:tag("error", { code = "409", type = "cancel" }) + :tag("already-bound", { xmlns = "x-lxmppd:extensions:legacyauth" }); + elseif err == "auth" then + reply:tag("error", { code = "401", type = "auth" }) + :tag("not-authorized", { xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas" }); + end + dispatch_stanza(reply); + return true; + end end - if not hosts[session.host].sessions[username] then - hosts[session.host].sessions[username] = { sessions = {} }; - end - hosts[session.host].sessions[username].sessions[resource] = session; send(session, st.reply(stanza)); return true; else diff -r 06eadafafefa -r 3fdfd6e0cb4e plugins/mod_saslauth.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/mod_saslauth.lua Thu Oct 02 01:08:58 2008 +0100 @@ -0,0 +1,53 @@ + +local st = require "util.stanza"; +local send = require "core.sessionmanager".send_to_session; + +local usermanager_validate_credentials = require "core.usermanager".validate_credentials; +local t_concat = table.concat; +local tostring = tostring; + +local log = require "util.logger".init("mod_saslauth"); + +local xmlns_sasl ='urn:ietf:params:xml:ns:xmpp-sasl'; + +local new_connhandler = require "net.connhandlers".new; +local new_sasl = require "util.sasl".new; + +add_handler("c2s_unauthed", "auth", + function (session, stanza) + if not session.sasl_handler then + session.sasl_handler = new_sasl(stanza.attr.mechanism, + function (username, password) + -- onAuth + require "core.usermanager" + if usermanager_validate_credentials(session.host, username, password) then + return true; + end + return false; + end, + function (username) + -- onSuccess + local success, err = sessionmanager.make_authenticated(session, username); + if not success then + sessionmanager.destroy_session(session); + end + session.sasl_handler = nil; + session.connhandler = new_connhandler("xmpp-client", session); + session.notopen = true; + end, + function (reason) + -- onFail + log("debug", "SASL failure, reason: %s", reason); + end, + function (stanza) + -- onWrite + log("debug", "SASL writes: %s", tostring(stanza)); + send(session, stanza); + end + ); + session.sasl_handler:feed(stanza); + else + error("Client tried to negotiate SASL again", 0); + end + + end); \ No newline at end of file diff -r 06eadafafefa -r 3fdfd6e0cb4e util/sasl.lua --- a/util/sasl.lua Thu Oct 02 00:00:35 2008 +0100 +++ b/util/sasl.lua Thu Oct 02 01:08:58 2008 +0100 @@ -1,34 +1,43 @@ -require "base64" -sasl = {} -function sasl:new_plain(onAuth, onSuccess, onFail, onWrite) +local base64 = require "base64" +local log = require "util.logger".init("sasl"); +local tostring = tostring; +local st = require "util.stanza"; +local s_match = string.match; +module "sasl" + + +local function new_plain(onAuth, onSuccess, onFail, onWrite) local object = { mechanism = "PLAIN", onAuth = onAuth, onSuccess = onSuccess, onFail = onFail, onWrite = onWrite} - local challenge = base64.encode(""); - onWrite(stanza.stanza("challenge", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):text(challenge)) + --local challenge = base64.encode(""); + --onWrite(st.stanza("challenge", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):text(challenge)) object.feed = function(self, stanza) - if (stanza.name ~= "response") then self.onFail() end - if (stanza.attr.xmlns ~= "urn:ietf:params:xml:ns:xmpp-sasl") then self.onFail() end - local response = base64.decode(stanza.tag[1]) - local authorization = string.match(response, "([^&\0]+)") - local authentication = string.match(response, "\0([^&\0]+)\0") - local password = string.match(response, "\0[^&\0]+\0([^&\0]+)") + if stanza.name ~= "response" and stanza.name ~= "auth" then self.onFail("invalid-stanza-tag") end + if stanza.attr.xmlns ~= "urn:ietf:params:xml:ns:xmpp-sasl" then self.onFail("invalid-stanza-namespace") end + local response = base64.decode(stanza[1]) + 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 - self.onWrite(stanza.stanza("success", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"})) - self.onSuccess() + self.onWrite(st.stanza("success", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"})) + self.onSuccess(authentication) else - self.onWrite(stanza.stanza("failure", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):tag("temporary-auth-failure")); + self.onWrite(st.stanza("failure", {xmlns = "urn:ietf:params:xml:ns:xmpp-sasl"}):tag("temporary-auth-failure")); end end return object end -function sasl:new(mechanism, onAuth, onSuccess, onFail, onWrite) + +function new(mechanism, onAuth, onSuccess, onFail, onWrite) local object if mechanism == "PLAIN" then object = new_plain(onAuth, onSuccess, onFail, onWrite) - else onFail() + else + log("debug", "Unsupported SASL mechanism: "..tostring(mechanism)); + onFail("unsupported-mechanism") end return object end -module "sasl" +return _M; \ No newline at end of file