1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : ejabberd_auth_internal.erl |
3 |
|
%%% Author : Alexey Shchepin <alexey@process-one.net> |
4 |
|
%%% Purpose : Authentification via mnesia |
5 |
|
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net> |
6 |
|
%%% |
7 |
|
%%% |
8 |
|
%%% ejabberd, Copyright (C) 2002-2011 ProcessOne |
9 |
|
%%% |
10 |
|
%%% This program is free software; you can redistribute it and/or |
11 |
|
%%% modify it under the terms of the GNU General Public License as |
12 |
|
%%% published by the Free Software Foundation; either version 2 of the |
13 |
|
%%% License, or (at your option) any later version. |
14 |
|
%%% |
15 |
|
%%% This program is distributed in the hope that it will be useful, |
16 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 |
|
%%% General Public License for more details. |
19 |
|
%%% |
20 |
|
%%% You should have received a copy of the GNU General Public License |
21 |
|
%%% along with this program; if not, write to the Free Software |
22 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 |
|
%%% |
24 |
|
%%%---------------------------------------------------------------------- |
25 |
|
|
26 |
|
-module(ejabberd_auth_internal). |
27 |
|
-author('alexey@process-one.net'). |
28 |
|
|
29 |
|
%% External exports |
30 |
|
-behaviour(mongoose_gen_auth). |
31 |
|
|
32 |
|
-export([start/1, |
33 |
|
stop/1, |
34 |
|
set_password/4, |
35 |
|
authorize/1, |
36 |
|
try_register/4, |
37 |
|
get_registered_users/3, |
38 |
|
get_registered_users_number/3, |
39 |
|
get_password/3, |
40 |
|
get_password_s/3, |
41 |
|
does_user_exist/3, |
42 |
|
remove_user/3, |
43 |
|
remove_domain/2, |
44 |
|
supports_sasl_module/2, |
45 |
|
supported_features/0 |
46 |
|
]). |
47 |
|
|
48 |
|
-export([scram_passwords/0]). |
49 |
|
|
50 |
|
%% Internal |
51 |
|
-export([check_password/4, |
52 |
|
check_password/6]). |
53 |
|
|
54 |
|
%% Utilities |
55 |
|
-export([dirty_get_registered_users/0]). |
56 |
|
|
57 |
|
-ignore_xref([dirty_get_registered_users/0, scram_passwords/0]). |
58 |
|
|
59 |
|
-include("mongoose.hrl"). |
60 |
|
-include("scram.hrl"). |
61 |
|
|
62 |
|
-record(passwd, {us, password}). |
63 |
|
|
64 |
|
-type passwd() :: #passwd{ |
65 |
|
us :: jid:simple_bare_jid(), |
66 |
|
password :: binary() | #scram{} | mongoose_scram:scram_map() |
67 |
|
}. |
68 |
|
|
69 |
|
-record(reg_users_counter, {vhost, count}). |
70 |
|
|
71 |
|
-type users_counter() :: #reg_users_counter { |
72 |
|
vhost :: binary(), |
73 |
|
count :: integer() |
74 |
|
}. |
75 |
|
|
76 |
|
%%%---------------------------------------------------------------------- |
77 |
|
%%% API |
78 |
|
%%%---------------------------------------------------------------------- |
79 |
|
|
80 |
|
-spec start(HostType :: mongooseim:host_type()) -> ok. |
81 |
|
start(HostType) -> |
82 |
:-( |
mongoose_mnesia:create_table(passwd, |
83 |
|
[{disc_copies, [node()]}, |
84 |
|
{attributes, record_info(fields, passwd)}, |
85 |
|
{storage_properties, [{ets, [{read_concurrency, true}]}]}]), |
86 |
:-( |
mongoose_mnesia:create_table(reg_users_counter, |
87 |
|
[{ram_copies, [node()]}, |
88 |
|
{attributes, record_info(fields, reg_users_counter)}]), |
89 |
:-( |
update_reg_users_counter_table(HostType), |
90 |
:-( |
ok. |
91 |
|
|
92 |
|
-spec stop(HostType :: mongooseim:host_type()) -> ok. |
93 |
|
stop(_HostType) -> |
94 |
:-( |
ok. |
95 |
|
|
96 |
|
-spec update_reg_users_counter_table(Server :: jid:server()) -> any(). |
97 |
|
update_reg_users_counter_table(Server) -> |
98 |
:-( |
Set = get_users(Server), |
99 |
:-( |
Size = length(Set), |
100 |
:-( |
LServer = jid:nameprep(Server), |
101 |
:-( |
F = fun() -> |
102 |
:-( |
write_counter(#reg_users_counter{vhost = LServer, count = Size}) |
103 |
|
end, |
104 |
:-( |
mnesia:sync_dirty(F). |
105 |
|
|
106 |
|
-spec supports_sasl_module(mongooseim:host_type(), cyrsasl:sasl_module()) -> boolean(). |
107 |
:-( |
supports_sasl_module(_HostType, cyrsasl_plain) -> true; |
108 |
:-( |
supports_sasl_module(HostType, cyrsasl_digest) -> not mongoose_scram:enabled(HostType); |
109 |
:-( |
supports_sasl_module(HostType, Mechanism) -> mongoose_scram:enabled(HostType, Mechanism). |
110 |
|
|
111 |
|
-spec authorize(mongoose_credentials:t()) -> {ok, mongoose_credentials:t()} |
112 |
|
| {error, any()}. |
113 |
|
authorize(Creds) -> |
114 |
:-( |
ejabberd_auth:authorize_with_check_password(?MODULE, Creds). |
115 |
|
|
116 |
|
-spec check_password(HostType :: mongooseim:host_type(), |
117 |
|
LUser :: jid:luser(), |
118 |
|
LServer :: jid:lserver(), |
119 |
|
Password :: binary()) -> boolean(). |
120 |
|
check_password(_HostType, LUser, LServer, Password) -> |
121 |
:-( |
US = {LUser, LServer}, |
122 |
:-( |
case catch dirty_read_passwd(US) of |
123 |
|
[#passwd{password = Scram}] when is_map(Scram) orelse is_record(Scram, scram) -> |
124 |
:-( |
mongoose_scram:check_password(Password, Scram); |
125 |
|
[#passwd{password = Password}] -> |
126 |
:-( |
Password /= <<>>; |
127 |
|
_ -> |
128 |
:-( |
false |
129 |
|
end. |
130 |
|
|
131 |
|
|
132 |
|
-spec check_password(HostType :: mongooseim:host_type(), |
133 |
|
LUser :: jid:luser(), |
134 |
|
LServer :: jid:lserver(), |
135 |
|
Password :: binary(), |
136 |
|
Digest :: binary(), |
137 |
|
DigestGen :: fun()) -> boolean(). |
138 |
|
check_password(_HostType, LUser, LServer, Password, Digest, DigestGen) -> |
139 |
:-( |
US = {LUser, LServer}, |
140 |
:-( |
case catch dirty_read_passwd(US) of |
141 |
|
[#passwd{password = Scram}] when is_record(Scram, scram) orelse is_map(Scram) -> |
142 |
:-( |
mongoose_scram:check_digest(Scram, Digest, DigestGen, Password); |
143 |
|
[#passwd{password = Passwd}] -> |
144 |
:-( |
ejabberd_auth:check_digest(Digest, DigestGen, Password, Passwd); |
145 |
|
_ -> |
146 |
:-( |
false |
147 |
|
end. |
148 |
|
|
149 |
|
|
150 |
|
-spec set_password(HostType :: mongooseim:host_type(), |
151 |
|
LUser :: jid:luser(), |
152 |
|
LServer :: jid:lserver(), |
153 |
|
Password :: binary()) -> ok | {error, not_allowed | invalid_jid}. |
154 |
|
set_password(HostType, LUser, LServer, Password) -> |
155 |
:-( |
US = {LUser, LServer}, |
156 |
:-( |
F = fun() -> |
157 |
:-( |
Password2 = get_scram(HostType, Password), |
158 |
:-( |
write_passwd(#passwd{us = US, password = Password2}) |
159 |
|
end, |
160 |
:-( |
{atomic, ok} = mnesia:transaction(F), |
161 |
:-( |
ok. |
162 |
|
|
163 |
|
-spec try_register(HostType :: mongooseim:host_type(), |
164 |
|
LUser :: jid:luser(), |
165 |
|
LServer :: jid:lserver(), |
166 |
|
Password :: binary() |
167 |
|
) -> ok | {error, exists | not_allowed}. |
168 |
|
try_register(HostType, LUser, LServer, Password) -> |
169 |
:-( |
US = {LUser, LServer}, |
170 |
:-( |
F = fun() -> |
171 |
:-( |
case read_passwd(US) of |
172 |
|
[] -> |
173 |
:-( |
Password2 = get_scram(HostType, Password), |
174 |
:-( |
write_passwd(#passwd{us = US, password = Password2}), |
175 |
:-( |
mnesia:dirty_update_counter(reg_users_counter, LServer, 1), |
176 |
:-( |
ok; |
177 |
|
[_E] -> |
178 |
:-( |
exists |
179 |
|
end |
180 |
|
end, |
181 |
:-( |
case mnesia:transaction(F) of |
182 |
|
{atomic, ok} -> |
183 |
:-( |
ok; |
184 |
|
{atomic, exists} -> |
185 |
:-( |
{error, exists}; |
186 |
|
Result -> |
187 |
:-( |
?LOG_ERROR(#{what => registration_error, |
188 |
:-( |
user => LUser, server => LServer, reason => Result}), |
189 |
:-( |
{error, not_allowed} |
190 |
|
end. |
191 |
|
|
192 |
|
|
193 |
|
%% @doc Get all registered users in Mnesia |
194 |
|
-spec dirty_get_registered_users() -> [jid:simple_bare_jid()]. |
195 |
|
dirty_get_registered_users() -> |
196 |
:-( |
mnesia:dirty_all_keys(passwd). |
197 |
|
|
198 |
|
-spec get_users(LServer :: jid:lserver()) -> [jid:simple_bare_jid()]. |
199 |
|
get_users(LServer) -> |
200 |
:-( |
mnesia:dirty_select( |
201 |
|
passwd, |
202 |
|
[{#passwd{us = '$1', _ = '_'}, |
203 |
|
[{'==', {element, 2, '$1'}, LServer}], |
204 |
|
['$1']}]). |
205 |
|
|
206 |
|
get_registered_users(_, LServer, Opts) -> |
207 |
:-( |
get_users(LServer, Opts). |
208 |
|
|
209 |
|
-type query_keyword() :: from | to | limit | offset | prefix. |
210 |
|
-type query_value() :: integer() | binary(). |
211 |
|
-spec get_users(LServer :: jid:lserver(), |
212 |
|
Query :: [{query_keyword(), query_value()}] |
213 |
|
) -> [jid:simple_bare_jid()]. |
214 |
|
get_users(LServer, [{from, Start}, {to, End}]) |
215 |
|
when is_integer(Start) and is_integer(End) -> |
216 |
:-( |
get_users(LServer, [{limit, End-Start+1}, {offset, Start}]); |
217 |
|
get_users(LServer, [{limit, Limit}, {offset, Offset}]) |
218 |
|
when is_integer(Limit) and is_integer(Offset) -> |
219 |
:-( |
get_users_within_interval(get_users(LServer), Limit, Offset); |
220 |
|
get_users(LServer, [{prefix, Prefix}]) |
221 |
|
when is_binary(Prefix) -> |
222 |
:-( |
Users = matching_users(Prefix, get_users(LServer)), |
223 |
:-( |
lists:keysort(1, Users); |
224 |
|
get_users(LServer, [{prefix, Prefix}, {from, Start}, {to, End}]) |
225 |
|
when is_binary(Prefix) and is_integer(Start) and is_integer(End) -> |
226 |
:-( |
get_users(LServer, [{prefix, Prefix}, {limit, End-Start+1}, {offset, Start}]); |
227 |
|
get_users(LServer, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}]) |
228 |
|
when is_binary(Prefix) and is_integer(Limit) and is_integer(Offset) -> |
229 |
:-( |
Users = matching_users(Prefix, get_users(LServer)), |
230 |
:-( |
get_users_within_interval(Users, Limit, Offset); |
231 |
|
get_users(LServer, _) -> |
232 |
:-( |
get_users(LServer). |
233 |
|
|
234 |
|
-spec get_users_number(LServer :: jid:server()) -> non_neg_integer(). |
235 |
|
get_users_number(LServer) -> |
236 |
:-( |
Query = mnesia:dirty_select( |
237 |
|
reg_users_counter, |
238 |
|
[{#reg_users_counter{vhost = LServer, count = '$1'}, |
239 |
|
[], |
240 |
|
['$1']}]), |
241 |
:-( |
case Query of |
242 |
|
[Count] -> |
243 |
:-( |
Count; |
244 |
:-( |
_ -> 0 |
245 |
|
end. |
246 |
|
|
247 |
|
get_registered_users_number(_, LServer, Query) -> |
248 |
:-( |
get_users_number(LServer, Query). |
249 |
|
|
250 |
|
-spec get_users_number(LServer :: jid:lserver(), Query :: [{prefix, binary()}]) -> integer(). |
251 |
|
get_users_number(LServer, [{prefix, Prefix}]) when is_binary(Prefix) -> |
252 |
:-( |
length(matching_users(Prefix, get_users(LServer))); |
253 |
|
get_users_number(LServer, _) -> |
254 |
:-( |
get_users_number(LServer). |
255 |
|
|
256 |
|
matching_users(Prefix, Users) -> |
257 |
:-( |
lists:filter(fun({U, _S}) -> |
258 |
:-( |
binary:longest_common_prefix([U, Prefix]) =:= byte_size(Prefix) |
259 |
|
end, Users). |
260 |
|
|
261 |
|
-spec get_password(mongooseim:host_type(), jid:luser(), jid:lserver()) -> |
262 |
|
ejabberd_auth:passterm() | false. |
263 |
|
get_password(_, LUser, LServer) -> |
264 |
:-( |
US = {LUser, LServer}, |
265 |
:-( |
case catch dirty_read_passwd(US) of |
266 |
|
[#passwd{password = Scram}] when is_record(Scram, scram) -> |
267 |
:-( |
mongoose_scram:scram_record_to_map(Scram); |
268 |
|
[#passwd{password = Params}] when is_map(Params)-> |
269 |
:-( |
Params; |
270 |
|
[#passwd{password = Password}] -> |
271 |
:-( |
Password; |
272 |
|
_ -> |
273 |
:-( |
false |
274 |
|
end. |
275 |
|
|
276 |
|
-spec get_password_s(mongooseim:host_type(), jid:luser(), jid:lserver()) -> binary(). |
277 |
|
get_password_s(_HostType, LUser, LServer) -> |
278 |
:-( |
US = {LUser, LServer}, |
279 |
:-( |
case catch dirty_read_passwd(US) of |
280 |
|
[#passwd{password = Scram}] when is_record(Scram, scram) -> |
281 |
:-( |
<<"">>; |
282 |
|
[#passwd{password = Params}] when is_map(Params)-> |
283 |
:-( |
<<"">>; |
284 |
|
[#passwd{password = Password}] -> |
285 |
:-( |
Password; |
286 |
|
_ -> |
287 |
:-( |
<<"">> |
288 |
|
end. |
289 |
|
|
290 |
|
-spec does_user_exist(mongooseim:host_type(), jid:luser(), jid:lserver()) -> |
291 |
|
boolean() | {error, atom()}. |
292 |
|
does_user_exist(_HostType, LUser, LServer) -> |
293 |
:-( |
US = {LUser, LServer}, |
294 |
:-( |
case catch dirty_read_passwd(US) of |
295 |
|
[] -> |
296 |
:-( |
false; |
297 |
|
[_] -> |
298 |
:-( |
true; |
299 |
|
Other -> |
300 |
:-( |
{error, Other} |
301 |
|
end. |
302 |
|
|
303 |
|
|
304 |
|
%% @doc Remove user. |
305 |
|
%% Note: it returns ok even if there was some problem removing the user. |
306 |
|
-spec remove_user(mongooseim:host_type(), jid:luser(), jid:lserver()) -> ok | {error, not_allowed}. |
307 |
|
remove_user(_HostType, LUser, LServer) -> |
308 |
:-( |
US = {LUser, LServer}, |
309 |
:-( |
F = fun() -> |
310 |
:-( |
mnesia:delete({passwd, US}), |
311 |
:-( |
mnesia:dirty_update_counter(reg_users_counter, |
312 |
|
LServer, -1) |
313 |
|
end, |
314 |
:-( |
mnesia:transaction(F), |
315 |
:-( |
ok. |
316 |
|
|
317 |
|
-spec remove_domain(mongooseim:host_type(), jid:lserver()) -> ok. |
318 |
|
remove_domain(_HostType, LServer) -> |
319 |
:-( |
Users = get_users(LServer), |
320 |
:-( |
F = fun() -> |
321 |
:-( |
lists:foreach(fun(User) -> |
322 |
:-( |
mnesia:delete({passwd, User}), |
323 |
:-( |
mnesia:dirty_update_counter(reg_users_counter, |
324 |
|
LServer, -1) |
325 |
|
end, Users) |
326 |
|
end, |
327 |
:-( |
mnesia:transaction(F), |
328 |
:-( |
ok. |
329 |
|
|
330 |
|
-spec scram_passwords() -> {atomic, ok}. |
331 |
|
scram_passwords() -> |
332 |
:-( |
?LOG_INFO(#{what => <<"scram_passwords">>, |
333 |
:-( |
text => <<"Converting the stored passwords into SCRAM bits">>}), |
334 |
:-( |
Fields = record_info(fields, passwd), |
335 |
:-( |
{atomic, ok} = mnesia:transform_table(passwd, fun scramming_function/1, Fields). |
336 |
|
|
337 |
|
-spec scramming_function(passwd()) -> passwd(). |
338 |
|
scramming_function(#passwd{us = {_, Server}, password = Password} = P) -> |
339 |
:-( |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(Server), |
340 |
:-( |
Scram = mongoose_scram:password_to_scram(HostType, Password, mongoose_scram:iterations(HostType)), |
341 |
:-( |
P#passwd{password = Scram}. |
342 |
|
|
343 |
|
-spec dirty_read_passwd(US :: jid:simple_bare_jid()) -> [passwd()]. |
344 |
|
dirty_read_passwd(US) -> |
345 |
:-( |
mnesia:dirty_read(passwd, US). |
346 |
|
|
347 |
|
-spec read_passwd(US :: jid:simple_bare_jid()) -> [passwd()]. |
348 |
|
read_passwd(US) -> |
349 |
:-( |
mnesia:read({passwd, US}). |
350 |
|
|
351 |
|
-spec write_passwd(passwd()) -> ok. |
352 |
|
write_passwd(#passwd{} = Passwd) -> |
353 |
:-( |
mnesia:write(Passwd). |
354 |
|
|
355 |
|
-spec write_counter(users_counter()) -> ok. |
356 |
|
write_counter(#reg_users_counter{} = Counter) -> |
357 |
:-( |
mnesia:write(Counter). |
358 |
|
|
359 |
|
-spec get_scram(binary(), binary()) -> mongoose_scram:scram() | binary(). |
360 |
|
get_scram(HostType, Password) -> |
361 |
:-( |
case mongoose_scram:enabled(HostType) and is_binary(Password) of |
362 |
|
true -> |
363 |
:-( |
Iterations = mongoose_scram:iterations(HostType), |
364 |
:-( |
mongoose_scram:password_to_scram(HostType, Password, Iterations); |
365 |
:-( |
false -> Password |
366 |
|
end. |
367 |
|
|
368 |
|
-spec get_users_within_interval(list(), integer(), integer()) -> list(). |
369 |
:-( |
get_users_within_interval([], _Limit, _Offset) -> []; |
370 |
|
get_users_within_interval(Users, Limit, Offset) -> |
371 |
:-( |
SortedUsers = lists:keysort(1, Users), |
372 |
:-( |
lists:sublist(SortedUsers, Offset, Limit). |
373 |
|
|
374 |
|
-spec supported_features() -> [atom()]. |
375 |
:-( |
supported_features() -> [dynamic_domains]. |