X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=util%2Fsasl.lua;h=a343d608727db0ab80c520b3068db906fafd0d6e;hb=a80f2d734aaf6cf54728a7d9e4a68335f91af8f9;hp=dd8c2002e0012c5cff0791b8f8f2a8e585659337;hpb=8aba1c77f0714f2460cc21660bb494fc217e042b;p=prosody.git diff --git a/util/sasl.lua b/util/sasl.lua index dd8c2002..a343d608 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -1,5 +1,5 @@ --- sasl.lua v0.2 --- Copyright (C) 2008 Tobias Markmann +-- sasl.lua v0.4 +-- Copyright (C) 2008-2009 Tobias Markmann -- -- All rights reserved. -- @@ -26,8 +26,6 @@ local math = require "math" local type = type local error = error local print = print -local idna_ascii = require "util.encodings".idna.to_ascii -local idna_unicode = require "util.encodings".idna.to_unicode module "sasl" @@ -43,7 +41,7 @@ local function new_plain(realm, password_handler) if authentication == nil or password == nil then return "failure", "malformed-request" end - local password_encoding, correct_password = self.password_handler(authentication, self.realm, "PLAIN") + local password_encoding, correct_password = self.password_handler(authentication, self.realm, self.realm, "PLAIN") if correct_password == nil then return "failure", "not-authorized" elseif correct_password == false then return "failure", "account-disabled" end @@ -62,8 +60,10 @@ local function new_plain(realm, password_handler) return object end + +-- implementing RFC 2831 local function new_digest_md5(realm, password_handler) - --TODO maybe support for authzid + --TODO complete support for authzid local function serialize(message) local data = "" @@ -131,29 +131,28 @@ local function new_digest_md5(realm, password_handler) local function parse(data) message = {} for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder - message[k] = v + message[k] = v; end - return message + return message; end - local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler} + local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler}; - --TODO: something better than math.random would be nice, maybe OpenSSL's random number generator - object.nonce = generate_uuid() - object.step = 0 - object.nonce_count = {} + object.nonce = generate_uuid(); + object.step = 0; + object.nonce_count = {}; function object.feed(self, message) - self.step = self.step + 1 + 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 + return "challenge", challenge; elseif (self.step == 2) then - local response = parse(message) + local response = parse(message); -- check for replay attack if response["nc"] then if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end @@ -161,13 +160,13 @@ local function new_digest_md5(realm, password_handler) -- check for username, it's REQUIRED by RFC 2831 if not response["username"] then - return "failure", "malformed-request" + return "failure", "malformed-request"; end - self["username"] = response["username"] + self["username"] = response["username"]; -- check for nonce, ... if not response["nonce"] then - return "failure", "malformed-request" + return "failure", "malformed-request"; else -- check if it's the right nonce if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end @@ -176,54 +175,64 @@ local function new_digest_md5(realm, password_handler) 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 then - response["realm"] = "" + 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; + 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." + 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 = "" + local domain = ""; + local protocol = ""; if response["digest-uri"] then - protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$") + 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.password_handler(response["username"], response["realm"], "DIGEST-MD5", decoder) + self.username = response["username"]; + local password_encoding, Y = self.password_handler(response["username"], domain, response["realm"], "DIGEST-MD5", decoder); if Y == nil then return "failure", "not-authorized" elseif Y == false then return "failure", "account-disabled" end - - local A1 = Y..":"..response["nonce"]..":"..response["cnonce"]--:authzid + 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 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) + 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) + 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}) + 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 @@ -232,13 +241,24 @@ local function new_digest_md5(realm, password_handler) else return "failure", "malformed-request" end end end + return object; +end + +local function new_anonymous(realm, password_handler) + local object = { mechanism = "ANONYMOUS", realm = realm, password_handler = password_handler} + function object.feed(self, message) + return "success" + end + object["username"] = generate_uuid() return object end + function new(mechanism, realm, password_handler) local object if mechanism == "PLAIN" then object = new_plain(realm, password_handler) elseif mechanism == "DIGEST-MD5" then object = new_digest_md5(realm, password_handler) + elseif mechanism == "ANONYMOUS" then object = new_anonymous(realm, password_handler) else log("debug", "Unsupported SASL mechanism: "..tostring(mechanism)); return nil