Changeset

2175:3ca8755581a1 sasl

Initial commit of the SASL redesign.
author Tobias Markmann <tm@ayena.de>
date Mon, 10 Aug 2009 12:14:40 +0200
parents 1639:0914d128c55e
children 2176:aaf2b2df61f7
files util/sasl.lua
diffstat 1 files changed, 22 insertions(+), 227 deletions(-) [+]
line wrap: on
line diff
--- a/util/sasl.lua	Wed Aug 05 16:37:28 2009 +0100
+++ b/util/sasl.lua	Mon Aug 10 12:14:40 2009 +0200
@@ -27,244 +27,39 @@
 local type = type
 local error = error
 local print = print
+local setmetatable = setmetatable;
+local assert = assert;
 
 module "sasl"
 
--- Credentials handler:
---   Arguments: ("PLAIN", user, host, password)
---   Returns: true (success) | false (fail) | nil (user unknown)
-local function new_plain(realm, credentials_handler)
-	local object = { mechanism = "PLAIN", realm = realm, credentials_handler = credentials_handler}
-	function object.feed(self, message)
-		if message == "" or message == nil then return "failure", "malformed-request" end
-		local response = message
-		local authorization = s_match(response, "([^&%z]+)")
-		local authentication = s_match(response, "%z([^&%z]+)%z")
-		local password = s_match(response, "%z[^&%z]+%z([^&%z]+)")
+local method = {}
+local mechanisms = {};
+local backend_mechanism = {};
 
-    if authentication == nil or password == nil then return "failure", "malformed-request" end
-    self.username = authentication
-    local auth_success = self.credentials_handler("PLAIN", self.username, self.realm, password)
-
-    if auth_success then
-      return "success"
-    elseif auth_success == nil then
-      return "failure", "account-disabled"
-    else
-      return "failure", "not-authorized"
-    end
-  end
-  return object
+-- register a new SASL mechanims
+local function registerMechanism(name, backends, f)
+	assert(type(name) == "string", "Parameter name MUST be a string.");
+	assert(type(backends) == "string" or type(backends) == "table", "Parameter backends MUST be either a string or a table.");
+	assert(type(f) == "function", "Parameter f MUST be a function.");
+	mechanism[name] = f
+	for _, backend_name in ipairs(backend)
 end
 
--- credentials_handler:
---   Arguments: (mechanism, node, domain, realm, decoder)
---   Returns: Password encoding, (plaintext) password
--- implementing RFC 2831
-local function new_digest_md5(realm, credentials_handler)
-	--TODO complete support for authzid
-
-	local function serialize(message)
-		local data = ""
-
-		if type(message) ~= "table" then error("serialize needs an argument of type table.") end
-
-		-- testing all possible values
-		if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end
-		if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end
-		if message["charset"] then data = data..[[charset=]]..message.charset.."," end
-		if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end
-		if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
-		if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end
-		data = data:gsub(",$", "")
-		return data
-	end
-
-	local function utf8tolatin1ifpossible(passwd)
-		local i = 1;
-		while i <= #passwd do
-			local passwd_i = to_byte(passwd:sub(i, i));
-			if passwd_i > 0x7F then
-				if passwd_i < 0xC0 or passwd_i > 0xC3 then
-					return passwd;
-				end
-				i = i + 1;
-				passwd_i = to_byte(passwd:sub(i, i));
-				if passwd_i < 0x80 or passwd_i > 0xBF then
-					return passwd;
-				end
-			end
-			i = i + 1;
-		end
-
-		local p = {};
-		local j = 0;
-		i = 1;
-		while (i <= #passwd) do
-			local passwd_i = to_byte(passwd:sub(i, i));
-			if passwd_i > 0x7F then
-				i = i + 1;
-				local passwd_i_1 = to_byte(passwd:sub(i, i));
-				t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever
-			else
-				t_insert(p, to_char(passwd_i));
-			end
-			i = i + 1;
-		end
-		return t_concat(p);
-	end
-	local function latin1toutf8(str)
-		local p = {};
-		for ch in gmatch(str, ".") do
-			ch = to_byte(ch);
-			if (ch < 0x80) then
-				t_insert(p, to_char(ch));
-			elseif (ch < 0xC0) then
-				t_insert(p, to_char(0xC2, ch));
-			else
-				t_insert(p, to_char(0xC3, ch - 64));
-			end
-		end
-		return t_concat(p);
-	end
-	local function parse(data)
-		message = {}
-		for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder
-			message[k] = v;
-		end
-		return message;
-	end
-
-	local object = { mechanism = "DIGEST-MD5", realm = realm, credentials_handler = credentials_handler};
-
-	object.nonce = generate_uuid();
-	object.step = 0;
-	object.nonce_count = {};
-
-	function object.feed(self, message)
-		self.step = self.step + 1;
-		if (self.step == 1) then
-			local challenge = serialize({	nonce = object.nonce,
-											qop = "auth",
-											charset = "utf-8",
-											algorithm = "md5-sess",
-											realm = self.realm});
-			return "challenge", challenge;
-		elseif (self.step == 2) then
-			local response = parse(message);
-			-- check for replay attack
-			if response["nc"] then
-				if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end
-			end
-
-			-- check for username, it's REQUIRED by RFC 2831
-			if not response["username"] then
-				return "failure", "malformed-request";
-			end
-			self["username"] = response["username"];
-
-			-- check for nonce, ...
-			if not response["nonce"] then
-				return "failure", "malformed-request";
-			else
-				-- check if it's the right nonce
-				if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end
-			end
-
-			if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end
-			if not response["qop"] then response["qop"] = "auth" end
-
-			if response["realm"] == nil or response["realm"] == "" then
-				response["realm"] = "";
-			elseif response["realm"] ~= self.realm then
-				return "failure", "not-authorized", "Incorrect realm value";
-			end
-
-			local decoder;
-			if response["charset"] == nil then
-				decoder = utf8tolatin1ifpossible;
-			elseif response["charset"] ~= "utf-8" then
-				return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8.";
-			end
-
-			local domain = "";
-			local protocol = "";
-			if response["digest-uri"] then
-				protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$");
-				if protocol == nil or domain == nil then return "failure", "malformed-request" end
-			else
-				return "failure", "malformed-request", "Missing entry for digest-uri in SASL message."
-			end
-
-			--TODO maybe realm support
-			self.username = response["username"];
-			local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], to_unicode(domain), response["realm"], decoder);
-			if Y == nil then return "failure", "not-authorized"
-			elseif Y == false then return "failure", "account-disabled" end
-			local A1 = "";
-			if response.authzid then
-				if response.authzid == self.username.."@"..self.realm then
-					-- COMPAT
-					log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920.");
-					A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
-				else
-					A1 = "?";
-				end
-			else
-				A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
-			end
-			local A2 = "AUTHENTICATE:"..protocol.."/"..domain;
-
-			local HA1 = md5(A1, true);
-			local HA2 = md5(A2, true);
-
-			local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2;
-			local response_value = md5(KD, true);
-
-			if response_value == response["response"] then
-				-- calculate rspauth
-				A2 = ":"..protocol.."/"..domain;
-
-				HA1 = md5(A1, true);
-				HA2 = md5(A2, true);
-
-				KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
-				local rspauth = md5(KD, true);
-				self.authenticated = true;
-				return "challenge", serialize({rspauth = rspauth});
-			else
-				return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
-			end
-		elseif self.step == 3 then
-			if self.authenticated ~= nil then return "success"
-			else return "failure", "malformed-request" end
-		end
-	end
-	return object;
+-- create a new SASL object which can be used to authenticate clients
+function new(realm, profile)
+	sasl_i = {};
+	
+	return setmetatable(sasl_i, method);
 end
 
--- Credentials handler: Can be nil. If specified, should take the mechanism as
--- the only argument, and return true for OK, or false for not-OK (TODO)
-local function new_anonymous(realm, credentials_handler)
-	local object = { mechanism = "ANONYMOUS", realm = realm, credentials_handler = credentials_handler}
-		function object.feed(self, message)
-			return "success"
-		end
-	object["username"] = generate_uuid()
-	return object
+-- get a list of possible SASL mechanims to use
+function method:mechanisms()
+
 end
 
+-- select a mechanism to use
+function method.select( mechanism )
 
-function new(mechanism, realm, credentials_handler)
-	local object
-	if mechanism == "PLAIN" then object = new_plain(realm, credentials_handler)
-	elseif mechanism == "DIGEST-MD5" then object = new_digest_md5(realm, credentials_handler)
-	elseif mechanism == "ANONYMOUS" then object = new_anonymous(realm, credentials_handler)
-	else
-		log("debug", "Unsupported SASL mechanism: "..tostring(mechanism));
-		return nil
-	end
-	return object
 end
 
 return _M;