diff --git a/doc/guide.tex b/doc/guide.tex index 69cd268f6..48900ce36 100644 --- a/doc/guide.tex +++ b/doc/guide.tex @@ -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. \titem{\{captcha\_protected, false|true\}} \ind{options!captcha\_protected} 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 is sent to each newly registered account. The first string is the subject, and the second string is the message body. diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl index 30ef97685..613ec1e3b 100644 --- a/src/ejabberd_auth.erl +++ b/src/ejabberd_auth.erl @@ -49,7 +49,8 @@ is_user_exists_in_other_modules/3, remove_user/2, remove_user/3, - plain_password_required/1 + plain_password_required/1, + entropy/1 ]). -export([auth_modules/1]). @@ -318,6 +319,29 @@ remove_user(User, Server, Password) -> end, 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 diff --git a/src/mod_register.erl b/src/mod_register.erl index ea53bd82a..6fe1818e7 100644 --- a/src/mod_register.erl +++ b/src/mod_register.erl @@ -262,7 +262,7 @@ try_register_or_set_password(User, Server, Password, From, IQ, SubEl, Source, Lang, CaptchaSucceed) -> case From of #jid{user = User, lserver = Server} -> - try_set_password(User, Server, Password, IQ, SubEl); + try_set_password(User, Server, Password, IQ, SubEl, Lang); _ when CaptchaSucceed -> case check_from(From, Server) of allow -> @@ -285,18 +285,25 @@ try_register_or_set_password(User, Server, Password, From, IQ, end. %% @doc Try to change password and return IQ response -try_set_password(User, Server, Password, IQ, SubEl) -> - case ejabberd_auth:set_password(User, Server, Password) of - ok -> - IQ#iq{type = result, sub_el = [SubEl]}; - {error, empty_password} -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}; - {error, not_allowed} -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; - {error, invalid_jid} -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}; - _ -> - IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} +try_set_password(User, Server, Password, IQ, SubEl, Lang) -> + case is_strong_password(Server, Password) of + true -> + case ejabberd_auth:set_password(User, Server, Password) of + ok -> + IQ#iq{type = result, sub_el = [SubEl]}; + {error, empty_password} -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}; + {error, not_allowed} -> + IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; + {error, invalid_jid} -> + 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. try_register(User, Server, Password, Source, Lang) -> @@ -312,23 +319,30 @@ try_register(User, Server, Password, Source, Lang) -> allow -> case check_timeout(Source) of true -> - case ejabberd_auth:try_register(User, Server, Password) of - {atomic, ok} -> - send_welcome_message(JID), - send_registration_notifications(JID, Source), - ok; - Error -> - remove_timeout(Source), - case Error of - {atomic, exists} -> - {error, ?ERR_CONFLICT}; - {error, invalid_jid} -> - {error, ?ERR_JID_MALFORMED}; - {error, not_allowed} -> - {error, ?ERR_NOT_ALLOWED}; - {error, _Reason} -> - {error, ?ERR_INTERNAL_SERVER_ERROR} - end + case is_strong_password(Server, Password) of + true -> + case ejabberd_auth:try_register( + User, Server, Password) of + {atomic, ok} -> + send_welcome_message(JID), + send_registration_notifications(JID, Source), + ok; + Error -> + remove_timeout(Source), + case Error of + {atomic, exists} -> + {error, ?ERR_CONFLICT}; + {error, invalid_jid} -> + {error, ?ERR_JID_MALFORMED}; + {error, not_allowed} -> + {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; false -> ErrText = "Users are not allowed to register " @@ -502,3 +516,18 @@ process_xdata_submit(El) -> error 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.