]> granicus.if.org Git - ejabberd/commitdiff
Fix and improve support for SCRAM auth method (EJAB-1196)
authorBadlop <badlop@process-one.net>
Mon, 15 Aug 2011 22:26:49 +0000 (00:26 +0200)
committerBadlop <badlop@process-one.net>
Mon, 15 Aug 2011 22:26:49 +0000 (00:26 +0200)
14 files changed:
doc/guide.tex
src/cyrsasl.erl
src/cyrsasl_scram.erl
src/ejabberd.cfg.example
src/ejabberd.hrl
src/ejabberd_auth.erl
src/ejabberd_auth_anonymous.erl
src/ejabberd_auth_external.erl
src/ejabberd_auth_internal.erl
src/ejabberd_auth_internal_scram.erl [deleted file]
src/ejabberd_auth_ldap.erl
src/ejabberd_auth_odbc.erl
src/ejabberd_auth_pam.erl
src/ejabberd_piefxis.erl

index 5939a9eec80a0ac7a3246d956687b00f7a8eba06..49cfd5bf88b9da0a5ee2cf4f43bccb26368992b4 100644 (file)
@@ -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}
 
index 627ee4d32f582a2c1da5a32493aba2f9d0fe1bb3..28098f76305e1c0769bdd9298b6a6ea33021fad2 100644 (file)
@@ -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,
index f0649f651006e8f11b4ac01f519b71d279486b95..270cce26fde21aaa1e7abd23df128d3f9299be77 100644 (file)
@@ -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 ->
index 92e78e32d11d4569ab50006568bfc4ff3b91e42b..57eab41bd5dbdae4f508284ee31cc89a406ad7ac 100644 (file)
 %% 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
index b1bf516cdbab38e2ec18200a938f70cec69cf8bb..9f8978be88f7e63c5dc7916143b27fcec845b97e 100644 (file)
@@ -37,6 +37,9 @@
 
 %%-define(DBGFSM, true).
 
+-record(scram, {storedkey, serverkey, salt, iterationcount}).
+-define(SCRAM_DEFAULT_ITERATION_COUNT, 4096).
+
 %% ---------------------------------
 %% Logging mechanism
 
index d937340f1295b102d2b5af7f6c45b6421ecef9c1..1eab9af838dd12d3780a85a9cdf4cfae0bddf8d6 100644 (file)
@@ -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.
index d3ecffc1b97d6ba107bf40a96eb066950a2db9b3..5295ab99ffc0a111e2d7039f1365cbba48b1b64e 100644 (file)
@@ -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.
index b625af84c6967f66d0eb0d8fc1e113ea892a31be..c4208b09e3083168f8c174e28348157ca48e8a36 100644 (file)
@@ -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) ->
index 847737f29b880216b7e5226c7482f36079509928..b196d3526b6518825294841bd33d26ebe3f720f0 100644 (file)
@@ -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 (file)
index f39ee4f..0000000
+++ /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.
-
-
-
index da928599b9636c15b1c28b8bc4e2005b29f1c985..77216a1cd95a1309517643baa912eb1afe6eaf05 100644 (file)
@@ -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) ->
index 60e6b80591315b6aa66b39a64573354a68c17716..e2d43a44969399a4f583430db0819c8dea91ca25 100644 (file)
@@ -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}
index a00d9e81662a852b1122e18774cba02916b6d5f1..3ff59016c8ddeacbfc8a71416f92bd3d5379920a 100644 (file)
@@ -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.
 
 %%====================================================================
index 02697163bb2de2f9e2bcd6d7755abd0aa787207d..cf5178bbe168cdab58a6242b6a7578a737ea4704 100644 (file)
@@ -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) ->