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