./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 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 257 mongoose_mnesia:create_table(passwd,
83 [{disc_copies, [node()]},
84 {attributes, record_info(fields, passwd)},
85 {storage_properties, [{ets, [{read_concurrency, true}]}]}]),
86 257 mongoose_mnesia:create_table(reg_users_counter,
87 [{ram_copies, [node()]},
88 {attributes, record_info(fields, reg_users_counter)}]),
89 257 update_reg_users_counter_table(HostType),
90 257 ok.
91
92 -spec stop(HostType :: mongooseim:host_type()) -> ok.
93 stop(_HostType) ->
94 1 ok.
95
96 -spec update_reg_users_counter_table(Server :: jid:server()) -> any().
97 update_reg_users_counter_table(Server) ->
98 257 Set = get_users(Server),
99 257 Size = length(Set),
100 257 LServer = jid:nameprep(Server),
101 257 F = fun() ->
102 257 write_counter(#reg_users_counter{vhost = LServer, count = Size})
103 end,
104 257 mnesia:sync_dirty(F).
105
106 -spec supports_sasl_module(mongooseim:host_type(), cyrsasl:sasl_module()) -> boolean().
107 10261 supports_sasl_module(_HostType, cyrsasl_plain) -> true;
108 7 supports_sasl_module(HostType, cyrsasl_digest) -> not mongoose_scram:enabled(HostType);
109 76968 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 3637 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 3640 US = {LUser, LServer},
122 3640 case catch dirty_read_passwd(US) of
123 [#passwd{password = Scram}] when is_map(Scram) orelse is_record(Scram, scram) ->
124 3629 mongoose_scram:check_password(Password, Scram);
125 [#passwd{password = Password}] ->
126 1 Password /= <<>>;
127 _ ->
128 10 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 1 US = {LUser, LServer},
140 1 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 1 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 9 US = {LUser, LServer},
156 9 F = fun() ->
157 9 Password2 = get_scram(HostType, Password),
158 9 write_passwd(#passwd{us = US, password = Password2})
159 end,
160 9 {atomic, ok} = mnesia:transaction(F),
161 9 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 3032 US = {LUser, LServer},
170 3032 F = fun() ->
171 3032 case read_passwd(US) of
172 [] ->
173 3032 Password2 = get_scram(HostType, Password),
174 3032 write_passwd(#passwd{us = US, password = Password2}),
175 3032 mnesia:dirty_update_counter(reg_users_counter, LServer, 1),
176 3032 ok;
177 [_E] ->
178
:-(
exists
179 end
180 end,
181 3032 case mnesia:transaction(F) of
182 {atomic, ok} ->
183 3032 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 499 mnesia:dirty_select(
201 passwd,
202 [{#passwd{us = '$1', _ = '_'},
203 [{'==', {element, 2, '$1'}, LServer}],
204 ['$1']}]).
205
206 get_registered_users(_, LServer, Opts) ->
207 240 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 2 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 2 get_users_within_interval(get_users(LServer), Limit, Offset);
220 get_users(LServer, [{prefix, Prefix}])
221 when is_binary(Prefix) ->
222 1 Users = matching_users(Prefix, get_users(LServer)),
223 1 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 2 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 2 Users = matching_users(Prefix, get_users(LServer)),
230 2 get_users_within_interval(Users, Limit, Offset);
231 get_users(LServer, _) ->
232 235 get_users(LServer).
233
234 -spec get_users_number(LServer :: jid:server()) -> non_neg_integer().
235 get_users_number(LServer) ->
236 3249 Query = mnesia:dirty_select(
237 reg_users_counter,
238 [{#reg_users_counter{vhost = LServer, count = '$1'},
239 [],
240 ['$1']}]),
241 3249 case Query of
242 [Count] ->
243 3249 Count;
244
:-(
_ -> 0
245 end.
246
247 get_registered_users_number(_, LServer, Query) ->
248 3250 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 1 length(matching_users(Prefix, get_users(LServer)));
253 get_users_number(LServer, _) ->
254 3249 get_users_number(LServer).
255
256 matching_users(Prefix, Users) ->
257 4 lists:filter(fun({U, _S}) ->
258 8 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 119 US = {LUser, LServer},
265 119 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 82 Params;
270 [#passwd{password = Password}] ->
271 35 Password;
272 _ ->
273 2 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 16 US = {LUser, LServer},
279 16 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 2 <<"">>;
284 [#passwd{password = Password}] ->
285 12 Password;
286 _ ->
287 2 <<"">>
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 7567 US = {LUser, LServer},
294 7567 case catch dirty_read_passwd(US) of
295 [] ->
296 3345 false;
297 [_] ->
298 4222 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 3031 US = {LUser, LServer},
309 3031 F = fun() ->
310 3031 mnesia:delete({passwd, US}),
311 3031 mnesia:dirty_update_counter(reg_users_counter,
312 LServer, -1)
313 end,
314 3031 mnesia:transaction(F),
315 3031 ok.
316
317 -spec remove_domain(mongooseim:host_type(), jid:lserver()) -> ok.
318 remove_domain(_HostType, LServer) ->
319 1 Users = get_users(LServer),
320 1 F = fun() ->
321 1 lists:foreach(fun(User) ->
322 1 mnesia:delete({passwd, User}),
323 1 mnesia:dirty_update_counter(reg_users_counter,
324 LServer, -1)
325 end, Users)
326 end,
327 1 mnesia:transaction(F),
328 1 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 11343 mnesia:dirty_read(passwd, US).
346
347 -spec read_passwd(US :: jid:simple_bare_jid()) -> [passwd()].
348 read_passwd(US) ->
349 3032 mnesia:read({passwd, US}).
350
351 -spec write_passwd(passwd()) -> ok.
352 write_passwd(#passwd{} = Passwd) ->
353 3041 mnesia:write(Passwd).
354
355 -spec write_counter(users_counter()) -> ok.
356 write_counter(#reg_users_counter{} = Counter) ->
357 257 mnesia:write(Counter).
358
359 -spec get_scram(binary(), binary()) -> mongoose_scram:scram() | binary().
360 get_scram(HostType, Password) ->
361 3041 case mongoose_scram:enabled(HostType) and is_binary(Password) of
362 true ->
363 3019 Iterations = mongoose_scram:iterations(HostType),
364 3019 mongoose_scram:password_to_scram(HostType, Password, Iterations);
365 22 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 4 SortedUsers = lists:keysort(1, Users),
372 4 lists:sublist(SortedUsers, Offset, Limit).
373
374 -spec supported_features() -> [atom()].
375 84 supported_features() -> [dynamic_domains].
Line Hits Source