1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : ejabberd_auth_anonymous.erl |
3 |
|
%%% Author : Mickael Remond <mickael.remond@process-one.net> |
4 |
|
%%% Purpose : Anonymous feature support in ejabberd |
5 |
|
%%% Created : 17 Feb 2006 by Mickael Remond <mremond@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_anonymous). |
27 |
|
-author('mickael.remond@process-one.net'). |
28 |
|
|
29 |
|
-export([start/1, |
30 |
|
stop/1, |
31 |
|
config_spec/0, |
32 |
|
register_connection/3, |
33 |
|
unregister_connection/3, |
34 |
|
session_cleanup/3 |
35 |
|
]). |
36 |
|
|
37 |
|
-behaviour(mongoose_gen_auth). |
38 |
|
|
39 |
|
%% Function used by ejabberd_auth: |
40 |
|
-export([login/3, |
41 |
|
set_password/4, |
42 |
|
authorize/1, |
43 |
|
get_password/3, |
44 |
|
does_user_exist/3, |
45 |
|
supports_sasl_module/2, |
46 |
|
get_registered_users/3, |
47 |
|
supported_features/0 |
48 |
|
]). |
49 |
|
|
50 |
|
%% Internal |
51 |
|
-export([check_password/4, |
52 |
|
check_password/6]). |
53 |
|
|
54 |
|
-ignore_xref([login/3]). |
55 |
|
|
56 |
|
-include("mongoose.hrl"). |
57 |
|
-include("jlib.hrl"). |
58 |
|
-include("session.hrl"). |
59 |
|
-include("mongoose_config_spec.hrl"). |
60 |
|
|
61 |
|
%% @doc Create the anonymous table if at least one host type has anonymous |
62 |
|
%% features enabled. Register to login / logout events |
63 |
|
-spec start(mongooseim:host_type()) -> ok. |
64 |
|
start(HostType) -> |
65 |
|
%% TODO: Check cluster mode |
66 |
77 |
ejabberd_auth_anonymous_backend:init(HostType), |
67 |
|
%% The hooks are needed to add / remove users from the anonymous tables |
68 |
77 |
gen_hook:add_handlers(hooks(HostType)), |
69 |
77 |
ok. |
70 |
|
|
71 |
|
-spec stop(mongooseim:host_type()) -> ok. |
72 |
|
stop(HostType) -> |
73 |
:-( |
gen_hook:delete_handlers(hooks(HostType)), |
74 |
:-( |
ejabberd_auth_anonymous_backend:stop(HostType), |
75 |
:-( |
ok. |
76 |
|
|
77 |
|
-spec hooks(mongooseim:host_type()) -> gen_hook:hook_list(). |
78 |
|
hooks(HostType) -> |
79 |
77 |
[ |
80 |
|
{sm_register_connection_hook, HostType, fun ?MODULE:register_connection/3, #{}, 100}, |
81 |
|
{sm_remove_connection_hook, HostType, fun ?MODULE:unregister_connection/3, #{}, 100}, |
82 |
|
{session_cleanup, HostType, fun ?MODULE:session_cleanup/3, #{}, 50} |
83 |
|
]. |
84 |
|
|
85 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
86 |
|
config_spec() -> |
87 |
106 |
#section{ |
88 |
|
items = #{<<"backend">> => #option{type = atom, validate = {module, ?MODULE}}, |
89 |
|
<<"allow_multiple_connections">> => #option{type = boolean}, |
90 |
|
<<"protocol">> => #option{type = atom, |
91 |
|
validate = {enum, [sasl_anon, login_anon, both]}} |
92 |
|
}, |
93 |
|
defaults = #{<<"backend">> => mnesia, |
94 |
|
<<"allow_multiple_connections">> => false, |
95 |
|
<<"protocol">> => sasl_anon} |
96 |
|
}. |
97 |
|
|
98 |
|
%% @doc Return true if multiple connections have been allowed in the config file |
99 |
|
%% defaults to false |
100 |
|
-spec allow_multiple_connections(mongooseim:host_type()) -> boolean(). |
101 |
|
allow_multiple_connections(HostType) -> |
102 |
:-( |
mongoose_config:get_opt([{auth, HostType}, anonymous, allow_multiple_connections]). |
103 |
|
|
104 |
|
does_user_exist(HostType, LUser, LServer) -> |
105 |
11 |
does_anonymous_user_exist(HostType, LUser, LServer). |
106 |
|
|
107 |
|
%% @doc Check if user exist in the anonymous database |
108 |
|
-spec does_anonymous_user_exist(mongooseim:host_type(), jid:luser(), jid:lserver()) -> boolean(). |
109 |
|
does_anonymous_user_exist(HostType, LUser, LServer) -> |
110 |
17 |
US = {LUser, LServer}, |
111 |
17 |
ejabberd_auth_anonymous_backend:does_anonymous_user_exist(HostType, US). |
112 |
|
|
113 |
|
%% @doc Remove connection from Mnesia tables |
114 |
|
-spec remove_connection( |
115 |
|
mongooseim:host_type(), ejabberd_sm:sid(), jid:luser(), jid:lserver()) -> ok. |
116 |
|
remove_connection(HostType, SID, LUser, LServer) -> |
117 |
4 |
US = {LUser, LServer}, |
118 |
4 |
ejabberd_auth_anonymous_backend:remove_connection(HostType, SID, US). |
119 |
|
|
120 |
|
%% @doc Register connection |
121 |
|
-spec register_connection(Acc, Params, Extra) -> {ok, Acc} when |
122 |
|
Acc :: term(), |
123 |
|
Params :: map(), |
124 |
|
Extra :: map(). |
125 |
|
register_connection(Acc, |
126 |
|
#{sid := SID, |
127 |
|
jid := #jid{luser = LUser, lserver = LServer}, |
128 |
|
info := #{auth_module := AuthModule}}, |
129 |
|
#{host_type := HostType}) |
130 |
|
when AuthModule =:= ejabberd_auth_anonymous; % login_anon |
131 |
|
AuthModule =:= cyrsasl_anonymous -> % sasl_anon |
132 |
4 |
mongoose_hooks:register_user(HostType, LServer, LUser), |
133 |
4 |
US = {LUser, LServer}, |
134 |
4 |
ejabberd_auth_anonymous_backend:add_connection(HostType, SID, US), |
135 |
4 |
{ok, Acc}; |
136 |
|
register_connection(Acc, _Params, _Extra) -> |
137 |
:-( |
{ok, Acc}. |
138 |
|
|
139 |
|
%% @doc Remove an anonymous user from the anonymous users table |
140 |
|
-spec unregister_connection(Acc, Params, Extra) -> {ok, Acc} when |
141 |
|
Acc :: mongoose_acc:t(), |
142 |
|
Params :: map(), |
143 |
|
Extra :: map(). |
144 |
|
unregister_connection(Acc, #{sid := SID, jid := #jid{luser = LUser, lserver = LServer}}, #{host_type := HostType}) -> |
145 |
4 |
purge_hook(does_anonymous_user_exist(HostType, LUser, LServer), HostType, LUser, LServer), |
146 |
4 |
remove_connection(HostType, SID, LUser, LServer), |
147 |
4 |
{ok, Acc}. |
148 |
|
|
149 |
|
%% @doc Launch the hook to purge user data only for anonymous users. |
150 |
|
-spec purge_hook(boolean(), mongooseim:host_type(), jid:luser(), jid:lserver()) -> 'ok'. |
151 |
|
purge_hook(false, _HostType, _LUser, _LServer) -> |
152 |
:-( |
ok; |
153 |
|
purge_hook(true, HostType, LUser, LServer) -> |
154 |
4 |
Acc = mongoose_acc:new(#{ location => ?LOCATION, |
155 |
|
host_type => HostType, |
156 |
|
lserver => LServer, |
157 |
|
element => undefined }), |
158 |
4 |
mongoose_hooks:anonymous_purge_hook(LServer, Acc, LUser). |
159 |
|
|
160 |
|
-spec session_cleanup(Acc, Params, Extra) -> {ok, Acc} when |
161 |
|
Acc :: mongoose_acc:t(), |
162 |
|
Params :: map(), |
163 |
|
Extra :: map(). |
164 |
|
session_cleanup(Acc, #{sid := SID, jid := #jid{luser = LUser, lserver = LServer}}, #{host_type := HostType}) -> |
165 |
:-( |
remove_connection(HostType, SID, LUser, LServer), |
166 |
:-( |
{ok, Acc}. |
167 |
|
|
168 |
|
%% --------------------------------- |
169 |
|
%% Specific anonymous auth functions |
170 |
|
%% --------------------------------- |
171 |
|
|
172 |
|
-spec authorize(mongoose_credentials:t()) -> {ok, mongoose_credentials:t()} |
173 |
|
| {error, any()}. |
174 |
|
authorize(Creds) -> |
175 |
2 |
ejabberd_auth:authorize_with_check_password(?MODULE, Creds). |
176 |
|
|
177 |
|
%% @doc When anonymous login is enabled, check the password for permanent users |
178 |
|
%% before allowing access |
179 |
|
-spec check_password(HostType :: mongooseim:host_type(), |
180 |
|
LUser :: jid:luser(), |
181 |
|
LServer :: jid:lserver(), |
182 |
|
Password :: binary()) -> boolean(). |
183 |
|
check_password(HostType, LUser, LServer, Password) -> |
184 |
2 |
check_password(HostType, LUser, LServer, Password, undefined, undefined). |
185 |
|
|
186 |
|
check_password(HostType, LUser, LServer, _Password, _Digest, _DigestGen) -> |
187 |
|
%% We refuse login for registered accounts (They cannot logged but |
188 |
|
%% they however are "reserved") |
189 |
2 |
case ejabberd_auth:does_stored_user_exist( |
190 |
|
HostType, jid:make_noprep(LUser, LServer, <<>>)) of |
191 |
|
%% If user exists in other module, reject anonymous authentication |
192 |
:-( |
true -> false; |
193 |
|
%% If we are not sure whether the user exists in other module, reject anon auth |
194 |
:-( |
{error, _Error} -> false; |
195 |
2 |
false -> login(HostType, LUser, LServer) |
196 |
|
end. |
197 |
|
|
198 |
|
|
199 |
|
-spec login(HostType :: mongooseim:host_type(), LUser :: jid:luser(), |
200 |
|
LServer :: jid:lserver()) -> boolean(). |
201 |
|
login(HostType, LUser, LServer) -> |
202 |
2 |
case is_protocol_enabled(HostType, login_anon) of |
203 |
:-( |
false -> false; |
204 |
|
true -> |
205 |
2 |
case does_anonymous_user_exist(HostType, LUser, LServer) of |
206 |
|
%% Reject the login if an anonymous user with the same login |
207 |
|
%% is already logged and if multiple login has not been enable |
208 |
|
%% in the config file. |
209 |
:-( |
true -> allow_multiple_connections(HostType); |
210 |
|
%% Accept login and add user to the anonymous table |
211 |
2 |
false -> true |
212 |
|
end |
213 |
|
end. |
214 |
|
|
215 |
|
|
216 |
|
%% @doc When anonymous login is enabled, check that the user is permanent before |
217 |
|
%% changing its password |
218 |
|
-spec set_password(HostType :: mongooseim:host_type(), |
219 |
|
LUser :: jid:luser(), |
220 |
|
LServer :: jid:lserver(), |
221 |
|
Password :: binary()) -> ok | {error, not_allowed}. |
222 |
|
set_password(HostType, LUser, LServer, _Password) -> |
223 |
:-( |
case does_anonymous_user_exist(HostType, LUser, LServer) of |
224 |
|
true -> |
225 |
:-( |
ok; |
226 |
|
false -> |
227 |
:-( |
{error, not_allowed} |
228 |
|
end. |
229 |
|
|
230 |
|
-spec get_registered_users(mongooseim:host_type(), jid:lserver(), list()) -> |
231 |
|
[jid:simple_bare_jid()]. |
232 |
|
get_registered_users(_HostType, LServer, _) -> |
233 |
12 |
[{U, S} || #session{us = {U, S}} <- ejabberd_sm:get_vh_session_list(LServer)]. |
234 |
|
|
235 |
|
%% @doc Return password of permanent user or false for anonymous users |
236 |
|
-spec get_password(HostType :: mongooseim:host_type(), |
237 |
|
LUser :: jid:luser(), |
238 |
|
LServer :: jid:lserver()) -> binary() | false. |
239 |
|
get_password(HostType, LUser, LServer) -> |
240 |
:-( |
case does_anonymous_user_exist(HostType, LUser, LServer) orelse login(HostType, LUser, LServer) of |
241 |
|
%% We return the default value if the user is anonymous |
242 |
|
true -> |
243 |
:-( |
<<>>; |
244 |
|
%% We return the permanent user password otherwise |
245 |
|
false -> |
246 |
:-( |
false |
247 |
|
end. |
248 |
|
|
249 |
|
%% @doc Returns true if the SASL mechanism is supportedon the server |
250 |
|
%% Anonymous login can be used with a standard authentication method |
251 |
|
%% (i.e. with clients that do not support SASL ANONYMOUS) |
252 |
|
-spec supports_sasl_module(mongooseim:host_type(), cyrsasl:sasl_module()) -> boolean(). |
253 |
|
supports_sasl_module(HostType, cyrsasl_anonymous) -> |
254 |
10 |
is_protocol_enabled(HostType, sasl_anon); |
255 |
|
supports_sasl_module(HostType, cyrsasl_plain) -> |
256 |
10 |
is_protocol_enabled(HostType, login_anon); |
257 |
|
supports_sasl_module(HostType, cyrsasl_digest) -> |
258 |
:-( |
is_protocol_enabled(HostType, login_anon); |
259 |
|
supports_sasl_module(HostType, Mechanism) -> |
260 |
80 |
case mongoose_scram:enabled(HostType, Mechanism) of |
261 |
|
true -> |
262 |
80 |
is_protocol_enabled(HostType, login_anon); |
263 |
|
_ -> |
264 |
:-( |
false |
265 |
|
end. |
266 |
|
|
267 |
|
%% @doc Returns true if the requested anonymous protocol is enabled |
268 |
|
-spec is_protocol_enabled(mongooseim:host_type(), sasl_anon | login_anon) -> boolean(). |
269 |
|
is_protocol_enabled(HostType, Protocol) -> |
270 |
102 |
case anonymous_protocol(HostType) of |
271 |
102 |
both -> true; |
272 |
:-( |
Protocol -> true; |
273 |
:-( |
_ -> false |
274 |
|
end. |
275 |
|
|
276 |
|
%% @doc Returns the anonymous protocol to use, defaults to sasl_anon |
277 |
|
-spec anonymous_protocol(mongooseim:host_type()) -> sasl_anon | login_anon | both. |
278 |
|
anonymous_protocol(HostType) -> |
279 |
102 |
mongoose_config:get_opt([{auth, HostType}, anonymous, protocol]). |
280 |
|
|
281 |
|
-spec supported_features() -> [atom()]. |
282 |
27 |
supported_features() -> [dynamic_domains]. |