Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/processone/ejabberd.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBadlop <badlop@process-one.net>2011-08-16 02:26:49 +0400
committerBadlop <badlop@process-one.net>2011-08-16 02:26:49 +0400
commit1ee6eae684c126b486c70a3cadd58fa282f517ad (patch)
tree9e15e7960fa6d3ca0b90fcf3e23ba158bf2d6535
parent24852b9be8f20eb6bed07ac78377ec0debcbf0c3 (diff)
Fix and improve support for SCRAM auth method (EJAB-1196)
-rw-r--r--doc/guide.tex21
-rw-r--r--src/cyrsasl.erl7
-rw-r--r--src/cyrsasl_scram.erl14
-rw-r--r--src/ejabberd.cfg.example4
-rw-r--r--src/ejabberd.hrl3
-rw-r--r--src/ejabberd_auth.erl15
-rw-r--r--src/ejabberd_auth_anonymous.erl4
-rw-r--r--src/ejabberd_auth_external.erl4
-rw-r--r--src/ejabberd_auth_internal.erl138
-rw-r--r--src/ejabberd_auth_internal_scram.erl369
-rw-r--r--src/ejabberd_auth_ldap.erl4
-rw-r--r--src/ejabberd_auth_odbc.erl4
-rw-r--r--src/ejabberd_auth_pam.erl4
-rw-r--r--src/ejabberd_piefxis.erl53
14 files changed, 225 insertions, 419 deletions
diff --git a/doc/guide.tex b/doc/guide.tex
index 5939a9eec..49cfd5bf8 100644
--- a/doc/guide.tex
+++ b/doc/guide.tex
@@ -1221,6 +1221,24 @@ Account creation is only supported by internal, external and odbc methods.
\ejabberd{} uses its internal Mnesia database as the default authentication method.
The value \term{internal} will enable the internal authentication method.
+The option \term{\{auth\_password\_format, plain|scram\}}
+defines in what format the users passwords are stored:
+\begin{description}
+ \titem{plain}
+ The password is stored as plain text in the database.
+ This is risky because the passwords can be read if your database gets compromised.
+ This is the default value.
+ This format allows clients to authenticate using:
+ the old Jabber Non-SASL (\xepref{0078}), \term{SASL PLAIN},
+ \term{SASL DIGEST-MD5}, and \term{SASL SCRAM-SHA-1}.
+
+ \titem{scram}
+ The password is not stored, only some information that allows to verify the hash provided by the client.
+ It is impossible to obtain the original plain password from the stored information;
+ for this reason, when this value is configured it cannot be changed to \term{plain} anymore.
+ This format allows clients to authenticate using: \term{SASL PLAIN} and \term{SASL SCRAM-SHA-1}.
+\end{description}
+
Examples:
\begin{itemize}
\item To use internal authentication on \jid{example.org} and LDAP
@@ -1229,9 +1247,10 @@ Examples:
{host_config, "example.org", [{auth_method, [internal]}]}.
{host_config, "example.net", [{auth_method, [ldap]}]}.
\end{verbatim}
-\item To use internal authentication on all virtual hosts:
+\item To use internal authentication with hashed passwords on all virtual hosts:
\begin{verbatim}
{auth_method, internal}.
+{auth_password_format, scram}.
\end{verbatim}
\end{itemize}
diff --git a/src/cyrsasl.erl b/src/cyrsasl.erl
index 627ee4d32..28098f763 100644
--- a/src/cyrsasl.erl
+++ b/src/cyrsasl.erl
@@ -34,6 +34,8 @@
server_start/3,
server_step/2]).
+-include("ejabberd.hrl").
+
-record(sasl_mechanism, {mechanism, module, password_type}).
-record(sasl_state, {service, myname, realm,
get_password, check_password, check_password_digest,
@@ -102,11 +104,14 @@ listmech(Host) ->
[{#sasl_mechanism{mechanism = '$1',
password_type = '$2',
_ = '_'},
- case ejabberd_auth:storage_type(Host) of
+ case catch ejabberd_auth:store_type(Host) of
external ->
[{'==', '$2', plain}];
scram ->
[{'/=', '$2', digest}];
+ {'EXIT',{undef,[{Module,store_type,[]} | _]}} ->
+ ?WARNING_MSG("~p doesn't implement the function store_type/0", [Module]),
+ [];
_Else ->
[]
end,
diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl
index f0649f651..270cce26f 100644
--- a/src/cyrsasl_scram.erl
+++ b/src/cyrsasl_scram.erl
@@ -39,7 +39,6 @@
-record(state, {step, stored_key, server_key, username, get_password, check_password,
auth_message, client_nonce, server_nonce}).
--define(DEFAULT_ITERATION_COUNT, 4096).
-define(SALT_LENGTH, 16).
-define(NONCE_LENGTH, 16).
@@ -54,9 +53,9 @@ mech_new(_Host, GetPassword, _CheckPassword, _CheckPasswordDigest) ->
mech_step(#state{step = 2} = State, ClientIn) ->
case string:tokens(ClientIn, ",") of
- ["n", UserNameAttribute, ClientNonceAttribute] ->
+ [CBind, UserNameAttribute, ClientNonceAttribute] when (CBind == "y") or (CBind == "n") ->
case parse_attribute(UserNameAttribute) of
- {$n, EscapedUserName} ->
+ {_, EscapedUserName} ->
case unescape_username(EscapedUserName) of
error ->
{error, "protocol-error-bad-username"};
@@ -72,8 +71,9 @@ mech_step(#state{step = 2} = State, ClientIn) ->
Ret;
true ->
TempSalt = crypto:rand_bytes(?SALT_LENGTH),
- SaltedPassword = scram:salted_password(Ret, TempSalt, ?DEFAULT_ITERATION_COUNT),
- {scram:stored_key(scram:client_key(SaltedPassword)), TempSalt, ?DEFAULT_ITERATION_COUNT}
+ SaltedPassword = scram:salted_password(Ret, TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT),
+ {scram:stored_key(scram:client_key(SaltedPassword)),
+ scram:server_key(SaltedPassword), TempSalt, ?SCRAM_DEFAULT_ITERATION_COUNT}
end,
ClientFirstMessageBare = string:substr(ClientIn, string:str(ClientIn, "n=")),
ServerNonce = base64:encode_to_string(crypto:rand_bytes(?NONCE_LENGTH)),
@@ -102,7 +102,9 @@ mech_step(#state{step = 4} = State, ClientIn) ->
case string:tokens(ClientIn, ",") of
[GS2ChannelBindingAttribute, NonceAttribute, ClientProofAttribute] ->
case parse_attribute(GS2ChannelBindingAttribute) of
- {$c, "biws"} -> %biws is base64 for n,, => channelbinding not supported
+ {$c, CVal} when (CVal == "biws") or (CVal == "eSws") ->
+ %% biws is base64 for n,, => channelbinding not supported
+ %% eSws is base64 for y,, => channelbinding supported by client only
Nonce = State#state.client_nonce ++ State#state.server_nonce,
case parse_attribute(NonceAttribute) of
{$r, CompareNonce} when CompareNonce == Nonce ->
diff --git a/src/ejabberd.cfg.example b/src/ejabberd.cfg.example
index 92e78e32d..57eab41bd 100644
--- a/src/ejabberd.cfg.example
+++ b/src/ejabberd.cfg.example
@@ -218,6 +218,10 @@
%% comment this line and enable the correct ones.
%%
{auth_method, internal}.
+%%
+%% Store the plain passwords or hashed for SCRAM:
+%%{auth_password_format, plain}.
+%%{auth_password_format, scram}.
%%
%% Authentication using external script
diff --git a/src/ejabberd.hrl b/src/ejabberd.hrl
index b1bf516cd..9f8978be8 100644
--- a/src/ejabberd.hrl
+++ b/src/ejabberd.hrl
@@ -37,6 +37,9 @@
%%-define(DBGFSM, true).
+-record(scram, {storedkey, serverkey, salt, iterationcount}).
+-define(SCRAM_DEFAULT_ITERATION_COUNT, 4096).
+
%% ---------------------------------
%% Logging mechanism
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index d937340f1..1eab9af83 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -50,7 +50,7 @@
remove_user/2,
remove_user/3,
plain_password_required/1,
- storage_type/1,
+ store_type/1,
entropy/1
]).
@@ -70,25 +70,26 @@ start() ->
end, auth_modules(Host))
end, ?MYHOSTS).
+%% This is only executed by ejabberd_c2s for non-SASL auth client
plain_password_required(Server) ->
lists:any(
fun(M) ->
M:plain_password_required()
end, auth_modules(Server)).
-storage_type(Server) ->
+store_type(Server) ->
lists:foldl(
fun(_, external) ->
external;
(M, scram) ->
- case M:storage_type() of
+ case M:store_type() of
external ->
external;
_Else ->
scram
end;
(M, plain) ->
- M:storage_type()
+ M:store_type()
end, plain, auth_modules(Server)).
%% @doc Check if the user and password can login in server.
@@ -248,8 +249,10 @@ get_password_s(User, Server) ->
case get_password(User, Server) of
false ->
"";
- Password ->
- Password
+ Password when is_list(Password) ->
+ Password;
+ _ ->
+ ""
end.
%% @doc Get the password of the user and the auth module.
diff --git a/src/ejabberd_auth_anonymous.erl b/src/ejabberd_auth_anonymous.erl
index d3ecffc1b..5295ab99f 100644
--- a/src/ejabberd_auth_anonymous.erl
+++ b/src/ejabberd_auth_anonymous.erl
@@ -51,7 +51,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
- storage_type/0,
+ store_type/0,
plain_password_required/0]).
-include("ejabberd.hrl").
@@ -249,5 +249,5 @@ remove_user(_User, _Server, _Password) ->
plain_password_required() ->
false.
-storage_type() ->
+store_type() ->
plain.
diff --git a/src/ejabberd_auth_external.erl b/src/ejabberd_auth_external.erl
index b625af84c..c4208b09e 100644
--- a/src/ejabberd_auth_external.erl
+++ b/src/ejabberd_auth_external.erl
@@ -43,7 +43,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
- storage_type/0,
+ store_type/0,
plain_password_required/0
]).
@@ -79,7 +79,7 @@ check_cache_last_options(Server) ->
plain_password_required() ->
true.
-storage_type() ->
+store_type() ->
external.
check_password(User, Server, Password) ->
diff --git a/src/ejabberd_auth_internal.erl b/src/ejabberd_auth_internal.erl
index 847737f29..b196d3526 100644
--- a/src/ejabberd_auth_internal.erl
+++ b/src/ejabberd_auth_internal.erl
@@ -43,7 +43,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
- storage_type/0,
+ store_type/0,
plain_password_required/0
]).
@@ -52,6 +52,8 @@
-record(passwd, {us, password}).
-record(reg_users_counter, {vhost, count}).
+-define(SALT_LENGTH, 16).
+
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
@@ -63,6 +65,7 @@ start(Host) ->
{attributes, record_info(fields, reg_users_counter)}]),
update_table(),
update_reg_users_counter_table(Host),
+ maybe_alert_password_scrammed_without_option(),
ok.
update_reg_users_counter_table(Server) ->
@@ -76,18 +79,26 @@ update_reg_users_counter_table(Server) ->
mnesia:sync_dirty(F).
plain_password_required() ->
- false.
+ case is_scrammed() of
+ false -> false;
+ true -> true
+ end.
-storage_type() ->
- plain.
+store_type() ->
+ case is_scrammed() of
+ false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
+ 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}] ->
+ [#passwd{password = Password}] when is_list(Password) ->
Password /= "";
+ [#passwd{password = Scram}] when is_record(Scram, scram) ->
+ is_password_scram_valid(Password, Scram);
_ ->
false
end.
@@ -97,7 +108,20 @@ check_password(User, Server, Password, Digest, DigestGen) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
- [#passwd{password = Passwd}] ->
+ [#passwd{password = Passwd}] when is_list(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 = base64:decode(Scram#scram.storedkey),
DigRes = if
Digest /= "" ->
Digest == DigestGen(Passwd);
@@ -124,8 +148,12 @@ set_password(User, Server, Password) ->
{error, invalid_jid};
true ->
F = fun() ->
+ Password2 = case is_scrammed() and is_list(Password) of
+ true -> password_to_scram(Password);
+ false -> Password
+ end,
mnesia:write(#passwd{us = US,
- password = Password})
+ password = Password2})
end,
{atomic, ok} = mnesia:transaction(F),
ok
@@ -143,8 +171,12 @@ try_register(User, Server, Password) ->
F = fun() ->
case mnesia:read({passwd, US}) of
[] ->
+ Password2 = case is_scrammed() and is_list(Password) of
+ true -> password_to_scram(Password);
+ false -> Password
+ end,
mnesia:write(#passwd{us = US,
- password = Password}),
+ password = Password2}),
mnesia:dirty_update_counter(
reg_users_counter,
LServer, 1),
@@ -239,8 +271,13 @@ get_password(User, Server) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
- [#passwd{password = Password}] ->
+ [#passwd{password = Password}] when is_list(Password) ->
Password;
+ [#passwd{password = Scram}] when is_record(Scram, scram) ->
+ {base64:decode(Scram#scram.storedkey),
+ base64:decode(Scram#scram.serverkey),
+ base64:decode(Scram#scram.salt),
+ Scram#scram.iterationcount};
_ ->
false
end.
@@ -250,8 +287,10 @@ get_password_s(User, Server) ->
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
- [#passwd{password = Password}] ->
+ [#passwd{password = Password}] when is_list(Password) ->
Password;
+ [#passwd{password = Scram}] when is_record(Scram, scram) ->
+ [];
_ ->
[]
end.
@@ -293,13 +332,21 @@ remove_user(User, Server, Password) ->
US = {LUser, LServer},
F = fun() ->
case mnesia:read({passwd, US}) of
- [#passwd{password = Password}] ->
+ [#passwd{password = Password}] when is_list(Password) ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter,
LServer, -1),
ok;
- [_] ->
- not_allowed;
+ [#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
@@ -313,11 +360,11 @@ remove_user(User, Server, Password) ->
bad_request
end.
-
update_table() ->
Fields = record_info(fields, passwd),
case mnesia:table_info(passwd, attributes) of
Fields ->
+ maybe_scram_passwords(),
ok;
[user, password] ->
?INFO_MSG("Converting passwd table from "
@@ -356,5 +403,68 @@ update_table() ->
mnesia:transform_table(passwd, ignore, Fields)
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_local_option({auth_password_format, ?MYNAME}).
+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
+ 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 = base64:encode(StoredKey),
+ serverkey = base64:encode(ServerKey),
+ salt = base64:encode(Salt),
+ iterationcount = IterationCount}.
+
+is_password_scram_valid(Password, Scram) ->
+ IterationCount = Scram#scram.iterationcount,
+ Salt = base64:decode(Scram#scram.salt),
+ SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
+ StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
+ (base64:decode(Scram#scram.storedkey) == StoredKey).
diff --git a/src/ejabberd_auth_internal_scram.erl b/src/ejabberd_auth_internal_scram.erl
deleted file mode 100644
index f39ee4fb1..000000000
--- a/src/ejabberd_auth_internal_scram.erl
+++ /dev/null
@@ -1,369 +0,0 @@
--module(ejabberd_auth_internal_scram).
--author('stephen.roettger@googlemail.com').
-
-%% External exports
--export([start/1,
- set_password/3,
- check_password/3,
- check_password/5,
- 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,
- storage_type/0,
- plain_password_required/0
- ]).
-
--include("ejabberd.hrl").
-
--record(passwd, {us, stored_key, salt, iteration_count, server_key}).
--record(reg_users_counter, {vhost, count}).
-
--define(DEFAULT_ITERATION_COUNT, 4096).
--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(),
- update_reg_users_counter_table(Host),
- ok.
-
-update_reg_users_counter_table(Server) ->
- Set = get_vh_registered_users(Server),
- Size = length(Set),
- LServer = jlib:nameprep(Server),
- F = fun() ->
- mnesia:write(#reg_users_counter{vhost = LServer,
- count = Size})
- end,
- mnesia:sync_dirty(F).
-
-plain_password_required() ->
- true.
-
-storage_type() ->
- scram.
-
-check_password(User, Server, Password) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- [UserEntry] = (catch mnesia:dirty_read({passwd, US})),
- IterationCount = UserEntry#passwd.iteration_count,
- Salt = UserEntry#passwd.salt,
- SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
- StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
- if
- (UserEntry#passwd.stored_key == StoredKey) ->
- true;
- true ->
- false
- end.
-
-check_password(User, Server, Password, Digest, DigestGen) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- case catch mnesia:dirty_read({passwd, US}) of
- [#passwd{stored_key = Passwd}] ->
- DigRes = if
- Digest /= "" ->
- Digest == DigestGen(Passwd);
- true ->
- false
- end,
- if DigRes ->
- true;
- true ->
- (Passwd == Password) and (Password /= "")
- end;
- _ ->
- false
- end.
-
-%% @spec (User::string(), Server::string(), Password::string()) ->
-%% ok | {error, invalid_jid}
-set_password(User, Server, Password) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- Salt = crypto:rand_bytes(?SALT_LENGTH),
- IterationCount = ?DEFAULT_ITERATION_COUNT,
- SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
- StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
- ServerKey = scram:server_key(SaltedPassword),
- if
- (LUser == error) or (LServer == error) ->
- {error, invalid_jid};
- true ->
- F = fun() ->
- mnesia:write(#passwd{us = US,
- stored_key = StoredKey,
- salt = Salt,
- iteration_count = IterationCount,
- server_key = ServerKey})
- end,
- {atomic, ok} = mnesia:transaction(F),
- ok
- end.
-
-%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason}
-try_register(User, Server, Password) ->
- try_register(User, Server, Password, ?DEFAULT_ITERATION_COUNT).
-
-try_register(User, Server, Password, IterationCount) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- 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),
- if
- (LUser == error) or (LServer == error) ->
- {error, invalid_jid};
- true ->
- F = fun() ->
- case mnesia:read({passwd, US}) of
- [] ->
- mnesia:write(#passwd{us = US,
- stored_key = StoredKey,
- salt = Salt,
- iteration_count = IterationCount,
- server_key = ServerKey}),
- mnesia:dirty_update_counter(
- reg_users_counter,
- LServer, 1),
- ok;
- [_E] ->
- exists
- end
- end,
- mnesia:transaction(F)
- end.
-
-%% Get all registered users in Mnesia
-dirty_get_registered_users() ->
- mnesia:dirty_all_keys(passwd).
-
-get_vh_registered_users(Server) ->
- LServer = jlib:nameprep(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)
- end;
-
-get_vh_registered_users(Server, [{prefix, Prefix}])
- when is_list(Prefix) ->
- Set = [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
- lists:keysort(1, Set);
-
-get_vh_registered_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}])
- when is_list(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_list(Prefix) and is_integer(Limit) and is_integer(Offset) ->
- case [{U,S} || {U, S} <- get_vh_registered_users(Server), lists: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 = jlib: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_list(Prefix) ->
- Set = [{U, S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
- length(Set);
-
-get_vh_registered_users_number(Server, _) ->
- get_vh_registered_users_number(Server).
-
-get_password(User, Server) ->
- %false.
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- case catch mnesia:dirty_read(passwd, US) of
- [#passwd{stored_key = Password, server_key = ServerKey, salt = Salt, iteration_count = IterationCount}] ->
- {Password, ServerKey, Salt, IterationCount};
- _ ->
- false
- end.
-
-get_password_s(User, Server) ->
- %"".
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- case catch mnesia:dirty_read(passwd, US) of
- [#passwd{stored_key = Password, server_key = ServerKey, salt = Salt, iteration_count = IterationCount}] ->
- {Password, ServerKey, Salt, IterationCount};
- _ ->
- []
- end.
-
-%% @spec (User, Server) -> true | false | {error, Error}
-is_user_exists(User, Server) ->
- LUser = jlib:nodeprep(User),
- LServer = jlib: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 = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- 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 = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- F = fun() ->
- case mnesia:read({passwd, US}) of
- [UserEntry] ->
- IterationCount = UserEntry#passwd.iteration_count,
- Salt = UserEntry#passwd.salt,
- SaltedPassword = scram:salted_password(Password, Salt, IterationCount),
- StoredKey = scram:stored_key(scram:client_key(SaltedPassword)),
- if
- (UserEntry#passwd.stored_key == StoredKey) ->
- mnesia:delete({passwd, US}),
- mnesia:dirty_update_counter(reg_users_counter,
- LServer, -1),
- ok;
- true ->
- not_allowed
- end;
- _Else ->
- not_exists
- end
- 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 ->
- ok;
- [user, stored_key] ->
- ?INFO_MSG("Converting passwd table from "
- "{user, stored_key} format", []),
- Host = ?MYNAME,
- {atomic, ok} = mnesia:create_table(
- ejabberd_auth_internal_tmp_table,
- [{disc_only_copies, [node()]},
- {type, bag},
- {local_content, true},
- {record_name, passwd},
- {attributes, record_info(fields, passwd)}]),
- mnesia:transform_table(passwd, ignore, Fields),
- F1 = fun() ->
- mnesia:write_lock_table(ejabberd_auth_internal_tmp_table),
- mnesia:foldl(
- fun(#passwd{us = U} = R, _) ->
- mnesia:dirty_write(
- ejabberd_auth_internal_tmp_table,
- R#passwd{us = {U, Host}})
- end, ok, passwd)
- end,
- mnesia:transaction(F1),
- mnesia:clear_table(passwd),
- F2 = fun() ->
- mnesia:write_lock_table(passwd),
- mnesia:foldl(
- fun(R, _) ->
- mnesia:dirty_write(R)
- end, ok, ejabberd_auth_internal_tmp_table)
- end,
- mnesia:transaction(F2),
- mnesia:delete_table(ejabberd_auth_internal_tmp_table);
- _ ->
- ?INFO_MSG("Recreating passwd table", []),
- mnesia:transform_table(passwd, ignore, Fields)
- end.
-
-
-
diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl
index da928599b..77216a1cd 100644
--- a/src/ejabberd_auth_ldap.erl
+++ b/src/ejabberd_auth_ldap.erl
@@ -54,7 +54,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
- storage_type/0,
+ store_type/0,
plain_password_required/0
]).
@@ -138,7 +138,7 @@ init(Host) ->
plain_password_required() ->
true.
-storage_type() ->
+store_type() ->
external.
check_password(User, Server, Password) ->
diff --git a/src/ejabberd_auth_odbc.erl b/src/ejabberd_auth_odbc.erl
index 60e6b8059..e2d43a449 100644
--- a/src/ejabberd_auth_odbc.erl
+++ b/src/ejabberd_auth_odbc.erl
@@ -43,7 +43,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
- storage_type/0,
+ store_type/0,
plain_password_required/0
]).
@@ -58,7 +58,7 @@ start(_Host) ->
plain_password_required() ->
false.
-storage_type() ->
+store_type() ->
plain.
%% @spec (User, Server, Password) -> true | false | {error, Error}
diff --git a/src/ejabberd_auth_pam.erl b/src/ejabberd_auth_pam.erl
index a00d9e816..3ff59016c 100644
--- a/src/ejabberd_auth_pam.erl
+++ b/src/ejabberd_auth_pam.erl
@@ -39,7 +39,7 @@
is_user_exists/2,
remove_user/2,
remove_user/3,
- storage_type/0,
+ store_type/0,
plain_password_required/0
]).
@@ -107,7 +107,7 @@ remove_user(_User, _Server, _Password) ->
plain_password_required() ->
true.
-storage_type() ->
+store_type() ->
external.
%%====================================================================
diff --git a/src/ejabberd_piefxis.erl b/src/ejabberd_piefxis.erl
index 02697163b..cf5178bbe 100644
--- a/src/ejabberd_piefxis.erl
+++ b/src/ejabberd_piefxis.erl
@@ -159,13 +159,13 @@ process_element(El=#xmlel{name=user, ns=_XMLNS},
State;
process_element(H=#xmlel{name=host},State) ->
- State#parsing_state{host=?BTL(exmpp_xml:get_attribute(H,"jid",none))};
+ State#parsing_state{host=?BTL(exmpp_xml:get_attribute(H, <<"jid">>, none))};
process_element(#xmlel{name='server-data'},State) ->
State;
process_element(El=#xmlel{name=include, ns=?NS_XINCLUDE}, State=#parsing_state{dir=Dir}) ->
- case exmpp_xml:get_attribute(El, href, none) of
+ case exmpp_xml:get_attribute(El, <<"href">>, none) of
none ->
ok;
HrefB ->
@@ -194,25 +194,26 @@ process_element(El,State) ->
%%%% Add user
add_user(El, Domain) ->
- User = exmpp_xml:get_attribute(El,name,none),
- Password = exmpp_xml:get_attribute(El,password,none),
- add_user(El, Domain, User, Password).
+ User = exmpp_xml:get_attribute(El, <<"name">>, none),
+ PasswordFormat = exmpp_xml:get_attribute(El, <<"password-format">>, none),
+ Password = exmpp_xml:get_attribute(El, <<"password">>, none),
+ add_user(El, Domain, User, PasswordFormat, Password).
%% @spec (El::xmlel(), Domain::string(), User::binary(), Password::binary() | none)
%% -> ok | {error, ErrorText::string()}
%% @doc Add a new user to the database.
%% If user already exists, it will be only updated.
-add_user(El, Domain, UserBinary, none) ->
+add_user(El, Domain, UserBinary, <<"plaintext">>, none) ->
User = ?BTL(UserBinary),
io:format("Account ~s@~s will not be created, updating it...~n",
[User, Domain]),
io:format(""),
populate_user_with_elements(El, Domain, User),
ok;
-add_user(El, Domain, UserBinary, PasswordBinary) ->
+add_user(El, Domain, UserBinary, PasswordFormat, PasswordBinary) ->
User = ?BTL(UserBinary),
- Password = ?BTL(PasswordBinary),
- case create_user(User,Password,Domain) of
+ Password2 = prepare_password(PasswordFormat, PasswordBinary, El),
+ case create_user(User,Password2,Domain) of
ok ->
populate_user_with_elements(El, Domain, User),
ok;
@@ -227,6 +228,21 @@ add_user(El, Domain, UserBinary, PasswordBinary) ->
{error, Other}
end.
+prepare_password(<<"plaintext">>, PasswordBinary, _El) ->
+ ?BTL(PasswordBinary);
+prepare_password(<<"scram">>, none, El) ->
+ ScramEl = exmpp_xml:get_element(El, 'scram-hash'),
+ #scram{storedkey = base64:decode(exmpp_xml:get_attribute(
+ ScramEl, <<"stored-key">>, none)),
+ serverkey = base64:decode(exmpp_xml:get_attribute(
+ ScramEl, <<"server-key">>, none)),
+ salt = base64:decode(exmpp_xml:get_attribute(
+ ScramEl, <<"salt">>, none)),
+ iterationcount = list_to_integer(exmpp_xml:get_attribute_as_list(
+ ScramEl, <<"iteration-count">>,
+ ?SCRAM_DEFAULT_ITERATION_COUNT))
+ }.
+
populate_user_with_elements(El, Domain, User) ->
exmpp_xml:foreach(
fun (_,Child) ->
@@ -346,7 +362,7 @@ populate_user(User,Domain,El=#xmlel{name='offline-messages'}) ->
fun (_Element, {xmlcdata, _}) ->
ok;
(_Element, Child) ->
- From = exmpp_xml:get_attribute(Child,from,none),
+ From = exmpp_xml:get_attribute(Child, <<"from">>,none),
FullFrom = jid_to_old_jid(exmpp_jid:parse(From)),
FullUser = jid_to_old_jid(exmpp_jid:make(User,
Domain)),
@@ -538,10 +554,23 @@ export_user(Fd, Username, Host) ->
%% @spec (Username::string(), Host::string()) -> string()
extract_user(Username, Host) ->
- Password = ejabberd_auth:get_password_s(Username, Host),
+ Password = ejabberd_auth:get_password(Username, Host),
+ PasswordStr = build_password_string(Password),
UserInfo = [extract_user_info(InfoName, Username, Host) || InfoName <- [roster, offline, private, vcard]],
UserInfoString = lists:flatten(UserInfo),
- io_lib:format("<user name='~s' password='~s'>~s</user>", [Username, Password, UserInfoString]).
+ io_lib:format("<user name='~s' ~s ~s</user>",
+ [Username, PasswordStr, UserInfoString]).
+
+build_password_string({StoredKey, ServerKey, Salt, IterationCount}) ->
+ io_lib:format("password-format='scram'>"
+ "<scram-hash stored-key='~s' server-key='~s' "
+ "salt='~s' iteration-count='~w'/> ",
+ [base64:encode_to_string(StoredKey),
+ base64:encode_to_string(ServerKey),
+ base64:encode_to_string(Salt),
+ IterationCount]);
+build_password_string(Password) when is_list(Password) ->
+ io_lib:format("password-format='plaintext' password='~s'>", [Password]).
%% @spec (InfoName::atom(), Username::string(), Host::string()) -> string()
extract_user_info(roster, Username, Host) ->