./ct_report/coverage/ejabberd_auth_internal.COVER.html

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 supports_sasl_module/2,
44 supported_features/0
45 ]).
46
47 -export([scram_passwords/0]).
48
49 %% Internal
50 -export([check_password/4,
51 check_password/6]).
52
53 %% Utilities
54 -export([dirty_get_registered_users/0]).
55
56 -ignore_xref([dirty_get_registered_users/0, scram_passwords/0]).
57
58 -include("mongoose.hrl").
59 -include("scram.hrl").
60
61 -record(passwd, {us, password}).
62
63 -type passwd() :: #passwd{
64 us :: jid:simple_bare_jid(),
65 password :: binary() | #scram{} | mongoose_scram:scram_map()
66 }.
67
68 -record(reg_users_counter, {vhost, count}).
69
70 -type users_counter() :: #reg_users_counter {
71 vhost :: binary(),
72 count :: integer()
73 }.
74
75 %%%----------------------------------------------------------------------
76 %%% API
77 %%%----------------------------------------------------------------------
78
79 -spec start(HostType :: mongooseim:host_type()) -> ok.
80 start(HostType) ->
81 5 mnesia:create_table(passwd, [{disc_copies, [node()]},
82 {attributes, record_info(fields, passwd)},
83 {storage_properties,
84 [{ets, [{read_concurrency, true}]}]}
85 ]),
86 5 mnesia:create_table(reg_users_counter,
87 [{ram_copies, [node()]},
88 {attributes, record_info(fields, reg_users_counter)}]),
89 5 mnesia:add_table_copy(passwd, node(), disc_copies),
90 5 mnesia:add_table_copy(reg_users_counter, node(), ram_copies),
91 5 update_reg_users_counter_table(HostType),
92 5 ok.
93
94 -spec stop(HostType :: mongooseim:host_type()) -> ok.
95 stop(_HostType) ->
96
:-(
ok.
97
98 -spec update_reg_users_counter_table(Server :: jid:server()) -> any().
99 update_reg_users_counter_table(Server) ->
100 5 Set = get_users(Server),
101 5 Size = length(Set),
102 5 LServer = jid:nameprep(Server),
103 5 F = fun() ->
104 5 write_counter(#reg_users_counter{vhost = LServer, count = Size})
105 end,
106 5 mnesia:sync_dirty(F).
107
108 -spec supports_sasl_module(mongooseim:host_type(), cyrsasl:sasl_module()) -> boolean().
109
:-(
supports_sasl_module(_HostType, cyrsasl_plain) -> true;
110
:-(
supports_sasl_module(HostType, cyrsasl_digest) -> not mongoose_scram:enabled(HostType);
111 66 supports_sasl_module(HostType, Mechanism) -> mongoose_scram:enabled(HostType, Mechanism).
112
113 -spec authorize(mongoose_credentials:t()) -> {ok, mongoose_credentials:t()}
114 | {error, any()}.
115 authorize(Creds) ->
116 2 ejabberd_auth:authorize_with_check_password(?MODULE, Creds).
117
118 -spec check_password(HostType :: mongooseim:host_type(),
119 LUser :: jid:luser(),
120 LServer :: jid:lserver(),
121 Password :: binary()) -> boolean().
122 check_password(_HostType, LUser, LServer, Password) ->
123 2 US = {LUser, LServer},
124 2 case catch dirty_read_passwd(US) of
125 [#passwd{password = Scram}] when is_map(Scram) orelse is_record(Scram, scram) ->
126 2 mongoose_scram:check_password(Password, Scram);
127 [#passwd{password = Password}] ->
128
:-(
Password /= <<>>;
129 _ ->
130
:-(
false
131 end.
132
133
134 -spec check_password(HostType :: mongooseim:host_type(),
135 LUser :: jid:luser(),
136 LServer :: jid:lserver(),
137 Password :: binary(),
138 Digest :: binary(),
139 DigestGen :: fun()) -> boolean().
140 check_password(_HostType, LUser, LServer, Password, Digest, DigestGen) ->
141
:-(
US = {LUser, LServer},
142
:-(
case catch dirty_read_passwd(US) of
143 [#passwd{password = Scram}] when is_record(Scram, scram) orelse is_map(Scram) ->
144
:-(
mongoose_scram:check_digest(Scram, Digest, DigestGen, Password);
145 [#passwd{password = Passwd}] ->
146
:-(
ejabberd_auth:check_digest(Digest, DigestGen, Password, Passwd);
147 _ ->
148
:-(
false
149 end.
150
151
152 -spec set_password(HostType :: mongooseim:host_type(),
153 LUser :: jid:luser(),
154 LServer :: jid:lserver(),
155 Password :: binary()) -> ok | {error, not_allowed | invalid_jid}.
156 set_password(HostType, LUser, LServer, Password) ->
157
:-(
US = {LUser, LServer},
158
:-(
F = fun() ->
159
:-(
Password2 = get_scram(HostType, Password),
160
:-(
write_passwd(#passwd{us = US, password = Password2})
161 end,
162
:-(
{atomic, ok} = mnesia:transaction(F),
163
:-(
ok.
164
165 -spec try_register(HostType :: mongooseim:host_type(),
166 LUser :: jid:luser(),
167 LServer :: jid:lserver(),
168 Password :: binary()
169 ) -> ok | {error, exists | not_allowed}.
170 try_register(HostType, LUser, LServer, Password) ->
171 4 US = {LUser, LServer},
172 4 F = fun() ->
173 4 case read_passwd(US) of
174 [] ->
175 4 Password2 = get_scram(HostType, Password),
176 4 write_passwd(#passwd{us = US, password = Password2}),
177 4 mnesia:dirty_update_counter(reg_users_counter, LServer, 1),
178 4 ok;
179 [_E] ->
180
:-(
exists
181 end
182 end,
183 4 case mnesia:transaction(F) of
184 {atomic, ok} ->
185 4 ok;
186 {atomic, exists} ->
187
:-(
{error, exists};
188 Result ->
189
:-(
?LOG_ERROR(#{what => registration_error,
190
:-(
user => LUser, server => LServer, reason => Result}),
191
:-(
{error, not_allowed}
192 end.
193
194
195 %% @doc Get all registered users in Mnesia
196 -spec dirty_get_registered_users() -> [jid:simple_bare_jid()].
197 dirty_get_registered_users() ->
198
:-(
mnesia:dirty_all_keys(passwd).
199
200 -spec get_users(LServer :: jid:lserver()) -> [jid:simple_bare_jid()].
201 get_users(LServer) ->
202 5 mnesia:dirty_select(
203 passwd,
204 [{#passwd{us = '$1', _ = '_'},
205 [{'==', {element, 2, '$1'}, LServer}],
206 ['$1']}]).
207
208 get_registered_users(_, LServer, Opts) ->
209
:-(
get_users(LServer, Opts).
210
211 -type query_keyword() :: from | to | limit | offset | prefix.
212 -type query_value() :: integer() | binary().
213 -spec get_users(LServer :: jid:lserver(),
214 Query :: [{query_keyword(), query_value()}]
215 ) -> [jid:simple_bare_jid()].
216 get_users(LServer, [{from, Start}, {to, End}])
217 when is_integer(Start) and is_integer(End) ->
218
:-(
get_users(LServer, [{limit, End-Start+1}, {offset, Start}]);
219 get_users(LServer, [{limit, Limit}, {offset, Offset}])
220 when is_integer(Limit) and is_integer(Offset) ->
221
:-(
get_users_within_interval(get_users(LServer), Limit, Offset);
222 get_users(LServer, [{prefix, Prefix}])
223 when is_binary(Prefix) ->
224
:-(
Users = matching_users(Prefix, get_users(LServer)),
225
:-(
lists:keysort(1, Users);
226 get_users(LServer, [{prefix, Prefix}, {from, Start}, {to, End}])
227 when is_binary(Prefix) and is_integer(Start) and is_integer(End) ->
228
:-(
get_users(LServer, [{prefix, Prefix}, {limit, End-Start+1}, {offset, Start}]);
229 get_users(LServer, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
230 when is_binary(Prefix) and is_integer(Limit) and is_integer(Offset) ->
231
:-(
Users = matching_users(Prefix, get_users(LServer)),
232
:-(
get_users_within_interval(Users, Limit, Offset);
233 get_users(LServer, _) ->
234
:-(
get_users(LServer).
235
236 -spec get_users_number(LServer :: jid:server()) -> non_neg_integer().
237 get_users_number(LServer) ->
238
:-(
Query = mnesia:dirty_select(
239 reg_users_counter,
240 [{#reg_users_counter{vhost = LServer, count = '$1'},
241 [],
242 ['$1']}]),
243
:-(
case Query of
244 [Count] ->
245
:-(
Count;
246
:-(
_ -> 0
247 end.
248
249 get_registered_users_number(_, LServer, Query) ->
250
:-(
get_users_number(LServer, Query).
251
252 -spec get_users_number(LServer :: jid:lserver(), Query :: [{prefix, binary()}]) -> integer().
253 get_users_number(LServer, [{prefix, Prefix}]) when is_binary(Prefix) ->
254
:-(
length(matching_users(Prefix, get_users(LServer)));
255 get_users_number(LServer, _) ->
256
:-(
get_users_number(LServer).
257
258 matching_users(Prefix, Users) ->
259
:-(
lists:filter(fun({U, _S}) ->
260
:-(
binary:longest_common_prefix([U, Prefix]) =:= byte_size(Prefix)
261 end, Users).
262
263 -spec get_password(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
264 ejabberd_auth:passterm() | false.
265 get_password(_, LUser, LServer) ->
266
:-(
US = {LUser, LServer},
267
:-(
case catch dirty_read_passwd(US) of
268 [#passwd{password = Scram}] when is_record(Scram, scram) ->
269
:-(
mongoose_scram:scram_record_to_map(Scram);
270 [#passwd{password = Params}] when is_map(Params)->
271
:-(
Params;
272 [#passwd{password = Password}] ->
273
:-(
Password;
274 _ ->
275
:-(
false
276 end.
277
278 -spec get_password_s(mongooseim:host_type(), jid:luser(), jid:lserver()) -> binary().
279 get_password_s(_HostType, LUser, LServer) ->
280
:-(
US = {LUser, LServer},
281
:-(
case catch dirty_read_passwd(US) of
282 [#passwd{password = Scram}] when is_record(Scram, scram) ->
283
:-(
<<"">>;
284 [#passwd{password = Params}] when is_map(Params)->
285
:-(
<<"">>;
286 [#passwd{password = Password}] ->
287
:-(
Password;
288 _ ->
289
:-(
<<"">>
290 end.
291
292 -spec does_user_exist(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
293 boolean() | {error, atom()}.
294 does_user_exist(_HostType, LUser, LServer) ->
295
:-(
US = {LUser, LServer},
296
:-(
case catch dirty_read_passwd(US) of
297 [] ->
298
:-(
false;
299 [_] ->
300
:-(
true;
301 Other ->
302
:-(
{error, Other}
303 end.
304
305
306 %% @doc Remove user.
307 %% Note: it returns ok even if there was some problem removing the user.
308 -spec remove_user(mongooseim:host_type(), jid:luser(), jid:lserver()) -> ok | {error, not_allowed}.
309 remove_user(_HostType, LUser, LServer) ->
310 4 US = {LUser, LServer},
311 4 F = fun() ->
312 4 mnesia:delete({passwd, US}),
313 4 mnesia:dirty_update_counter(reg_users_counter,
314 LServer, -1)
315 end,
316 4 mnesia:transaction(F),
317 4 ok.
318
319 -spec scram_passwords() -> {atomic, ok}.
320 scram_passwords() ->
321
:-(
?LOG_INFO(#{what => <<"scram_passwords">>,
322
:-(
text => <<"Converting the stored passwords into SCRAM bits">>}),
323
:-(
Fields = record_info(fields, passwd),
324
:-(
{atomic, ok} = mnesia:transform_table(passwd, fun scramming_function/1, Fields).
325
326 -spec scramming_function(passwd()) -> passwd().
327 scramming_function(#passwd{us = {_, Server}, password = Password} = P) ->
328
:-(
{ok, HostType} = mongoose_domain_api:get_domain_host_type(Server),
329
:-(
Scram = mongoose_scram:password_to_scram(HostType, Password, mongoose_scram:iterations(HostType)),
330
:-(
P#passwd{password = Scram}.
331
332 -spec dirty_read_passwd(US :: jid:simple_bare_jid()) -> [passwd()].
333 dirty_read_passwd(US) ->
334 2 mnesia:dirty_read(passwd, US).
335
336 -spec read_passwd(US :: jid:simple_bare_jid()) -> [passwd()].
337 read_passwd(US) ->
338 4 mnesia:read({passwd, US}).
339
340 -spec write_passwd(passwd()) -> ok.
341 write_passwd(#passwd{} = Passwd) ->
342 4 mnesia:write(Passwd).
343
344 -spec write_counter(users_counter()) -> ok.
345 write_counter(#reg_users_counter{} = Counter) ->
346 5 mnesia:write(Counter).
347
348 -spec get_scram(binary(), binary()) -> mongoose_scram:scram() | binary().
349 get_scram(HostType, Password) ->
350 4 case mongoose_scram:enabled(HostType) and is_binary(Password) of
351 true ->
352 4 Iterations = mongoose_scram:iterations(HostType),
353 4 mongoose_scram:password_to_scram(HostType, Password, Iterations);
354
:-(
false -> Password
355 end.
356
357 -spec get_users_within_interval(list(), integer(), integer()) -> list().
358
:-(
get_users_within_interval([], _Limit, _Offset) -> [];
359 get_users_within_interval(Users, Limit, Offset) ->
360
:-(
SortedUsers = lists:keysort(1, Users),
361
:-(
lists:sublist(SortedUsers, Offset, Limit).
362
363 -spec supported_features() -> [atom()].
364 1 supported_features() -> [dynamic_domains].
Line Hits Source