From 6c154ff8dc301ba80bf09c0cd34ee9c3871ddebf Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Wed, 8 Apr 2015 22:21:09 -0300 Subject: [PATCH 1/2] Use SASL PLAIN authzid as client identity if auth module permits it This allows the authentication modules to perform SASL proxy authentication. It puts the onus on them to authorize the authcid to masquerade as the authzid. Doesn't currently implement such functionality in existing auth modules, since they cannot currently codify a relationship between the two identities. Does not permit the authzid to use a domain differently from the one of the connection. Note: digest might not work, but I have no interest in it, being deprecated. Conflicts: src/ejabberd_auth.erl src/ejabberd_auth_odbc.erl --- src/cyrsasl.erl | 2 +- src/cyrsasl_digest.erl | 4 +- src/cyrsasl_plain.erl | 13 ++- src/ejabberd_auth.erl | 44 ++++---- src/ejabberd_auth_anonymous.erl | 10 +- src/ejabberd_auth_external.erl | 54 +++++----- src/ejabberd_auth_internal.erl | 84 ++++++++------- src/ejabberd_auth_ldap.erl | 26 +++-- src/ejabberd_auth_odbc.erl | 174 +++++++++++++++++--------------- src/ejabberd_auth_pam.erl | 36 ++++--- src/ejabberd_auth_riak.erl | 78 +++++++------- src/ejabberd_c2s.erl | 28 ++--- src/ejabberd_commands.erl | 2 +- src/ejabberd_web_admin.erl | 2 +- 14 files changed, 299 insertions(+), 258 deletions(-) diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl index 764473bab09..09b1a1a6f12 100644 --- a/src/cyrsasl.erl +++ b/src/cyrsasl.erl @@ -128,7 +128,7 @@ register_mechanism(Mechanism, Module, PasswordType) -> %% end. check_credentials(_State, Props) -> - User = proplists:get_value(username, Props, <<>>), + User = proplists:get_value(authzid, Props, <<>>), case jlib:nodeprep(User) of error -> {error, <<"not-authorized">>}; <<"">> -> {error, <<"not-authorized">>}; diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl index 10f9641308d..2ba044cf683 100644 --- a/src/cyrsasl_digest.erl +++ b/src/cyrsasl_digest.erl @@ -47,7 +47,7 @@ username = <<"">> :: binary(), authzid = <<"">> :: binary(), get_password = fun(_) -> {false, <<>>} end :: get_password_fun(), - check_password = fun(_, _, _, _) -> false end :: check_password_fun(), + check_password = fun(_, _, _, _, _) -> false end :: check_password_fun(), auth_module :: atom(), host = <<"">> :: binary(), hostfqdn = <<"">> :: binary()}). @@ -98,7 +98,7 @@ mech_step(#state{step = 3, nonce = Nonce} = State, case (State#state.get_password)(UserName) of {false, _} -> {error, <<"not-authorized">>, UserName}; {Passwd, AuthModule} -> - case (State#state.check_password)(UserName, <<"">>, + case (State#state.check_password)(UserName, AuthzId, <<"">>, proplists:get_value(<<"response">>, KeyVals, <<>>), %xml:get_attr_s(<<"response">>, KeyVals), fun (PW) -> diff --git a/src/cyrsasl_plain.erl b/src/cyrsasl_plain.erl index d2fb373e420..9ce8cda376d 100644 --- a/src/cyrsasl_plain.erl +++ b/src/cyrsasl_plain.erl @@ -45,7 +45,7 @@ mech_new(_Host, _GetPassword, CheckPassword, _CheckPasswordDigest) -> mech_step(State, ClientIn) -> case prepare(ClientIn) of [AuthzId, User, Password] -> - case (State#state.check_password)(User, Password) of + case (State#state.check_password)(User, AuthzId, Password) of {true, AuthModule} -> {ok, [{username, User}, {authzid, AuthzId}, @@ -60,12 +60,17 @@ prepare(ClientIn) -> [<<"">>, UserMaybeDomain, Password] -> case parse_domain(UserMaybeDomain) of %% login@domainpwd - [User, _Domain] -> [UserMaybeDomain, User, Password]; + [User, _Domain] -> [User, User, Password]; %% loginpwd [User] -> [<<"">>, User, Password] end; - %% login@domainloginpwd - [AuthzId, User, Password] -> [AuthzId, User, Password]; + [AuthzId, User, Password] -> + case parse_domain(AuthzId) of + %% login@domainloginpwd + [AuthzUser, _Domain] -> [AuthzUser, User, Password]; + %% loginloginpwd + [AuthzUser] -> [AuthzUser, User, Password] + end; _ -> error end. diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 34d4a52b282..1968044891d 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -30,9 +30,9 @@ -author('alexey@process-one.net'). %% External exports --export([start/0, set_password/3, check_password/3, - check_password/5, check_password_with_authmodule/3, - check_password_with_authmodule/5, try_register/3, +-export([start/0, set_password/3, check_password/4, + check_password/6, check_password_with_authmodule/4, + check_password_with_authmodule/6, try_register/3, dirty_get_registered_users/0, get_vh_registered_users/1, get_vh_registered_users/2, export/1, import/1, get_vh_registered_users_number/1, import/3, @@ -61,8 +61,8 @@ -callback remove_user(binary(), binary()) -> any(). -callback remove_user(binary(), binary(), binary()) -> any(). -callback is_user_exists(binary(), binary()) -> boolean() | {error, atom()}. --callback check_password(binary(), binary(), binary()) -> boolean(). --callback check_password(binary(), binary(), binary(), binary(), +-callback check_password(binary(), binary(), binary(), binary()) -> boolean(). +-callback check_password(binary(), binary(), binary(), binary(), binary(), fun((binary()) -> binary())) -> boolean(). -callback try_register(binary(), binary(), binary()) -> {atomic, atom()} | {error, atom()}. @@ -72,7 +72,7 @@ -callback get_vh_registered_users_number(binary()) -> number(). -callback get_vh_registered_users_number(binary(), opts()) -> number(). -callback get_password(binary(), binary()) -> false | binary(). --callback get_password_s(binary(), binary()) -> binary(). +-callback get_password_s(binary(), binary()) -> binary(). start() -> %% This is only executed by ejabberd_c2s for non-SASL auth client @@ -100,10 +100,10 @@ store_type(Server) -> end, plain, auth_modules(Server)). --spec check_password(binary(), binary(), binary()) -> boolean(). +-spec check_password(binary(), binary(), binary(), binary()) -> boolean(). -check_password(User, Server, Password) -> - case check_password_with_authmodule(User, Server, +check_password(User, AuthzId, Server, Password) -> + case check_password_with_authmodule(User, AuthzId, Server, Password) of {true, _AuthModule} -> true; @@ -111,15 +111,15 @@ check_password(User, Server, Password) -> end. %% @doc Check if the user and password can login in server. -%% @spec (User::string(), Server::string(), Password::string(), +%% @spec (User::string(), AuthzId::string(), Server::string(), Password::string(), %% Digest::string(), DigestGen::function()) -> %% true | false --spec check_password(binary(), binary(), binary(), binary(), +-spec check_password(binary(), binary(), binary(), binary(), binary(), fun((binary()) -> binary())) -> boolean(). - -check_password(User, Server, Password, Digest, + +check_password(User, AuthzId, Server, Password, Digest, DigestGen) -> - case check_password_with_authmodule(User, Server, + case check_password_with_authmodule(User, AuthzId, Server, Password, Digest, DigestGen) of {true, _AuthModule} -> true; @@ -130,28 +130,28 @@ check_password(User, Server, Password, Digest, %% 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()) -> +%% @spec (User::string(), AuthzId::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 --spec check_password_with_authmodule(binary(), binary(), binary()) -> false | +%% | ejabberd_auth_odbc | ejabberd_auth_pam | ejabberd_auth_riak +-spec check_password_with_authmodule(binary(), binary(), binary(), binary()) -> false | {true, atom()}. -check_password_with_authmodule(User, Server, +check_password_with_authmodule(User, AuthzId, Server, Password) -> check_password_loop(auth_modules(Server), - [User, Server, Password]). + [User, AuthzId, Server, Password]). --spec check_password_with_authmodule(binary(), binary(), binary(), binary(), +-spec check_password_with_authmodule(binary(), binary(), binary(), binary(), binary(), fun((binary()) -> binary())) -> false | {true, atom()}. -check_password_with_authmodule(User, Server, Password, +check_password_with_authmodule(User, AuthzId, Server, Password, Digest, DigestGen) -> check_password_loop(auth_modules(Server), - [User, Server, Password, Digest, DigestGen]). + [User, AuthzId, Server, Password, Digest, DigestGen]). check_password_loop([], _Args) -> false; check_password_loop([AuthModule | AuthModules], Args) -> diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl index cb320dea557..05f790db675 100644 --- a/src/ejabberd_auth_anonymous.erl +++ b/src/ejabberd_auth_anonymous.erl @@ -38,8 +38,8 @@ %% Function used by ejabberd_auth: --export([login/2, set_password/3, check_password/3, - check_password/5, try_register/3, +-export([login/2, set_password/3, check_password/4, + check_password/6, try_register/3, dirty_get_registered_users/0, get_vh_registered_users/1, get_vh_registered_users/2, get_vh_registered_users_number/1, get_vh_registered_users_number/2, get_password_s/2, @@ -174,11 +174,11 @@ purge_hook(true, LUser, LServer) -> %% When anonymous login is enabled, check the password for permenant users %% before allowing access -check_password(User, Server, Password) -> - check_password(User, Server, Password, undefined, +check_password(User, AuthzId, Server, Password) -> + check_password(User, AuthzId, Server, Password, undefined, undefined). -check_password(User, Server, _Password, _Digest, +check_password(User, _AuthzId, Server, _Password, _Digest, _DigestGen) -> case ejabberd_auth:is_user_exists_in_other_modules(?MODULE, diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl index 0aa825f73bf..44c931cbf09 100644 --- a/src/ejabberd_auth_external.erl +++ b/src/ejabberd_auth_external.erl @@ -30,8 +30,8 @@ -behaviour(ejabberd_auth). %% External exports --export([start/1, set_password/3, check_password/3, - check_password/5, try_register/3, +-export([start/1, set_password/3, check_password/4, + check_password/6, try_register/3, dirty_get_registered_users/0, get_vh_registered_users/1, get_vh_registered_users/2, get_vh_registered_users_number/1, @@ -75,16 +75,20 @@ plain_password_required() -> true. store_type() -> external. -check_password(User, Server, Password) -> - case get_cache_option(Server) of - false -> check_password_extauth(User, Server, Password); - {true, CacheTime} -> - check_password_cache(User, Server, Password, CacheTime) +check_password(User, AuthzId, Server, Password) -> + if AuthzId /= <<>> andalso AuthzId /= User -> + false; + true -> + case get_cache_option(Server) of + false -> check_password_extauth(User, AuthzId, Server, Password); + {true, CacheTime} -> + check_password_cache(User, AuthzId, Server, Password, CacheTime) + end end. -check_password(User, Server, Password, _Digest, +check_password(User, AuthzId, Server, Password, _Digest, _DigestGen) -> - check_password(User, Server, Password). + check_password(User, AuthzId, Server, Password). set_password(User, Server, Password) -> case extauth:set_password(User, Server, Password) of @@ -177,8 +181,8 @@ get_cache_option(Host) -> CacheTime -> {true, CacheTime} end. -%% @spec (User, Server, Password) -> true | false -check_password_extauth(User, Server, Password) -> +%% @spec (User, AuthzId, Server, Password) -> true | false +check_password_extauth(User, _AuthzId, Server, Password) -> extauth:check_password(User, Server, Password) andalso Password /= <<"">>. @@ -186,35 +190,35 @@ check_password_extauth(User, Server, Password) -> try_register_extauth(User, Server, Password) -> extauth:try_register(User, Server, Password). -check_password_cache(User, Server, Password, 0) -> - check_password_external_cache(User, Server, Password); -check_password_cache(User, Server, Password, +check_password_cache(User, AuthzId, Server, Password, 0) -> + check_password_external_cache(User, AuthzId, Server, Password); +check_password_cache(User, AuthzId, Server, Password, CacheTime) -> case get_last_access(User, Server) of online -> - check_password_internal(User, Server, Password); + check_password_internal(User, AuthzId, Server, Password); never -> - check_password_external_cache(User, Server, Password); + check_password_external_cache(User, AuthzId, Server, Password); mod_last_required -> ?ERROR_MSG("extauth is used, extauth_cache is enabled " "but mod_last is not enabled in that " "host", []), - check_password_external_cache(User, Server, Password); + check_password_external_cache(User, AuthzId, Server, Password); TimeStamp -> case is_fresh_enough(TimeStamp, CacheTime) of %% If no need to refresh, check password against Mnesia true -> - case check_password_internal(User, Server, Password) of + case check_password_internal(User, AuthzId, Server, Password) of %% If password valid in Mnesia, accept it true -> true; %% Else (password nonvalid in Mnesia), check in extauth and cache result false -> - check_password_external_cache(User, Server, Password) + check_password_external_cache(User, AuthzId, Server, Password) end; %% Else (need to refresh), check in extauth and cache result false -> - check_password_external_cache(User, Server, Password) + check_password_external_cache(User, AuthzId, Server, Password) end end. @@ -240,8 +244,8 @@ get_password_cache(User, Server, CacheTime) -> end. %% Check the password using extauth; if success then cache it -check_password_external_cache(User, Server, Password) -> - case check_password_extauth(User, Server, Password) of +check_password_external_cache(User, AuthzId, Server, Password) -> + case check_password_extauth(User, AuthzId, Server, Password) of true -> set_password_internal(User, Server, Password), true; false -> false @@ -255,9 +259,9 @@ try_register_external_cache(User, Server, Password) -> _ -> {error, not_allowed} end. -%% @spec (User, Server, Password) -> true | false -check_password_internal(User, Server, Password) -> - ejabberd_auth_internal:check_password(User, Server, +%% @spec (User, AuthzId, Server, Password) -> true | false +check_password_internal(User, AuthzId, Server, Password) -> + ejabberd_auth_internal:check_password(User, AuthzId, Server, Password). %% @spec (User, Server, Password) -> ok | {error, invalid_jid} diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl index 415c217139e..7c627b18254 100644 --- a/src/ejabberd_auth_internal.erl +++ b/src/ejabberd_auth_internal.erl @@ -30,8 +30,8 @@ -behaviour(ejabberd_auth). %% External exports --export([start/1, set_password/3, check_password/3, - check_password/5, try_register/3, +-export([start/1, set_password/3, check_password/4, + check_password/6, try_register/3, dirty_get_registered_users/0, get_vh_registered_users/1, get_vh_registered_users/2, get_vh_registered_users_number/1, @@ -85,45 +85,53 @@ store_type() -> true -> scram %% allows: PLAIN SCRAM end. -check_password(User, Server, Password) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - US = {LUser, LServer}, - case catch mnesia:dirty_read({passwd, US}) of - [#passwd{password = Password}] - when is_binary(Password) -> - Password /= <<"">>; - [#passwd{password = Scram}] - when is_record(Scram, scram) -> - is_password_scram_valid(Password, Scram); - _ -> false +check_password(User, AuthzId, Server, Password) -> + if AuthzId /= <<>> andalso AuthzId /= User -> + false; + true -> + LUser = jlib:nodeprep(User), + LServer = jlib:nameprep(Server), + US = {LUser, LServer}, + case catch mnesia:dirty_read({passwd, US}) of + [#passwd{password = Password}] + when is_binary(Password) -> + Password /= <<"">>; + [#passwd{password = Scram}] + when is_record(Scram, scram) -> + is_password_scram_valid(Password, Scram); + _ -> false + end end. -check_password(User, Server, Password, Digest, +check_password(User, AuthzId, Server, Password, Digest, DigestGen) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - US = {LUser, LServer}, - case catch mnesia:dirty_read({passwd, US}) of - [#passwd{password = Passwd}] when is_binary(Passwd) -> - DigRes = if Digest /= <<"">> -> - Digest == DigestGen(Passwd); - true -> false - end, - if DigRes -> true; - true -> (Passwd == Password) and (Password /= <<"">>) - end; - [#passwd{password = Scram}] - when is_record(Scram, scram) -> - Passwd = jlib:decode_base64(Scram#scram.storedkey), - DigRes = if Digest /= <<"">> -> - Digest == DigestGen(Passwd); - true -> false - end, - if DigRes -> true; - true -> (Passwd == Password) and (Password /= <<"">>) - end; - _ -> false + if AuthzId /= <<>> andalso AuthzId /= User -> + false; + true -> + LUser = jlib:nodeprep(User), + LServer = jlib:nameprep(Server), + US = {LUser, LServer}, + case catch mnesia:dirty_read({passwd, US}) of + [#passwd{password = Passwd}] when is_binary(Passwd) -> + DigRes = if Digest /= <<"">> -> + Digest == DigestGen(Passwd); + true -> false + end, + if DigRes -> true; + true -> (Passwd == Password) and (Password /= <<"">>) + end; + [#passwd{password = Scram}] + when is_record(Scram, scram) -> + Passwd = jlib:decode_base64(Scram#scram.storedkey), + DigRes = if Digest /= <<"">> -> + Digest == DigestGen(Passwd); + true -> false + end, + if DigRes -> true; + true -> (Passwd == Password) and (Password /= <<"">>) + end; + _ -> false + end end. %% @spec (User::string(), Server::string(), Password::string()) -> diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index 3055d1044b5..45964d669b5 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -36,7 +36,7 @@ %% External exports -export([start/1, stop/1, start_link/1, set_password/3, - check_password/3, check_password/5, try_register/3, + check_password/4, check_password/6, try_register/3, dirty_get_registered_users/0, get_vh_registered_users/1, get_vh_registered_users/2, get_vh_registered_users_number/1, @@ -115,19 +115,23 @@ plain_password_required() -> true. store_type() -> external. -check_password(User, Server, Password) -> - if Password == <<"">> -> false; - true -> - case catch check_password_ldap(User, Server, Password) - of - {'EXIT', _} -> false; - Result -> Result - end +check_password(User, AuthzId, Server, Password) -> + if AuthzId /= <<>> andalso AuthzId /= User -> + false; + true -> + if Password == <<"">> -> false; + true -> + case catch check_password_ldap(User, Server, Password) + of + {'EXIT', _} -> false; + Result -> Result + end + end end. -check_password(User, Server, Password, _Digest, +check_password(User, AuthzId, Server, Password, _Digest, _DigestGen) -> - check_password(User, Server, Password). + check_password(User, AuthzId, Server, Password). set_password(User, Server, Password) -> {ok, State} = eldap_utils:get_state(Server, ?MODULE), diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl index 2316ef3bec3..4148baf1385 100644 --- a/src/ejabberd_auth_odbc.erl +++ b/src/ejabberd_auth_odbc.erl @@ -30,8 +30,8 @@ -behaviour(ejabberd_auth). %% External exports --export([start/1, set_password/3, check_password/3, - check_password/5, try_register/3, +-export([start/1, set_password/3, check_password/4, + check_password/6, try_register/3, dirty_get_registered_users/0, get_vh_registered_users/1, get_vh_registered_users/2, get_vh_registered_users_number/1, @@ -63,89 +63,97 @@ store_type() -> true -> scram %% allows: PLAIN SCRAM end. -%% @spec (User, Server, Password) -> true | false | {error, Error} -check_password(User, Server, Password) -> - LServer = jlib:nameprep(Server), - LUser = jlib:nodeprep(User), - if (LUser == error) or (LServer == error) -> - false; - (LUser == <<>>) or (LServer == <<>>) -> - false; - true -> - Username = ejabberd_odbc:escape(LUser), - case is_scrammed() of - true -> - try odbc_queries:get_password_scram(LServer, Username) of - {selected, [<<"password">>, <<"serverkey">>, - <<"salt">>, <<"iterationcount">>], - [[StoredKey, ServerKey, Salt, IterationCount]]} -> - Scram = - #scram{storedkey = StoredKey, - serverkey = ServerKey, - salt = Salt, - iterationcount = binary_to_integer( - IterationCount)}, - is_password_scram_valid(Password, Scram); - {selected, [<<"password">>, <<"serverkey">>, - <<"salt">>, <<"iterationcount">>], []} -> - false; %% Account does not exist - {error, _Error} -> - false %% Typical error is that table doesn't exist - catch - _:_ -> - false %% Typical error is database not accessible - end; - false -> - try odbc_queries:get_password(LServer, Username) of - {selected, [<<"password">>], [[Password]]} -> - Password /= <<"">>; - {selected, [<<"password">>], [[_Password2]]} -> - false; %% Password is not correct - {selected, [<<"password">>], []} -> - false; %% Account does not exist - {error, _Error} -> - false %% Typical error is that table doesn't exist - catch - _:_ -> - false %% Typical error is database not accessible - end - end +%% @spec (User, AuthzId, Server, Password) -> true | false | {error, Error} +check_password(User, AuthzId, Server, Password) -> + if AuthzId /= <<>> andalso AuthzId /= User -> + false; + true -> + LServer = jlib:nameprep(Server), + LUser = jlib:nodeprep(User), + if (LUser == error) or (LServer == error) -> + false; + (LUser == <<>>) or (LServer == <<>>) -> + false; + true -> + Username = ejabberd_odbc:escape(LUser), + case is_scrammed() of + true -> + try odbc_queries:get_password_scram(LServer, Username) of + {selected, [<<"password">>, <<"serverkey">>, + <<"salt">>, <<"iterationcount">>], + [[StoredKey, ServerKey, Salt, IterationCount]]} -> + Scram = + #scram{storedkey = StoredKey, + serverkey = ServerKey, + salt = Salt, + iterationcount = binary_to_integer( + IterationCount)}, + is_password_scram_valid(Password, Scram); + {selected, [<<"password">>, <<"serverkey">>, + <<"salt">>, <<"iterationcount">>], []} -> + false; %% Account does not exist + {error, _Error} -> + false %% Typical error is that table doesn't exist + catch + _:_ -> + false %% Typical error is database not accessible + end; + false -> + try odbc_queries:get_password(LServer, Username) of + {selected, [<<"password">>], [[Password]]} -> + Password /= <<"">>; + {selected, [<<"password">>], [[_Password2]]} -> + false; %% Password is not correct + {selected, [<<"password">>], []} -> + false; %% Account does not exist + {error, _Error} -> + false %% Typical error is that table doesn't exist + catch + _:_ -> + false %% Typical error is database not accessible + end + end + end end. -%% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error} -check_password(User, Server, Password, Digest, +%% @spec (User, AuthzId, Server, Password, Digest, DigestGen) -> true | false | {error, Error} +check_password(User, AuthzId, Server, Password, Digest, DigestGen) -> - LServer = jlib:nameprep(Server), - LUser = jlib:nodeprep(User), - if (LUser == error) or (LServer == error) -> - false; - (LUser == <<>>) or (LServer == <<>>) -> - false; - true -> - case is_scrammed() of - false -> - Username = ejabberd_odbc:escape(LUser), - try odbc_queries:get_password(LServer, Username) of - %% Account exists, check if password is valid - {selected, [<<"password">>], [[Passwd]]} -> - DigRes = if Digest /= <<"">> -> - Digest == DigestGen(Passwd); - true -> false - end, - if DigRes -> true; - true -> (Passwd == Password) and (Password /= <<"">>) - end; - {selected, [<<"password">>], []} -> - false; %% Account does not exist - {error, _Error} -> - false %% Typical error is that table doesn't exist - catch - _:_ -> - false %% Typical error is database not accessible - end; - true -> - false - end + if AuthzId /= <<>> andalso AuthzId /= User -> + false; + true -> + LServer = jlib:nameprep(Server), + LUser = jlib:nodeprep(User), + if (LUser == error) or (LServer == error) -> + false; + (LUser == <<>>) or (LServer == <<>>) -> + false; + true -> + case is_scrammed() of + false -> + Username = ejabberd_odbc:escape(LUser), + try odbc_queries:get_password(LServer, Username) of + %% Account exists, check if password is valid + {selected, [<<"password">>], [[Passwd]]} -> + DigRes = if Digest /= <<"">> -> + Digest == DigestGen(Passwd); + true -> false + end, + if DigRes -> true; + true -> (Passwd == Password) and (Password /= <<"">>) + end; + {selected, [<<"password">>], []} -> + false; %% Account does not exist + {error, _Error} -> + false %% Typical error is that table doesn't exist + catch + _:_ -> + false %% Typical error is database not accessible + end; + true -> + false + end + end end. %% @spec (User::string(), Server::string(), Password::string()) -> @@ -352,7 +360,7 @@ remove_user(User, Server, Password) -> true -> case is_scrammed() of true -> - case check_password(User, Server, Password) of + case check_password(User, <<"">>, Server, Password) of true -> remove_user(User, Server), ok; diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl index f3fdf628dd4..da893eb4674 100644 --- a/src/ejabberd_auth_pam.erl +++ b/src/ejabberd_auth_pam.erl @@ -32,8 +32,8 @@ %%==================================================================== %% API %%==================================================================== --export([start/1, set_password/3, check_password/3, - check_password/5, try_register/3, +-export([start/1, set_password/3, check_password/4, + check_password/6, try_register/3, dirty_get_registered_users/0, get_vh_registered_users/1, get_vh_registered_users/2, get_vh_registered_users_number/1, get_vh_registered_users_number/2, @@ -47,21 +47,25 @@ start(_Host) -> set_password(_User, _Server, _Password) -> {error, not_allowed}. -check_password(User, Server, Password, _Digest, +check_password(User, AuthzId, Server, Password, _Digest, _DigestGen) -> - check_password(User, Server, Password). - -check_password(User, Host, Password) -> - Service = get_pam_service(Host), - UserInfo = case get_pam_userinfotype(Host) of - username -> User; - jid -> <> - end, - case catch epam:authenticate(Service, UserInfo, - Password) - of - true -> true; - _ -> false + check_password(User, AuthzId, Server, Password). + +check_password(User, AuthzId, Host, Password) -> + if AuthzId /= <<>> andalso AuthzId /= User -> + false; + true -> + Service = get_pam_service(Host), + UserInfo = case get_pam_userinfotype(Host) of + username -> User; + jid -> <> + end, + case catch epam:authenticate(Service, UserInfo, + Password) + of + true -> true; + _ -> false + end end. try_register(_User, _Server, _Password) -> diff --git a/src/ejabberd_auth_riak.erl b/src/ejabberd_auth_riak.erl index fb9be2c3e83..496fbb82fcb 100644 --- a/src/ejabberd_auth_riak.erl +++ b/src/ejabberd_auth_riak.erl @@ -30,8 +30,8 @@ -behaviour(ejabberd_auth). %% External exports --export([start/1, set_password/3, check_password/3, - check_password/5, try_register/3, +-export([start/1, set_password/3, check_password/4, + check_password/6, try_register/3, dirty_get_registered_users/0, get_vh_registered_users/1, get_vh_registered_users/2, get_vh_registered_users_number/1, @@ -66,42 +66,50 @@ store_type() -> passwd_schema() -> {record_info(fields, passwd), #passwd{}}. -check_password(User, Server, Password) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of - {ok, #passwd{password = Password}} when is_binary(Password) -> - Password /= <<"">>; - {ok, #passwd{password = Scram}} when is_record(Scram, scram) -> - is_password_scram_valid(Password, Scram); - _ -> - false +check_password(User, AuthzId, Server, Password) -> + if AuthzId /= <<>> andalso AuthzId /= User -> + false; + true -> + LUser = jlib:nodeprep(User), + LServer = jlib:nameprep(Server), + case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of + {ok, #passwd{password = Password}} when is_binary(Password) -> + Password /= <<"">>; + {ok, #passwd{password = Scram}} when is_record(Scram, scram) -> + is_password_scram_valid(Password, Scram); + _ -> + false + end end. -check_password(User, Server, Password, Digest, +check_password(User, AuthzId, Server, Password, Digest, DigestGen) -> - LUser = jlib:nodeprep(User), - LServer = jlib:nameprep(Server), - case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of - {ok, #passwd{password = Passwd}} when is_binary(Passwd) -> - DigRes = if Digest /= <<"">> -> - Digest == DigestGen(Passwd); - true -> false - end, - if DigRes -> true; - true -> (Passwd == Password) and (Password /= <<"">>) - end; - {ok, #passwd{password = Scram}} - when is_record(Scram, scram) -> - Passwd = jlib:decode_base64(Scram#scram.storedkey), - DigRes = if Digest /= <<"">> -> - Digest == DigestGen(Passwd); - true -> false - end, - if DigRes -> true; - true -> (Passwd == Password) and (Password /= <<"">>) - end; - _ -> false + if AuthzId /= <<>> andalso AuthzId /= User -> + false; + true -> + LUser = jlib:nodeprep(User), + LServer = jlib:nameprep(Server), + case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of + {ok, #passwd{password = Passwd}} when is_binary(Passwd) -> + DigRes = if Digest /= <<"">> -> + Digest == DigestGen(Passwd); + true -> false + end, + if DigRes -> true; + true -> (Passwd == Password) and (Password /= <<"">>) + end; + {ok, #passwd{password = Scram}} + when is_record(Scram, scram) -> + Passwd = jlib:decode_base64(Scram#scram.storedkey), + DigRes = if Digest /= <<"">> -> + Digest == DigestGen(Passwd); + true -> false + end, + if DigRes -> true; + true -> (Passwd == Password) and (Password /= <<"">>) + end; + _ -> false + end end. set_password(User, Server, Password) -> diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 7632cb121f3..920d1b4abbb 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -393,17 +393,17 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> SASLState = cyrsasl:server_new( <<"jabber">>, Server, <<"">>, [], - fun(U) -> + fun(U, _AuthzId) -> ejabberd_auth:get_password_with_authmodule( U, Server) end, - fun(U, P) -> + fun(U, AuthzId, P) -> ejabberd_auth:check_password_with_authmodule( - U, Server, P) + U, AuthzId, Server, P) end, - fun(U, P, D, DG) -> + fun(U, AuthzId, P, D, DG) -> ejabberd_auth:check_password_with_authmodule( - U, Server, P, D, DG) + U, AuthzId, Server, P, D, DG) end), Mechs = case TLSEnabled or not TLSRequired of @@ -753,9 +753,7 @@ wait_for_feature_request({xmlstreamelement, El}, of {ok, Props} -> (StateData#state.sockmod):reset_stream(StateData#state.socket), - %U = xml:get_attr_s(username, Props), - U = proplists:get_value(username, Props, <<>>), - %AuthModule = xml:get_attr_s(auth_module, Props), + U = identity(Props), AuthModule = proplists:get_value(auth_module, Props, undefined), ?INFO_MSG("(~w) Accepted authentication for ~s " "by ~p from ~s", @@ -906,9 +904,7 @@ wait_for_sasl_response({xmlstreamelement, El}, {ok, Props} -> catch (StateData#state.sockmod):reset_stream(StateData#state.socket), -% U = xml:get_attr_s(username, Props), - U = proplists:get_value(username, Props, <<>>), -% AuthModule = xml:get_attr_s(auth_module, Props), + U = identity(Props), AuthModule = proplists:get_value(auth_module, Props, <<>>), ?INFO_MSG("(~w) Accepted authentication for ~s " "by ~p from ~s", @@ -929,9 +925,7 @@ wait_for_sasl_response({xmlstreamelement, El}, user = U}); {ok, Props, ServerOut} -> (StateData#state.sockmod):reset_stream(StateData#state.socket), -% U = xml:get_attr_s(username, Props), - U = proplists:get_value(username, Props, <<>>), -% AuthModule = xml:get_attr_s(auth_module, Props), + U = identity(Props), AuthModule = proplists:get_value(auth_module, Props, undefined), ?INFO_MSG("(~w) Accepted authentication for ~s " "by ~p from ~s", @@ -3140,3 +3134,9 @@ pack_string(String, Pack) -> transform_listen_option(Opt, Opts) -> [Opt|Opts]. + +identity(Props) -> + case proplists:get_value(authzid, Props, <<>>) of + <<>> -> proplists:get_value(username, Props, <<>>); + AuthzId -> AuthzId + end. diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl index c279f2d0f6f..d8b5b34ff16 100644 --- a/src/ejabberd_commands.erl +++ b/src/ejabberd_commands.erl @@ -391,7 +391,7 @@ check_auth(noauth) -> no_auth_provided; check_auth({User, Server, Password}) -> %% Check the account exists and password is valid - case ejabberd_auth:check_password(User, Server, Password) of + case ejabberd_auth:check_password(User, <<"">>, Server, Password) of true -> {ok, User, Server}; _ -> throw({error, invalid_account_data}) end. diff --git a/src/ejabberd_web_admin.erl b/src/ejabberd_web_admin.erl index 65bac357f49..f573f264fcf 100644 --- a/src/ejabberd_web_admin.erl +++ b/src/ejabberd_web_admin.erl @@ -263,7 +263,7 @@ get_auth_admin(Auth, HostHTTP, RPath, Method) -> get_auth_account(HostOfRule, AccessRule, User, Server, Pass) -> - case ejabberd_auth:check_password(User, Server, Pass) of + case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of true -> case is_acl_match(HostOfRule, AccessRule, jlib:make_jid(User, Server, <<"">>)) From ec6ea22d9cd96b9d19e77a92e550043b5be196bb Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Thu, 9 Apr 2015 00:09:12 -0300 Subject: [PATCH 2/2] MojoAuth module Permits use of MojoAuth (http://mojoauth.mojolingo.com/) in ejabberd. MojoAuth is a set of standard approaches to cross-app authentication based on HMAC which is specified in RFC2104. --- rebar.config.script | 1 + src/ejabberd_auth.erl | 2 +- src/ejabberd_auth_mojoauth.erl | 99 ++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/ejabberd_auth_mojoauth.erl diff --git a/rebar.config.script b/rebar.config.script index 69964852bb1..19d7668ce51 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -59,6 +59,7 @@ Deps = [{p1_cache_tab, ".*", {git, "git://github.com/processone/cache_tab"}}, {p1_stun, ".*", {git, "git://github.com/processone/stun"}}, {p1_yaml, ".*", {git, "git://github.com/processone/p1_yaml"}}, {ehyperloglog, ".*", {git, "https://github.com/vaxelfel/eHyperLogLog.git"}}, + {mojoauth, ".*", {git, "https://github.com/mojolingo/mojoauth.erl.git"}}, {p1_utils, ".*", {git, "git://github.com/processone/p1_utils"}}], ConfigureCmd = fun(Pkg, Flags) -> diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 1968044891d..ec87d75982d 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -134,7 +134,7 @@ check_password(User, AuthzId, Server, Password, Digest, %% {true, AuthModule} | false %% where %% AuthModule = ejabberd_auth_anonymous | ejabberd_auth_external -%% | ejabberd_auth_internal | ejabberd_auth_ldap +%% | ejabberd_auth_internal | ejabberd_auth_ldap | ejabberd_auth_mojoauth %% | ejabberd_auth_odbc | ejabberd_auth_pam | ejabberd_auth_riak -spec check_password_with_authmodule(binary(), binary(), binary(), binary()) -> false | {true, atom()}. diff --git a/src/ejabberd_auth_mojoauth.erl b/src/ejabberd_auth_mojoauth.erl new file mode 100644 index 00000000000..126adce97d4 --- /dev/null +++ b/src/ejabberd_auth_mojoauth.erl @@ -0,0 +1,99 @@ +%%%---------------------------------------------------------------------- +%%% File : ejabberd_auth_mojoauth.erl +%%% Author : Ben Langfeld +%%% Purpose : Authentication via MojoAuth (http://mojoauth.mojolingo.com/) +%%% Created : 18 February 2015 by Ben Langfeld +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2015 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- + +-module(ejabberd_auth_mojoauth). + +-author('ben@langfeld.me'). + +-behaviour(ejabberd_auth). + +%% External exports +-export([start/1, set_password/3, check_password/4, + check_password/6, try_register/3, + dirty_get_registered_users/0, get_vh_registered_users/1, + get_vh_registered_users/2, + get_vh_registered_users_number/1, + get_vh_registered_users_number/2, get_password/2, + get_password_s/2, is_user_exists/2, remove_user/2, + remove_user/3, store_type/0, + plain_password_required/0]). + +-include("ejabberd.hrl"). +-include("logger.hrl"). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start(Host) -> + ejabberd_auth_internal:start(Host). + +plain_password_required() -> true. + +store_type() -> external. + +secret(Server) -> + LServer = jlib:nameprep(Server), + ejabberd_config:get_option( + {mojoauth_secret, LServer}, + fun(V) -> iolist_to_binary(V) end, + "mojoauth"). + +check_password(User, AuthzId, Server, Password) -> + case mojoauth:test_credentials([{username, User}, {password, Password}], secret(Server)) of + {ok, AuthzId} -> true; + _ -> false + end. + +check_password(User, AuthzId, Server, Password, _Digest, _DigestGen) -> + check_password(User, AuthzId, Server, Password). + +set_password(_User, _Server, _Password) -> {error, not_allowed}. + +try_register(_User, _Server, _Password) -> {error, not_allowed}. + +dirty_get_registered_users() -> + ejabberd_auth_internal:dirty_get_registered_users(). + +get_vh_registered_users(Server) -> + ejabberd_auth_internal:get_vh_registered_users(Server). + +get_vh_registered_users(Server, Data) -> + ejabberd_auth_internal:get_vh_registered_users(Server, Data). + +get_vh_registered_users_number(Server) -> + ejabberd_auth_internal:get_vh_registered_users_number(Server). + +get_vh_registered_users_number(Server, Data) -> + ejabberd_auth_internal:get_vh_registered_users_number(Server, Data). + +get_password(_User, _Server) -> false. + +get_password_s(_User, _Server) -> <<"">>. + +is_user_exists(_User, _Server) -> true. + +remove_user(_User, _Server) -> false. + +remove_user(_User, _Server, _Password) -> false.