./ct_report/coverage/ejabberd_auth_external.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : ejabberd_auth_external.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Authentification via LDAP external script
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_external).
27 -author('alexey@process-one.net').
28
29 %% External exports
30 -behaviour(mongoose_gen_auth).
31
32 -export([start/1,
33 stop/1,
34 config_spec/0,
35 set_password/4,
36 authorize/1,
37 try_register/4,
38 get_registered_users/3,
39 get_registered_users_number/3,
40 get_password/3,
41 get_password_s/3,
42 does_user_exist/3,
43 remove_user/3,
44 supports_sasl_module/2,
45 supported_features/0
46 ]).
47
48 %% Internal
49 -export([check_password/4,
50 check_password/6]).
51
52 -include("mongoose.hrl").
53 -include("mongoose_config_spec.hrl").
54
55 %%%----------------------------------------------------------------------
56 %%% API
57 %%%----------------------------------------------------------------------
58
59 -spec start(HostType :: mongooseim:host_type()) -> 'ok'.
60 start(HostType) ->
61
:-(
Program = mongoose_config:get_opt([{auth, HostType}, external, program]),
62
:-(
extauth:start(HostType, Program),
63
:-(
case check_cache_last_options(HostType) of
64 cache ->
65
:-(
ok = ejabberd_auth_internal:start(HostType);
66 no_cache ->
67
:-(
ok
68 end.
69
70 -spec stop(mongooseim:host_type()) -> ok.
71 stop(HostType) ->
72
:-(
extauth:stop(HostType).
73
74 -spec config_spec() -> mongoose_config_spec:config_section().
75 config_spec() ->
76 8 #section{
77 items = #{<<"instances">> => #option{type = integer,
78 validate = positive},
79 <<"program">> => #option{type = string,
80 validate = non_empty}
81 },
82 required = [<<"program">>],
83 defaults = #{<<"instances">> => 1}
84 }.
85
86 -spec check_cache_last_options(mongooseim:host_type()) -> 'cache' | 'no_cache'.
87 check_cache_last_options(HostType) ->
88 %% if extauth_cache is enabled, then a mod_last module must also be enabled
89
:-(
case get_cache_option(HostType) of
90
:-(
false -> no_cache;
91 {true, _CacheTime} ->
92
:-(
case get_mod_last_configured(HostType) of
93 no_mod_last ->
94
:-(
?LOG_ERROR(#{what => mod_last_required_by_extauth,
95 text => <<"extauth configured with extauth_cache,"
96 " but mod_last is not enabled">>,
97
:-(
host_type => HostType}),
98
:-(
no_cache;
99
:-(
_ -> cache
100 end
101 end.
102
103 -spec supports_sasl_module(binary(), cyrsasl:sasl_module()) -> boolean().
104
:-(
supports_sasl_module(_, Module) -> Module =:= cyrsasl_plain.
105
106 -spec authorize(mongoose_credentials:t()) -> {ok, mongoose_credentials:t()}
107 | {error, any()}.
108 authorize(Creds) ->
109
:-(
ejabberd_auth:authorize_with_check_password(?MODULE, Creds).
110
111 -spec check_password(HostType :: mongooseim:host_type(),
112 LUser :: jid:luser(),
113 LServer :: jid:lserver(),
114 Password :: binary()) -> boolean().
115 check_password(HostType, LUser, LServer, Password) ->
116
:-(
case get_cache_option(HostType) of
117
:-(
false -> check_password_extauth(HostType, LUser, LServer, Password);
118
:-(
{true, CacheTime} -> check_password_cache(HostType, LUser, LServer, Password, CacheTime)
119 end.
120
121
122 -spec check_password(HostType :: mongooseim:host_type(),
123 LUser :: jid:luser(),
124 LServer :: jid:lserver(),
125 Password :: binary(),
126 Digest :: binary(),
127 DigestGen :: fun()) -> boolean().
128 check_password(HostType, LUser, LServer, Password, _Digest, _DigestGen) ->
129
:-(
check_password(HostType, LUser, LServer, Password).
130
131
132 -spec set_password(HostType :: mongooseim:host_type(),
133 LUser :: jid:luser(),
134 LServer :: jid:lserver(),
135 Password :: binary()) -> ok | {error, not_allowed}.
136 set_password(HostType, LUser, LServer, Password) ->
137
:-(
case extauth:set_password(HostType, LUser, LServer, Password) of
138 true ->
139
:-(
UseCache = get_cache_option(HostType),
140
:-(
maybe_set_password_internal(UseCache, HostType, LUser, LServer, Password);
141
:-(
_ -> {error, unknown_problem}
142 end.
143
144 maybe_set_password_internal(false, _, _, _, _) ->
145
:-(
ok;
146 maybe_set_password_internal({true, _}, HostType, LUser, LServer, Password) ->
147
:-(
set_password_internal(HostType, LUser, LServer, Password).
148
149
150 -spec try_register(HostType :: mongooseim:host_type(),
151 LUser :: jid:luser(),
152 LServer :: jid:lserver(),
153 Password :: binary()
154 ) -> ok | {error, not_allowed}.
155 try_register(HostType, LUser, LServer, Password) ->
156
:-(
case get_cache_option(HostType) of
157
:-(
false -> extauth:try_register(HostType, LUser, LServer, Password);
158
:-(
{true, _CacheTime} -> try_register_external_cache(HostType, LUser, LServer, Password)
159 end.
160
161
162 -spec get_registered_users(HostType :: mongooseim:host_type(),
163 LServer :: jid:lserver(),
164 Opts :: list()) -> [jid:simple_bare_jid()].
165 get_registered_users(HostType, LServer, Opts) ->
166
:-(
ejabberd_auth_internal:get_registered_users(HostType, LServer, Opts).
167
168
169 -spec get_registered_users_number(HostType :: mongooseim:host_type(),
170 LServer :: jid:lserver(),
171 Opts :: list()) -> non_neg_integer().
172 get_registered_users_number(HostType, LServer, Opts) ->
173
:-(
ejabberd_auth_internal:get_registered_users_number(HostType, LServer, Opts).
174
175
176 %% @doc The password can only be returned if cache is enabled, cached info
177 %% exists and is fresh enough.
178 -spec get_password(HostType :: mongooseim:host_type(),
179 LUser :: jid:luser(),
180 LServer :: jid:lserver()) -> binary() | false.
181 get_password(HostType, LUser, LServer) ->
182
:-(
case get_cache_option(HostType) of
183
:-(
false -> false;
184
:-(
{true, CacheTime} -> get_password_cache(HostType, LUser, LServer, CacheTime)
185 end.
186
187
188 -spec get_password_s(HostType :: mongooseim:host_type(),
189 LUser :: jid:luser(),
190 LServer :: jid:lserver()) -> binary().
191 get_password_s(HostType, LUser, LServer) ->
192
:-(
case get_password(HostType, LUser, LServer) of
193
:-(
false -> <<"">>;
194
:-(
Other -> Other
195 end.
196
197
198 -spec does_user_exist(HostType :: mongooseim:host_type(),
199 LUser :: jid:luser(),
200 LServer :: jid:lserver()) -> boolean() | {error, atom()}.
201 does_user_exist(HostType, LUser, LServer) ->
202
:-(
try extauth:does_user_exist(HostType, LUser, LServer) of
203
:-(
Res -> Res
204 catch
205
:-(
_:Error -> {error, Error}
206 end.
207
208
209 -spec remove_user(HostType :: mongooseim:host_type(),
210 User :: jid:luser(),
211 Server :: jid:lserver()) -> ok | {error, not_allowed}.
212 remove_user(HostType, LUser, LServer) ->
213
:-(
case extauth:remove_user(HostType, LUser, LServer) of
214
:-(
false -> {error, not_allowed};
215 true ->
216
:-(
case get_cache_option(HostType) of
217
:-(
false -> ok;
218 {true, _CacheTime} ->
219
:-(
ejabberd_auth_internal:remove_user(HostType, LUser, LServer)
220 end,
221
:-(
ok
222 end.
223
224 -spec supported_features() -> [atom()].
225
:-(
supported_features() -> [dynamic_domains].
226
227 %%%
228 %%% Extauth cache management
229 %%%
230
231 %% FIXME there is no such option in config spec
232 -spec get_cache_option(mongooseim:host_type()) -> false | {true, CacheTime::integer()}.
233 get_cache_option(HostType) ->
234
:-(
case mongoose_config:lookup_opt({extauth_cache, HostType}) of
235
:-(
{ok, CacheTime} -> {true, CacheTime};
236
:-(
{error, not_found} -> false
237 end.
238
239 -spec check_password_extauth(HostType :: mongooseim:host_type(),
240 LUser :: jid:luser(),
241 LServer :: jid:lserver(),
242 Password :: binary()) -> boolean().
243 check_password_extauth(HostType, LUser, LServer, Password) ->
244
:-(
extauth:check_password(HostType, LUser, LServer, Password) andalso Password /= "".
245
246 -spec check_password_cache(HostType :: mongooseim:host_type(),
247 LUser :: jid:luser(),
248 LServer :: jid:lserver(),
249 Password :: binary(),
250 CacheTime :: integer()) -> boolean().
251 check_password_cache(HostType, LUser, LServer, Password, CacheTime) ->
252
:-(
case get_last_access(HostType, LUser, LServer) of
253 online ->
254
:-(
check_password_internal(HostType, LUser, LServer, Password);
255 never ->
256
:-(
check_password_external_cache(HostType, LUser, LServer, Password);
257 mod_last_required ->
258
:-(
?LOG_ERROR(#{what => mod_last_required_by_extauth,
259 text => <<"extauth configured with extauth_cache but "
260 "mod_last is not enabled">>,
261
:-(
user => LUser, server => LServer}),
262
:-(
check_password_external_cache(HostType, LUser, LServer, Password);
263 TimeStamp ->
264 %% If last access exists, compare last access with cache refresh time
265
:-(
case is_fresh_enough(TimeStamp, CacheTime) of
266 %% If no need to refresh, check password against Mnesia
267 true ->
268
:-(
check_caches(HostType, LUser, LServer, Password);
269 %% Else (need to refresh), check in extauth and cache result
270 false ->
271
:-(
check_password_external_cache(HostType, LUser, LServer, Password)
272 end
273 end.
274
275 check_caches(HostType, LUser, LServer, Password) ->
276
:-(
case check_password_internal(HostType, LUser, LServer, Password) of
277
:-(
true -> true;
278
:-(
false -> check_password_external_cache(HostType, LUser, LServer, Password)
279 end.
280
281 get_password_internal(HostType, LUser, LServer) ->
282
:-(
ejabberd_auth_internal:get_password(HostType, LUser, LServer).
283
284
285 -spec get_password_cache(HostType :: mongooseim:host_type(),
286 LUser :: jid:luser(),
287 LServer :: jid:lserver(),
288 CacheTime :: integer()) -> false | binary().
289 get_password_cache(HostType, LUser, LServer, CacheTime) ->
290
:-(
case get_last_access(HostType, LUser, LServer) of
291 online ->
292
:-(
get_password_internal(HostType, LUser, LServer);
293 never ->
294
:-(
false;
295 mod_last_required ->
296
:-(
?LOG_ERROR(#{what => mod_last_required_by_extauth,
297 text => <<"extauth configured with extauth_cache but "
298 "mod_last is not enabled">>,
299
:-(
user => LUser, server => LServer}),
300
:-(
false;
301 TimeStamp ->
302
:-(
case is_fresh_enough(TimeStamp, CacheTime) of
303 true ->
304
:-(
get_password_internal(HostType, LUser, LServer);
305 false ->
306
:-(
false
307 end
308 end.
309
310
311 %% @doc Check the password using extauth; if success then cache it
312 check_password_external_cache(HostType, LUser, LServer, Password) ->
313
:-(
case check_password_extauth(HostType, LUser, LServer, Password) of
314 true ->
315 %% FIXME: here we must provide a host type as a first argument
316 %% for set_password_internal/4, current implementation will
317 %% not work with dynamic domains.
318
:-(
set_password_internal(LServer, LUser, LServer, Password), true;
319 false ->
320
:-(
false
321 end.
322
323
324 %% @doc Try to register using extauth; if success then cache it
325 -spec try_register_external_cache(HostType :: mongooseim:host_type(),
326 LUser :: jid:luser(),
327 LServer :: jid:lserver(),
328 Password :: binary()) -> ok | {error, not_allowed}.
329 try_register_external_cache(HostType, LUser, LServer, Password) ->
330
:-(
case extauth:try_register(HostType, LUser, LServer, Password) of
331 ok = R ->
332
:-(
set_password_internal(HostType, LUser, LServer, Password),
333
:-(
R;
334
:-(
_ -> {error, not_allowed}
335 end.
336
337
338 -spec check_password_internal(HostType :: mongooseim:host_type(),
339 LUser :: jid:luser(),
340 LServer :: jid:lserver(),
341 Password :: binary()) -> boolean().
342 check_password_internal(HostType, LUser, LServer, Password) ->
343 %% FIXME: here we must provide a host type as a first argument
344 %% for ejabberd_auth_internal:check_password/4, current implementation
345 %% will not work with dynamic domains.
346
:-(
ejabberd_auth_internal:check_password(HostType, LUser, LServer, Password).
347
348
349 -spec set_password_internal(HostType :: mongooseim:host_type(),
350 LUser :: jid:luser(),
351 LServer :: jid:lserver(),
352 Password :: binary()) -> ok | {error, invalid_jid}.
353 set_password_internal(HostType, LUser, LServer, Password) ->
354
:-(
ejabberd_auth_internal:set_password(HostType, LUser, LServer, Password).
355
356
357 -spec is_fresh_enough(TimeLast :: integer(),
358 CacheTime :: integer()) -> boolean().
359 is_fresh_enough(TimeStampLast, CacheTime) ->
360
:-(
Now = erlang:system_time(second),
361
:-(
(TimeStampLast + CacheTime > Now).
362
363
364 %% @doc Code copied from mod_configure.erl
365 %% Code copied from web/ejabberd_web_admin.erl
366 -spec get_last_access(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
367 online | never | mod_last_required | integer().
368 get_last_access(HostType, LUser, LServer) ->
369
:-(
JID = jid:make_noprep(LUser, LServer, <<>>),
370
:-(
case ejabberd_sm:get_user_resources(JID) of
371 [] ->
372
:-(
case get_last_info(HostType, LUser, LServer) of
373 mod_last_required ->
374
:-(
mod_last_required;
375 not_found ->
376
:-(
never;
377 {ok, Timestamp, _Status} ->
378
:-(
Timestamp
379 end;
380 _ ->
381
:-(
online
382 end.
383
384 -spec get_last_info(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
385 {ok, mod_last:timestamp(), mod_last:status()} | not_found | mod_last_required.
386 get_last_info(HostType, LUser, LServer) ->
387
:-(
case gen_mod:is_loaded(HostType, mod_last) of
388
:-(
true -> mod_last:get_last_info(HostType, LUser, LServer);
389
:-(
_ -> mod_last_required
390 end.
391
392 -spec get_mod_last_configured(mongooseim:host_type()) -> mod_last | mod_last_rdbms | no_mod_last.
393 get_mod_last_configured(HostType) ->
394
:-(
ML = is_configured(HostType, mod_last),
395
:-(
MLO = is_configured(HostType, mod_last_rdbms),
396
:-(
case {ML, MLO} of
397
:-(
{true, _} -> mod_last;
398
:-(
{false, true} -> mod_last_rdbms;
399
:-(
{false, false} -> no_mod_last
400 end.
401
402 is_configured(HostType, Module) ->
403
:-(
maps:is_key(Module, mongoose_config:get_opt({modules, HostType})).
Line Hits Source