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