aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ejabberd_app.erl25
-rw-r--r--src/ejabberd_c2s.erl9
-rw-r--r--src/ejabberd_cluster.erl232
-rw-r--r--src/ejabberd_cluster_mnesia.erl144
-rw-r--r--src/ejabberd_commands.erl275
-rw-r--r--src/ejabberd_commands_doc.erl87
-rw-r--r--src/ejabberd_http_ws.erl2
-rw-r--r--src/ejabberd_listener.erl4
-rw-r--r--src/ejabberd_logger.erl3
-rw-r--r--src/ejabberd_mnesia.erl2
-rw-r--r--src/ejabberd_oauth.erl26
-rw-r--r--src/ejabberd_router_mnesia.erl2
-rw-r--r--src/ejabberd_s2s.erl30
-rw-r--r--src/ejabberd_sm.erl24
-rw-r--r--src/ejabberd_sup.erl7
-rw-r--r--src/ext_mod.erl68
-rw-r--r--src/mod_admin_extra.erl140
-rw-r--r--src/mod_http_api.erl12
-rw-r--r--src/mod_http_fileserver.erl16
-rw-r--r--src/mod_mam.erl3
-rw-r--r--src/mod_muc.erl3
-rw-r--r--src/mod_muc_riak.erl2
-rw-r--r--src/mod_muc_room.erl2
-rw-r--r--src/mod_privacy.erl6
-rw-r--r--src/mod_pubsub.erl37
-rw-r--r--src/node_pep.erl2
-rw-r--r--src/node_pep_sql.erl2
27 files changed, 619 insertions, 546 deletions
diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl
index b52450d24..64edf508c 100644
--- a/src/ejabberd_app.erl
+++ b/src/ejabberd_app.erl
@@ -25,12 +25,11 @@
-module(ejabberd_app).
--behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(application).
--export([start/2, prep_stop/1, stop/1, opt_type/1]).
+-export([start/2, prep_stop/1, stop/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -49,13 +48,12 @@ start(normal, _Args) ->
setup_if_elixir_conf_used(),
ejabberd_config:start(),
ejabberd_mnesia:start(),
- set_settings_from_config(),
file_queue_init(),
maybe_add_nameservers(),
- connect_nodes(),
case ejabberd_sup:start_link() of
{ok, SupPid} ->
register_elixir_config_hooks(),
+ ejabberd_cluster:wait_for_sync(infinity),
{T2, _} = statistics(wall_clock),
?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs",
[?VERSION, node(), (T2-T1)/1000]),
@@ -88,12 +86,6 @@ stop(_State) ->
%%% Internal functions
%%%
-connect_nodes() ->
- Nodes = ejabberd_config:get_option(cluster_nodes, []),
- lists:foreach(fun(Node) ->
- net_kernel:connect_node(Node)
- end, Nodes).
-
%% If ejabberd is running on some Windows machine, get nameservers and add to Erlang
maybe_add_nameservers() ->
case os:type() of
@@ -136,10 +128,6 @@ delete_pid_file() ->
file:delete(PidFilename)
end.
-set_settings_from_config() ->
- Ticktime = ejabberd_config:get_option(net_ticktime, 60),
- net_kernel:set_net_ticktime(Ticktime).
-
file_queue_init() ->
QueueDir = case ejabberd_config:queue_dir() of
undefined ->
@@ -160,15 +148,6 @@ start_apps() ->
ejabberd:start_app(xmpp),
ejabberd:start_app(cache_tab).
--spec opt_type(net_ticktime) -> fun((pos_integer()) -> pos_integer());
- (cluster_nodes) -> fun(([node()]) -> [node()]);
- (atom()) -> atom().
-opt_type(net_ticktime) ->
- fun (P) when is_integer(P), P > 0 -> P end;
-opt_type(cluster_nodes) ->
- fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
-opt_type(_) -> [cluster_nodes, net_ticktime].
-
setup_if_elixir_conf_used() ->
case ejabberd_config:is_using_elixir_config() of
true -> 'Elixir.Ejabberd.Config.Store':start_link();
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 7cbc16f9d..159cb4054 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -46,7 +46,7 @@
reject_unauthenticated_packet/2, process_closed/2,
process_terminated/2, process_info/2]).
%% API
--export([get_presence/1, resend_presence/1, resend_presence/2,
+-export([get_presence/1, set_presence/2, resend_presence/1, resend_presence/2,
open_session/1, call/3, send/2, close/1, close/2, stop/1,
reply/2, copy_state/2, set_timeout/2, route/2,
host_up/1, host_down/1]).
@@ -97,6 +97,10 @@ reply(Ref, Reply) ->
get_presence(Ref) ->
call(Ref, get_presence, 1000).
+-spec set_presence(pid(), presence()) -> ok.
+set_presence(Ref, Pres) ->
+ call(Ref, {set_presence, Pres}, 1000).
+
-spec resend_presence(pid()) -> ok.
resend_presence(Pid) ->
resend_presence(Pid, undefined).
@@ -525,6 +529,9 @@ handle_call(get_presence, From, #{jid := JID} = State) ->
end,
reply(From, Pres),
State;
+handle_call({set_presence, Pres}, From, State) ->
+ reply(From, ok),
+ process_self_presence(State, Pres);
handle_call(Request, From, #{lserver := LServer} = State) ->
ejabberd_hooks:run_fold(
c2s_handle_call, LServer, State, [Request, From]).
diff --git a/src/ejabberd_cluster.erl b/src/ejabberd_cluster.erl
index aeae294b0..d9429a108 100644
--- a/src/ejabberd_cluster.erl
+++ b/src/ejabberd_cluster.erl
@@ -1,8 +1,6 @@
-%%%----------------------------------------------------------------------
-%%% File : ejabberd_cluster.erl
-%%% Author : Christophe Romain <christophe.romain@process-one.net>
-%%% Purpose : Ejabberd clustering management
-%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% Created : 5 Jul 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
@@ -21,132 +19,188 @@
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
-%%%----------------------------------------------------------------------
-
+%%%-------------------------------------------------------------------
-module(ejabberd_cluster).
+-behaviour(ejabberd_config).
+-behaviour(gen_server).
%% API
--export([get_nodes/0, call/4, multicall/3, multicall/4,
- eval_everywhere/3, eval_everywhere/4]).
--export([join/1, leave/1, get_known_nodes/0]).
--export([node_id/0, get_node_by_id/1]).
+-export([start_link/0, call/4, multicall/3, multicall/4, eval_everywhere/3,
+ eval_everywhere/4]).
+%% Backend dependent API
+-export([get_nodes/0, get_known_nodes/0, join/1, leave/1, subscribe/0,
+ subscribe/1, node_id/0, get_node_by_id/1, send/2, wait_for_sync/1]).
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+-export([opt_type/1]).
--include("ejabberd.hrl").
-include("logger.hrl").
--spec get_nodes() -> [node()].
+-type dst() :: pid() | atom() | {atom(), node()}.
-get_nodes() ->
- mnesia:system_info(running_db_nodes).
+-callback init() -> ok | {error, any()}.
+-callback get_nodes() -> [node()].
+-callback get_known_nodes() -> [node()].
+-callback join(node()) -> ok | {error, any()}.
+-callback leave(node()) -> ok | {error, any()}.
+-callback node_id() -> binary().
+-callback get_node_by_id(binary()) -> node().
+-callback send({atom(), node()}, term()) -> boolean().
+-callback wait_for_sync(timeout()) -> ok | {error, any()}.
+-callback subscribe(dst()) -> ok.
--spec get_known_nodes() -> [node()].
+-record(state, {}).
-get_known_nodes() ->
- lists:usort(mnesia:system_info(db_nodes)
- ++ mnesia:system_info(extra_db_nodes)).
+%%%===================================================================
+%%% API
+%%%===================================================================
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec call(node(), module(), atom(), [any()]) -> any().
-
call(Node, Module, Function, Args) ->
- rpc:call(Node, Module, Function, Args, 5000).
+ rpc:call(Node, Module, Function, Args, rpc_timeout()).
-spec multicall(module(), atom(), [any()]) -> {list(), [node()]}.
-
multicall(Module, Function, Args) ->
multicall(get_nodes(), Module, Function, Args).
-spec multicall([node()], module(), atom(), list()) -> {list(), [node()]}.
-
multicall(Nodes, Module, Function, Args) ->
- rpc:multicall(Nodes, Module, Function, Args, 5000).
+ rpc:multicall(Nodes, Module, Function, Args, rpc_timeout()).
-spec eval_everywhere(module(), atom(), [any()]) -> ok.
-
eval_everywhere(Module, Function, Args) ->
eval_everywhere(get_nodes(), Module, Function, Args),
ok.
-spec eval_everywhere([node()], module(), atom(), [any()]) -> ok.
-
eval_everywhere(Nodes, Module, Function, Args) ->
rpc:eval_everywhere(Nodes, Module, Function, Args),
ok.
--spec join(node()) -> ok | {error, any()}.
+%%%===================================================================
+%%% Backend dependent API
+%%%===================================================================
+-spec get_nodes() -> [node()].
+get_nodes() ->
+ Mod = get_mod(),
+ Mod:get_nodes().
+
+-spec get_known_nodes() -> [node()].
+get_known_nodes() ->
+ Mod = get_mod(),
+ Mod:get_known_nodes().
+-spec join(node()) -> ok | {error, any()}.
join(Node) ->
- case {node(), net_adm:ping(Node)} of
- {Node, _} ->
- {error, {not_master, Node}};
- {_, pong} ->
- application:stop(ejabberd),
- application:stop(mnesia),
- mnesia:delete_schema([node()]),
- application:start(mnesia),
- mnesia:change_config(extra_db_nodes, [Node]),
- mnesia:change_table_copy_type(schema, node(), disc_copies),
- spawn(fun() ->
- lists:foreach(fun(Table) ->
- Type = call(Node, mnesia, table_info, [Table, storage_type]),
- mnesia:add_table_copy(Table, node(), Type)
- end, mnesia:system_info(tables)--[schema])
- end),
- application:start(ejabberd);
- _ ->
- {error, {no_ping, Node}}
- end.
+ Mod = get_mod(),
+ Mod:join(Node).
-spec leave(node()) -> ok | {error, any()}.
-
leave(Node) ->
- case {node(), net_adm:ping(Node)} of
- {Node, _} ->
- Cluster = get_nodes()--[Node],
- leave(Cluster, Node);
- {_, pong} ->
- rpc:call(Node, ?MODULE, leave, [Node], 10000);
- {_, pang} ->
- case mnesia:del_table_copy(schema, Node) of
- {atomic, ok} -> ok;
- {aborted, Reason} -> {error, Reason}
- end
- end.
-leave([], Node) ->
- {error, {no_cluster, Node}};
-leave([Master|_], Node) ->
- application:stop(ejabberd),
- application:stop(mnesia),
- call(Master, mnesia, del_table_copy, [schema, Node]),
- spawn(fun() ->
- mnesia:delete_schema([node()]),
- erlang:halt(0)
- end),
- ok.
+ Mod = get_mod(),
+ Mod:leave(Node).
-spec node_id() -> binary().
node_id() ->
- integer_to_binary(erlang:phash2(node())).
+ Mod = get_mod(),
+ Mod:node_id().
-spec get_node_by_id(binary()) -> node().
-get_node_by_id(Hash) ->
- try binary_to_integer(Hash) of
- I -> match_node_id(I)
- catch _:_ ->
- node()
+get_node_by_id(ID) ->
+ Mod = get_mod(),
+ Mod:get_node_by_id(ID).
+
+-spec send(dst(), term()) -> boolean().
+send(Dst, Msg) ->
+ IsLocal = case Dst of
+ {_, Node} -> Node == node();
+ Pid when is_pid(Pid) -> node(Pid) == node();
+ Name when is_atom(Name) -> true;
+ _ -> false
+ end,
+ if IsLocal ->
+ erlang:send(Dst, Msg),
+ true;
+ true ->
+ Mod = get_mod(),
+ Mod:send(Dst, Msg)
end.
+-spec wait_for_sync(timeout()) -> ok | {error, any()}.
+wait_for_sync(Timeout) ->
+ Mod = get_mod(),
+ Mod:wait_for_sync(Timeout).
+
+-spec subscribe() -> ok.
+subscribe() ->
+ subscribe(self()).
+
+-spec subscribe(dst()) -> ok.
+subscribe(Proc) ->
+ Mod = get_mod(),
+ Mod:subscribe(Proc).
+
+%%%===================================================================
+%%% gen_server API
+%%%===================================================================
+init([]) ->
+ Ticktime = ejabberd_config:get_option(net_ticktime, 60),
+ Nodes = ejabberd_config:get_option(cluster_nodes, []),
+ net_kernel:set_net_ticktime(Ticktime),
+ lists:foreach(fun(Node) ->
+ net_kernel:connect_node(Node)
+ end, Nodes),
+ Mod = get_mod(),
+ case Mod:init() of
+ ok ->
+ Mod:subscribe(?MODULE),
+ {ok, #state{}};
+ {error, Reason} ->
+ {stop, Reason}
+ end.
+
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info({node_up, Node}, State) ->
+ ?INFO_MSG("Node ~s has joined", [Node]),
+ {noreply, State};
+handle_info({node_down, Node}, State) ->
+ ?INFO_MSG("Node ~s has left", [Node]),
+ {noreply, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
--spec match_node_id(integer()) -> node().
-match_node_id(I) ->
- match_node_id(I, get_nodes()).
-
--spec match_node_id(integer(), [node()]) -> node().
-match_node_id(I, [Node|Nodes]) ->
- case erlang:phash2(Node) of
- I -> Node;
- _ -> match_node_id(I, Nodes)
- end;
-match_node_id(_I, []) ->
- node().
+get_mod() ->
+ Backend = ejabberd_config:get_option(cluster_backend, mnesia),
+ list_to_atom("ejabberd_cluster_" ++ atom_to_list(Backend)).
+
+rpc_timeout() ->
+ timer:seconds(ejabberd_config:get_option(rpc_timeout, 5)).
+
+opt_type(net_ticktime) ->
+ fun (P) when is_integer(P), P > 0 -> P end;
+opt_type(cluster_nodes) ->
+ fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
+opt_type(rpc_timeout) ->
+ fun (T) when is_integer(T), T > 0 -> T end;
+opt_type(cluster_backend) ->
+ fun (T) -> ejabberd_config:v_db(?MODULE, T) end;
+opt_type(_) ->
+ [rpc_timeout, cluster_backend, cluster_nodes, net_ticktime].
diff --git a/src/ejabberd_cluster_mnesia.erl b/src/ejabberd_cluster_mnesia.erl
new file mode 100644
index 000000000..67fc60fde
--- /dev/null
+++ b/src/ejabberd_cluster_mnesia.erl
@@ -0,0 +1,144 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_cluster_mnesia.erl
+%%% Author : Christophe Romain <christophe.romain@process-one.net>
+%%% Purpose : Ejabberd clustering management via Mnesia
+%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2002-2017 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.
+%%%
+%%% 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(ejabberd_cluster_mnesia).
+-behaviour(ejabberd_cluster).
+
+%% API
+-export([init/0, get_nodes/0, join/1, leave/1,
+ get_known_nodes/0, node_id/0, get_node_by_id/1,
+ send/2, wait_for_sync/1, subscribe/1]).
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+
+-spec init() -> ok.
+init() ->
+ ok.
+
+-spec get_nodes() -> [node()].
+
+get_nodes() ->
+ mnesia:system_info(running_db_nodes).
+
+-spec get_known_nodes() -> [node()].
+
+get_known_nodes() ->
+ lists:usort(mnesia:system_info(db_nodes)
+ ++ mnesia:system_info(extra_db_nodes)).
+
+-spec join(node()) -> ok | {error, any()}.
+
+join(Node) ->
+ case {node(), net_adm:ping(Node)} of
+ {Node, _} ->
+ {error, {not_master, Node}};
+ {_, pong} ->
+ application:stop(ejabberd),
+ application:stop(mnesia),
+ mnesia:delete_schema([node()]),
+ application:start(mnesia),
+ mnesia:change_config(extra_db_nodes, [Node]),
+ mnesia:change_table_copy_type(schema, node(), disc_copies),
+ spawn(fun() ->
+ lists:foreach(fun(Table) ->
+ Type = ejabberd_cluster:call(
+ Node, mnesia, table_info, [Table, storage_type]),
+ mnesia:add_table_copy(Table, node(), Type)
+ end, mnesia:system_info(tables)--[schema])
+ end),
+ application:start(ejabberd);
+ _ ->
+ {error, {no_ping, Node}}
+ end.
+
+-spec leave(node()) -> ok | {error, any()}.
+
+leave(Node) ->
+ case {node(), net_adm:ping(Node)} of
+ {Node, _} ->
+ Cluster = get_nodes()--[Node],
+ leave(Cluster, Node);
+ {_, pong} ->
+ rpc:call(Node, ?MODULE, leave, [Node], 10000);
+ {_, pang} ->
+ case mnesia:del_table_copy(schema, Node) of
+ {atomic, ok} -> ok;
+ {aborted, Reason} -> {error, Reason}
+ end
+ end.
+leave([], Node) ->
+ {error, {no_cluster, Node}};
+leave([Master|_], Node) ->
+ application:stop(ejabberd),
+ application:stop(mnesia),
+ ejabberd_cluster:call(Master, mnesia, del_table_copy, [schema, Node]),
+ spawn(fun() ->
+ mnesia:delete_schema([node()]),
+ erlang:halt(0)
+ end),
+ ok.
+
+-spec node_id() -> binary().
+node_id() ->
+ integer_to_binary(erlang:phash2(node())).
+
+-spec get_node_by_id(binary()) -> node().
+get_node_by_id(Hash) ->
+ try binary_to_integer(Hash) of
+ I -> match_node_id(I)
+ catch _:_ ->
+ node()
+ end.
+
+-spec send({atom(), node()}, term()) -> boolean().
+send(Dst, Msg) ->
+ erlang:send(Dst, Msg).
+
+-spec wait_for_sync(timeout()) -> ok.
+wait_for_sync(Timeout) ->
+ ?INFO_MSG("Waiting for Mnesia synchronization to complete", []),
+ mnesia:wait_for_tables(mnesia:system_info(local_tables), Timeout),
+ ok.
+
+-spec subscribe(_) -> ok.
+subscribe(_) ->
+ ok.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+-spec match_node_id(integer()) -> node().
+match_node_id(I) ->
+ match_node_id(I, get_nodes()).
+
+-spec match_node_id(integer(), [node()]) -> node().
+match_node_id(I, [Node|Nodes]) ->
+ case erlang:phash2(Node) of
+ I -> Node;
+ _ -> match_node_id(I, Nodes)
+ end;
+match_node_id(_I, []) ->
+ node().
diff --git a/src/ejabberd_commands.erl b/src/ejabberd_commands.erl
index eccb0d621..265907299 100644
--- a/src/ejabberd_commands.erl
+++ b/src/ejabberd_commands.erl
@@ -221,7 +221,6 @@
get_command_format/1,
get_command_format/2,
get_command_format/3,
- get_command_policy_and_scope/1,
get_command_definition/1,
get_command_definition/2,
get_tags_commands/0,
@@ -230,11 +229,6 @@
register_commands/1,
unregister_commands/1,
expose_commands/1,
- execute_command/2,
- execute_command/3,
- execute_command/4,
- execute_command/5,
- execute_command/6,
opt_type/1,
get_commands_spec/0,
get_commands_definition/0,
@@ -361,6 +355,8 @@ expose_commands(Commands) ->
Commands),
case ejabberd_config:add_option(commands, [{add_commands, Names}]) of
+ ok ->
+ ok;
{aborted, Reason} ->
{error, Reason};
{atomic, Result} ->
@@ -427,17 +423,6 @@ get_command_format(Name, Auth, Version) ->
{Args, Result}
end.
--spec get_command_policy_and_scope(atom()) -> {ok, open|user|admin|restricted, [oauth_scope()]} | {error, command_not_found}.
-
-%% @doc return command policy.
-get_command_policy_and_scope(Name) ->
- case get_command_definition(Name) of
- #ejabberd_commands{policy = Policy} = Cmd ->
- {ok, Policy, cmd_scope(Cmd)};
- command_not_found ->
- {error, command_not_found}
- end.
-
%% The oauth scopes for a command are the command name itself,
%% also might include either 'ejabberd:user' or 'ejabberd:admin'
cmd_scope(#ejabberd_commands{policy = Policy, name = Name}) ->
@@ -503,129 +488,6 @@ execute_command2(Name, Arguments, CallerInfo, Version) ->
throw({error, access_rules_unauthorized})
end.
-%% @spec (Name::atom(), Arguments) -> ResultTerm
-%% where
-%% Arguments = [any()]
-%% @doc Execute a command.
-%% Can return the following exceptions:
-%% command_unknown | account_unprivileged | invalid_account_data |
-%% no_auth_provided | access_rules_unauthorized
-execute_command(Name, Arguments) ->
- execute_command(Name, Arguments, ?DEFAULT_VERSION).
-
--spec execute_command(atom(),
- [any()],
- integer() |
- {binary(), binary(), binary(), boolean()} |
- noauth | admin
- ) -> any().
-
-%% @spec (Name::atom(), Arguments, integer() | Auth) -> ResultTerm
-%% where
-%% Auth = {User::string(), Server::string(), Password::string(),
-%% Admin::boolean()}
-%% | noauth
-%% | admin
-%% Arguments = [any()]
-%%
-%% @doc Execute a command in a given API version
-%% Can return the following exceptions:
-%% command_unknown | account_unprivileged | invalid_account_data |
-%% no_auth_provided
-execute_command(Name, Arguments, Version) when is_integer(Version) ->
- execute_command([], noauth, Name, Arguments, Version);
-execute_command(Name, Arguments, Auth) ->
- execute_command([], Auth, Name, Arguments, ?DEFAULT_VERSION).
-
-%% @spec (AccessCommands, Auth, Name::atom(), Arguments) ->
-%% ResultTerm | {error, Error}
-%% where
-%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
-%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
-%% | noauth
-%% | admin
-%% Arguments = [any()]
-%%
-%% @doc Execute a command
-%% Can return the following exceptions:
-%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
-execute_command(AccessCommands, Auth, Name, Arguments) ->
- execute_command(AccessCommands, Auth, Name, Arguments, ?DEFAULT_VERSION).
-
--spec execute_command([{atom(), [atom()], [any()]}] | undefined,
- {binary(), binary(), binary(), boolean()} |
- noauth | admin,
- atom(),
- [any()],
- integer()
- ) -> any().
-
-%% @spec (AccessCommands, Auth, Name::atom(), Arguments, integer()) -> ResultTerm
-%% where
-%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
-%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
-%% | noauth
-%% | admin
-%% Arguments = [any()]
-%%
-%% @doc Execute a command in a given API version
-%% Can return the following exceptions:
-%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided | access_rules_unauthorized
-execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
- execute_command(AccessCommands1, Auth1, Name, Arguments, Version, #{}).
-
-execute_command(AccessCommands1, Auth1, Name, Arguments, Version, CallerInfo) ->
- Auth = case is_admin(Name, Auth1, CallerInfo) of
- true -> admin;
- false -> Auth1
- end,
- TokenJID = oauth_token_user(Auth1),
- Command = get_command_definition(Name, Version),
- AccessCommands = get_all_access_commands(AccessCommands1),
-
- case check_access_commands(AccessCommands, Auth, Name, Command, Arguments, CallerInfo) of
- ok -> execute_check_policy(Auth, TokenJID, Command, Arguments)
- end.
-
-
-execute_check_policy(
- _Auth, _JID, #ejabberd_commands{policy = open} = Command, Arguments) ->
- do_execute_command(Command, Arguments);
-execute_check_policy(
- noauth, _JID, Command, Arguments) ->
- do_execute_command(Command, Arguments);
-execute_check_policy(
- _Auth, _JID, #ejabberd_commands{policy = restricted} = Command, Arguments) ->
- do_execute_command(Command, Arguments);
-execute_check_policy(
- _Auth, JID, #ejabberd_commands{policy = admin} = Command, Arguments) ->
- execute_check_access(JID, Command, Arguments);
-execute_check_policy(
- admin, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
- execute_check_access(JID, Command, Arguments);
-execute_check_policy(
- {User, Server, _, _}, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
- execute_check_access(JID, Command, [User, Server | Arguments]).
-
-execute_check_access(_FromJID, #ejabberd_commands{access = []} = Command, Arguments) ->
- do_execute_command(Command, Arguments);
-execute_check_access(undefined, _Command, _Arguments) ->
- throw({error, access_rules_unauthorized});
-execute_check_access(FromJID, #ejabberd_commands{access = AccessRefs} = Command, Arguments) ->
- %% TODO Review: Do we have smarter / better way to check rule on other Host than global ?
- Host = global,
- Rules = lists:map(
- fun({Mod, AccessName, Default}) ->
- gen_mod:get_module_opt(Host, Mod, AccessName, Default);
- (Default) ->
- Default
- end, AccessRefs),
- case acl:any_rules_allowed(Host, Rules, FromJID) of
- true ->
- do_execute_command(Command, Arguments);
- false ->
- throw({error, access_rules_unauthorized})
- end.
do_execute_command(Command, Arguments) ->
Module = Command#ejabberd_commands.module,
@@ -672,58 +534,6 @@ get_tags_commands(Version) ->
%% Access verification
%% -----------------------------
-%% @spec (AccessCommands, Auth, Method, Command, Arguments) -> ok
-%% where
-%% AccessCommands = [ {Access, CommandNames, Arguments} ]
-%% Auth = {User::string(), Server::string(), Password::string()} | noauth
-%% Method = atom()
-%% Arguments = [any()]
-%% @doc Check access is allowed to that command.
-%% At least one AccessCommand must be satisfied.
-%% It may throw {error, Error} where:
-%% Error = account_unprivileged | invalid_account_data
-check_access_commands([], _Auth, _Method, _Command, _Arguments, _CallerInfo) ->
- ok;
-check_access_commands(AccessCommands, Auth, Method, Command1, Arguments, CallerInfo) ->
- Command =
- case {Command1#ejabberd_commands.policy, Auth} of
- {user, {_, _, _, _}} ->
- Command1;
- {user, _} ->
- Command1#ejabberd_commands{
- args = [{user, binary}, {server, binary} |
- Command1#ejabberd_commands.args]};
- _ ->
- Command1
- end,
- AccessCommandsAllowed =
- lists:filter(
- fun({Access, Commands, ArgumentRestrictions}) ->
- case check_access(Command, Access, Auth, CallerInfo) of
- true ->
- check_access_command(Commands, Command,
- ArgumentRestrictions,
- Method, Arguments);
- false ->
- false
- end;
- ({Access, Commands}) ->
- ArgumentRestrictions = [],
- case check_access(Command, Access, Auth, CallerInfo) of
- true ->
- check_access_command(Commands, Command,
- ArgumentRestrictions,
- Method, Arguments);
- false ->
- false
- end
- end,
- AccessCommands),
- case AccessCommandsAllowed of
- [] -> throw({error, account_unprivileged});
- L when is_list(L) -> ok
- end.
-
-spec check_auth(ejabberd_commands(), noauth) -> noauth_provided;
(ejabberd_commands(),
{binary(), binary(), binary(), boolean()}) ->
@@ -746,80 +556,6 @@ check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
_ -> throw({error, invalid_account_data})
end.
-check_access(Command, ?POLICY_ACCESS, _, _)
- when Command#ejabberd_commands.policy == open ->
- true;
-check_access(_Command, _Access, admin, _) ->
- true;
-check_access(_Command, _Access, {_User, _Server, _, true}, _) ->
- false;
-check_access(Command, Access, Auth, CallerInfo)
- when Access =/= ?POLICY_ACCESS;
- Command#ejabberd_commands.policy == open;
- Command#ejabberd_commands.policy == user ->
- case check_auth(Command, Auth) of
- {ok, User, Server} ->
- check_access2(Access, CallerInfo#{usr => jid:split(jid:make(User, Server))}, Server);
- no_auth_provided ->
- case Command#ejabberd_commands.policy of
- user ->
- false;
- _ ->
- check_access2(Access, CallerInfo, global)
- end;
- _ ->
- false
- end;
-check_access(_Command, _Access, _Auth, _CallerInfo) ->
- false.
-
-check_access2(?POLICY_ACCESS, _CallerInfo, _Server) ->
- true;
-check_access2(Access, AccessInfo, Server) ->
- %% Check this user has access permission
- case acl:access_matches(Access, AccessInfo, Server) of
- allow -> true;
- deny -> false
- end.
-
-check_access_command(Commands, Command, ArgumentRestrictions,
- Method, Arguments) ->
- case Commands==all orelse lists:member(Method, Commands) of
- true -> check_access_arguments(Command, ArgumentRestrictions,
- Arguments);
- false -> false
- end.
-
-check_access_arguments(Command, ArgumentRestrictions, Arguments) ->
- ArgumentsTagged = tag_arguments(Command#ejabberd_commands.args, Arguments),
- lists:all(
- fun({ArgName, ArgAllowedValue}) ->
- %% If the call uses the argument, check the value is acceptable
- case lists:keysearch(ArgName, 1, ArgumentsTagged) of
- {value, {ArgName, ArgValue}} -> ArgValue == ArgAllowedValue;
- false -> true
- end
- end, ArgumentRestrictions).
-
-tag_arguments(ArgsDefs, Args) ->
- lists:zipwith(
- fun({ArgName, _ArgType}, ArgValue) ->
- {ArgName, ArgValue}
- end,
- ArgsDefs,
- Args).
-
-
-%% Get commands for all version
-get_all_access_commands(AccessCommands) ->
- get_access_commands(AccessCommands, ?DEFAULT_VERSION).
-
-get_access_commands(undefined, Version) ->
- Cmds = get_exposed_commands(Version),
- [{?POLICY_ACCESS, Cmds, []}];
-get_access_commands(AccessCommands, _Version) ->
- AccessCommands.
-
get_exposed_commands() ->
get_exposed_commands(?DEFAULT_VERSION).
get_exposed_commands(Version) ->
@@ -854,13 +590,6 @@ expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds) when is_list(L
[Command|Acc]
end, [], L).
-oauth_token_user(noauth) ->
- undefined;
-oauth_token_user(admin) ->
- undefined;
-oauth_token_user({User, Server, _, _}) ->
- jid:make(User, Server).
-
is_admin(_Name, admin, _Extra) ->
true;
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
diff --git a/src/ejabberd_commands_doc.erl b/src/ejabberd_commands_doc.erl
index c3b7b4724..f90b70bce 100644
--- a/src/ejabberd_commands_doc.erl
+++ b/src/ejabberd_commands_doc.erl
@@ -87,9 +87,6 @@ md_tag(strong, V) ->
md_tag(_, V) ->
V.
-unbinarize(binary) -> string;
-unbinarize(Other) -> Other.
-
perl_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
[?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
perl_gen({Name, string}, Str, _Indent, HTMLOutput) ->
@@ -252,7 +249,7 @@ json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput) ->
{200, json_gen(ResultDesc, Result, Indent, HTMLOutput)};
{{Name0, _}, _} ->
{200, [Indent, ?OP_L("{"), ?STR_A(Name0), ?OP_L(": "),
- json_gen(ResultDesc, Result, Indent, HTMLOutput), Indent, ?OP_L("}")]}
+ json_gen(ResultDesc, Result, Indent, HTMLOutput), ?OP_L("}")]}
end,
CodeStr = case Code of
200 -> <<" 200 OK">>;
@@ -340,48 +337,62 @@ gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc,
end
end.
+format_type({list, {_, {tuple, Els}}}) ->
+ io_lib:format("[~s]", [format_type({tuple, Els})]);
+format_type({list, El}) ->
+ io_lib:format("[~s]", [format_type(El)]);
+format_type({tuple, Els}) ->
+ Args = [format_type(El) || El <- Els],
+ io_lib:format("{~s}", [string:join(Args, ", ")]);
+format_type({Name, Type}) ->
+ io_lib:format("~s::~s", [Name, format_type(Type)]);
+format_type(binary) ->
+ "string";
+format_type(atom) ->
+ "string";
+format_type(Type) ->
+ io_lib:format("~p", [Type]).
+
gen_param(Name, Type, undefined, HTMLOutput) ->
- [?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>,
- ?RAW(io_lib:format("~p", [unbinarize(Type)]))])];
+ [?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))])];
gen_param(Name, Type, Desc, HTMLOutput) ->
- [?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>,
- ?RAW(io_lib:format("~p", [unbinarize(Type)]))]),
+ [?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))]),
?TAG(dd, ?RAW(Desc))].
gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
args=Args, args_desc=ArgsDesc,
result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
- LDesc = case LongDesc of
- "" -> Desc;
- _ -> LongDesc
- end,
- ArgsText = case ArgsDesc of
- none ->
- [?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput)
- || {AName, Type} <- Args])];
- _ ->
- [?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput)
- || {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])]
- end,
- ResultText = case Result of
- {res,rescode} ->
- [?TAG(dl, [gen_param(res, integer,
- "Status code (0 on success, 1 otherwise)",
- HTMLOutput)])];
- {res,restuple} ->
- [?TAG(dl, [gen_param(res, string,
- "Raw result string",
- HTMLOutput)])];
- {RName, Type} ->
- case ResultDesc of
- none ->
- [?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
- _ ->
- [?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
- end
- end,
-
try
+ LDesc = case LongDesc of
+ "" -> Desc;
+ _ -> LongDesc
+ end,
+ ArgsText = case ArgsDesc of
+ none ->
+ [?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput)
+ || {AName, Type} <- Args])];
+ _ ->
+ [?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput)
+ || {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])]
+ end,
+ ResultText = case Result of
+ {res,rescode} ->
+ [?TAG(dl, [gen_param(res, integer,
+ "Status code (0 on success, 1 otherwise)",
+ HTMLOutput)])];
+ {res,restuple} ->
+ [?TAG(dl, [gen_param(res, string,
+ "Raw result string",
+ HTMLOutput)])];
+ {RName, Type} ->
+ case ResultDesc of
+ none ->
+ [?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
+ _ ->
+ [?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
+ end
+ end,
+
[?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
?TAG(p, ?RAW(LDesc)),
?TAG(h2, <<"Arguments:">>), ArgsText,
diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl
index f4a73cc39..2c44d6552 100644
--- a/src/ejabberd_http_ws.erl
+++ b/src/ejabberd_http_ws.erl
@@ -241,6 +241,7 @@ handle_info(PingPong, StateName, StateData) when PingPong == ping orelse
StateData2#state{pong_expected = false}};
handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) ->
+ ?DEBUG("Closing websocket connection from hitting inactivity timeout", []),
{stop, normal, StateData};
handle_info({timeout, Timer, _}, StateName,
#state{ping_timer = Timer, ws = {_, WsPid}} = StateData) ->
@@ -253,6 +254,7 @@ handle_info({timeout, Timer, _}, StateName,
{next_state, StateName,
StateData#state{ping_timer = PingTimer, pong_expected = true}};
true ->
+ ?DEBUG("Closing websocket connection from missing pongs", []),
{stop, normal, StateData}
end;
handle_info(_, StateName, StateData) ->
diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl
index 6dd8e706d..e9b4306e5 100644
--- a/src/ejabberd_listener.erl
+++ b/src/ejabberd_listener.erl
@@ -109,6 +109,7 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
{ok, Socket} ->
%% Inform my parent that this port was opened succesfully
proc_lib:init_ack({ok, self()}),
+ application:ensure_started(ejabberd),
start_module_sup(Port, Module),
?INFO_MSG("Start accepting UDP connections at ~s for ~p",
[format_portip(PortIP), Module]),
@@ -134,6 +135,7 @@ init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
%% Inform my parent that this port was opened succesfully
proc_lib:init_ack({ok, self()}),
+ application:ensure_started(ejabberd),
start_module_sup(Port, Module),
?INFO_MSG("Start accepting TCP connections at ~s for ~p",
[format_portip(PortIP), Module]),
@@ -302,7 +304,7 @@ accept(ListenSocket, Module, Opts, Interval) ->
ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
PPort, inet_parse:ntoa(Addr), Port]);
_ ->
- ok
+ gen_tcp:close(Socket)
end,
accept(ListenSocket, Module, Opts, NewInterval);
{error, Reason} ->
diff --git a/src/ejabberd_logger.erl b/src/ejabberd_logger.erl
index 1e48732c8..eee9d3b83 100644
--- a/src/ejabberd_logger.erl
+++ b/src/ejabberd_logger.erl
@@ -151,6 +151,9 @@ do_start() ->
application:set_env(lager, crash_log_size, LogRotateSize),
application:set_env(lager, crash_log_count, LogRotateCount),
ejabberd:start_app(lager),
+ lists:foreach(fun(Handler) ->
+ lager:set_loghwm(Handler, LogRateLimit)
+ end, gen_event:which_handlers(lager_event)),
ok.
%% @spec () -> ok
diff --git a/src/ejabberd_mnesia.erl b/src/ejabberd_mnesia.erl
index 34691545a..16e385011 100644
--- a/src/ejabberd_mnesia.erl
+++ b/src/ejabberd_mnesia.erl
@@ -68,8 +68,6 @@ init([]) ->
_ -> ok
end,
ejabberd:start_app(mnesia, permanent),
- ?DEBUG("Waiting for Mnesia tables synchronization...", []),
- mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity),
Schema = read_schema_file(),
{ok, #state{schema = Schema}};
false ->
diff --git a/src/ejabberd_oauth.erl b/src/ejabberd_oauth.erl
index 026b30680..3e3fc3082 100644
--- a/src/ejabberd_oauth.erl
+++ b/src/ejabberd_oauth.erl
@@ -50,7 +50,7 @@
config_reloaded/0,
opt_type/1]).
--export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
+-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1]).
-include("xmpp.hrl").
@@ -96,14 +96,6 @@ get_commands_spec() ->
policy = restricted,
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}}
},
- #ejabberd_commands{name = oauth_list_scopes, tags = [oauth],
- desc = "List scopes that can be granted, and commands",
- longdesc = "List scopes that can be granted to tokens generated through the command line, together with the commands they allow",
- module = ?MODULE, function = oauth_list_scopes,
- args = [],
- policy = restricted,
- result = {scopes, {list, {scope, {tuple, [{scope, string}, {commands, string}]}}}}
- },
#ejabberd_commands{name = oauth_revoke_token, tags = [oauth],
desc = "Revoke authorization for a token (only Mnesia)",
module = ?MODULE, function = oauth_revoke_token,
@@ -143,9 +135,6 @@ oauth_revoke_token(Token) ->
ok = mnesia:dirty_delete(oauth_token, list_to_binary(Token)),
oauth_list_tokens().
-oauth_list_scopes() ->
- [ {Scope, string:join([atom_to_list(Cmd) || Cmd <- Cmds], ",")} || {Scope, Cmds} <- dict:to_list(get_cmd_scopes())].
-
config_reloaded() ->
DBMod = get_db_backend(),
case init_cache(DBMod) of
@@ -240,17 +229,6 @@ verify_resowner_scope(_, _, _) ->
{error, badscope}.
-get_cmd_scopes() ->
- ScopeMap = lists:foldl(fun(Cmd, Accum) ->
- case ejabberd_commands:get_command_policy_and_scope(Cmd) of
- {ok, Policy, Scopes} when Policy =/= restricted ->
- lists:foldl(fun(Scope, Accum2) ->
- dict:append(Scope, Cmd, Accum2)
- end, Accum, Scopes);
- _ -> Accum
- end end, dict:new(), ejabberd_commands:get_exposed_commands()),
- ScopeMap.
-
%% This is callback for oauth tokens generated through the command line. Only open and admin commands are
%% made available.
%verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) ->
@@ -755,7 +733,7 @@ css() ->
text-decoration: underline;
}
- .container > .section {
+ .container > .section {
background: #424A55;
}
diff --git a/src/ejabberd_router_mnesia.erl b/src/ejabberd_router_mnesia.erl
index 76336d0b0..d84f7a609 100644
--- a/src/ejabberd_router_mnesia.erl
+++ b/src/ejabberd_router_mnesia.erl
@@ -149,7 +149,7 @@ init([]) ->
lists:foreach(
fun (Pid) -> erlang:monitor(process, Pid) end,
mnesia:dirty_select(route,
- [{{route, '_', '$1', '_'}, [], ['$1']}])),
+ [{#route{pid = '$1', _ = '_'}, [], ['$1']}])),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl
index 4b74b8c4a..a0e9411cf 100644
--- a/src/ejabberd_s2s.erl
+++ b/src/ejabberd_s2s.erl
@@ -546,25 +546,23 @@ parent_domains(Domain) ->
get_commands_spec() ->
[#ejabberd_commands{
- name = incoming_s2s_number,
- tags = [stats, s2s],
+ name = incoming_s2s_number, tags = [stats, s2s],
desc = "Number of incoming s2s connections on the node",
- policy = admin,
- module = ?MODULE, function = incoming_s2s_number,
- args = [], result = {s2s_incoming, integer}},
+ policy = admin,
+ module = ?MODULE, function = incoming_s2s_number,
+ args = [], result = {s2s_incoming, integer}},
#ejabberd_commands{
- name = outgoing_s2s_number,
- tags = [stats, s2s],
+ name = outgoing_s2s_number, tags = [stats, s2s],
desc = "Number of outgoing s2s connections on the node",
- policy = admin,
- module = ?MODULE, function = outgoing_s2s_number,
- args = [], result = {s2s_outgoing, integer}},
- #ejabberd_commands{name = stop_all_connections,
- tags = [s2s],
- desc = "Stop all outgoing and incoming connections",
- policy = admin,
- module = ?MODULE, function = stop_all_connections,
- args = [], result = {res, rescode}}].
+ policy = admin,
+ module = ?MODULE, function = outgoing_s2s_number,
+ args = [], result = {s2s_outgoing, integer}},
+ #ejabberd_commands{
+ name = stop_all_connections, tags = [s2s],
+ desc = "Stop all outgoing and incoming connections",
+ policy = admin,
+ module = ?MODULE, function = stop_all_connections,
+ args = [], result = {res, rescode}}].
%% TODO Move those stats commands to ejabberd stats command ?
incoming_s2s_number() ->
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 344febb5d..333edffa6 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -957,30 +957,36 @@ cache_nodes(Mod, LServer) ->
%%% ejabberd commands
get_commands_spec() ->
- [#ejabberd_commands{name = connected_users,
- tags = [session],
+ [#ejabberd_commands{name = connected_users, tags = [session],
desc = "List all established sessions",
policy = admin,
module = ?MODULE, function = connected_users, args = [],
+ result_desc = "List of users sessions",
+ result_example = [<<"user1@example.com">>, <<"user2@example.com">>],
result = {connected_users, {list, {sessions, string}}}},
- #ejabberd_commands{name = connected_users_number,
- tags = [session, stats],
+ #ejabberd_commands{name = connected_users_number, tags = [session, stats],
desc = "Get the number of established sessions",
policy = admin,
module = ?MODULE, function = connected_users_number,
+ result_example = 2,
args = [], result = {num_sessions, integer}},
- #ejabberd_commands{name = user_resources,
- tags = [session],
+ #ejabberd_commands{name = user_resources, tags = [session],
desc = "List user's connected resources",
policy = user,
module = ?MODULE, function = user_resources,
- args = [],
+ args = [{user, binary}, {host, binary}],
+ args_desc = ["User name", "Server name"],
+ args_example = [<<"user1">>, <<"example.com">>],
+ result_example = [<<"tka1">>, <<"Gajim">>, <<"mobile-app">>],
result = {resources, {list, {resource, string}}}},
- #ejabberd_commands{name = kick_user,
- tags = [session],
+ #ejabberd_commands{name = kick_user, tags = [session],
desc = "Disconnect user's active sessions",
module = ?MODULE, function = kick_user,
args = [{user, binary}, {host, binary}],
+ args_desc = ["User name", "Server name"],
+ args_example = [<<"user1">>, <<"example.com">>],
+ result_desc = "Number of resources that were kicked",
+ result_example = 3,
result = {num_resources, integer}}].
-spec connected_users() -> [binary()].
diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl
index 224ed16c1..35527ebd7 100644
--- a/src/ejabberd_sup.erl
+++ b/src/ejabberd_sup.erl
@@ -41,6 +41,12 @@ init([]) ->
brutal_kill,
worker,
[ejabberd_hooks]},
+ Cluster = {ejabberd_cluster,
+ {ejabberd_cluster, start_link, []},
+ permanent,
+ 5000,
+ worker,
+ [ejabberd_cluster]},
SystemMonitor =
{ejabberd_system_monitor,
{ejabberd_system_monitor, start_link, []},
@@ -152,6 +158,7 @@ init([]) ->
permanent, 5000, worker, [ejabberd_pkix]},
{ok, {{one_for_one, 10, 1},
[Hooks,
+ Cluster,
CyrSASL,
Translation,
AccessPerms,
diff --git a/src/ext_mod.erl b/src/ext_mod.erl
index 86bd21983..ecdfa04c3 100644
--- a/src/ext_mod.erl
+++ b/src/ext_mod.erl
@@ -80,16 +80,18 @@ code_change(_OldVsn, State, _Extra) ->
get_commands_spec() ->
[#ejabberd_commands{name = modules_update_specs,
tags = [admin,modules],
- desc = "",
- longdesc = "",
+ desc = "Update the module source code from Git",
+ longdesc = "A connection to Internet is required",
module = ?MODULE, function = update,
args = [],
result = {res, rescode}},
#ejabberd_commands{name = modules_available,
tags = [admin,modules],
- desc = "",
- longdesc = "",
+ desc = "List the contributed modules available to install",
module = ?MODULE, function = available_command,
+ result_desc = "List of tuples with module name and description",
+ result_example = [{mod_cron, "Execute scheduled commands"},
+ {mod_rest, "ReST frontend"}],
args = [],
result = {modules, {list,
{module, {tuple,
@@ -97,9 +99,11 @@ get_commands_spec() ->
{summary, string}]}}}}},
#ejabberd_commands{name = modules_installed,
tags = [admin,modules],
- desc = "",
- longdesc = "",
+ desc = "List the contributed modules already installed",
module = ?MODULE, function = installed_command,
+ result_desc = "List of tuples with module name and description",
+ result_example = [{mod_cron, "Execute scheduled commands"},
+ {mod_rest, "ReST frontend"}],
args = [],
result = {modules, {list,
{module, {tuple,
@@ -107,46 +111,64 @@ get_commands_spec() ->
{summary, string}]}}}}},
#ejabberd_commands{name = module_install,
tags = [admin,modules],
- desc = "",
- longdesc = "",
+ desc = "Compile, install and start an available contributed module",
module = ?MODULE, function = install,
+ args_desc = ["Module name"],
+ args_example = [<<"mod_rest">>],
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_uninstall,
tags = [admin,modules],
- desc = "",
- longdesc = "",
+ desc = "Uninstall a contributed module",
module = ?MODULE, function = uninstall,
+ args_desc = ["Module name"],
+ args_example = [<<"mod_rest">>],
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_upgrade,
tags = [admin,modules],
- desc = "",
- longdesc = "",
+ desc = "Upgrade the running code of an installed module",
+ longdesc = "In practice, this uninstalls and installs the module",
module = ?MODULE, function = upgrade,
+ args_desc = ["Module name"],
+ args_example = [<<"mod_rest">>],
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_check,
tags = [admin,modules],
- desc = "",
- longdesc = "",
+ desc = "Check the contributed module repository compliance",
module = ?MODULE, function = check,
+ args_desc = ["Module name"],
+ args_example = [<<"mod_rest">>],
args = [{module, binary}],
result = {res, rescode}}
].
%% -- public modules functions
update() ->
- add_sources(?REPOS),
+ Contrib = maps:put(?REPOS, [], maps:new()),
+ Jungles = lists:foldl(fun({Package, Spec}, Acc) ->
+ Repo = proplists:get_value(url, Spec, ""),
+ Mods = maps:get(Repo, Acc, []),
+ maps:put(Repo, [Package|Mods], Acc)
+ end, Contrib, modules_spec(sources_dir(), "*/*")),
+ Repos = maps:fold(fun(Repo, _Mods, Acc) ->
+ Update = add_sources(Repo),
+ ?INFO_MSG("Update packages from repo ~s: ~p", [Repo, Update]),
+ case Update of
+ ok -> Acc;
+ Error -> [{repository, Repo, Error}|Acc]
+ end
+ end, [], Jungles),
Res = lists:foldl(fun({Package, Spec}, Acc) ->
- Path = proplists:get_value(url, Spec, ""),
- Update = add_sources(Package, Path),
+ Repo = proplists:get_value(url, Spec, ""),
+ Update = add_sources(Package, Repo),
?INFO_MSG("Update package ~s: ~p", [Package, Update]),
case Update of
ok -> Acc;
- Error -> [Error|Acc]
+ Error -> [{Package, Repo, Error}|Acc]
end
- end, [], modules_spec(sources_dir(), "*")),
+ end, Repos, modules_spec(sources_dir(), "*")),
case Res of
[] -> ok;
[Error|_] -> Error
@@ -538,7 +560,9 @@ compile_result(Results) ->
compile_options() ->
[verbose, report_errors, report_warnings]
++ [{i, filename:join(app_dir(App), "include")}
- || App <- [fast_xml, xmpp, p1_utils, ejabberd]].
+ || App <- [fast_xml, xmpp, p1_utils, ejabberd]]
+ ++ [{i, filename:join(mod_dir(Mod), "include")}
+ || Mod <- installed()].
app_dir(App) ->
case code:lib_dir(App) of
@@ -553,6 +577,10 @@ app_dir(App) ->
Dir
end.
+mod_dir({Package, Spec}) ->
+ Default = filename:join(modules_dir(), Package),
+ proplists:get_value(path, Spec, Default).
+
compile_erlang_file(Dest, File) ->
compile_erlang_file(Dest, File, compile_options()).
diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl
index e5d2892ff..013c342d8 100644
--- a/src/mod_admin_extra.erl
+++ b/src/mod_admin_extra.erl
@@ -303,6 +303,9 @@ get_commands_spec() ->
desc = "List of users logged in host with their statuses",
module = ?MODULE, function = status_list,
args = [{host, binary}, {status, binary}],
+ args_example = [<<"myserver.com">>, <<"dnd">>],
+ args_desc = ["Server name", "Status type to check"],
+ result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}],
result = {users, {list,
{userstatus, {tuple, [
{user, string},
@@ -316,6 +319,9 @@ get_commands_spec() ->
desc = "List of logged users with this status",
module = ?MODULE, function = status_list,
args = [{status, binary}],
+ args_example = [<<"dnd">>],
+ args_desc = ["Status type to check"],
+ result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}],
result = {users, {list,
{userstatus, {tuple, [
{user, string},
@@ -330,6 +336,8 @@ get_commands_spec() ->
desc = "List all established sessions and their information",
module = ?MODULE, function = connected_users_info,
args = [],
+ result_example = [{"user1@myserver.com/tka", "c2s", "127.0.0.1",
+ 40092, 8, "ejabberd@localhost", 28}],
result = {connected_users_info,
{list,
{sessions, {tuple,
@@ -346,6 +354,9 @@ get_commands_spec() ->
tags = [session],
desc = "Get the list of established sessions in a vhost",
module = ?MODULE, function = connected_users_vhost,
+ args_example = [<<"myexample.com">>],
+ args_desc = ["Server name"],
+ result_example = [<<"user1@myserver.com/tka">>, <<"user2@localhost/tka">>],
args = [{host, binary}],
result = {connected_users_vhost, {list, {sessions, string}}}},
#ejabberd_commands{name = user_sessions_info,
@@ -353,6 +364,10 @@ get_commands_spec() ->
desc = "Get information about all sessions of a user",
module = ?MODULE, function = user_sessions_info,
args = [{user, binary}, {host, binary}],
+ args_example = [<<"peter">>, <<"myserver.com">>],
+ args_desc = ["User name", "Server name"],
+ result_example = [{"c2s", "127.0.0.1", 42656,8, "ejabberd@localhost",
+ 231, <<"dnd">>, <<"tka">>, <<>>}],
result = {sessions_info,
{list,
{session, {tuple,
@@ -385,6 +400,9 @@ get_commands_spec() ->
"defined by the user client.",
module = ?MODULE, function = get_presence,
args = [{user, binary}, {server, binary}],
+ args_example = [<<"peter">>, <<"myexample.com">>],
+ args_desc = ["User name", "Server name"],
+ result_example = {<<"user1@myserver.com/tka">>, <<"dnd">>, <<"Busy">>},
result =
{presence,
{tuple,
@@ -398,24 +416,40 @@ get_commands_spec() ->
{resource, binary}, {type, binary},
{show, binary}, {status, binary},
{priority, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>,<<"tka1">>,
+ <<"available">>,<<"away">>,<<"BB">>, <<"7">>],
+ args_desc = ["User name", "Server name", "Resource",
+ "Type: available, error, probe...",
+ "Show: away, chat, dnd, xa.", "Status text",
+ "Priority, provide this value as an integer"],
result = {res, rescode}},
#ejabberd_commands{name = set_nickname, tags = [vcard],
desc = "Set nickname in a user's vCard",
module = ?MODULE, function = set_nickname,
args = [{user, binary}, {host, binary}, {nickname, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>,<<"User 1">>],
+ args_desc = ["User name", "Server name", "Nickname"],
result = {res, rescode}},
#ejabberd_commands{name = get_vcard, tags = [vcard],
desc = "Get content from a vCard field",
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
module = ?MODULE, function = get_vcard,
args = [{user, binary}, {host, binary}, {name, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>,<<"NICKNAME">>],
+ args_desc = ["User name", "Server name", "Field name"],
+ result_example = "User 1",
+ result_desc = "Field content",
result = {content, string}},
#ejabberd_commands{name = get_vcard2, tags = [vcard],
- desc = "Get content from a vCard field",
+ desc = "Get content from a vCard subfield",
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
module = ?MODULE, function = get_vcard,
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>,<<"N">>, <<"FAMILY">>],
+ args_desc = ["User name", "Server name", "Field name", "Subfield name"],
+ result_example = "Schubert",
+ result_desc = "Field content",
result = {content, string}},
#ejabberd_commands{name = get_vcard2_multi, tags = [vcard],
desc = "Get multiple contents from a vCard field",
@@ -429,12 +463,16 @@ get_commands_spec() ->
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
module = ?MODULE, function = set_vcard,
args = [{user, binary}, {host, binary}, {name, binary}, {content, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>, <<"URL">>, <<"www.example.com">>],
+ args_desc = ["User name", "Server name", "Field name", "Value"],
result = {res, rescode}},
#ejabberd_commands{name = set_vcard2, tags = [vcard],
desc = "Set content in a vCard subfield",
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
module = ?MODULE, function = set_vcard,
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {content, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>,<<"TEL">>, <<"NUMBER">>, <<"123456">>],
+ args_desc = ["User name", "Server name", "Field name", "Subfield name", "Value"],
result = {res, rescode}},
#ejabberd_commands{name = set_vcard2_multi, tags = [vcard],
desc = "Set multiple contents in a vCard subfield",
@@ -451,6 +489,10 @@ get_commands_spec() ->
{user, binary}, {server, binary},
{nick, binary}, {group, binary},
{subs, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>,
+ <<"User 2">>, <<"Friends">>, <<"both">>],
+ args_desc = ["User name", "Server name", "Contact user name", "Contact server name",
+ "Nickname", "Group", "Subscription"],
result = {res, rescode}},
%%{"", "subs= none, from, to or both"},
%%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
@@ -460,6 +502,8 @@ get_commands_spec() ->
module = ?MODULE, function = delete_rosteritem,
args = [{localuser, binary}, {localserver, binary},
{user, binary}, {server, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>],
+ args_desc = ["User name", "Server name", "Contact user name", "Contact server name"],
result = {res, rescode}},
#ejabberd_commands{name = process_rosteritems, tags = [roster],
desc = "List/delete rosteritems that match filter (only Mnesia)",
@@ -514,8 +558,14 @@ get_commands_spec() ->
]}}}}},
#ejabberd_commands{name = push_roster, tags = [roster],
desc = "Push template roster from file to a user",
+ longdesc = "The text file must contain an erlang term: a list "
+ "of tuples with username, servername, group and nick. Example:\n"
+ "[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
+ " {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
module = ?MODULE, function = push_roster,
args = [{file, binary}, {user, binary}, {host, binary}],
+ args_example = [<<"/home/ejabberd/roster.txt">>, <<"user1">>, <<"localhost">>],
+ args_desc = ["File path", "User name", "Server name"],
result = {res, rescode}},
#ejabberd_commands{name = push_roster_all, tags = [roster],
desc = "Push template roster from file to all those users",
@@ -525,11 +575,15 @@ get_commands_spec() ->
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
module = ?MODULE, function = push_roster_all,
args = [{file, binary}],
+ args_example = [<<"/home/ejabberd/roster.txt">>],
+ args_desc = ["File path"],
result = {res, rescode}},
#ejabberd_commands{name = push_alltoall, tags = [roster],
desc = "Add all the users to all the users of Host in Group",
module = ?MODULE, function = push_alltoall,
args = [{host, binary}, {group, binary}],
+ args_example = [<<"myserver.com">>,<<"Everybody">>],
+ args_desc = ["Server name", "Group name"],
result = {res, rescode}},
#ejabberd_commands{name = get_last, tags = [last],
@@ -538,6 +592,10 @@ get_commands_spec() ->
"2017-02-23T22:25:28.063062Z ONLINE",
module = ?MODULE, function = get_last,
args = [{user, binary}, {host, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>],
+ args_desc = ["User name", "Server name"],
+ result_example = {<<"2017-06-30T14:32:16.060684Z">>, "ONLINE"},
+ result_desc = "Last activity timestamp and status",
result = {last_activity,
{tuple, [{timestamp, string},
{status, string}
@@ -548,17 +606,24 @@ get_commands_spec() ->
"1970-01-01 00:00:00 UTC, for example: date +%s",
module = mod_last, function = store_last_info,
args = [{user, binary}, {host, binary}, {timestamp, integer}, {status, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>, 1500045311, <<"GoSleeping">>],
+ args_desc = ["User name", "Server name", "Number of seconds since epoch", "Status message"],
result = {res, rescode}},
#ejabberd_commands{name = private_get, tags = [private],
desc = "Get some information from a user private storage",
module = ?MODULE, function = private_get,
args = [{user, binary}, {host, binary}, {element, binary}, {ns, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>,<<"storage">>, <<"storage:rosternotes">>],
+ args_desc = ["User name", "Server name", "Element name", "Namespace"],
result = {res, string}},
#ejabberd_commands{name = private_set, tags = [private],
desc = "Set to the user private storage",
module = ?MODULE, function = private_set,
args = [{user, binary}, {host, binary}, {element, binary}],
+ args_example = [<<"user1">>,<<"myserver.com">>,
+ <<"<query xmlns='jabber:iq:private'> <storage xmlns='storage:rosternotes'/></query>">>],
+ args_desc = ["User name", "Server name", "XML storage element"],
result = {res, rescode}},
#ejabberd_commands{name = srg_create, tags = [shared_roster_group],
@@ -568,41 +633,63 @@ get_commands_spec() ->
"put \\ \" around the argument and\nseparate the "
"identifiers with \\ \\ n\n"
"For example:\n"
- " ejabberdctl srg_create group3 localhost "
+ " ejabberdctl srg_create group3 myserver.com "
"name desc \\\"group1\\\\ngroup2\\\"",
module = ?MODULE, function = srg_create,
args = [{group, binary}, {host, binary},
{name, binary}, {description, binary}, {display, binary}],
+ args_example = [<<"group3">>, <<"myserver.com">>, <<"Group3">>,
+ <<"Third group">>, <<"group1\\\\ngroup2">>],
+ args_desc = ["Group identifier", "Group server name", "Group name",
+ "Group description", "Groups to display"],
result = {res, rescode}},
#ejabberd_commands{name = srg_delete, tags = [shared_roster_group],
desc = "Delete a Shared Roster Group",
module = ?MODULE, function = srg_delete,
args = [{group, binary}, {host, binary}],
+ args_example = [<<"group3">>, <<"myserver.com">>],
+ args_desc = ["Group identifier", "Group server name"],
result = {res, rescode}},
#ejabberd_commands{name = srg_list, tags = [shared_roster_group],
desc = "List the Shared Roster Groups in Host",
module = ?MODULE, function = srg_list,
args = [{host, binary}],
+ args_example = [<<"myserver.com">>],
+ args_desc = ["Server name"],
+ result_example = [<<"group1">>, <<"group2">>],
+ result_desc = "List of group identifiers",
result = {groups, {list, {id, string}}}},
#ejabberd_commands{name = srg_get_info, tags = [shared_roster_group],
desc = "Get info of a Shared Roster Group",
module = ?MODULE, function = srg_get_info,
args = [{group, binary}, {host, binary}],
+ args_example = [<<"group3">>, <<"myserver.com">>],
+ args_desc = ["Group identifier", "Group server name"],
+ result_example = [{<<"name">>, "Group 3"}, {<<"displayed_groups">>, "group1"}],
+ result_desc = "List of group informations, as key and value",
result = {informations, {list, {information, {tuple, [{key, string}, {value, string}]}}}}},
#ejabberd_commands{name = srg_get_members, tags = [shared_roster_group],
desc = "Get members of a Shared Roster Group",
module = ?MODULE, function = srg_get_members,
args = [{group, binary}, {host, binary}],
+ args_example = [<<"group3">>, <<"myserver.com">>],
+ args_desc = ["Group identifier", "Group server name"],
+ result_example = [<<"user1@localhost">>, <<"user2@localhost">>],
+ result_desc = "List of group identifiers",
result = {members, {list, {member, string}}}},
#ejabberd_commands{name = srg_user_add, tags = [shared_roster_group],
desc = "Add the JID user@host to the Shared Roster Group",
module = ?MODULE, function = srg_user_add,
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
+ args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
+ args_desc = ["Username", "User server name", "Group identifier", "Group server name"],
result = {res, rescode}},
#ejabberd_commands{name = srg_user_del, tags = [shared_roster_group],
desc = "Delete this JID user@host from the Shared Roster Group",
module = ?MODULE, function = srg_user_del,
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
+ args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
+ args_desc = ["Username", "User server name", "Group identifier", "Group server name"],
result = {res, rescode}},
#ejabberd_commands{name = get_offline_count,
@@ -611,27 +698,42 @@ get_commands_spec() ->
policy = user,
module = mod_offline, function = count_offline_messages,
args = [],
+ result_example = 5,
+ result_desc = "Number",
result = {value, integer}},
#ejabberd_commands{name = send_message, tags = [stanza],
desc = "Send a message to a local or remote bare of full JID",
module = ?MODULE, function = send_message,
args = [{type, binary}, {from, binary}, {to, binary},
{subject, binary}, {body, binary}],
+ args_example = [<<"headline">>, <<"admin@localhost">>, <<"user1@localhost">>,
+ <<"Restart">>, <<"In 5 minutes">>],
+ args_desc = ["Message type: normal, chat, headline", "Sender JID",
+ "Receiver JID", "Subject, or empty string", "Body"],
result = {res, rescode}},
#ejabberd_commands{name = send_stanza_c2s, tags = [stanza],
desc = "Send a stanza as if sent from a c2s session",
module = ?MODULE, function = send_stanza_c2s,
args = [{user, binary}, {host, binary}, {resource, binary}, {stanza, binary}],
+ args_example = [<<"admin">>, <<"myserver.com">>, <<"bot">>,
+ <<"<message to='user1@localhost'><ext attr='value'/></message>">>],
+ args_desc = ["Username", "Server name", "Resource", "Stanza"],
result = {res, rescode}},
#ejabberd_commands{name = send_stanza, tags = [stanza],
desc = "Send a stanza; provide From JID and valid To JID",
module = ?MODULE, function = send_stanza,
args = [{from, binary}, {to, binary}, {stanza, binary}],
+ args_example = [<<"admin@localhost">>, <<"user1@localhost">>,
+ <<"<message><ext attr='value'/></message>">>],
+ args_desc = ["Sender JID", "Destination JID", "Stanza"],
result = {res, rescode}},
#ejabberd_commands{name = privacy_set, tags = [stanza],
desc = "Send a IQ set privacy stanza for a local account",
module = ?MODULE, function = privacy_set,
args = [{user, binary}, {host, binary}, {xmlquery, binary}],
+ args_example = [<<"user1">>, <<"myserver.com">>,
+ <<"<query xmlns='jabber:iq:privacy'>...">>],
+ args_desc = ["Username", "Server name", "Query XML element"],
result = {res, rescode}},
#ejabberd_commands{name = stats, tags = [stats],
@@ -639,12 +741,20 @@ get_commands_spec() ->
policy = admin,
module = ?MODULE, function = stats,
args = [{name, binary}],
+ args_example = [<<"registeredusers">>],
+ args_desc = ["Statistic name"],
+ result_example = 6,
+ result_desc = "Integer statistic value",
result = {stat, integer}},
#ejabberd_commands{name = stats_host, tags = [stats],
desc = "Get statistical value for this host: registeredusers onlineusers",
policy = admin,
module = ?MODULE, function = stats,
args = [{name, binary}, {host, binary}],
+ args_example = [<<"registeredusers">>, <<"example.com">>],
+ args_desc = ["Statistic name", "Server JID"],
+ result_example = 6,
+ result_desc = "Integer statistic value",
result = {stat, integer}}
].
@@ -952,7 +1062,7 @@ connected_users_info() ->
connected_users_vhost(Host) ->
USRs = ejabberd_sm:get_vh_session_list(Host),
- [ [U, $@, S, $/, R] || {U, S, R} <- USRs].
+ [ jid:encode(jid:make(USR)) || USR <- USRs].
%% Code copied from ejabberd_sm.erl and customized
dirty_get_sessions_list2() ->
@@ -1002,20 +1112,16 @@ set_presence(User, Host, Resource, Type, Show, Status, Priority0) ->
Priority = if is_integer(Priority0) -> Priority0;
true -> binary_to_integer(Priority0)
end,
- case ejabberd_sm:get_session_pid(User, Host, Resource) of
- none ->
- error;
- Pid ->
- From = jid:make(User, Host, Resource),
- To = jid:make(User, Host),
- Presence = #presence{from = From, to = To,
- type = misc:binary_to_atom(Type),
- show = misc:binary_to_atom(Show),
- status = xmpp:mk_text(Status),
- priority = Priority},
- Pid ! {route, Presence},
- ok
- end.
+ Pres = #presence{
+ from = jid:make(User, Host, Resource),
+ to = jid:make(User, Host),
+ type = misc:binary_to_atom(Type),
+ status = xmpp:mk_text(Status),
+ show = misc:binary_to_atom(Show),
+ priority = Priority,
+ sub_els = []},
+ Ref = ejabberd_sm:get_session_pid(User, Host, Resource),
+ ejabberd_c2s:set_presence(Ref, Pres).
user_sessions_info(User, Host) ->
CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
diff --git a/src/mod_http_api.erl b/src/mod_http_api.erl
index 7be05dbf1..ef881d14b 100644
--- a/src/mod_http_api.erl
+++ b/src/mod_http_api.erl
@@ -538,9 +538,17 @@ json_error(HTTPCode, JSONCode, Message) ->
log(Call, Args, {Addr, Port}) ->
AddrS = misc:ip_to_list({Addr, Port}),
- ?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]);
+ ?INFO_MSG("API call ~s ~p from ~s:~p", [Call, hide_sensitive_args(Args), AddrS, Port]);
log(Call, Args, IP) ->
- ?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
+ ?INFO_MSG("API call ~s ~p (~p)", [Call, hide_sensitive_args(Args), IP]).
+
+hide_sensitive_args(Args=[_H|_T]) ->
+ lists:map( fun({<<"password">>, Password}) -> {<<"password">>, ejabberd_config:may_hide_data(Password)};
+ ({<<"newpass">>,NewPassword}) -> {<<"newpass">>, ejabberd_config:may_hide_data(NewPassword)};
+ (E) -> E end,
+ Args);
+hide_sensitive_args(NonListArgs) ->
+ NonListArgs.
permission_addon() ->
Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access, none),
diff --git a/src/mod_http_fileserver.erl b/src/mod_http_fileserver.erl
index c2144042e..4e3cfd08b 100644
--- a/src/mod_http_fileserver.erl
+++ b/src/mod_http_fileserver.erl
@@ -59,8 +59,13 @@
-define(HTTP_ERR_FILE_NOT_FOUND,
{-1, 404, [], <<"Not found">>}).
+-define(REQUEST_AUTH_HEADERS,
+ [{<<"WWW-Authenticate">>, <<"Basic realm=\"ejabberd\"">>}]).
+
-define(HTTP_ERR_FORBIDDEN,
{-1, 403, [], <<"Forbidden">>}).
+-define(HTTP_ERR_REQUEST_AUTH,
+ {-1, 401, ?REQUEST_AUTH_HEADERS, <<"Unauthorized">>}).
-define(DEFAULT_CONTENT_TYPE,
<<"application/octet-stream">>).
@@ -317,12 +322,17 @@ serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentT
false
end,
case CanProceed of
+ false ->
+ ?HTTP_ERR_REQUEST_AUTH;
true ->
FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
case file:read_file_info(FileName) of
- {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND;
- {error, enotdir} -> ?HTTP_ERR_FILE_NOT_FOUND;
- {error, eacces} -> ?HTTP_ERR_FORBIDDEN;
+ {error, enoent} ->
+ ?HTTP_ERR_FILE_NOT_FOUND;
+ {error, enotdir} ->
+ ?HTTP_ERR_FILE_NOT_FOUND;
+ {error, eacces} ->
+ ?HTTP_ERR_FORBIDDEN;
{ok, #file_info{type = directory}} -> serve_index(FileName,
DirectoryIndices,
CustomHeaders,
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
index eb2082fe2..d4c18d41b 100644
--- a/src/mod_mam.erl
+++ b/src/mod_mam.erl
@@ -1008,6 +1008,9 @@ get_commands_spec() ->
longdesc = "Valid message TYPEs: "
"\"chat\", \"groupchat\", \"all\".",
module = ?MODULE, function = delete_old_messages,
+ args_desc = ["Type of messages to delete (chat, groupchat, all)",
+ "Days to keep messages"],
+ args_example = [<<"all">>, 31],
args = [{type, binary}, {days, integer}],
result = {res, rescode}}].
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index 69fc2d0dc..8b6d7b8b9 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -166,7 +166,7 @@ restore_room(ServerHost, Host, Name) ->
forget_room(ServerHost, Host, Name) ->
LServer = jid:nameprep(ServerHost),
- ejabberd_hooks:run(remove_room, LServer, [LServer, Name, Host]),
+ ejabberd_hooks:run(destroy_room, LServer, [LServer, Name, Host]),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:forget_room(LServer, Host, Name).
@@ -256,6 +256,7 @@ handle_call({create, Room, From, Nick, Opts}, _From,
Nick, NewOpts, QueueType),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:register_online_room(ServerHost, Room, Host, Pid),
+ ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]),
{reply, ok, State}.
handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{host = OldHost}) ->
diff --git a/src/mod_muc_riak.erl b/src/mod_muc_riak.erl
index 8dd1eddf8..42e644fdd 100644
--- a/src/mod_muc_riak.erl
+++ b/src/mod_muc_riak.erl
@@ -60,7 +60,7 @@ restore_room(_LServer, Host, Name) ->
forget_room(_LServer, Host, Name) ->
{atomic, ejabberd_riak:delete(muc_room, {Name, Host})}.
-can_use_nick(LServer, Host, JID, Nick) ->
+can_use_nick(_LServer, Host, JID, Nick) ->
{LUser, LServer, _} = jid:tolower(JID),
LUS = {LUser, LServer},
case ejabberd_riak:get_by_index(muc_registered,
diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl
index ec1cffd6a..31dbbbfa7 100644
--- a/src/mod_muc_room.erl
+++ b/src/mod_muc_room.erl
@@ -4004,6 +4004,7 @@ tab_add_online_user(JID, StateData) ->
Room = StateData#state.room,
Host = StateData#state.host,
ServerHost = StateData#state.server_host,
+ ejabberd_hooks:run(join_room, ServerHost, [ServerHost, Room, Host, JID]),
mod_muc:register_online_user(ServerHost, jid:tolower(JID), Room, Host).
-spec tab_remove_online_user(jid(), state()) -> any().
@@ -4011,6 +4012,7 @@ tab_remove_online_user(JID, StateData) ->
Room = StateData#state.room,
Host = StateData#state.host,
ServerHost = StateData#state.server_host,
+ ejabberd_hooks:run(leave_room, ServerHost, [ServerHost, Room, Host, JID]),
mod_muc:unregister_online_user(ServerHost, jid:tolower(JID), Room, Host).
-spec tab_count_user(jid(), state()) -> non_neg_integer().
diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl
index eca229813..85384610d 100644
--- a/src/mod_privacy.erl
+++ b/src/mod_privacy.erl
@@ -410,9 +410,11 @@ decode_item(#privacy_item{order = Order,
match_presence_out = MatchPresenceOut}
end.
--spec c2s_copy_session(ejabberd_c2s:state(), c2s_state()) -> c2s_state().
+-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
c2s_copy_session(State, #{privacy_active_list := List}) ->
- State#{privacy_active_list => List}.
+ State#{privacy_active_list => List};
+c2s_copy_session(State, _) ->
+ State.
-spec user_send_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
user_send_packet({#iq{type = Type,
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 1a620cb6b..263b33ab9 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -546,14 +546,7 @@ disco_identity(Host, Node, From) ->
case get_allowed_items_call(Host, Nidx, From, Type,
Options, Owners) of
{result, _} ->
- {result, [#identity{category = <<"pubsub">>,
- type = <<"pep">>},
- #identity{category = <<"pubsub">>,
- type = <<"leaf">>,
- name = case get_option(Options, title) of
- false -> <<>>;
- [Title] -> Title
- end}]};
+ {result, [#identity{category = <<"pubsub">>, type = <<"pep">>}]};
_ ->
{result, []}
end
@@ -586,8 +579,7 @@ disco_features(Host, Node, From) ->
Type, Options, Owners) of
{result, _} ->
{result,
- [?NS_PUBSUB |
- [feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
+ [?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
_ ->
{result, []}
end
@@ -620,7 +612,7 @@ disco_items(Host, <<>>, From) ->
jid = jid:make(Host),
name = case get_option(Options, title) of
false -> <<>>;
- [Title] -> Title
+ Title -> Title
end} | Acc];
_ ->
Acc
@@ -679,16 +671,14 @@ caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} =
presence(Host, {presence, U, S, [R], JID}).
-spec presence_probe(jid(), jid(), pid()) -> ok.
-presence_probe(#jid{luser = U, lserver = S, lresource = R} = JID, JID, Pid) ->
- presence(S, {presence, JID, Pid}),
- presence(S, {presence, U, S, [R], JID});
presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) ->
%% ignore presence_probe from my other ressources
%% to not get duplicated last items
ok;
-presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = S} = JID, _Pid) ->
- presence(S, {presence, U, S, [R], JID});
-presence_probe(_Host, _JID, _Pid) ->
+presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, Pid) ->
+ presence(S, {presence, From, Pid}),
+ presence(S, {presence, From#jid.luser, S, [From#jid.lresource], To});
+presence_probe(_From, _To, _Pid) ->
%% ignore presence_probe from remote contacts,
%% those are handled via caps_add
ok.
@@ -1723,7 +1713,7 @@ delete_node(Host, Node, Owner) ->
%%<li>The node does not support subscriptions.</li>
%%<li>The node does not exist.</li>
%%</ul>
--spec subscribe_node(host(), binary(), jid(), binary(), [{binary(), [binary()]}]) ->
+-spec subscribe_node(host(), binary(), jid(), jid(), [{binary(), [binary()]}]) ->
{result, pubsub()} | {error, stanza_error()}.
subscribe_node(Host, Node, From, JID, Configuration) ->
SubModule = subscription_plugin(Host),
@@ -2152,9 +2142,14 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) ->
node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]).
get_last_items(Host, Type, Nidx, LJID, Count) ->
- case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of
- {result, Items} -> Items;
- _ -> []
+ case get_cached_item(Host, Nidx) of
+ undefined ->
+ case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of
+ {result, Items} -> Items;
+ _ -> []
+ end;
+ LastItem ->
+ [LastItem]
end.
%% @doc <p>Resend the items of a node to the user.</p>
diff --git a/src/node_pep.erl b/src/node_pep.erl
index 6dd7a68c4..cc0dd41fb 100644
--- a/src/node_pep.erl
+++ b/src/node_pep.erl
@@ -119,7 +119,7 @@ create_node(Nidx, Owner) ->
delete_node(Nodes) ->
{result, {_, _, Result}} = node_flat:delete_node(Nodes),
- {result, {[], Result}}.
+ {result, {default, Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->
diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl
index 68a2ce946..b84c945bd 100644
--- a/src/node_pep_sql.erl
+++ b/src/node_pep_sql.erl
@@ -73,7 +73,7 @@ create_node(Nidx, Owner) ->
delete_node(Nodes) ->
{result, {_, _, Result}} = node_flat_sql:delete_node(Nodes),
- {result, {[], Result}}.
+ {result, {default, Result}}.
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
SendLast, PresenceSubscription, RosterGroup, Options) ->