Merge 0.9->trunk
[prosody.git] / plugins / mod_auth_internal_hashed.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 -- Copyright (C) 2010 Jeff Mitchell
5 --
6 -- This project is MIT/X11 licensed. Please see the
7 -- COPYING file in the source package for more information.
8 --
9
10 local datamanager = require "util.datamanager";
11 local log = require "util.logger".init("auth_internal_hashed");
12 local getAuthenticationDatabaseSHA1 = require "util.sasl.scram".getAuthenticationDatabaseSHA1;
13 local usermanager = require "core.usermanager";
14 local generate_uuid = require "util.uuid".generate;
15 local new_sasl = require "util.sasl".new;
16
17 local to_hex;
18 do
19         local function replace_byte_with_hex(byte)
20                 return ("%02x"):format(byte:byte());
21         end
22         function to_hex(binary_string)
23                 return binary_string:gsub(".", replace_byte_with_hex);
24         end
25 end
26
27 local from_hex;
28 do
29         local function replace_hex_with_byte(hex)
30                 return string.char(tonumber(hex, 16));
31         end
32         function from_hex(hex_string)
33                 return hex_string:gsub("..", replace_hex_with_byte);
34         end
35 end
36
37
38 -- Default; can be set per-user
39 local iteration_count = 4096;
40
41 local host = module.host;
42 -- define auth provider
43 local provider = {};
44 log("debug", "initializing internal_hashed authentication provider for host '%s'", host);
45
46 function provider.test_password(username, password)
47         local credentials = datamanager.load(username, host, "accounts") or {};
48
49         if credentials.password ~= nil and string.len(credentials.password) ~= 0 then
50                 if credentials.password ~= password then
51                         return nil, "Auth failed. Provided password is incorrect.";
52                 end
53
54                 if provider.set_password(username, credentials.password) == nil then
55                         return nil, "Auth failed. Could not set hashed password from plaintext.";
56                 else
57                         return true;
58                 end
59         end
60
61         if credentials.iteration_count == nil or credentials.salt == nil or string.len(credentials.salt) == 0 then
62                 return nil, "Auth failed. Stored salt and iteration count information is not complete.";
63         end
64         
65         local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, credentials.salt, credentials.iteration_count);
66         
67         local stored_key_hex = to_hex(stored_key);
68         local server_key_hex = to_hex(server_key);
69         
70         if valid and stored_key_hex == credentials.stored_key and server_key_hex == credentials.server_key then
71                 return true;
72         else
73                 return nil, "Auth failed. Invalid username, password, or password hash information.";
74         end
75 end
76
77 function provider.set_password(username, password)
78         local account = datamanager.load(username, host, "accounts");
79         if account then
80                 account.salt = account.salt or generate_uuid();
81                 account.iteration_count = account.iteration_count or iteration_count;
82                 local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, account.salt, account.iteration_count);
83                 local stored_key_hex = to_hex(stored_key);
84                 local server_key_hex = to_hex(server_key);
85                 
86                 account.stored_key = stored_key_hex
87                 account.server_key = server_key_hex
88
89                 account.password = nil;
90                 return datamanager.store(username, host, "accounts", account);
91         end
92         return nil, "Account not available.";
93 end
94
95 function provider.user_exists(username)
96         local account = datamanager.load(username, host, "accounts");
97         if not account then
98                 log("debug", "account not found for username '%s' at host '%s'", username, host);
99                 return nil, "Auth failed. Invalid username";
100         end
101         return true;
102 end
103
104 function provider.users()
105         return datamanager.users(host, "accounts");
106 end
107
108 function provider.create_user(username, password)
109         if password == nil then
110                 return datamanager.store(username, host, "accounts", {});
111         end
112         local salt = generate_uuid();
113         local valid, stored_key, server_key = getAuthenticationDatabaseSHA1(password, salt, iteration_count);
114         local stored_key_hex = to_hex(stored_key);
115         local server_key_hex = to_hex(server_key);
116         return datamanager.store(username, host, "accounts", {stored_key = stored_key_hex, server_key = server_key_hex, salt = salt, iteration_count = iteration_count});
117 end
118
119 function provider.delete_user(username)
120         return datamanager.store(username, host, "accounts", nil);
121 end
122
123 function provider.get_sasl_handler()
124         local testpass_authentication_profile = {
125                 plain_test = function(sasl, username, password, realm)
126                         return usermanager.test_password(username, realm, password), true;
127                 end,
128                 scram_sha_1 = function(sasl, username, realm)
129                         local credentials = datamanager.load(username, host, "accounts");
130                         if not credentials then return; end
131                         if credentials.password then
132                                 usermanager.set_password(username, credentials.password, host);
133                                 credentials = datamanager.load(username, host, "accounts");
134                                 if not credentials then return; end
135                         end
136                         
137                         local stored_key, server_key, iteration_count, salt = credentials.stored_key, credentials.server_key, credentials.iteration_count, credentials.salt;
138                         stored_key = stored_key and from_hex(stored_key);
139                         server_key = server_key and from_hex(server_key);
140                         return stored_key, server_key, iteration_count, salt, true;
141                 end
142         };
143         return new_sasl(host, testpass_authentication_profile);
144 end
145         
146 module:provides("auth", provider);
147