mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-28 16:34:13 +01:00
Sync fork with upstream
This commit is contained in:
commit
61d1411ab3
@ -158,12 +158,12 @@ listen:
|
|||||||
ip: "::"
|
ip: "::"
|
||||||
module: ejabberd_http
|
module: ejabberd_http
|
||||||
request_handlers:
|
request_handlers:
|
||||||
"/websocket": ejabberd_http_ws
|
"/ws": ejabberd_http_ws
|
||||||
|
"/bosh": mod_bosh
|
||||||
"/api": mod_http_api
|
"/api": mod_http_api
|
||||||
"/.well-known": acme_challenge
|
"/.well-known": acme_challenge
|
||||||
## "/pub/archive": mod_http_fileserver
|
## "/pub/archive": mod_http_fileserver
|
||||||
web_admin: true
|
web_admin: true
|
||||||
http_bind: true
|
|
||||||
## register: true
|
## register: true
|
||||||
captcha: true
|
captcha: true
|
||||||
##
|
##
|
||||||
@ -266,12 +266,12 @@ listen:
|
|||||||
## Outgoing S2S options
|
## Outgoing S2S options
|
||||||
##
|
##
|
||||||
## Preferred address families (which to try first) and connect timeout
|
## Preferred address families (which to try first) and connect timeout
|
||||||
## in milliseconds.
|
## in seconds.
|
||||||
##
|
##
|
||||||
## outgoing_s2s_families:
|
## outgoing_s2s_families:
|
||||||
## - ipv4
|
## - ipv4
|
||||||
## - ipv6
|
## - ipv6
|
||||||
## outgoing_s2s_timeout: 10000
|
## outgoing_s2s_timeout: 190
|
||||||
|
|
||||||
###. ==============
|
###. ==============
|
||||||
###' AUTHENTICATION
|
###' AUTHENTICATION
|
||||||
|
@ -10,26 +10,33 @@ FIREWALL_WINDOW=""
|
|||||||
ERLANG_NODE=ejabberd@localhost
|
ERLANG_NODE=ejabberd@localhost
|
||||||
|
|
||||||
# define default environment variables
|
# define default environment variables
|
||||||
ERL={{erl}}
|
ERL="{{erl}}"
|
||||||
IEX={{bindir}}/iex
|
IEX="{{bindir}}/iex"
|
||||||
EPMD={{epmd}}
|
EPMD="{{epmd}}"
|
||||||
INSTALLUSER={{installuser}}
|
INSTALLUSER="{{installuser}}"
|
||||||
|
|
||||||
# check the proper system user is used if defined
|
# check the proper system user is used
|
||||||
EXEC_CMD="false"
|
case `id -un` in
|
||||||
if [ -n "$INSTALLUSER" ] ; then
|
"$INSTALLUSER")
|
||||||
if [ $(id -g) -eq $(id -g $INSTALLUSER || echo -1) ] ; then
|
|
||||||
EXEC_CMD="as_current_user"
|
EXEC_CMD="as_current_user"
|
||||||
else
|
;;
|
||||||
id -Gn | grep -q wheel && EXEC_CMD="as_install_user"
|
root)
|
||||||
fi
|
if [ -n "$INSTALLUSER" ] ; then
|
||||||
else
|
EXEC_CMD="as_install_user"
|
||||||
EXEC_CMD="as_current_user"
|
else
|
||||||
fi
|
EXEC_CMD="as_current_user"
|
||||||
if [ "$EXEC_CMD" = "false" ] ; then
|
echo "WARNING: This is not recommended to run ejabberd as root" >&2
|
||||||
echo "ERROR: This command can only be run by root or the user $INSTALLUSER" >&2
|
fi
|
||||||
exit 7
|
;;
|
||||||
fi
|
*)
|
||||||
|
if [ -n "$INSTALLUSER" ] ; then
|
||||||
|
echo "ERROR: This command can only be run by root or the user $INSTALLUSER" >&2
|
||||||
|
exit 7
|
||||||
|
else
|
||||||
|
EXEC_CMD="as_current_user"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
# parse command line parameters
|
# parse command line parameters
|
||||||
for arg; do
|
for arg; do
|
||||||
@ -62,14 +69,14 @@ done
|
|||||||
# define erl parameters
|
# define erl parameters
|
||||||
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
|
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
|
||||||
if [ "$FIREWALL_WINDOW" != "" ] ; then
|
if [ "$FIREWALL_WINDOW" != "" ] ; then
|
||||||
ERLANG_OPTS="$ERLANG_OPTS -kernel " \
|
ERLANG_OPTS="$ERLANG_OPTS -kernel \
|
||||||
"inet_dist_listen_min ${FIREWALL_WINDOW%-*} " \
|
inet_dist_listen_min ${FIREWALL_WINDOW%-*} \
|
||||||
"inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||||
fi
|
fi
|
||||||
if [ "$INET_DIST_INTERFACE" != "" ] ; then
|
if [ "$INET_DIST_INTERFACE" != "" ] ; then
|
||||||
INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
|
INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
|
||||||
if [ "$INET_DIST_INTERFACE2" != "" ] ; then
|
if [ "$INET_DIST_INTERFACE2" != "" ] ; then
|
||||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface \"$INET_DIST_INTERFACE2\""
|
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
ERL_LIBS={{libdir}}
|
ERL_LIBS={{libdir}}
|
||||||
@ -103,19 +110,19 @@ export ERL_LIBS
|
|||||||
exec_cmd()
|
exec_cmd()
|
||||||
{
|
{
|
||||||
case $EXEC_CMD in
|
case $EXEC_CMD in
|
||||||
as_install_user) su -c '"$0" $@"' "$INSTALLUSER" -- "$@" ;;
|
as_install_user) su -c '"$0" "$@"' "$INSTALLUSER" -- "$@" ;;
|
||||||
as_current_user) "$@" ;;
|
as_current_user) "$@" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
exec_erl()
|
exec_erl()
|
||||||
{
|
{
|
||||||
NODE=$1; shift
|
NODE=$1; shift
|
||||||
exec_cmd $ERL ${S:--}name $NODE $ERLANG_OPTS "$@"
|
exec_cmd "$ERL" ${S:--}name $NODE $ERLANG_OPTS "$@"
|
||||||
}
|
}
|
||||||
exec_iex()
|
exec_iex()
|
||||||
{
|
{
|
||||||
NODE=$1; shift
|
NODE=$1; shift
|
||||||
exec_cmd $IEX ${S:---}name $NODE --erl "$ERLANG_OPTS" "$@"
|
exec_cmd "$IEX" ${S:---}name $NODE --erl "$ERLANG_OPTS" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
# usage
|
# usage
|
||||||
@ -223,12 +230,6 @@ check_start()
|
|||||||
"$EPMD" -kill >/dev/null
|
"$EPMD" -kill >/dev/null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} || {
|
|
||||||
[ -d "$SPOOL_DIR" ] || exec_cmd mkdir -p "$SPOOL_DIR"
|
|
||||||
cd "$SPOOL_DIR" || {
|
|
||||||
echo "ERROR: ejabberd can not access directory $SPOOL_DIR"
|
|
||||||
exit 6
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,6 +254,13 @@ wait_status()
|
|||||||
[ $timeout -gt 0 ]
|
[ $timeout -gt 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ensure we can change current directory to SPOOL_DIR
|
||||||
|
[ -d "$SPOOL_DIR" ] || exec_cmd mkdir -p "$SPOOL_DIR"
|
||||||
|
cd "$SPOOL_DIR" || {
|
||||||
|
echo "ERROR: can not access directory $SPOOL_DIR"
|
||||||
|
exit 6
|
||||||
|
}
|
||||||
|
|
||||||
# main
|
# main
|
||||||
case $1 in
|
case $1 in
|
||||||
start)
|
start)
|
||||||
@ -287,7 +295,7 @@ case $1 in
|
|||||||
ping)
|
ping)
|
||||||
PEER=${2:-$ERLANG_NODE}
|
PEER=${2:-$ERLANG_NODE}
|
||||||
[ "$PEER" = "${PEER%.*}" ] && PS="-s"
|
[ "$PEER" = "${PEER%.*}" ] && PS="-s"
|
||||||
exec_cmd $ERL ${PS:--}name $(uid ping $(hostname $PS)) $ERLANG_OPTS \
|
exec_cmd "$ERL" ${PS:--}name $(uid ping $(hostname $PS)) $ERLANG_OPTS \
|
||||||
-noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"$PEER"')])' \
|
-noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"$PEER"')])' \
|
||||||
-s erlang halt -output text
|
-s erlang halt -output text
|
||||||
;;
|
;;
|
||||||
|
18
rebar.config
18
rebar.config
@ -18,20 +18,21 @@
|
|||||||
%%%
|
%%%
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager", {tag, "3.4.2"}}},
|
{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager",
|
||||||
|
{tag, {if_version_above, "17", "3.4.2", "3.2.1"}}}},
|
||||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.9"}}},
|
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.9"}}},
|
||||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.8"}}},
|
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.10"}}},
|
||||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.12"}}},
|
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.15"}}},
|
||||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
|
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
|
||||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
|
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
|
||||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.11"}}},
|
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.14"}}},
|
||||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.10"}}},
|
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.10"}}},
|
||||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
|
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.2"}}},
|
||||||
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
|
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
|
||||||
{jose, ".*", {git, "git://github.com/potatosalad/erlang-jose.git", {branch, "master"}}},
|
{jose, ".*", {git, "git://github.com/potatosalad/erlang-jose.git", {branch, "master"}}},
|
||||||
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.11"}}}},
|
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.14"}}}},
|
||||||
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.12"}}}},
|
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.15"}}}},
|
||||||
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
|
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
|
||||||
{tag, "1.0.3"}}}},
|
{tag, "1.0.3"}}}},
|
||||||
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
|
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
|
||||||
@ -39,7 +40,7 @@
|
|||||||
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
|
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
|
||||||
{tag, "1.1.5"}}}},
|
{tag, "1.1.5"}}}},
|
||||||
{if_var_true, pam, {epam, ".*", {git, "https://github.com/processone/epam",
|
{if_var_true, pam, {epam, ".*", {git, "https://github.com/processone/epam",
|
||||||
{tag, "1.0.2"}}}},
|
{tag, "1.0.3"}}}},
|
||||||
{if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
|
{if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
|
||||||
{tag, "1.0.2"}}}},
|
{tag, "1.0.2"}}}},
|
||||||
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client",
|
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client",
|
||||||
@ -71,6 +72,7 @@
|
|||||||
p1_utils,
|
p1_utils,
|
||||||
p1_mysql,
|
p1_mysql,
|
||||||
p1_pgsql,
|
p1_pgsql,
|
||||||
|
p1_oauth2,
|
||||||
epam,
|
epam,
|
||||||
ezlib,
|
ezlib,
|
||||||
iconv]}}.
|
iconv]}}.
|
||||||
|
@ -25,12 +25,11 @@
|
|||||||
|
|
||||||
-module(ejabberd_app).
|
-module(ejabberd_app).
|
||||||
|
|
||||||
-behaviour(ejabberd_config).
|
|
||||||
-author('alexey@process-one.net').
|
-author('alexey@process-one.net').
|
||||||
|
|
||||||
-behaviour(application).
|
-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("ejabberd.hrl").
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
@ -49,13 +48,12 @@ start(normal, _Args) ->
|
|||||||
setup_if_elixir_conf_used(),
|
setup_if_elixir_conf_used(),
|
||||||
ejabberd_config:start(),
|
ejabberd_config:start(),
|
||||||
ejabberd_mnesia:start(),
|
ejabberd_mnesia:start(),
|
||||||
set_settings_from_config(),
|
|
||||||
file_queue_init(),
|
file_queue_init(),
|
||||||
maybe_add_nameservers(),
|
maybe_add_nameservers(),
|
||||||
connect_nodes(),
|
|
||||||
case ejabberd_sup:start_link() of
|
case ejabberd_sup:start_link() of
|
||||||
{ok, SupPid} ->
|
{ok, SupPid} ->
|
||||||
register_elixir_config_hooks(),
|
register_elixir_config_hooks(),
|
||||||
|
ejabberd_cluster:wait_for_sync(infinity),
|
||||||
{T2, _} = statistics(wall_clock),
|
{T2, _} = statistics(wall_clock),
|
||||||
?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs",
|
?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs",
|
||||||
[?VERSION, node(), (T2-T1)/1000]),
|
[?VERSION, node(), (T2-T1)/1000]),
|
||||||
@ -88,12 +86,6 @@ stop(_State) ->
|
|||||||
%%% Internal functions
|
%%% 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
|
%% If ejabberd is running on some Windows machine, get nameservers and add to Erlang
|
||||||
maybe_add_nameservers() ->
|
maybe_add_nameservers() ->
|
||||||
case os:type() of
|
case os:type() of
|
||||||
@ -136,10 +128,6 @@ delete_pid_file() ->
|
|||||||
file:delete(PidFilename)
|
file:delete(PidFilename)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
set_settings_from_config() ->
|
|
||||||
Ticktime = ejabberd_config:get_option(net_ticktime, 60),
|
|
||||||
net_kernel:set_net_ticktime(Ticktime).
|
|
||||||
|
|
||||||
file_queue_init() ->
|
file_queue_init() ->
|
||||||
QueueDir = case ejabberd_config:queue_dir() of
|
QueueDir = case ejabberd_config:queue_dir() of
|
||||||
undefined ->
|
undefined ->
|
||||||
@ -160,15 +148,6 @@ start_apps() ->
|
|||||||
ejabberd:start_app(xmpp),
|
ejabberd:start_app(xmpp),
|
||||||
ejabberd:start_app(cache_tab).
|
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() ->
|
setup_if_elixir_conf_used() ->
|
||||||
case ejabberd_config:is_using_elixir_config() of
|
case ejabberd_config:is_using_elixir_config() of
|
||||||
true -> 'Elixir.Ejabberd.Config.Store':start_link();
|
true -> 'Elixir.Ejabberd.Config.Store':start_link();
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
reject_unauthenticated_packet/2, process_closed/2,
|
reject_unauthenticated_packet/2, process_closed/2,
|
||||||
process_terminated/2, process_info/2]).
|
process_terminated/2, process_info/2]).
|
||||||
%% API
|
%% 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,
|
open_session/1, call/3, send/2, close/1, close/2, stop/1,
|
||||||
reply/2, copy_state/2, set_timeout/2, route/2,
|
reply/2, copy_state/2, set_timeout/2, route/2,
|
||||||
host_up/1, host_down/1]).
|
host_up/1, host_down/1]).
|
||||||
@ -67,7 +67,10 @@
|
|||||||
start(SockData, Opts) ->
|
start(SockData, Opts) ->
|
||||||
case proplists:get_value(supervisor, Opts, true) of
|
case proplists:get_value(supervisor, Opts, true) of
|
||||||
true ->
|
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],
|
xmpp_stream_in:start(?MODULE, [SockData, Opts],
|
||||||
ejabberd_config:fsm_limit_opts(Opts))
|
ejabberd_config:fsm_limit_opts(Opts))
|
||||||
@ -94,6 +97,10 @@ reply(Ref, Reply) ->
|
|||||||
get_presence(Ref) ->
|
get_presence(Ref) ->
|
||||||
call(Ref, get_presence, 1000).
|
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.
|
-spec resend_presence(pid()) -> ok.
|
||||||
resend_presence(Pid) ->
|
resend_presence(Pid) ->
|
||||||
resend_presence(Pid, undefined).
|
resend_presence(Pid, undefined).
|
||||||
@ -522,6 +529,9 @@ handle_call(get_presence, From, #{jid := JID} = State) ->
|
|||||||
end,
|
end,
|
||||||
reply(From, Pres),
|
reply(From, Pres),
|
||||||
State;
|
State;
|
||||||
|
handle_call({set_presence, Pres}, From, State) ->
|
||||||
|
reply(From, ok),
|
||||||
|
process_self_presence(State, Pres);
|
||||||
handle_call(Request, From, #{lserver := LServer} = State) ->
|
handle_call(Request, From, #{lserver := LServer} = State) ->
|
||||||
ejabberd_hooks:run_fold(
|
ejabberd_hooks:run_fold(
|
||||||
c2s_handle_call, LServer, State, [Request, From]).
|
c2s_handle_call, LServer, State, [Request, From]).
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
%%%----------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
%%% File : ejabberd_cluster.erl
|
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
%%% Author : Christophe Romain <christophe.romain@process-one.net>
|
%%% Created : 5 Jul 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||||
%%% Purpose : Ejabberd clustering management
|
|
||||||
%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
|
|
||||||
%%%
|
%%%
|
||||||
%%%
|
%%%
|
||||||
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
||||||
@ -21,132 +19,188 @@
|
|||||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
%%%
|
%%%
|
||||||
%%%----------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
|
|
||||||
-module(ejabberd_cluster).
|
-module(ejabberd_cluster).
|
||||||
|
-behaviour(ejabberd_config).
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([get_nodes/0, call/4, multicall/3, multicall/4,
|
-export([start_link/0, call/4, multicall/3, multicall/4, eval_everywhere/3,
|
||||||
eval_everywhere/3, eval_everywhere/4]).
|
eval_everywhere/4]).
|
||||||
-export([join/1, leave/1, get_known_nodes/0]).
|
%% Backend dependent API
|
||||||
-export([node_id/0, get_node_by_id/1]).
|
-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").
|
-include("logger.hrl").
|
||||||
|
|
||||||
-spec get_nodes() -> [node()].
|
-type dst() :: pid() | atom() | {atom(), node()}.
|
||||||
|
|
||||||
get_nodes() ->
|
-callback init() -> ok | {error, any()}.
|
||||||
mnesia:system_info(running_db_nodes).
|
-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)
|
%%% API
|
||||||
++ mnesia:system_info(extra_db_nodes)).
|
%%%===================================================================
|
||||||
|
start_link() ->
|
||||||
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
-spec call(node(), module(), atom(), [any()]) -> any().
|
-spec call(node(), module(), atom(), [any()]) -> any().
|
||||||
|
|
||||||
call(Node, Module, Function, Args) ->
|
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()]}.
|
-spec multicall(module(), atom(), [any()]) -> {list(), [node()]}.
|
||||||
|
|
||||||
multicall(Module, Function, Args) ->
|
multicall(Module, Function, Args) ->
|
||||||
multicall(get_nodes(), Module, Function, Args).
|
multicall(get_nodes(), Module, Function, Args).
|
||||||
|
|
||||||
-spec multicall([node()], module(), atom(), list()) -> {list(), [node()]}.
|
-spec multicall([node()], module(), atom(), list()) -> {list(), [node()]}.
|
||||||
|
|
||||||
multicall(Nodes, Module, Function, Args) ->
|
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.
|
-spec eval_everywhere(module(), atom(), [any()]) -> ok.
|
||||||
|
|
||||||
eval_everywhere(Module, Function, Args) ->
|
eval_everywhere(Module, Function, Args) ->
|
||||||
eval_everywhere(get_nodes(), Module, Function, Args),
|
eval_everywhere(get_nodes(), Module, Function, Args),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
-spec eval_everywhere([node()], module(), atom(), [any()]) -> ok.
|
-spec eval_everywhere([node()], module(), atom(), [any()]) -> ok.
|
||||||
|
|
||||||
eval_everywhere(Nodes, Module, Function, Args) ->
|
eval_everywhere(Nodes, Module, Function, Args) ->
|
||||||
rpc:eval_everywhere(Nodes, Module, Function, Args),
|
rpc:eval_everywhere(Nodes, Module, Function, Args),
|
||||||
ok.
|
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) ->
|
join(Node) ->
|
||||||
case {node(), net_adm:ping(Node)} of
|
Mod = get_mod(),
|
||||||
{Node, _} ->
|
Mod:join(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.
|
|
||||||
|
|
||||||
-spec leave(node()) -> ok | {error, any()}.
|
-spec leave(node()) -> ok | {error, any()}.
|
||||||
|
|
||||||
leave(Node) ->
|
leave(Node) ->
|
||||||
case {node(), net_adm:ping(Node)} of
|
Mod = get_mod(),
|
||||||
{Node, _} ->
|
Mod:leave(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.
|
|
||||||
|
|
||||||
-spec node_id() -> binary().
|
-spec node_id() -> binary().
|
||||||
node_id() ->
|
node_id() ->
|
||||||
integer_to_binary(erlang:phash2(node())).
|
Mod = get_mod(),
|
||||||
|
Mod:node_id().
|
||||||
|
|
||||||
-spec get_node_by_id(binary()) -> node().
|
-spec get_node_by_id(binary()) -> node().
|
||||||
get_node_by_id(Hash) ->
|
get_node_by_id(ID) ->
|
||||||
try binary_to_integer(Hash) of
|
Mod = get_mod(),
|
||||||
I -> match_node_id(I)
|
Mod:get_node_by_id(ID).
|
||||||
catch _:_ ->
|
|
||||||
node()
|
-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.
|
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
|
%%% Internal functions
|
||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
-spec match_node_id(integer()) -> node().
|
get_mod() ->
|
||||||
match_node_id(I) ->
|
Backend = ejabberd_config:get_option(cluster_backend, mnesia),
|
||||||
match_node_id(I, get_nodes()).
|
list_to_atom("ejabberd_cluster_" ++ atom_to_list(Backend)).
|
||||||
|
|
||||||
-spec match_node_id(integer(), [node()]) -> node().
|
rpc_timeout() ->
|
||||||
match_node_id(I, [Node|Nodes]) ->
|
timer:seconds(ejabberd_config:get_option(rpc_timeout, 5)).
|
||||||
case erlang:phash2(Node) of
|
|
||||||
I -> Node;
|
opt_type(net_ticktime) ->
|
||||||
_ -> match_node_id(I, Nodes)
|
fun (P) when is_integer(P), P > 0 -> P end;
|
||||||
end;
|
opt_type(cluster_nodes) ->
|
||||||
match_node_id(_I, []) ->
|
fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
|
||||||
node().
|
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].
|
||||||
|
144
src/ejabberd_cluster_mnesia.erl
Normal file
144
src/ejabberd_cluster_mnesia.erl
Normal file
@ -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().
|
@ -221,7 +221,6 @@
|
|||||||
get_command_format/1,
|
get_command_format/1,
|
||||||
get_command_format/2,
|
get_command_format/2,
|
||||||
get_command_format/3,
|
get_command_format/3,
|
||||||
get_command_policy_and_scope/1,
|
|
||||||
get_command_definition/1,
|
get_command_definition/1,
|
||||||
get_command_definition/2,
|
get_command_definition/2,
|
||||||
get_tags_commands/0,
|
get_tags_commands/0,
|
||||||
@ -230,11 +229,6 @@
|
|||||||
register_commands/1,
|
register_commands/1,
|
||||||
unregister_commands/1,
|
unregister_commands/1,
|
||||||
expose_commands/1,
|
expose_commands/1,
|
||||||
execute_command/2,
|
|
||||||
execute_command/3,
|
|
||||||
execute_command/4,
|
|
||||||
execute_command/5,
|
|
||||||
execute_command/6,
|
|
||||||
opt_type/1,
|
opt_type/1,
|
||||||
get_commands_spec/0,
|
get_commands_spec/0,
|
||||||
get_commands_definition/0,
|
get_commands_definition/0,
|
||||||
@ -361,6 +355,8 @@ expose_commands(Commands) ->
|
|||||||
Commands),
|
Commands),
|
||||||
|
|
||||||
case ejabberd_config:add_option(commands, [{add_commands, Names}]) of
|
case ejabberd_config:add_option(commands, [{add_commands, Names}]) of
|
||||||
|
ok ->
|
||||||
|
ok;
|
||||||
{aborted, Reason} ->
|
{aborted, Reason} ->
|
||||||
{error, Reason};
|
{error, Reason};
|
||||||
{atomic, Result} ->
|
{atomic, Result} ->
|
||||||
@ -427,17 +423,6 @@ get_command_format(Name, Auth, Version) ->
|
|||||||
{Args, Result}
|
{Args, Result}
|
||||||
end.
|
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,
|
%% The oauth scopes for a command are the command name itself,
|
||||||
%% also might include either 'ejabberd:user' or 'ejabberd:admin'
|
%% also might include either 'ejabberd:user' or 'ejabberd:admin'
|
||||||
cmd_scope(#ejabberd_commands{policy = Policy, name = Name}) ->
|
cmd_scope(#ejabberd_commands{policy = Policy, name = Name}) ->
|
||||||
@ -503,129 +488,6 @@ execute_command2(Name, Arguments, CallerInfo, Version) ->
|
|||||||
throw({error, access_rules_unauthorized})
|
throw({error, access_rules_unauthorized})
|
||||||
end.
|
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) ->
|
do_execute_command(Command, Arguments) ->
|
||||||
Module = Command#ejabberd_commands.module,
|
Module = Command#ejabberd_commands.module,
|
||||||
@ -672,58 +534,6 @@ get_tags_commands(Version) ->
|
|||||||
%% Access verification
|
%% 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;
|
-spec check_auth(ejabberd_commands(), noauth) -> noauth_provided;
|
||||||
(ejabberd_commands(),
|
(ejabberd_commands(),
|
||||||
{binary(), binary(), binary(), boolean()}) ->
|
{binary(), binary(), binary(), boolean()}) ->
|
||||||
@ -746,80 +556,6 @@ check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
|
|||||||
_ -> throw({error, invalid_account_data})
|
_ -> throw({error, invalid_account_data})
|
||||||
end.
|
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() ->
|
||||||
get_exposed_commands(?DEFAULT_VERSION).
|
get_exposed_commands(?DEFAULT_VERSION).
|
||||||
get_exposed_commands(Version) ->
|
get_exposed_commands(Version) ->
|
||||||
@ -854,13 +590,6 @@ expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds) when is_list(L
|
|||||||
[Command|Acc]
|
[Command|Acc]
|
||||||
end, [], L).
|
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) ->
|
is_admin(_Name, admin, _Extra) ->
|
||||||
true;
|
true;
|
||||||
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
|
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
|
||||||
|
@ -69,9 +69,9 @@ list_join_with([El|Tail], M) ->
|
|||||||
end, [El], Tail)).
|
end, [El], Tail)).
|
||||||
|
|
||||||
md_tag(dt, V) ->
|
md_tag(dt, V) ->
|
||||||
[<<"\n">>, V, <<"\n">>];
|
[<<"- ">>, V];
|
||||||
md_tag(dd, V) ->
|
md_tag(dd, V) ->
|
||||||
[<<"\n: ">>, V, <<"\n">>];
|
[<<" : ">>, V, <<"\n">>];
|
||||||
md_tag(li, V) ->
|
md_tag(li, V) ->
|
||||||
[<<"- ">>, V, <<"\n">>];
|
[<<"- ">>, V, <<"\n">>];
|
||||||
md_tag(pre, V) ->
|
md_tag(pre, V) ->
|
||||||
@ -87,14 +87,6 @@ md_tag(strong, V) ->
|
|||||||
md_tag(_, V) ->
|
md_tag(_, V) ->
|
||||||
V.
|
V.
|
||||||
|
|
||||||
|
|
||||||
%% rescode_to_int(ok) ->
|
|
||||||
%% 0;
|
|
||||||
%% rescode_to_int(true) ->
|
|
||||||
%% 0;
|
|
||||||
%% rescode_to_int(_) ->
|
|
||||||
%% 1.
|
|
||||||
|
|
||||||
perl_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
|
perl_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
|
||||||
[?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
|
[?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
|
||||||
perl_gen({Name, string}, Str, _Indent, HTMLOutput) ->
|
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)};
|
{200, json_gen(ResultDesc, Result, Indent, HTMLOutput)};
|
||||||
{{Name0, _}, _} ->
|
{{Name0, _}, _} ->
|
||||||
{200, [Indent, ?OP_L("{"), ?STR_A(Name0), ?OP_L(": "),
|
{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,
|
end,
|
||||||
CodeStr = case Code of
|
CodeStr = case Code of
|
||||||
200 -> <<" 200 OK">>;
|
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(<<"xmlrpc">>, Langs) of true -> ?TAG(li, ?TAG(pre, XML)); _ -> [] end,
|
||||||
case lists:member(<<"json">>, Langs) of true -> ?TAG(li, ?TAG(pre, JSON)); _ -> [] end])];
|
case lists:member(<<"json">>, Langs) of true -> ?TAG(li, ?TAG(pre, JSON)); _ -> [] end])];
|
||||||
true ->
|
true ->
|
||||||
[<<"\n">>, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end,
|
case Langs of
|
||||||
case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end,
|
Val when length(Val) == 0 orelse length(Val) == 1 ->
|
||||||
case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end,
|
[case lists:member(<<"java">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end,
|
||||||
case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end,
|
case lists:member(<<"perl">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end,
|
||||||
<<"{: .code-samples-labels}\n">>,
|
case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
|
||||||
case lists:member(<<"java">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end,
|
case lists:member(<<"json">>, Langs) of true -> [<<"\n">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
|
||||||
case lists:member(<<"perl">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end,
|
<<"\n\n">>];
|
||||||
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">>, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end,
|
||||||
<<"{: .code-samples-tabs}\n\n">>]
|
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.
|
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,
|
gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
|
||||||
args=Args, args_desc=ArgsDesc,
|
args=Args, args_desc=ArgsDesc,
|
||||||
result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
|
result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
|
||||||
LDesc = case LongDesc of
|
try
|
||||||
"" -> Desc;
|
LDesc = case LongDesc of
|
||||||
_ -> LongDesc
|
"" -> Desc;
|
||||||
end,
|
_ -> LongDesc
|
||||||
ArgsText = case ArgsDesc of
|
end,
|
||||||
none ->
|
ArgsText = case ArgsDesc of
|
||||||
[?TAG(ul, "args-list", lists:map(fun({AName, Type}) ->
|
none ->
|
||||||
[?TAG(li, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>,
|
[?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput)
|
||||||
?RAW(io_lib:format("~p", [Type]))])]
|
|| {AName, Type} <- Args])];
|
||||||
end, Args))];
|
_ ->
|
||||||
_ ->
|
[?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput)
|
||||||
[?TAG(dl, "args-list", lists:map(fun({{AName, Type}, ADesc}) ->
|
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])]
|
||||||
[?TAG(dt, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>,
|
end,
|
||||||
?RAW(io_lib:format("~p", [Type]))]),
|
ResultText = case Result of
|
||||||
?TAG(dd, ?RAW(ADesc))]
|
{res,rescode} ->
|
||||||
end, lists:zip(Args, ArgsDesc)))]
|
[?TAG(dl, [gen_param(res, integer,
|
||||||
end,
|
"Status code (0 on success, 1 otherwise)",
|
||||||
ResultText = case ResultDesc of
|
HTMLOutput)])];
|
||||||
none ->
|
{res,restuple} ->
|
||||||
[?RAW(io_lib:format("~p", [Result]))];
|
[?TAG(dl, [gen_param(res, string,
|
||||||
_ ->
|
"Raw result string",
|
||||||
[?TAG(dl, [
|
HTMLOutput)])];
|
||||||
?TAG(dt, io_lib:format("~p", [Result])),
|
{RName, Type} ->
|
||||||
?TAG_R(dd, ResultDesc)])]
|
case ResultDesc of
|
||||||
end,
|
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(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
|
||||||
?TAG(p, ?RAW(LDesc)),
|
?TAG(p, ?RAW(LDesc)),
|
||||||
?TAG(h2, <<"Arguments:">>),
|
?TAG(h2, <<"Arguments:">>), ArgsText,
|
||||||
ArgsText,
|
?TAG(h2, <<"Result:">>), ResultText,
|
||||||
?TAG(h2, <<"Result:">>),
|
?TAG(h2, <<"Examples:">>), gen_calls(Cmd, HTMLOutput, Langs)]
|
||||||
ResultText,
|
catch
|
||||||
?TAG(h2, <<"Examples:">>),
|
_:Ex ->
|
||||||
gen_calls(Cmd, HTMLOutput, Langs)].
|
throw(iolist_to_binary(io_lib:format(
|
||||||
|
<<"Error when generating documentation for command '~p': ~p">>,
|
||||||
|
[Name, Ex])))
|
||||||
|
end.
|
||||||
|
|
||||||
find_commands_definitions() ->
|
find_commands_definitions() ->
|
||||||
case code:lib_dir(ejabberd, ebin) of
|
case code:lib_dir(ejabberd, ebin) of
|
||||||
|
@ -81,8 +81,14 @@ start_link(WS) ->
|
|||||||
gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
|
gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
|
||||||
|
|
||||||
send_xml({http_ws, FsmRef, _IP}, Packet) ->
|
send_xml({http_ws, FsmRef, _IP}, Packet) ->
|
||||||
gen_fsm:sync_send_all_state_event(FsmRef,
|
case catch gen_fsm:sync_send_all_state_event(FsmRef,
|
||||||
{send_xml, Packet}).
|
{send_xml, Packet},
|
||||||
|
15000)
|
||||||
|
of
|
||||||
|
{'EXIT', {timeout, _}} -> {error, timeout};
|
||||||
|
{'EXIT', _} -> {error, einval};
|
||||||
|
Res -> Res
|
||||||
|
end.
|
||||||
|
|
||||||
setopts({http_ws, FsmRef, _IP}, Opts) ->
|
setopts({http_ws, FsmRef, _IP}, Opts) ->
|
||||||
case lists:member({active, once}, Opts) of
|
case lists:member({active, once}, Opts) of
|
||||||
|
@ -109,6 +109,7 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
|||||||
{ok, Socket} ->
|
{ok, Socket} ->
|
||||||
%% Inform my parent that this port was opened succesfully
|
%% Inform my parent that this port was opened succesfully
|
||||||
proc_lib:init_ack({ok, self()}),
|
proc_lib:init_ack({ok, self()}),
|
||||||
|
application:ensure_started(ejabberd),
|
||||||
start_module_sup(Port, Module),
|
start_module_sup(Port, Module),
|
||||||
?INFO_MSG("Start accepting UDP connections at ~s for ~p",
|
?INFO_MSG("Start accepting UDP connections at ~s for ~p",
|
||||||
[format_portip(PortIP), Module]),
|
[format_portip(PortIP), Module]),
|
||||||
@ -134,6 +135,7 @@ init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
|||||||
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
|
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
|
||||||
%% Inform my parent that this port was opened succesfully
|
%% Inform my parent that this port was opened succesfully
|
||||||
proc_lib:init_ack({ok, self()}),
|
proc_lib:init_ack({ok, self()}),
|
||||||
|
application:ensure_started(ejabberd),
|
||||||
start_module_sup(Port, Module),
|
start_module_sup(Port, Module),
|
||||||
?INFO_MSG("Start accepting TCP connections at ~s for ~p",
|
?INFO_MSG("Start accepting TCP connections at ~s for ~p",
|
||||||
[format_portip(PortIP), Module]),
|
[format_portip(PortIP), Module]),
|
||||||
@ -302,7 +304,7 @@ accept(ListenSocket, Module, Opts, Interval) ->
|
|||||||
ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
|
ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
|
||||||
PPort, inet_parse:ntoa(Addr), Port]);
|
PPort, inet_parse:ntoa(Addr), Port]);
|
||||||
_ ->
|
_ ->
|
||||||
ok
|
gen_tcp:close(Socket)
|
||||||
end,
|
end,
|
||||||
accept(ListenSocket, Module, Opts, NewInterval);
|
accept(ListenSocket, Module, Opts, NewInterval);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
|
@ -68,8 +68,6 @@ init([]) ->
|
|||||||
_ -> ok
|
_ -> ok
|
||||||
end,
|
end,
|
||||||
ejabberd:start_app(mnesia, permanent),
|
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(),
|
Schema = read_schema_file(),
|
||||||
{ok, #state{schema = Schema}};
|
{ok, #state{schema = Schema}};
|
||||||
false ->
|
false ->
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
config_reloaded/0,
|
config_reloaded/0,
|
||||||
opt_type/1]).
|
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").
|
-include("xmpp.hrl").
|
||||||
|
|
||||||
@ -96,14 +96,6 @@ get_commands_spec() ->
|
|||||||
policy = restricted,
|
policy = restricted,
|
||||||
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}}
|
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],
|
#ejabberd_commands{name = oauth_revoke_token, tags = [oauth],
|
||||||
desc = "Revoke authorization for a token (only Mnesia)",
|
desc = "Revoke authorization for a token (only Mnesia)",
|
||||||
module = ?MODULE, function = oauth_revoke_token,
|
module = ?MODULE, function = oauth_revoke_token,
|
||||||
@ -143,9 +135,6 @@ oauth_revoke_token(Token) ->
|
|||||||
ok = mnesia:dirty_delete(oauth_token, list_to_binary(Token)),
|
ok = mnesia:dirty_delete(oauth_token, list_to_binary(Token)),
|
||||||
oauth_list_tokens().
|
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() ->
|
config_reloaded() ->
|
||||||
DBMod = get_db_backend(),
|
DBMod = get_db_backend(),
|
||||||
case init_cache(DBMod) of
|
case init_cache(DBMod) of
|
||||||
@ -240,17 +229,6 @@ verify_resowner_scope(_, _, _) ->
|
|||||||
{error, badscope}.
|
{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
|
%% This is callback for oauth tokens generated through the command line. Only open and admin commands are
|
||||||
%% made available.
|
%% made available.
|
||||||
%verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) ->
|
%verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) ->
|
||||||
@ -755,7 +733,7 @@ css() ->
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container > .section {
|
.container > .section {
|
||||||
background: #424A55;
|
background: #424A55;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ init([]) ->
|
|||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun (Pid) -> erlang:monitor(process, Pid) end,
|
fun (Pid) -> erlang:monitor(process, Pid) end,
|
||||||
mnesia:dirty_select(route,
|
mnesia:dirty_select(route,
|
||||||
[{{route, '_', '$1', '_'}, [], ['$1']}])),
|
[{#route{pid = '$1', _ = '_'}, [], ['$1']}])),
|
||||||
{ok, #state{}}.
|
{ok, #state{}}.
|
||||||
|
|
||||||
handle_call(_Request, _From, State) ->
|
handle_call(_Request, _From, State) ->
|
||||||
|
@ -546,25 +546,23 @@ parent_domains(Domain) ->
|
|||||||
|
|
||||||
get_commands_spec() ->
|
get_commands_spec() ->
|
||||||
[#ejabberd_commands{
|
[#ejabberd_commands{
|
||||||
name = incoming_s2s_number,
|
name = incoming_s2s_number, tags = [stats, s2s],
|
||||||
tags = [stats, s2s],
|
|
||||||
desc = "Number of incoming s2s connections on the node",
|
desc = "Number of incoming s2s connections on the node",
|
||||||
policy = admin,
|
policy = admin,
|
||||||
module = ?MODULE, function = incoming_s2s_number,
|
module = ?MODULE, function = incoming_s2s_number,
|
||||||
args = [], result = {s2s_incoming, integer}},
|
args = [], result = {s2s_incoming, integer}},
|
||||||
#ejabberd_commands{
|
#ejabberd_commands{
|
||||||
name = outgoing_s2s_number,
|
name = outgoing_s2s_number, tags = [stats, s2s],
|
||||||
tags = [stats, s2s],
|
|
||||||
desc = "Number of outgoing s2s connections on the node",
|
desc = "Number of outgoing s2s connections on the node",
|
||||||
policy = admin,
|
policy = admin,
|
||||||
module = ?MODULE, function = outgoing_s2s_number,
|
module = ?MODULE, function = outgoing_s2s_number,
|
||||||
args = [], result = {s2s_outgoing, integer}},
|
args = [], result = {s2s_outgoing, integer}},
|
||||||
#ejabberd_commands{name = stop_all_connections,
|
#ejabberd_commands{
|
||||||
tags = [s2s],
|
name = stop_all_connections, tags = [s2s],
|
||||||
desc = "Stop all outgoing and incoming connections",
|
desc = "Stop all outgoing and incoming connections",
|
||||||
policy = admin,
|
policy = admin,
|
||||||
module = ?MODULE, function = stop_all_connections,
|
module = ?MODULE, function = stop_all_connections,
|
||||||
args = [], result = {res, rescode}}].
|
args = [], result = {res, rescode}}].
|
||||||
|
|
||||||
%% TODO Move those stats commands to ejabberd stats command ?
|
%% TODO Move those stats commands to ejabberd stats command ?
|
||||||
incoming_s2s_number() ->
|
incoming_s2s_number() ->
|
||||||
|
@ -57,7 +57,10 @@
|
|||||||
start(SockData, Opts) ->
|
start(SockData, Opts) ->
|
||||||
case proplists:get_value(supervisor, Opts, true) of
|
case proplists:get_value(supervisor, Opts, true) of
|
||||||
true ->
|
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],
|
xmpp_stream_in:start(?MODULE, [SockData, Opts],
|
||||||
ejabberd_config:fsm_limit_opts(Opts))
|
ejabberd_config:fsm_limit_opts(Opts))
|
||||||
|
@ -54,10 +54,13 @@
|
|||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
start(From, To, Opts) ->
|
start(From, To, Opts) ->
|
||||||
case proplists:get_value(supervisor, Opts, true) of
|
case proplists:get_value(supervisor, Opts, true) of
|
||||||
true ->
|
true ->
|
||||||
supervisor:start_child(ejabberd_s2s_out_sup,
|
case supervisor:start_child(ejabberd_s2s_out_sup,
|
||||||
[From, To, Opts]);
|
[From, To, Opts]) of
|
||||||
_ ->
|
{ok, undefined} -> ignore;
|
||||||
|
Res -> Res
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
xmpp_stream_out:start(?MODULE, [ejabberd_socket, From, To, Opts],
|
xmpp_stream_out:start(?MODULE, [ejabberd_socket, From, To, Opts],
|
||||||
ejabberd_config:fsm_limit_opts([]))
|
ejabberd_config:fsm_limit_opts([]))
|
||||||
end.
|
end.
|
||||||
|
@ -957,30 +957,36 @@ cache_nodes(Mod, LServer) ->
|
|||||||
%%% ejabberd commands
|
%%% ejabberd commands
|
||||||
|
|
||||||
get_commands_spec() ->
|
get_commands_spec() ->
|
||||||
[#ejabberd_commands{name = connected_users,
|
[#ejabberd_commands{name = connected_users, tags = [session],
|
||||||
tags = [session],
|
|
||||||
desc = "List all established sessions",
|
desc = "List all established sessions",
|
||||||
policy = admin,
|
policy = admin,
|
||||||
module = ?MODULE, function = connected_users, args = [],
|
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}}}},
|
result = {connected_users, {list, {sessions, string}}}},
|
||||||
#ejabberd_commands{name = connected_users_number,
|
#ejabberd_commands{name = connected_users_number, tags = [session, stats],
|
||||||
tags = [session, stats],
|
|
||||||
desc = "Get the number of established sessions",
|
desc = "Get the number of established sessions",
|
||||||
policy = admin,
|
policy = admin,
|
||||||
module = ?MODULE, function = connected_users_number,
|
module = ?MODULE, function = connected_users_number,
|
||||||
|
result_example = 2,
|
||||||
args = [], result = {num_sessions, integer}},
|
args = [], result = {num_sessions, integer}},
|
||||||
#ejabberd_commands{name = user_resources,
|
#ejabberd_commands{name = user_resources, tags = [session],
|
||||||
tags = [session],
|
|
||||||
desc = "List user's connected resources",
|
desc = "List user's connected resources",
|
||||||
policy = user,
|
policy = user,
|
||||||
module = ?MODULE, function = user_resources,
|
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}}}},
|
result = {resources, {list, {resource, string}}}},
|
||||||
#ejabberd_commands{name = kick_user,
|
#ejabberd_commands{name = kick_user, tags = [session],
|
||||||
tags = [session],
|
|
||||||
desc = "Disconnect user's active sessions",
|
desc = "Disconnect user's active sessions",
|
||||||
module = ?MODULE, function = kick_user,
|
module = ?MODULE, function = kick_user,
|
||||||
args = [{user, binary}, {host, binary}],
|
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}}].
|
result = {num_resources, integer}}].
|
||||||
|
|
||||||
-spec connected_users() -> [binary()].
|
-spec connected_users() -> [binary()].
|
||||||
|
@ -87,7 +87,7 @@
|
|||||||
%% API
|
%% API
|
||||||
%%====================================================================
|
%%====================================================================
|
||||||
-spec start(atom(), sockmod(), socket(), [proplists:property()])
|
-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) ->
|
start(Module, SockMod, Socket, Opts) ->
|
||||||
case Module:socket_type() of
|
case Module:socket_type() of
|
||||||
independent -> {ok, independent};
|
independent -> {ok, independent};
|
||||||
|
@ -41,6 +41,12 @@ init([]) ->
|
|||||||
brutal_kill,
|
brutal_kill,
|
||||||
worker,
|
worker,
|
||||||
[ejabberd_hooks]},
|
[ejabberd_hooks]},
|
||||||
|
Cluster = {ejabberd_cluster,
|
||||||
|
{ejabberd_cluster, start_link, []},
|
||||||
|
permanent,
|
||||||
|
5000,
|
||||||
|
worker,
|
||||||
|
[ejabberd_cluster]},
|
||||||
SystemMonitor =
|
SystemMonitor =
|
||||||
{ejabberd_system_monitor,
|
{ejabberd_system_monitor,
|
||||||
{ejabberd_system_monitor, start_link, []},
|
{ejabberd_system_monitor, start_link, []},
|
||||||
@ -152,6 +158,7 @@ init([]) ->
|
|||||||
permanent, 5000, worker, [ejabberd_pkix]},
|
permanent, 5000, worker, [ejabberd_pkix]},
|
||||||
{ok, {{one_for_one, 10, 1},
|
{ok, {{one_for_one, 10, 1},
|
||||||
[Hooks,
|
[Hooks,
|
||||||
|
Cluster,
|
||||||
CyrSASL,
|
CyrSASL,
|
||||||
Translation,
|
Translation,
|
||||||
AccessPerms,
|
AccessPerms,
|
||||||
|
@ -80,16 +80,18 @@ code_change(_OldVsn, State, _Extra) ->
|
|||||||
get_commands_spec() ->
|
get_commands_spec() ->
|
||||||
[#ejabberd_commands{name = modules_update_specs,
|
[#ejabberd_commands{name = modules_update_specs,
|
||||||
tags = [admin,modules],
|
tags = [admin,modules],
|
||||||
desc = "",
|
desc = "Update the module source code from Git",
|
||||||
longdesc = "",
|
longdesc = "A connection to Internet is required",
|
||||||
module = ?MODULE, function = update,
|
module = ?MODULE, function = update,
|
||||||
args = [],
|
args = [],
|
||||||
result = {res, integer}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = modules_available,
|
#ejabberd_commands{name = modules_available,
|
||||||
tags = [admin,modules],
|
tags = [admin,modules],
|
||||||
desc = "",
|
desc = "List the contributed modules available to install",
|
||||||
longdesc = "",
|
|
||||||
module = ?MODULE, function = available_command,
|
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 = [],
|
args = [],
|
||||||
result = {modules, {list,
|
result = {modules, {list,
|
||||||
{module, {tuple,
|
{module, {tuple,
|
||||||
@ -97,9 +99,11 @@ get_commands_spec() ->
|
|||||||
{summary, string}]}}}}},
|
{summary, string}]}}}}},
|
||||||
#ejabberd_commands{name = modules_installed,
|
#ejabberd_commands{name = modules_installed,
|
||||||
tags = [admin,modules],
|
tags = [admin,modules],
|
||||||
desc = "",
|
desc = "List the contributed modules already installed",
|
||||||
longdesc = "",
|
|
||||||
module = ?MODULE, function = installed_command,
|
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 = [],
|
args = [],
|
||||||
result = {modules, {list,
|
result = {modules, {list,
|
||||||
{module, {tuple,
|
{module, {tuple,
|
||||||
@ -107,32 +111,37 @@ get_commands_spec() ->
|
|||||||
{summary, string}]}}}}},
|
{summary, string}]}}}}},
|
||||||
#ejabberd_commands{name = module_install,
|
#ejabberd_commands{name = module_install,
|
||||||
tags = [admin,modules],
|
tags = [admin,modules],
|
||||||
desc = "",
|
desc = "Compile, install and start an available contributed module",
|
||||||
longdesc = "",
|
|
||||||
module = ?MODULE, function = install,
|
module = ?MODULE, function = install,
|
||||||
|
args_desc = ["Module name"],
|
||||||
|
args_example = [<<"mod_rest">>],
|
||||||
args = [{module, binary}],
|
args = [{module, binary}],
|
||||||
result = {res, integer}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = module_uninstall,
|
#ejabberd_commands{name = module_uninstall,
|
||||||
tags = [admin,modules],
|
tags = [admin,modules],
|
||||||
desc = "",
|
desc = "Uninstall a contributed module",
|
||||||
longdesc = "",
|
|
||||||
module = ?MODULE, function = uninstall,
|
module = ?MODULE, function = uninstall,
|
||||||
|
args_desc = ["Module name"],
|
||||||
|
args_example = [<<"mod_rest">>],
|
||||||
args = [{module, binary}],
|
args = [{module, binary}],
|
||||||
result = {res, integer}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = module_upgrade,
|
#ejabberd_commands{name = module_upgrade,
|
||||||
tags = [admin,modules],
|
tags = [admin,modules],
|
||||||
desc = "",
|
desc = "Upgrade the running code of an installed module",
|
||||||
longdesc = "",
|
longdesc = "In practice, this uninstalls and installs the module",
|
||||||
module = ?MODULE, function = upgrade,
|
module = ?MODULE, function = upgrade,
|
||||||
|
args_desc = ["Module name"],
|
||||||
|
args_example = [<<"mod_rest">>],
|
||||||
args = [{module, binary}],
|
args = [{module, binary}],
|
||||||
result = {res, integer}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = module_check,
|
#ejabberd_commands{name = module_check,
|
||||||
tags = [admin,modules],
|
tags = [admin,modules],
|
||||||
desc = "",
|
desc = "Check the contributed module repository compliance",
|
||||||
longdesc = "",
|
|
||||||
module = ?MODULE, function = check,
|
module = ?MODULE, function = check,
|
||||||
|
args_desc = ["Module name"],
|
||||||
|
args_example = [<<"mod_rest">>],
|
||||||
args = [{module, binary}],
|
args = [{module, binary}],
|
||||||
result = {res, integer}}
|
result = {res, rescode}}
|
||||||
].
|
].
|
||||||
%% -- public modules functions
|
%% -- public modules functions
|
||||||
|
|
||||||
|
@ -303,6 +303,9 @@ get_commands_spec() ->
|
|||||||
desc = "List of users logged in host with their statuses",
|
desc = "List of users logged in host with their statuses",
|
||||||
module = ?MODULE, function = status_list,
|
module = ?MODULE, function = status_list,
|
||||||
args = [{host, binary}, {status, binary}],
|
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,
|
result = {users, {list,
|
||||||
{userstatus, {tuple, [
|
{userstatus, {tuple, [
|
||||||
{user, string},
|
{user, string},
|
||||||
@ -316,6 +319,9 @@ get_commands_spec() ->
|
|||||||
desc = "List of logged users with this status",
|
desc = "List of logged users with this status",
|
||||||
module = ?MODULE, function = status_list,
|
module = ?MODULE, function = status_list,
|
||||||
args = [{status, binary}],
|
args = [{status, binary}],
|
||||||
|
args_example = [<<"dnd">>],
|
||||||
|
args_desc = ["Status type to check"],
|
||||||
|
result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}],
|
||||||
result = {users, {list,
|
result = {users, {list,
|
||||||
{userstatus, {tuple, [
|
{userstatus, {tuple, [
|
||||||
{user, string},
|
{user, string},
|
||||||
@ -330,6 +336,8 @@ get_commands_spec() ->
|
|||||||
desc = "List all established sessions and their information",
|
desc = "List all established sessions and their information",
|
||||||
module = ?MODULE, function = connected_users_info,
|
module = ?MODULE, function = connected_users_info,
|
||||||
args = [],
|
args = [],
|
||||||
|
result_example = [{"user1@myserver.com/tka", "c2s", "127.0.0.1",
|
||||||
|
40092, 8, "ejabberd@localhost", 28}],
|
||||||
result = {connected_users_info,
|
result = {connected_users_info,
|
||||||
{list,
|
{list,
|
||||||
{sessions, {tuple,
|
{sessions, {tuple,
|
||||||
@ -346,6 +354,9 @@ get_commands_spec() ->
|
|||||||
tags = [session],
|
tags = [session],
|
||||||
desc = "Get the list of established sessions in a vhost",
|
desc = "Get the list of established sessions in a vhost",
|
||||||
module = ?MODULE, function = connected_users_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}],
|
args = [{host, binary}],
|
||||||
result = {connected_users_vhost, {list, {sessions, string}}}},
|
result = {connected_users_vhost, {list, {sessions, string}}}},
|
||||||
#ejabberd_commands{name = user_sessions_info,
|
#ejabberd_commands{name = user_sessions_info,
|
||||||
@ -353,6 +364,10 @@ get_commands_spec() ->
|
|||||||
desc = "Get information about all sessions of a user",
|
desc = "Get information about all sessions of a user",
|
||||||
module = ?MODULE, function = user_sessions_info,
|
module = ?MODULE, function = user_sessions_info,
|
||||||
args = [{user, binary}, {host, binary}],
|
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,
|
result = {sessions_info,
|
||||||
{list,
|
{list,
|
||||||
{session, {tuple,
|
{session, {tuple,
|
||||||
@ -385,6 +400,9 @@ get_commands_spec() ->
|
|||||||
"defined by the user client.",
|
"defined by the user client.",
|
||||||
module = ?MODULE, function = get_presence,
|
module = ?MODULE, function = get_presence,
|
||||||
args = [{user, binary}, {server, binary}],
|
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 =
|
result =
|
||||||
{presence,
|
{presence,
|
||||||
{tuple,
|
{tuple,
|
||||||
@ -398,24 +416,40 @@ get_commands_spec() ->
|
|||||||
{resource, binary}, {type, binary},
|
{resource, binary}, {type, binary},
|
||||||
{show, binary}, {status, binary},
|
{show, binary}, {status, binary},
|
||||||
{priority, 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}},
|
result = {res, rescode}},
|
||||||
|
|
||||||
#ejabberd_commands{name = set_nickname, tags = [vcard],
|
#ejabberd_commands{name = set_nickname, tags = [vcard],
|
||||||
desc = "Set nickname in a user's vCard",
|
desc = "Set nickname in a user's vCard",
|
||||||
module = ?MODULE, function = set_nickname,
|
module = ?MODULE, function = set_nickname,
|
||||||
args = [{user, binary}, {host, binary}, {nickname, binary}],
|
args = [{user, binary}, {host, binary}, {nickname, binary}],
|
||||||
|
args_example = [<<"user1">>,<<"myserver.com">>,<<"User 1">>],
|
||||||
|
args_desc = ["User name", "Server name", "Nickname"],
|
||||||
result = {res, rescode}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = get_vcard, tags = [vcard],
|
#ejabberd_commands{name = get_vcard, tags = [vcard],
|
||||||
desc = "Get content from a vCard field",
|
desc = "Get content from a vCard field",
|
||||||
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
|
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
|
||||||
module = ?MODULE, function = get_vcard,
|
module = ?MODULE, function = get_vcard,
|
||||||
args = [{user, binary}, {host, binary}, {name, binary}],
|
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}},
|
result = {content, string}},
|
||||||
#ejabberd_commands{name = get_vcard2, tags = [vcard],
|
#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,
|
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
|
||||||
module = ?MODULE, function = get_vcard,
|
module = ?MODULE, function = get_vcard,
|
||||||
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}],
|
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}},
|
result = {content, string}},
|
||||||
#ejabberd_commands{name = get_vcard2_multi, tags = [vcard],
|
#ejabberd_commands{name = get_vcard2_multi, tags = [vcard],
|
||||||
desc = "Get multiple contents from a vCard field",
|
desc = "Get multiple contents from a vCard field",
|
||||||
@ -429,12 +463,16 @@ get_commands_spec() ->
|
|||||||
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
|
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
|
||||||
module = ?MODULE, function = set_vcard,
|
module = ?MODULE, function = set_vcard,
|
||||||
args = [{user, binary}, {host, binary}, {name, binary}, {content, binary}],
|
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}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = set_vcard2, tags = [vcard],
|
#ejabberd_commands{name = set_vcard2, tags = [vcard],
|
||||||
desc = "Set content in a vCard subfield",
|
desc = "Set content in a vCard subfield",
|
||||||
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
|
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
|
||||||
module = ?MODULE, function = set_vcard,
|
module = ?MODULE, function = set_vcard,
|
||||||
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {content, binary}],
|
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}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = set_vcard2_multi, tags = [vcard],
|
#ejabberd_commands{name = set_vcard2_multi, tags = [vcard],
|
||||||
desc = "Set multiple contents in a vCard subfield",
|
desc = "Set multiple contents in a vCard subfield",
|
||||||
@ -451,6 +489,10 @@ get_commands_spec() ->
|
|||||||
{user, binary}, {server, binary},
|
{user, binary}, {server, binary},
|
||||||
{nick, binary}, {group, binary},
|
{nick, binary}, {group, binary},
|
||||||
{subs, 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}},
|
result = {res, rescode}},
|
||||||
%%{"", "subs= none, from, to or both"},
|
%%{"", "subs= none, from, to or both"},
|
||||||
%%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
|
%%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
|
||||||
@ -460,6 +502,8 @@ get_commands_spec() ->
|
|||||||
module = ?MODULE, function = delete_rosteritem,
|
module = ?MODULE, function = delete_rosteritem,
|
||||||
args = [{localuser, binary}, {localserver, binary},
|
args = [{localuser, binary}, {localserver, binary},
|
||||||
{user, binary}, {server, 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}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = process_rosteritems, tags = [roster],
|
#ejabberd_commands{name = process_rosteritems, tags = [roster],
|
||||||
desc = "List/delete rosteritems that match filter (only Mnesia)",
|
desc = "List/delete rosteritems that match filter (only Mnesia)",
|
||||||
@ -514,8 +558,14 @@ get_commands_spec() ->
|
|||||||
]}}}}},
|
]}}}}},
|
||||||
#ejabberd_commands{name = push_roster, tags = [roster],
|
#ejabberd_commands{name = push_roster, tags = [roster],
|
||||||
desc = "Push template roster from file to a user",
|
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,
|
module = ?MODULE, function = push_roster,
|
||||||
args = [{file, binary}, {user, binary}, {host, binary}],
|
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}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = push_roster_all, tags = [roster],
|
#ejabberd_commands{name = push_roster_all, tags = [roster],
|
||||||
desc = "Push template roster from file to all those users",
|
desc = "Push template roster from file to all those users",
|
||||||
@ -525,11 +575,15 @@ get_commands_spec() ->
|
|||||||
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
|
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
|
||||||
module = ?MODULE, function = push_roster_all,
|
module = ?MODULE, function = push_roster_all,
|
||||||
args = [{file, binary}],
|
args = [{file, binary}],
|
||||||
|
args_example = [<<"/home/ejabberd/roster.txt">>],
|
||||||
|
args_desc = ["File path"],
|
||||||
result = {res, rescode}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = push_alltoall, tags = [roster],
|
#ejabberd_commands{name = push_alltoall, tags = [roster],
|
||||||
desc = "Add all the users to all the users of Host in Group",
|
desc = "Add all the users to all the users of Host in Group",
|
||||||
module = ?MODULE, function = push_alltoall,
|
module = ?MODULE, function = push_alltoall,
|
||||||
args = [{host, binary}, {group, binary}],
|
args = [{host, binary}, {group, binary}],
|
||||||
|
args_example = [<<"myserver.com">>,<<"Everybody">>],
|
||||||
|
args_desc = ["Server name", "Group name"],
|
||||||
result = {res, rescode}},
|
result = {res, rescode}},
|
||||||
|
|
||||||
#ejabberd_commands{name = get_last, tags = [last],
|
#ejabberd_commands{name = get_last, tags = [last],
|
||||||
@ -538,6 +592,10 @@ get_commands_spec() ->
|
|||||||
"2017-02-23T22:25:28.063062Z ONLINE",
|
"2017-02-23T22:25:28.063062Z ONLINE",
|
||||||
module = ?MODULE, function = get_last,
|
module = ?MODULE, function = get_last,
|
||||||
args = [{user, binary}, {host, binary}],
|
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,
|
result = {last_activity,
|
||||||
{tuple, [{timestamp, string},
|
{tuple, [{timestamp, string},
|
||||||
{status, string}
|
{status, string}
|
||||||
@ -548,17 +606,24 @@ get_commands_spec() ->
|
|||||||
"1970-01-01 00:00:00 UTC, for example: date +%s",
|
"1970-01-01 00:00:00 UTC, for example: date +%s",
|
||||||
module = mod_last, function = store_last_info,
|
module = mod_last, function = store_last_info,
|
||||||
args = [{user, binary}, {host, binary}, {timestamp, integer}, {status, binary}],
|
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}},
|
result = {res, rescode}},
|
||||||
|
|
||||||
#ejabberd_commands{name = private_get, tags = [private],
|
#ejabberd_commands{name = private_get, tags = [private],
|
||||||
desc = "Get some information from a user private storage",
|
desc = "Get some information from a user private storage",
|
||||||
module = ?MODULE, function = private_get,
|
module = ?MODULE, function = private_get,
|
||||||
args = [{user, binary}, {host, binary}, {element, binary}, {ns, binary}],
|
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}},
|
result = {res, string}},
|
||||||
#ejabberd_commands{name = private_set, tags = [private],
|
#ejabberd_commands{name = private_set, tags = [private],
|
||||||
desc = "Set to the user private storage",
|
desc = "Set to the user private storage",
|
||||||
module = ?MODULE, function = private_set,
|
module = ?MODULE, function = private_set,
|
||||||
args = [{user, binary}, {host, binary}, {element, binary}],
|
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}},
|
result = {res, rescode}},
|
||||||
|
|
||||||
#ejabberd_commands{name = srg_create, tags = [shared_roster_group],
|
#ejabberd_commands{name = srg_create, tags = [shared_roster_group],
|
||||||
@ -568,41 +633,63 @@ get_commands_spec() ->
|
|||||||
"put \\ \" around the argument and\nseparate the "
|
"put \\ \" around the argument and\nseparate the "
|
||||||
"identifiers with \\ \\ n\n"
|
"identifiers with \\ \\ n\n"
|
||||||
"For example:\n"
|
"For example:\n"
|
||||||
" ejabberdctl srg_create group3 localhost "
|
" ejabberdctl srg_create group3 myserver.com "
|
||||||
"name desc \\\"group1\\\\ngroup2\\\"",
|
"name desc \\\"group1\\\\ngroup2\\\"",
|
||||||
module = ?MODULE, function = srg_create,
|
module = ?MODULE, function = srg_create,
|
||||||
args = [{group, binary}, {host, binary},
|
args = [{group, binary}, {host, binary},
|
||||||
{name, binary}, {description, binary}, {display, 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}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = srg_delete, tags = [shared_roster_group],
|
#ejabberd_commands{name = srg_delete, tags = [shared_roster_group],
|
||||||
desc = "Delete a Shared Roster Group",
|
desc = "Delete a Shared Roster Group",
|
||||||
module = ?MODULE, function = srg_delete,
|
module = ?MODULE, function = srg_delete,
|
||||||
args = [{group, binary}, {host, binary}],
|
args = [{group, binary}, {host, binary}],
|
||||||
|
args_example = [<<"group3">>, <<"myserver.com">>],
|
||||||
|
args_desc = ["Group identifier", "Group server name"],
|
||||||
result = {res, rescode}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = srg_list, tags = [shared_roster_group],
|
#ejabberd_commands{name = srg_list, tags = [shared_roster_group],
|
||||||
desc = "List the Shared Roster Groups in Host",
|
desc = "List the Shared Roster Groups in Host",
|
||||||
module = ?MODULE, function = srg_list,
|
module = ?MODULE, function = srg_list,
|
||||||
args = [{host, binary}],
|
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}}}},
|
result = {groups, {list, {id, string}}}},
|
||||||
#ejabberd_commands{name = srg_get_info, tags = [shared_roster_group],
|
#ejabberd_commands{name = srg_get_info, tags = [shared_roster_group],
|
||||||
desc = "Get info of a Shared Roster Group",
|
desc = "Get info of a Shared Roster Group",
|
||||||
module = ?MODULE, function = srg_get_info,
|
module = ?MODULE, function = srg_get_info,
|
||||||
args = [{group, binary}, {host, binary}],
|
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}]}}}}},
|
result = {informations, {list, {information, {tuple, [{key, string}, {value, string}]}}}}},
|
||||||
#ejabberd_commands{name = srg_get_members, tags = [shared_roster_group],
|
#ejabberd_commands{name = srg_get_members, tags = [shared_roster_group],
|
||||||
desc = "Get members of a Shared Roster Group",
|
desc = "Get members of a Shared Roster Group",
|
||||||
module = ?MODULE, function = srg_get_members,
|
module = ?MODULE, function = srg_get_members,
|
||||||
args = [{group, binary}, {host, binary}],
|
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}}}},
|
result = {members, {list, {member, string}}}},
|
||||||
#ejabberd_commands{name = srg_user_add, tags = [shared_roster_group],
|
#ejabberd_commands{name = srg_user_add, tags = [shared_roster_group],
|
||||||
desc = "Add the JID user@host to the Shared Roster Group",
|
desc = "Add the JID user@host to the Shared Roster Group",
|
||||||
module = ?MODULE, function = srg_user_add,
|
module = ?MODULE, function = srg_user_add,
|
||||||
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
|
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}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = srg_user_del, tags = [shared_roster_group],
|
#ejabberd_commands{name = srg_user_del, tags = [shared_roster_group],
|
||||||
desc = "Delete this JID user@host from the Shared Roster Group",
|
desc = "Delete this JID user@host from the Shared Roster Group",
|
||||||
module = ?MODULE, function = srg_user_del,
|
module = ?MODULE, function = srg_user_del,
|
||||||
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
|
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}},
|
result = {res, rescode}},
|
||||||
|
|
||||||
#ejabberd_commands{name = get_offline_count,
|
#ejabberd_commands{name = get_offline_count,
|
||||||
@ -611,27 +698,42 @@ get_commands_spec() ->
|
|||||||
policy = user,
|
policy = user,
|
||||||
module = mod_offline, function = count_offline_messages,
|
module = mod_offline, function = count_offline_messages,
|
||||||
args = [],
|
args = [],
|
||||||
|
result_example = 5,
|
||||||
|
result_desc = "Number",
|
||||||
result = {value, integer}},
|
result = {value, integer}},
|
||||||
#ejabberd_commands{name = send_message, tags = [stanza],
|
#ejabberd_commands{name = send_message, tags = [stanza],
|
||||||
desc = "Send a message to a local or remote bare of full JID",
|
desc = "Send a message to a local or remote bare of full JID",
|
||||||
module = ?MODULE, function = send_message,
|
module = ?MODULE, function = send_message,
|
||||||
args = [{type, binary}, {from, binary}, {to, binary},
|
args = [{type, binary}, {from, binary}, {to, binary},
|
||||||
{subject, binary}, {body, 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}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = send_stanza_c2s, tags = [stanza],
|
#ejabberd_commands{name = send_stanza_c2s, tags = [stanza],
|
||||||
desc = "Send a stanza as if sent from a c2s session",
|
desc = "Send a stanza as if sent from a c2s session",
|
||||||
module = ?MODULE, function = send_stanza_c2s,
|
module = ?MODULE, function = send_stanza_c2s,
|
||||||
args = [{user, binary}, {host, binary}, {resource, binary}, {stanza, binary}],
|
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}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = send_stanza, tags = [stanza],
|
#ejabberd_commands{name = send_stanza, tags = [stanza],
|
||||||
desc = "Send a stanza; provide From JID and valid To JID",
|
desc = "Send a stanza; provide From JID and valid To JID",
|
||||||
module = ?MODULE, function = send_stanza,
|
module = ?MODULE, function = send_stanza,
|
||||||
args = [{from, binary}, {to, binary}, {stanza, binary}],
|
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}},
|
result = {res, rescode}},
|
||||||
#ejabberd_commands{name = privacy_set, tags = [stanza],
|
#ejabberd_commands{name = privacy_set, tags = [stanza],
|
||||||
desc = "Send a IQ set privacy stanza for a local account",
|
desc = "Send a IQ set privacy stanza for a local account",
|
||||||
module = ?MODULE, function = privacy_set,
|
module = ?MODULE, function = privacy_set,
|
||||||
args = [{user, binary}, {host, binary}, {xmlquery, binary}],
|
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}},
|
result = {res, rescode}},
|
||||||
|
|
||||||
#ejabberd_commands{name = stats, tags = [stats],
|
#ejabberd_commands{name = stats, tags = [stats],
|
||||||
@ -639,12 +741,20 @@ get_commands_spec() ->
|
|||||||
policy = admin,
|
policy = admin,
|
||||||
module = ?MODULE, function = stats,
|
module = ?MODULE, function = stats,
|
||||||
args = [{name, binary}],
|
args = [{name, binary}],
|
||||||
|
args_example = [<<"registeredusers">>],
|
||||||
|
args_desc = ["Statistic name"],
|
||||||
|
result_example = 6,
|
||||||
|
result_desc = "Integer statistic value",
|
||||||
result = {stat, integer}},
|
result = {stat, integer}},
|
||||||
#ejabberd_commands{name = stats_host, tags = [stats],
|
#ejabberd_commands{name = stats_host, tags = [stats],
|
||||||
desc = "Get statistical value for this host: registeredusers onlineusers",
|
desc = "Get statistical value for this host: registeredusers onlineusers",
|
||||||
policy = admin,
|
policy = admin,
|
||||||
module = ?MODULE, function = stats,
|
module = ?MODULE, function = stats,
|
||||||
args = [{name, binary}, {host, binary}],
|
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}}
|
result = {stat, integer}}
|
||||||
].
|
].
|
||||||
|
|
||||||
@ -952,7 +1062,7 @@ connected_users_info() ->
|
|||||||
|
|
||||||
connected_users_vhost(Host) ->
|
connected_users_vhost(Host) ->
|
||||||
USRs = ejabberd_sm:get_vh_session_list(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
|
%% Code copied from ejabberd_sm.erl and customized
|
||||||
dirty_get_sessions_list2() ->
|
dirty_get_sessions_list2() ->
|
||||||
@ -1002,20 +1112,16 @@ set_presence(User, Host, Resource, Type, Show, Status, Priority0) ->
|
|||||||
Priority = if is_integer(Priority0) -> Priority0;
|
Priority = if is_integer(Priority0) -> Priority0;
|
||||||
true -> binary_to_integer(Priority0)
|
true -> binary_to_integer(Priority0)
|
||||||
end,
|
end,
|
||||||
case ejabberd_sm:get_session_pid(User, Host, Resource) of
|
Pres = #presence{
|
||||||
none ->
|
from = jid:make(User, Host, Resource),
|
||||||
error;
|
to = jid:make(User, Host),
|
||||||
Pid ->
|
type = misc:binary_to_atom(Type),
|
||||||
From = jid:make(User, Host, Resource),
|
status = xmpp:mk_text(Status),
|
||||||
To = jid:make(User, Host),
|
show = misc:binary_to_atom(Show),
|
||||||
Presence = #presence{from = From, to = To,
|
priority = Priority,
|
||||||
type = misc:binary_to_atom(Type),
|
sub_els = []},
|
||||||
show = misc:binary_to_atom(Show),
|
Ref = ejabberd_sm:get_session_pid(User, Host, Resource),
|
||||||
status = xmpp:mk_text(Status),
|
ejabberd_c2s:set_presence(Ref, Pres).
|
||||||
priority = Priority},
|
|
||||||
Pid ! {route, Presence},
|
|
||||||
ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
user_sessions_info(User, Host) ->
|
user_sessions_info(User, Host) ->
|
||||||
CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
|
CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
|
||||||
@ -1401,11 +1507,11 @@ srg_get_members(Group, Host) ->
|
|||||||
|| {MUser, MServer} <- Members].
|
|| {MUser, MServer} <- Members].
|
||||||
|
|
||||||
srg_user_add(User, Host, Group, GroupHost) ->
|
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.
|
ok.
|
||||||
|
|
||||||
srg_user_del(User, Host, Group, GroupHost) ->
|
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.
|
ok.
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,9 +58,12 @@ filter_packet({#message{} = Msg, State} = Acc) ->
|
|||||||
LFrom = jid:tolower(From),
|
LFrom = jid:tolower(From),
|
||||||
LBFrom = jid:remove_resource(LFrom),
|
LBFrom = jid:remove_resource(LFrom),
|
||||||
#{pres_a := PresA, jid := JID, lserver := LServer} = State,
|
#{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
|
case (Msg#message.body == [] andalso
|
||||||
Msg#message.subject == [])
|
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(LFrom, PresA)
|
||||||
orelse (?SETS):is_element(LBFrom, PresA)
|
orelse (?SETS):is_element(LBFrom, PresA)
|
||||||
orelse sets_bare_member(LBFrom, PresA) of
|
orelse sets_bare_member(LBFrom, PresA) of
|
||||||
@ -128,4 +131,6 @@ mod_opt_type(drop) ->
|
|||||||
fun (B) when is_boolean(B) -> B end;
|
fun (B) when is_boolean(B) -> B end;
|
||||||
mod_opt_type(log) ->
|
mod_opt_type(log) ->
|
||||||
fun (B) when is_boolean(B) -> B end;
|
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].
|
||||||
|
@ -199,8 +199,8 @@ c2s_authenticated_packet(C2SState, _) ->
|
|||||||
C2SState.
|
C2SState.
|
||||||
|
|
||||||
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
|
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
|
||||||
c2s_copy_session(C2SState, #{csi_state := State, csi_queue := Q}) ->
|
c2s_copy_session(C2SState, #{csi_queue := Q}) ->
|
||||||
C2SState#{csi_state => State, csi_queue => Q};
|
C2SState#{csi_queue => Q};
|
||||||
c2s_copy_session(C2SState, _) ->
|
c2s_copy_session(C2SState, _) ->
|
||||||
C2SState.
|
C2SState.
|
||||||
|
|
||||||
|
@ -1008,6 +1008,9 @@ get_commands_spec() ->
|
|||||||
longdesc = "Valid message TYPEs: "
|
longdesc = "Valid message TYPEs: "
|
||||||
"\"chat\", \"groupchat\", \"all\".",
|
"\"chat\", \"groupchat\", \"all\".",
|
||||||
module = ?MODULE, function = delete_old_messages,
|
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}],
|
args = [{type, binary}, {days, integer}],
|
||||||
result = {res, rescode}}].
|
result = {res, rescode}}].
|
||||||
|
|
||||||
|
@ -198,15 +198,14 @@ export(_Server) ->
|
|||||||
[]
|
[]
|
||||||
end},
|
end},
|
||||||
{archive_msg,
|
{archive_msg,
|
||||||
fun(Host, #archive_msg{us ={_LUser, LServer},
|
fun(Host, #archive_msg{us ={LUser, LServer},
|
||||||
id = _ID, timestamp = TS, peer = Peer,
|
id = _ID, timestamp = TS, peer = Peer,
|
||||||
bare_peer = {PUser, PServer, <<>>},
|
|
||||||
type = Type, nick = Nick, packet = Pkt})
|
type = Type, nick = Nick, packet = Pkt})
|
||||||
when LServer == Host ->
|
when LServer == Host ->
|
||||||
TStmp = now_to_usec(TS),
|
TStmp = now_to_usec(TS),
|
||||||
SUser = case Type of
|
SUser = case Type of
|
||||||
chat -> PUser;
|
chat -> LUser;
|
||||||
groupchat -> jid:encode({PUser, PServer, <<>>})
|
groupchat -> jid:encode({LUser, LServer, <<>>})
|
||||||
end,
|
end,
|
||||||
BarePeer = jid:encode(jid:tolower(jid:remove_resource(Peer))),
|
BarePeer = jid:encode(jid:tolower(jid:remove_resource(Peer))),
|
||||||
LPeer = jid:encode(jid:tolower(Peer)),
|
LPeer = jid:encode(jid:tolower(Peer)),
|
||||||
|
@ -208,8 +208,11 @@ get_commands_spec() ->
|
|||||||
module = ?MODULE, function = send_direct_invitation,
|
module = ?MODULE, function = send_direct_invitation,
|
||||||
args_desc = ["Room name", "MUC service", "Password, or none",
|
args_desc = ["Room name", "MUC service", "Password, or none",
|
||||||
"Reason text, or none", "Users JIDs separated with : characters"],
|
"Reason text, or none", "Users JIDs separated with : characters"],
|
||||||
args_example = ["room1", "muc.example.com", none, none, "user2@localhost:user3@example.com"],
|
args_example = [<<"room1">>, <<"muc.example.com">>,
|
||||||
args = [{name, binary}, {service, binary}, {password, binary}, {reason, binary}, {users, binary}],
|
<<>>, <<"Check this out!">>,
|
||||||
|
"user2@localhost:user3@example.com"],
|
||||||
|
args = [{name, binary}, {service, binary}, {password, binary},
|
||||||
|
{reason, binary}, {users, binary}],
|
||||||
result = {res, rescode}},
|
result = {res, rescode}},
|
||||||
|
|
||||||
#ejabberd_commands{name = change_room_option, tags = [muc_room],
|
#ejabberd_commands{name = change_room_option, tags = [muc_room],
|
||||||
@ -277,7 +280,7 @@ get_commands_spec() ->
|
|||||||
args_desc = ["Room name", "MUC service"],
|
args_desc = ["Room name", "MUC service"],
|
||||||
args_example = ["room1", "muc.example.com"],
|
args_example = ["room1", "muc.example.com"],
|
||||||
result_desc = "The list of affiliations with username, domain, affiliation and reason",
|
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}],
|
args = [{name, binary}, {service, binary}],
|
||||||
result = {affiliations, {list,
|
result = {affiliations, {list,
|
||||||
{affiliation, {tuple,
|
{affiliation, {tuple,
|
||||||
@ -1009,7 +1012,7 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
|
|||||||
case mod_muc:find_online_room(Name, Service) of
|
case mod_muc:find_online_room(Name, Service) of
|
||||||
{ok, Pid} ->
|
{ok, Pid} ->
|
||||||
%% Get the PID for the online room so we can get the state of the room
|
%% 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)),
|
mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)),
|
||||||
ok;
|
ok;
|
||||||
error ->
|
error ->
|
||||||
|
@ -2608,7 +2608,7 @@ process_item_change(UJID) ->
|
|||||||
-type admin_action() :: {jid(), affiliation | role,
|
-type admin_action() :: {jid(), affiliation | role,
|
||||||
affiliation() | role(), binary()}.
|
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) ->
|
process_item_change(Item, SD, UJID) ->
|
||||||
try case Item of
|
try case Item of
|
||||||
{JID, affiliation, owner, _} when JID#jid.luser == <<"">> ->
|
{JID, affiliation, owner, _} when JID#jid.luser == <<"">> ->
|
||||||
@ -2658,8 +2658,15 @@ process_item_change(Item, SD, UJID) ->
|
|||||||
SD1
|
SD1
|
||||||
end
|
end
|
||||||
catch E:R ->
|
catch E:R ->
|
||||||
?ERROR_MSG("failed to set item ~p from ~s: ~p",
|
FromSuffix = case UJID of
|
||||||
[Item, jid:encode(UJID),
|
#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()}}]),
|
{E, {R, erlang:get_stacktrace()}}]),
|
||||||
{error, xmpp:err_internal_server_error()}
|
{error, xmpp:err_internal_server_error()}
|
||||||
end.
|
end.
|
||||||
|
@ -998,9 +998,8 @@ build_service_limit_record(LimitOpts) ->
|
|||||||
build_limit_record(LimitOptsR, remote)}.
|
build_limit_record(LimitOptsR, remote)}.
|
||||||
|
|
||||||
get_from_limitopts(LimitOpts, SenderT) ->
|
get_from_limitopts(LimitOpts, SenderT) ->
|
||||||
[{StanzaT, Number}
|
{SenderT, Result} = lists:keyfind(SenderT, 1, LimitOpts),
|
||||||
|| {SenderT2, StanzaT, Number} <- LimitOpts,
|
Result.
|
||||||
SenderT =:= SenderT2].
|
|
||||||
|
|
||||||
build_remote_limit_record(LimitOpts, SenderT) ->
|
build_remote_limit_record(LimitOpts, SenderT) ->
|
||||||
build_limit_record(LimitOpts, SenderT).
|
build_limit_record(LimitOpts, SenderT).
|
||||||
@ -1118,6 +1117,13 @@ depends(_Host, _Opts) ->
|
|||||||
mod_opt_type(access) ->
|
mod_opt_type(access) ->
|
||||||
fun acl:access_rules_validator/1;
|
fun acl:access_rules_validator/1;
|
||||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||||
mod_opt_type(limits) ->
|
mod_opt_type({limits, Type}) when (Type == local) or (Type == remote) ->
|
||||||
fun (A) when is_list(A) -> A end;
|
fun(L) ->
|
||||||
mod_opt_type(_) -> [access, host, limits].
|
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}].
|
||||||
|
@ -671,7 +671,7 @@ user_queue(User, Server, Query, Lang) ->
|
|||||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||||
Res = user_queue_parse_query(LUser, LServer, Query),
|
Res = user_queue_parse_query(LUser, LServer, Query),
|
||||||
HdrsAll = Mod:read_message_headers(LUser, LServer),
|
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),
|
FMsgs = format_user_queue(Hdrs),
|
||||||
[?XC(<<"h1">>,
|
[?XC(<<"h1">>,
|
||||||
(str:format(?T(<<"~s's Offline Messages Queue">>),
|
(str:format(?T(<<"~s's Offline Messages Queue">>),
|
||||||
|
@ -181,6 +181,13 @@ count_messages(LUser, LServer) ->
|
|||||||
|
|
||||||
export(_Server) ->
|
export(_Server) ->
|
||||||
[{offline_msg,
|
[{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},
|
fun(Host, #offline_msg{us = {LUser, LServer},
|
||||||
timestamp = TimeStamp, from = From, to = To,
|
timestamp = TimeStamp, from = From, to = To,
|
||||||
packet = El})
|
packet = El})
|
||||||
@ -192,8 +199,7 @@ export(_Server) ->
|
|||||||
Packet1, jid:make(LServer),
|
Packet1, jid:make(LServer),
|
||||||
TimeStamp, <<"Offline Storage">>),
|
TimeStamp, <<"Offline Storage">>),
|
||||||
XML = fxml:element_to_binary(xmpp:encode(Packet2)),
|
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);")]
|
"%(LUser)s, %(XML)s);")]
|
||||||
catch _:{xmpp_codec, Why} ->
|
catch _:{xmpp_codec, Why} ->
|
||||||
?ERROR_MSG("failed to decode packet ~p of user ~s@~s: ~s",
|
?ERROR_MSG("failed to decode packet ~p of user ~s@~s: ~s",
|
||||||
|
@ -410,9 +410,11 @@ decode_item(#privacy_item{order = Order,
|
|||||||
match_presence_out = MatchPresenceOut}
|
match_presence_out = MatchPresenceOut}
|
||||||
end.
|
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}) ->
|
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()}.
|
-spec user_send_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
|
||||||
user_send_packet({#iq{type = Type,
|
user_send_packet({#iq{type = Type,
|
||||||
|
@ -3009,60 +3009,61 @@ c2s_handle_info(C2SState, _) ->
|
|||||||
|
|
||||||
subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
|
subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
|
||||||
NodesToDeliver = fun (Depth, Node, Subs, Acc) ->
|
NodesToDeliver = fun (Depth, Node, Subs, Acc) ->
|
||||||
NodeName = case Node#pubsub_node.nodeid of
|
NodeName = case Node#pubsub_node.nodeid of
|
||||||
{_, N} -> N;
|
{_, N} -> N;
|
||||||
Other -> Other
|
Other -> Other
|
||||||
end,
|
end,
|
||||||
NodeOptions = Node#pubsub_node.options,
|
NodeOptions = Node#pubsub_node.options,
|
||||||
lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) ->
|
lists:foldl(fun({LJID, SubID, SubOptions}, {JIDs, Recipients}) ->
|
||||||
case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of
|
case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of
|
||||||
true ->
|
true ->
|
||||||
case state_can_deliver(LJID, SubOptions) of
|
case state_can_deliver(LJID, SubOptions) of
|
||||||
[] -> {JIDs, Recipients};
|
[] -> {JIDs, Recipients};
|
||||||
JIDsToDeliver ->
|
[LJID] -> {JIDs, [{LJID, NodeName, [SubID]} | Recipients]};
|
||||||
lists:foldl(
|
JIDsToDeliver ->
|
||||||
fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) ->
|
lists:foldl(
|
||||||
case lists:member(JIDToDeliver, JIDs) of
|
fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) ->
|
||||||
%% check if the JIDs co-accumulator contains the Subscription Jid,
|
case lists:member(JIDToDeliver, JIDs) of
|
||||||
false ->
|
%% check if the JIDs co-accumulator contains the Subscription Jid,
|
||||||
%% - if not,
|
false ->
|
||||||
%% - add the Jid to JIDs list co-accumulator ;
|
%% - if not,
|
||||||
%% - create a tuple of the Jid, Nidx, and SubID (as list),
|
%% - add the Jid to JIDs list co-accumulator ;
|
||||||
%% and add the tuple to the Recipients list co-accumulator
|
%% - create a tuple of the Jid, Nidx, and SubID (as list),
|
||||||
{[JIDToDeliver | JIDsAcc],
|
%% and add the tuple to the Recipients list co-accumulator
|
||||||
[{JIDToDeliver, NodeName, [SubID]}
|
{[JIDToDeliver | JIDsAcc],
|
||||||
| RecipientsAcc]};
|
[{JIDToDeliver, NodeName, [SubID]}
|
||||||
true ->
|
| RecipientsAcc]};
|
||||||
%% - if the JIDs co-accumulator contains the Jid
|
true ->
|
||||||
%% get the tuple containing the Jid from the Recipient list co-accumulator
|
%% - if the JIDs co-accumulator contains the Jid
|
||||||
{_, {JIDToDeliver, NodeName1, SubIDs}} =
|
%% get the tuple containing the Jid from the Recipient list co-accumulator
|
||||||
lists:keysearch(JIDToDeliver, 1, RecipientsAcc),
|
{_, {JIDToDeliver, NodeName1, SubIDs}} =
|
||||||
%% delete the tuple from the Recipients list
|
lists:keysearch(JIDToDeliver, 1, RecipientsAcc),
|
||||||
% v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients),
|
%% delete the tuple from the Recipients list
|
||||||
% v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients, {LJID, Nidx1, [SubID | SubIDs]}),
|
% v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients),
|
||||||
%% add the SubID to the SubIDs list in the tuple,
|
% v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients, {LJID, Nidx1, [SubID | SubIDs]}),
|
||||||
%% and add the tuple back to the Recipients list co-accumulator
|
%% add the SubID to the SubIDs list in the tuple,
|
||||||
% v1.1 : {JIDs, lists:append(Recipients1, [{LJID, Nidx1, lists:append(SubIDs, [SubID])}])}
|
%% and add the tuple back to the Recipients list co-accumulator
|
||||||
% v1.2 : {JIDs, [{LJID, Nidx1, [SubID | SubIDs]} | Recipients1]}
|
% v1.1 : {JIDs, lists:append(Recipients1, [{LJID, Nidx1, lists:append(SubIDs, [SubID])}])}
|
||||||
% v2: {JIDs, Recipients1}
|
% v1.2 : {JIDs, [{LJID, Nidx1, [SubID | SubIDs]} | Recipients1]}
|
||||||
{JIDsAcc,
|
% v2: {JIDs, Recipients1}
|
||||||
lists:keyreplace(JIDToDeliver, 1,
|
{JIDsAcc,
|
||||||
RecipientsAcc,
|
lists:keyreplace(JIDToDeliver, 1,
|
||||||
{JIDToDeliver, NodeName1,
|
RecipientsAcc,
|
||||||
[SubID | SubIDs]})}
|
{JIDToDeliver, NodeName1,
|
||||||
end
|
[SubID | SubIDs]})}
|
||||||
end, {JIDs, Recipients}, JIDsToDeliver)
|
end
|
||||||
end;
|
end, {JIDs, Recipients}, JIDsToDeliver)
|
||||||
false ->
|
end;
|
||||||
{JIDs, Recipients}
|
false ->
|
||||||
end
|
{JIDs, Recipients}
|
||||||
end, Acc, Subs)
|
end
|
||||||
end,
|
end, Acc, Subs)
|
||||||
|
end,
|
||||||
DepthsToDeliver = fun({Depth, SubsByNode}, Acc1) ->
|
DepthsToDeliver = fun({Depth, SubsByNode}, Acc1) ->
|
||||||
lists:foldl(fun({Node, Subs}, Acc2) ->
|
lists:foldl(fun({Node, Subs}, Acc2) ->
|
||||||
NodesToDeliver(Depth, Node, Subs, Acc2)
|
NodesToDeliver(Depth, Node, Subs, Acc2)
|
||||||
end, Acc1, SubsByNode)
|
end, Acc1, SubsByNode)
|
||||||
end,
|
end,
|
||||||
{_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth),
|
{_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth),
|
||||||
JIDSubs.
|
JIDSubs.
|
||||||
|
|
||||||
|
@ -441,6 +441,7 @@ update_num_stanzas_in(#{mgmt_state := MgmtState,
|
|||||||
update_num_stanzas_in(State, _El) ->
|
update_num_stanzas_in(State, _El) ->
|
||||||
State.
|
State.
|
||||||
|
|
||||||
|
-spec send_rack(state()) -> state().
|
||||||
send_rack(#{mgmt_ack_timer := _} = State) ->
|
send_rack(#{mgmt_ack_timer := _} = State) ->
|
||||||
State;
|
State;
|
||||||
send_rack(#{mgmt_xmlns := Xmlns,
|
send_rack(#{mgmt_xmlns := Xmlns,
|
||||||
@ -450,6 +451,7 @@ send_rack(#{mgmt_xmlns := Xmlns,
|
|||||||
State1 = State#{mgmt_ack_timer => TRef, mgmt_stanzas_req => NumStanzasOut},
|
State1 = State#{mgmt_ack_timer => TRef, mgmt_stanzas_req => NumStanzasOut},
|
||||||
send(State1, #sm_r{xmlns = Xmlns}).
|
send(State1, #sm_r{xmlns = Xmlns}).
|
||||||
|
|
||||||
|
-spec resend_rack(state()) -> state().
|
||||||
resend_rack(#{mgmt_ack_timer := _,
|
resend_rack(#{mgmt_ack_timer := _,
|
||||||
mgmt_queue := Queue,
|
mgmt_queue := Queue,
|
||||||
mgmt_stanzas_out := NumStanzasOut,
|
mgmt_stanzas_out := NumStanzasOut,
|
||||||
|
@ -50,7 +50,7 @@ from_dir(ProsodyDir) ->
|
|||||||
convert_dir(Path, Host, SubDir)
|
convert_dir(Path, Host, SubDir)
|
||||||
end, ["vcard", "accounts", "roster",
|
end, ["vcard", "accounts", "roster",
|
||||||
"private", "config", "offline",
|
"private", "config", "offline",
|
||||||
"privacy"])
|
"privacy", "pubsub"])
|
||||||
end, HostDirs);
|
end, HostDirs);
|
||||||
{error, Why} = Err ->
|
{error, Why} = Err ->
|
||||||
?ERROR_MSG("failed to list ~s: ~s",
|
?ERROR_MSG("failed to list ~s: ~s",
|
||||||
@ -115,7 +115,7 @@ maybe_get_scram_auth(Data) ->
|
|||||||
#scram{
|
#scram{
|
||||||
storedkey = misc:hex_to_base64(proplists:get_value(<<"stored_key">>, Data, <<"">>)),
|
storedkey = misc:hex_to_base64(proplists:get_value(<<"stored_key">>, Data, <<"">>)),
|
||||||
serverkey = misc:hex_to_base64(proplists:get_value(<<"server_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)
|
iterationcount = round(IC)
|
||||||
};
|
};
|
||||||
_ -> <<"">>
|
_ -> <<"">>
|
||||||
@ -212,6 +212,46 @@ convert_data(Host, "privacy", User, [Data]) ->
|
|||||||
end
|
end
|
||||||
end, Lists)},
|
end, Lists)},
|
||||||
mod_privacy:set_list(Priv);
|
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) ->
|
convert_data(_Host, _Type, _User, _Data) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
@ -333,6 +373,88 @@ convert_privacy_item({_, Item}) ->
|
|||||||
match_presence_in = MatchPresIn,
|
match_presence_in = MatchPresIn,
|
||||||
match_presence_out = MatchPresOut}.
|
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) ->
|
el_to_offline_msg(LUser, LServer, #xmlel{attrs = Attrs} = El) ->
|
||||||
try
|
try
|
||||||
TS = xmpp_util:decode_timestamp(
|
TS = xmpp_util:decode_timestamp(
|
||||||
|
@ -25,12 +25,15 @@ defmodule EjabberdAdminTest do
|
|||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
:mnesia.start
|
:mnesia.start
|
||||||
|
:ejabberd_mnesia.start
|
||||||
# For some myterious reason, :ejabberd_commands.init mays
|
# For some myterious reason, :ejabberd_commands.init mays
|
||||||
# sometimes fails if module is not loaded before
|
# sometimes fails if module is not loaded before
|
||||||
{:module, :ejabberd_commands} = Code.ensure_loaded(:ejabberd_commands)
|
{:module, :ejabberd_commands} = Code.ensure_loaded(:ejabberd_commands)
|
||||||
|
:ejabberd_hooks.start_link
|
||||||
|
{:ok, _} = :acl.start_link
|
||||||
{:ok, _} = :ejabberd_access_permissions.start_link()
|
{:ok, _} = :ejabberd_access_permissions.start_link()
|
||||||
:ejabberd_commands.init
|
:ejabberd_commands.start_link
|
||||||
:ejabberd_admin.start
|
:ejabberd_admin.start_link
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -41,40 +44,44 @@ defmodule EjabberdAdminTest do
|
|||||||
test "Logvel can be set and retrieved" do
|
test "Logvel can be set and retrieved" do
|
||||||
:ejabberd_logger.start()
|
:ejabberd_logger.start()
|
||||||
|
|
||||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [1])
|
assert :lager == call_command(:set_loglevel, [1])
|
||||||
assert {1, :critical, 'Critical'} ==
|
assert {1, :critical, 'Critical'} ==
|
||||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
call_command(:get_loglevel, [])
|
||||||
|
|
||||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [2])
|
assert :lager == call_command(:set_loglevel, [2])
|
||||||
assert {2, :error, 'Error'} ==
|
assert {2, :error, 'Error'} ==
|
||||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
call_command(:get_loglevel, [])
|
||||||
|
|
||||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [3])
|
assert :lager == call_command(:set_loglevel, [3])
|
||||||
assert {3, :warning, 'Warning'} ==
|
assert {3, :warning, 'Warning'} ==
|
||||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
call_command(:get_loglevel, [])
|
||||||
|
|
||||||
assert {:wrong_loglevel, 6} ==
|
assert {:wrong_loglevel, 6} ==
|
||||||
catch_throw :ejabberd_commands.execute_command(:set_loglevel, [6])
|
catch_throw call_command(:set_loglevel, [6])
|
||||||
assert {3, :warning, 'Warning'} ==
|
assert {3, :warning, 'Warning'} ==
|
||||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
call_command(:get_loglevel, [])
|
||||||
|
|
||||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [4])
|
assert :lager == call_command(:set_loglevel, [4])
|
||||||
assert {4, :info, 'Info'} ==
|
assert {4, :info, 'Info'} ==
|
||||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
call_command(:get_loglevel, [])
|
||||||
|
|
||||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [5])
|
assert :lager == call_command(:set_loglevel, [5])
|
||||||
assert {5, :debug, 'Debug'} ==
|
assert {5, :debug, 'Debug'} ==
|
||||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
call_command(:get_loglevel, [])
|
||||||
|
|
||||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [0])
|
assert :lager == call_command(:set_loglevel, [0])
|
||||||
assert {0, :no_log, 'No log'} ==
|
assert {0, :no_log, 'No log'} ==
|
||||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
call_command(:get_loglevel, [])
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp call_command(name, args) do
|
||||||
|
:ejabberd_commands.execute_command2(name, args, %{:caller_module => :ejabberd_ctl})
|
||||||
|
end
|
||||||
|
|
||||||
test "command status works with ejabberd stopped" do
|
test "command status works with ejabberd stopped" do
|
||||||
assert :ejabberd_not_running ==
|
assert :ejabberd_not_running ==
|
||||||
elem(:ejabberd_commands.execute_command(:status, []), 0)
|
elem(call_command(:status, []), 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -1,481 +0,0 @@
|
|||||||
# ----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
## TODO Fix next test error: add admin user ACL
|
|
||||||
|
|
||||||
defmodule EjabberdCommandsMockTest do
|
|
||||||
use ExUnit.Case, async: false
|
|
||||||
|
|
||||||
require EjabberdOauthMock
|
|
||||||
|
|
||||||
@author "jsautret@process-one.net"
|
|
||||||
|
|
||||||
# mocked callback module
|
|
||||||
@module :test_module
|
|
||||||
# Admin user
|
|
||||||
@admin "admin"
|
|
||||||
@adminpass "adminpass"
|
|
||||||
# Non admin user
|
|
||||||
@user "user"
|
|
||||||
@userpass "userpass"
|
|
||||||
# XMPP domain
|
|
||||||
@domain "domain"
|
|
||||||
|
|
||||||
require Record
|
|
||||||
Record.defrecord :ejabberd_commands, Record.extract(:ejabberd_commands, from_lib: "ejabberd/include/ejabberd_commands.hrl")
|
|
||||||
|
|
||||||
setup_all do
|
|
||||||
:ok = :ejabberd.start_app(:lager)
|
|
||||||
try do
|
|
||||||
:stringprep.start
|
|
||||||
rescue
|
|
||||||
_ -> :ok
|
|
||||||
end
|
|
||||||
:mnesia.start
|
|
||||||
:ejabberd_mnesia.start
|
|
||||||
:jid.start
|
|
||||||
:ejabberd_hooks.start_link
|
|
||||||
:ok = :ejabberd_config.start(["domain1", "domain2"], [])
|
|
||||||
{:ok, _} = :ejabberd_access_permissions.start_link()
|
|
||||||
{:ok, _} = :acl.start_link
|
|
||||||
:ejabberd_oauth.start_link
|
|
||||||
:ejabberd_commands.start_link
|
|
||||||
EjabberdOauthMock.init
|
|
||||||
on_exit fn -> :meck.unload end
|
|
||||||
end
|
|
||||||
|
|
||||||
setup do
|
|
||||||
:meck.unload
|
|
||||||
:meck.new(@module, [:non_strict])
|
|
||||||
:mnesia.clear_table(:ejabberd_commands)
|
|
||||||
:ejabberd_commands.register_commands(:ejabberd_commands.get_commands_spec())
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
test "API command can be registered, listed and unregistered" do
|
|
||||||
command = ejabberd_commands name: :test, module: @module,
|
|
||||||
function: :test_command
|
|
||||||
|
|
||||||
assert :ok == :ejabberd_commands.register_commands [command]
|
|
||||||
commands = :ejabberd_commands.list_commands
|
|
||||||
assert Enum.member? commands, {:test, [], ''}
|
|
||||||
|
|
||||||
assert :ok == :ejabberd_commands.unregister_commands [command]
|
|
||||||
commands = :ejabberd_commands.list_commands
|
|
||||||
refute Enum.member? commands, {:test, [], ''}
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
test "API command with versions can be registered, listed and unregistered" do
|
|
||||||
command1 = ejabberd_commands name: :test, module: @module,
|
|
||||||
function: :test_command, version: 1, desc: 'version1'
|
|
||||||
command3 = ejabberd_commands name: :test, module: @module,
|
|
||||||
function: :test_command, version: 3, desc: 'version3'
|
|
||||||
assert :ejabberd_commands.register_commands [command1, command3]
|
|
||||||
|
|
||||||
version1 = {:test, [], 'version1'}
|
|
||||||
version3 = {:test, [], 'version3'}
|
|
||||||
|
|
||||||
# default version is latest one
|
|
||||||
commands = :ejabberd_commands.list_commands
|
|
||||||
refute Enum.member? commands, version1
|
|
||||||
assert Enum.member? commands, version3
|
|
||||||
|
|
||||||
# no such command in APIv0
|
|
||||||
commands = :ejabberd_commands.list_commands 0
|
|
||||||
refute Enum.member? commands, version1
|
|
||||||
refute Enum.member? commands, version3
|
|
||||||
|
|
||||||
commands = :ejabberd_commands.list_commands 1
|
|
||||||
assert Enum.member? commands, version1
|
|
||||||
refute Enum.member? commands, version3
|
|
||||||
|
|
||||||
commands = :ejabberd_commands.list_commands 2
|
|
||||||
assert Enum.member? commands, version1
|
|
||||||
refute Enum.member? commands, version3
|
|
||||||
|
|
||||||
commands = :ejabberd_commands.list_commands 3
|
|
||||||
refute Enum.member? commands, version1
|
|
||||||
assert Enum.member? commands, version3
|
|
||||||
|
|
||||||
commands = :ejabberd_commands.list_commands 4
|
|
||||||
refute Enum.member? commands, version1
|
|
||||||
assert Enum.member? commands, version3
|
|
||||||
|
|
||||||
assert :ok == :ejabberd_commands.unregister_commands [command1]
|
|
||||||
|
|
||||||
commands = :ejabberd_commands.list_commands 1
|
|
||||||
refute Enum.member? commands, version1
|
|
||||||
refute Enum.member? commands, version3
|
|
||||||
|
|
||||||
commands = :ejabberd_commands.list_commands 3
|
|
||||||
refute Enum.member? commands, version1
|
|
||||||
assert Enum.member? commands, version3
|
|
||||||
|
|
||||||
assert :ok == :ejabberd_commands.unregister_commands [command3]
|
|
||||||
|
|
||||||
commands = :ejabberd_commands.list_commands 1
|
|
||||||
refute Enum.member? commands, version1
|
|
||||||
refute Enum.member? commands, version3
|
|
||||||
|
|
||||||
commands = :ejabberd_commands.list_commands 3
|
|
||||||
refute Enum.member? commands, version1
|
|
||||||
refute Enum.member? commands, version3
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
test "API command can be registered and executed" do
|
|
||||||
mock_commands_config
|
|
||||||
|
|
||||||
# Create & register a mocked command test() -> :result
|
|
||||||
command_name = :test
|
|
||||||
function = :test_command
|
|
||||||
command = ejabberd_commands(name: command_name,
|
|
||||||
module: @module,
|
|
||||||
function: function)
|
|
||||||
:meck.expect @module, function, fn -> :result end
|
|
||||||
assert :ok == :ejabberd_commands.register_commands [command]
|
|
||||||
|
|
||||||
assert :result == :ejabberd_commands.execute_command(command_name, [])
|
|
||||||
|
|
||||||
assert :meck.validate @module
|
|
||||||
end
|
|
||||||
|
|
||||||
test "API command with versions can be registered and executed" do
|
|
||||||
mock_commands_config
|
|
||||||
|
|
||||||
command_name = :test
|
|
||||||
|
|
||||||
function1 = :test_command1
|
|
||||||
command1 = ejabberd_commands(name: command_name,
|
|
||||||
version: 1,
|
|
||||||
module: @module,
|
|
||||||
function: function1)
|
|
||||||
:meck.expect(@module, function1, fn -> :result1 end)
|
|
||||||
|
|
||||||
function3 = :test_command3
|
|
||||||
command3 = ejabberd_commands(name: command_name,
|
|
||||||
version: 3,
|
|
||||||
module: @module,
|
|
||||||
function: function3)
|
|
||||||
:meck.expect(@module, function3, fn -> :result3 end)
|
|
||||||
|
|
||||||
assert :ok == :ejabberd_commands.register_commands [command1, command3]
|
|
||||||
|
|
||||||
# default version is latest one
|
|
||||||
assert :result3 == :ejabberd_commands.execute_command(command_name, [])
|
|
||||||
# no such command in APIv0
|
|
||||||
assert {:error, :unknown_command} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(command_name, [], 0)
|
|
||||||
assert :result1 == :ejabberd_commands.execute_command(command_name, [], 1)
|
|
||||||
assert :result1 == :ejabberd_commands.execute_command(command_name, [], 2)
|
|
||||||
assert :result3 == :ejabberd_commands.execute_command(command_name, [], 3)
|
|
||||||
assert :result3 == :ejabberd_commands.execute_command(command_name, [], 4)
|
|
||||||
|
|
||||||
assert :meck.validate @module
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
test "API command with user policy" do
|
|
||||||
mock_commands_config [:user, :admin]
|
|
||||||
|
|
||||||
# Register a command test(user, domain) -> {:versionN, user, domain}
|
|
||||||
# with policy=user and versions 1 & 3
|
|
||||||
command_name = :test
|
|
||||||
command1 = ejabberd_commands(name: command_name,
|
|
||||||
module: @module,
|
|
||||||
function: :test_command1,
|
|
||||||
policy: :user, version: 1)
|
|
||||||
command3 = ejabberd_commands(name: command_name,
|
|
||||||
module: @module,
|
|
||||||
function: :test_command3,
|
|
||||||
policy: :user, version: 3)
|
|
||||||
:meck.expect(@module, :test_command1,
|
|
||||||
fn(user, domain) when is_binary(user) and is_binary(domain) ->
|
|
||||||
{:version1, user, domain}
|
|
||||||
end)
|
|
||||||
:meck.expect(@module, :test_command3,
|
|
||||||
fn(user, domain) when is_binary(user) and is_binary(domain) ->
|
|
||||||
{:version3, user, domain}
|
|
||||||
end)
|
|
||||||
assert :ok == :ejabberd_commands.register_commands [command1, command3]
|
|
||||||
|
|
||||||
# A normal user must not pass user info as parameter
|
|
||||||
assert {:version1, @user, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@user, @domain,
|
|
||||||
@userpass, false},
|
|
||||||
command_name,
|
|
||||||
[], 2)
|
|
||||||
assert {:version3, @user, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@user, @domain,
|
|
||||||
@userpass, false},
|
|
||||||
command_name,
|
|
||||||
[], 3)
|
|
||||||
token = EjabberdOauthMock.get_token @user, @domain, command_name
|
|
||||||
assert {:version3, @user, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@user, @domain,
|
|
||||||
{:oauth, token}, false},
|
|
||||||
command_name,
|
|
||||||
[], 4)
|
|
||||||
# Expired oauth token
|
|
||||||
token = EjabberdOauthMock.get_token @user, @domain, command_name, 1
|
|
||||||
:timer.sleep 1500
|
|
||||||
assert {:error, :invalid_account_data} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@user, @domain,
|
|
||||||
{:oauth, token}, false},
|
|
||||||
command_name,
|
|
||||||
[], 4)
|
|
||||||
# Wrong oauth scope
|
|
||||||
token = EjabberdOauthMock.get_token @user, @domain, :bad_command
|
|
||||||
assert {:error, :invalid_account_data} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@user, @domain,
|
|
||||||
{:oauth, token}, false},
|
|
||||||
command_name,
|
|
||||||
[], 4)
|
|
||||||
|
|
||||||
|
|
||||||
assert :function_clause ==
|
|
||||||
catch_error :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@user, @domain,
|
|
||||||
@userpass, false},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain], 2)
|
|
||||||
# @user is not admin
|
|
||||||
assert {:error, :account_unprivileged} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@user, @domain,
|
|
||||||
@userpass, true},
|
|
||||||
command_name,
|
|
||||||
[], 2)
|
|
||||||
assert {:error, :account_unprivileged} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@user, @domain,
|
|
||||||
@userpass, true},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain], 2)
|
|
||||||
assert {:error, :account_unprivileged} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@user, @domain,
|
|
||||||
{:oauth, token}, true},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain], 2)
|
|
||||||
|
|
||||||
|
|
||||||
# An admin must explicitely pass user info
|
|
||||||
assert {:version1, @user, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined, :admin,
|
|
||||||
command_name, [@user, @domain], 2)
|
|
||||||
assert {:version3, @user, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined, :admin,
|
|
||||||
command_name, [@user, @domain], 4)
|
|
||||||
assert {:version1, @user, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain, @adminpass, true},
|
|
||||||
command_name, [@user, @domain], 1)
|
|
||||||
token = EjabberdOauthMock.get_token @admin, @domain, command_name
|
|
||||||
assert {:version3, @user, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain, {:oauth, token}, true},
|
|
||||||
command_name, [@user, @domain], 3)
|
|
||||||
# Wrong @admin password
|
|
||||||
assert {:error, :account_unprivileged} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain,
|
|
||||||
@adminpass<>"bad", true},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain], 3)
|
|
||||||
# @admin calling as a normal user
|
|
||||||
assert {:version3, @admin, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain,
|
|
||||||
@adminpass, false},
|
|
||||||
command_name, [], 5)
|
|
||||||
assert {:version3, @admin, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain,
|
|
||||||
{:oauth, token}, false},
|
|
||||||
command_name, [], 6)
|
|
||||||
assert :function_clause ==
|
|
||||||
catch_error :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain,
|
|
||||||
@adminpass, false},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain], 5)
|
|
||||||
assert :meck.validate @module
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
test "API command with admin policy" do
|
|
||||||
mock_commands_config [:admin]
|
|
||||||
|
|
||||||
# Register a command test(user, domain) -> {user, domain}
|
|
||||||
# with policy=admin
|
|
||||||
command_name = :test
|
|
||||||
function = :test_command
|
|
||||||
command = ejabberd_commands(name: command_name,
|
|
||||||
args: [{:user, :binary}, {:host, :binary}],
|
|
||||||
module: @module,
|
|
||||||
function: function,
|
|
||||||
policy: :admin)
|
|
||||||
:meck.expect(@module, function,
|
|
||||||
fn(user, domain) when is_binary(user) and is_binary(domain) ->
|
|
||||||
{user, domain}
|
|
||||||
end)
|
|
||||||
assert :ok == :ejabberd_commands.register_commands [command]
|
|
||||||
|
|
||||||
# A normal user cannot call the command
|
|
||||||
assert {:error, :account_unprivileged} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@user, @domain,
|
|
||||||
@userpass, false},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain])
|
|
||||||
|
|
||||||
# An admin can call the command
|
|
||||||
assert {@user, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain,
|
|
||||||
@adminpass, true},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain])
|
|
||||||
|
|
||||||
# An admin can call the command with oauth token
|
|
||||||
token = EjabberdOauthMock.get_token @admin, @domain, command_name
|
|
||||||
assert {@user, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain,
|
|
||||||
{:oauth, token}, true},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain])
|
|
||||||
|
|
||||||
|
|
||||||
# An admin with bad password cannot call the command
|
|
||||||
assert {:error, :account_unprivileged} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain,
|
|
||||||
"bad"<>@adminpass, false},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain])
|
|
||||||
|
|
||||||
# An admin cannot call the command with bad oauth token
|
|
||||||
assert {:error, :account_unprivileged} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain,
|
|
||||||
{:oauth, "bad"<>token}, true},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain])
|
|
||||||
|
|
||||||
# An admin as a normal user cannot call the command
|
|
||||||
assert {:error, :account_unprivileged} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain,
|
|
||||||
@adminpass, false},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain])
|
|
||||||
|
|
||||||
# An admin as a normal user cannot call the command with oauth token
|
|
||||||
assert {:error, :account_unprivileged} ==
|
|
||||||
catch_throw :ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain,
|
|
||||||
{:oauth, token}, false},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain])
|
|
||||||
|
|
||||||
assert :meck.validate @module
|
|
||||||
end
|
|
||||||
|
|
||||||
test "Commands can perform extra check on access" do
|
|
||||||
mock_commands_config [:admin, :open]
|
|
||||||
|
|
||||||
command_name = :test
|
|
||||||
function = :test_command
|
|
||||||
command = ejabberd_commands(name: command_name,
|
|
||||||
args: [{:user, :binary}, {:host, :binary}],
|
|
||||||
access: [:basic_rule_1],
|
|
||||||
module: @module,
|
|
||||||
function: function,
|
|
||||||
policy: :open)
|
|
||||||
:meck.expect(@module, function,
|
|
||||||
fn(user, domain) when is_binary(user) and is_binary(domain) ->
|
|
||||||
{user, domain}
|
|
||||||
end)
|
|
||||||
assert :ok == :ejabberd_commands.register_commands [command]
|
|
||||||
|
|
||||||
# :acl.add(:global, :basic_acl_1, {:user, @user, @host})
|
|
||||||
# :acl.add_access(:global, :basic_rule_1, [{:allow, [{:acl, :basic_acl_1}]}])
|
|
||||||
|
|
||||||
assert {@user, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@user, @domain,
|
|
||||||
@userpass, false},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain])
|
|
||||||
assert {@user, @domain} ==
|
|
||||||
:ejabberd_commands.execute_command(:undefined,
|
|
||||||
{@admin, @domain,
|
|
||||||
@adminpass, false},
|
|
||||||
command_name,
|
|
||||||
[@user, @domain])
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
##########################################################
|
|
||||||
# Utils
|
|
||||||
|
|
||||||
# Mock a config where only @admin user is allowed to call commands
|
|
||||||
# as admin
|
|
||||||
def mock_commands_config(commands \\ []) do
|
|
||||||
EjabberdAuthMock.init
|
|
||||||
EjabberdAuthMock.create_user @user, @domain, @userpass
|
|
||||||
EjabberdAuthMock.create_user @admin, @domain, @adminpass
|
|
||||||
|
|
||||||
:meck.new :ejabberd_config
|
|
||||||
:meck.expect(:ejabberd_config, :get_option,
|
|
||||||
fn(:commands_admin_access, _) -> :commands_admin_access
|
|
||||||
(:oauth_access, _) -> :all
|
|
||||||
(:commands, _) -> [{:add_commands, commands}]
|
|
||||||
(_, default) -> default
|
|
||||||
end)
|
|
||||||
:meck.expect(:ejabberd_config, :get_myhosts,
|
|
||||||
fn() -> [@domain] end)
|
|
||||||
:meck.expect(:ejabberd_config, :default_db,
|
|
||||||
fn(_) -> :mnesia end)
|
|
||||||
:meck.expect(:ejabberd_config, :default_db,
|
|
||||||
fn(_, _) -> :mnesia end)
|
|
||||||
|
|
||||||
:meck.new :acl
|
|
||||||
:meck.expect(:acl, :access_matches,
|
|
||||||
fn(:commands_admin_access, info, _scope) ->
|
|
||||||
case info do
|
|
||||||
%{usr: {@admin, @domain, _}} -> :allow
|
|
||||||
_ -> :deny
|
|
||||||
end;
|
|
||||||
(:all, _, _scope) ->
|
|
||||||
:allow
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
@ -1,108 +0,0 @@
|
|||||||
# ----------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
defmodule EjabberdCommandsTest do
|
|
||||||
@author "mremond@process-one.net"
|
|
||||||
|
|
||||||
use ExUnit.Case, async: true
|
|
||||||
|
|
||||||
require Record
|
|
||||||
Record.defrecord :ejabberd_commands, Record.extract(:ejabberd_commands, from_lib: "ejabberd/include/ejabberd_commands.hrl")
|
|
||||||
|
|
||||||
setup_all do
|
|
||||||
:mnesia.start
|
|
||||||
:ejabberd_mnesia.start
|
|
||||||
:stringprep.start
|
|
||||||
:ok = :ejabberd_config.start(["localhost"], [])
|
|
||||||
{:ok, _} = :ejabberd_access_permissions.start_link()
|
|
||||||
|
|
||||||
:ejabberd_commands.start_link
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
test "Check that we can register a command" do
|
|
||||||
:ok = :ejabberd_commands.register_commands([user_test_command])
|
|
||||||
commands = :ejabberd_commands.list_commands
|
|
||||||
assert Enum.member?(commands, {:test_user, [], "Test user"})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "get_exposed_commands/0 returns registered commands" do
|
|
||||||
commands = [open_test_command]
|
|
||||||
:ok = :ejabberd_commands.register_commands(commands)
|
|
||||||
:ok = :ejabberd_commands.expose_commands(commands)
|
|
||||||
exposed_commands = :ejabberd_commands.get_exposed_commands
|
|
||||||
assert Enum.member?(exposed_commands, :test_open)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "Check that admin commands are rejected with noauth credentials" do
|
|
||||||
:ok = :ejabberd_commands.register_commands([admin_test_command])
|
|
||||||
|
|
||||||
assert catch_throw(:ejabberd_commands.execute_command(:undefined, :noauth, :test_admin, [])) == {:error, :account_unprivileged}
|
|
||||||
|
|
||||||
# Command executed from ejabberdctl passes anyway with access commands trick
|
|
||||||
# TODO: We should refactor to have explicit call when bypassing auth check for command-line
|
|
||||||
:ok = :ejabberd_commands.execute_command([], :noauth, :test_admin, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO Test that we can add command to list of expose commands
|
|
||||||
# This can be done with:
|
|
||||||
# ejabberd_config:add_local_option(commands, [[{add_commands, [open_cmd]}]]).
|
|
||||||
|
|
||||||
# test "Check that a user can use a user command" do
|
|
||||||
# [Command] = ets:lookup(ejabberd_commands, test_user),
|
|
||||||
# AccessCommands = ejabberd_commands:get_access_commands(undefined),
|
|
||||||
# ejabberd_commands:check_access_commands(AccessCommands, {<<"test">>,<<"localhost">>, {oauth,<<"MyToken">>}, false}, test_user, Command, []).
|
|
||||||
# end
|
|
||||||
|
|
||||||
defp user_test_command do
|
|
||||||
ejabberd_commands(name: :test_user, tags: [:roster],
|
|
||||||
desc: "Test user",
|
|
||||||
policy: :user,
|
|
||||||
module: __MODULE__,
|
|
||||||
function: :test_user,
|
|
||||||
args: [],
|
|
||||||
result: {:contacts, {:list, {:contact, {:tuple, [
|
|
||||||
{:jid, :string},
|
|
||||||
{:nick, :string}
|
|
||||||
]}}}})
|
|
||||||
end
|
|
||||||
|
|
||||||
defp open_test_command do
|
|
||||||
ejabberd_commands(name: :test_open, tags: [:test],
|
|
||||||
desc: "Test open",
|
|
||||||
policy: :open,
|
|
||||||
module: __MODULE__,
|
|
||||||
function: :test_open,
|
|
||||||
args: [],
|
|
||||||
result: {:res, :rescode})
|
|
||||||
end
|
|
||||||
|
|
||||||
defp admin_test_command do
|
|
||||||
ejabberd_commands(name: :test_admin, tags: [:roster],
|
|
||||||
desc: "Test admin",
|
|
||||||
policy: :restricted,
|
|
||||||
module: __MODULE__,
|
|
||||||
function: :test_admin,
|
|
||||||
args: [],
|
|
||||||
result: {:res, :rescode})
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_admin, do: :ok
|
|
||||||
end
|
|
@ -30,6 +30,7 @@ defmodule EjabberdCyrsaslTest do
|
|||||||
:ejabberd_mnesia.start
|
:ejabberd_mnesia.start
|
||||||
:ok = start_module(:stringprep)
|
:ok = start_module(:stringprep)
|
||||||
start_module(:jid)
|
start_module(:jid)
|
||||||
|
:ejabberd_hooks.start_link
|
||||||
:ok = :ejabberd_config.start(["domain1"], [])
|
:ok = :ejabberd_config.start(["domain1"], [])
|
||||||
{:ok, _} = :cyrsasl.start_link
|
{:ok, _} = :cyrsasl.start_link
|
||||||
cyrstate = :cyrsasl.server_new("domain1", "domain1", "domain1", :ok, &get_password/1,
|
cyrstate = :cyrsasl.server_new("domain1", "domain1", "domain1", :ok, &get_password/1,
|
||||||
|
@ -99,7 +99,7 @@ run_elixir_test(Func) ->
|
|||||||
|
|
||||||
'Elixir.Code':load_file(list_to_binary(filename:join(test_dir(), atom_to_list(Func)))),
|
'Elixir.Code':load_file(list_to_binary(filename:join(test_dir(), atom_to_list(Func)))),
|
||||||
%% I did not use map syntax, so that this file can still be build under R16
|
%% I did not use map syntax, so that this file can still be build under R16
|
||||||
'Elixir.ExUnit.Server':cases_loaded(),
|
catch 'Elixir.ExUnit.Server':cases_loaded(),
|
||||||
ResultMap = 'Elixir.ExUnit':run(),
|
ResultMap = 'Elixir.ExUnit':run(),
|
||||||
case maps:find(failures, ResultMap) of
|
case maps:find(failures, ResultMap) of
|
||||||
{ok, 0} ->
|
{ok, 0} ->
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
-define(ERROR_MSG(Fmt, Args), error_logger:error_msg(Fmt, Args)).
|
-define(ERROR_MSG(Fmt, Args), error_logger:error_msg(Fmt, Args)).
|
||||||
|
|
||||||
-define(TCP_SEND_TIMEOUT, 32000).
|
-define(TCP_SEND_TIMEOUT, 32000).
|
||||||
-define(SERVER, ?MODULE).
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
-record(state, {listener = make_ref() :: reference()}).
|
-record(state, {listener = make_ref() :: reference()}).
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ accept(ListenSocket, Tree) ->
|
|||||||
process(Socket, Tree) ->
|
process(Socket, Tree) ->
|
||||||
case gen_tcp:recv(Socket, 0) of
|
case gen_tcp:recv(Socket, 0) of
|
||||||
{ok, B} ->
|
{ok, B} ->
|
||||||
case asn1rt:decode('ELDAPv3', 'LDAPMessage', B) of
|
case 'ELDAPv3':decode('LDAPMessage', B) of
|
||||||
{ok, Msg} ->
|
{ok, Msg} ->
|
||||||
Replies = process_msg(Msg, Tree),
|
Replies = process_msg(Msg, Tree),
|
||||||
Id = Msg#'LDAPMessage'.messageID,
|
Id = Msg#'LDAPMessage'.messageID,
|
||||||
@ -131,8 +131,8 @@ process(Socket, Tree) ->
|
|||||||
Reply = #'LDAPMessage'{messageID = Id,
|
Reply = #'LDAPMessage'{messageID = Id,
|
||||||
protocolOp = ReplyOp},
|
protocolOp = ReplyOp},
|
||||||
%%?DEBUG("sent:~n~p", [Reply]),
|
%%?DEBUG("sent:~n~p", [Reply]),
|
||||||
{ok, Bytes} = asn1rt:encode(
|
{ok, Bytes} = 'ELDAPv3':encode(
|
||||||
'ELDAPv3', 'LDAPMessage', Reply),
|
'LDAPMessage', Reply),
|
||||||
gen_tcp:send(Socket, Bytes)
|
gen_tcp:send(Socket, Bytes)
|
||||||
end, Replies),
|
end, Replies),
|
||||||
process(Socket, Tree);
|
process(Socket, Tree);
|
||||||
|
@ -41,16 +41,20 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
:jid.start
|
:jid.start
|
||||||
:stringprep.start
|
:stringprep.start
|
||||||
:mnesia.start
|
:mnesia.start
|
||||||
|
:ejabberd_mnesia.start
|
||||||
:p1_sha.load_nif
|
:p1_sha.load_nif
|
||||||
|
:ejabberd_hooks.start_link
|
||||||
rescue
|
rescue
|
||||||
_ -> :ok
|
_ -> :ok
|
||||||
end
|
end
|
||||||
{:ok, _} = :ejabberd_access_permissions.start_link()
|
:acl.start_link
|
||||||
:ejabberd_commands.init
|
:ejabberd_access_permissions.start_link()
|
||||||
:ok = :ejabberd_config.start([@domain], [])
|
:ejabberd_commands.start_link
|
||||||
|
:ok = :ejabberd_config.start([@domain], [])
|
||||||
|
:gen_mod.start_link
|
||||||
:mod_admin_extra.start(@domain, [])
|
:mod_admin_extra.start(@domain, [])
|
||||||
:sel_application.start_app(:moka)
|
:sel_application.start_app(:moka)
|
||||||
{:ok, _pid} = :ejabberd_hooks.start_link
|
:ejabberd_hooks.start_link
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -66,9 +70,9 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
test "check_account works" do
|
test "check_account works" do
|
||||||
EjabberdAuthMock.create_user @user, @domain, @password
|
EjabberdAuthMock.create_user @user, @domain, @password
|
||||||
|
|
||||||
assert :ejabberd_commands.execute_command(:check_account, [@user, @domain])
|
assert call_command(:check_account, [@user, @domain])
|
||||||
refute :ejabberd_commands.execute_command(:check_account, [@user, "bad_domain"])
|
refute call_command(:check_account, [@user, "bad_domain"])
|
||||||
refute :ejabberd_commands.execute_command(:check_account, ["bad_user", @domain])
|
refute call_command(:check_account, ["bad_user", @domain])
|
||||||
|
|
||||||
assert :meck.validate :ejabberd_auth
|
assert :meck.validate :ejabberd_auth
|
||||||
end
|
end
|
||||||
@ -77,13 +81,13 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
|
|
||||||
EjabberdAuthMock.create_user @user, @domain, @password
|
EjabberdAuthMock.create_user @user, @domain, @password
|
||||||
|
|
||||||
assert :ejabberd_commands.execute_command(:check_password,
|
assert call_command(:check_password,
|
||||||
[@user, @domain, @password])
|
[@user, @domain, @password])
|
||||||
refute :ejabberd_commands.execute_command(:check_password,
|
refute call_command(:check_password,
|
||||||
[@user, @domain, "bad_password"])
|
[@user, @domain, "bad_password"])
|
||||||
refute :ejabberd_commands.execute_command(:check_password,
|
refute call_command(:check_password,
|
||||||
[@user, "bad_domain", @password])
|
[@user, "bad_domain", @password])
|
||||||
refute :ejabberd_commands.execute_command(:check_password,
|
refute call_command(:check_password,
|
||||||
["bad_user", @domain, @password])
|
["bad_user", @domain, @password])
|
||||||
|
|
||||||
assert :meck.validate :ejabberd_auth
|
assert :meck.validate :ejabberd_auth
|
||||||
@ -95,21 +99,21 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
EjabberdAuthMock.create_user @user, @domain, @password
|
EjabberdAuthMock.create_user @user, @domain, @password
|
||||||
hash = "5F4DCC3B5AA765D61D8327DEB882CF99" # echo -n password|md5
|
hash = "5F4DCC3B5AA765D61D8327DEB882CF99" # echo -n password|md5
|
||||||
|
|
||||||
assert :ejabberd_commands.execute_command(:check_password_hash,
|
assert call_command(:check_password_hash,
|
||||||
[@user, @domain, hash, "md5"])
|
[@user, @domain, hash, "md5"])
|
||||||
refute :ejabberd_commands.execute_command(:check_password_hash,
|
refute call_command(:check_password_hash,
|
||||||
[@user, @domain, "bad_hash", "md5"])
|
[@user, @domain, "bad_hash", "md5"])
|
||||||
refute :ejabberd_commands.execute_command(:check_password_hash,
|
refute call_command(:check_password_hash,
|
||||||
[@user, "bad_domain", hash, "md5"])
|
[@user, "bad_domain", hash, "md5"])
|
||||||
refute :ejabberd_commands.execute_command(:check_password_hash,
|
refute call_command(:check_password_hash,
|
||||||
["bad_user", @domain, hash, "md5"])
|
["bad_user", @domain, hash, "md5"])
|
||||||
|
|
||||||
hash = "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8" # echo -n password|shasum
|
hash = "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8" # echo -n password|shasum
|
||||||
assert :ejabberd_commands.execute_command(:check_password_hash,
|
assert call_command(:check_password_hash,
|
||||||
[@user, @domain, hash, "sha"])
|
[@user, @domain, hash, "sha"])
|
||||||
|
|
||||||
assert :unkown_hash_method ==
|
assert :unkown_hash_method ==
|
||||||
catch_throw :ejabberd_commands.execute_command(:check_password_hash,
|
catch_throw call_command(:check_password_hash,
|
||||||
[@user, @domain, hash, "bad_method"])
|
[@user, @domain, hash, "bad_method"])
|
||||||
|
|
||||||
assert :meck.validate :ejabberd_auth
|
assert :meck.validate :ejabberd_auth
|
||||||
@ -119,14 +123,14 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
test "set_password works" do
|
test "set_password works" do
|
||||||
EjabberdAuthMock.create_user @user, @domain, @password
|
EjabberdAuthMock.create_user @user, @domain, @password
|
||||||
|
|
||||||
assert :ejabberd_commands.execute_command(:change_password,
|
assert call_command(:change_password,
|
||||||
[@user, @domain, "new_password"])
|
[@user, @domain, "new_password"])
|
||||||
refute :ejabberd_commands.execute_command(:check_password,
|
refute call_command(:check_password,
|
||||||
[@user, @domain, @password])
|
[@user, @domain, @password])
|
||||||
assert :ejabberd_commands.execute_command(:check_password,
|
assert call_command(:check_password,
|
||||||
[@user, @domain, "new_password"])
|
[@user, @domain, "new_password"])
|
||||||
assert {:not_found, 'unknown_user'} ==
|
assert {:not_found, 'unknown_user'} ==
|
||||||
catch_throw :ejabberd_commands.execute_command(:change_password,
|
catch_throw call_command(:change_password,
|
||||||
["bad_user", @domain,
|
["bad_user", @domain,
|
||||||
@password])
|
@password])
|
||||||
assert :meck.validate :ejabberd_auth
|
assert :meck.validate :ejabberd_auth
|
||||||
@ -135,23 +139,23 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
###################### Sessions
|
###################### Sessions
|
||||||
|
|
||||||
test "num_resources works" do
|
test "num_resources works" do
|
||||||
assert 0 == :ejabberd_commands.execute_command(:num_resources,
|
assert 0 == call_command(:num_resources,
|
||||||
[@user, @domain])
|
[@user, @domain])
|
||||||
|
|
||||||
EjabberdSmMock.connect_resource @user, @domain, @resource
|
EjabberdSmMock.connect_resource @user, @domain, @resource
|
||||||
assert 1 == :ejabberd_commands.execute_command(:num_resources,
|
assert 1 == call_command(:num_resources,
|
||||||
[@user, @domain])
|
[@user, @domain])
|
||||||
|
|
||||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
|
EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
|
||||||
assert 2 == :ejabberd_commands.execute_command(:num_resources,
|
assert 2 == call_command(:num_resources,
|
||||||
[@user, @domain])
|
[@user, @domain])
|
||||||
|
|
||||||
EjabberdSmMock.connect_resource @user<>"1", @domain, @resource
|
EjabberdSmMock.connect_resource @user<>"1", @domain, @resource
|
||||||
assert 2 == :ejabberd_commands.execute_command(:num_resources,
|
assert 2 == call_command(:num_resources,
|
||||||
[@user, @domain])
|
[@user, @domain])
|
||||||
|
|
||||||
EjabberdSmMock.disconnect_resource @user, @domain, @resource
|
EjabberdSmMock.disconnect_resource @user, @domain, @resource
|
||||||
assert 1 == :ejabberd_commands.execute_command(:num_resources,
|
assert 1 == call_command(:num_resources,
|
||||||
[@user, @domain])
|
[@user, @domain])
|
||||||
|
|
||||||
assert :meck.validate :ejabberd_sm
|
assert :meck.validate :ejabberd_sm
|
||||||
@ -163,14 +167,14 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
|
EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
|
||||||
|
|
||||||
assert :bad_argument ==
|
assert :bad_argument ==
|
||||||
elem(catch_throw(:ejabberd_commands.execute_command(:resource_num,
|
elem(catch_throw(call_command(:resource_num,
|
||||||
[@user, @domain, 0])), 0)
|
[@user, @domain, 0])), 0)
|
||||||
assert @resource<>"1" ==
|
assert @resource<>"1" ==
|
||||||
:ejabberd_commands.execute_command(:resource_num, [@user, @domain, 1])
|
call_command(:resource_num, [@user, @domain, 1])
|
||||||
assert @resource<>"3" ==
|
assert @resource<>"3" ==
|
||||||
:ejabberd_commands.execute_command(:resource_num, [@user, @domain, 3])
|
call_command(:resource_num, [@user, @domain, 3])
|
||||||
assert :bad_argument ==
|
assert :bad_argument ==
|
||||||
elem(catch_throw(:ejabberd_commands.execute_command(:resource_num,
|
elem(catch_throw(call_command(:resource_num,
|
||||||
[@user, @domain, 4])), 0)
|
[@user, @domain, 4])), 0)
|
||||||
assert :meck.validate :ejabberd_sm
|
assert :meck.validate :ejabberd_sm
|
||||||
end
|
end
|
||||||
@ -184,7 +188,7 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
assert 1 == length EjabberdSmMock.get_session @user, @domain, @resource<>"2"
|
assert 1 == length EjabberdSmMock.get_session @user, @domain, @resource<>"2"
|
||||||
|
|
||||||
assert :ok ==
|
assert :ok ==
|
||||||
:ejabberd_commands.execute_command(:kick_session,
|
call_command(:kick_session,
|
||||||
[@user, @domain,
|
[@user, @domain,
|
||||||
@resource<>"2", "kick"])
|
@resource<>"2", "kick"])
|
||||||
|
|
||||||
@ -199,18 +203,18 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
test "get_last works" do
|
test "get_last works" do
|
||||||
|
|
||||||
assert {_, 'NOT FOUND'} =
|
assert {_, 'NOT FOUND'} =
|
||||||
:ejabberd_commands.execute_command(:get_last, [@user, @domain])
|
call_command(:get_last, [@user, @domain])
|
||||||
|
|
||||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
|
EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
|
||||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
|
EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
|
||||||
|
|
||||||
assert {_, 'ONLINE'} =
|
assert {_, 'ONLINE'} =
|
||||||
:ejabberd_commands.execute_command(:get_last, [@user, @domain])
|
call_command(:get_last, [@user, @domain])
|
||||||
|
|
||||||
EjabberdSmMock.disconnect_resource @user, @domain, @resource<>"1"
|
EjabberdSmMock.disconnect_resource @user, @domain, @resource<>"1"
|
||||||
|
|
||||||
assert {_, 'ONLINE'} =
|
assert {_, 'ONLINE'} =
|
||||||
:ejabberd_commands.execute_command(:get_last, [@user, @domain])
|
call_command(:get_last, [@user, @domain])
|
||||||
|
|
||||||
now = {megasecs, secs, _microsecs} = :os.timestamp
|
now = {megasecs, secs, _microsecs} = :os.timestamp
|
||||||
timestamp = megasecs * 1000000 + secs
|
timestamp = megasecs * 1000000 + secs
|
||||||
@ -221,7 +225,7 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
"~w-~.2.0w-~.2.0wT~.2.0w:~.2.0w:~.2.0wZ",
|
"~w-~.2.0w-~.2.0wT~.2.0w:~.2.0w:~.2.0wZ",
|
||||||
[year, month, day, hour, minute, second]))
|
[year, month, day, hour, minute, second]))
|
||||||
assert {result, ""} ==
|
assert {result, ""} ==
|
||||||
:ejabberd_commands.execute_command(:get_last, [@user, @domain])
|
call_command(:get_last, [@user, @domain])
|
||||||
|
|
||||||
assert :meck.validate :mod_last
|
assert :meck.validate :mod_last
|
||||||
end
|
end
|
||||||
@ -238,7 +242,7 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
assert [] == ModRosterMock.get_roster(@user, @domain)
|
assert [] == ModRosterMock.get_roster(@user, @domain)
|
||||||
|
|
||||||
assert :ok ==
|
assert :ok ==
|
||||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
call_command(:add_rosteritem, [@user, @domain,
|
||||||
@user<>"1", @domain,
|
@user<>"1", @domain,
|
||||||
"nick1",
|
"nick1",
|
||||||
"group1",
|
"group1",
|
||||||
@ -261,7 +265,7 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
{:item, {@user<>"1", @domain, ""}, :both}])
|
{:item, {@user<>"1", @domain, ""}, :both}])
|
||||||
|
|
||||||
assert :ok ==
|
assert :ok ==
|
||||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
call_command(:add_rosteritem, [@user, @domain,
|
||||||
@user<>"2", @domain,
|
@user<>"2", @domain,
|
||||||
"nick2",
|
"nick2",
|
||||||
"group2",
|
"group2",
|
||||||
@ -278,7 +282,7 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
{:item, {@user<>"2", @domain, ""}, :both}])
|
{:item, {@user<>"2", @domain, ""}, :both}])
|
||||||
|
|
||||||
|
|
||||||
:ejabberd_commands.execute_command(:delete_rosteritem, [@user, @domain,
|
call_command(:delete_rosteritem, [@user, @domain,
|
||||||
@user<>"1", @domain])
|
@user<>"1", @domain])
|
||||||
result = ModRosterMock.get_roster(@user, @domain)
|
result = ModRosterMock.get_roster(@user, @domain)
|
||||||
assert 1 == length result
|
assert 1 == length result
|
||||||
@ -296,7 +300,7 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
[jid,
|
[jid,
|
||||||
{:item, {@user<>"1", @domain, ""}, :none}])
|
{:item, {@user<>"1", @domain, ""}, :none}])
|
||||||
|
|
||||||
:ejabberd_commands.execute_command(:delete_rosteritem, [@user, @domain,
|
call_command(:delete_rosteritem, [@user, @domain,
|
||||||
@user<>"2", @domain])
|
@user<>"2", @domain])
|
||||||
|
|
||||||
# Check that the item roster user2 was pushed with subscription
|
# Check that the item roster user2 was pushed with subscription
|
||||||
@ -321,39 +325,47 @@ defmodule EjabberdModAdminExtraTest do
|
|||||||
|
|
||||||
test "get_roster works" do
|
test "get_roster works" do
|
||||||
assert [] == ModRosterMock.get_roster(@user, @domain)
|
assert [] == ModRosterMock.get_roster(@user, @domain)
|
||||||
assert [] == :ejabberd_commands.execute_command(:get_roster, [@user, @domain],
|
assert [] == call_command(:get_roster, [@user, @domain],
|
||||||
:admin)
|
:admin)
|
||||||
|
|
||||||
assert :ok ==
|
assert :ok ==
|
||||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
call_command(:add_rosteritem, [@user, @domain,
|
||||||
@user<>"1", @domain,
|
@user<>"1", @domain,
|
||||||
"nick1",
|
"nick1",
|
||||||
"group1",
|
"group1",
|
||||||
"both"])
|
"both"])
|
||||||
assert [{@user<>"1@"<>@domain, "", 'both', 'none', "group1"}] ==
|
assert [{@user<>"1@"<>@domain, "", 'both', 'none', "group1"}] ==
|
||||||
:ejabberd_commands.execute_command(:get_roster, [@user, @domain], :admin)
|
call_command(:get_roster, [@user, @domain], :admin)
|
||||||
assert :ok ==
|
assert :ok ==
|
||||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
call_command(:add_rosteritem, [@user, @domain,
|
||||||
@user<>"2", @domain,
|
@user<>"2", @domain,
|
||||||
"nick2",
|
"nick2",
|
||||||
"group2",
|
"group2",
|
||||||
"none"])
|
"none"])
|
||||||
result = :ejabberd_commands.execute_command(:get_roster, [@user, @domain], :admin)
|
result = call_command(:get_roster, [@user, @domain], :admin)
|
||||||
assert 2 == length result
|
assert 2 == length result
|
||||||
assert Enum.member?(result, {@user<>"1@"<>@domain, "", 'both', 'none', "group1"})
|
assert Enum.member?(result, {@user<>"1@"<>@domain, "", 'both', 'none', "group1"})
|
||||||
assert Enum.member?(result, {@user<>"2@"<>@domain, "", 'none', 'none', "group2"})
|
assert Enum.member?(result, {@user<>"2@"<>@domain, "", 'none', 'none', "group2"})
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp call_command(name, args) do
|
||||||
|
:ejabberd_commands.execute_command2(name, args, %{:caller_module => :ejabberd_ctl})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp call_command(name, args, mode) do
|
||||||
|
call_command(name, args)
|
||||||
|
end
|
||||||
|
|
||||||
# kick_user command is defined in ejabberd_sm, move to extra?
|
# kick_user command is defined in ejabberd_sm, move to extra?
|
||||||
# test "kick_user works" do
|
# test "kick_user works" do
|
||||||
# assert 0 == :ejabberd_commands.execute_command(:num_resources,
|
# assert 0 == call_command(:num_resources,
|
||||||
# [@user, @domain])
|
# [@user, @domain])
|
||||||
# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"1")
|
# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"1")
|
||||||
# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"2")
|
# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"2")
|
||||||
# assert 2 ==
|
# assert 2 ==
|
||||||
# :ejabberd_commands.execute_command(:kick_user, [@user, @domain])
|
# call_command(:kick_user, [@user, @domain])
|
||||||
# assert 0 == :ejabberd_commands.execute_command(:num_resources,
|
# assert 0 == call_command(:num_resources,
|
||||||
# [@user, @domain])
|
# [@user, @domain])
|
||||||
# assert :meck.validate :ejabberd_sm
|
# assert :meck.validate :ejabberd_sm
|
||||||
# end
|
# end
|
||||||
|
@ -45,10 +45,11 @@ defmodule ModHttpApiMockTest do
|
|||||||
:jid.start
|
:jid.start
|
||||||
:mnesia.start
|
:mnesia.start
|
||||||
:ejabberd_mnesia.start
|
:ejabberd_mnesia.start
|
||||||
:stringprep.start
|
:stringprep.start
|
||||||
|
:ejabberd_hooks.start_link
|
||||||
:ejabberd_config.start([@domain], [])
|
:ejabberd_config.start([@domain], [])
|
||||||
{:ok, _} = :ejabberd_access_permissions.start_link()
|
{:ok, _} = :ejabberd_access_permissions.start_link()
|
||||||
:ejabberd_commands.init
|
:ejabberd_commands.start_link
|
||||||
rescue
|
rescue
|
||||||
_ -> :ok
|
_ -> :ok
|
||||||
end
|
end
|
||||||
@ -73,18 +74,12 @@ defmodule ModHttpApiMockTest do
|
|||||||
fn (@acommand, %{usr: {@user, @domain, _}}, @version) ->
|
fn (@acommand, %{usr: {@user, @domain, _}}, @version) ->
|
||||||
{[], {:res, :rescode}}
|
{[], {:res, :rescode}}
|
||||||
end)
|
end)
|
||||||
:meck.expect(:ejabberd_commands, :get_command_policy_and_scope,
|
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
||||||
fn (@acommand) -> {:ok, :user, [:erlang.atom_to_binary(@acommand,:utf8)]} end)
|
|
||||||
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
|
||||||
fn () -> [@acommand] end)
|
fn () -> [@acommand] end)
|
||||||
:meck.expect(:ejabberd_commands, :execute_command2,
|
:meck.expect(:ejabberd_commands, :execute_command2,
|
||||||
fn (@acommand, [], %{usr: {@user, @domain, _}}, @version) ->
|
fn (@acommand, [], %{usr: {@user, @domain, _}}, @version) ->
|
||||||
:ok
|
:ok
|
||||||
end)
|
end)
|
||||||
:meck.expect(:ejabberd_commands, :execute_command,
|
|
||||||
fn (:undefined, {@user, @domain, @userpass, false}, @acommand, [], @version, _) ->
|
|
||||||
:ok
|
|
||||||
end)
|
|
||||||
|
|
||||||
:ejabberd_config.add_local_option(:commands, [[{:add_commands, [@acommand]}]])
|
:ejabberd_config.add_local_option(:commands, [[{:add_commands, [@acommand]}]])
|
||||||
|
|
||||||
@ -130,9 +125,7 @@ defmodule ModHttpApiMockTest do
|
|||||||
fn (@acommand, %{usr: {@user, @domain, _}}, @version) ->
|
fn (@acommand, %{usr: {@user, @domain, _}}, @version) ->
|
||||||
{[], {:res, :rescode}}
|
{[], {:res, :rescode}}
|
||||||
end)
|
end)
|
||||||
:meck.expect(:ejabberd_commands, :get_command_policy_and_scope,
|
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
||||||
fn (@acommand) -> {:ok, :user, [:erlang.atom_to_binary(@acommand,:utf8), "ejabberd:user"]} end)
|
|
||||||
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
|
||||||
fn () -> [@acommand] end)
|
fn () -> [@acommand] end)
|
||||||
:meck.expect(:ejabberd_commands, :execute_command2,
|
:meck.expect(:ejabberd_commands, :execute_command2,
|
||||||
fn (@acommand, [], %{usr: {@user, @domain, _}, oauth_scope: ["ejabberd:user"]}, @version) ->
|
fn (@acommand, [], %{usr: {@user, @domain, _}, oauth_scope: ["ejabberd:user"]}, @version) ->
|
||||||
@ -142,11 +135,6 @@ defmodule ModHttpApiMockTest do
|
|||||||
(@acommand, [], %{usr: {@user, @domain, _}, oauth_scope: _}, @version) ->
|
(@acommand, [], %{usr: {@user, @domain, _}, oauth_scope: _}, @version) ->
|
||||||
throw({:error, :access_rules_unauthorized})
|
throw({:error, :access_rules_unauthorized})
|
||||||
end)
|
end)
|
||||||
:meck.expect(:ejabberd_commands, :execute_command,
|
|
||||||
fn (:undefined, {@user, @domain, {:oauth, _token}, false},
|
|
||||||
@acommand, [], @version, _) ->
|
|
||||||
:ok
|
|
||||||
end)
|
|
||||||
|
|
||||||
|
|
||||||
# Correct OAuth call using specific scope
|
# Correct OAuth call using specific scope
|
||||||
@ -231,15 +219,8 @@ defmodule ModHttpApiMockTest do
|
|||||||
fn (@acommand, {@user, @domain, {:oauth, _token}, false}, @version) ->
|
fn (@acommand, {@user, @domain, {:oauth, _token}, false}, @version) ->
|
||||||
{[], {:res, :rescode}}
|
{[], {:res, :rescode}}
|
||||||
end)
|
end)
|
||||||
:meck.expect(:ejabberd_commands, :get_command_policy_and_scope,
|
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
||||||
fn (@acommand) -> {:ok, :user, [:erlang.atom_to_binary(@acommand,:utf8), "ejabberd:user"]} end)
|
|
||||||
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
|
||||||
fn () -> [@acommand] end)
|
fn () -> [@acommand] end)
|
||||||
:meck.expect(:ejabberd_commands, :execute_command,
|
|
||||||
fn (:undefined, {@user, @domain, {:oauth, _token}, false},
|
|
||||||
@acommand, [], @version, _) ->
|
|
||||||
:ok
|
|
||||||
end)
|
|
||||||
|
|
||||||
#Mock acl to allow oauth authorizations
|
#Mock acl to allow oauth authorizations
|
||||||
:meck.expect(:acl, :match_rule, fn(_Server, _Access, _Jid) -> :allow end)
|
:meck.expect(:acl, :match_rule, fn(_Server, _Access, _Jid) -> :allow end)
|
||||||
|
@ -31,9 +31,11 @@ defmodule ModHttpApiTest do
|
|||||||
:ok = :mnesia.start
|
:ok = :mnesia.start
|
||||||
:ejabberd_mnesia.start
|
:ejabberd_mnesia.start
|
||||||
:stringprep.start
|
:stringprep.start
|
||||||
|
:ejabberd_hooks.start_link
|
||||||
:ok = :ejabberd_config.start(["localhost"], [])
|
:ok = :ejabberd_config.start(["localhost"], [])
|
||||||
|
:acl.start_link
|
||||||
{:ok, _} = :ejabberd_access_permissions.start_link()
|
{:ok, _} = :ejabberd_access_permissions.start_link()
|
||||||
:ok = :ejabberd_commands.init
|
{:ok, _} = :ejabberd_commands.start_link
|
||||||
:ok = :ejabberd_commands.register_commands(cmds)
|
:ok = :ejabberd_commands.register_commands(cmds)
|
||||||
on_exit fn ->
|
on_exit fn ->
|
||||||
:meck.unload
|
:meck.unload
|
||||||
@ -42,7 +44,7 @@ defmodule ModHttpApiTest do
|
|||||||
|
|
||||||
test "We can expose several commands to API at a time" do
|
test "We can expose several commands to API at a time" do
|
||||||
setup_mocks()
|
setup_mocks()
|
||||||
:ejabberd_commands.expose_commands([:open_cmd, :user_cmd])
|
assert :ok == :ejabberd_commands.expose_commands([:open_cmd, :user_cmd])
|
||||||
commands = :ejabberd_commands.get_exposed_commands()
|
commands = :ejabberd_commands.get_exposed_commands()
|
||||||
assert Enum.member?(commands, :open_cmd)
|
assert Enum.member?(commands, :open_cmd)
|
||||||
assert Enum.member?(commands, :user_cmd)
|
assert Enum.member?(commands, :user_cmd)
|
||||||
@ -58,14 +60,14 @@ defmodule ModHttpApiTest do
|
|||||||
# This related to the commands config file option
|
# This related to the commands config file option
|
||||||
test "Attempting to access a command that is not exposed as HTTP API returns 403" do
|
test "Attempting to access a command that is not exposed as HTTP API returns 403" do
|
||||||
setup_mocks()
|
setup_mocks()
|
||||||
:ejabberd_commands.expose_commands([])
|
assert :ok == :ejabberd_commands.expose_commands([])
|
||||||
request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
|
request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
|
||||||
{403, _, _} = :mod_http_api.process(["open_cmd"], request)
|
{403, _, _} = :mod_http_api.process(["open_cmd"], request)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Call to user, admin or restricted commands without authentication are rejected" do
|
test "Call to user, admin or restricted commands without authentication are rejected" do
|
||||||
setup_mocks()
|
setup_mocks()
|
||||||
:ejabberd_commands.expose_commands([:user_cmd, :admin_cmd, :restricted])
|
assert :ok == :ejabberd_commands.expose_commands([:user_cmd, :admin_cmd, :restricted])
|
||||||
request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
|
request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
|
||||||
{403, _, _} = :mod_http_api.process(["user_cmd"], request)
|
{403, _, _} = :mod_http_api.process(["user_cmd"], request)
|
||||||
{403, _, _} = :mod_http_api.process(["admin_cmd"], request)
|
{403, _, _} = :mod_http_api.process(["admin_cmd"], request)
|
||||||
|
@ -23,6 +23,7 @@ defmodule ModRosterMock do
|
|||||||
|
|
||||||
require Record
|
require Record
|
||||||
Record.defrecord :roster, Record.extract(:roster, from_lib: "ejabberd/include/mod_roster.hrl")
|
Record.defrecord :roster, Record.extract(:roster, from_lib: "ejabberd/include/mod_roster.hrl")
|
||||||
|
Record.defrecord :roster_version, Record.extract(:roster_version, from_lib: "ejabberd/include/mod_roster.hrl")
|
||||||
|
|
||||||
@agent __MODULE__
|
@agent __MODULE__
|
||||||
|
|
||||||
@ -37,6 +38,13 @@ defmodule ModRosterMock do
|
|||||||
|
|
||||||
mock_with_moka module
|
mock_with_moka module
|
||||||
|
|
||||||
|
:ejabberd_mnesia.create(:mod_roster_mnesia, :roster,
|
||||||
|
[ram_copies: [node()],
|
||||||
|
attributes: Keyword.keys(roster(roster())),
|
||||||
|
index: [:us]])
|
||||||
|
:ejabberd_mnesia.create(:mod_roster_mnesia, :roster_version,
|
||||||
|
[ram_copies: [node()],
|
||||||
|
attributes: Keyword.keys(roster_version(roster_version()))])
|
||||||
#:mod_roster.stop(domain)
|
#:mod_roster.stop(domain)
|
||||||
:mod_roster.start(domain, [])
|
:mod_roster.start(domain, [])
|
||||||
end
|
end
|
||||||
@ -92,6 +100,11 @@ defmodule ModRosterMock do
|
|||||||
:moka.load(roster_mock0)
|
:moka.load(roster_mock0)
|
||||||
|
|
||||||
roster_mock = :moka.start(:mod_roster_mnesia)
|
roster_mock = :moka.start(:mod_roster_mnesia)
|
||||||
|
:moka.replace(roster_mock, :init,
|
||||||
|
fn (_host, _opts) ->
|
||||||
|
:ok
|
||||||
|
end)
|
||||||
|
|
||||||
:moka.replace(roster_mock, :gen_mod, :db_type,
|
:moka.replace(roster_mock, :gen_mod, :db_type,
|
||||||
fn (_host, _opts) ->
|
fn (_host, _opts) ->
|
||||||
{:none}
|
{:none}
|
||||||
|
@ -22,17 +22,24 @@ sub get_deps {
|
|||||||
return { } unless $config =~ /\{\s*deps\s*,\s*\[(.*?)\]/s;
|
return { } unless $config =~ /\{\s*deps\s*,\s*\[(.*?)\]/s;
|
||||||
my $sdeps = $1;
|
my $sdeps = $1;
|
||||||
|
|
||||||
while ($sdeps =~ /\{\s*(\w+)\s*,\s*".*?"\s*,\s*\{\s*git\s*,\s*"(.*?)"\s*,\s*(?:{\s*tag\s*,\s*"(.*?)"|"(.*?)" )/sg) {
|
while ($sdeps =~ /\{\s* (\w+) \s*,\s* ".*?" \s*,\s* \{\s*git \s*,\s* "(.*?)" \s*,\s*
|
||||||
|
(?:
|
||||||
|
(?:{\s*tag \s*,\s* "(.*?)") |
|
||||||
|
"(.*?)" |
|
||||||
|
( \{ (?: (?-1) | [^{}]+ )+ \} ) )/sgx) {
|
||||||
next unless not %fdeps or exists $fdeps{$1};
|
next unless not %fdeps or exists $fdeps{$1};
|
||||||
$deps{$1} = { repo => $2, commit => $3 || $4 };
|
$deps{$1} = { repo => $2, commit => $3 || $4 };
|
||||||
}
|
}
|
||||||
return \%deps;
|
return \%deps;
|
||||||
}
|
}
|
||||||
my (%info_updates, %top_deps_updates, %sub_deps_updates, @operations);
|
my (%info_updates, %top_deps_updates, %sub_deps_updates, @operations);
|
||||||
|
my $epoch = 1;
|
||||||
|
|
||||||
sub top_deps {
|
sub top_deps {
|
||||||
state %deps;
|
state %deps;
|
||||||
if (not %deps) {
|
state $my_epoch = $epoch;
|
||||||
|
if (not %deps or $my_epoch != $epoch) {
|
||||||
|
$my_epoch = $epoch;
|
||||||
my $config = slurp "rebar.config";
|
my $config = slurp "rebar.config";
|
||||||
croak "Unable to extract floating_deps" unless $config =~ /\{floating_deps, \[(.*?)\]/s;
|
croak "Unable to extract floating_deps" unless $config =~ /\{floating_deps, \[(.*?)\]/s;
|
||||||
|
|
||||||
@ -45,18 +52,20 @@ sub top_deps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub update_deps_repos {
|
sub update_deps_repos {
|
||||||
|
my ($force) = @_;
|
||||||
my $deps = top_deps();
|
my $deps = top_deps();
|
||||||
|
$epoch++;
|
||||||
mkdir(".deps-update") unless -d ".deps-update";
|
mkdir(".deps-update") unless -d ".deps-update";
|
||||||
for my $dep (keys %{$deps}) {
|
for my $dep (keys %{$deps}) {
|
||||||
my $dd = ".deps-update/$dep";
|
my $dd = ".deps-update/$dep";
|
||||||
if (not -d $dd) {
|
if (not -d $dd) {
|
||||||
say "Downloading $dep...";
|
say "Downloading $dep...";
|
||||||
my $repo = $deps->{$dep}->{repo};
|
my $repo = $deps->{$dep}->{repo};
|
||||||
$repo =~ s/^https?/git/;
|
$repo =~ s!^https?://github.com/!git\@github.com:!;
|
||||||
system("git", "-C", ".deps-update", "clone", $repo);
|
system("git", "-C", ".deps-update", "clone", $repo);
|
||||||
} elsif (time() - stat($dd)->mtime > 24 * 60 * 60) {
|
} elsif (time() - stat($dd)->mtime > 24 * 60 * 60 or $force) {
|
||||||
say "Updating $dep...";
|
say "Updating $dep...";
|
||||||
system("git", "-C", $dd, "fetch");
|
system("git", "-C", $dd, "pull");
|
||||||
touch($dd)
|
touch($dd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,7 +73,9 @@ sub update_deps_repos {
|
|||||||
|
|
||||||
sub sub_deps {
|
sub sub_deps {
|
||||||
state %sub_deps;
|
state %sub_deps;
|
||||||
if (not %sub_deps) {
|
state $my_epoch = $epoch;
|
||||||
|
if (not %sub_deps or $my_epoch != $epoch) {
|
||||||
|
$my_epoch = $epoch;
|
||||||
my $deps = top_deps();
|
my $deps = top_deps();
|
||||||
for my $dep (keys %{$deps}) {
|
for my $dep (keys %{$deps}) {
|
||||||
my $rc = ".deps-update/$dep/rebar.config";
|
my $rc = ".deps-update/$dep/rebar.config";
|
||||||
@ -90,7 +101,9 @@ sub rev_deps_helper {
|
|||||||
|
|
||||||
sub rev_deps {
|
sub rev_deps {
|
||||||
state %rev_deps;
|
state %rev_deps;
|
||||||
if (not %rev_deps) {
|
state $deps_epoch = $epoch;
|
||||||
|
if (not %rev_deps or $deps_epoch != $epoch) {
|
||||||
|
$deps_epoch = $epoch;
|
||||||
my $sub_deps = sub_deps();
|
my $sub_deps = sub_deps();
|
||||||
for my $dep (keys %$sub_deps) {
|
for my $dep (keys %$sub_deps) {
|
||||||
$rev_deps{$_}->{direct}->{$dep} = 1 for keys %{$sub_deps->{$dep}};
|
$rev_deps{$_}->{direct}->{$dep} = 1 for keys %{$sub_deps->{$dep}};
|
||||||
@ -109,13 +122,46 @@ sub update_changelog {
|
|||||||
my $reason = join "\n", map {"* $_"} @reasons;
|
my $reason = join "\n", map {"* $_"} @reasons;
|
||||||
my $content = slurp($cl);
|
my $content = slurp($cl);
|
||||||
if (not $content =~ /^# Version $version/) {
|
if (not $content =~ /^# Version $version/) {
|
||||||
$content = "# Version $version\n\n$reason\n\n$content"
|
$content = "# Version $version\n\n$reason\n\n$content";
|
||||||
} else {
|
} else {
|
||||||
$content =~ s/(# Version $version\n\n)/$1$reason\n/;
|
$content =~ s/(# Version $version\n\n)/$1$reason\n/;
|
||||||
}
|
}
|
||||||
write_file($cl, $content);
|
write_file($cl, $content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub edit_changelog {
|
||||||
|
my ($dep, $version) = @_;
|
||||||
|
my $cl = ".deps-update/$dep/CHANGELOG.md";
|
||||||
|
|
||||||
|
return if not -f $cl;
|
||||||
|
|
||||||
|
my $top_deps = top_deps();
|
||||||
|
my $git_info = deps_git_info();
|
||||||
|
|
||||||
|
say color("red"), "$dep", color("reset"), " ($top_deps->{$dep}->{commit}):";
|
||||||
|
say " $_" for @{$git_info->{$dep}->{new_commits}};
|
||||||
|
say "";
|
||||||
|
|
||||||
|
my $content = slurp($cl);
|
||||||
|
my $old_content = $content;
|
||||||
|
|
||||||
|
if (not $content =~ /^# Version $version/) {
|
||||||
|
$content = "# Version $version\n\n* \n\n$content";
|
||||||
|
} else {
|
||||||
|
$content =~ s/(# Version $version\n\n)/$1* \n/;
|
||||||
|
}
|
||||||
|
write_file($cl, $content);
|
||||||
|
|
||||||
|
system("$ENV{EDITOR} $cl");
|
||||||
|
|
||||||
|
my $new_content = slurp($cl);
|
||||||
|
if ($new_content eq $content) {
|
||||||
|
write_file($cl, $old_content);
|
||||||
|
} else {
|
||||||
|
system("git", "-C", ".deps-update/$dep", "commit", "-a", "-m", "Update changelog");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub update_app_src {
|
sub update_app_src {
|
||||||
my ($dep, $version) = @_;
|
my ($dep, $version) = @_;
|
||||||
my $app = ".deps-update/$dep/src/$dep.app.src";
|
my $app = ".deps-update/$dep/src/$dep.app.src";
|
||||||
@ -130,7 +176,7 @@ sub update_deps_versions {
|
|||||||
my $config = slurp $config_path;
|
my $config = slurp $config_path;
|
||||||
|
|
||||||
for (keys %deps) {
|
for (keys %deps) {
|
||||||
$config =~ s/(\{\s*$_\s*,\s*".*?"\s*,\s*\{\s*git\s*,\s*".*?"\s*,\s*)(?:{\s*tag\s*,\s*"(.*?)"\s*}|"(.*?)" )/$1\{tag, "$deps{$_}"}/s;
|
$config =~ s/(\{\s*$_\s*,\s*".*?"\s*,\s*\{\s*git\s*,\s*".*?"\s*,\s*)(?:{\s*tag\s*,\s*"(.*?)"\s*}|"(.*?)")/$1\{tag, "$deps{$_}"}/s;
|
||||||
}
|
}
|
||||||
|
|
||||||
write_file($config_path, $config);
|
write_file($config_path, $config);
|
||||||
@ -173,7 +219,9 @@ sub cmp_ver {
|
|||||||
|
|
||||||
sub deps_git_info {
|
sub deps_git_info {
|
||||||
state %info;
|
state %info;
|
||||||
if (not %info) {
|
state $my_epoch = $epoch;
|
||||||
|
if (not %info or $my_epoch != $epoch) {
|
||||||
|
$my_epoch = $epoch;
|
||||||
my $deps = top_deps();
|
my $deps = top_deps();
|
||||||
for my $dep (keys %{$deps}) {
|
for my $dep (keys %{$deps}) {
|
||||||
my $dir = ".deps-update/$dep";
|
my $dir = ".deps-update/$dep";
|
||||||
@ -218,10 +266,19 @@ sub schedule_operation {
|
|||||||
my $idx = first { $operations[$_]->{dep} eq $dep } 0..$#operations;
|
my $idx = first { $operations[$_]->{dep} eq $dep } 0..$#operations;
|
||||||
|
|
||||||
if (defined $idx) {
|
if (defined $idx) {
|
||||||
push @{$operations[$idx]->{reasons}}, $reason;
|
my $mop = $operations[$idx];
|
||||||
push @{$operations[$idx]->{operations}}, $op;
|
if (defined $op) {
|
||||||
|
my $oidx = first { $mop->{operations}->[$_]->[0] eq $op->[0] } 0..$#{$mop->{operations}};
|
||||||
|
if (defined $oidx) {
|
||||||
|
$mop->{reasons}->[$oidx] = $reason;
|
||||||
|
$mop->{operations}->[$oidx] = $op;
|
||||||
|
} else {
|
||||||
|
push @{$mop->{reasons}}, $reason;
|
||||||
|
push @{$mop->{operations}}, $op;
|
||||||
|
}
|
||||||
|
}
|
||||||
return if $type eq "update";
|
return if $type eq "update";
|
||||||
$operations[$idx]->{type} = $type;
|
$mop->{type} = $type;
|
||||||
$info_updates{$dep}->{new_commits} = [];
|
$info_updates{$dep}->{new_commits} = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -268,6 +325,7 @@ sub git_push {
|
|||||||
|
|
||||||
update_deps_repos();
|
update_deps_repos();
|
||||||
|
|
||||||
|
MAIN:
|
||||||
while (1) {
|
while (1) {
|
||||||
my $top_deps = top_deps();
|
my $top_deps = top_deps();
|
||||||
my $git_info = deps_git_info();
|
my $git_info = deps_git_info();
|
||||||
@ -295,6 +353,7 @@ while (1) {
|
|||||||
my $cmd = show_commands($old_deps ? (U => "Update dependency") : (),
|
my $cmd = show_commands($old_deps ? (U => "Update dependency") : (),
|
||||||
$changed_deps ? (T => "Tag new release") : (),
|
$changed_deps ? (T => "Tag new release") : (),
|
||||||
@operations ? (A => "Apply changes") : (),
|
@operations ? (A => "Apply changes") : (),
|
||||||
|
R => "Refresh repositiories",
|
||||||
E => "Exit");
|
E => "Exit");
|
||||||
last if $cmd eq "E";
|
last if $cmd eq "E";
|
||||||
|
|
||||||
@ -320,6 +379,9 @@ while (1) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($cmd eq "R") {
|
||||||
|
update_deps_repos(1);
|
||||||
|
}
|
||||||
if ($cmd eq "T") {
|
if ($cmd eq "T") {
|
||||||
while (1) {
|
while (1) {
|
||||||
my @deps_to_tag;
|
my @deps_to_tag;
|
||||||
@ -343,47 +405,85 @@ while (1) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $changelog_updated = 0;
|
||||||
|
|
||||||
if ($cmd eq "A") {
|
if ($cmd eq "A") {
|
||||||
$top_deps = top_deps();
|
APPLY: {
|
||||||
$git_info = deps_git_info();
|
$top_deps = top_deps();
|
||||||
my $sub_deps = sub_deps();
|
$git_info = deps_git_info();
|
||||||
|
my $sub_deps = sub_deps();
|
||||||
|
|
||||||
for my $dep (keys %$top_deps) {
|
for my $dep (keys %$top_deps) {
|
||||||
for my $sdep (keys %{$sub_deps->{$dep}}) {
|
for my $sdep (keys %{$sub_deps->{$dep}}) {
|
||||||
next if $sub_deps->{$dep}->{$sdep}->{commit} eq $top_deps->{$sdep}->{commit};
|
next if not defined $top_deps->{$sdep} or
|
||||||
schedule_operation("update", $dep, $git_info->{$dep}->{new_tag},
|
$sub_deps->{$dep}->{$sdep}->{commit} eq $top_deps->{$sdep}->{commit};
|
||||||
"Updating $sdep to version $top_deps->{$sdep}->{commit}.", [$sdep, $top_deps->{$sdep}->{commit}]);
|
say "$dep $sdep ", $sub_deps->{$dep}->{$sdep}->{commit}, " <=> $sdep ",
|
||||||
}
|
$top_deps->{$sdep}->{commit};
|
||||||
}
|
schedule_operation("update", $dep, $git_info->{$dep}->{new_tag},
|
||||||
|
"Updating $sdep to version $top_deps->{$sdep}->{commit}.",
|
||||||
%info_updates = ();
|
[ $sdep, $top_deps->{$sdep}->{commit} ]);
|
||||||
%top_deps_updates = ();
|
}
|
||||||
%sub_deps_updates = ();
|
|
||||||
|
|
||||||
$top_deps = top_deps();
|
|
||||||
$git_info = deps_git_info();
|
|
||||||
$sub_deps = sub_deps();
|
|
||||||
|
|
||||||
my %top_changes;
|
|
||||||
for my $op (@operations) {
|
|
||||||
update_changelog($op->{dep}, $op->{version}, @{$op->{reasons}})
|
|
||||||
if @{$op->{reasons}};
|
|
||||||
update_deps_versions(".deps-update/$op->{dep}/rebar.config", unpairs(@{$op->{operations}}))
|
|
||||||
if @{$op->{operations}};
|
|
||||||
if ($git_info->{$op->{dep}}->{last_tag} ne $op->{version}) {
|
|
||||||
update_app_src($op->{dep}, $op->{version});
|
|
||||||
git_tag($op->{dep}, $op->{version}, "Release $op->{version}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$top_changes{$op->{dep}} = $op->{version};
|
%info_updates = ();
|
||||||
}
|
%top_deps_updates = ();
|
||||||
update_deps_versions("rebar.config", %top_changes);
|
%sub_deps_updates = ();
|
||||||
|
|
||||||
for my $op (@operations) {
|
$top_deps = top_deps();
|
||||||
if ($git_info->{$op->{dep}}->{last_tag} ne $op->{version}) {
|
$git_info = deps_git_info();
|
||||||
git_push($op->{dep});
|
$sub_deps = sub_deps();
|
||||||
|
|
||||||
|
print color("bold blue"), "List of operations:\n", color("reset");
|
||||||
|
for my $op (@operations) {
|
||||||
|
print color("red"), $op->{dep}, color("reset"),
|
||||||
|
" ($top_deps->{$op->{dep}}->{commit} -> $op->{version})";
|
||||||
|
if (@{$op->{operations}}) {
|
||||||
|
say ":";
|
||||||
|
say " $_->[0] -> $_->[1]" for @{$op->{operations}};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
say "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
say "";
|
||||||
|
my %to_tag;
|
||||||
|
if (not $changelog_updated) {
|
||||||
|
for my $op (@operations) {
|
||||||
|
if ($git_info->{$op->{dep}}->{last_tag} ne $op->{version}) {
|
||||||
|
$to_tag{$op->{dep}} = $op->{version};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
my $cmd = show_commands(A => "Apply", (%to_tag ? (U => "Update Changelogs") : ()), E => "Exit");
|
||||||
|
if ($cmd eq "U") {
|
||||||
|
for my $dep (keys %to_tag) {
|
||||||
|
edit_changelog($dep, $to_tag{$dep});
|
||||||
|
}
|
||||||
|
redo APPLY;
|
||||||
|
}
|
||||||
|
elsif ($cmd eq "A") {
|
||||||
|
my %top_changes;
|
||||||
|
for my $op (@operations) {
|
||||||
|
update_changelog($op->{dep}, $op->{version}, @{$op->{reasons}})
|
||||||
|
if @{$op->{reasons}};
|
||||||
|
update_deps_versions(".deps-update/$op->{dep}/rebar.config", unpairs(@{$op->{operations}}))
|
||||||
|
if @{$op->{operations}};
|
||||||
|
if ($git_info->{$op->{dep}}->{last_tag} ne $op->{version}) {
|
||||||
|
update_app_src($op->{dep}, $op->{version});
|
||||||
|
git_tag($op->{dep}, $op->{version}, "Release $op->{version}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$top_changes{$op->{dep}} = $op->{version};
|
||||||
|
}
|
||||||
|
update_deps_versions("rebar.config", %top_changes);
|
||||||
|
for my $op (@operations) {
|
||||||
|
if ($git_info->{$op->{dep}}->{last_tag} ne $op->{version}) {
|
||||||
|
git_push($op->{dep});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last MAIN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user