aboutsummaryrefslogtreecommitdiffstats
path: root/util/sasl.lua
diff options
context:
space:
mode:
Diffstat (limited to 'util/sasl.lua')
-rw-r--r--util/sasl.lua251
1 files changed, 23 insertions, 228 deletions
diff --git a/util/sasl.lua b/util/sasl.lua
index 48412ea7..b2dd4034 100644
--- a/util/sasl.lua
+++ b/util/sasl.lua
@@ -27,244 +27,39 @@ local math = require "math"
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]+)")
-
- 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
+local method = {}
+local mechanisms = {};
+local backend_mechanism = {};
+
+-- 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;