diff options
author | Konstantinos Kallas <konstantinos.kallas@hotmail.com> | 2017-07-26 09:54:23 +0300 |
---|---|---|
committer | Konstantinos Kallas <konstantinos.kallas@hotmail.com> | 2017-07-26 09:54:23 +0300 |
commit | 61d1411ab3321b437f6741703405860d18c82b93 (patch) | |
tree | 44481834335fb872eee63e205e81417fa26249d5 /src | |
parent | Add an erl_opt so that rsa can be used when the otp version is enough (diff) | |
parent | Update oauth2 dependency (diff) |
Sync fork with upstream
Diffstat (limited to 'src')
32 files changed, 841 insertions, 622 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 8f374a440..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]). @@ -67,7 +67,10 @@ start(SockData, Opts) -> case proplists:get_value(supervisor, Opts, true) of true -> - supervisor:start_child(ejabberd_c2s_sup, [SockData, Opts]); + case supervisor:start_child(ejabberd_c2s_sup, [SockData, Opts]) of + {ok, undefined} -> ignore; + Res -> Res + end; _ -> xmpp_stream_in:start(?MODULE, [SockData, Opts], ejabberd_config:fsm_limit_opts(Opts)) @@ -94,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). @@ -522,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 fc921c066..f90b70bce 100644 --- a/src/ejabberd_commands_doc.erl +++ b/src/ejabberd_commands_doc.erl @@ -69,9 +69,9 @@ list_join_with([El|Tail], M) -> end, [El], Tail)). md_tag(dt, V) -> - [<<"\n">>, V, <<"\n">>]; + [<<"- ">>, V]; md_tag(dd, V) -> - [<<"\n: ">>, V, <<"\n">>]; + [<<" : ">>, V, <<"\n">>]; md_tag(li, V) -> [<<"- ">>, V, <<"\n">>]; md_tag(pre, V) -> @@ -87,14 +87,6 @@ md_tag(strong, V) -> md_tag(_, V) -> V. - -%% rescode_to_int(ok) -> -%% 0; -%% rescode_to_int(true) -> -%% 0; -%% rescode_to_int(_) -> -%% 1. - perl_gen({Name, integer}, Int, _Indent, HTMLOutput) -> [?ARG(Name), ?OP_L(" => "), ?NUM(Int)]; perl_gen({Name, string}, Str, _Indent, HTMLOutput) -> @@ -257,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">>; @@ -324,55 +316,94 @@ gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc, case lists:member(<<"xmlrpc">>, Langs) of true -> ?TAG(li, ?TAG(pre, XML)); _ -> [] end, case lists:member(<<"json">>, Langs) of true -> ?TAG(li, ?TAG(pre, JSON)); _ -> [] end])]; true -> - [<<"\n">>, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end, - case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end, - case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end, - case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end, - <<"{: .code-samples-labels}\n">>, - case lists:member(<<"java">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end, - case lists:member(<<"perl">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end, - case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end, - case lists:member(<<"json">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end, - <<"{: .code-samples-tabs}\n\n">>] + case Langs of + Val when length(Val) == 0 orelse length(Val) == 1 -> + [case lists:member(<<"java">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end, + case lists:member(<<"perl">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end, + case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end, + case lists:member(<<"json">>, Langs) of true -> [<<"\n">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end, + <<"\n\n">>]; + _ -> + [<<"\n">>, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end, + case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end, + case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end, + case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end, + <<"{: .code-samples-labels}\n">>, + case lists:member(<<"java">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end, + case lists:member(<<"perl">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end, + case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end, + case lists:member(<<"json">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end, + <<"{: .code-samples-tabs}\n\n">>] + 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(format_type(Type))])]; +gen_param(Name, Type, Desc, HTMLOutput) -> + [?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", lists:map(fun({AName, Type}) -> - [?TAG(li, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>, - ?RAW(io_lib:format("~p", [Type]))])] - end, Args))]; - _ -> - [?TAG(dl, "args-list", lists:map(fun({{AName, Type}, ADesc}) -> - [?TAG(dt, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>, - ?RAW(io_lib:format("~p", [Type]))]), - ?TAG(dd, ?RAW(ADesc))] - end, lists:zip(Args, ArgsDesc)))] - end, - ResultText = case ResultDesc of - none -> - [?RAW(io_lib:format("~p", [Result]))]; - _ -> - [?TAG(dl, [ - ?TAG(dt, io_lib:format("~p", [Result])), - ?TAG_R(dd, ResultDesc)])] - end, - - [?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]), - ?TAG(p, ?RAW(LDesc)), - ?TAG(h2, <<"Arguments:">>), - ArgsText, - ?TAG(h2, <<"Result:">>), - ResultText, - ?TAG(h2, <<"Examples:">>), - gen_calls(Cmd, HTMLOutput, Langs)]. + 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, + ?TAG(h2, <<"Result:">>), ResultText, + ?TAG(h2, <<"Examples:">>), gen_calls(Cmd, HTMLOutput, Langs)] + catch + _:Ex -> + throw(iolist_to_binary(io_lib:format( + <<"Error when generating documentation for command '~p': ~p">>, + [Name, Ex]))) + end. find_commands_definitions() -> case code:lib_dir(ejabberd, ebin) of diff --git a/src/ejabberd_http_ws.erl b/src/ejabberd_http_ws.erl index 18ba071dd..f4a73cc39 100644 --- a/src/ejabberd_http_ws.erl +++ b/src/ejabberd_http_ws.erl @@ -81,8 +81,14 @@ start_link(WS) -> gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS). send_xml({http_ws, FsmRef, _IP}, Packet) -> - gen_fsm:sync_send_all_state_event(FsmRef, - {send_xml, Packet}). + case catch gen_fsm:sync_send_all_state_event(FsmRef, + {send_xml, Packet}, + 15000) + of + {'EXIT', {timeout, _}} -> {error, timeout}; + {'EXIT', _} -> {error, einval}; + Res -> Res + end. setopts({http_ws, FsmRef, _IP}, Opts) -> case lists:member({active, once}, Opts) of 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_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_s2s_in.erl b/src/ejabberd_s2s_in.erl index 76a844f87..48a650a4e 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -57,7 +57,10 @@ start(SockData, Opts) -> case proplists:get_value(supervisor, Opts, true) of true -> - supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts]); + case supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts]) of + {ok, undefined} -> ignore; + Res -> Res + end; _ -> xmpp_stream_in:start(?MODULE, [SockData, Opts], ejabberd_config:fsm_limit_opts(Opts)) diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index bcacd8e77..e8cad9792 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -54,10 +54,13 @@ %%%=================================================================== start(From, To, Opts) -> case proplists:get_value(supervisor, Opts, true) of - true -> - supervisor:start_child(ejabberd_s2s_out_sup, - [From, To, Opts]); - _ -> + true -> + case supervisor:start_child(ejabberd_s2s_out_sup, + [From, To, Opts]) of + {ok, undefined} -> ignore; + Res -> Res + end; + _ -> xmpp_stream_out:start(?MODULE, [ejabberd_socket, From, To, Opts], ejabberd_config:fsm_limit_opts([])) end. 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_socket.erl b/src/ejabberd_socket.erl index e1712be2f..9953a76ae 100644 --- a/src/ejabberd_socket.erl +++ b/src/ejabberd_socket.erl @@ -87,7 +87,7 @@ %% API %%==================================================================== -spec start(atom(), sockmod(), socket(), [proplists:property()]) - -> {ok, pid() | independent} | {error, inet:posix() | any()}. + -> {ok, pid() | independent} | {error, inet:posix() | any()} | ignore. start(Module, SockMod, Socket, Opts) -> case Module:socket_type() of independent -> {ok, independent}; 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 c5ce6874f..80296f7b7 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, integer}}, + 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,32 +111,37 @@ 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, integer}}, + 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, integer}}, + 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, integer}}, + 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, integer}} + result = {res, rescode}} ]. %% -- public modules functions diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl index df2206d8d..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()}), @@ -1401,11 +1507,11 @@ srg_get_members(Group, Host) -> || {MUser, MServer} <- Members]. srg_user_add(User, Host, Group, GroupHost) -> - {atomic, _} = mod_shared_roster:add_user_to_group(GroupHost, {User, Host}, Group), + mod_shared_roster:add_user_to_group(GroupHost, {User, Host}, Group), ok. srg_user_del(User, Host, Group, GroupHost) -> - {atomic, _} = mod_shared_roster:remove_user_from_group(GroupHost, {User, Host}, Group), + mod_shared_roster:remove_user_from_group(GroupHost, {User, Host}, Group), ok. diff --git a/src/mod_block_strangers.erl b/src/mod_block_strangers.erl index 636d5077b..49d79e043 100644 --- a/src/mod_block_strangers.erl +++ b/src/mod_block_strangers.erl @@ -58,9 +58,12 @@ filter_packet({#message{} = Msg, State} = Acc) -> LFrom = jid:tolower(From), LBFrom = jid:remove_resource(LFrom), #{pres_a := PresA, jid := JID, lserver := LServer} = State, + AllowLocalUsers = + gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users, true), case (Msg#message.body == [] andalso Msg#message.subject == []) - orelse ejabberd_router:is_my_route(From#jid.lserver) + orelse (AllowLocalUsers andalso + ejabberd_router:is_my_route(From#jid.lserver)) orelse (?SETS):is_element(LFrom, PresA) orelse (?SETS):is_element(LBFrom, PresA) orelse sets_bare_member(LBFrom, PresA) of @@ -128,4 +131,6 @@ mod_opt_type(drop) -> fun (B) when is_boolean(B) -> B end; mod_opt_type(log) -> fun (B) when is_boolean(B) -> B end; -mod_opt_type(_) -> [drop, log]. +mod_opt_type(allow_local_users) -> + fun (B) when is_boolean(B) -> B end; +mod_opt_type(_) -> [drop, log, allow_local_users]. diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl index f2fcb37ee..efe6a260f 100644 --- a/src/mod_client_state.erl +++ b/src/mod_client_state.erl @@ -199,8 +199,8 @@ c2s_authenticated_packet(C2SState, _) -> C2SState. -spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state(). -c2s_copy_session(C2SState, #{csi_state := State, csi_queue := Q}) -> - C2SState#{csi_state => State, csi_queue => Q}; +c2s_copy_session(C2SState, #{csi_queue := Q}) -> + C2SState#{csi_queue => Q}; c2s_copy_session(C2SState, _) -> C2SState. 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_mam_sql.erl b/src/mod_mam_sql.erl index fae14955f..7e02b5791 100644 --- a/src/mod_mam_sql.erl +++ b/src/mod_mam_sql.erl @@ -198,15 +198,14 @@ export(_Server) -> [] end}, {archive_msg, - fun(Host, #archive_msg{us ={_LUser, LServer}, + fun(Host, #archive_msg{us ={LUser, LServer}, id = _ID, timestamp = TS, peer = Peer, - bare_peer = {PUser, PServer, <<>>}, type = Type, nick = Nick, packet = Pkt}) when LServer == Host -> TStmp = now_to_usec(TS), SUser = case Type of - chat -> PUser; - groupchat -> jid:encode({PUser, PServer, <<>>}) + chat -> LUser; + groupchat -> jid:encode({LUser, LServer, <<>>}) end, BarePeer = jid:encode(jid:tolower(jid:remove_resource(Peer))), LPeer = jid:encode(jid:tolower(Peer)), diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl index 8726856b5..2d1e66ba5 100644 --- a/src/mod_muc_admin.erl +++ b/src/mod_muc_admin.erl @@ -208,8 +208,11 @@ get_commands_spec() -> module = ?MODULE, function = send_direct_invitation, args_desc = ["Room name", "MUC service", "Password, or none", "Reason text, or none", "Users JIDs separated with : characters"], - args_example = ["room1", "muc.example.com", none, none, "user2@localhost:user3@example.com"], - args = [{name, binary}, {service, binary}, {password, binary}, {reason, binary}, {users, binary}], + args_example = [<<"room1">>, <<"muc.example.com">>, + <<>>, <<"Check this out!">>, + "user2@localhost:user3@example.com"], + args = [{name, binary}, {service, binary}, {password, binary}, + {reason, binary}, {users, binary}], result = {res, rescode}}, #ejabberd_commands{name = change_room_option, tags = [muc_room], @@ -277,7 +280,7 @@ get_commands_spec() -> args_desc = ["Room name", "MUC service"], args_example = ["room1", "muc.example.com"], result_desc = "The list of affiliations with username, domain, affiliation and reason", - result_example = [{"user1", "example.com", "member"}], + result_example = [{"user1", "example.com", member, "member"}], args = [{name, binary}, {service, binary}], result = {affiliations, {list, {affiliation, {tuple, @@ -1009,7 +1012,7 @@ set_room_affiliation(Name, Service, JID, AffiliationString) -> case mod_muc:find_online_room(Name, Service) of {ok, Pid} -> %% Get the PID for the online room so we can get the state of the room - {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, <<"">>}), + {ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}), mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)), ok; error -> diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index 149e6c221..ec1cffd6a 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -2608,7 +2608,7 @@ process_item_change(UJID) -> -type admin_action() :: {jid(), affiliation | role, affiliation() | role(), binary()}. --spec process_item_change(admin_action(), state(), jid()) -> state() | {error, stanza_error()}. +-spec process_item_change(admin_action(), state(), undefined | jid()) -> state() | {error, stanza_error()}. process_item_change(Item, SD, UJID) -> try case Item of {JID, affiliation, owner, _} when JID#jid.luser == <<"">> -> @@ -2658,8 +2658,15 @@ process_item_change(Item, SD, UJID) -> SD1 end catch E:R -> - ?ERROR_MSG("failed to set item ~p from ~s: ~p", - [Item, jid:encode(UJID), + FromSuffix = case UJID of + #jid{} -> + JidString = jid:encode(UJID), + <<" from ", JidString/binary>>; + undefined -> + <<"">> + end, + ?ERROR_MSG("failed to set item ~p~s: ~p", + [Item, FromSuffix, {E, {R, erlang:get_stacktrace()}}]), {error, xmpp:err_internal_server_error()} end. diff --git a/src/mod_multicast.erl b/src/mod_multicast.erl index d2627c252..4d3e481a7 100644 --- a/src/mod_multicast.erl +++ b/src/mod_multicast.erl @@ -998,9 +998,8 @@ build_service_limit_record(LimitOpts) -> build_limit_record(LimitOptsR, remote)}. get_from_limitopts(LimitOpts, SenderT) -> - [{StanzaT, Number} - || {SenderT2, StanzaT, Number} <- LimitOpts, - SenderT =:= SenderT2]. + {SenderT, Result} = lists:keyfind(SenderT, 1, LimitOpts), + Result. build_remote_limit_record(LimitOpts, SenderT) -> build_limit_record(LimitOpts, SenderT). @@ -1118,6 +1117,13 @@ depends(_Host, _Opts) -> mod_opt_type(access) -> fun acl:access_rules_validator/1; mod_opt_type(host) -> fun iolist_to_binary/1; -mod_opt_type(limits) -> - fun (A) when is_list(A) -> A end; -mod_opt_type(_) -> [access, host, limits]. +mod_opt_type({limits, Type}) when (Type == local) or (Type == remote) -> + fun(L) -> + lists:map( + fun ({message, infinite}) -> infinite; + ({presence, infinite}) -> infinite; + ({message, I}) when is_integer(I) -> I; + ({presence, I}) when is_integer(I) -> I + end, L) + end; +mod_opt_type(_) -> [access, host, {limits, local}, {limits, remote}]. diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 2c2c6185a..0be61f71f 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -671,7 +671,7 @@ user_queue(User, Server, Query, Lang) -> Mod = gen_mod:db_mod(LServer, ?MODULE), Res = user_queue_parse_query(LUser, LServer, Query), HdrsAll = Mod:read_message_headers(LUser, LServer), - Hdrs = get_messages_subset(US, Server, HdrsAll), + Hdrs = get_messages_subset(User, Server, HdrsAll), FMsgs = format_user_queue(Hdrs), [?XC(<<"h1">>, (str:format(?T(<<"~s's Offline Messages Queue">>), diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl index bcf5b7aad..f43f4c929 100644 --- a/src/mod_offline_sql.erl +++ b/src/mod_offline_sql.erl @@ -181,6 +181,13 @@ count_messages(LUser, LServer) -> export(_Server) -> [{offline_msg, + fun(Host, #offline_msg{us = {LUser, LServer}}) + when LServer == Host -> + [?SQL("delete from spool where username=%(LUser)s;")]; + (_Host, _R) -> + [] + end}, + {offline_msg, fun(Host, #offline_msg{us = {LUser, LServer}, timestamp = TimeStamp, from = From, to = To, packet = El}) @@ -192,8 +199,7 @@ export(_Server) -> Packet1, jid:make(LServer), TimeStamp, <<"Offline Storage">>), XML = fxml:element_to_binary(xmpp:encode(Packet2)), - [?SQL("delete from spool where username=%(LUser)s;"), - ?SQL("insert into spool(username, xml) values (" + [?SQL("insert into spool(username, xml) values (" "%(LUser)s, %(XML)s);")] catch _:{xmpp_codec, Why} -> ?ERROR_MSG("failed to decode packet ~p of user ~s@~s: ~s", 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 91d2f928a..1a620cb6b 100644 --- a/src/mod_pubsub.erl +++ b/src/mod_pubsub.erl @@ -3009,60 +3009,61 @@ c2s_handle_info(C2SState, _) -> subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> NodesToDeliver = fun (Depth, Node, Subs, Acc) -> - NodeName = case Node#pubsub_node.nodeid of - {_, N} -> N; - Other -> Other - end, - NodeOptions = Node#pubsub_node.options, - lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) -> - case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of - true -> - case state_can_deliver(LJID, SubOptions) of - [] -> {JIDs, Recipients}; - JIDsToDeliver -> - lists:foldl( - fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) -> - case lists:member(JIDToDeliver, JIDs) of - %% check if the JIDs co-accumulator contains the Subscription Jid, - false -> - %% - if not, - %% - add the Jid to JIDs list co-accumulator ; - %% - create a tuple of the Jid, Nidx, and SubID (as list), - %% and add the tuple to the Recipients list co-accumulator - {[JIDToDeliver | JIDsAcc], - [{JIDToDeliver, NodeName, [SubID]} - | RecipientsAcc]}; - true -> - %% - if the JIDs co-accumulator contains the Jid - %% get the tuple containing the Jid from the Recipient list co-accumulator - {_, {JIDToDeliver, NodeName1, SubIDs}} = - lists:keysearch(JIDToDeliver, 1, RecipientsAcc), - %% delete the tuple from the Recipients list - % v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients), - % v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients, {LJID, Nidx1, [SubID | SubIDs]}), - %% add the SubID to the SubIDs list in the tuple, - %% and add the tuple back to the Recipients list co-accumulator - % v1.1 : {JIDs, lists:append(Recipients1, [{LJID, Nidx1, lists:append(SubIDs, [SubID])}])} - % v1.2 : {JIDs, [{LJID, Nidx1, [SubID | SubIDs]} | Recipients1]} - % v2: {JIDs, Recipients1} - {JIDsAcc, - lists:keyreplace(JIDToDeliver, 1, - RecipientsAcc, - {JIDToDeliver, NodeName1, - [SubID | SubIDs]})} - end - end, {JIDs, Recipients}, JIDsToDeliver) - end; - false -> - {JIDs, Recipients} - end - end, Acc, Subs) - end, + NodeName = case Node#pubsub_node.nodeid of + {_, N} -> N; + Other -> Other + end, + NodeOptions = Node#pubsub_node.options, + lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) -> + case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of + true -> + case state_can_deliver(LJID, SubOptions) of + [] -> {JIDs, Recipients}; + [LJID] -> {JIDs, [{LJID, NodeName, [SubID]} | Recipients]}; + JIDsToDeliver -> + lists:foldl( + fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) -> + case lists:member(JIDToDeliver, JIDs) of + %% check if the JIDs co-accumulator contains the Subscription Jid, + false -> + %% - if not, + %% - add the Jid to JIDs list co-accumulator ; + %% - create a tuple of the Jid, Nidx, and SubID (as list), + %% and add the tuple to the Recipients list co-accumulator + {[JIDToDeliver | JIDsAcc], + [{JIDToDeliver, NodeName, [SubID]} + | RecipientsAcc]}; + true -> + %% - if the JIDs co-accumulator contains the Jid + %% get the tuple containing the Jid from the Recipient list co-accumulator + {_, {JIDToDeliver, NodeName1, SubIDs}} = + lists:keysearch(JIDToDeliver, 1, RecipientsAcc), + %% delete the tuple from the Recipients list + % v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients), + % v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients, {LJID, Nidx1, [SubID | SubIDs]}), + %% add the SubID to the SubIDs list in the tuple, + %% and add the tuple back to the Recipients list co-accumulator + % v1.1 : {JIDs, lists:append(Recipients1, [{LJID, Nidx1, lists:append(SubIDs, [SubID])}])} + % v1.2 : {JIDs, [{LJID, Nidx1, [SubID | SubIDs]} | Recipients1]} + % v2: {JIDs, Recipients1} + {JIDsAcc, + lists:keyreplace(JIDToDeliver, 1, + RecipientsAcc, + {JIDToDeliver, NodeName1, + [SubID | SubIDs]})} + end + end, {JIDs, Recipients}, JIDsToDeliver) + end; + false -> + {JIDs, Recipients} + end + end, Acc, Subs) + end, DepthsToDeliver = fun({Depth, SubsByNode}, Acc1) -> - lists:foldl(fun({Node, Subs}, Acc2) -> - NodesToDeliver(Depth, Node, Subs, Acc2) - end, Acc1, SubsByNode) - end, + lists:foldl(fun({Node, Subs}, Acc2) -> + NodesToDeliver(Depth, Node, Subs, Acc2) + end, Acc1, SubsByNode) + end, {_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth), JIDSubs. diff --git a/src/mod_stream_mgmt.erl b/src/mod_stream_mgmt.erl index 7b546465e..127eea3e8 100644 --- a/src/mod_stream_mgmt.erl +++ b/src/mod_stream_mgmt.erl @@ -441,6 +441,7 @@ update_num_stanzas_in(#{mgmt_state := MgmtState, update_num_stanzas_in(State, _El) -> State. +-spec send_rack(state()) -> state(). send_rack(#{mgmt_ack_timer := _} = State) -> State; send_rack(#{mgmt_xmlns := Xmlns, @@ -450,6 +451,7 @@ send_rack(#{mgmt_xmlns := Xmlns, State1 = State#{mgmt_ack_timer => TRef, mgmt_stanzas_req => NumStanzasOut}, send(State1, #sm_r{xmlns = Xmlns}). +-spec resend_rack(state()) -> state(). resend_rack(#{mgmt_ack_timer := _, mgmt_queue := Queue, mgmt_stanzas_out := NumStanzasOut, diff --git a/src/prosody2ejabberd.erl b/src/prosody2ejabberd.erl index 312a177be..c60f2c24b 100644 --- a/src/prosody2ejabberd.erl +++ b/src/prosody2ejabberd.erl @@ -50,7 +50,7 @@ from_dir(ProsodyDir) -> convert_dir(Path, Host, SubDir) end, ["vcard", "accounts", "roster", "private", "config", "offline", - "privacy"]) + "privacy", "pubsub"]) end, HostDirs); {error, Why} = Err -> ?ERROR_MSG("failed to list ~s: ~s", @@ -115,7 +115,7 @@ maybe_get_scram_auth(Data) -> #scram{ storedkey = misc:hex_to_base64(proplists:get_value(<<"stored_key">>, Data, <<"">>)), serverkey = misc:hex_to_base64(proplists:get_value(<<"server_key">>, Data, <<"">>)), - salt = misc:hex_to_base64(proplists:get_value(<<"salt">>, Data, <<"">>)), + salt = base64:encode(proplists:get_value(<<"salt">>, Data, <<"">>)), iterationcount = round(IC) }; _ -> <<"">> @@ -212,6 +212,46 @@ convert_data(Host, "privacy", User, [Data]) -> end end, Lists)}, mod_privacy:set_list(Priv); +convert_data(PubSub, "pubsub", NodeId, [Data]) -> + Host = url_decode(PubSub), + Node = url_decode(NodeId), + Type = node_type(Host, Node), + NodeData = convert_node_config(Host, Data), + DefaultConfig = mod_pubsub:config(Host, default_node_config, []), + Owner = proplists:get_value(owner, NodeData), + Options = lists:foldl( + fun({_Opt, undefined}, Acc) -> + Acc; + ({Opt, Val}, Acc) -> + lists:keystore(Opt, 1, Acc, {Opt, Val}) + end, DefaultConfig, proplists:get_value(options, NodeData)), + case mod_pubsub:tree_action(Host, create_node, [Host, Node, Type, Owner, Options, []]) of + {ok, Nidx} -> + case mod_pubsub:node_action(Host, Type, create_node, [Nidx, Owner]) of + {result, _} -> + Access = open, % always allow subscriptions proplists:get_value(access_model, Options), + Publish = open, % always allow publications proplists:get_value(publish_model, Options), + MaxItems = proplists:get_value(max_items, Options), + Affiliations = proplists:get_value(affiliations, NodeData), + Subscriptions = proplists:get_value(subscriptions, NodeData), + Items = proplists:get_value(items, NodeData), + [mod_pubsub:node_action(Host, Type, set_affiliation, + [Nidx, Entity, Aff]) + || {Entity, Aff} <- Affiliations, Entity =/= Owner], + [mod_pubsub:node_action(Host, Type, subscribe_node, + [Nidx, jid:make(Entity), Entity, Access, never, [], [], []]) + || Entity <- Subscriptions], + [mod_pubsub:node_action(Host, Type, publish_item, + [Nidx, Publisher, Publish, MaxItems, ItemId, Payload, []]) + || {ItemId, Publisher, Payload} <- Items]; + Error -> + Error + end; + Error -> + ?ERROR_MSG("failed to import pubsub node ~s on host ~s:~n~p", + [Node, Host, NodeData]), + Error + end; convert_data(_Host, _Type, _User, _Data) -> ok. @@ -333,6 +373,88 @@ convert_privacy_item({_, Item}) -> match_presence_in = MatchPresIn, match_presence_out = MatchPresOut}. +url_decode(Encoded) -> + url_decode(Encoded, <<>>). +url_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) -> + Hex = list_to_integer([Hi, Lo], 16), + url_decode(Tail, <<Acc/binary, Hex>>); +url_decode(<<H, Tail/binary>>, Acc) -> + url_decode(Tail, <<Acc/binary, H>>); +url_decode(<<>>, Acc) -> + Acc. + +node_type(_Host, <<"urn:", _Tail/binary>>) -> <<"pep">>; +node_type(_Host, <<"http:", _Tail/binary>>) -> <<"pep">>; +node_type(_Host, <<"https:", _Tail/binary>>) -> <<"pep">>; +node_type(Host, _) -> hd(mod_pubsub:plugins(Host)). + +max_items(Config, Default) -> + case round(proplists:get_value(<<"max_items">>, Config, Default)) of + I when I =< 0 -> Default; + I -> I + end. + +convert_node_affiliations(Data) -> + lists:flatmap( + fun({J, Aff}) -> + try jid:decode(J) of + JID -> + [{JID, misc:binary_to_atom(Aff)}] + catch _:{bad_jid, _} -> + [] + end + end, proplists:get_value(<<"affiliations">>, Data, [])). + +convert_node_subscriptions(Data) -> + lists:flatmap( + fun({J, true}) -> + try jid:decode(J) of + JID -> + [jid:tolower(JID)] + catch _:{bad_jid, _} -> + [] + end; + (_) -> + [] + end, proplists:get_value(<<"subscribers">>, Data, [])). + +convert_node_items(Host, Data) -> + Authors = proplists:get_value(<<"data_author">>, Data, []), + lists:flatmap( + fun({ItemId, Item}) -> + try catch jid:decode(proplists:get_value(ItemId, Authors, Host)) of + JID -> + [El] = deserialize(Item), + [{ItemId, JID, El#xmlel.children}] + catch _:{bad_jid, _} -> + [] + end + end, proplists:get_value(<<"data">>, Data, [])). + +convert_node_config(Host, Data) -> + Config = proplists:get_value(<<"config">>, Data, []), + [{affiliations, convert_node_affiliations(Data)}, + {subscriptions, convert_node_subscriptions(Data)}, + {owner, jid:decode(proplists:get_value(<<"creator">>, Config, Host))}, + {items, convert_node_items(Host, Data)}, + {options, [ + {deliver_notifications, + proplists:get_value(<<"deliver_notifications">>, Config, true)}, + {deliver_payloads, + proplists:get_value(<<"deliver_payloads">>, Config, true)}, + {persist_items, + proplists:get_value(<<"persist_items">>, Config, true)}, + {max_items, + max_items(Config, 10)}, + {access_model, + misc:binary_to_atom(proplists:get_value(<<"access_model">>, Config, <<"open">>))}, + {publish_model, + misc:binary_to_atom(proplists:get_value(<<"publish_model">>, Config, <<"publishers">>))}, + {title, + proplists:get_value(<<"title">>, Config, <<"">>)} + ]} + ]. + el_to_offline_msg(LUser, LServer, #xmlel{attrs = Attrs} = El) -> try TS = xmpp_util:decode_timestamp( |