mod_pubsub, util.pubsub: Support for fetching items
[prosody.git] / util / sasl / digest-md5.lua
index 8acbce4b4ddd905c06f8bc5f37aeae6f2b8c6962..2837148ec4723270b92d11b30ac551299e230184 100644 (file)
@@ -1,5 +1,5 @@
 -- sasl.lua v0.4
--- Copyright (C) 2008-2009 Tobias Markmann
+-- Copyright (C) 2008-2010 Tobias Markmann
 --
 --    All rights reserved.
 --
 --
 --    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+local tostring = tostring;
+local type = type;
+
+local s_gmatch = string.gmatch;
+local s_match = string.match;
+local t_concat = table.concat;
+local t_insert = table.insert;
+local to_byte, to_char = string.byte, string.char;
+
+local md5 = require "util.hashes".md5;
+local log = require "util.logger".init("sasl");
+local generate_uuid = require "util.uuid".generate;
+
+module "digest-md5"
 
 --=========================
 --SASL DIGEST-MD5 according to RFC 2831
-local function sasl_mechanism_digest_md5(self, message)
+
+--[[
+Supported Authentication Backends
+
+digest_md5:
+       function(username, domain, realm, encoding) -- domain and realm are usually the same; for some broken
+                                                                                               -- implementations it's not
+               return digesthash, state;
+       end
+
+digest_md5_test:
+       function(username, domain, realm, encoding, digesthash)
+               return true or false, state;
+       end
+]]
+
+local function digest(self, message)
        --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["realm"] then data = data..[[realm="]]..message.realm..[[",]] end
                if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end
@@ -68,7 +96,7 @@ local function sasl_mechanism_digest_md5(self, message)
        end
        local function latin1toutf8(str)
                local p = {};
-               for ch in gmatch(str, ".") do
+               for ch in s_gmatch(str, ".") do
                        ch = to_byte(ch);
                        if (ch < 0x80) then
                                t_insert(p, to_char(ch));
@@ -82,7 +110,8 @@ local function sasl_mechanism_digest_md5(self, message)
        end
        local function parse(data)
                local message = {}
-               for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder
+               -- COMPAT: %z in the pattern to work around jwchat bug (sends "charset=utf-8\0")
+               for k, v in s_gmatch(data, [[([%w%-]+)="?([^",%z]*)"?,?]]) do -- FIXME The hacky regex makes me shudder
                        message[k] = v;
                end
                return message;
@@ -96,7 +125,7 @@ local function sasl_mechanism_digest_md5(self, message)
 
        self.step = self.step + 1;
        if (self.step == 1) then
-               local challenge = serialize({   nonce = object.nonce,
+               local challenge = serialize({   nonce = self.nonce,
                                                                                qop = "auth",
                                                                                charset = "utf-8",
                                                                                algorithm = "md5-sess",
@@ -150,17 +179,30 @@ local function sasl_mechanism_digest_md5(self, message)
 
                --TODO maybe realm support
                self.username = response["username"];
-               local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder);
-               if Y == nil then return "failure", "not-authorized"
-               elseif Y == false then return "failure", "account-disabled" end
+               local Y, state;
+               if self.profile.plain then
+                       local password, state = self.profile.plain(response["username"], self.realm)
+                       if state == nil then return "failure", "not-authorized"
+                       elseif state == false then return "failure", "account-disabled" end
+                       Y = md5(response["username"]..":"..response["realm"]..":"..password);
+               elseif self.profile["digest-md5"] then
+                       Y, state = self.profile["digest-md5"](response["username"], self.realm, response["realm"], response["charset"])
+                       if state == nil then return "failure", "not-authorized"
+                       elseif state == false then return "failure", "account-disabled" end
+               elseif self.profile["digest-md5-test"] then
+                       -- TODO
+               end
+               --local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, 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
+                       if response.authzid == self.username or response.authzid == self.username.."@"..self.realm then
                                -- COMPAT
-                               log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920.");
+                               log("warn", "Client is violating RFC 3920 (section 6.1, point 7).");
                                A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid;
                        else
-                               A1 = "?";
+                               return "failure", "invalid-authzid";
                        end
                else
                        A1 = Y..":"..response["nonce"]..":"..response["cnonce"];
@@ -183,6 +225,7 @@ local function sasl_mechanism_digest_md5(self, message)
                        KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2
                        local rspauth = md5(KD, true);
                        self.authenticated = true;
+                       --TODO: considering sending the rspauth in a success node for saving one roundtrip; allowed according to http://tools.ietf.org/html/draft-saintandre-rfc3920bis-09#section-7.3.6
                        return "challenge", serialize({rspauth = rspauth});
                else
                        return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated."
@@ -191,4 +234,10 @@ local function sasl_mechanism_digest_md5(self, message)
                if self.authenticated ~= nil then return "success"
                else return "failure", "malformed-request" end
        end
-end
\ No newline at end of file
+end
+
+function init(registerMechanism)
+       registerMechanism("DIGEST-MD5", {"plain"}, digest);
+end
+
+return _M;
\ No newline at end of file