+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
%% 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.
-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).
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
{continue,
"rspauth=" ++ RspAuth,
State#state{step = 5,
+ auth_module = AuthModule,
username = UserName,
authzid = AuthzId}};
_ ->
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"}.
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;
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,
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,
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};
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) ->
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) ->
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}, _) ->
pres_invis = false,
privacy_list = #userlist{},
conn = unknown,
+ auth_module = unknown,
ip,
lang}).
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(
(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),
jid = JID,
sid = SID,
conn = Conn,
+ auth_module = AuthModule,
pres_f = ?SETS:from_list(Fs1),
pres_t = ?SETS:from_list(Ts1),
privacy_list = PrivList});
{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,
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),