* src/ejabberd_auth.erl: Improve anonymous authentication to not

remove rosters accidentally (EJAB-549). New functions in
ejabberd_auth to get/check password and know which module accepted
the authentication. New element 'auth_module' in ejabberd_c2s
record 'statedata'. Cyrsasl provides a new property in the
response: {auth_module, AuthModule}.
* src/ejabberd_auth_anonymous.erl: Likewise
* src/ejabberd_c2s.erl: Likewise
* src/cyrsasl_anonymous.erl: Likewise
* src/cyrsasl_digest.erl: Likewise
* src/cyrsasl_plain.erl: Likewise

SVN Revision: 1297
This commit is contained in:
Badlop 2008-04-22 17:41:30 +00:00
parent ee6aae8211
commit 96f0c001d9
7 changed files with 104 additions and 18 deletions

View File

@ -1,3 +1,17 @@
2008-04-22 Badlop <badlop@process-one.net>
* src/ejabberd_auth.erl: Improve anonymous authentication to not
remove rosters accidentally (EJAB-549). New functions in
ejabberd_auth to get/check password and know which module accepted
the authentication. New element 'auth_module' in ejabberd_c2s
record 'statedata'. Cyrsasl provides a new property in the
response: {auth_module, AuthModule}.
* src/ejabberd_auth_anonymous.erl: Likewise
* src/ejabberd_c2s.erl: Likewise
* src/cyrsasl_anonymous.erl: Likewise
* src/cyrsasl_digest.erl: Likewise
* src/cyrsasl_plain.erl: Likewise
2008-04-18 Badlop <badlop@process-one.net>
* src/ejabberd_s2s_out.erl: Fix long timeout when reconnecting s2s

View File

@ -51,5 +51,6 @@ mech_step(State, _ClientIn) ->
%% Checks that the username is available
case ejabberd_auth:is_user_exists(User, Server) of
true -> {error, "not-authorized"};
false -> {ok, [{username, User}]}
false -> {ok, [{username, User},
{auth_module, ejabberd_auth_anonymous}]}
end.

View File

@ -18,7 +18,7 @@
-behaviour(cyrsasl).
-record(state, {step, nonce, username, authzid, get_password}).
-record(state, {step, nonce, username, authzid, get_password, auth_module}).
start(_Opts) ->
cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE, true).
@ -44,9 +44,9 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
UserName = xml:get_attr_s("username", KeyVals),
AuthzId = xml:get_attr_s("authzid", KeyVals),
case (State#state.get_password)(UserName) of
false ->
{false, _} ->
{error, "not-authorized", UserName};
Passwd ->
{Passwd, AuthModule} ->
Response = response(KeyVals, UserName, Passwd,
Nonce, AuthzId, "AUTHENTICATE"),
case xml:get_attr_s("response", KeyVals) of
@ -57,6 +57,7 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
{continue,
"rspauth=" ++ RspAuth,
State#state{step = 5,
auth_module = AuthModule,
username = UserName,
authzid = AuthzId}};
_ ->
@ -65,9 +66,11 @@ mech_step(#state{step = 3, nonce = Nonce} = State, ClientIn) ->
end
end;
mech_step(#state{step = 5,
auth_module = AuthModule,
username = UserName,
authzid = AuthzId}, "") ->
{ok, [{username, UserName}, {authzid, AuthzId}]};
{ok, [{username, UserName}, {authzid, AuthzId},
{auth_module, AuthModule}]};
mech_step(A, B) ->
?DEBUG("SASL DIGEST: A ~p B ~p", [A,B]),
{error, "bad-protocol"}.

View File

@ -47,8 +47,9 @@ mech_step(State, ClientIn) ->
case parse(ClientIn) of
[AuthzId, User, Password] ->
case (State#state.check_password)(User, Password) of
true ->
{ok, [{username, User}, {authzid, AuthzId}]};
{true, AuthModule} ->
{ok, [{username, User}, {authzid, AuthzId},
{auth_module, AuthModule}]};
_ ->
{error, "not-authorized", User}
end;

View File

@ -34,6 +34,8 @@
set_password/3,
check_password/3,
check_password/5,
check_password_with_authmodule/3,
check_password_with_authmodule/5,
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
@ -42,6 +44,7 @@
get_vh_registered_users_number/2,
get_password/2,
get_password_s/2,
get_password_with_authmodule/2,
is_user_exists/2,
is_user_exists_in_other_modules/3,
remove_user/2,
@ -73,18 +76,57 @@ plain_password_required(Server) ->
M:plain_password_required()
end, auth_modules(Server)).
%% @doc Check if the user and password can login in server.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% true | false
check_password(User, Server, Password) ->
lists:any(
fun(M) ->
M:check_password(User, Server, Password)
end, auth_modules(Server)).
%% @doc Check if the user and password can login in server.
%% @spec (User::string(), Server::string(), Password::string(),
%% StreamID::string(), Digest::string()) ->
%% true | false
check_password(User, Server, Password, StreamID, Digest) ->
lists:any(
fun(M) ->
M:check_password(User, Server, Password, StreamID, Digest)
end, auth_modules(Server)).
%% @doc Check if the user and password can login in server.
%% The user can login if at least an authentication method accepts the user
%% and the password.
%% The first authentication method that accepts the credentials is returned.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% {true, AuthModule} | false
%% where
%% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external
%% | ejabberd_auth_internal | ejabberd_auth_ldap
%% | ejabberd_auth_odbc | ejabberd_auth_pam
check_password_with_authmodule(User, Server, Password) ->
Res = lists:dropwhile(
fun(M) ->
not apply(M, check_password,
[User, Server, Password])
end, auth_modules(Server)),
case Res of
[] -> false;
[AuthMod | _] -> {true, AuthMod}
end.
check_password_with_authmodule(User, Server, Password, StreamID, Digest) ->
Res = lists:dropwhile(
fun(M) ->
not apply(M, check_password,
[User, Server, Password, StreamID, Digest])
end, auth_modules(Server)),
case Res of
[] -> false;
[AuthMod | _] -> {true, AuthMod}
end.
%% We do not allow empty password:
set_password(_User, _Server, "") ->
{error, not_allowed};
@ -163,6 +205,8 @@ get_vh_registered_users_number(Server, Opts) ->
end
end, auth_modules(Server))).
%% @doc Get the password of the user.
%% @spec (User::string(), Server::string()) -> Password::string()
get_password(User, Server) ->
lists:foldl(
fun(M, false) ->
@ -179,6 +223,17 @@ get_password_s(User, Server) ->
Password
end.
%% @doc Get the password of the user and the auth module.
%% @spec (User::string(), Server::string()) ->
%% {Password::string(), AuthModule::atom()} | {false, none}
get_password_with_authmodule(User, Server) ->
lists:foldl(
fun(M, {false, _}) ->
{M:get_password(User, Server), M};
(_M, {Password, AuthModule}) ->
{Password, AuthModule}
end, {false, none}, auth_modules(Server)).
%% Returns true if the user exists in the DB or if an anonymous user is logged
%% under the given name
is_user_exists(User, Server) ->

View File

@ -141,11 +141,17 @@ remove_connection(SID, LUser, LServer) ->
mnesia:transaction(F).
%% Register connection
register_connection(SID, #jid{luser = LUser, lserver = LServer}, _) ->
US = {LUser, LServer},
mnesia:sync_dirty(
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
end).
register_connection(SID, #jid{luser = LUser, lserver = LServer}, Info) ->
AuthModule = xml:get_attr_s(auth_module, Info),
case AuthModule == ?MODULE of
true ->
US = {LUser, LServer},
mnesia:sync_dirty(
fun() -> mnesia:write(#anonymous{us = US, sid=SID})
end);
false ->
ok
end.
%% Remove an anonymous user from the anonymous users table
unregister_connection(SID, #jid{luser = LUser, lserver = LServer}, _) ->

View File

@ -87,6 +87,7 @@
pres_invis = false,
privacy_list = #userlist{},
conn = unknown,
auth_module = unknown,
ip,
lang}).
@ -238,11 +239,11 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
cyrsasl:server_new(
"jabber", Server, "", [],
fun(U) ->
ejabberd_auth:get_password(
ejabberd_auth:get_password_with_authmodule(
U, Server)
end,
fun(U, P) ->
ejabberd_auth:check_password(
ejabberd_auth:check_password_with_authmodule(
U, Server, P)
end),
Mechs = lists:map(
@ -430,17 +431,18 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
(acl:match_rule(StateData#state.server,
StateData#state.access, JID) == allow) of
true ->
case ejabberd_auth:check_password(
case ejabberd_auth:check_password_with_authmodule(
U, StateData#state.server, P,
StateData#state.streamid, D) of
true ->
{true, AuthModule} ->
?INFO_MSG(
"(~w) Accepted legacy authentication for ~s",
[StateData#state.socket,
jlib:jid_to_string(JID)]),
SID = {now(), self()},
Conn = get_conn_type(StateData),
Info = [{ip, StateData#state.ip}, {conn, Conn}],
Info = [{ip, StateData#state.ip}, {conn, Conn},
{auth_module, AuthModule}],
ejabberd_sm:open_session(
SID, U, StateData#state.server, R, Info),
Res1 = jlib:make_result_iq_reply(El),
@ -468,6 +470,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
jid = JID,
sid = SID,
conn = Conn,
auth_module = AuthModule,
pres_f = ?SETS:from_list(Fs1),
pres_t = ?SETS:from_list(Ts1),
privacy_list = PrivList});
@ -674,12 +677,14 @@ wait_for_sasl_response({xmlstreamelement, El}, StateData) ->
{xmlelement, "success",
[{"xmlns", ?NS_SASL}], []}),
U = xml:get_attr_s(username, Props),
AuthModule = xml:get_attr_s(auth_module, Props),
?INFO_MSG("(~w) Accepted authentication for ~s",
[StateData#state.socket, U]),
fsm_next_state(wait_for_stream,
StateData#state{
streamid = new_id(),
authenticated = true,
auth_module = AuthModule,
user = U});
{continue, ServerOut, NewSASLState} ->
send_element(StateData,
@ -790,7 +795,8 @@ wait_for_session({xmlstreamelement, El}, StateData) ->
jlib:jid_to_string(JID)]),
SID = {now(), self()},
Conn = get_conn_type(StateData),
Info = [{ip, StateData#state.ip}, {conn, Conn}],
Info = [{ip, StateData#state.ip}, {conn, Conn},
{auth_module, StateData#state.auth_module}],
ejabberd_sm:open_session(
SID, U, StateData#state.server, R, Info),
Res = jlib:make_result_iq_reply(El),