Almost a SASL framework, supports negotiation and challenge-response, mechanism code split out into util/sasl/

Thu, 18 Sep 2014 18:59:34 +0200

author
Kim Alvefur <zash@zash.se>
date
Thu, 18 Sep 2014 18:59:34 +0200
changeset 354
58cd27b74ba5
parent 353
8cd05c3d0f1f
child 355
dfe095fcf89c

Almost a SASL framework, supports negotiation and challenge-response, mechanism code split out into util/sasl/

plugins/sasl.lua file | annotate | diff | comparison | revisions
util/sasl/anonymous.lua file | annotate | diff | comparison | revisions
util/sasl/plain.lua file | annotate | diff | comparison | revisions
--- 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
 	
--- /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
+
--- /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
+

mercurial