Add password entropy check (EJAB-1326)
This commit is contained in:
parent
0a1b0498a6
commit
641dc7d695
|
@ -3777,6 +3777,10 @@ change it by defining access rule in this option. Use with care: allowing regist
|
||||||
from s2s leads to uncontrolled massive accounts creation by rogue users.
|
from s2s leads to uncontrolled massive accounts creation by rogue users.
|
||||||
\titem{\{captcha\_protected, false|true\}} \ind{options!captcha\_protected}
|
\titem{\{captcha\_protected, false|true\}} \ind{options!captcha\_protected}
|
||||||
Protect registrations with CAPTCHA (see section \ref{captcha}). The default is \term{false}.
|
Protect registrations with CAPTCHA (see section \ref{captcha}). The default is \term{false}.
|
||||||
|
\titem{\{password\_strength, Entropy\}} \ind{options!password\_strength}
|
||||||
|
This option sets the minimum informational entropy for passwords. The value \term{Entropy}
|
||||||
|
is a number of bits of entropy. The recommended minimum is 32 bits.
|
||||||
|
The default is 0, i.e. no checks are performed.
|
||||||
\titem{\{welcome\_message, Message\}} \ind{options!welcomem}Set a welcome message that
|
\titem{\{welcome\_message, Message\}} \ind{options!welcomem}Set a welcome message that
|
||||||
is sent to each newly registered account. The first string is the subject, and
|
is sent to each newly registered account. The first string is the subject, and
|
||||||
the second string is the message body.
|
the second string is the message body.
|
||||||
|
|
|
@ -49,7 +49,8 @@
|
||||||
is_user_exists_in_other_modules/3,
|
is_user_exists_in_other_modules/3,
|
||||||
remove_user/2,
|
remove_user/2,
|
||||||
remove_user/3,
|
remove_user/3,
|
||||||
plain_password_required/1
|
plain_password_required/1,
|
||||||
|
entropy/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([auth_modules/1]).
|
-export([auth_modules/1]).
|
||||||
|
@ -318,6 +319,29 @@ remove_user(User, Server, Password) ->
|
||||||
end,
|
end,
|
||||||
R.
|
R.
|
||||||
|
|
||||||
|
%% @spec (IOList) -> non_negative_float()
|
||||||
|
%% @doc Calculate informational entropy.
|
||||||
|
entropy(IOList) ->
|
||||||
|
case binary_to_list(iolist_to_binary(IOList)) of
|
||||||
|
"" ->
|
||||||
|
0.0;
|
||||||
|
S ->
|
||||||
|
Set = lists:foldl(
|
||||||
|
fun(C, [Digit, Printable, LowLetter, HiLetter, Other]) ->
|
||||||
|
if C >= $a, C =< $z ->
|
||||||
|
[Digit, Printable, 26, HiLetter, Other];
|
||||||
|
C >= $0, C =< $9 ->
|
||||||
|
[9, Printable, LowLetter, HiLetter, Other];
|
||||||
|
C >= $A, C =< $Z ->
|
||||||
|
[Digit, Printable, LowLetter, 26, Other];
|
||||||
|
C >= 16#21, C =< 16#7e ->
|
||||||
|
[Digit, 33, LowLetter, HiLetter, Other];
|
||||||
|
true ->
|
||||||
|
[Digit, Printable, LowLetter, HiLetter, 128]
|
||||||
|
end
|
||||||
|
end, [0, 0, 0, 0, 0], S),
|
||||||
|
length(S) * math:log(lists:sum(Set))/math:log(2)
|
||||||
|
end.
|
||||||
|
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
|
|
|
@ -262,7 +262,7 @@ try_register_or_set_password(User, Server, Password, From, IQ,
|
||||||
SubEl, Source, Lang, CaptchaSucceed) ->
|
SubEl, Source, Lang, CaptchaSucceed) ->
|
||||||
case From of
|
case From of
|
||||||
#jid{user = User, lserver = Server} ->
|
#jid{user = User, lserver = Server} ->
|
||||||
try_set_password(User, Server, Password, IQ, SubEl);
|
try_set_password(User, Server, Password, IQ, SubEl, Lang);
|
||||||
_ when CaptchaSucceed ->
|
_ when CaptchaSucceed ->
|
||||||
case check_from(From, Server) of
|
case check_from(From, Server) of
|
||||||
allow ->
|
allow ->
|
||||||
|
@ -285,18 +285,25 @@ try_register_or_set_password(User, Server, Password, From, IQ,
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Try to change password and return IQ response
|
%% @doc Try to change password and return IQ response
|
||||||
try_set_password(User, Server, Password, IQ, SubEl) ->
|
try_set_password(User, Server, Password, IQ, SubEl, Lang) ->
|
||||||
case ejabberd_auth:set_password(User, Server, Password) of
|
case is_strong_password(Server, Password) of
|
||||||
ok ->
|
true ->
|
||||||
IQ#iq{type = result, sub_el = [SubEl]};
|
case ejabberd_auth:set_password(User, Server, Password) of
|
||||||
{error, empty_password} ->
|
ok ->
|
||||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
|
IQ#iq{type = result, sub_el = [SubEl]};
|
||||||
{error, not_allowed} ->
|
{error, empty_password} ->
|
||||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]};
|
||||||
{error, invalid_jid} ->
|
{error, not_allowed} ->
|
||||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
|
||||||
_ ->
|
{error, invalid_jid} ->
|
||||||
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
|
||||||
|
_ ->
|
||||||
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
ErrText = "The password is too weak",
|
||||||
|
IQ#iq{type = error,
|
||||||
|
sub_el = [SubEl, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)]}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
try_register(User, Server, Password, Source, Lang) ->
|
try_register(User, Server, Password, Source, Lang) ->
|
||||||
|
@ -312,23 +319,30 @@ try_register(User, Server, Password, Source, Lang) ->
|
||||||
allow ->
|
allow ->
|
||||||
case check_timeout(Source) of
|
case check_timeout(Source) of
|
||||||
true ->
|
true ->
|
||||||
case ejabberd_auth:try_register(User, Server, Password) of
|
case is_strong_password(Server, Password) of
|
||||||
{atomic, ok} ->
|
true ->
|
||||||
send_welcome_message(JID),
|
case ejabberd_auth:try_register(
|
||||||
send_registration_notifications(JID, Source),
|
User, Server, Password) of
|
||||||
ok;
|
{atomic, ok} ->
|
||||||
Error ->
|
send_welcome_message(JID),
|
||||||
remove_timeout(Source),
|
send_registration_notifications(JID, Source),
|
||||||
case Error of
|
ok;
|
||||||
{atomic, exists} ->
|
Error ->
|
||||||
{error, ?ERR_CONFLICT};
|
remove_timeout(Source),
|
||||||
{error, invalid_jid} ->
|
case Error of
|
||||||
{error, ?ERR_JID_MALFORMED};
|
{atomic, exists} ->
|
||||||
{error, not_allowed} ->
|
{error, ?ERR_CONFLICT};
|
||||||
{error, ?ERR_NOT_ALLOWED};
|
{error, invalid_jid} ->
|
||||||
{error, _Reason} ->
|
{error, ?ERR_JID_MALFORMED};
|
||||||
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
{error, not_allowed} ->
|
||||||
end
|
{error, ?ERR_NOT_ALLOWED};
|
||||||
|
{error, _Reason} ->
|
||||||
|
{error, ?ERR_INTERNAL_SERVER_ERROR}
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
ErrText = "The password is too weak",
|
||||||
|
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
|
||||||
end;
|
end;
|
||||||
false ->
|
false ->
|
||||||
ErrText = "Users are not allowed to register "
|
ErrText = "Users are not allowed to register "
|
||||||
|
@ -502,3 +516,18 @@ process_xdata_submit(El) ->
|
||||||
error
|
error
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
is_strong_password(Server, Password) ->
|
||||||
|
LServer = jlib:nameprep(Server),
|
||||||
|
case gen_mod:get_module_opt(LServer, ?MODULE, password_strength, 0) of
|
||||||
|
Entropy when is_number(Entropy), Entropy >= 0 ->
|
||||||
|
if Entropy == 0 ->
|
||||||
|
true;
|
||||||
|
true ->
|
||||||
|
ejabberd_auth:entropy(Password) >= Entropy
|
||||||
|
end;
|
||||||
|
Wrong ->
|
||||||
|
?WARNING_MSG("Wrong value for password_strength option: ~p",
|
||||||
|
[Wrong]),
|
||||||
|
true
|
||||||
|
end.
|
||||||
|
|
Loading…
Reference in New Issue