diff options
Diffstat (limited to 'src/ejabberd_auth_mnesia.erl')
-rw-r--r-- | src/ejabberd_auth_mnesia.erl | 618 |
1 files changed, 190 insertions, 428 deletions
diff --git a/src/ejabberd_auth_mnesia.erl b/src/ejabberd_auth_mnesia.erl index 2a4554d15..f3f1b80b7 100644 --- a/src/ejabberd_auth_mnesia.erl +++ b/src/ejabberd_auth_mnesia.erl @@ -1,11 +1,11 @@ %%%---------------------------------------------------------------------- %%% File : ejabberd_auth_mnesia.erl %%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : Authentification via mnesia +%%% Purpose : Authentication via mnesia %%% 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,53 +25,45 @@ -module(ejabberd_auth_mnesia). --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, export/1, import/1, - import/3, plain_password_required/0, opt_type/1]). +-export([start/1, stop/1, set_password/3, try_register/3, + get_users/2, init_db/0, + count_users/2, get_password/2, + remove_user/2, store_type/1, import/2, + plain_password_required/1, use_cache/1]). +-export([need_transform/1, transform/1]). --include("ejabberd.hrl"). -include("logger.hrl"). --include("ejabberd_sql_pt.hrl"). - --record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1', - password = <<"">> :: binary() | scram() | '_'}). +-include("scram.hrl"). +-include("ejabberd_auth.hrl"). -record(reg_users_counter, {vhost = <<"">> :: binary(), count = 0 :: integer() | '$1'}). --define(SALT_LENGTH, 16). - %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start(Host) -> - mnesia:create_table(passwd, - [{disc_copies, [node()]}, - {attributes, record_info(fields, passwd)}]), - mnesia:create_table(reg_users_counter, - [{ram_copies, [node()]}, - {attributes, record_info(fields, reg_users_counter)}]), - update_table(), + init_db(), update_reg_users_counter_table(Host), - maybe_alert_password_scrammed_without_option(), ok. +stop(_Host) -> + ok. + +init_db() -> + ejabberd_mnesia:create(?MODULE, passwd, + [{disc_only_copies, [node()]}, + {attributes, record_info(fields, passwd)}]), + ejabberd_mnesia:create(?MODULE, reg_users_counter, + [{ram_copies, [node()]}, + {attributes, record_info(fields, reg_users_counter)}]). + update_reg_users_counter_table(Server) -> - Set = get_vh_registered_users(Server), + Set = get_users(Server, []), Size = length(Set), LServer = jid:nameprep(Server), F = fun () -> @@ -80,429 +72,199 @@ update_reg_users_counter_table(Server) -> end, mnesia:sync_dirty(F). -plain_password_required() -> - is_scrammed(). - -store_type() -> - case is_scrammed() of - false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM - true -> scram %% allows: PLAIN SCRAM +use_cache(Host) -> + case mnesia:table_info(passwd, storage_type) of + disc_only_copies -> + ejabberd_option:auth_use_cache(Host); + _ -> + false end. -check_password(User, AuthzId, Server, Password) -> - if AuthzId /= <<>> andalso AuthzId /= User -> - false; - true -> - LUser = jid:nodeprep(User), - LServer = jid: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. +plain_password_required(Server) -> + store_type(Server) == scram. -check_password(User, AuthzId, Server, Password, Digest, - DigestGen) -> - if AuthzId /= <<>> andalso AuthzId /= User -> - false; - true -> - LUser = jid:nodeprep(User), - LServer = jid: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. +store_type(Server) -> + ejabberd_auth:password_format(Server). -%% @spec (User::string(), Server::string(), Password::string()) -> -%% ok | {error, invalid_jid} set_password(User, Server, Password) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - US = {LUser, LServer}, - if (LUser == error) or (LServer == error) -> - {error, invalid_jid}; - true -> - F = fun () -> - Password2 = case is_scrammed() and is_binary(Password) - of - true -> password_to_scram(Password); - false -> Password - end, - mnesia:write(#passwd{us = US, password = Password2}) - end, - {atomic, ok} = mnesia:transaction(F), - ok + US = {User, Server}, + F = fun () -> + mnesia:write(#passwd{us = US, password = Password}) + end, + case mnesia:transaction(F) of + {atomic, ok} -> + {cache, {ok, Password}}; + {aborted, Reason} -> + ?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]), + {nocache, {error, db_failure}} end. -%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {error, not_allowed} | {error, Reason} -try_register(User, Server, PasswordList) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - Password = if is_list(PasswordList); is_binary(PasswordList) -> - iolist_to_binary(PasswordList); - true -> PasswordList - end, - US = {LUser, LServer}, - if (LUser == error) or (LServer == error) -> - {error, invalid_jid}; - true -> - F = fun () -> - case mnesia:read({passwd, US}) of - [] -> - Password2 = case is_scrammed() and - is_binary(Password) - of - true -> password_to_scram(Password); - false -> Password - end, - mnesia:write(#passwd{us = US, - password = Password2}), - mnesia:dirty_update_counter(reg_users_counter, - LServer, 1), - ok; - [_E] -> exists - end - end, - mnesia:transaction(F) +try_register(User, Server, Password) -> + US = {User, Server}, + F = fun () -> + case mnesia:read({passwd, US}) of + [] -> + mnesia:write(#passwd{us = US, password = Password}), + mnesia:dirty_update_counter(reg_users_counter, Server, 1), + {ok, Password}; + [_] -> + {error, exists} + end + end, + case mnesia:transaction(F) of + {atomic, Res} -> + {cache, Res}; + {aborted, Reason} -> + ?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]), + {nocache, {error, db_failure}} end. -%% Get all registered users in Mnesia -dirty_get_registered_users() -> - mnesia:dirty_all_keys(passwd). - -get_vh_registered_users(Server) -> - LServer = jid:nameprep(Server), +get_users(Server, []) -> mnesia:dirty_select(passwd, [{#passwd{us = '$1', _ = '_'}, - [{'==', {element, 2, '$1'}, LServer}], ['$1']}]). - -get_vh_registered_users(Server, - [{from, Start}, {to, End}]) - when is_integer(Start) and is_integer(End) -> - get_vh_registered_users(Server, - [{limit, End - Start + 1}, {offset, Start}]); -get_vh_registered_users(Server, - [{limit, Limit}, {offset, Offset}]) - when is_integer(Limit) and is_integer(Offset) -> - case get_vh_registered_users(Server) of - [] -> []; - Users -> - Set = lists:keysort(1, Users), - L = length(Set), - Start = if Offset < 1 -> 1; - Offset > L -> L; - true -> Offset - end, - lists:sublist(Set, Start, Limit) + [{'==', {element, 2, '$1'}, Server}], ['$1']}]); +get_users(Server, [{from, Start}, {to, End}]) + when is_integer(Start) and is_integer(End) -> + get_users(Server, [{limit, End - Start + 1}, {offset, Start}]); +get_users(Server, [{limit, Limit}, {offset, Offset}]) + when is_integer(Limit) and is_integer(Offset) -> + case get_users(Server, []) of + [] -> + []; + Users -> + Set = lists:keysort(1, Users), + L = length(Set), + Start = if Offset < 1 -> 1; + Offset > L -> L; + true -> Offset + end, + lists:sublist(Set, Start, Limit) end; -get_vh_registered_users(Server, [{prefix, Prefix}]) - when is_binary(Prefix) -> - Set = [{U, S} - || {U, S} <- get_vh_registered_users(Server), - str:prefix(Prefix, U)], +get_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) -> + Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)], lists:keysort(1, Set); -get_vh_registered_users(Server, - [{prefix, Prefix}, {from, Start}, {to, End}]) - when is_binary(Prefix) and is_integer(Start) and - is_integer(End) -> - get_vh_registered_users(Server, - [{prefix, Prefix}, {limit, End - Start + 1}, - {offset, Start}]); -get_vh_registered_users(Server, - [{prefix, Prefix}, {limit, Limit}, {offset, Offset}]) - when is_binary(Prefix) and is_integer(Limit) and - is_integer(Offset) -> - case [{U, S} - || {U, S} <- get_vh_registered_users(Server), - str:prefix(Prefix, U)] - of - [] -> []; - Users -> - Set = lists:keysort(1, Users), - L = length(Set), - Start = if Offset < 1 -> 1; - Offset > L -> L; - true -> Offset - end, - lists:sublist(Set, Start, Limit) +get_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}]) + when is_binary(Prefix) and is_integer(Start) and is_integer(End) -> + get_users(Server, [{prefix, Prefix}, {limit, End - Start + 1}, + {offset, Start}]); +get_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}]) + when is_binary(Prefix) and is_integer(Limit) and is_integer(Offset) -> + case [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)] of + [] -> + []; + Users -> + Set = lists:keysort(1, Users), + L = length(Set), + Start = if Offset < 1 -> 1; + Offset > L -> L; + true -> Offset + end, + lists:sublist(Set, Start, Limit) end; -get_vh_registered_users(Server, _) -> - get_vh_registered_users(Server). - -get_vh_registered_users_number(Server) -> - LServer = jid:nameprep(Server), - Query = mnesia:dirty_select(reg_users_counter, - [{#reg_users_counter{vhost = LServer, - count = '$1'}, - [], ['$1']}]), - case Query of - [Count] -> Count; - _ -> 0 - end. - -get_vh_registered_users_number(Server, - [{prefix, Prefix}]) - when is_binary(Prefix) -> - Set = [{U, S} - || {U, S} <- get_vh_registered_users(Server), - str:prefix(Prefix, U)], +get_users(Server, _) -> + get_users(Server, []). + +count_users(Server, []) -> + case mnesia:dirty_select( + reg_users_counter, + [{#reg_users_counter{vhost = Server, count = '$1'}, + [], ['$1']}]) of + [Count] -> Count; + _ -> 0 + end; +count_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) -> + Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)], length(Set); -get_vh_registered_users_number(Server, _) -> - get_vh_registered_users_number(Server). +count_users(Server, _) -> + count_users(Server, []). get_password(User, Server) -> - LUser = jid:nodeprep(User), - LServer = jid: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) -> - {jlib:decode_base64(Scram#scram.storedkey), - jlib:decode_base64(Scram#scram.serverkey), - jlib:decode_base64(Scram#scram.salt), - Scram#scram.iterationcount}; - _ -> false + case mnesia:dirty_read(passwd, {User, Server}) of + [#passwd{password = Password}] -> + {cache, {ok, Password}}; + _ -> + {cache, error} end. -get_password_s(User, Server) -> - LUser = jid:nodeprep(User), - LServer = jid: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) -> - <<"">>; - _ -> <<"">> - end. - -%% @spec (User, Server) -> true | false | {error, Error} -is_user_exists(User, Server) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - US = {LUser, LServer}, - case catch mnesia:dirty_read({passwd, US}) of - [] -> false; - [_] -> true; - Other -> {error, Other} - end. - -%% @spec (User, Server) -> ok -%% @doc Remove user. -%% Note: it returns ok even if there was some problem removing the user. remove_user(User, Server) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - US = {LUser, LServer}, + US = {User, Server}, F = fun () -> mnesia:delete({passwd, US}), - mnesia:dirty_update_counter(reg_users_counter, LServer, - -1) - end, - mnesia:transaction(F), - ok. - -%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request -%% @doc Remove user if the provided password is correct. -remove_user(User, Server, Password) -> - LUser = jid:nodeprep(User), - LServer = jid:nameprep(Server), - US = {LUser, LServer}, - F = fun () -> - case mnesia:read({passwd, US}) of - [#passwd{password = Password}] - when is_binary(Password) -> - mnesia:delete({passwd, US}), - mnesia:dirty_update_counter(reg_users_counter, LServer, - -1), - ok; - [#passwd{password = Scram}] - when is_record(Scram, scram) -> - case is_password_scram_valid(Password, Scram) of - true -> - mnesia:delete({passwd, US}), - mnesia:dirty_update_counter(reg_users_counter, - LServer, -1), - ok; - false -> not_allowed - end; - _ -> not_exists - end + mnesia:dirty_update_counter(reg_users_counter, Server, -1), + ok end, case mnesia:transaction(F) of - {atomic, ok} -> ok; - {atomic, Res} -> Res; - _ -> bad_request - end. - -update_table() -> - Fields = record_info(fields, passwd), - case mnesia:table_info(passwd, attributes) of - Fields -> - convert_to_binary(Fields), - maybe_scram_passwords(), - ok; - _ -> - ?INFO_MSG("Recreating passwd table", []), - mnesia:transform_table(passwd, ignore, Fields) + {atomic, ok} -> + ok; + {aborted, Reason} -> + ?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]), + {error, db_failure} end. -convert_to_binary(Fields) -> - ejabberd_config:convert_table_to_binary( - passwd, Fields, set, - fun(#passwd{us = {U, _}}) -> U end, - fun(#passwd{us = {U, S}, password = Pass} = R) -> - NewUS = {iolist_to_binary(U), iolist_to_binary(S)}, - NewPass = case Pass of - #scram{storedkey = StoredKey, - serverkey = ServerKey, - salt = Salt} -> - Pass#scram{ - storedkey = iolist_to_binary(StoredKey), - serverkey = iolist_to_binary(ServerKey), - salt = iolist_to_binary(Salt)}; - _ -> - iolist_to_binary(Pass) - end, - R#passwd{us = NewUS, password = NewPass} - end). - -%%% -%%% SCRAM -%%% - -%% The passwords are stored scrammed in the table either if the option says so, -%% or if at least the first password is scrammed. -is_scrammed() -> - OptionScram = is_option_scram(), - FirstElement = mnesia:dirty_read(passwd, - mnesia:dirty_first(passwd)), - case {OptionScram, FirstElement} of - {true, _} -> true; - {false, [#passwd{password = Scram}]} - when is_record(Scram, scram) -> - true; - _ -> false - end. - -is_option_scram() -> - scram == - ejabberd_config:get_option({auth_password_format, ?MYNAME}, - fun(V) -> V end). - -maybe_alert_password_scrammed_without_option() -> - case is_scrammed() andalso not is_option_scram() of - true -> - ?ERROR_MSG("Some passwords were stored in the database " - "as SCRAM, but 'auth_password_format' " - "is not configured 'scram'. The option " - "will now be considered to be 'scram'.", - []); - false -> ok - end. - -maybe_scram_passwords() -> - case is_scrammed() of - true -> scram_passwords(); - false -> ok +need_transform(#reg_users_counter{}) -> + false; +need_transform({passwd, {U, S}, Pass}) -> + if is_binary(Pass) -> + case store_type(S) of + scram -> + ?INFO_MSG("Passwords in Mnesia table 'passwd' " + "will be SCRAM'ed", []), + true; + plain -> + false + end; + is_record(Pass, scram) -> + case store_type(S) of + scram -> + false; + plain -> + ?WARNING_MSG("Some passwords were stored in the database " + "as SCRAM, but 'auth_password_format' " + "is not configured as 'scram': some " + "authentication mechanisms such as DIGEST-MD5 " + "would *fail*", []), + false + end; + is_list(U) orelse is_list(S) orelse is_list(Pass) -> + ?INFO_MSG("Mnesia table 'passwd' will be converted to binary", []), + true end. -scram_passwords() -> - ?INFO_MSG("Converting the stored passwords into " - "SCRAM bits", - []), - Fun = fun (#passwd{password = Password} = P) -> - Scram = password_to_scram(Password), - P#passwd{password = Scram} - end, - Fields = record_info(fields, passwd), - mnesia:transform_table(passwd, Fun, Fields). - -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(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. - -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;"), - ?SQL("insert into users(username, password) " - "values (%(LUser)s, %(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;"), - ?SQL("insert into users(username, password, serverkey, salt, " - "iterationcount) " - "values (%(LUser)s, %(StoredKey)s, %(ServerKey)s," - " %(Salt)s, %(IterationCount)d);")]; - (_Host, _R) -> - [] - end}]. - -import(LServer) -> - [{<<"select username, password from users;">>, - fun([LUser, Password]) -> - #passwd{us = {LUser, LServer}, password = Password} - end}]. - -import(_LServer, mnesia, #passwd{} = P) -> - mnesia:dirty_write(P); -import(_, _, _) -> - pass. +transform({passwd, {U, S}, Pass}) + when is_list(U) orelse is_list(S) orelse is_list(Pass) -> + NewUS = {iolist_to_binary(U), iolist_to_binary(S)}, + NewPass = case Pass of + #scram{storedkey = StoredKey, + serverkey = ServerKey, + salt = Salt} -> + Pass#scram{ + storedkey = iolist_to_binary(StoredKey), + serverkey = iolist_to_binary(ServerKey), + salt = iolist_to_binary(Salt)}; + _ -> + iolist_to_binary(Pass) + end, + transform(#passwd{us = NewUS, password = NewPass}); +transform(#passwd{us = {U, S}, password = Password} = P) + when is_binary(Password) -> + case store_type(S) of + scram -> + case jid:resourceprep(Password) of + error -> + ?ERROR_MSG("SASLprep failed for password of user ~ts@~ts", + [U, S]), + P; + _ -> + Scram = ejabberd_auth:password_to_scram(Password), + P#passwd{password = Scram} + end; + plain -> + P + end; +transform(#passwd{password = Password} = P) + when is_record(Password, scram) -> + P. -opt_type(auth_password_format) -> fun (V) -> V end; -opt_type(_) -> [auth_password_format]. +import(LServer, [LUser, Password, _TimeStamp]) -> + mnesia:dirty_write( + #passwd{us = {LUser, LServer}, password = Password}). |