26
1
mirror of https://github.com/processone/ejabberd.git synced 2024-12-30 17:43:57 +01:00
xmpp.chapril.org-ejabberd/src/mod_configure.erl

1606 lines
56 KiB
Erlang
Raw Normal View History

%%%----------------------------------------------------------------------
%%% File : mod_configure.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Support for online configuration of ejabberd
%%% Created : 19 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
2023-01-09 17:09:06 +01:00
%%% ejabberd, Copyright (C) 2002-2023 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
2014-02-22 11:27:40 +01:00
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_configure).
-author('alexey@process-one.net').
2015-05-21 17:02:36 +02:00
-protocol({xep, 133, '1.1'}).
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, get_local_identity/5,
get_local_features/5, get_local_items/5,
adhoc_local_items/4, adhoc_local_commands/4,
get_sm_identity/5, get_sm_features/5, get_sm_items/5,
adhoc_sm_items/4, adhoc_sm_commands/4, mod_options/1,
depends/2, mod_doc/0]).
-include("logger.hrl").
-include_lib("xmpp/include/xmpp.hrl").
2016-01-15 13:34:48 +01:00
-include("ejabberd_sm.hrl").
-include("translate.hrl").
2017-01-13 11:28:55 +01:00
-include_lib("stdlib/include/ms_transform.hrl").
start(Host, _Opts) ->
ejabberd_hooks:add(disco_local_items, Host, ?MODULE,
get_local_items, 50),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
get_local_features, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
get_local_identity, 50),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE,
get_sm_items, 50),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE,
get_sm_identity, 50),
ejabberd_hooks:add(adhoc_local_items, Host, ?MODULE,
adhoc_local_items, 50),
ejabberd_hooks:add(adhoc_local_commands, Host, ?MODULE,
adhoc_local_commands, 50),
ejabberd_hooks:add(adhoc_sm_items, Host, ?MODULE,
adhoc_sm_items, 50),
ejabberd_hooks:add(adhoc_sm_commands, Host, ?MODULE,
adhoc_sm_commands, 50),
ok.
stop(Host) ->
ejabberd_hooks:delete(adhoc_sm_commands, Host, ?MODULE,
adhoc_sm_commands, 50),
ejabberd_hooks:delete(adhoc_sm_items, Host, ?MODULE,
adhoc_sm_items, 50),
ejabberd_hooks:delete(adhoc_local_commands, Host,
?MODULE, adhoc_local_commands, 50),
ejabberd_hooks:delete(adhoc_local_items, Host, ?MODULE,
adhoc_local_items, 50),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE,
get_sm_identity, 50),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE,
get_sm_items, 50),
ejabberd_hooks:delete(disco_local_identity, Host,
?MODULE, get_local_identity, 50),
ejabberd_hooks:delete(disco_local_features, Host,
?MODULE, get_local_features, 50),
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE,
get_local_items, 50).
reload(_Host, _NewOpts, _OldOpts) ->
ok.
depends(_Host, _Opts) ->
[{mod_adhoc, hard}, {mod_last, soft}].
%%%-----------------------------------------------------------------------
-define(INFO_IDENTITY(Category, Type, Name, Lang),
[#identity{category = Category, type = Type, name = tr(Lang, Name)}]).
-define(INFO_COMMAND(Name, Lang),
?INFO_IDENTITY(<<"automation">>, <<"command-node">>,
Name, Lang)).
-define(NODEJID(To, Name, Node),
#disco_item{jid = To, name = tr(Lang, Name), node = Node}).
-define(NODE(Name, Node),
#disco_item{jid = jid:make(Server),
node = Node,
name = tr(Lang, Name)}).
-define(NS_ADMINX(Sub),
<<(?NS_ADMIN)/binary, "#", Sub/binary>>).
-define(NS_ADMINL(Sub),
[<<"http:">>, <<"jabber.org">>, <<"protocol">>,
<<"admin">>, Sub]).
2019-07-10 09:31:51 +02:00
-spec tokenize(binary()) -> [binary()].
tokenize(Node) -> str:tokens(Node, <<"/#">>).
2019-07-10 09:31:51 +02:00
-spec get_sm_identity([identity()], jid(), jid(), binary(), binary()) -> [identity()].
get_sm_identity(Acc, _From, _To, Node, Lang) ->
case Node of
<<"config">> ->
?INFO_COMMAND(?T("Configuration"), Lang);
_ -> Acc
end.
2019-07-10 09:31:51 +02:00
-spec get_local_identity([identity()], jid(), jid(), binary(), binary()) -> [identity()].
get_local_identity(Acc, _From, _To, Node, Lang) ->
LNode = tokenize(Node),
case LNode of
[<<"running nodes">>, ENode] ->
?INFO_IDENTITY(<<"ejabberd">>, <<"node">>, ENode, Lang);
[<<"running nodes">>, _ENode, <<"DB">>] ->
?INFO_COMMAND(?T("Database"), Lang);
[<<"running nodes">>, _ENode, <<"backup">>,
<<"backup">>] ->
?INFO_COMMAND(?T("Backup"), Lang);
[<<"running nodes">>, _ENode, <<"backup">>,
<<"restore">>] ->
?INFO_COMMAND(?T("Restore"), Lang);
[<<"running nodes">>, _ENode, <<"backup">>,
<<"textfile">>] ->
?INFO_COMMAND(?T("Dump to Text File"), Lang);
[<<"running nodes">>, _ENode, <<"import">>,
<<"file">>] ->
?INFO_COMMAND(?T("Import File"), Lang);
[<<"running nodes">>, _ENode, <<"import">>,
<<"dir">>] ->
?INFO_COMMAND(?T("Import Directory"), Lang);
[<<"running nodes">>, _ENode, <<"restart">>] ->
?INFO_COMMAND(?T("Restart Service"), Lang);
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
?INFO_COMMAND(?T("Shut Down Service"), Lang);
?NS_ADMINL(<<"add-user">>) ->
?INFO_COMMAND(?T("Add User"), Lang);
?NS_ADMINL(<<"delete-user">>) ->
?INFO_COMMAND(?T("Delete User"), Lang);
?NS_ADMINL(<<"end-user-session">>) ->
?INFO_COMMAND(?T("End User Session"), Lang);
?NS_ADMINL(<<"get-user-password">>) ->
?INFO_COMMAND(?T("Get User Password"), Lang);
?NS_ADMINL(<<"change-user-password">>) ->
?INFO_COMMAND(?T("Change User Password"), Lang);
?NS_ADMINL(<<"get-user-lastlogin">>) ->
?INFO_COMMAND(?T("Get User Last Login Time"), Lang);
?NS_ADMINL(<<"user-stats">>) ->
?INFO_COMMAND(?T("Get User Statistics"), Lang);
?NS_ADMINL(<<"get-registered-users-list">>) ->
?INFO_COMMAND(?T("Get List of Registered Users"),
Lang);
?NS_ADMINL(<<"get-registered-users-num">>) ->
?INFO_COMMAND(?T("Get Number of Registered Users"),
Lang);
?NS_ADMINL(<<"get-online-users-list">>) ->
?INFO_COMMAND(?T("Get List of Online Users"), Lang);
?NS_ADMINL(<<"get-online-users-num">>) ->
?INFO_COMMAND(?T("Get Number of Online Users"), Lang);
_ -> Acc
end.
%%%-----------------------------------------------------------------------
-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
deny -> {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
allow -> {result, Feats}
end).
2019-07-10 09:31:51 +02:00
-spec get_sm_features(mod_disco:features_acc(), jid(), jid(),
binary(), binary()) -> mod_disco:features_acc().
get_sm_features(Acc, From,
#jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false -> Acc;
_ ->
Allow = acl:match_rule(LServer, configure, From),
case Node of
<<"config">> -> ?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
_ -> Acc
end
end.
2019-07-10 09:31:51 +02:00
-spec get_local_features(mod_disco:features_acc(), jid(), jid(),
binary(), binary()) -> mod_disco:features_acc().
get_local_features(Acc, From,
#jid{lserver = LServer} = _To, Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false -> Acc;
_ ->
LNode = tokenize(Node),
Allow = acl:match_rule(LServer, configure, From),
case LNode of
[<<"config">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"user">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"online users">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"all users">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"all users">>, <<$@, _/binary>>] ->
?INFO_RESULT(Allow, [], Lang);
[<<"outgoing s2s">> | _] -> ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"stopped nodes">>] -> ?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode] ->
?INFO_RESULT(Allow, [?NS_STATS], Lang);
[<<"running nodes">>, _ENode, <<"DB">>] ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"backup">>] ->
?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"import">>] ->
?INFO_RESULT(Allow, [], Lang);
[<<"running nodes">>, _ENode, <<"import">>, _] ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"restart">>] ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
[<<"config">>, _] ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"add-user">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"delete-user">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"end-user-session">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-user-password">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"change-user-password">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-user-lastlogin">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"user-stats">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-registered-users-list">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-registered-users-num">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-online-users-list">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
?NS_ADMINL(<<"get-online-users-num">>) ->
?INFO_RESULT(Allow, [?NS_COMMANDS], Lang);
_ -> Acc
end
end.
%%%-----------------------------------------------------------------------
2019-07-10 09:31:51 +02:00
-spec adhoc_sm_items(mod_disco:items_acc(),
jid(), jid(), binary()) -> mod_disco:items_acc().
adhoc_sm_items(Acc, From, #jid{lserver = LServer} = To,
Lang) ->
case acl:match_rule(LServer, configure, From) of
allow ->
Items = case Acc of
{result, Its} -> Its;
empty -> []
end,
Nodes = [#disco_item{jid = To, node = <<"config">>,
name = tr(Lang, ?T("Configuration"))}],
{result, Items ++ Nodes};
_ -> Acc
end.
%%%-----------------------------------------------------------------------
2019-07-10 09:31:51 +02:00
-spec get_sm_items(mod_disco:items_acc(), jid(), jid(),
binary(), binary()) -> mod_disco:items_acc().
get_sm_items(Acc, From,
#jid{user = User, server = Server, lserver = LServer} =
To,
Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false -> Acc;
_ ->
Items = case Acc of
{result, Its} -> Its;
empty -> []
end,
case {acl:match_rule(LServer, configure, From), Node} of
{allow, <<"">>} ->
Nodes = [?NODEJID(To, ?T("Configuration"),
<<"config">>),
?NODEJID(To, ?T("User Management"), <<"user">>)],
{result,
Items ++ Nodes ++ get_user_resources(User, Server)};
{allow, <<"config">>} -> {result, []};
{_, <<"config">>} ->
{error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
_ -> Acc
end
end.
2019-07-10 09:31:51 +02:00
-spec get_user_resources(binary(), binary()) -> [disco_item()].
get_user_resources(User, Server) ->
Rs = ejabberd_sm:get_user_resources(User, Server),
lists:map(fun (R) ->
#disco_item{jid = jid:make(User, Server, R),
name = User}
end,
lists:sort(Rs)).
%%%-----------------------------------------------------------------------
2019-07-10 09:31:51 +02:00
-spec adhoc_local_items(mod_disco:items_acc(),
jid(), jid(), binary()) -> mod_disco:items_acc().
adhoc_local_items(Acc, From,
#jid{lserver = LServer, server = Server} = To, Lang) ->
case acl:match_rule(LServer, configure, From) of
allow ->
Items = case Acc of
{result, Its} -> Its;
empty -> []
end,
PermLev = get_permission_level(From),
Nodes = recursively_get_local_items(PermLev, LServer,
<<"">>, Server, Lang),
2017-01-13 11:36:28 +01:00
Nodes1 = lists:filter(
fun (#disco_item{node = Nd}) ->
2019-07-10 09:31:51 +02:00
F = get_local_features(empty, From, To, Nd, Lang),
2017-01-13 11:36:28 +01:00
case F of
{result, [?NS_COMMANDS]} -> true;
_ -> false
end
end,
Nodes),
{result, Items ++ Nodes1};
_ -> Acc
end.
2019-07-10 09:31:51 +02:00
-spec recursively_get_local_items(global | vhost, binary(), binary(),
binary(), binary()) -> [disco_item()].
recursively_get_local_items(_PermLev, _LServer,
<<"online users">>, _Server, _Lang) ->
[];
recursively_get_local_items(_PermLev, _LServer,
<<"all users">>, _Server, _Lang) ->
[];
recursively_get_local_items(PermLev, LServer, Node,
Server, Lang) ->
LNode = tokenize(Node),
Items = case get_local_items({PermLev, LServer}, LNode,
Server, Lang)
of
{result, Res} -> Res;
{error, _Error} -> []
end,
lists:flatten(
lists:map(
fun(#disco_item{jid = #jid{server = S}, node = Nd} = Item) ->
if (S /= Server) or
(Nd == <<"">>) ->
[];
true ->
[Item,
recursively_get_local_items(
PermLev, LServer, Nd, Server, Lang)]
end
end,
Items)).
2019-07-10 09:31:51 +02:00
-spec get_permission_level(jid()) -> global | vhost.
get_permission_level(JID) ->
case acl:match_rule(global, configure, JID) of
allow -> global;
deny -> vhost
end.
%%%-----------------------------------------------------------------------
-define(ITEMS_RESULT(Allow, LNode, Fallback),
case Allow of
deny -> Fallback;
allow ->
PermLev = get_permission_level(From),
case get_local_items({PermLev, LServer}, LNode,
jid:encode(To), Lang)
of
{result, Res} -> {result, Res};
{error, Error} -> {error, Error}
end
end).
2019-07-10 09:31:51 +02:00
-spec get_local_items(mod_disco:items_acc(), jid(), jid(),
binary(), binary()) -> mod_disco:items_acc().
get_local_items(Acc, From, #jid{lserver = LServer} = To,
<<"">>, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false -> Acc;
_ ->
Items = case Acc of
{result, Its} -> Its;
empty -> []
end,
Allow = acl:match_rule(LServer, configure, From),
case Allow of
deny -> {result, Items};
allow ->
PermLev = get_permission_level(From),
case get_local_items({PermLev, LServer}, [],
jid:encode(To), Lang)
of
{result, Res} -> {result, Items ++ Res};
{error, _Error} -> {result, Items}
end
end
end;
get_local_items(Acc, From, #jid{lserver = LServer} = To,
Node, Lang) ->
case gen_mod:is_loaded(LServer, mod_adhoc) of
false -> Acc;
_ ->
LNode = tokenize(Node),
Allow = acl:match_rule(LServer, configure, From),
Err = xmpp:err_forbidden(?T("Access denied by service policy"), Lang),
case LNode of
[<<"config">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"user">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"online users">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"all users">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"all users">>, <<$@, _/binary>>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"outgoing s2s">> | _] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"stopped nodes">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"DB">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"backup">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"import">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"import">>, _] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"restart">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
[<<"config">>, _] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"add-user">>) ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"delete-user">>) ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"end-user-session">>) ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-user-password">>) ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"change-user-password">>) ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-user-lastlogin">>) ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"user-stats">>) ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-registered-users-list">>) ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-registered-users-num">>) ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-online-users-list">>) ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
?NS_ADMINL(<<"get-online-users-num">>) ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
_ -> Acc
end
end.
%%%-----------------------------------------------------------------------
2019-07-10 09:31:51 +02:00
-spec get_local_items({global | vhost, binary()}, [binary()],
binary(), binary()) -> {result, [disco_item()]} | {error, stanza_error()}.
get_local_items(_Host, [], Server, Lang) ->
{result,
[?NODE(?T("Configuration"), <<"config">>),
?NODE(?T("User Management"), <<"user">>),
?NODE(?T("Online Users"), <<"online users">>),
?NODE(?T("All Users"), <<"all users">>),
?NODE(?T("Outgoing s2s Connections"),
<<"outgoing s2s">>),
?NODE(?T("Running Nodes"), <<"running nodes">>),
?NODE(?T("Stopped Nodes"), <<"stopped nodes">>)]};
get_local_items(_Host, [<<"config">>, _], _Server,
_Lang) ->
{result, []};
get_local_items(_Host, [<<"user">>], Server, Lang) ->
{result,
[?NODE(?T("Add User"), (?NS_ADMINX(<<"add-user">>))),
?NODE(?T("Delete User"),
(?NS_ADMINX(<<"delete-user">>))),
?NODE(?T("End User Session"),
(?NS_ADMINX(<<"end-user-session">>))),
?NODE(?T("Get User Password"),
(?NS_ADMINX(<<"get-user-password">>))),
?NODE(?T("Change User Password"),
(?NS_ADMINX(<<"change-user-password">>))),
?NODE(?T("Get User Last Login Time"),
(?NS_ADMINX(<<"get-user-lastlogin">>))),
?NODE(?T("Get User Statistics"),
(?NS_ADMINX(<<"user-stats">>))),
?NODE(?T("Get List of Registered Users"),
(?NS_ADMINX(<<"get-registered-users-list">>))),
?NODE(?T("Get Number of Registered Users"),
(?NS_ADMINX(<<"get-registered-users-num">>))),
?NODE(?T("Get List of Online Users"),
(?NS_ADMINX(<<"get-online-users-list">>))),
?NODE(?T("Get Number of Online Users"),
(?NS_ADMINX(<<"get-online-users-num">>)))]};
get_local_items(_Host, [<<"http:">> | _], _Server,
_Lang) ->
{result, []};
get_local_items({_, Host}, [<<"online users">>],
_Server, _Lang) ->
{result, get_online_vh_users(Host)};
get_local_items({_, Host}, [<<"all users">>], _Server,
_Lang) ->
{result, get_all_vh_users(Host)};
get_local_items({_, Host},
[<<"all users">>, <<$@, Diap/binary>>], _Server,
_Lang) ->
Use cache for authentication backends The commit introduces the following API incompatibilities: In ejabberd_auth.erl: * dirty_get_registered_users/0 is renamed to get_users/0 * get_vh_registered_users/1 is renamed to get_users/1 * get_vh_registered_users/2 is renamed to get_users/2 * get_vh_registered_users_number/1 is renamed to count_users/1 * get_vh_registered_users_number/2 is renamed to count_users/2 In ejabberd_auth callbacks * plain_password_required/0 is replaced by plain_password_required/1 where the argument is a virtual host * store_type/0 is replaced by store_type/1 where the argument is a virtual host * set_password/3 is now an optional callback * remove_user/3 callback is no longer needed * remove_user/2 now should return `ok | {error, atom()}` * is_user_exists/2 now must only be implemented for backends with `external` store type * check_password/6 is no longer needed * check_password/4 now must only be implemented for backends with `external` store type * try_register/3 is now an optional callback and should return `ok | {error, atom()}` * dirty_get_registered_users/0 is no longer needed * get_vh_registered_users/1 is no longer needed * get_vh_registered_users/2 is renamed to get_users/2 * get_vh_registered_users_number/1 is no longer needed * get_vh_registered_users_number/2 is renamed to count_users/2 * get_password_s/2 is no longer needed * get_password/2 now must only be implemented for backends with `plain` or `scram` store type Additionally, the commit introduces two new callbacks: * use_cache/1 where the argument is a virtual host * cache_nodes/1 where the argument is a virtual host New options are also introduced: `auth_use_cache`, `auth_cache_missed`, `auth_cache_life_time` and `auth_cache_size`.
2017-05-11 13:37:21 +02:00
Users = ejabberd_auth:get_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
try
[S1, S2] = ejabberd_regexp:split(Diap, <<"-">>),
N1 = binary_to_integer(S1),
N2 = binary_to_integer(S2),
Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
{result, lists:map(
fun({S, U}) ->
#disco_item{jid = jid:make(U, S),
name = <<U/binary, $@, S/binary>>}
end, Sub)}
catch _:_ ->
2019-07-10 09:31:51 +02:00
{error, xmpp:err_not_acceptable()}
end;
get_local_items({_, Host}, [<<"outgoing s2s">>],
_Server, Lang) ->
{result, get_outgoing_s2s(Host, Lang)};
get_local_items({_, Host}, [<<"outgoing s2s">>, To],
_Server, Lang) ->
{result, get_outgoing_s2s(Host, Lang, To)};
get_local_items(_Host, [<<"running nodes">>], Server,
Lang) ->
{result, get_running_nodes(Server, Lang)};
get_local_items(_Host, [<<"stopped nodes">>], _Server,
Lang) ->
{result, get_stopped_nodes(Lang)};
get_local_items({global, _Host},
[<<"running nodes">>, ENode], Server, Lang) ->
{result,
[?NODE(?T("Database"),
<<"running nodes/", ENode/binary, "/DB">>),
?NODE(?T("Backup Management"),
<<"running nodes/", ENode/binary, "/backup">>),
?NODE(?T("Import Users From jabberd14 Spool Files"),
<<"running nodes/", ENode/binary, "/import">>),
?NODE(?T("Restart Service"),
<<"running nodes/", ENode/binary, "/restart">>),
?NODE(?T("Shut Down Service"),
<<"running nodes/", ENode/binary, "/shutdown">>)]};
get_local_items(_Host,
[<<"running nodes">>, _ENode, <<"DB">>], _Server,
_Lang) ->
{result, []};
get_local_items(_Host,
[<<"running nodes">>, ENode, <<"backup">>], Server,
Lang) ->
{result,
[?NODE(?T("Backup"),
<<"running nodes/", ENode/binary, "/backup/backup">>),
?NODE(?T("Restore"),
<<"running nodes/", ENode/binary, "/backup/restore">>),
?NODE(?T("Dump to Text File"),
<<"running nodes/", ENode/binary,
"/backup/textfile">>)]};
get_local_items(_Host,
[<<"running nodes">>, _ENode, <<"backup">>, _], _Server,
_Lang) ->
{result, []};
get_local_items(_Host,
[<<"running nodes">>, ENode, <<"import">>], Server,
Lang) ->
{result,
[?NODE(?T("Import File"),
<<"running nodes/", ENode/binary, "/import/file">>),
?NODE(?T("Import Directory"),
<<"running nodes/", ENode/binary, "/import/dir">>)]};
get_local_items(_Host,
[<<"running nodes">>, _ENode, <<"import">>, _], _Server,
_Lang) ->
{result, []};
get_local_items(_Host,
[<<"running nodes">>, _ENode, <<"restart">>], _Server,
_Lang) ->
{result, []};
get_local_items(_Host,
[<<"running nodes">>, _ENode, <<"shutdown">>], _Server,
_Lang) ->
{result, []};
get_local_items(_Host, _, _Server, _Lang) ->
{error, xmpp:err_item_not_found()}.
2019-07-10 09:31:51 +02:00
-spec get_online_vh_users(binary()) -> [disco_item()].
get_online_vh_users(Host) ->
2019-07-15 16:03:29 +02:00
USRs = ejabberd_sm:get_vh_session_list(Host),
SURs = lists:sort([{S, U, R} || {U, S, R} <- USRs]),
lists:map(
fun({S, U, R}) ->
#disco_item{jid = jid:make(U, S, R),
name = <<U/binary, "@", S/binary>>}
end, SURs).
2019-07-10 09:31:51 +02:00
-spec get_all_vh_users(binary()) -> [disco_item()].
get_all_vh_users(Host) ->
2019-07-15 16:03:29 +02:00
Users = ejabberd_auth:get_users(Host),
SUsers = lists:sort([{S, U} || {U, S} <- Users]),
case length(SUsers) of
N when N =< 100 ->
lists:map(fun({S, U}) ->
#disco_item{jid = jid:make(U, S),
name = <<U/binary, $@, S/binary>>}
end, SUsers);
N ->
NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1)) + 1,
M = trunc(N / NParts) + 1,
lists:map(
fun (K) ->
L = K + M - 1,
Node = <<"@",
(integer_to_binary(K))/binary,
"-",
(integer_to_binary(L))/binary>>,
{FS, FU} = lists:nth(K, SUsers),
{LS, LU} = if L < N -> lists:nth(L, SUsers);
true -> lists:last(SUsers)
end,
Name = <<FU/binary, "@", FS/binary, " -- ",
LU/binary, "@", LS/binary>>,
#disco_item{jid = jid:make(Host),
node = <<"all users/", Node/binary>>,
name = Name}
end, lists:seq(1, N, M))
end.
2019-07-10 09:31:51 +02:00
-spec get_outgoing_s2s(binary(), binary()) -> [disco_item()].
get_outgoing_s2s(Host, Lang) ->
2019-07-15 16:03:29 +02:00
Connections = ejabberd_s2s:dirty_get_connections(),
DotHost = <<".", Host/binary>>,
TConns = [TH || {FH, TH} <- Connections,
Host == FH orelse str:suffix(DotHost, FH)],
lists:map(
fun (T) ->
Name = str:translate_and_format(Lang, ?T("To ~ts"),[T]),
2019-07-15 16:03:29 +02:00
#disco_item{jid = jid:make(Host),
node = <<"outgoing s2s/", T/binary>>,
name = Name}
end, lists:usort(TConns)).
2019-07-10 09:31:51 +02:00
-spec get_outgoing_s2s(binary(), binary(), binary()) -> [disco_item()].
get_outgoing_s2s(Host, Lang, To) ->
2019-07-15 16:03:29 +02:00
Connections = ejabberd_s2s:dirty_get_connections(),
lists:map(
fun ({F, _T}) ->
Node = <<"outgoing s2s/", To/binary, "/", F/binary>>,
Name = str:translate_and_format(Lang, ?T("From ~ts"), [F]),
2019-07-15 16:03:29 +02:00
#disco_item{jid = jid:make(Host), node = Node, name = Name}
end,
lists:keysort(
1,
lists:filter(fun (E) -> element(2, E) == To end,
Connections))).
2019-07-10 09:31:51 +02:00
-spec get_running_nodes(binary(), binary()) -> [disco_item()].
get_running_nodes(Server, _Lang) ->
2019-07-15 16:03:29 +02:00
DBNodes = mnesia:system_info(running_db_nodes),
lists:map(
fun (N) ->
S = iolist_to_binary(atom_to_list(N)),
#disco_item{jid = jid:make(Server),
node = <<"running nodes/", S/binary>>,
name = S}
end, lists:sort(DBNodes)).
2019-07-10 09:31:51 +02:00
-spec get_stopped_nodes(binary()) -> [disco_item()].
get_stopped_nodes(_Lang) ->
2019-07-15 16:03:29 +02:00
DBNodes = lists:usort(mnesia:system_info(db_nodes) ++
mnesia:system_info(extra_db_nodes))
-- mnesia:system_info(running_db_nodes),
lists:map(
fun (N) ->
S = iolist_to_binary(atom_to_list(N)),
#disco_item{jid = jid:make(ejabberd_config:get_myname()),
node = <<"stopped nodes/", S/binary>>,
name = S}
end, lists:sort(DBNodes)).
%%-------------------------------------------------------------------------
-define(COMMANDS_RESULT(LServerOrGlobal, From, To,
Request, Lang),
case acl:match_rule(LServerOrGlobal, configure, From) of
deny -> {error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
allow -> adhoc_local_commands(From, To, Request)
end).
2016-08-09 09:56:32 +02:00
-spec adhoc_local_commands(adhoc_command(), jid(), jid(), adhoc_command()) ->
adhoc_command() | {error, stanza_error()}.
adhoc_local_commands(Acc, From,
#jid{lserver = LServer} = To,
#adhoc_command{node = Node, lang = Lang} = Request) ->
LNode = tokenize(Node),
case LNode of
[<<"running nodes">>, _ENode, <<"DB">>] ->
?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"backup">>, _] ->
?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"import">>, _] ->
?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"restart">>] ->
?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"running nodes">>, _ENode, <<"shutdown">>] ->
?COMMANDS_RESULT(global, From, To, Request, Lang);
[<<"config">>, _] ->
?COMMANDS_RESULT(LServer, From, To, Request, Lang);
?NS_ADMINL(_) ->
?COMMANDS_RESULT(LServer, From, To, Request, Lang);
_ -> Acc
end.
2019-07-10 09:31:51 +02:00
-spec adhoc_local_commands(jid(), jid(), adhoc_command()) -> adhoc_command() | {error, stanza_error()}.
adhoc_local_commands(From,
#jid{lserver = LServer} = _To,
#adhoc_command{lang = Lang, node = Node,
sid = SessionID, action = Action,
xdata = XData} = Request) ->
LNode = tokenize(Node),
ActionIsExecute = Action == execute orelse Action == complete,
if Action == cancel ->
#adhoc_command{status = canceled, lang = Lang,
node = Node, sid = SessionID};
XData == undefined, ActionIsExecute ->
case get_form(LServer, LNode, Lang) of
{result, Form} ->
xmpp_util:make_adhoc_response(
Request,
#adhoc_command{status = executing, xdata = Form});
{result, Status, Form} ->
xmpp_util:make_adhoc_response(
Request,
#adhoc_command{status = Status, xdata = Form});
{error, Error} -> {error, Error}
end;
XData /= undefined, ActionIsExecute ->
2019-07-15 16:03:29 +02:00
case set_form(From, LServer, LNode, Lang, XData) of
{result, Res} ->
xmpp_util:make_adhoc_response(
Request,
#adhoc_command{xdata = Res, status = completed});
2019-07-15 16:03:29 +02:00
%%{'EXIT', _} -> {error, xmpp:err_bad_request()};
{error, Error} -> {error, Error}
end;
true ->
{error, xmpp:err_bad_request(?T("Unexpected action"), Lang)}
end.
-define(TVFIELD(Type, Var, Val),
#xdata_field{type = Type, var = Var, values = [Val]}).
-define(HFIELD(),
?TVFIELD(hidden, <<"FORM_TYPE">>, (?NS_ADMIN))).
-define(TLFIELD(Type, Label, Var),
#xdata_field{type = Type, label = tr(Lang, Label), var = Var}).
-define(XFIELD(Type, Label, Var, Val),
#xdata_field{type = Type, label = tr(Lang, Label),
var = Var, values = [Val]}).
-define(XMFIELD(Type, Label, Var, Vals),
#xdata_field{type = Type, label = tr(Lang, Label),
var = Var, values = Vals}).
-define(TABLEFIELD(Table, Val),
#xdata_field{
type = 'list-single',
label = iolist_to_binary(atom_to_list(Table)),
var = iolist_to_binary(atom_to_list(Table)),
values = [iolist_to_binary(atom_to_list(Val))],
options = [#xdata_option{label = tr(Lang, ?T("RAM copy")),
value = <<"ram_copies">>},
#xdata_option{label = tr(Lang, ?T("RAM and disc copy")),
value = <<"disc_copies">>},
#xdata_option{label = tr(Lang, ?T("Disc only copy")),
value = <<"disc_only_copies">>},
#xdata_option{label = tr(Lang, ?T("Remote copy")),
value = <<"unknown">>}]}).
2019-07-10 09:31:51 +02:00
-spec get_form(binary(), [binary()], binary()) -> {result, xdata()} |
{result, completed, xdata()} |
{error, stanza_error()}.
get_form(_Host, [<<"running nodes">>, ENode, <<"DB">>],
Lang) ->
case search_running_node(ENode) of
false ->
Txt = ?T("No running node found"),
{error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
2015-10-07 00:06:58 +02:00
case ejabberd_cluster:call(Node, mnesia, system_info, [tables]) of
{badrpc, Reason} ->
?ERROR_MSG("RPC call mnesia:system_info(tables) on node "
"~ts failed: ~p", [Node, Reason]),
{error, xmpp:err_internal_server_error()};
Tables ->
STables = lists:sort(Tables),
Title = <<(tr(Lang, ?T("Database Tables Configuration at ")))/binary,
ENode/binary>>,
Instr = tr(Lang, ?T("Choose storage type of tables")),
try
Fs = lists:map(
fun(Table) ->
case ejabberd_cluster:call(
Node, mnesia, table_info,
[Table, storage_type]) of
Type when is_atom(Type) ->
?TABLEFIELD(Table, Type)
end
end, STables),
{result, #xdata{title = Title,
type = form,
instructions = [Instr],
fields = [?HFIELD()|Fs]}}
catch _:{case_clause, {badrpc, Reason}} ->
?ERROR_MSG("RPC call mnesia:table_info/2 "
"on node ~ts failed: ~p", [Node, Reason]),
{error, xmpp:err_internal_server_error()}
end
end
end;
get_form(_Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"backup">>],
Lang) ->
{result,
#xdata{title = <<(tr(Lang, ?T("Backup to File at ")))/binary, ENode/binary>>,
type = form,
instructions = [tr(Lang, ?T("Enter path to backup file"))],
fields = [?HFIELD(),
?XFIELD('text-single', ?T("Path to File"),
<<"path">>, <<"">>)]}};
get_form(_Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"restore">>],
Lang) ->
{result,
#xdata{title = <<(tr(Lang, ?T("Restore Backup from File at ")))/binary,
ENode/binary>>,
type = form,
instructions = [tr(Lang, ?T("Enter path to backup file"))],
fields = [?HFIELD(),
?XFIELD('text-single', ?T("Path to File"),
<<"path">>, <<"">>)]}};
get_form(_Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"textfile">>],
Lang) ->
{result,
#xdata{title = <<(tr(Lang, ?T("Dump Backup to Text File at ")))/binary,
ENode/binary>>,
type = form,
instructions = [tr(Lang, ?T("Enter path to text file"))],
fields = [?HFIELD(),
?XFIELD('text-single', ?T("Path to File"),
<<"path">>, <<"">>)]}};
get_form(_Host,
[<<"running nodes">>, ENode, <<"import">>, <<"file">>],
Lang) ->
{result,
#xdata{title = <<(tr(Lang, ?T("Import User from File at ")))/binary,
ENode/binary>>,
type = form,
instructions = [tr(Lang, ?T("Enter path to jabberd14 spool file"))],
fields = [?HFIELD(),
?XFIELD('text-single', ?T("Path to File"),
<<"path">>, <<"">>)]}};
get_form(_Host,
[<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
Lang) ->
{result,
#xdata{title = <<(tr(Lang, ?T("Import Users from Dir at ")))/binary,
ENode/binary>>,
type = form,
instructions = [tr(Lang, ?T("Enter path to jabberd14 spool dir"))],
fields = [?HFIELD(),
?XFIELD('text-single', ?T("Path to Dir"),
<<"path">>, <<"">>)]}};
get_form(_Host,
[<<"running nodes">>, _ENode, <<"restart">>], Lang) ->
Make_option =
fun (LabelNum, LabelUnit, Value) ->
#xdata_option{
label = <<LabelNum/binary, (tr(Lang, LabelUnit))/binary>>,
value = Value}
end,
{result,
#xdata{title = tr(Lang, ?T("Restart Service")),
type = form,
fields = [?HFIELD(),
#xdata_field{
type = 'list-single',
label = tr(Lang, ?T("Time delay")),
var = <<"delay">>,
required = true,
options =
[Make_option(<<"">>, <<"immediately">>, <<"1">>),
Make_option(<<"15 ">>, <<"seconds">>, <<"15">>),
Make_option(<<"30 ">>, <<"seconds">>, <<"30">>),
Make_option(<<"60 ">>, <<"seconds">>, <<"60">>),
Make_option(<<"90 ">>, <<"seconds">>, <<"90">>),
Make_option(<<"2 ">>, <<"minutes">>, <<"120">>),
Make_option(<<"3 ">>, <<"minutes">>, <<"180">>),
Make_option(<<"4 ">>, <<"minutes">>, <<"240">>),
Make_option(<<"5 ">>, <<"minutes">>, <<"300">>),
Make_option(<<"10 ">>, <<"minutes">>, <<"600">>),
Make_option(<<"15 ">>, <<"minutes">>, <<"900">>),
Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>)]},
#xdata_field{type = fixed,
label = tr(Lang,
?T("Send announcement to all online users "
"on all hosts"))},
#xdata_field{var = <<"subject">>,
type = 'text-single',
label = tr(Lang, ?T("Subject"))},
#xdata_field{var = <<"announcement">>,
type = 'text-multi',
label = tr(Lang, ?T("Message body"))}]}};
get_form(_Host,
[<<"running nodes">>, _ENode, <<"shutdown">>], Lang) ->
Make_option =
fun (LabelNum, LabelUnit, Value) ->
#xdata_option{
label = <<LabelNum/binary, (tr(Lang, LabelUnit))/binary>>,
value = Value}
end,
{result,
#xdata{title = tr(Lang, ?T("Shut Down Service")),
type = form,
fields = [?HFIELD(),
#xdata_field{
type = 'list-single',
label = tr(Lang, ?T("Time delay")),
var = <<"delay">>,
required = true,
options =
[Make_option(<<"">>, <<"immediately">>, <<"1">>),
Make_option(<<"15 ">>, <<"seconds">>, <<"15">>),
Make_option(<<"30 ">>, <<"seconds">>, <<"30">>),
Make_option(<<"60 ">>, <<"seconds">>, <<"60">>),
Make_option(<<"90 ">>, <<"seconds">>, <<"90">>),
Make_option(<<"2 ">>, <<"minutes">>, <<"120">>),
Make_option(<<"3 ">>, <<"minutes">>, <<"180">>),
Make_option(<<"4 ">>, <<"minutes">>, <<"240">>),
Make_option(<<"5 ">>, <<"minutes">>, <<"300">>),
Make_option(<<"10 ">>, <<"minutes">>, <<"600">>),
Make_option(<<"15 ">>, <<"minutes">>, <<"900">>),
Make_option(<<"30 ">>, <<"minutes">>, <<"1800">>)]},
#xdata_field{type = fixed,
label = tr(Lang,
?T("Send announcement to all online users "
"on all hosts"))},
#xdata_field{var = <<"subject">>,
type = 'text-single',
label = tr(Lang, ?T("Subject"))},
#xdata_field{var = <<"announcement">>,
type = 'text-multi',
label = tr(Lang, ?T("Message body"))}]}};
get_form(_Host, ?NS_ADMINL(<<"add-user">>), Lang) ->
{result,
#xdata{title = tr(Lang, ?T("Add User")),
type = form,
fields = [?HFIELD(),
#xdata_field{type = 'jid-single',
label = tr(Lang, ?T("Jabber ID")),
required = true,
var = <<"accountjid">>},
#xdata_field{type = 'text-private',
label = tr(Lang, ?T("Password")),
required = true,
var = <<"password">>},
#xdata_field{type = 'text-private',
label = tr(Lang, ?T("Password Verification")),
required = true,
var = <<"password-verify">>}]}};
get_form(_Host, ?NS_ADMINL(<<"delete-user">>), Lang) ->
{result,
#xdata{title = tr(Lang, ?T("Delete User")),
type = form,
fields = [?HFIELD(),
#xdata_field{type = 'jid-multi',
label = tr(Lang, ?T("Jabber ID")),
required = true,
var = <<"accountjids">>}]}};
get_form(_Host, ?NS_ADMINL(<<"end-user-session">>),
Lang) ->
{result,
#xdata{title = tr(Lang, ?T("End User Session")),
type = form,
fields = [?HFIELD(),
#xdata_field{type = 'jid-single',
label = tr(Lang, ?T("Jabber ID")),
required = true,
var = <<"accountjid">>}]}};
get_form(_Host, ?NS_ADMINL(<<"get-user-password">>),
Lang) ->
{result,
#xdata{title = tr(Lang, ?T("Get User Password")),
type = form,
fields = [?HFIELD(),
#xdata_field{type = 'jid-single',
label = tr(Lang, ?T("Jabber ID")),
var = <<"accountjid">>,
required = true}]}};
get_form(_Host, ?NS_ADMINL(<<"change-user-password">>),
Lang) ->
{result,
#xdata{title = tr(Lang, ?T("Change User Password")),
type = form,
fields = [?HFIELD(),
#xdata_field{type = 'jid-single',
label = tr(Lang, ?T("Jabber ID")),
required = true,
var = <<"accountjid">>},
#xdata_field{type = 'text-private',
label = tr(Lang, ?T("Password")),
required = true,
var = <<"password">>}]}};
get_form(_Host, ?NS_ADMINL(<<"get-user-lastlogin">>),
Lang) ->
{result,
#xdata{title = tr(Lang, ?T("Get User Last Login Time")),
type = form,
fields = [?HFIELD(),
#xdata_field{type = 'jid-single',
label = tr(Lang, ?T("Jabber ID")),
var = <<"accountjid">>,
required = true}]}};
get_form(_Host, ?NS_ADMINL(<<"user-stats">>), Lang) ->
{result,
#xdata{title = tr(Lang, ?T("Get User Statistics")),
type = form,
fields = [?HFIELD(),
#xdata_field{type = 'jid-single',
label = tr(Lang, ?T("Jabber ID")),
var = <<"accountjid">>,
required = true}]}};
get_form(Host, ?NS_ADMINL(<<"get-registered-users-list">>), Lang) ->
Values = [jid:encode(jid:make(U, Host))
|| {U, _} <- ejabberd_auth:get_users(Host)],
{result, completed,
#xdata{type = form,
fields = [?HFIELD(),
#xdata_field{type = 'jid-multi',
label = tr(Lang, ?T("The list of all users")),
var = <<"registereduserjids">>,
values = Values}]}};
get_form(Host,
?NS_ADMINL(<<"get-registered-users-num">>), Lang) ->
Use cache for authentication backends The commit introduces the following API incompatibilities: In ejabberd_auth.erl: * dirty_get_registered_users/0 is renamed to get_users/0 * get_vh_registered_users/1 is renamed to get_users/1 * get_vh_registered_users/2 is renamed to get_users/2 * get_vh_registered_users_number/1 is renamed to count_users/1 * get_vh_registered_users_number/2 is renamed to count_users/2 In ejabberd_auth callbacks * plain_password_required/0 is replaced by plain_password_required/1 where the argument is a virtual host * store_type/0 is replaced by store_type/1 where the argument is a virtual host * set_password/3 is now an optional callback * remove_user/3 callback is no longer needed * remove_user/2 now should return `ok | {error, atom()}` * is_user_exists/2 now must only be implemented for backends with `external` store type * check_password/6 is no longer needed * check_password/4 now must only be implemented for backends with `external` store type * try_register/3 is now an optional callback and should return `ok | {error, atom()}` * dirty_get_registered_users/0 is no longer needed * get_vh_registered_users/1 is no longer needed * get_vh_registered_users/2 is renamed to get_users/2 * get_vh_registered_users_number/1 is no longer needed * get_vh_registered_users_number/2 is renamed to count_users/2 * get_password_s/2 is no longer needed * get_password/2 now must only be implemented for backends with `plain` or `scram` store type Additionally, the commit introduces two new callbacks: * use_cache/1 where the argument is a virtual host * cache_nodes/1 where the argument is a virtual host New options are also introduced: `auth_use_cache`, `auth_cache_missed`, `auth_cache_life_time` and `auth_cache_size`.
2017-05-11 13:37:21 +02:00
Num = integer_to_binary(ejabberd_auth:count_users(Host)),
{result, completed,
#xdata{type = form,
fields = [?HFIELD(),
#xdata_field{type = 'text-single',
label = tr(Lang, ?T("Number of registered users")),
var = <<"registeredusersnum">>,
values = [Num]}]}};
get_form(Host, ?NS_ADMINL(<<"get-online-users-list">>), Lang) ->
Accounts = [jid:encode(jid:make(U, Host))
|| {U, _, _} <- ejabberd_sm:get_vh_session_list(Host)],
Values = lists:usort(Accounts),
{result, completed,
#xdata{type = form,
fields = [?HFIELD(),
#xdata_field{type = 'jid-multi',
label = tr(Lang, ?T("The list of all online users")),
var = <<"onlineuserjids">>,
values = Values}]}};
get_form(Host, ?NS_ADMINL(<<"get-online-users-num">>),
Lang) ->
Num = integer_to_binary(ejabberd_sm:get_vh_session_number(Host)),
{result, completed,
#xdata{type = form,
fields = [?HFIELD(),
#xdata_field{type = 'text-single',
label = tr(Lang, ?T("Number of online users")),
var = <<"onlineusersnum">>,
values = [Num]}]}};
get_form(_Host, _, _Lang) ->
{error, xmpp:err_service_unavailable()}.
2019-07-10 09:31:51 +02:00
-spec set_form(jid(), binary(), [binary()], binary(), xdata()) -> {result, xdata() | undefined} |
{error, stanza_error()}.
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"DB">>], Lang, XData) ->
case search_running_node(ENode) of
false ->
Txt = ?T("No running node found"),
{error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
lists:foreach(
fun(#xdata_field{var = SVar, values = SVals}) ->
Table = misc:binary_to_atom(SVar),
Type = case SVals of
[<<"unknown">>] -> unknown;
[<<"ram_copies">>] -> ram_copies;
[<<"disc_copies">>] -> disc_copies;
[<<"disc_only_copies">>] -> disc_only_copies;
_ -> false
end,
if Type == false -> ok;
Type == unknown ->
mnesia:del_table_copy(Table, Node);
true ->
case mnesia:add_table_copy(Table, Node, Type) of
{aborted, _} ->
mnesia:change_table_copy_type(
Table, Node, Type);
_ -> ok
end
end
end, XData#xdata.fields),
{result, undefined}
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"backup">>],
Lang, XData) ->
case search_running_node(ENode) of
false ->
Txt = ?T("No running node found"),
{error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
case xmpp_util:get_xdata_values(<<"path">>, XData) of
[] ->
Txt = ?T("No 'path' found in data form"),
{error, xmpp:err_bad_request(Txt, Lang)};
[String] ->
2019-07-17 19:51:33 +02:00
case ejabberd_cluster:call(
Node, mnesia, backup, [binary_to_list(String)],
timer:minutes(10)) of
{badrpc, Reason} ->
?ERROR_MSG("RPC call mnesia:backup(~ts) to node ~ts "
"failed: ~p", [String, Node, Reason]),
{error, xmpp:err_internal_server_error()};
{error, Reason} ->
?ERROR_MSG("RPC call mnesia:backup(~ts) to node ~ts "
"failed: ~p", [String, Node, Reason]),
{error, xmpp:err_internal_server_error()};
_ ->
{result, undefined}
end;
_ ->
Txt = ?T("Incorrect value of 'path' in data form"),
{error, xmpp:err_bad_request(Txt, Lang)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"restore">>],
Lang, XData) ->
case search_running_node(ENode) of
false ->
Txt = ?T("No running node found"),
{error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
case xmpp_util:get_xdata_values(<<"path">>, XData) of
[] ->
Txt = ?T("No 'path' found in data form"),
{error, xmpp:err_bad_request(Txt, Lang)};
[String] ->
2019-07-17 19:51:33 +02:00
case ejabberd_cluster:call(
Node, ejabberd_admin, restore,
[String], timer:minutes(10)) of
{badrpc, Reason} ->
?ERROR_MSG("RPC call ejabberd_admin:restore(~ts) to node "
"~ts failed: ~p", [String, Node, Reason]),
{error, xmpp:err_internal_server_error()};
{error, Reason} ->
?ERROR_MSG("RPC call ejabberd_admin:restore(~ts) to node "
"~ts failed: ~p", [String, Node, Reason]),
{error, xmpp:err_internal_server_error()};
_ ->
{result, undefined}
end;
_ ->
Txt = ?T("Incorrect value of 'path' in data form"),
{error, xmpp:err_bad_request(Txt, Lang)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"backup">>,
<<"textfile">>],
Lang, XData) ->
case search_running_node(ENode) of
false ->
Txt = ?T("No running node found"),
{error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
case xmpp_util:get_xdata_values(<<"path">>, XData) of
[] ->
Txt = ?T("No 'path' found in data form"),
{error, xmpp:err_bad_request(Txt, Lang)};
[String] ->
2019-07-17 19:51:33 +02:00
case ejabberd_cluster:call(
Node, ejabberd_admin, dump_to_textfile,
[String], timer:minutes(10)) of
{badrpc, Reason} ->
?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~ts) "
"to node ~ts failed: ~p", [String, Node, Reason]),
{error, xmpp:err_internal_server_error()};
{error, Reason} ->
?ERROR_MSG("RPC call ejabberd_admin:dump_to_textfile(~ts) "
"to node ~ts failed: ~p", [String, Node, Reason]),
{error, xmpp:err_internal_server_error()};
_ ->
{result, undefined}
end;
_ ->
Txt = ?T("Incorrect value of 'path' in data form"),
{error, xmpp:err_bad_request(Txt, Lang)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"import">>, <<"file">>],
Lang, XData) ->
case search_running_node(ENode) of
false ->
Txt = ?T("No running node found"),
{error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
case xmpp_util:get_xdata_values(<<"path">>, XData) of
[] ->
Txt = ?T("No 'path' found in data form"),
{error, xmpp:err_bad_request(Txt, Lang)};
[String] ->
ejabberd_cluster:call(Node, jd2ejd, import_file, [String]),
{result, undefined};
_ ->
Txt = ?T("Incorrect value of 'path' in data form"),
{error, xmpp:err_bad_request(Txt, Lang)}
end
end;
set_form(_From, _Host,
[<<"running nodes">>, ENode, <<"import">>, <<"dir">>],
Lang, XData) ->
case search_running_node(ENode) of
false ->
Txt = ?T("No running node found"),
{error, xmpp:err_item_not_found(Txt, Lang)};
Node ->
case xmpp_util:get_xdata_values(<<"path">>, XData) of
[] ->
Txt = ?T("No 'path' found in data form"),
{error, xmpp:err_bad_request(Txt, Lang)};
[String] ->
ejabberd_cluster:call(Node, jd2ejd, import_dir, [String]),
{result, undefined};
_ ->
Txt = ?T("Incorrect value of 'path' in data form"),
{error, xmpp:err_bad_request(Txt, Lang)}
end
end;
set_form(From, Host,
[<<"running nodes">>, ENode, <<"restart">>], _Lang,
XData) ->
stop_node(From, Host, ENode, restart, XData);
set_form(From, Host,
[<<"running nodes">>, ENode, <<"shutdown">>], _Lang,
XData) ->
stop_node(From, Host, ENode, stop, XData);
set_form(From, Host, ?NS_ADMINL(<<"add-user">>), _Lang,
XData) ->
AccountString = get_value(<<"accountjid">>, XData),
Password = get_value(<<"password">>, XData),
Password = get_value(<<"password-verify">>, XData),
AccountJID = jid:decode(AccountString),
User = AccountJID#jid.luser,
Server = AccountJID#jid.lserver,
2019-06-14 11:33:26 +02:00
true = lists:member(Server, ejabberd_option:hosts()),
true = Server == Host orelse
get_permission_level(From) == global,
case ejabberd_auth:try_register(User, Server, Password) of
ok -> {result, undefined};
{error, exists} -> {error, xmpp:err_conflict()};
{error, not_allowed} -> {error, xmpp:err_not_allowed()}
end;
set_form(From, Host, ?NS_ADMINL(<<"delete-user">>),
_Lang, XData) ->
AccountStringList = get_values(<<"accountjids">>,
XData),
[_ | _] = AccountStringList,
ASL2 = lists:map(fun (AccountString) ->
JID = jid:decode(AccountString),
User = JID#jid.luser,
Server = JID#jid.lserver,
true = Server == Host orelse
get_permission_level(From) == global,
2017-05-11 14:49:06 +02:00
true = ejabberd_auth:user_exists(User, Server),
{User, Server}
end,
AccountStringList),
[ejabberd_auth:remove_user(User, Server)
|| {User, Server} <- ASL2],
{result, undefined};
set_form(From, Host, ?NS_ADMINL(<<"end-user-session">>),
_Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
JID = jid:decode(AccountString),
LServer = JID#jid.lserver,
true = LServer == Host orelse
get_permission_level(From) == global,
case JID#jid.lresource of
<<>> ->
ejabberd_sm:kick_user(JID#jid.luser, JID#jid.lserver);
R ->
ejabberd_sm:kick_user(JID#jid.luser, JID#jid.lserver, R)
end,
{result, undefined};
set_form(From, Host,
?NS_ADMINL(<<"get-user-password">>), Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
JID = jid:decode(AccountString),
User = JID#jid.luser,
Server = JID#jid.lserver,
true = Server == Host orelse
get_permission_level(From) == global,
Password = ejabberd_auth:get_password(User, Server),
true = is_binary(Password),
{result,
#xdata{type = form,
fields = [?HFIELD(),
?XFIELD('jid-single', ?T("Jabber ID"),
<<"accountjid">>, AccountString),
?XFIELD('text-single', ?T("Password"),
<<"password">>, Password)]}};
set_form(From, Host,
?NS_ADMINL(<<"change-user-password">>), _Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
Password = get_value(<<"password">>, XData),
JID = jid:decode(AccountString),
User = JID#jid.luser,
Server = JID#jid.lserver,
true = Server == Host orelse
get_permission_level(From) == global,
2017-05-11 14:49:06 +02:00
true = ejabberd_auth:user_exists(User, Server),
ejabberd_auth:set_password(User, Server, Password),
{result, undefined};
set_form(From, Host,
?NS_ADMINL(<<"get-user-lastlogin">>), Lang, XData) ->
AccountString = get_value(<<"accountjid">>, XData),
JID = jid:decode(AccountString),
User = JID#jid.luser,
Server = JID#jid.lserver,
true = Server == Host orelse
get_permission_level(From) == global,
FLast = case ejabberd_sm:get_user_resources(User,
Server)
of
[] ->
case get_last_info(User, Server) of
not_found -> tr(Lang, ?T("Never"));
{ok, Timestamp, _Status} ->
Shift = Timestamp,
TimeStamp = {Shift div 1000000, Shift rem 1000000, 0},
{{Year, Month, Day}, {Hour, Minute, Second}} =
calendar:now_to_local_time(TimeStamp),
(str:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
[Year, Month, Day, Hour,
Minute, Second]))
end;
_ -> tr(Lang, ?T("Online"))
end,
{result,
#xdata{type = form,
fields = [?HFIELD(),
?XFIELD('jid-single', ?T("Jabber ID"),
<<"accountjid">>, AccountString),
?XFIELD('text-single', ?T("Last login"),
<<"lastlogin">>, FLast)]}};
set_form(From, Host, ?NS_ADMINL(<<"user-stats">>), Lang,
XData) ->
AccountString = get_value(<<"accountjid">>, XData),
JID = jid:decode(AccountString),
User = JID#jid.luser,
Server = JID#jid.lserver,
true = Server == Host orelse
get_permission_level(From) == global,
Resources = ejabberd_sm:get_user_resources(User,
Server),
IPs1 = [ejabberd_sm:get_user_ip(User, Server, Resource)
|| Resource <- Resources],
IPs = [<<(misc:ip_to_list(IP))/binary, ":",
(integer_to_binary(Port))/binary>>
|| {IP, Port} <- IPs1],
Items = ejabberd_hooks:run_fold(roster_get, Server, [],
[{User, Server}]),
Rostersize = integer_to_binary(erlang:length(Items)),
{result,
#xdata{type = form,
fields = [?HFIELD(),
?XFIELD('jid-single', ?T("Jabber ID"),
<<"accountjid">>, AccountString),
?XFIELD('text-single', ?T("Roster size"),
<<"rostersize">>, Rostersize),
?XMFIELD('text-multi', ?T("IP addresses"),
<<"ipaddresses">>, IPs),
?XMFIELD('text-multi', ?T("Resources"),
<<"onlineresources">>, Resources)]}};
set_form(_From, _Host, _, _Lang, _XData) ->
{error, xmpp:err_service_unavailable()}.
2019-07-10 09:31:51 +02:00
-spec get_value(binary(), xdata()) -> binary().
get_value(Field, XData) ->
hd(get_values(Field, XData)).
2019-07-10 09:31:51 +02:00
-spec get_values(binary(), xdata()) -> [binary()].
get_values(Field, XData) ->
2016-08-09 09:56:32 +02:00
xmpp_util:get_xdata_values(Field, XData).
2019-07-10 09:31:51 +02:00
-spec search_running_node(binary()) -> false | node().
search_running_node(SNode) ->
search_running_node(SNode,
mnesia:system_info(running_db_nodes)).
2019-07-10 09:31:51 +02:00
-spec search_running_node(binary(), [node()]) -> false | node().
search_running_node(_, []) -> false;
search_running_node(SNode, [Node | Nodes]) ->
2019-07-10 09:31:51 +02:00
case atom_to_binary(Node, utf8) of
SNode -> Node;
_ -> search_running_node(SNode, Nodes)
end.
2019-07-10 09:31:51 +02:00
-spec stop_node(jid(), binary(), binary(), restart | stop, xdata()) -> {result, undefined}.
stop_node(From, Host, ENode, Action, XData) ->
Delay = binary_to_integer(get_value(<<"delay">>, XData)),
2019-07-15 16:03:29 +02:00
Subject = case get_values(<<"subject">>, XData) of
[] ->
[];
2019-07-15 16:03:29 +02:00
[S|_] ->
[#xdata_field{var = <<"subject">>, values = [S]}]
end,
Announcement = case get_values(<<"announcement">>, XData) of
[] ->
[];
As ->
[#xdata_field{var = <<"body">>, values = As}]
end,
case Subject ++ Announcement of
[] ->
ok;
Fields ->
Request = #adhoc_command{node = ?NS_ADMINX(<<"announce-allhosts">>),
action = complete,
xdata = #xdata{type = submit,
fields = Fields}},
To = jid:make(Host),
mod_announce:announce_commands(empty, From, To, Request)
end,
Time = timer:seconds(Delay),
Node = misc:binary_to_atom(ENode),
2019-07-10 09:31:51 +02:00
{ok, _} = timer:apply_after(Time, ejabberd_cluster, call, [Node, init, Action, []]),
{result, undefined}.
2019-07-10 09:31:51 +02:00
-spec get_last_info(binary(), binary()) -> {ok, non_neg_integer(), binary()} | not_found.
get_last_info(User, Server) ->
case gen_mod:is_loaded(Server, mod_last) of
true -> mod_last:get_last_info(User, Server);
false -> not_found
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2019-07-10 09:31:51 +02:00
-spec adhoc_sm_commands(adhoc_command(), jid(), jid(), adhoc_command()) -> adhoc_command() |
{error, stanza_error()}.
adhoc_sm_commands(_Acc, From,
#jid{user = User, server = Server, lserver = LServer},
#adhoc_command{lang = Lang, node = <<"config">>,
action = Action, xdata = XData} = Request) ->
case acl:match_rule(LServer, configure, From) of
deny ->
{error, xmpp:err_forbidden(?T("Access denied by service policy"), Lang)};
allow ->
ActionIsExecute = Action == execute orelse Action == complete,
if Action == cancel ->
xmpp_util:make_adhoc_response(
Request, #adhoc_command{status = canceled});
XData == undefined, ActionIsExecute ->
case get_sm_form(User, Server, <<"config">>, Lang) of
{result, Form} ->
xmpp_util:make_adhoc_response(
Request, #adhoc_command{status = executing,
xdata = Form});
{error, Error} ->
{error, Error}
end;
XData /= undefined, ActionIsExecute ->
set_sm_form(User, Server, <<"config">>, Request);
true ->
Txt = ?T("Unexpected action"),
{error, xmpp:err_bad_request(Txt, Lang)}
end
end;
adhoc_sm_commands(Acc, _From, _To, _Request) -> Acc.
2019-07-10 09:31:51 +02:00
-spec get_sm_form(binary(), binary(), binary(), binary()) -> {result, xdata()} |
{error, stanza_error()}.
get_sm_form(User, Server, <<"config">>, Lang) ->
{result,
#xdata{type = form,
title = <<(tr(Lang, ?T("Administration of ")))/binary, User/binary>>,
fields =
[?HFIELD(),
#xdata_field{
type = 'list-single',
label = tr(Lang, ?T("Action on user")),
var = <<"action">>,
values = [<<"edit">>],
options = [#xdata_option{
label = tr(Lang, ?T("Edit Properties")),
value = <<"edit">>},
#xdata_option{
label = tr(Lang, ?T("Remove User")),
value = <<"remove">>}]},
?XFIELD('text-private', ?T("Password"),
<<"password">>,
ejabberd_auth:get_password_s(User, Server))]}};
get_sm_form(_User, _Server, _Node, _Lang) ->
{error, xmpp:err_service_unavailable()}.
2019-07-10 09:31:51 +02:00
-spec set_sm_form(binary(), binary(), binary(), adhoc_command()) -> adhoc_command() |
{error, stanza_error()}.
set_sm_form(User, Server, <<"config">>,
#adhoc_command{lang = Lang, node = Node,
sid = SessionID, xdata = XData}) ->
Response = #adhoc_command{lang = Lang, node = Node,
sid = SessionID, status = completed},
case xmpp_util:get_xdata_values(<<"action">>, XData) of
[<<"edit">>] ->
case xmpp_util:get_xdata_values(<<"password">>, XData) of
[Password] ->
ejabberd_auth:set_password(User, Server, Password),
xmpp_util:make_adhoc_response(Response);
_ ->
Txt = ?T("No 'password' found in data form"),
{error, xmpp:err_not_acceptable(Txt, Lang)}
end;
[<<"remove">>] ->
2019-07-15 16:03:29 +02:00
ejabberd_auth:remove_user(User, Server),
xmpp_util:make_adhoc_response(Response);
_ ->
Txt = ?T("Incorrect value of 'action' in data form"),
{error, xmpp:err_not_acceptable(Txt, Lang)}
end;
set_sm_form(_User, _Server, _Node, _Request) ->
{error, xmpp:err_service_unavailable()}.
2015-06-01 14:38:27 +02:00
-spec tr(binary(), binary()) -> binary().
tr(Lang, Text) ->
translate:translate(Lang, Text).
mod_options(_) -> [].
mod_doc() ->
#{desc =>
?T("The module provides server configuration functionality via "
"https://xmpp.org/extensions/xep-0050.html"
"[XEP-0050: Ad-Hoc Commands]. This module requires "
"_`mod_adhoc`_ to be loaded.")}.