X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=util%2Fsasl.lua;h=a343d608727db0ab80c520b3068db906fafd0d6e;hb=a80f2d734aaf6cf54728a7d9e4a68335f91af8f9;hp=ab8b814b10268fbe1874fe20ef8f75a13c5d251c;hpb=b19869e1c1b5ba95d86dc25e363a8c1b9cee5d24;p=prosody.git diff --git a/util/sasl.lua b/util/sasl.lua index ab8b814b..a343d608 100644 --- a/util/sasl.lua +++ b/util/sasl.lua @@ -1,5 +1,5 @@ --- sasl.lua v0.1 --- 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 = "" @@ -81,6 +81,39 @@ local function new_digest_md5(realm, password_handler) 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 @@ -98,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 @@ -128,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 @@ -143,51 +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"] = "" end - local raw_realm = 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; if response["charset"] == nil then - response["username"] = latin1toutf8(response["username"]) - response["realm"] = latin1toutf8(response["realm"]) + 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", raw_realm) + 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 = ""; + 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 A1 = Y..":"..response["nonce"]..":"..response["cnonce"]--:authzid - local A2 = "AUTHENTICATE:"..protocol.."/"..idna_ascii(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.."/"..idna_ascii(domain) + 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 @@ -196,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