# HG changeset patch # User Kim Alvefur # Date 1411059574 -7200 # Node ID 58cd27b74ba5a91f736cb7c3ea87850d9004e2b6 # Parent 8cd05c3d0f1fcb11b3622237cba4da7b3d75b520 Almost a SASL framework, supports negotiation and challenge-response, mechanism code split out into util/sasl/ diff -r 8cd05c3d0f1f -r 58cd27b74ba5 plugins/sasl.lua --- a/plugins/sasl.lua Mon Sep 08 12:10:21 2014 +0200 +++ b/plugins/sasl.lua Thu Sep 18 18:59:34 2014 +0200 @@ -1,37 +1,74 @@ -local base64 = require "mime".b64; +-- local verse = require"verse"; +local base64, unbase64 = require "mime".b64, require"mime".unb64; local xmlns_sasl = "urn:ietf:params:xml:ns:xmpp-sasl"; function verse.plugins.sasl(stream) local function handle_features(features_stanza) if stream.authenticated then return; end stream:debug("Authenticating with SASL..."); - --stream.sasl_state, initial_data = sasl_new({"PLAIN"}, stream.username, stream.password, stream.jid); - local mechanism , initial_data - if stream.username then - mechanism = "PLAIN" - initial_data = base64("\0"..stream.username.."\0"..stream.password); - else - mechanism = "ANONYMOUS" + local sasl_mechanisms = features_stanza:get_child("mechanisms", xmlns_sasl); + if not sasl_mechanisms then return end + + local mechanisms = {}; + local preference = {}; + + for mech in sasl_mechanisms:childtags("mechanism") do + mech = mech:get_text(); + stream:debug("Server offers %s", mech); + if not mechanisms[mech] then + local name = mech:match("[^-]+"); + local ok, impl = pcall(require, "util.sasl."..name:lower()); + if ok then + stream:debug("Loaded SASL %s module", name); + impl(stream, mechanisms, preference); + elseif not tostring(impl):match("not found") then + stream:debug("Loading failed: %s", tostring(impl)); + end + end end - stream:debug("Selecting %s mechanism...",mechanism); + + local supported = {}; -- by the server + for mech in pairs(mechanisms) do + table.insert(supported, mech); + end + if not supported[1] then + stream:event("authentication-failure", { condition = "no-supported-sasl-mechanisms" }); + stream:close(); + return; + end + table.sort(supported, function (a, b) return preference[a] > preference[b]; end); + local mechanism, initial_data = supported[1]; + stream:debug("Selecting %s mechanism...", mechanism); + stream.sasl_mechanism = coroutine.wrap(mechanisms[mechanism]); + initial_data = stream:sasl_mechanism(mechanism); local auth_stanza = verse.stanza("auth", { xmlns = xmlns_sasl, mechanism = mechanism }); if initial_data then - auth_stanza:text(initial_data); + auth_stanza:text(base64(initial_data)); end stream:send(auth_stanza); return true; end local function handle_sasl(sasl_stanza) - if sasl_stanza.name == "success" then - stream.authenticated = true; - stream:event("authentication-success"); - elseif sasl_stanza.name == "failure" then + if sasl_stanza.name == "failure" then local err = sasl_stanza.tags[1]; local text = sasl_stanza:get_child_text("text"); stream:event("authentication-failure", { condition = err.name, text = text }); + stream:close(); + return false; end - stream:reopen(); + local ok, err = stream.sasl_mechanism(sasl_stanza.name, unbase64(sasl_stanza:get_text())); + if not ok then + stream:event("authentication-failure", { condition = err }); + stream:close(); + return false; + elseif ok == true then + stream:event("authentication-success"); + stream.authenticated = true + stream:reopen(); + else + stream:send(verse.stanza("response", { xmlns = xmlns_sasl }):text(base64(ok))); + end return true; end diff -r 8cd05c3d0f1f -r 58cd27b74ba5 util/sasl/anonymous.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/util/sasl/anonymous.lua Thu Sep 18 18:59:34 2014 +0200 @@ -0,0 +1,8 @@ + +return function (stream, mechanisms, preference) + mechanisms["ANONYMOUS"] = function () + return coroutine.yield() == "success"; + end; + preference["ANONYMOUS"] = 0; +end + diff -r 8cd05c3d0f1f -r 58cd27b74ba5 util/sasl/plain.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/util/sasl/plain.lua Thu Sep 18 18:59:34 2014 +0200 @@ -0,0 +1,10 @@ + +return function (stream, mechanisms, preference) + if stream.username and stream.password then + mechanisms["PLAIN"] = function (stream) + return "success" == coroutine.yield("\0"..stream.username.."\0"..stream.password); + end; + preference["PLAIN"] = 5; + end +end +