diff --git a/ChangeLog b/ChangeLog index 6e0151260..23bf00dcc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2008-04-22 Badlop + + * 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 * src/ejabberd_s2s_out.erl: Fix long timeout when reconnecting s2s diff --git a/src/cyrsasl_anonymous.erl b/src/cyrsasl_anonymous.erl index 7393685d8..b9cf7449c 100644 --- a/src/cyrsasl_anonymous.erl +++ b/src/cyrsasl_anonymous.erl @@ -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. diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl index 6d87ae199..5395205d7 100644 --- a/src/cyrsasl_digest.erl +++ b/src/cyrsasl_digest.erl @@ -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"}. diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl index eaeb1be47..1b9cddb49 100644 --- a/src/cyrsasl_plain.erl +++ b/src/cyrsasl_plain.erl @@ -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; diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 5201900d4..d6a7e5228 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -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) -> diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl index be04fc0a8..3c5c58fed 100644 --- a/src/ejabberd_auth_anonymous.erl +++ b/src/ejabberd_auth_anonymous.erl @@ -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}, _) -> diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 4974c0acf..53bb41caf 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -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),