diff options
Diffstat (limited to 'src/ejabberd_auth_sql.erl')
-rw-r--r-- | src/ejabberd_auth_sql.erl | 637 |
1 files changed, 247 insertions, 390 deletions
diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl index d6d945e02..106c83b47 100644 --- a/src/ejabberd_auth_sql.erl +++ b/src/ejabberd_auth_sql.erl @@ -1,11 +1,11 @@ %%%---------------------------------------------------------------------- %%% File : ejabberd_auth_sql.erl %%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : Authentification via ODBC +%%% Purpose : Authentication via ODBC %%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net> %%% %%% -%%% ejabberd, Copyright (C) 2002-2016 ProcessOne +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as @@ -25,27 +25,20 @@ -module(ejabberd_auth_sql). --compile([{parse_transform, ejabberd_sql_pt}]). - --behaviour(ejabberd_config). -author('alexey@process-one.net'). -behaviour(ejabberd_auth). --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, - convert_to_scram/1, opt_type/1]). +-export([start/1, stop/1, set_password/3, try_register/3, + get_users/2, count_users/2, get_password/2, + remove_user/2, store_type/1, plain_password_required/1, + convert_to_scram/1, export/1, which_users_exists/2]). --include("ejabberd.hrl"). +-include("scram.hrl"). -include("logger.hrl"). -include("ejabberd_sql_pt.hrl"). +-include("ejabberd_auth.hrl"). -define(SALT_LENGTH, 16). @@ -54,399 +47,229 @@ %%%---------------------------------------------------------------------- start(_Host) -> ok. -plain_password_required() -> - case is_scrammed() of - false -> false; - true -> true - end. +stop(_Host) -> ok. -store_type() -> - case is_scrammed() of - false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM - true -> scram %% allows: PLAIN SCRAM - end. +plain_password_required(Server) -> + store_type(Server) == scram. -%% @spec (User, AuthzId, Server, Password) -> true | false | {error, Error} -check_password(User, AuthzId, Server, Password) -> - if AuthzId /= <<>> andalso AuthzId /= User -> - false; - true -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - false; - (LUser == <<>>) or (LServer == <<>>) -> - false; - true -> - case is_scrammed() of - true -> - try sql_queries:get_password_scram(LServer, LUser) of - {selected, - [{StoredKey, ServerKey, Salt, IterationCount}]} -> - Scram = - #scram{storedkey = StoredKey, - serverkey = ServerKey, - salt = Salt, - iterationcount = IterationCount}, - is_password_scram_valid_stored(Password, Scram, LUser, LServer); - {selected, []} -> - 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 sql_queries:get_password(LServer, LUser) of - {selected, [{Password}]} -> - Password /= <<"">>; - {selected, [{_Password2}]} -> - false; %% Password is not correct - {selected, []} -> - 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. +store_type(Server) -> + ejabberd_auth:password_format(Server). -%% @spec (User, AuthzId, Server, Password, Digest, DigestGen) -> true | false | {error, Error} -check_password(User, AuthzId, Server, Password, Digest, - DigestGen) -> - if AuthzId /= <<>> andalso AuthzId /= User -> - false; - true -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - false; - (LUser == <<>>) or (LServer == <<>>) -> - false; - true -> - case is_scrammed() of - false -> - try sql_queries:get_password(LServer, LUser) of - %% Account exists, check if password is valid - {selected, [{Passwd}]} -> - DigRes = if Digest /= <<"">> -> - Digest == DigestGen(Passwd); - true -> false - end, - if DigRes -> true; - true -> (Passwd == Password) and (Password /= <<"">>) - end; - {selected, []} -> - 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()) -> -%% ok | {error, invalid_jid} set_password(User, Server, Password) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - {error, invalid_jid}; - (LUser == <<>>) or (LServer == <<>>) -> - {error, invalid_jid}; - true -> - case is_scrammed() of - true -> - Scram = password_to_scram(Password), - case catch sql_queries:set_password_scram_t( - LServer, - LUser, - Scram#scram.storedkey, - Scram#scram.serverkey, - Scram#scram.salt, - Scram#scram.iterationcount - ) - of - {atomic, ok} -> ok; - Other -> {error, Other} - end; - false -> - case catch sql_queries:set_password_t(LServer, - LUser, Password) - of - {atomic, ok} -> ok; - Other -> {error, Other} - end - end + F = fun() -> + if is_record(Password, scram) -> + set_password_scram_t( + User, Server, + Password#scram.storedkey, Password#scram.serverkey, + Password#scram.salt, Password#scram.iterationcount); + true -> + set_password_t(User, Server, Password) + end + end, + case ejabberd_sql:sql_transaction(Server, F) of + {atomic, _} -> + {cache, {ok, Password}}; + {aborted, _} -> + {nocache, {error, db_failure}} end. -%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} try_register(User, Server, Password) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - {error, invalid_jid}; - (LUser == <<>>) or (LServer == <<>>) -> - {error, invalid_jid}; - true -> - case is_scrammed() of - true -> - Scram = password_to_scram(Password), - case catch sql_queries:add_user_scram( - LServer, - LUser, - Scram#scram.storedkey, - Scram#scram.serverkey, - Scram#scram.salt, - Scram#scram.iterationcount - ) of - {updated, 1} -> {atomic, ok}; - _ -> {atomic, exists} - end; - false -> - case catch sql_queries:add_user(LServer, LUser, - Password) of - {updated, 1} -> {atomic, ok}; - _ -> {atomic, exists} - end - end + Res = if is_record(Password, scram) -> + add_user_scram( + Server, User, + Password#scram.storedkey, Password#scram.serverkey, + Password#scram.salt, Password#scram.iterationcount); + true -> + add_user(Server, User, Password) + end, + case Res of + {updated, 1} -> {cache, {ok, Password}}; + _ -> {nocache, {error, exists}} end. -dirty_get_registered_users() -> - Servers = ejabberd_config:get_vh_by_auth_method(sql), - lists:flatmap(fun (Server) -> - get_vh_registered_users(Server) - end, - Servers). - -get_vh_registered_users(Server) -> - case jid:nameprep(Server) of - error -> []; - <<>> -> []; - LServer -> - case catch sql_queries:list_users(LServer) of - {selected, Res} -> - [{U, LServer} || {U} <- Res]; - _ -> [] - end - end. - -get_vh_registered_users(Server, Opts) -> - case jid:nameprep(Server) of - error -> []; - <<>> -> []; - LServer -> - case catch sql_queries:list_users(LServer, Opts) of - {selected, Res} -> - [{U, LServer} || {U} <- Res]; - _ -> [] - end - end. - -get_vh_registered_users_number(Server) -> - case jid:nameprep(Server) of - error -> 0; - <<>> -> 0; - LServer -> - case catch sql_queries:users_number(LServer) of - {selected, [{Res}]} -> - Res; - _ -> 0 - end +get_users(Server, Opts) -> + case list_users(Server, Opts) of + {selected, Res} -> + [{U, Server} || {U} <- Res]; + _ -> [] end. -get_vh_registered_users_number(Server, Opts) -> - case jid:nameprep(Server) of - error -> 0; - <<>> -> 0; - LServer -> - case catch sql_queries:users_number(LServer, Opts) of - {selected, [{Res}]} -> - Res; - _Other -> 0 - end +count_users(Server, Opts) -> + case users_number(Server, Opts) of + {selected, [{Res}]} -> + Res; + _Other -> 0 end. get_password(User, Server) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - false; - (LUser == <<>>) or (LServer == <<>>) -> - false; - true -> - case is_scrammed() of - true -> - case catch sql_queries:get_password_scram( - LServer, LUser) of - {selected, - [{StoredKey, ServerKey, Salt, IterationCount}]} -> - {jlib:decode_base64(StoredKey), - jlib:decode_base64(ServerKey), - jlib:decode_base64(Salt), - IterationCount}; - _ -> false - end; - false -> - case catch sql_queries:get_password(LServer, LUser) - of - {selected, [{Password}]} -> Password; - _ -> false - end - end - end. - -get_password_s(User, Server) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - <<"">>; - (LUser == <<>>) or (LServer == <<>>) -> - <<"">>; - true -> - case is_scrammed() of - false -> - case catch sql_queries:get_password(LServer, LUser) of - {selected, [{Password}]} -> Password; - _ -> <<"">> - end; - true -> <<"">> - end + case get_password_scram(Server, User) of + {selected, [{Password, <<>>, <<>>, 0}]} -> + {cache, {ok, Password}}; + {selected, [{StoredKey, ServerKey, Salt, IterationCount}]} -> + {cache, {ok, #scram{storedkey = StoredKey, + serverkey = ServerKey, + salt = Salt, + iterationcount = IterationCount}}}; + {selected, []} -> + {cache, error}; + _ -> + {nocache, error} end. -%% @spec (User, Server) -> true | false | {error, Error} -is_user_exists(User, Server) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - false; - (LUser == <<>>) or (LServer == <<>>) -> - false; - true -> - try sql_queries:get_password(LServer, LUser) of - {selected, [{_Password}]} -> - true; %% Account exists - {selected, []} -> - false; %% Account does not exist - {error, Error} -> {error, Error} - catch - _:B -> {error, B} - end - end. - -%% @spec (User, Server) -> ok | error -%% @doc Remove user. -%% Note: it may return ok even if there was some problem removing the user. remove_user(User, Server) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - error; - (LUser == <<>>) or (LServer == <<>>) -> - error; - true -> - catch sql_queries:del_user(LServer, LUser), - ok + case del_user(Server, User) of + {updated, _} -> + ok; + _ -> + {error, db_failure} end. -%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed -%% @doc Remove user if the provided password is correct. -remove_user(User, Server, Password) -> - LServer = jid:nameprep(Server), - LUser = jid:nodeprep(User), - if (LUser == error) or (LServer == error) -> - error; - (LUser == <<>>) or (LServer == <<>>) -> - error; - true -> - case is_scrammed() of - true -> - case check_password(User, <<"">>, Server, Password) of - true -> - remove_user(User, Server), - ok; - false -> not_allowed - end; - false -> - F = fun () -> - Result = sql_queries:del_user_return_password( - LServer, LUser, Password), - case Result of - {selected, [{Password}]} -> ok; - {selected, []} -> not_exists; - _ -> not_allowed - end - end, - {atomic, Result} = sql_queries:sql_transaction( - LServer, F), - Result - end - end. - -%%% -%%% SCRAM -%%% - -is_scrammed() -> - scram == - ejabberd_config:get_option({auth_password_format, ?MYNAME}, - fun(V) -> V end). - -password_to_scram(Password) -> - password_to_scram(Password, - ?SCRAM_DEFAULT_ITERATION_COUNT). - -password_to_scram(Password, IterationCount) -> - Salt = crypto:rand_bytes(?SALT_LENGTH), - SaltedPassword = scram:salted_password(Password, Salt, - IterationCount), - StoredKey = - scram:stored_key(scram:client_key(SaltedPassword)), - ServerKey = scram:server_key(SaltedPassword), - #scram{storedkey = jlib:encode_base64(StoredKey), - serverkey = jlib:encode_base64(ServerKey), - salt = jlib:encode_base64(Salt), - iterationcount = IterationCount}. - -is_password_scram_valid_stored(Pass, {scram,Pass,<<>>,<<>>,0}, LUser, LServer) -> - ?INFO_MSG("Apparently, SQL auth method and scram password formatting are " - "enabled, but the password of user '~s' in the 'users' table is not " - "scrammed. You may want to execute this command: " - "ejabberdctl convert_to_scram ~s", [LUser, LServer]), - false; -is_password_scram_valid_stored(Password, Scram, _, _) -> - is_password_scram_valid(Password, Scram). - -is_password_scram_valid(Password, Scram) -> - IterationCount = Scram#scram.iterationcount, - Salt = jlib:decode_base64(Scram#scram.salt), - SaltedPassword = scram:salted_password(Password, Salt, - IterationCount), - StoredKey = - scram:stored_key(scram:client_key(SaltedPassword)), - jlib:decode_base64(Scram#scram.storedkey) == StoredKey. - -define(BATCH_SIZE, 1000). -set_password_scram_t(LUser, +set_password_scram_t(LUser, LServer, StoredKey, ServerKey, Salt, IterationCount) -> ?SQL_UPSERT_T( "users", ["!username=%(LUser)s", + "!server_host=%(LServer)s", "password=%(StoredKey)s", "serverkey=%(ServerKey)s", "salt=%(Salt)s", "iterationcount=%(IterationCount)d"]). +set_password_t(LUser, LServer, Password) -> + ?SQL_UPSERT_T( + "users", + ["!username=%(LUser)s", + "!server_host=%(LServer)s", + "password=%(Password)s"]). + +get_password_scram(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d" + " from users" + " where username=%(LUser)s and %(LServer)H")). + +add_user_scram(LServer, LUser, + StoredKey, ServerKey, Salt, IterationCount) -> + ejabberd_sql:sql_query( + LServer, + ?SQL_INSERT( + "users", + ["username=%(LUser)s", + "server_host=%(LServer)s", + "password=%(StoredKey)s", + "serverkey=%(ServerKey)s", + "salt=%(Salt)s", + "iterationcount=%(IterationCount)d"])). + +add_user(LServer, LUser, Password) -> + ejabberd_sql:sql_query( + LServer, + ?SQL_INSERT( + "users", + ["username=%(LUser)s", + "server_host=%(LServer)s", + "password=%(Password)s"])). + +del_user(LServer, LUser) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("delete from users where username=%(LUser)s and %(LServer)H")). + +list_users(LServer, []) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(username)s from users where %(LServer)H")); +list_users(LServer, [{from, Start}, {to, End}]) + when is_integer(Start) and is_integer(End) -> + list_users(LServer, + [{limit, End - Start + 1}, {offset, Start - 1}]); +list_users(LServer, + [{prefix, Prefix}, {from, Start}, {to, End}]) + when is_binary(Prefix) and is_integer(Start) and + is_integer(End) -> + list_users(LServer, + [{prefix, Prefix}, {limit, End - Start + 1}, + {offset, Start - 1}]); +list_users(LServer, [{limit, Limit}, {offset, Offset}]) + when is_integer(Limit) and is_integer(Offset) -> + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(username)s from users " + "where %(LServer)H " + "order by username " + "limit %(Limit)d offset %(Offset)d")); +list_users(LServer, + [{prefix, Prefix}, {limit, Limit}, {offset, Offset}]) + when is_binary(Prefix) and is_integer(Limit) and + is_integer(Offset) -> + SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix), + SPrefix2 = <<SPrefix/binary, $%>>, + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(username)s from users " + "where username like %(SPrefix2)s escape '^' and %(LServer)H " + "order by username " + "limit %(Limit)d offset %(Offset)d")). + +users_number(LServer) -> + ejabberd_sql:sql_query( + LServer, + fun(pgsql, _) -> + case + ejabberd_option:pgsql_users_number_estimate(LServer) of + true -> + ejabberd_sql:sql_query_t( + ?SQL("select @(reltuples :: bigint)d from pg_class" + " where oid = 'users'::regclass::oid")); + _ -> + ejabberd_sql:sql_query_t( + ?SQL("select @(count(*))d from users where %(LServer)H")) + end; + (_Type, _) -> + ejabberd_sql:sql_query_t( + ?SQL("select @(count(*))d from users where %(LServer)H")) + end). + +users_number(LServer, [{prefix, Prefix}]) + when is_binary(Prefix) -> + SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix), + SPrefix2 = <<SPrefix/binary, $%>>, + ejabberd_sql:sql_query( + LServer, + ?SQL("select @(count(*))d from users " + "where username like %(SPrefix2)s escape '^' and %(LServer)H")); +users_number(LServer, []) -> + users_number(LServer). + +which_users_exists(LServer, LUsers) when length(LUsers) =< 100 -> + try ejabberd_sql:sql_query( + LServer, + ?SQL("select @(username)s from users where username in %(LUsers)ls")) of + {selected, Matching} -> + [U || {U} <- Matching]; + {error, _} = E -> + E + catch _:B -> + {error, B} + end; +which_users_exists(LServer, LUsers) -> + {First, Rest} = lists:split(100, LUsers), + case which_users_exists(LServer, First) of + {error, _} = E -> + E; + V -> + case which_users_exists(LServer, Rest) of + {error, _} = E2 -> + E2; + V2 -> + V ++ V2 + end + end. + + convert_to_scram(Server) -> LServer = jid:nameprep(Server), if @@ -459,27 +282,34 @@ convert_to_scram(Server) -> case ejabberd_sql:sql_query_t( ?SQL("select @(username)s, @(password)s" " from users" - " where iterationcount=0" + " where iterationcount=0 and %(LServer)H" " limit %(BatchSize)d")) of {selected, []} -> ok; {selected, Rs} -> lists:foreach( fun({LUser, Password}) -> - Scram = password_to_scram(Password), - set_password_scram_t( - LUser, - Scram#scram.storedkey, - Scram#scram.serverkey, - Scram#scram.salt, - Scram#scram.iterationcount - ) + case jid:resourceprep(Password) of + error -> + ?ERROR_MSG( + "SASLprep failed for " + "password of user ~ts@~ts", + [LUser, LServer]); + _ -> + Scram = ejabberd_auth:password_to_scram(Password), + set_password_scram_t( + LUser, LServer, + Scram#scram.storedkey, + Scram#scram.serverkey, + Scram#scram.salt, + Scram#scram.iterationcount) + end end, Rs), continue; Err -> {bad_reply, Err} end end, - case sql_queries:sql_transaction(LServer, F) of + case ejabberd_sql:sql_transaction(LServer, F) of {atomic, ok} -> ok; {atomic, continue} -> convert_to_scram(Server); {atomic, Error} -> {error, Error}; @@ -487,5 +317,32 @@ convert_to_scram(Server) -> end end. -opt_type(auth_password_format) -> fun (V) -> V end; -opt_type(_) -> [auth_password_format]. +export(_Server) -> + [{passwd, + fun(Host, #passwd{us = {LUser, LServer}, password = Password}) + when LServer == Host, + is_binary(Password) -> + [?SQL("delete from users where username=%(LUser)s and %(LServer)H;"), + ?SQL_INSERT( + "users", + ["username=%(LUser)s", + "server_host=%(LServer)s", + "password=%(Password)s"])]; + (Host, #passwd{us = {LUser, LServer}, password = #scram{} = Scram}) + when LServer == Host -> + StoredKey = Scram#scram.storedkey, + ServerKey = Scram#scram.serverkey, + Salt = Scram#scram.salt, + IterationCount = Scram#scram.iterationcount, + [?SQL("delete from users where username=%(LUser)s and %(LServer)H;"), + ?SQL_INSERT( + "users", + ["username=%(LUser)s", + "server_host=%(LServer)s", + "password=%(StoredKey)s", + "serverkey=%(ServerKey)s", + "salt=%(Salt)s", + "iterationcount=%(IterationCount)d"])]; + (_Host, _R) -> + [] + end}]. |