mirror of
https://github.com/processone/ejabberd
synced 2025-10-03 17:59:31 +02:00
Add support for storing multiple passwords formats per user
This adds option 'auth_stored_password_types' that can be used to setup storage of multiple passwords for each user. When this is set, on each password set, database will now store password in each format specified.
This commit is contained in:
parent
ced72f4a89
commit
7862c6a7db
20 changed files with 553 additions and 270 deletions
|
@ -18,5 +18,5 @@
|
||||||
%%%
|
%%%
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | {binary(), binary(), atom()} | '$1',
|
||||||
password = <<"">> :: binary() | scram() | '_'}).
|
password = <<"">> :: binary() | scram() | '_'}).
|
||||||
|
|
|
@ -77,7 +77,7 @@
|
||||||
{stringprep, "~> 1.0.29", {git, "https://github.com/processone/stringprep", {tag, "1.0.30"}}},
|
{stringprep, "~> 1.0.29", {git, "https://github.com/processone/stringprep", {tag, "1.0.30"}}},
|
||||||
{if_var_true, stun,
|
{if_var_true, stun,
|
||||||
{stun, "~> 1.2.12", {git, "https://github.com/processone/stun", {tag, "1.2.15"}}}},
|
{stun, "~> 1.2.12", {git, "https://github.com/processone/stun", {tag, "1.2.15"}}}},
|
||||||
{xmpp, "~> 1.9.2", {git, "https://github.com/processone/xmpp", "be24923968261c2661a9e116650dce5ac95c9d23"}},
|
{xmpp, "~> 1.9.2", {git, "https://github.com/processone/xmpp", "8929be60aa0c56653b13f5fcada1e460337a016b"}},
|
||||||
{yconf, "~> 1.0.17", {git, "https://github.com/processone/yconf", "9682a6025ed543eedf34637e4cfcc66837074af6"}}
|
{yconf, "~> 1.0.17", {git, "https://github.com/processone/yconf", "9682a6025ed543eedf34637e4cfcc66837074af6"}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1},
|
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1},
|
||||||
{<<"xmpp">>,
|
{<<"xmpp">>,
|
||||||
{git,"https://github.com/processone/xmpp",
|
{git,"https://github.com/processone/xmpp",
|
||||||
{ref,"be24923968261c2661a9e116650dce5ac95c9d23"}},
|
{ref,"8929be60aa0c56653b13f5fcada1e460337a016b"}},
|
||||||
0},
|
0},
|
||||||
{<<"yconf">>,
|
{<<"yconf">>,
|
||||||
{git,"https://github.com/processone/yconf",
|
{git,"https://github.com/processone/yconf",
|
||||||
|
|
|
@ -18,13 +18,14 @@
|
||||||
|
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
username text NOT NULL,
|
username text NOT NULL,
|
||||||
|
type smallint,
|
||||||
server_host text NOT NULL,
|
server_host text NOT NULL,
|
||||||
password text NOT NULL,
|
password text NOT NULL,
|
||||||
serverkey text NOT NULL DEFAULT '',
|
serverkey text NOT NULL DEFAULT '',
|
||||||
salt text NOT NULL DEFAULT '',
|
salt text NOT NULL DEFAULT '',
|
||||||
iterationcount integer NOT NULL DEFAULT 0,
|
iterationcount integer NOT NULL DEFAULT 0,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (server_host, username)
|
PRIMARY KEY (server_host, username, type)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,14 @@
|
||||||
--
|
--
|
||||||
|
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
username text PRIMARY KEY,
|
username text,
|
||||||
|
type smallint,
|
||||||
password text NOT NULL,
|
password text NOT NULL,
|
||||||
serverkey text NOT NULL DEFAULT '',
|
serverkey text NOT NULL DEFAULT '',
|
||||||
salt text NOT NULL DEFAULT '',
|
salt text NOT NULL DEFAULT '',
|
||||||
iterationcount integer NOT NULL DEFAULT 0,
|
iterationcount integer NOT NULL DEFAULT 0,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
primary key (username, type)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -403,6 +403,7 @@ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW
|
||||||
CREATE TABLE [dbo].[users] (
|
CREATE TABLE [dbo].[users] (
|
||||||
[username] [varchar] (250) NOT NULL,
|
[username] [varchar] (250) NOT NULL,
|
||||||
[server_host] [varchar] (250) NOT NULL,
|
[server_host] [varchar] (250) NOT NULL,
|
||||||
|
[type] [smallint] NOT NULL,
|
||||||
[password] [text] NOT NULL,
|
[password] [text] NOT NULL,
|
||||||
[serverkey] [text] NOT NULL DEFAULT '',
|
[serverkey] [text] NOT NULL DEFAULT '',
|
||||||
[salt] [text] NOT NULL DEFAULT '',
|
[salt] [text] NOT NULL DEFAULT '',
|
||||||
|
@ -411,7 +412,8 @@ CREATE TABLE [dbo].[users] (
|
||||||
CONSTRAINT [users_PRIMARY] PRIMARY KEY CLUSTERED
|
CONSTRAINT [users_PRIMARY] PRIMARY KEY CLUSTERED
|
||||||
(
|
(
|
||||||
[server_host] ASC,
|
[server_host] ASC,
|
||||||
[username] ASC
|
[username] ASC,
|
||||||
|
[type] ASC
|
||||||
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
||||||
) TEXTIMAGE_ON [PRIMARY];
|
) TEXTIMAGE_ON [PRIMARY];
|
||||||
|
|
||||||
|
|
|
@ -379,6 +379,7 @@ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW
|
||||||
|
|
||||||
CREATE TABLE [dbo].[users] (
|
CREATE TABLE [dbo].[users] (
|
||||||
[username] [varchar] (250) NOT NULL,
|
[username] [varchar] (250) NOT NULL,
|
||||||
|
[type] [smallint] NOT NULL,
|
||||||
[password] [text] NOT NULL,
|
[password] [text] NOT NULL,
|
||||||
[serverkey] [text] NOT NULL DEFAULT '',
|
[serverkey] [text] NOT NULL DEFAULT '',
|
||||||
[salt] [text] NOT NULL DEFAULT '',
|
[salt] [text] NOT NULL DEFAULT '',
|
||||||
|
@ -386,7 +387,8 @@ CREATE TABLE [dbo].[users] (
|
||||||
[created_at] [datetime] NOT NULL DEFAULT GETDATE(),
|
[created_at] [datetime] NOT NULL DEFAULT GETDATE(),
|
||||||
CONSTRAINT [users_PRIMARY] PRIMARY KEY CLUSTERED
|
CONSTRAINT [users_PRIMARY] PRIMARY KEY CLUSTERED
|
||||||
(
|
(
|
||||||
[username] ASC
|
[username] ASC,
|
||||||
|
[type] ASC
|
||||||
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
||||||
) TEXTIMAGE_ON [PRIMARY];
|
) TEXTIMAGE_ON [PRIMARY];
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,14 @@
|
||||||
|
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
username varchar(191) NOT NULL,
|
username varchar(191) NOT NULL,
|
||||||
|
type smallint NOT NULL,
|
||||||
server_host varchar(191) NOT NULL,
|
server_host varchar(191) NOT NULL,
|
||||||
password text NOT NULL,
|
password text NOT NULL,
|
||||||
serverkey varchar(128) NOT NULL DEFAULT '',
|
serverkey varchar(128) NOT NULL DEFAULT '',
|
||||||
salt varchar(128) NOT NULL DEFAULT '',
|
salt varchar(128) NOT NULL DEFAULT '',
|
||||||
iterationcount integer NOT NULL DEFAULT 0,
|
iterationcount integer NOT NULL DEFAULT 0,
|
||||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (server_host(191), username)
|
PRIMARY KEY (server_host(191), username, type)
|
||||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
-- Add support for SCRAM auth to a database created before ejabberd 16.03:
|
-- Add support for SCRAM auth to a database created before ejabberd 16.03:
|
||||||
|
|
|
@ -17,12 +17,14 @@
|
||||||
--
|
--
|
||||||
|
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
username varchar(191) PRIMARY KEY,
|
username varchar(191) NOT NULL,
|
||||||
|
type smallint NOT NULL,,
|
||||||
password text NOT NULL,
|
password text NOT NULL,
|
||||||
serverkey varchar(128) NOT NULL DEFAULT '',
|
serverkey varchar(128) NOT NULL DEFAULT '',
|
||||||
salt varchar(128) NOT NULL DEFAULT '',
|
salt varchar(128) NOT NULL DEFAULT '',
|
||||||
iterationcount integer NOT NULL DEFAULT 0,
|
iterationcount integer NOT NULL DEFAULT 0,
|
||||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (username, type)
|
||||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
-- Add support for SCRAM auth to a database created before ejabberd 16.03:
|
-- Add support for SCRAM auth to a database created before ejabberd 16.03:
|
||||||
|
|
|
@ -172,12 +172,13 @@
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
username text NOT NULL,
|
username text NOT NULL,
|
||||||
server_host text NOT NULL,
|
server_host text NOT NULL,
|
||||||
|
"type" smallint NOT NULL,
|
||||||
"password" text NOT NULL,
|
"password" text NOT NULL,
|
||||||
serverkey text NOT NULL DEFAULT '',
|
serverkey text NOT NULL DEFAULT '',
|
||||||
salt text NOT NULL DEFAULT '',
|
salt text NOT NULL DEFAULT '',
|
||||||
iterationcount integer NOT NULL DEFAULT 0,
|
iterationcount integer NOT NULL DEFAULT 0,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
PRIMARY KEY (server_host, username)
|
PRIMARY KEY (server_host, username, "type")
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Add support for SCRAM auth to a database created before ejabberd 16.03:
|
-- Add support for SCRAM auth to a database created before ejabberd 16.03:
|
||||||
|
|
|
@ -17,12 +17,14 @@
|
||||||
--
|
--
|
||||||
|
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
username text PRIMARY KEY,
|
username text NOT NULL,
|
||||||
|
"type" smallint NOT NULL
|
||||||
"password" text NOT NULL,
|
"password" text NOT NULL,
|
||||||
serverkey text NOT NULL DEFAULT '',
|
serverkey text NOT NULL DEFAULT '',
|
||||||
salt text NOT NULL DEFAULT '',
|
salt text NOT NULL DEFAULT '',
|
||||||
iterationcount integer NOT NULL DEFAULT 0,
|
iterationcount integer NOT NULL DEFAULT 0,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT now()
|
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
PRIMARY KEY (username, "type")
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Add support for SCRAM auth to a database created before ejabberd 16.03:
|
-- Add support for SCRAM auth to a database created before ejabberd 16.03:
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
-export([auth_modules/1, convert_to_scram/1]).
|
-export([auth_modules/1, convert_to_scram/1, drop_password_type/2, set_password_instance/3]).
|
||||||
|
|
||||||
-include_lib("xmpp/include/scram.hrl").
|
-include_lib("xmpp/include/scram.hrl").
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
|
@ -76,26 +76,38 @@
|
||||||
-callback store_type(binary()) -> plain | external | scram.
|
-callback store_type(binary()) -> plain | external | scram.
|
||||||
-callback set_password(binary(), binary(), password()) ->
|
-callback set_password(binary(), binary(), password()) ->
|
||||||
{ets_cache:tag(), {ok, password()} | {error, db_failure | not_allowed}}.
|
{ets_cache:tag(), {ok, password()} | {error, db_failure | not_allowed}}.
|
||||||
|
-callback set_password_multiple(binary(), binary(), [password()]) ->
|
||||||
|
{ets_cache:tag(), {ok, [password()]} | {error, db_failure | not_allowed}}.
|
||||||
|
-callback set_password_instance(binary(), binary(), password()) ->
|
||||||
|
ok | {error, db_failure | not_allowed}.
|
||||||
-callback remove_user(binary(), binary()) -> ok | {error, db_failure | not_allowed}.
|
-callback remove_user(binary(), binary()) -> ok | {error, db_failure | not_allowed}.
|
||||||
-callback user_exists(binary(), binary()) -> {ets_cache:tag(), boolean() | {error, db_failure}}.
|
-callback user_exists(binary(), binary()) -> {ets_cache:tag(), boolean() | {error, db_failure}}.
|
||||||
-callback check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean() | {stop, boolean()}}.
|
-callback check_password(binary(), binary(), binary(), binary()) -> {ets_cache:tag(), boolean() | {stop, boolean()}}.
|
||||||
-callback try_register(binary(), binary(), password()) ->
|
-callback try_register(binary(), binary(), password()) ->
|
||||||
{ets_cache:tag(), {ok, password()} | {error, exists | db_failure | not_allowed}}.
|
{ets_cache:tag(), {ok, password()} | {error, exists | db_failure | not_allowed}}.
|
||||||
|
-callback try_register_multiple(binary(), binary(), [password()]) ->
|
||||||
|
{ets_cache:tag(), {ok, [password()]} | {error, exists | db_failure | not_allowed}}.
|
||||||
-callback get_users(binary(), opts()) -> [{binary(), binary()}].
|
-callback get_users(binary(), opts()) -> [{binary(), binary()}].
|
||||||
-callback count_users(binary(), opts()) -> number().
|
-callback count_users(binary(), opts()) -> number().
|
||||||
-callback get_password(binary(), binary()) -> {ets_cache:tag(), {ok, password()} | error}.
|
-callback get_password(binary(), binary()) -> {ets_cache:tag(), {ok, password() | [password()]} | error}.
|
||||||
|
-callback drop_password_type(binary(), atom()) ->
|
||||||
|
ok | {error, db_failure | not_allowed}.
|
||||||
-callback use_cache(binary()) -> boolean().
|
-callback use_cache(binary()) -> boolean().
|
||||||
-callback cache_nodes(binary()) -> boolean().
|
-callback cache_nodes(binary()) -> boolean().
|
||||||
|
|
||||||
-optional_callbacks([reload/1,
|
-optional_callbacks([reload/1,
|
||||||
set_password/3,
|
set_password/3,
|
||||||
|
set_password_multiple/3,
|
||||||
|
set_password_instance/3,
|
||||||
remove_user/2,
|
remove_user/2,
|
||||||
user_exists/2,
|
user_exists/2,
|
||||||
check_password/4,
|
check_password/4,
|
||||||
try_register/3,
|
try_register/3,
|
||||||
|
try_register_multiple/3,
|
||||||
get_users/2,
|
get_users/2,
|
||||||
count_users/2,
|
count_users/2,
|
||||||
get_password/2,
|
get_password/2,
|
||||||
|
drop_password_type/2,
|
||||||
use_cache/1,
|
use_cache/1,
|
||||||
cache_nodes/1]).
|
cache_nodes/1]).
|
||||||
|
|
||||||
|
@ -265,15 +277,41 @@ check_password_with_authmodule(User, AuthzId, Server, Password, Digest, DigestGe
|
||||||
false
|
false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
convert_password_for_storage(_Server, #scram{} = Password) ->
|
||||||
|
{Password, [Password]};
|
||||||
|
convert_password_for_storage(Server, Password) ->
|
||||||
|
P = case ejabberd_option:auth_stored_password_types(Server) of
|
||||||
|
[] ->
|
||||||
|
case ejabberd_option:auth_password_format(Server) of
|
||||||
|
plain ->
|
||||||
|
[Password];
|
||||||
|
_ ->
|
||||||
|
[password_to_scram(Server, Password)]
|
||||||
|
end;
|
||||||
|
M ->
|
||||||
|
lists:sort(lists:map(
|
||||||
|
fun(scram_sha1) ->
|
||||||
|
password_to_scram(Server, Password, sha, ?SCRAM_DEFAULT_ITERATION_COUNT);
|
||||||
|
(scram_sha256) ->
|
||||||
|
password_to_scram(Server, Password, sha256, ?SCRAM_DEFAULT_ITERATION_COUNT);
|
||||||
|
(scram_sha512) ->
|
||||||
|
password_to_scram(Server, Password, sha512, ?SCRAM_DEFAULT_ITERATION_COUNT);
|
||||||
|
(plain) ->
|
||||||
|
Password
|
||||||
|
end, M))
|
||||||
|
end,
|
||||||
|
{Password, P}.
|
||||||
|
|
||||||
-spec set_password(binary(), binary(), password()) -> ok | {error,
|
-spec set_password(binary(), binary(), password()) -> ok | {error,
|
||||||
db_failure | not_allowed |
|
db_failure | not_allowed |
|
||||||
invalid_jid | invalid_password}.
|
invalid_jid | invalid_password}.
|
||||||
set_password(User, Server, Password) ->
|
set_password(User, Server, Password) ->
|
||||||
case validate_credentials(User, Server, Password) of
|
case validate_credentials(User, Server, Password) of
|
||||||
{ok, LUser, LServer} ->
|
{ok, LUser, LServer} ->
|
||||||
|
{Plain, Passwords} = convert_password_for_storage(Server, Password),
|
||||||
lists:foldl(
|
lists:foldl(
|
||||||
fun(M, {error, _}) ->
|
fun(M, {error, _}) ->
|
||||||
db_set_password(LUser, LServer, Password, M);
|
db_set_password(LUser, LServer, Plain, Passwords, M);
|
||||||
(_, ok) ->
|
(_, ok) ->
|
||||||
ok
|
ok
|
||||||
end, {error, not_allowed}, auth_modules(LServer));
|
end, {error, not_allowed}, auth_modules(LServer));
|
||||||
|
@ -281,6 +319,32 @@ set_password(User, Server, Password) ->
|
||||||
Err
|
Err
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
set_password_instance(User, Server, Password) ->
|
||||||
|
case validate_credentials(User, Server, Password) of
|
||||||
|
{ok, LUser, LServer} ->
|
||||||
|
lists:foldl(
|
||||||
|
fun(Mod, {error, _} = Acc) ->
|
||||||
|
case erlang:function_exported(Mod, set_password_instance, 3) of
|
||||||
|
true ->
|
||||||
|
R = Mod:set_password_instance(LUser, LServer, Password),
|
||||||
|
case use_cache(Mod, LServer) of
|
||||||
|
true ->
|
||||||
|
ets_cache:delete(cache_tab(Mod), {LUser, LServer},
|
||||||
|
cache_nodes(Mod, LServer));
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
R;
|
||||||
|
_ ->
|
||||||
|
Acc
|
||||||
|
end;
|
||||||
|
(_, ok) ->
|
||||||
|
ok
|
||||||
|
end, {error, not_allowed}, auth_modules(LServer));
|
||||||
|
Err ->
|
||||||
|
Err
|
||||||
|
end.
|
||||||
|
|
||||||
-spec try_register(binary(), binary(), password()) -> ok | {error,
|
-spec try_register(binary(), binary(), password()) -> ok | {error,
|
||||||
db_failure | not_allowed | exists |
|
db_failure | not_allowed | exists |
|
||||||
invalid_jid | invalid_password}.
|
invalid_jid | invalid_password}.
|
||||||
|
@ -293,22 +357,23 @@ try_register(User, Server, Password) ->
|
||||||
false ->
|
false ->
|
||||||
case ejabberd_router:is_my_host(LServer) of
|
case ejabberd_router:is_my_host(LServer) of
|
||||||
true ->
|
true ->
|
||||||
case ejabberd_hooks:run_fold(check_register_user, LServer, true, [User, Server, Password]) of
|
case ejabberd_hooks:run_fold(check_register_user, LServer, true,
|
||||||
|
[User, Server, Password]) of
|
||||||
true ->
|
true ->
|
||||||
case lists:foldl(
|
{Plain, Passwords} = convert_password_for_storage(Server, Password),
|
||||||
fun(_, ok) ->
|
case lists:foldl(
|
||||||
ok;
|
fun(_, ok) ->
|
||||||
(Mod, _) ->
|
ok;
|
||||||
db_try_register(
|
(Mod, _) ->
|
||||||
LUser, LServer, Password, Mod)
|
db_try_register(
|
||||||
end, {error, not_allowed},
|
LUser, LServer, Plain, Passwords, Mod)
|
||||||
auth_modules(LServer)) of
|
end, {error, not_allowed}, auth_modules(LServer)) of
|
||||||
ok ->
|
ok ->
|
||||||
ejabberd_hooks:run(
|
ejabberd_hooks:run(
|
||||||
register_user, LServer, [LUser, LServer]);
|
register_user, LServer, [LUser, LServer]);
|
||||||
{error, _} = Err ->
|
{error, _} = Err ->
|
||||||
Err
|
Err
|
||||||
end;
|
end;
|
||||||
false ->
|
false ->
|
||||||
{error, not_allowed}
|
{error, not_allowed}
|
||||||
end;
|
end;
|
||||||
|
@ -356,32 +421,27 @@ count_users(Server, Opts) ->
|
||||||
auth_modules(LServer)))
|
auth_modules(LServer)))
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_password(binary(), binary()) -> false | password().
|
-spec get_password(binary(), binary()) -> false | [password()].
|
||||||
get_password(User, Server) ->
|
get_password(User, Server) ->
|
||||||
case validate_credentials(User, Server) of
|
case get_password_with_authmodule(User, Server) of
|
||||||
{ok, LUser, LServer} ->
|
{Passwords, _} -> Passwords
|
||||||
case lists:foldl(
|
|
||||||
fun(M, error) -> db_get_password(LUser, LServer, M);
|
|
||||||
(_M, Acc) -> Acc
|
|
||||||
end, error, auth_modules(LServer)) of
|
|
||||||
{ok, Password} ->
|
|
||||||
Password;
|
|
||||||
error ->
|
|
||||||
false
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
false
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_password_s(binary(), binary()) -> password().
|
-spec get_password_s(binary(), binary()) -> password().
|
||||||
get_password_s(User, Server) ->
|
get_password_s(User, Server) ->
|
||||||
case get_password(User, Server) of
|
case get_password(User, Server) of
|
||||||
false -> <<"">>;
|
false -> <<"">>;
|
||||||
Password -> Password
|
Passwords ->
|
||||||
|
{_, Pass} = lists:foldl(
|
||||||
|
fun(Plain, _) when is_binary(Plain) -> {true, Plain};
|
||||||
|
(Pass, {false, _}) -> {true, Pass};
|
||||||
|
(_, Acc) -> Acc
|
||||||
|
end, {false, <<"">>}, Passwords),
|
||||||
|
Pass
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_password_with_authmodule(binary(), binary()) ->
|
-spec get_password_with_authmodule(binary(), binary()) ->
|
||||||
{false | {false, atom(), binary()} | password(), module()}.
|
{false | {false, atom(), binary()} | [password()], module()}.
|
||||||
get_password_with_authmodule(User, Server) ->
|
get_password_with_authmodule(User, Server) ->
|
||||||
case validate_credentials(User, Server) of
|
case validate_credentials(User, Server) of
|
||||||
{ok, LUser, LServer} ->
|
{ok, LUser, LServer} ->
|
||||||
|
@ -395,8 +455,10 @@ get_password_with_authmodule(User, Server) ->
|
||||||
(_M, Acc) ->
|
(_M, Acc) ->
|
||||||
Acc
|
Acc
|
||||||
end, {error, undefined}, auth_modules(LServer)) of
|
end, {error, undefined}, auth_modules(LServer)) of
|
||||||
{{ok, Password}, Module} ->
|
{{ok, Password}, Module} when is_list(Password) ->
|
||||||
{Password, Module};
|
{Password, Module};
|
||||||
|
{{ok, Password}, Module} ->
|
||||||
|
{[Password], Module};
|
||||||
{error, Module} ->
|
{error, Module} ->
|
||||||
{false, Module}
|
{false, Module}
|
||||||
end
|
end
|
||||||
|
@ -455,6 +517,30 @@ user_exists_in_other_modules_loop([AuthModule | AuthModules], User, Server) ->
|
||||||
maybe_exists
|
maybe_exists
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
drop_password_type(LServer, Type) ->
|
||||||
|
Hash = case Type of
|
||||||
|
plain -> plain;
|
||||||
|
scram_sha1 -> sha;
|
||||||
|
scram_sha256 -> sha256;
|
||||||
|
scram_sha512 -> sha512
|
||||||
|
end,
|
||||||
|
lists:foreach(
|
||||||
|
fun(M) ->
|
||||||
|
case erlang:function_exported(M, drop_password_type, 2) of
|
||||||
|
true ->
|
||||||
|
M:drop_password_type(LServer, Hash),
|
||||||
|
case use_cache(M, LServer) of
|
||||||
|
true ->
|
||||||
|
ets_cache:clear(cache_tab(M),
|
||||||
|
cache_nodes(M, LServer));
|
||||||
|
false ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
end, auth_modules(LServer)).
|
||||||
|
|
||||||
-spec which_users_exists(list({binary(), binary()})) -> list({binary(), binary()}).
|
-spec which_users_exists(list({binary(), binary()})) -> list({binary(), binary()}).
|
||||||
which_users_exists(USPairs) ->
|
which_users_exists(USPairs) ->
|
||||||
ByServer = lists:foldl(
|
ByServer = lists:foldl(
|
||||||
|
@ -592,54 +678,86 @@ get_is_banned(User, Server) ->
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% Backend calls
|
%%% Backend calls
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
-spec db_try_register(binary(), binary(), password(), module()) -> ok | {error, exists | db_failure | not_allowed}.
|
-spec db_try_register(binary(), binary(), binary(), [password()], module()) -> ok | {error, exists | db_failure | not_allowed}.
|
||||||
db_try_register(User, Server, Password, Mod) ->
|
db_try_register(User, Server, PlainPassword, Passwords, Mod) ->
|
||||||
case erlang:function_exported(Mod, try_register, 3) of
|
Ret = case erlang:function_exported(Mod, try_register_multiple, 3) of
|
||||||
true ->
|
true ->
|
||||||
Password1 = case Mod:store_type(Server) of
|
case use_cache(Mod, Server) of
|
||||||
scram -> password_to_scram(Server, Password);
|
true ->
|
||||||
_ -> Password
|
ets_cache:update(
|
||||||
end,
|
cache_tab(Mod), {User, Server}, {ok, Passwords},
|
||||||
Ret = case use_cache(Mod, Server) of
|
fun() -> Mod:try_register_multiple(User, Server, Passwords) end,
|
||||||
true ->
|
cache_nodes(Mod, Server));
|
||||||
ets_cache:update(
|
false ->
|
||||||
cache_tab(Mod), {User, Server}, {ok, Password},
|
ets_cache:untag(Mod:try_register_multiple(User, Server, Passwords))
|
||||||
fun() -> Mod:try_register(User, Server, Password1) end,
|
|
||||||
cache_nodes(Mod, Server));
|
|
||||||
false ->
|
|
||||||
ets_cache:untag(Mod:try_register(User, Server, Password1))
|
|
||||||
end,
|
|
||||||
case Ret of
|
|
||||||
{ok, _} -> ok;
|
|
||||||
{error, _} = Err -> Err
|
|
||||||
end;
|
end;
|
||||||
false ->
|
_ ->
|
||||||
{error, not_allowed}
|
case erlang:function_exported(Mod, try_register, 3) of
|
||||||
|
true ->
|
||||||
|
case use_cache(Mod, Server) of
|
||||||
|
true ->
|
||||||
|
ets_cache:update(
|
||||||
|
cache_tab(Mod), {User, Server}, {ok, [PlainPassword]},
|
||||||
|
fun() ->
|
||||||
|
case Mod:try_register(User, Server, PlainPassword) of
|
||||||
|
{Tag, {ok, Pass}} -> {Tag, {ok, [Pass]}};
|
||||||
|
Other -> Other
|
||||||
|
end
|
||||||
|
end, cache_nodes(Mod, Server));
|
||||||
|
false ->
|
||||||
|
case Mod:try_register(User, Server, PlainPassword) of
|
||||||
|
{_, {ok, Pass}} -> {ok, [Pass]};
|
||||||
|
V -> ets_cache:untag(V)
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
{error, not_allowed}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
case Ret of
|
||||||
|
{ok, _} -> ok;
|
||||||
|
{error, _} = Err -> Err
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec db_set_password(binary(), binary(), password(), module()) -> ok | {error, db_failure | not_allowed}.
|
-spec db_set_password(binary(), binary(), binary(), [password()], module()) -> ok | {error, db_failure | not_allowed}.
|
||||||
db_set_password(User, Server, Password, Mod) ->
|
db_set_password(User, Server, PlainPassword, Passwords, Mod) ->
|
||||||
case erlang:function_exported(Mod, set_password, 3) of
|
Ret = case erlang:function_exported(Mod, set_password_multiple, 3) of
|
||||||
true ->
|
true ->
|
||||||
Password1 = case Mod:store_type(Server) of
|
case use_cache(Mod, Server) of
|
||||||
scram -> password_to_scram(Server, Password);
|
|
||||||
_ -> Password
|
|
||||||
end,
|
|
||||||
Ret = case use_cache(Mod, Server) of
|
|
||||||
true ->
|
true ->
|
||||||
ets_cache:update(
|
ets_cache:update(
|
||||||
cache_tab(Mod), {User, Server}, {ok, Password},
|
cache_tab(Mod), {User, Server}, {ok, Passwords},
|
||||||
fun() -> Mod:set_password(User, Server, Password1) end,
|
fun() -> Mod:set_password_multiple(User, Server, Passwords) end,
|
||||||
cache_nodes(Mod, Server));
|
cache_nodes(Mod, Server));
|
||||||
false ->
|
false ->
|
||||||
ets_cache:untag(Mod:set_password(User, Server, Password1))
|
ets_cache:untag(Mod:set_password_multiple(User, Server, Passwords))
|
||||||
end,
|
end;
|
||||||
case Ret of
|
_ ->
|
||||||
{ok, _} -> ok;
|
case erlang:function_exported(Mod, set_password, 3) of
|
||||||
{error, _} = Err -> Err
|
true ->
|
||||||
end;
|
case use_cache(Mod, Server) of
|
||||||
false ->
|
true ->
|
||||||
{error, not_allowed}
|
ets_cache:update(
|
||||||
|
cache_tab(Mod), {User, Server}, {ok, [PlainPassword]},
|
||||||
|
fun() ->
|
||||||
|
case Mod:set_password(User, Server, PlainPassword) of
|
||||||
|
{Tag, {ok, Pass}} -> {Tag, {ok, [Pass]}};
|
||||||
|
Other -> Other
|
||||||
|
end
|
||||||
|
end, cache_nodes(Mod, Server));
|
||||||
|
false ->
|
||||||
|
case Mod:set_password(User, Server, PlainPassword) of
|
||||||
|
{_, {ok, Pass}} -> {ok, [Pass]};
|
||||||
|
V -> ets_cache:untag(V)
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
{error, not_allowed}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
case Ret of
|
||||||
|
{ok, _} -> ok;
|
||||||
|
{error, _} = Err -> Err
|
||||||
end.
|
end.
|
||||||
|
|
||||||
db_get_password(User, Server, Mod) ->
|
db_get_password(User, Server, Mod) ->
|
||||||
|
@ -655,10 +773,20 @@ db_get_password(User, Server, Mod) ->
|
||||||
error;
|
error;
|
||||||
true when UseCache ->
|
true when UseCache ->
|
||||||
ets_cache:lookup(
|
ets_cache:lookup(
|
||||||
cache_tab(Mod), {User, Server},
|
cache_tab(Mod), {User, Server},
|
||||||
fun() -> Mod:get_password(User, Server) end);
|
fun() ->
|
||||||
|
case Mod:get_password(User, Server) of
|
||||||
|
{_, {ok, List}} = V when is_list(List) -> V;
|
||||||
|
{Tag, {ok, Single}} -> {Tag, {ok, [Single]}};
|
||||||
|
Other -> Other
|
||||||
|
end
|
||||||
|
end);
|
||||||
true ->
|
true ->
|
||||||
ets_cache:untag(Mod:get_password(User, Server))
|
case Mod:get_password(User, Server) of
|
||||||
|
{_, {ok, List}} when is_list(List) -> {ok, List};
|
||||||
|
{_, {ok, Single}} -> {ok, [Single]};
|
||||||
|
Other -> ets_cache:untag(Other)
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
db_user_exists(User, Server, Mod) ->
|
db_user_exists(User, Server, Mod) ->
|
||||||
|
@ -703,8 +831,8 @@ db_user_exists(User, Server, Mod) ->
|
||||||
db_check_password(User, AuthzId, Server, ProvidedPassword,
|
db_check_password(User, AuthzId, Server, ProvidedPassword,
|
||||||
Digest, DigestFun, Mod) ->
|
Digest, DigestFun, Mod) ->
|
||||||
case db_get_password(User, Server, Mod) of
|
case db_get_password(User, Server, Mod) of
|
||||||
{ok, ValidPassword} ->
|
{ok, ValidPasswords} ->
|
||||||
match_passwords(ProvidedPassword, ValidPassword, Digest, DigestFun);
|
match_passwords(ProvidedPassword, ValidPasswords, Digest, DigestFun);
|
||||||
error ->
|
error ->
|
||||||
case {Mod:store_type(Server), use_cache(Mod, Server)} of
|
case {Mod:store_type(Server), use_cache(Mod, Server)} of
|
||||||
{external, true} ->
|
{external, true} ->
|
||||||
|
@ -809,7 +937,9 @@ password_to_scram(Host, Password) ->
|
||||||
password_to_scram(_Host, #scram{} = Password, _IterationCount) ->
|
password_to_scram(_Host, #scram{} = Password, _IterationCount) ->
|
||||||
Password;
|
Password;
|
||||||
password_to_scram(Host, Password, IterationCount) ->
|
password_to_scram(Host, Password, IterationCount) ->
|
||||||
Hash = ejabberd_option:auth_scram_hash(Host),
|
password_to_scram(Host, Password, ejabberd_option:auth_scram_hash(Host), IterationCount).
|
||||||
|
|
||||||
|
password_to_scram(_Host, Password, Hash, IterationCount) ->
|
||||||
Salt = p1_rand:bytes(?SALT_LENGTH),
|
Salt = p1_rand:bytes(?SALT_LENGTH),
|
||||||
SaltedPassword = scram:salted_password(Hash, Password, Salt, IterationCount),
|
SaltedPassword = scram:salted_password(Hash, Password, Salt, IterationCount),
|
||||||
StoredKey = scram:stored_key(Hash, scram:client_key(Hash, SaltedPassword)),
|
StoredKey = scram:stored_key(Hash, scram:client_key(Hash, SaltedPassword)),
|
||||||
|
@ -897,11 +1027,19 @@ auth_modules(Server) ->
|
||||||
misc:atom_to_binary(M)])
|
misc:atom_to_binary(M)])
|
||||||
|| M <- Methods].
|
|| M <- Methods].
|
||||||
|
|
||||||
-spec match_passwords(password(), password(),
|
-spec match_passwords(password(), [password()],
|
||||||
binary(), digest_fun() | undefined) -> boolean().
|
binary(), digest_fun() | undefined) -> boolean().
|
||||||
match_passwords(Password, #scram{} = Scram, <<"">>, undefined) ->
|
match_passwords(Provided, Passwords, Digest, DigestFun) ->
|
||||||
|
lists:any(
|
||||||
|
fun(Pass) ->
|
||||||
|
match_password(Provided, Pass, Digest, DigestFun)
|
||||||
|
end, Passwords).
|
||||||
|
|
||||||
|
-spec match_password(password(), password(),
|
||||||
|
binary(), digest_fun() | undefined) -> boolean().
|
||||||
|
match_password(Password, #scram{} = Scram, <<"">>, undefined) ->
|
||||||
is_password_scram_valid(Password, Scram);
|
is_password_scram_valid(Password, Scram);
|
||||||
match_passwords(Password, #scram{} = Scram, Digest, DigestFun) ->
|
match_password(Password, #scram{} = Scram, Digest, DigestFun) ->
|
||||||
StoredKey = base64:decode(Scram#scram.storedkey),
|
StoredKey = base64:decode(Scram#scram.storedkey),
|
||||||
DigRes = if Digest /= <<"">> ->
|
DigRes = if Digest /= <<"">> ->
|
||||||
Digest == DigestFun(StoredKey);
|
Digest == DigestFun(StoredKey);
|
||||||
|
@ -912,9 +1050,9 @@ match_passwords(Password, #scram{} = Scram, Digest, DigestFun) ->
|
||||||
true ->
|
true ->
|
||||||
StoredKey == Password andalso Password /= <<"">>
|
StoredKey == Password andalso Password /= <<"">>
|
||||||
end;
|
end;
|
||||||
match_passwords(ProvidedPassword, ValidPassword, <<"">>, undefined) ->
|
match_password(ProvidedPassword, ValidPassword, <<"">>, undefined) ->
|
||||||
ProvidedPassword == ValidPassword andalso ProvidedPassword /= <<"">>;
|
ProvidedPassword == ValidPassword andalso ProvidedPassword /= <<"">>;
|
||||||
match_passwords(ProvidedPassword, ValidPassword, Digest, DigestFun) ->
|
match_password(ProvidedPassword, ValidPassword, Digest, DigestFun) ->
|
||||||
DigRes = if Digest /= <<"">> ->
|
DigRes = if Digest /= <<"">> ->
|
||||||
Digest == DigestFun(ValidPassword);
|
Digest == DigestFun(ValidPassword);
|
||||||
true -> false
|
true -> false
|
||||||
|
@ -983,7 +1121,7 @@ convert_to_scram(Server) ->
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun({U, S}) ->
|
fun({U, S}) ->
|
||||||
case get_password(U, S) of
|
case get_password(U, S) of
|
||||||
Pass when is_binary(Pass) ->
|
[Pass] when is_binary(Pass) ->
|
||||||
SPass = password_to_scram(Server, Pass),
|
SPass = password_to_scram(Server, Pass),
|
||||||
set_password(U, S, SPass);
|
set_password(U, S, SPass);
|
||||||
_ ->
|
_ ->
|
||||||
|
|
|
@ -29,11 +29,11 @@
|
||||||
|
|
||||||
-behaviour(ejabberd_auth).
|
-behaviour(ejabberd_auth).
|
||||||
|
|
||||||
-export([start/1, stop/1, set_password/3, try_register/3,
|
-export([start/1, stop/1, set_password_multiple/3, try_register_multiple/3,
|
||||||
get_users/2, init_db/0,
|
get_users/2, init_db/0,
|
||||||
count_users/2, get_password/2,
|
count_users/2, get_password/2,
|
||||||
remove_user/2, store_type/1, import/2,
|
remove_user/2, store_type/1, import/2,
|
||||||
plain_password_required/1, use_cache/1]).
|
plain_password_required/1, use_cache/1, drop_password_type/2, set_password_instance/3]).
|
||||||
-export([need_transform/1, transform/1]).
|
-export([need_transform/1, transform/1]).
|
||||||
|
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
|
@ -86,30 +86,58 @@ plain_password_required(Server) ->
|
||||||
store_type(Server) ->
|
store_type(Server) ->
|
||||||
ejabberd_auth:password_format(Server).
|
ejabberd_auth:password_format(Server).
|
||||||
|
|
||||||
set_password(User, Server, Password) ->
|
set_password_multiple(User, Server, Passwords) ->
|
||||||
US = {User, Server},
|
F = fun() ->
|
||||||
F = fun () ->
|
lists:foreach(
|
||||||
mnesia:write(#passwd{us = US, password = Password})
|
fun(#scram{hash = Hash} = Password) ->
|
||||||
|
mnesia:write(#passwd{us = {User, Server, Hash}, password = Password});
|
||||||
|
(Plain) ->
|
||||||
|
mnesia:write(#passwd{us = {User, Server, plain}, password = Plain})
|
||||||
|
end, Passwords)
|
||||||
end,
|
end,
|
||||||
case mnesia:transaction(F) of
|
case mnesia:transaction(F) of
|
||||||
{atomic, ok} ->
|
{atomic, ok} ->
|
||||||
{cache, {ok, Password}};
|
{cache, {ok, Passwords}};
|
||||||
{aborted, Reason} ->
|
{aborted, Reason} ->
|
||||||
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||||
{nocache, {error, db_failure}}
|
{nocache, {error, db_failure}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
try_register(User, Server, Password) ->
|
set_password_instance(User, Server, Password) ->
|
||||||
US = {User, Server},
|
F = fun() ->
|
||||||
F = fun () ->
|
case Password of
|
||||||
case mnesia:read({passwd, US}) of
|
#scram{hash = Hash} = Password ->
|
||||||
[] ->
|
mnesia:write(#passwd{us = {User, Server, Hash}, password = Password});
|
||||||
mnesia:write(#passwd{us = US, password = Password}),
|
Plain ->
|
||||||
mnesia:dirty_update_counter(reg_users_counter, Server, 1),
|
mnesia:write(#passwd{us = {User, Server, plain}, password = Plain})
|
||||||
{ok, Password};
|
end
|
||||||
[_] ->
|
end,
|
||||||
{error, exists}
|
case mnesia:transaction(F) of
|
||||||
end
|
{atomic, ok} ->
|
||||||
|
ok;
|
||||||
|
{aborted, Reason} ->
|
||||||
|
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||||
|
{error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
try_register_multiple(User, Server, Passwords) ->
|
||||||
|
F = fun() ->
|
||||||
|
case mnesia:select(passwd, [{{'_', {'$1', '$2', '_'}, '$3'},
|
||||||
|
[{'==', '$1', User},
|
||||||
|
{'==', '$2', Server}],
|
||||||
|
['$3']}]) of
|
||||||
|
[] ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(#scram{hash = Hash} = Password) ->
|
||||||
|
mnesia:write(#passwd{us = {User, Server, Hash}, password = Password});
|
||||||
|
(Plain) ->
|
||||||
|
mnesia:write(#passwd{us = {User, Server, plain}, password = Plain})
|
||||||
|
end, Passwords),
|
||||||
|
mnesia:dirty_update_counter(reg_users_counter, Server, 1),
|
||||||
|
{ok, Passwords};
|
||||||
|
[_] ->
|
||||||
|
{error, exists}
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
case mnesia:transaction(F) of
|
case mnesia:transaction(F) of
|
||||||
{atomic, Res} ->
|
{atomic, Res} ->
|
||||||
|
@ -120,9 +148,10 @@ try_register(User, Server, Password) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_users(Server, []) ->
|
get_users(Server, []) ->
|
||||||
mnesia:dirty_select(passwd,
|
Users = mnesia:dirty_select(passwd,
|
||||||
[{#passwd{us = '$1', _ = '_'},
|
[{#passwd{us = '$1', _ = '_'},
|
||||||
[{'==', {element, 2, '$1'}, Server}], ['$1']}]);
|
[{'==', {element, 2, '$1'}, Server}], ['$1']}]),
|
||||||
|
lists:uniq(lists:map(fun({U, S, _}) -> {U, S} end, Users));
|
||||||
get_users(Server, [{from, Start}, {to, End}])
|
get_users(Server, [{from, Start}, {to, End}])
|
||||||
when is_integer(Start) and is_integer(End) ->
|
when is_integer(Start) and is_integer(End) ->
|
||||||
get_users(Server, [{limit, End - Start + 1}, {offset, Start}]);
|
get_users(Server, [{limit, End - Start + 1}, {offset, Start}]);
|
||||||
|
@ -179,22 +208,48 @@ count_users(Server, _) ->
|
||||||
count_users(Server, []).
|
count_users(Server, []).
|
||||||
|
|
||||||
get_password(User, Server) ->
|
get_password(User, Server) ->
|
||||||
case mnesia:dirty_read(passwd, {User, Server}) of
|
case mnesia:dirty_select(passwd, [{{'_', {'$1', '$2', '_'}, '$3'},
|
||||||
[{passwd, _, {scram, SK, SEK, Salt, IC}}] ->
|
[{'==', '$1', User},
|
||||||
{cache, {ok, #scram{storedkey = SK, serverkey = SEK,
|
{'==', '$2', Server}],
|
||||||
salt = Salt, hash = sha, iterationcount = IC}}};
|
['$3']}]) of
|
||||||
[#passwd{password = Password}] ->
|
[_|_] = List ->
|
||||||
{cache, {ok, Password}};
|
List2 = lists:map(
|
||||||
|
fun({scram, SK, SEK, Salt, IC}) ->
|
||||||
|
#scram{storedkey = SK, serverkey = SEK,
|
||||||
|
salt = Salt, hash = sha, iterationcount = IC};
|
||||||
|
(Other) -> Other
|
||||||
|
end, List),
|
||||||
|
{cache, {ok, List2}};
|
||||||
_ ->
|
_ ->
|
||||||
{cache, error}
|
{cache, error}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
drop_password_type(Server, Hash) ->
|
||||||
|
F = fun() ->
|
||||||
|
Keys = mnesia:select(passwd, [{{'_', '$1', '_'},
|
||||||
|
[{'==', {element, 3, '$1'}, Hash},
|
||||||
|
{'==', {element, 2, '$1'}, Server}],
|
||||||
|
['$1']}]),
|
||||||
|
lists:foreach(fun(Key) -> mnesia:delete({passwd, Key}) end, Keys),
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
case mnesia:transaction(F) of
|
||||||
|
{atomic, ok} ->
|
||||||
|
ok;
|
||||||
|
{aborted, Reason} ->
|
||||||
|
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||||
|
{error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
remove_user(User, Server) ->
|
remove_user(User, Server) ->
|
||||||
US = {User, Server},
|
|
||||||
F = fun () ->
|
F = fun () ->
|
||||||
mnesia:delete({passwd, US}),
|
Keys = mnesia:select(passwd, [{{'_', '$1', '_'},
|
||||||
mnesia:dirty_update_counter(reg_users_counter, Server, -1),
|
[{'==', {element, 1, '$1'}, User},
|
||||||
ok
|
{'==', {element, 2, '$1'}, Server}],
|
||||||
|
['$1']}]),
|
||||||
|
lists:foreach(fun(Key) -> mnesia:delete({passwd, Key}) end, Keys),
|
||||||
|
mnesia:dirty_update_counter(reg_users_counter, Server, -1),
|
||||||
|
ok
|
||||||
end,
|
end,
|
||||||
case mnesia:transaction(F) of
|
case mnesia:transaction(F) of
|
||||||
{atomic, ok} ->
|
{atomic, ok} ->
|
||||||
|
@ -206,45 +261,10 @@ remove_user(User, Server) ->
|
||||||
|
|
||||||
need_transform(#reg_users_counter{}) ->
|
need_transform(#reg_users_counter{}) ->
|
||||||
false;
|
false;
|
||||||
need_transform({passwd, {U, S}, Pass}) ->
|
need_transform({passwd, {_U, _S, _T}, _Pass}) ->
|
||||||
case Pass of
|
false;
|
||||||
_ when is_binary(Pass) ->
|
need_transform({passwd, {_U, _S}, _Pass}) ->
|
||||||
case store_type(S) of
|
true.
|
||||||
scram ->
|
|
||||||
?INFO_MSG("Passwords in Mnesia table 'passwd' "
|
|
||||||
"will be SCRAM'ed", []),
|
|
||||||
true;
|
|
||||||
plain ->
|
|
||||||
false
|
|
||||||
end;
|
|
||||||
{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;
|
|
||||||
#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;
|
|
||||||
_ when is_list(U) orelse is_list(S) orelse is_list(Pass) ->
|
|
||||||
?INFO_MSG("Mnesia table 'passwd' will be converted to binary", []),
|
|
||||||
true
|
|
||||||
end.
|
|
||||||
|
|
||||||
transform({passwd, {U, S}, Pass})
|
transform({passwd, {U, S}, Pass})
|
||||||
when is_list(U) orelse is_list(S) orelse is_list(Pass) ->
|
when is_list(U) orelse is_list(S) orelse is_list(Pass) ->
|
||||||
|
@ -263,24 +283,14 @@ transform({passwd, {U, S}, Pass})
|
||||||
transform(#passwd{us = NewUS, password = NewPass});
|
transform(#passwd{us = NewUS, password = NewPass});
|
||||||
transform(#passwd{us = {U, S}, password = Password} = P)
|
transform(#passwd{us = {U, S}, password = Password} = P)
|
||||||
when is_binary(Password) ->
|
when is_binary(Password) ->
|
||||||
case store_type(S) of
|
P#passwd{us = {U, S, plain}, password = Password};
|
||||||
scram ->
|
transform({passwd, {U, S}, {scram, SK, SEK, Salt, IC}}) ->
|
||||||
case jid:resourceprep(Password) of
|
#passwd{us = {U, S, sha},
|
||||||
error ->
|
password = #scram{storedkey = SK, serverkey = SEK,
|
||||||
?ERROR_MSG("SASLprep failed for password of user ~ts@~ts",
|
salt = Salt, hash = sha, iterationcount = IC}};
|
||||||
[U, S]),
|
transform(#passwd{us = {U, S}, password = #scram{hash = Hash}} = P) ->
|
||||||
P;
|
P#passwd{us = {U, S, Hash}};
|
||||||
_ ->
|
transform(Other) -> Other.
|
||||||
Scram = ejabberd_auth:password_to_scram(S, Password),
|
|
||||||
P#passwd{password = Scram}
|
|
||||||
end;
|
|
||||||
plain ->
|
|
||||||
P
|
|
||||||
end;
|
|
||||||
transform({passwd, _, {scram, _, _, _, _}} = P) ->
|
|
||||||
P;
|
|
||||||
transform(#passwd{password = #scram{}} = P) ->
|
|
||||||
P.
|
|
||||||
|
|
||||||
import(LServer, [LUser, Password, _TimeStamp]) ->
|
import(LServer, [LUser, Password, _TimeStamp]) ->
|
||||||
mnesia:dirty_write(
|
mnesia:dirty_write(
|
||||||
|
|
|
@ -30,10 +30,10 @@
|
||||||
|
|
||||||
-behaviour(ejabberd_auth).
|
-behaviour(ejabberd_auth).
|
||||||
|
|
||||||
-export([start/1, stop/1, set_password/3, try_register/3,
|
-export([start/1, stop/1, set_password_multiple/3, try_register_multiple/3,
|
||||||
get_users/2, count_users/2, get_password/2,
|
get_users/2, count_users/2, get_password/2,
|
||||||
remove_user/2, store_type/1, plain_password_required/1,
|
remove_user/2, store_type/1, plain_password_required/1,
|
||||||
export/1, which_users_exists/2]).
|
export/1, which_users_exists/2, drop_password_type/2, set_password_instance/3]).
|
||||||
-export([sql_schemas/0]).
|
-export([sql_schemas/0]).
|
||||||
|
|
||||||
-include_lib("xmpp/include/scram.hrl").
|
-include_lib("xmpp/include/scram.hrl").
|
||||||
|
@ -49,7 +49,34 @@ start(Host) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
sql_schemas() ->
|
sql_schemas() ->
|
||||||
[#sql_schema{
|
[
|
||||||
|
#sql_schema{
|
||||||
|
version = 2,
|
||||||
|
tables =
|
||||||
|
[#sql_table{
|
||||||
|
name = <<"users">>,
|
||||||
|
columns =
|
||||||
|
[#sql_column{name = <<"username">>, type = text},
|
||||||
|
#sql_column{name = <<"server_host">>, type = text},
|
||||||
|
#sql_column{name = <<"type">>, type = smallint},
|
||||||
|
#sql_column{name = <<"password">>, type = text},
|
||||||
|
#sql_column{name = <<"serverkey">>, type = {text, 128},
|
||||||
|
default = true},
|
||||||
|
#sql_column{name = <<"salt">>, type = {text, 128},
|
||||||
|
default = true},
|
||||||
|
#sql_column{name = <<"iterationcount">>, type = integer,
|
||||||
|
default = true},
|
||||||
|
#sql_column{name = <<"created_at">>, type = timestamp,
|
||||||
|
default = true}],
|
||||||
|
indices = [#sql_index{
|
||||||
|
columns = [<<"server_host">>, <<"username">>, <<"type">>],
|
||||||
|
unique = true}]}],
|
||||||
|
update = [
|
||||||
|
{add_column, <<"users">>, <<"type">>},
|
||||||
|
{update_primary_key,<<"users">>,
|
||||||
|
[<<"server_host">>, <<"username">>, <<"type">>]}
|
||||||
|
]},
|
||||||
|
#sql_schema{
|
||||||
version = 1,
|
version = 1,
|
||||||
tables =
|
tables =
|
||||||
[#sql_table{
|
[#sql_table{
|
||||||
|
@ -78,42 +105,87 @@ plain_password_required(Server) ->
|
||||||
store_type(Server) ->
|
store_type(Server) ->
|
||||||
ejabberd_auth:password_format(Server).
|
ejabberd_auth:password_format(Server).
|
||||||
|
|
||||||
set_password(User, Server, Password) ->
|
hash_to_num(plain) -> 1;
|
||||||
|
hash_to_num(sha) -> 2;
|
||||||
|
hash_to_num(sha256) -> 3;
|
||||||
|
hash_to_num(sha512) -> 4.
|
||||||
|
|
||||||
|
num_to_hash(2) -> sha;
|
||||||
|
num_to_hash(3) -> sha256;
|
||||||
|
num_to_hash(4) -> sha512.
|
||||||
|
|
||||||
|
set_password_instance(User, Server, #scram{hash = Hash, storedkey = SK, serverkey = SEK,
|
||||||
|
salt = Salt, iterationcount = IC}) ->
|
||||||
|
F = fun() ->
|
||||||
|
set_password_scram_t(User, Server, Hash,
|
||||||
|
SK, SEK, Salt, IC)
|
||||||
|
end,
|
||||||
|
case ejabberd_sql:sql_transaction(Server, F) of
|
||||||
|
{atomic, _} ->
|
||||||
|
ok;
|
||||||
|
{aborted, _} ->
|
||||||
|
{error, db_failure}
|
||||||
|
end;
|
||||||
|
set_password_instance(User, Server, Plain) ->
|
||||||
|
F = fun() ->
|
||||||
|
set_password_t(User, Server, Plain)
|
||||||
|
end,
|
||||||
|
case ejabberd_sql:sql_transaction(Server, F) of
|
||||||
|
{atomic, _} ->
|
||||||
|
ok;
|
||||||
|
{aborted, _} ->
|
||||||
|
{error, db_failure}
|
||||||
|
end.
|
||||||
|
|
||||||
|
set_password_multiple(User, Server, Passwords) ->
|
||||||
F =
|
F =
|
||||||
fun() ->
|
fun() ->
|
||||||
case Password of
|
ejabberd_sql:sql_query_t(
|
||||||
#scram{hash = Hash, storedkey = SK, serverkey = SEK,
|
?SQL("delete from users where username=%(User)s and %(Server)H")),
|
||||||
salt = Salt, iterationcount = IC} ->
|
lists:foreach(
|
||||||
SK2 = scram_hash_encode(Hash, SK),
|
fun(#scram{hash = Hash, storedkey = SK, serverkey = SEK,
|
||||||
|
salt = Salt, iterationcount = IC}) ->
|
||||||
set_password_scram_t(
|
set_password_scram_t(
|
||||||
User, Server,
|
User, Server, Hash,
|
||||||
SK2, SEK, Salt, IC);
|
SK, SEK, Salt, IC);
|
||||||
_ ->
|
(Plain) ->
|
||||||
set_password_t(User, Server, Password)
|
set_password_t(User, Server, Plain)
|
||||||
end
|
end, Passwords)
|
||||||
end,
|
end,
|
||||||
case ejabberd_sql:sql_transaction(Server, F) of
|
case ejabberd_sql:sql_transaction(Server, F) of
|
||||||
{atomic, _} ->
|
{atomic, _} ->
|
||||||
{cache, {ok, Password}};
|
{cache, {ok, Passwords}};
|
||||||
{aborted, _} ->
|
{aborted, _} ->
|
||||||
{nocache, {error, db_failure}}
|
{nocache, {error, db_failure}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
try_register(User, Server, Password) ->
|
try_register_multiple(User, Server, Passwords) ->
|
||||||
Res =
|
F =
|
||||||
case Password of
|
fun() ->
|
||||||
#scram{hash = Hash, storedkey = SK, serverkey = SEK,
|
case ejabberd_sql:sql_query_t(
|
||||||
salt = Salt, iterationcount = IC} ->
|
?SQL("select @(count(*))d from users where username=%(User)s and %(Server)H")) of
|
||||||
SK2 = scram_hash_encode(Hash, SK),
|
{selected, [{0}]} ->
|
||||||
add_user_scram(
|
lists:foreach(
|
||||||
Server, User,
|
fun(#scram{hash = Hash, storedkey = SK, serverkey = SEK,
|
||||||
SK2, SEK, Salt, IC);
|
salt = Salt, iterationcount = IC}) ->
|
||||||
_ ->
|
set_password_scram_t(
|
||||||
add_user(Server, User, Password)
|
User, Server, Hash,
|
||||||
end,
|
SK, SEK, Salt, IC);
|
||||||
case Res of
|
(Plain) ->
|
||||||
{updated, 1} -> {cache, {ok, Password}};
|
set_password_t(User, Server, Plain)
|
||||||
_ -> {nocache, {error, exists}}
|
end, Passwords),
|
||||||
|
{cache, {ok, Passwords}};
|
||||||
|
{selected, _} ->
|
||||||
|
{nocache, {error, exists}};
|
||||||
|
_ ->
|
||||||
|
{nocache, {error, db_failure}}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
case ejabberd_sql:sql_transaction(Server, F) of
|
||||||
|
{atomic, Res} ->
|
||||||
|
Res;
|
||||||
|
{aborted, _} ->
|
||||||
|
{nocache, {error, db_failure}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_users(Server, Opts) ->
|
get_users(Server, Opts) ->
|
||||||
|
@ -132,21 +204,41 @@ count_users(Server, Opts) ->
|
||||||
|
|
||||||
get_password(User, Server) ->
|
get_password(User, Server) ->
|
||||||
case get_password_scram(Server, User) of
|
case get_password_scram(Server, User) of
|
||||||
{selected, [{Password, <<>>, <<>>, 0}]} ->
|
|
||||||
{cache, {ok, Password}};
|
|
||||||
{selected, [{StoredKey, ServerKey, Salt, IterationCount}]} ->
|
|
||||||
{Hash, SK} = case StoredKey of
|
|
||||||
<<"sha256:", Rest/binary>> -> {sha256, Rest};
|
|
||||||
<<"sha512:", Rest/binary>> -> {sha512, Rest};
|
|
||||||
Other -> {sha, Other}
|
|
||||||
end,
|
|
||||||
{cache, {ok, #scram{storedkey = SK,
|
|
||||||
serverkey = ServerKey,
|
|
||||||
salt = Salt,
|
|
||||||
hash = Hash,
|
|
||||||
iterationcount = IterationCount}}};
|
|
||||||
{selected, []} ->
|
{selected, []} ->
|
||||||
{cache, error};
|
{cache, error};
|
||||||
|
{selected, Passwords} ->
|
||||||
|
Converted = lists:map(
|
||||||
|
fun({0, Password, <<>>, <<>>, 0}) ->
|
||||||
|
update_password_type(User, Server, 1),
|
||||||
|
Password;
|
||||||
|
({_, Password, <<>>, <<>>, 0}) ->
|
||||||
|
Password;
|
||||||
|
({0, StoredKey, ServerKey, Salt, IterationCount}) ->
|
||||||
|
{Hash, SK} = case StoredKey of
|
||||||
|
<<"sha256:", Rest/binary>> ->
|
||||||
|
update_password_type(User, Server, 3, Rest),
|
||||||
|
{sha256, Rest};
|
||||||
|
<<"sha512:", Rest/binary>> ->
|
||||||
|
update_password_type(User, Server, 4, Rest),
|
||||||
|
{sha512, Rest};
|
||||||
|
Other ->
|
||||||
|
update_password_type(User, Server, 2),
|
||||||
|
{sha, Other}
|
||||||
|
end,
|
||||||
|
#scram{storedkey = SK,
|
||||||
|
serverkey = ServerKey,
|
||||||
|
salt = Salt,
|
||||||
|
hash = Hash,
|
||||||
|
iterationcount = IterationCount};
|
||||||
|
({Type, StoredKey, ServerKey, Salt, IterationCount}) ->
|
||||||
|
Hash = num_to_hash(Type),
|
||||||
|
#scram{storedkey = StoredKey,
|
||||||
|
serverkey = ServerKey,
|
||||||
|
salt = Salt,
|
||||||
|
hash = Hash,
|
||||||
|
iterationcount = IterationCount}
|
||||||
|
end, Passwords),
|
||||||
|
{cache, {ok, Converted}};
|
||||||
_ ->
|
_ ->
|
||||||
{nocache, error}
|
{nocache, error}
|
||||||
end.
|
end.
|
||||||
|
@ -159,6 +251,13 @@ remove_user(User, Server) ->
|
||||||
{error, db_failure}
|
{error, db_failure}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
drop_password_type(LServer, Hash) ->
|
||||||
|
Type = hash_to_num(Hash),
|
||||||
|
ejabberd_sql:sql_query(
|
||||||
|
LServer,
|
||||||
|
?SQL("delete from users"
|
||||||
|
" where type=%(Type)d and %(LServer)H")).
|
||||||
|
|
||||||
scram_hash_encode(Hash, StoreKey) ->
|
scram_hash_encode(Hash, StoreKey) ->
|
||||||
case Hash of
|
case Hash of
|
||||||
sha -> StoreKey;
|
sha -> StoreKey;
|
||||||
|
@ -166,12 +265,14 @@ scram_hash_encode(Hash, StoreKey) ->
|
||||||
sha512 -> <<"sha512:", StoreKey/binary>>
|
sha512 -> <<"sha512:", StoreKey/binary>>
|
||||||
end.
|
end.
|
||||||
|
|
||||||
set_password_scram_t(LUser, LServer,
|
set_password_scram_t(LUser, LServer, Hash,
|
||||||
StoredKey, ServerKey, Salt, IterationCount) ->
|
StoredKey, ServerKey, Salt, IterationCount) ->
|
||||||
|
Type = hash_to_num(Hash),
|
||||||
?SQL_UPSERT_T(
|
?SQL_UPSERT_T(
|
||||||
"users",
|
"users",
|
||||||
["!username=%(LUser)s",
|
["!username=%(LUser)s",
|
||||||
"!server_host=%(LServer)s",
|
"!server_host=%(LServer)s",
|
||||||
|
"!type=%(Type)d",
|
||||||
"password=%(StoredKey)s",
|
"password=%(StoredKey)s",
|
||||||
"serverkey=%(ServerKey)s",
|
"serverkey=%(ServerKey)s",
|
||||||
"salt=%(Salt)s",
|
"salt=%(Salt)s",
|
||||||
|
@ -182,40 +283,31 @@ set_password_t(LUser, LServer, Password) ->
|
||||||
"users",
|
"users",
|
||||||
["!username=%(LUser)s",
|
["!username=%(LUser)s",
|
||||||
"!server_host=%(LServer)s",
|
"!server_host=%(LServer)s",
|
||||||
|
"!type=1",
|
||||||
"password=%(Password)s",
|
"password=%(Password)s",
|
||||||
"serverkey=''",
|
"serverkey=''",
|
||||||
"salt=''",
|
"salt=''",
|
||||||
"iterationcount=0"]).
|
"iterationcount=0"]).
|
||||||
|
|
||||||
|
update_password_type(LUser, LServer, Type, Password) ->
|
||||||
|
ejabberd_sql:sql_query(
|
||||||
|
LServer,
|
||||||
|
?SQL("update users set type=%(Type)d, password=%(Password)s"
|
||||||
|
" where username=%(LUser)s and type=0 and %(LServer)H")).
|
||||||
|
|
||||||
|
update_password_type(LUser, LServer, Type) ->
|
||||||
|
ejabberd_sql:sql_query(
|
||||||
|
LServer,
|
||||||
|
?SQL("update users set type=%(Type)d"
|
||||||
|
" where username=%(LUser)s and type=0 and %(LServer)H")).
|
||||||
|
|
||||||
get_password_scram(LServer, LUser) ->
|
get_password_scram(LServer, LUser) ->
|
||||||
ejabberd_sql:sql_query(
|
ejabberd_sql:sql_query(
|
||||||
LServer,
|
LServer,
|
||||||
?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d"
|
?SQL("select @(type)d, @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d"
|
||||||
" from users"
|
" from users"
|
||||||
" where username=%(LUser)s and %(LServer)H")).
|
" where username=%(LUser)s and %(LServer)H")).
|
||||||
|
|
||||||
add_user_scram(LServer, LUser,
|
|
||||||
StoredKey, ServerKey, Salt, IterationCount) ->
|
|
||||||
ejabberd_sql:sql_query(
|
|
||||||
LServer,
|
|
||||||
?SQL_INSERT(
|
|
||||||
"users",
|
|
||||||
["username=%(LUser)s",
|
|
||||||
"server_host=%(LServer)s",
|
|
||||||
"password=%(StoredKey)s",
|
|
||||||
"serverkey=%(ServerKey)s",
|
|
||||||
"salt=%(Salt)s",
|
|
||||||
"iterationcount=%(IterationCount)d"])).
|
|
||||||
|
|
||||||
add_user(LServer, LUser, Password) ->
|
|
||||||
ejabberd_sql:sql_query(
|
|
||||||
LServer,
|
|
||||||
?SQL_INSERT(
|
|
||||||
"users",
|
|
||||||
["username=%(LUser)s",
|
|
||||||
"server_host=%(LServer)s",
|
|
||||||
"password=%(Password)s"])).
|
|
||||||
|
|
||||||
del_user(LServer, LUser) ->
|
del_user(LServer, LUser) ->
|
||||||
ejabberd_sql:sql_query(
|
ejabberd_sql:sql_query(
|
||||||
LServer,
|
LServer,
|
||||||
|
@ -224,7 +316,7 @@ del_user(LServer, LUser) ->
|
||||||
list_users(LServer, []) ->
|
list_users(LServer, []) ->
|
||||||
ejabberd_sql:sql_query(
|
ejabberd_sql:sql_query(
|
||||||
LServer,
|
LServer,
|
||||||
?SQL("select @(username)s from users where %(LServer)H"));
|
?SQL("select @(distinct username)s from users where %(LServer)H"));
|
||||||
list_users(LServer, [{from, Start}, {to, End}])
|
list_users(LServer, [{from, Start}, {to, End}])
|
||||||
when is_integer(Start) and is_integer(End) ->
|
when is_integer(Start) and is_integer(End) ->
|
||||||
list_users(LServer,
|
list_users(LServer,
|
||||||
|
@ -240,7 +332,7 @@ list_users(LServer, [{limit, Limit}, {offset, Offset}])
|
||||||
when is_integer(Limit) and is_integer(Offset) ->
|
when is_integer(Limit) and is_integer(Offset) ->
|
||||||
ejabberd_sql:sql_query(
|
ejabberd_sql:sql_query(
|
||||||
LServer,
|
LServer,
|
||||||
?SQL("select @(username)s from users "
|
?SQL("select @(distinct username)s from users "
|
||||||
"where %(LServer)H "
|
"where %(LServer)H "
|
||||||
"order by username "
|
"order by username "
|
||||||
"limit %(Limit)d offset %(Offset)d"));
|
"limit %(Limit)d offset %(Offset)d"));
|
||||||
|
@ -252,7 +344,7 @@ list_users(LServer,
|
||||||
SPrefix2 = <<SPrefix/binary, $%>>,
|
SPrefix2 = <<SPrefix/binary, $%>>,
|
||||||
ejabberd_sql:sql_query(
|
ejabberd_sql:sql_query(
|
||||||
LServer,
|
LServer,
|
||||||
?SQL("select @(username)s from users "
|
?SQL("select @(distinct username)s from users "
|
||||||
"where username like %(SPrefix2)s %ESCAPE and %(LServer)H "
|
"where username like %(SPrefix2)s %ESCAPE and %(LServer)H "
|
||||||
"order by username "
|
"order by username "
|
||||||
"limit %(Limit)d offset %(Offset)d")).
|
"limit %(Limit)d offset %(Offset)d")).
|
||||||
|
@ -269,11 +361,11 @@ users_number(LServer) ->
|
||||||
" where oid = 'users'::regclass::oid"));
|
" where oid = 'users'::regclass::oid"));
|
||||||
_ ->
|
_ ->
|
||||||
ejabberd_sql:sql_query_t(
|
ejabberd_sql:sql_query_t(
|
||||||
?SQL("select @(count(*))d from users where %(LServer)H"))
|
?SQL("select @(count(distinct username))d from users where %(LServer)H"))
|
||||||
end;
|
end;
|
||||||
(_Type, _) ->
|
(_Type, _) ->
|
||||||
ejabberd_sql:sql_query_t(
|
ejabberd_sql:sql_query_t(
|
||||||
?SQL("select @(count(*))d from users where %(LServer)H"))
|
?SQL("select @(count(distinct username))d from users where %(LServer)H"))
|
||||||
end).
|
end).
|
||||||
|
|
||||||
users_number(LServer, [{prefix, Prefix}])
|
users_number(LServer, [{prefix, Prefix}])
|
||||||
|
@ -282,7 +374,7 @@ users_number(LServer, [{prefix, Prefix}])
|
||||||
SPrefix2 = <<SPrefix/binary, $%>>,
|
SPrefix2 = <<SPrefix/binary, $%>>,
|
||||||
ejabberd_sql:sql_query(
|
ejabberd_sql:sql_query(
|
||||||
LServer,
|
LServer,
|
||||||
?SQL("select @(count(*))d from users "
|
?SQL("select @(count(distinct username))d from users "
|
||||||
"where username like %(SPrefix2)s %ESCAPE and %(LServer)H"));
|
"where username like %(SPrefix2)s %ESCAPE and %(LServer)H"));
|
||||||
users_number(LServer, []) ->
|
users_number(LServer, []) ->
|
||||||
users_number(LServer).
|
users_number(LServer).
|
||||||
|
@ -290,7 +382,7 @@ users_number(LServer, []) ->
|
||||||
which_users_exists(LServer, LUsers) when length(LUsers) =< 100 ->
|
which_users_exists(LServer, LUsers) when length(LUsers) =< 100 ->
|
||||||
try ejabberd_sql:sql_query(
|
try ejabberd_sql:sql_query(
|
||||||
LServer,
|
LServer,
|
||||||
?SQL("select @(username)s from users where username in %(LUsers)ls")) of
|
?SQL("select @(distinct username)s from users where username in %(LUsers)ls")) of
|
||||||
{selected, Matching} ->
|
{selected, Matching} ->
|
||||||
[U || {U} <- Matching];
|
[U || {U} <- Matching];
|
||||||
{error, _} = E ->
|
{error, _} = E ->
|
||||||
|
|
|
@ -423,16 +423,27 @@ sasl_mechanisms(Mechs, #{lserver := LServer, stream_encrypted := Encrypted} = St
|
||||||
Type = ejabberd_auth:store_type(LServer),
|
Type = ejabberd_auth:store_type(LServer),
|
||||||
Mechs1 = ejabberd_option:disable_sasl_mechanisms(LServer),
|
Mechs1 = ejabberd_option:disable_sasl_mechanisms(LServer),
|
||||||
|
|
||||||
ScramHash = ejabberd_option:auth_scram_hash(LServer),
|
{Digest, ShaAv, Sha256Av, Sha512Av} =
|
||||||
ShaAv = Type == plain orelse (Type == scram andalso ScramHash == sha),
|
case ejabberd_option:auth_stored_password_types(LServer) of
|
||||||
Sha256Av = Type == plain orelse (Type == scram andalso ScramHash == sha256),
|
[] ->
|
||||||
Sha512Av = Type == plain orelse (Type == scram andalso ScramHash == sha512),
|
ScramHash = ejabberd_option:auth_scram_hash(LServer),
|
||||||
|
{Type == plain,
|
||||||
|
Type == plain orelse (Type == scram andalso ScramHash == sha),
|
||||||
|
Type == plain orelse (Type == scram andalso ScramHash == sha256),
|
||||||
|
Type == plain orelse (Type == scram andalso ScramHash == sha512)};
|
||||||
|
Methods ->
|
||||||
|
HasPlain = lists:member(plain, Methods),
|
||||||
|
{HasPlain,
|
||||||
|
HasPlain orelse lists:member(scram_sha1, Methods),
|
||||||
|
HasPlain orelse lists:member(scram_sha256, Methods),
|
||||||
|
HasPlain orelse lists:member(scram_sha512, Methods)}
|
||||||
|
end,
|
||||||
%% I re-created it from cyrsasl ets magic, but I think it's wrong
|
%% I re-created it from cyrsasl ets magic, but I think it's wrong
|
||||||
%% TODO: need to check before 18.09 release
|
%% TODO: need to check before 18.09 release
|
||||||
lists:filter(
|
lists:filter(
|
||||||
fun(<<"ANONYMOUS">>) ->
|
fun(<<"ANONYMOUS">>) ->
|
||||||
ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer);
|
ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer);
|
||||||
(<<"DIGEST-MD5">>) -> Type == plain;
|
(<<"DIGEST-MD5">>) -> Digest;
|
||||||
(<<"SCRAM-SHA-1">>) -> ShaAv;
|
(<<"SCRAM-SHA-1">>) -> ShaAv;
|
||||||
(<<"SCRAM-SHA-1-PLUS">>) -> ShaAv andalso Encrypted;
|
(<<"SCRAM-SHA-1-PLUS">>) -> ShaAv andalso Encrypted;
|
||||||
(<<"SCRAM-SHA-256">>) -> Sha256Av;
|
(<<"SCRAM-SHA-256">>) -> Sha256Av;
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
-export([auth_opts/0, auth_opts/1]).
|
-export([auth_opts/0, auth_opts/1]).
|
||||||
-export([auth_password_format/0, auth_password_format/1]).
|
-export([auth_password_format/0, auth_password_format/1]).
|
||||||
-export([auth_scram_hash/0, auth_scram_hash/1]).
|
-export([auth_scram_hash/0, auth_scram_hash/1]).
|
||||||
|
-export([auth_stored_password_types/0, auth_stored_password_types/1]).
|
||||||
-export([auth_use_cache/0, auth_use_cache/1]).
|
-export([auth_use_cache/0, auth_use_cache/1]).
|
||||||
-export([c2s_cafile/0, c2s_cafile/1]).
|
-export([c2s_cafile/0, c2s_cafile/1]).
|
||||||
-export([c2s_ciphers/0, c2s_ciphers/1]).
|
-export([c2s_ciphers/0, c2s_ciphers/1]).
|
||||||
|
@ -264,6 +265,13 @@ auth_scram_hash() ->
|
||||||
auth_scram_hash(Host) ->
|
auth_scram_hash(Host) ->
|
||||||
ejabberd_config:get_option({auth_scram_hash, Host}).
|
ejabberd_config:get_option({auth_scram_hash, Host}).
|
||||||
|
|
||||||
|
-spec auth_stored_password_types() -> ['plain' | 'scram_sha1' | 'scram_sha256' | 'scram_sha512'].
|
||||||
|
auth_stored_password_types() ->
|
||||||
|
auth_stored_password_types(global).
|
||||||
|
-spec auth_stored_password_types(global | binary()) -> ['plain' | 'scram_sha1' | 'scram_sha256' | 'scram_sha512'].
|
||||||
|
auth_stored_password_types(Host) ->
|
||||||
|
ejabberd_config:get_option({auth_stored_password_types, Host}).
|
||||||
|
|
||||||
-spec auth_use_cache() -> boolean().
|
-spec auth_use_cache() -> boolean().
|
||||||
auth_use_cache() ->
|
auth_use_cache() ->
|
||||||
auth_use_cache(global).
|
auth_use_cache(global).
|
||||||
|
|
|
@ -77,6 +77,8 @@ opt_type(auth_opts) ->
|
||||||
{path_prefix, V}
|
{path_prefix, V}
|
||||||
end, L)
|
end, L)
|
||||||
end;
|
end;
|
||||||
|
opt_type(auth_stored_password_types) ->
|
||||||
|
econf:list(econf:enum([plain, scram_sha1, scram_sha256, scram_sha512]));
|
||||||
opt_type(auth_password_format) ->
|
opt_type(auth_password_format) ->
|
||||||
econf:enum([plain, scram]);
|
econf:enum([plain, scram]);
|
||||||
opt_type(auth_scram_hash) ->
|
opt_type(auth_scram_hash) ->
|
||||||
|
@ -546,6 +548,7 @@ options() ->
|
||||||
{auth_opts, []},
|
{auth_opts, []},
|
||||||
{auth_password_format, plain},
|
{auth_password_format, plain},
|
||||||
{auth_scram_hash, sha},
|
{auth_scram_hash, sha},
|
||||||
|
{auth_stored_password_types, []},
|
||||||
{auth_external_user_exists_check, true},
|
{auth_external_user_exists_check, true},
|
||||||
{auth_use_cache,
|
{auth_use_cache,
|
||||||
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
|
fun(Host) -> ejabberd_config:get_option({use_cache, Host}) end},
|
||||||
|
|
|
@ -399,6 +399,14 @@ doc() ->
|
||||||
"You shouldn't change this if you already have passwords generated with "
|
"You shouldn't change this if you already have passwords generated with "
|
||||||
"a different algorithm - users that have such passwords will not be able "
|
"a different algorithm - users that have such passwords will not be able "
|
||||||
"to authenticate. The default value is 'sha'.")}},
|
"to authenticate. The default value is 'sha'.")}},
|
||||||
|
{auth_stored_password_types,
|
||||||
|
#{value => "[plain | scram_sha1 | scram_sha256 | scram_sha512]",
|
||||||
|
desc =>
|
||||||
|
?T("List with password types that should be stored concurently for each user in database. "
|
||||||
|
"Each time user set it password, database will be updated to store passwords in format "
|
||||||
|
"compatible with each format listed here. This can be used to migrate user passwords "
|
||||||
|
"to more secure format. This options if set, will override values set in 'auth_scream_hash' "
|
||||||
|
"and 'auth_password_format'. By default this value is not set.")}},
|
||||||
{auth_external_user_exists_check,
|
{auth_external_user_exists_check,
|
||||||
#{value => "true | false",
|
#{value => "true | false",
|
||||||
note => "added in 23.10",
|
note => "added in 23.10",
|
||||||
|
|
|
@ -107,7 +107,7 @@ c2s_handle_sasl2_task_data({_, #{user := User, server := Server,
|
||||||
#scram_upgrade_hash{data = SaltedPassword} ->
|
#scram_upgrade_hash{data = SaltedPassword} ->
|
||||||
StoredKey = scram:stored_key(Algo, scram:client_key(Algo, SaltedPassword)),
|
StoredKey = scram:stored_key(Algo, scram:client_key(Algo, SaltedPassword)),
|
||||||
ServerKey = scram:server_key(Algo, SaltedPassword),
|
ServerKey = scram:server_key(Algo, SaltedPassword),
|
||||||
ejabberd_auth:set_password(User, Server,
|
ejabberd_auth:set_password_instance(User, Server,
|
||||||
#scram{hash = Algo, iterationcount = Iter, salt = Salt,
|
#scram{hash = Algo, iterationcount = Iter, salt = Salt,
|
||||||
serverkey = ServerKey, storedkey = StoredKey}),
|
serverkey = ServerKey, storedkey = StoredKey}),
|
||||||
State2 = maps:remove(scram_upgrade, State),
|
State2 = maps:remove(scram_upgrade, State),
|
||||||
|
|
|
@ -78,7 +78,7 @@ adduser(Config) ->
|
||||||
"server/" ++ binary_to_list(Server) ++ "/users/",
|
"server/" ++ binary_to_list(Server) ++ "/users/",
|
||||||
<<"register/user=", (mue(User))/binary, "®ister/password=",
|
<<"register/user=", (mue(User))/binary, "®ister/password=",
|
||||||
(mue(Password))/binary, "®ister=Register">>),
|
(mue(Password))/binary, "®ister=Register">>),
|
||||||
Password = ejabberd_auth:get_password(User, Server),
|
Password = ejabberd_auth:get_password_s(User, Server),
|
||||||
?match({_, _}, binary:match(Body, <<"User ", User/binary, "@", Server/binary,
|
?match({_, _}, binary:match(Body, <<"User ", User/binary, "@", Server/binary,
|
||||||
" successfully registered">>)).
|
" successfully registered">>)).
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ changepassword(Config) ->
|
||||||
++ "/user/" ++ binary_to_list(mue(User)) ++ "/",
|
++ "/user/" ++ binary_to_list(mue(User)) ++ "/",
|
||||||
<<"change_password/newpass=", (mue(Password))/binary,
|
<<"change_password/newpass=", (mue(Password))/binary,
|
||||||
"&change_password=Change+Password">>),
|
"&change_password=Change+Password">>),
|
||||||
?match(Password, ejabberd_auth:get_password(User, Server)),
|
?match(Password, ejabberd_auth:get_password_s(User, Server)),
|
||||||
?match({_, _}, binary:match(Body, <<"<div class='result'><code>ok</code></div>">>)).
|
?match({_, _}, binary:match(Body, <<"<div class='result'><code>ok</code></div>">>)).
|
||||||
|
|
||||||
removeuser(Config) ->
|
removeuser(Config) ->
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue