Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1820b4f63b | |||
| 63aabed320 | |||
| fd7bf7fed3 | |||
| 7a90cda8ff | |||
| 35eeaa5869 | |||
| 024713a441 | |||
| 32fbfe1981 | |||
| 9c5427e0c2 | |||
| 8f5a1c4a2a | |||
| 7d3609d954 | |||
| fc7ba53c37 | |||
| a96d72330d | |||
| 7d626b4f5c | |||
| e903348dd3 | |||
| bee251d928 | |||
| c658907331 | |||
| 3fec782494 | |||
| 52525eb76d | |||
| 8679cfd2f3 | |||
| 25af3fb029 | |||
| 2bceebc39d | |||
| 92532a0d66 | |||
| b673539a2a | |||
| e1aaa1c99d | |||
| aa9eb001d0 | |||
| 101e808124 | |||
| 766b7c65a6 | |||
| 0516a3dc0e | |||
| 3d185c0fb8 | |||
| 7815164216 | |||
| 65f4094804 | |||
| 06450f4a82 | |||
| ce0beb550c | |||
| 96c0483533 | |||
| 1274bcdba9 | |||
| 5dcc97c85a | |||
| 6deceeec2e | |||
| 9edcbadd60 | |||
| f65492e27f | |||
| 1df61a82c8 | |||
| 73509686f1 | |||
| 93e521d65e | |||
| 50511fcff7 | |||
| 5e26190b98 | |||
| f22bd6eb46 | |||
| 0ba6c78ed0 | |||
| 636d68e0a9 | |||
| 7e6d1c24c2 | |||
| 67918b17d3 | |||
| 51fa438340 | |||
| 35a11526f9 | |||
| 0cfec92c14 | |||
| e5f64bc24a | |||
| 5c48ba4609 | |||
| 3ca62a797a | |||
| b66dab1313 | |||
| 58110e4bc1 | |||
| ea96615460 | |||
| b8c26671c4 | |||
| 2e88d001d6 | |||
| 1a0db3de3a | |||
| 250876ea1a | |||
| 66510c1d78 | |||
| d6f1d3df5b | |||
| 72dbb6e7c1 | |||
| b1082a96c9 | |||
| ed17586cf0 | |||
| 26b8dd75f7 | |||
| c3473c2077 | |||
| e216654c52 | |||
| cc3391cc1c | |||
| b8d56a7c11 | |||
| 1bb9e83501 | |||
| de1a66dfbe | |||
| 95613a11ab | |||
| 2cd193f97c | |||
| 33a9d6a3c3 | |||
| fdb863ce70 | |||
| 43fc29873e | |||
| e216a54ead | |||
| d4cdc3a246 | |||
| f6bdc6fdb2 | |||
| 8f25baada6 | |||
| aaef1a14b4 | |||
| 22e8f5fd51 | |||
| 88ab787ba6 | |||
| eb9faffadd | |||
| 3b0eee785f | |||
| 5ef542a638 | |||
| ffdaff3740 | |||
| 56d273477e | |||
| c69720a1ab |
+1
-1
@@ -1,7 +1,7 @@
|
||||
FROM debian:jessie-slim
|
||||
MAINTAINER Rafael Römhild <rafael@roemhild.de>
|
||||
|
||||
ENV EJABBERD_BRANCH=17.04 \
|
||||
ENV EJABBERD_BRANCH=17.08 \
|
||||
EJABBERD_USER=ejabberd \
|
||||
EJABBERD_HTTPS=true \
|
||||
EJABBERD_STARTTLS=true \
|
||||
|
||||
@@ -116,11 +116,14 @@ To compile ejabberd you need:
|
||||
needed on systems with GNU Libc.
|
||||
- ImageMagick's Convert program. Optional. For CAPTCHA challenges.
|
||||
|
||||
If your system splits packages in libraries and development headers, you must
|
||||
install the development packages also.
|
||||
|
||||
### 1. Compile and install on *nix systems
|
||||
|
||||
To compile ejabberd, execute the following commands. The first one is only
|
||||
necessary if your source tree didn't come with a `configure` script.
|
||||
necessary if your source tree didn't come with a `configure` script (In this
|
||||
case you need autoconf installed).
|
||||
|
||||
./autogen.sh
|
||||
./configure
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
|
||||
AC_PREREQ(2.53)
|
||||
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 0.0` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
|
||||
REQUIRE_ERLANG_MIN="6.1 (Erlang/OTP 17.1)"
|
||||
REQUIRE_ERLANG_MAX="9.0.0 (No Max)"
|
||||
REQUIRE_ERLANG_MIN="6.4 (Erlang/OTP 17.5)"
|
||||
REQUIRE_ERLANG_MAX="100.0.0 (No Max)"
|
||||
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
|
||||
|
||||
@@ -725,6 +725,8 @@ modules:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep" # pep requires mod_caps
|
||||
mod_push: {}
|
||||
mod_push_keepalive: {}
|
||||
## mod_register:
|
||||
##
|
||||
## Protect In-Band account registrations with CAPTCHA.
|
||||
|
||||
+32
-26
@@ -13,23 +13,30 @@ ERLANG_NODE=ejabberd@localhost
|
||||
ERL="{{erl}}"
|
||||
IEX="{{bindir}}/iex"
|
||||
EPMD="{{epmd}}"
|
||||
INSTALLUSER={{installuser}}
|
||||
INSTALLUSER="{{installuser}}"
|
||||
|
||||
# check the proper system user is used if defined
|
||||
EXEC_CMD="false"
|
||||
if [ -n "$INSTALLUSER" ] ; then
|
||||
if [ $(id -g) -eq $(id -g $INSTALLUSER || echo -1) ] ; then
|
||||
# check the proper system user is used
|
||||
case `id -un` in
|
||||
"$INSTALLUSER")
|
||||
EXEC_CMD="as_current_user"
|
||||
else
|
||||
id -Gn | grep -q wheel && EXEC_CMD="as_install_user"
|
||||
fi
|
||||
else
|
||||
EXEC_CMD="as_current_user"
|
||||
fi
|
||||
if [ "$EXEC_CMD" = "false" ] ; then
|
||||
echo "ERROR: This command can only be run by root or the user $INSTALLUSER" >&2
|
||||
exit 7
|
||||
fi
|
||||
;;
|
||||
root)
|
||||
if [ -n "$INSTALLUSER" ] ; then
|
||||
EXEC_CMD="as_install_user"
|
||||
else
|
||||
EXEC_CMD="as_current_user"
|
||||
echo "WARNING: This is not recommended to run ejabberd as root" >&2
|
||||
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
|
||||
for arg; do
|
||||
@@ -62,14 +69,12 @@ done
|
||||
# define erl parameters
|
||||
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
|
||||
if [ "$FIREWALL_WINDOW" != "" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel " \
|
||||
"inet_dist_listen_min ${FIREWALL_WINDOW%-*} " \
|
||||
"inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||
fi
|
||||
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)
|
||||
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
|
||||
ERL_LIBS={{libdir}}
|
||||
@@ -103,7 +108,7 @@ export ERL_LIBS
|
||||
exec_cmd()
|
||||
{
|
||||
case $EXEC_CMD in
|
||||
as_install_user) su -c '"$0" $@"' "$INSTALLUSER" -- "$@" ;;
|
||||
as_install_user) su -c '"$0" "$@"' "$INSTALLUSER" -- "$@" ;;
|
||||
as_current_user) "$@" ;;
|
||||
esac
|
||||
}
|
||||
@@ -223,12 +228,6 @@ check_start()
|
||||
"$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 +252,13 @@ wait_status()
|
||||
[ $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
|
||||
case $1 in
|
||||
start)
|
||||
|
||||
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
|
||||
|
||||
def project do
|
||||
[app: :ejabberd,
|
||||
version: "17.6.0",
|
||||
version: "17.8.0",
|
||||
description: description(),
|
||||
elixir: "~> 1.4",
|
||||
elixirc_paths: ["lib"],
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
%{"cache_tab": {:hex, :cache_tab, "1.0.8", "eac8923f0f20c35e630317790c4d4c2629c5bc792753fa48eb5391bd39c80245", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"distillery": {:hex, :distillery, "1.4.0", "d633cd322c8efa0428082b00b7f902daf8caa166d45f9022bbc19a896d2e1e56", [:mix], []},
|
||||
"earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], []},
|
||||
"esip": {:hex, :esip, "1.0.12", "e0505afe74bb362b0ea486e2a64b3c1934b1eb541a7b3e990b23045e4bdc07d4", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.11", [hex: :stun, optional: false]}]},
|
||||
"ex_doc": {:hex, :ex_doc, "0.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
|
||||
%{"cache_tab": {:hex, :cache_tab, "1.0.10", "dd6aba8951ba15cab4ad483d997f8eefdb0cb00225971d0629c730d107a2bed6", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"distillery": {:hex, :distillery, "1.4.1", "546d851bf27ae8fe0727e10e4fc4e146ad836eecee138263a60431e688044ed3", [:mix], []},
|
||||
"earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [:mix], []},
|
||||
"eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
|
||||
"esip": {:hex, :esip, "1.0.15", "82c8b0178618c10b1ac9690841d94025c982d63f8cd6c8f8bf920cf33e301658", [:rebar3], [{:fast_tls, "1.0.15", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.14", [hex: :stun, optional: false]}]},
|
||||
"ex_doc": {:hex, :ex_doc, "0.16.2", "3b3e210ebcd85a7c76b4e73f85c5640c011d2a0b2f06dcdf5acdb2ae904e5084", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
|
||||
"ezlib": {:hex, :ezlib, "1.0.2", "22004ecf553a7d831404394d5642712e2aede90522e22bd6ccc089ca410ee098", [:rebar3], []},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.12", "861b591f23103142782c5b72de8898673a37acd78646c50dbda978e1e1c5b463", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_tls": {:hex, :fast_tls, "1.0.15", "96546e6a8b8384fbbcddf435c4c42cf2c0a3dc1858c3c9c2e62a74ae1ddd526a", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.23", "1e7b311d3353806ee832d7630fef57713987cea40a7020669cf057d537de4721", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.10", "ce5d52b77cb21968c8b73aa29b39f56a4ffd7e1e11f853d5597e7277858f155e", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], []},
|
||||
"iconv": {:hex, :iconv, "1.0.5", "ae871aa11c854695db37e48fd5e5583b02e106126fbdf21bb53448f5a47c092b", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"jiffy": {:hex, :jiffy, "0.14.11", "919a87d491c5a6b5e3bbc27fafedc3a0761ca0b4c405394f121f582fd4e3f0e5", [:rebar3], []},
|
||||
"lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.3", "e2cc26f2e8d17c3885a9c2fee3ff64fcac5915896f50ab6f6aa9b0da1eed341c", [:rebar3], []},
|
||||
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []},
|
||||
"p1_pgsql": {:hex, :p1_pgsql, "1.1.3", "ce94c83e9605c88d5f541b8f4b49edff3dc2bbacd1b6409c4cad0fbf7bef2ac4", [:rebar3], []},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.9", "c33c230efbeb4dcc02911161e3cb1a93231a92df15e3fc97de655a9271a26d9f", [:rebar3], []},
|
||||
"sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []},
|
||||
"stringprep": {:hex, :stringprep, "1.0.9", "9182ba39931cd1db528b8883cad0d63530abe2bf21835d26cec2f9af8bc00be0", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"stun": {:hex, :stun, "1.0.11", "386cb3e3543e17a6351028a43e047c2172225d035c826a72fcb67672da9874e5", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"xmpp": {:hex, :xmpp, "1.1.11", "8c49964d0d48b81080d2c5700fcf6cc19950ae9dc60a71bd3ff3d4620336d052", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}}
|
||||
"stun": {:hex, :stun, "1.0.14", "6dc2080c25a72f7087301dc7333c1ea7d27ea4d88efaa379fc2b5924f3b17006", [:rebar3], [{:fast_tls, "1.0.15", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
|
||||
"xmpp": {:hex, :xmpp, "1.1.14", "e186f5208e7a448a4af784a8d2cb87cefe99dd49b24623e25d38115b23a50e12", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}}
|
||||
|
||||
+8
-7
@@ -21,17 +21,17 @@
|
||||
{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"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.9"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.13"}}},
|
||||
{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.15"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.13"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.14"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.10"}}},
|
||||
{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"}}},
|
||||
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.12"}}}},
|
||||
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.13"}}}},
|
||||
{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.15"}}}},
|
||||
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
|
||||
{tag, "1.0.3"}}}},
|
||||
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
|
||||
@@ -71,11 +71,12 @@
|
||||
p1_utils,
|
||||
p1_mysql,
|
||||
p1_pgsql,
|
||||
p1_oauth2,
|
||||
epam,
|
||||
ezlib,
|
||||
iconv]}}.
|
||||
|
||||
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl"]}.
|
||||
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl", "src/mod_push.erl"]}.
|
||||
|
||||
{erl_opts, [nowarn_deprecated_function,
|
||||
{i, "include"},
|
||||
|
||||
+2
-23
@@ -25,12 +25,11 @@
|
||||
|
||||
-module(ejabberd_app).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-export([start/2, prep_stop/1, stop/1, opt_type/1]).
|
||||
-export([start/2, prep_stop/1, stop/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -49,13 +48,12 @@ start(normal, _Args) ->
|
||||
setup_if_elixir_conf_used(),
|
||||
ejabberd_config:start(),
|
||||
ejabberd_mnesia:start(),
|
||||
set_settings_from_config(),
|
||||
file_queue_init(),
|
||||
maybe_add_nameservers(),
|
||||
connect_nodes(),
|
||||
case ejabberd_sup:start_link() of
|
||||
{ok, SupPid} ->
|
||||
register_elixir_config_hooks(),
|
||||
ejabberd_cluster:wait_for_sync(infinity),
|
||||
{T2, _} = statistics(wall_clock),
|
||||
?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs",
|
||||
[?VERSION, node(), (T2-T1)/1000]),
|
||||
@@ -88,12 +86,6 @@ stop(_State) ->
|
||||
%%% Internal functions
|
||||
%%%
|
||||
|
||||
connect_nodes() ->
|
||||
Nodes = ejabberd_config:get_option(cluster_nodes, []),
|
||||
lists:foreach(fun(Node) ->
|
||||
net_kernel:connect_node(Node)
|
||||
end, Nodes).
|
||||
|
||||
%% If ejabberd is running on some Windows machine, get nameservers and add to Erlang
|
||||
maybe_add_nameservers() ->
|
||||
case os:type() of
|
||||
@@ -136,10 +128,6 @@ delete_pid_file() ->
|
||||
file:delete(PidFilename)
|
||||
end.
|
||||
|
||||
set_settings_from_config() ->
|
||||
Ticktime = ejabberd_config:get_option(net_ticktime, 60),
|
||||
net_kernel:set_net_ticktime(Ticktime).
|
||||
|
||||
file_queue_init() ->
|
||||
QueueDir = case ejabberd_config:queue_dir() of
|
||||
undefined ->
|
||||
@@ -160,15 +148,6 @@ start_apps() ->
|
||||
ejabberd:start_app(xmpp),
|
||||
ejabberd:start_app(cache_tab).
|
||||
|
||||
-spec opt_type(net_ticktime) -> fun((pos_integer()) -> pos_integer());
|
||||
(cluster_nodes) -> fun(([node()]) -> [node()]);
|
||||
(atom()) -> atom().
|
||||
opt_type(net_ticktime) ->
|
||||
fun (P) when is_integer(P), P > 0 -> P end;
|
||||
opt_type(cluster_nodes) ->
|
||||
fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
|
||||
opt_type(_) -> [cluster_nodes, net_ticktime].
|
||||
|
||||
setup_if_elixir_conf_used() ->
|
||||
case ejabberd_config:is_using_elixir_config() of
|
||||
true -> 'Elixir.Ejabberd.Config.Store':start_link();
|
||||
|
||||
@@ -261,12 +261,12 @@ try_register(User, Server, Password) ->
|
||||
ok;
|
||||
(Mod, _) ->
|
||||
db_try_register(
|
||||
User, Server, Password, Mod)
|
||||
LUser, LServer, Password, Mod)
|
||||
end, {error, not_allowed},
|
||||
auth_modules(LServer)) of
|
||||
ok ->
|
||||
ejabberd_hooks:run(
|
||||
register_user, Server, [User, Server]);
|
||||
register_user, LServer, [LUser, LServer]);
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end;
|
||||
|
||||
@@ -208,6 +208,8 @@ remove_user(User, Server) ->
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
need_transform(#reg_users_counter{}) ->
|
||||
false;
|
||||
need_transform(#passwd{us = {U, S}, password = Pass}) ->
|
||||
if is_binary(Pass) ->
|
||||
case store_type(S) of
|
||||
|
||||
+18
-20
@@ -27,9 +27,7 @@
|
||||
-protocol({xep, 124, '1.11'}).
|
||||
-protocol({xep, 206, '1.4'}).
|
||||
|
||||
-define(GEN_FSM, p1_fsm).
|
||||
|
||||
-behaviour(?GEN_FSM).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% API
|
||||
-export([start/2, start/3, start_link/3]).
|
||||
@@ -137,18 +135,18 @@ start(#body{attrs = Attrs} = Body, IP, SID) ->
|
||||
end.
|
||||
|
||||
start(StateName, State) ->
|
||||
(?GEN_FSM):start_link(?MODULE, [StateName, State],
|
||||
p1_fsm:start_link(?MODULE, [StateName, State],
|
||||
?FSMOPTS).
|
||||
|
||||
start_link(Body, IP, SID) ->
|
||||
(?GEN_FSM):start_link(?MODULE, [Body, IP, SID],
|
||||
p1_fsm:start_link(?MODULE, [Body, IP, SID],
|
||||
?FSMOPTS).
|
||||
|
||||
send({http_bind, FsmRef, IP}, Packet) ->
|
||||
send_xml({http_bind, FsmRef, IP}, Packet).
|
||||
|
||||
send_xml({http_bind, FsmRef, _IP}, Packet) ->
|
||||
case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
|
||||
case catch p1_fsm:sync_send_all_state_event(FsmRef,
|
||||
{send_xml, Packet},
|
||||
?SEND_TIMEOUT)
|
||||
of
|
||||
@@ -160,12 +158,12 @@ send_xml({http_bind, FsmRef, _IP}, Packet) ->
|
||||
setopts({http_bind, FsmRef, _IP}, Opts) ->
|
||||
case lists:member({active, once}, Opts) of
|
||||
true ->
|
||||
(?GEN_FSM):send_all_state_event(FsmRef,
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{activate, self()});
|
||||
_ ->
|
||||
case lists:member({active, false}, Opts) of
|
||||
true ->
|
||||
case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
|
||||
case catch p1_fsm:sync_send_all_state_event(FsmRef,
|
||||
deactivate_socket)
|
||||
of
|
||||
{'EXIT', _} -> {error, einval};
|
||||
@@ -181,7 +179,7 @@ custom_receiver({http_bind, FsmRef, _IP}) ->
|
||||
{receiver, ?MODULE, FsmRef}.
|
||||
|
||||
become_controller(FsmRef, C2SPid) ->
|
||||
(?GEN_FSM):send_all_state_event(FsmRef,
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{become_controller, C2SPid}).
|
||||
|
||||
change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
|
||||
@@ -190,14 +188,14 @@ change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
|
||||
reset_stream({http_bind, _FsmRef, _IP}) -> ok.
|
||||
|
||||
change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
|
||||
(?GEN_FSM):send_all_state_event(FsmRef,
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{change_shaper, Shaper}).
|
||||
|
||||
monitor({http_bind, FsmRef, _IP}) ->
|
||||
erlang:monitor(process, FsmRef).
|
||||
|
||||
close({http_bind, FsmRef, _IP}) ->
|
||||
catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
|
||||
catch p1_fsm:sync_send_all_state_event(FsmRef,
|
||||
close).
|
||||
|
||||
sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
|
||||
@@ -269,7 +267,7 @@ process_request(Data, IP, Type) ->
|
||||
end.
|
||||
|
||||
process_request(Pid, Req, _IP, Type) ->
|
||||
case catch (?GEN_FSM):sync_send_event(Pid, Req,
|
||||
case catch p1_fsm:sync_send_event(Pid, Req,
|
||||
infinity)
|
||||
of
|
||||
#body{} = Resp -> bosh_response(Resp, Type);
|
||||
@@ -571,7 +569,7 @@ handle_sync_event({send_xml, El}, _From, StateName,
|
||||
of
|
||||
{{value, {TRef, From, Body}}, Q} ->
|
||||
cancel_timer(TRef),
|
||||
(?GEN_FSM):send_event(self(), {Body, From}),
|
||||
p1_fsm:send_event(self(), {Body, From}),
|
||||
State1#state{shaped_receivers = Q};
|
||||
_ -> State1
|
||||
end,
|
||||
@@ -598,7 +596,7 @@ handle_info({timeout, TRef, shaper_timeout}, StateName,
|
||||
State) ->
|
||||
case p1_queue:out(State#state.shaped_receivers) of
|
||||
{{value, {TRef, From, Req}}, Q} ->
|
||||
(?GEN_FSM):send_event(self(), {Req, From}),
|
||||
p1_fsm:send_event(self(), {Req, From}),
|
||||
{next_state, StateName,
|
||||
State#state{shaped_receivers = Q}};
|
||||
{{value, _}, _} ->
|
||||
@@ -630,7 +628,7 @@ terminate(_Reason, _StateName, State) ->
|
||||
mod_bosh:close_session(State#state.sid),
|
||||
case State#state.c2s_pid of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
(?GEN_FSM):send_event(C2SPid, closed);
|
||||
p1_fsm:send_event(C2SPid, closed);
|
||||
_ -> ok
|
||||
end,
|
||||
bounce_receivers(State, closed),
|
||||
@@ -644,7 +642,7 @@ print_state(State) -> State.
|
||||
route_els(#state{el_ibuf = Buf, c2s_pid = C2SPid} = State) ->
|
||||
NewBuf = p1_queue:dropwhile(
|
||||
fun(El) ->
|
||||
?GEN_FSM:send_event(C2SPid, El),
|
||||
p1_fsm:send_event(C2SPid, El),
|
||||
true
|
||||
end, Buf),
|
||||
State#state{el_ibuf = NewBuf}.
|
||||
@@ -653,7 +651,7 @@ route_els(State, Els) ->
|
||||
case State#state.c2s_pid of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
lists:foreach(fun (El) ->
|
||||
(?GEN_FSM):send_event(C2SPid, El)
|
||||
p1_fsm:send_event(C2SPid, El)
|
||||
end,
|
||||
Els),
|
||||
State;
|
||||
@@ -676,7 +674,7 @@ reply(State, Body, RID, From) ->
|
||||
case catch gb_trees:take_smallest(Receivers) of
|
||||
{NextRID, {From1, Req}, Receivers1}
|
||||
when NextRID == RID + 1 ->
|
||||
(?GEN_FSM):send_event(self(), {Req, From1}),
|
||||
p1_fsm:send_event(self(), {Req, From1}),
|
||||
State2#state{receivers = Receivers1};
|
||||
_ -> State2#state{receivers = Receivers}
|
||||
end.
|
||||
@@ -715,7 +713,7 @@ do_reply(State, From, Body, RID) ->
|
||||
?DEBUG("send reply:~n** RequestID: ~p~n** Reply: "
|
||||
"~p~n** To: ~p~n** State: ~p",
|
||||
[RID, Body, From, State]),
|
||||
(?GEN_FSM):reply(From, Body),
|
||||
p1_fsm:reply(From, Body),
|
||||
Responses = gb_trees:delete_any(RID,
|
||||
State#state.responses),
|
||||
Responses1 = case gb_trees:size(Responses) of
|
||||
@@ -1053,7 +1051,7 @@ buf_out(Buf, I, Els) ->
|
||||
end.
|
||||
|
||||
cancel_timer(TRef) when is_reference(TRef) ->
|
||||
(?GEN_FSM):cancel_timer(TRef);
|
||||
p1_fsm:cancel_timer(TRef);
|
||||
cancel_timer(_) -> false.
|
||||
|
||||
restart_timer(TRef, Timeout, Msg) ->
|
||||
|
||||
+26
-10
@@ -46,8 +46,8 @@
|
||||
reject_unauthenticated_packet/2, process_closed/2,
|
||||
process_terminated/2, process_info/2]).
|
||||
%% API
|
||||
-export([get_presence/1, resend_presence/1, resend_presence/2,
|
||||
open_session/1, call/3, send/2, close/1, close/2, stop/1,
|
||||
-export([get_presence/1, set_presence/2, resend_presence/1, resend_presence/2,
|
||||
open_session/1, call/3, cast/2, send/2, close/1, close/2, stop/1,
|
||||
reply/2, copy_state/2, set_timeout/2, route/2,
|
||||
host_up/1, host_down/1]).
|
||||
|
||||
@@ -90,6 +90,10 @@ socket_type() ->
|
||||
call(Ref, Msg, Timeout) ->
|
||||
xmpp_stream_in:call(Ref, Msg, Timeout).
|
||||
|
||||
-spec cast(pid(), term()) -> ok.
|
||||
cast(Ref, Msg) ->
|
||||
xmpp_stream_in:cast(Ref, Msg).
|
||||
|
||||
reply(Ref, Reply) ->
|
||||
xmpp_stream_in:reply(Ref, Reply).
|
||||
|
||||
@@ -97,6 +101,10 @@ reply(Ref, Reply) ->
|
||||
get_presence(Ref) ->
|
||||
call(Ref, get_presence, 1000).
|
||||
|
||||
-spec set_presence(pid(), presence()) -> ok.
|
||||
set_presence(Ref, Pres) ->
|
||||
call(Ref, {set_presence, Pres}, 1000).
|
||||
|
||||
-spec resend_presence(pid()) -> ok.
|
||||
resend_presence(Pid) ->
|
||||
resend_presence(Pid, undefined).
|
||||
@@ -289,14 +297,19 @@ process_terminated(State, _Reason) ->
|
||||
%%%===================================================================
|
||||
%%% xmpp_stream_in callbacks
|
||||
%%%===================================================================
|
||||
tls_options(#{lserver := LServer, tls_options := DefaultOpts}) ->
|
||||
TLSOpts1 = case ejabberd_config:get_option(
|
||||
{c2s_certfile, LServer},
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, LServer})) of
|
||||
undefined -> DefaultOpts;
|
||||
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
|
||||
{certfile, CertFile})
|
||||
tls_options(#{lserver := LServer, tls_options := DefaultOpts,
|
||||
stream_encrypted := Encrypted}) ->
|
||||
TLSOpts1 = case {Encrypted, proplists:get_value(certfile, DefaultOpts)} of
|
||||
{true, CertFile} when CertFile /= undefined -> DefaultOpts;
|
||||
{_, _} ->
|
||||
case ejabberd_config:get_option(
|
||||
{c2s_certfile, LServer},
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, LServer})) of
|
||||
undefined -> DefaultOpts;
|
||||
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
|
||||
{certfile, CertFile})
|
||||
end
|
||||
end,
|
||||
TLSOpts2 = case ejabberd_config:get_option(
|
||||
{c2s_ciphers, LServer}) of
|
||||
@@ -525,6 +538,9 @@ handle_call(get_presence, From, #{jid := JID} = State) ->
|
||||
end,
|
||||
reply(From, Pres),
|
||||
State;
|
||||
handle_call({set_presence, Pres}, From, State) ->
|
||||
reply(From, ok),
|
||||
process_self_presence(State, Pres);
|
||||
handle_call(Request, From, #{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(
|
||||
c2s_handle_call, LServer, State, [Request, From]).
|
||||
|
||||
+11
-16
@@ -388,29 +388,24 @@ get_transfer_protocol(PortString) ->
|
||||
|
||||
get_port_listeners(PortNumber) ->
|
||||
AllListeners = ejabberd_config:get_option(listen, []),
|
||||
lists:filter(fun (Listener) when is_list(Listener) ->
|
||||
case proplists:get_value(port, Listener) of
|
||||
PortNumber -> true;
|
||||
_ -> false
|
||||
end;
|
||||
(_) -> false
|
||||
end,
|
||||
AllListeners).
|
||||
lists:filter(
|
||||
fun({{Port, _IP, _Transport}, _Module, _Opts}) ->
|
||||
Port == PortNumber
|
||||
end, AllListeners).
|
||||
|
||||
get_captcha_transfer_protocol([]) ->
|
||||
throw(<<"The port number mentioned in captcha_host "
|
||||
"is not a ejabberd_http listener with "
|
||||
"'captcha' option. Change the port number "
|
||||
"or specify http:// in that option.">>);
|
||||
get_captcha_transfer_protocol([Listener | Listeners]) when is_list(Listener) ->
|
||||
case proplists:get_value(module, Listener) == ejabberd_http andalso
|
||||
proplists:get_bool(captcha, Listener) of
|
||||
get_captcha_transfer_protocol([{_, ejabberd_http, Opts} | Listeners]) ->
|
||||
case proplists:get_bool(captcha, Opts) of
|
||||
true ->
|
||||
case proplists:get_bool(tls, Listener) of
|
||||
true -> https;
|
||||
false -> http
|
||||
end;
|
||||
false -> get_captcha_transfer_protocol(Listeners)
|
||||
case proplists:get_bool(tls, Opts) of
|
||||
true -> https;
|
||||
false -> http
|
||||
end;
|
||||
false -> get_captcha_transfer_protocol(Listeners)
|
||||
end;
|
||||
get_captcha_transfer_protocol([_ | Listeners]) ->
|
||||
get_captcha_transfer_protocol(Listeners).
|
||||
|
||||
+142
-88
@@ -1,8 +1,6 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_cluster.erl
|
||||
%%% Author : Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% Purpose : Ejabberd clustering management
|
||||
%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 5 Jul 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
||||
@@ -21,132 +19,188 @@
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_cluster).
|
||||
-behaviour(ejabberd_config).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([get_nodes/0, call/4, multicall/3, multicall/4,
|
||||
eval_everywhere/3, eval_everywhere/4]).
|
||||
-export([join/1, leave/1, get_known_nodes/0]).
|
||||
-export([node_id/0, get_node_by_id/1]).
|
||||
-export([start_link/0, call/4, multicall/3, multicall/4, eval_everywhere/3,
|
||||
eval_everywhere/4]).
|
||||
%% Backend dependent API
|
||||
-export([get_nodes/0, get_known_nodes/0, join/1, leave/1, subscribe/0,
|
||||
subscribe/1, node_id/0, get_node_by_id/1, send/2, wait_for_sync/1]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-spec get_nodes() -> [node()].
|
||||
-type dst() :: pid() | atom() | {atom(), node()}.
|
||||
|
||||
get_nodes() ->
|
||||
mnesia:system_info(running_db_nodes).
|
||||
-callback init() -> ok | {error, any()}.
|
||||
-callback get_nodes() -> [node()].
|
||||
-callback get_known_nodes() -> [node()].
|
||||
-callback join(node()) -> ok | {error, any()}.
|
||||
-callback leave(node()) -> ok | {error, any()}.
|
||||
-callback node_id() -> binary().
|
||||
-callback get_node_by_id(binary()) -> node().
|
||||
-callback send({atom(), node()}, term()) -> boolean().
|
||||
-callback wait_for_sync(timeout()) -> ok | {error, any()}.
|
||||
-callback subscribe(dst()) -> ok.
|
||||
|
||||
-spec get_known_nodes() -> [node()].
|
||||
-record(state, {}).
|
||||
|
||||
get_known_nodes() ->
|
||||
lists:usort(mnesia:system_info(db_nodes)
|
||||
++ mnesia:system_info(extra_db_nodes)).
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
-spec call(node(), module(), atom(), [any()]) -> any().
|
||||
|
||||
call(Node, Module, Function, Args) ->
|
||||
rpc:call(Node, Module, Function, Args, 5000).
|
||||
rpc:call(Node, Module, Function, Args, rpc_timeout()).
|
||||
|
||||
-spec multicall(module(), atom(), [any()]) -> {list(), [node()]}.
|
||||
|
||||
multicall(Module, Function, Args) ->
|
||||
multicall(get_nodes(), Module, Function, Args).
|
||||
|
||||
-spec multicall([node()], module(), atom(), list()) -> {list(), [node()]}.
|
||||
|
||||
multicall(Nodes, Module, Function, Args) ->
|
||||
rpc:multicall(Nodes, Module, Function, Args, 5000).
|
||||
rpc:multicall(Nodes, Module, Function, Args, rpc_timeout()).
|
||||
|
||||
-spec eval_everywhere(module(), atom(), [any()]) -> ok.
|
||||
|
||||
eval_everywhere(Module, Function, Args) ->
|
||||
eval_everywhere(get_nodes(), Module, Function, Args),
|
||||
ok.
|
||||
|
||||
-spec eval_everywhere([node()], module(), atom(), [any()]) -> ok.
|
||||
|
||||
eval_everywhere(Nodes, Module, Function, Args) ->
|
||||
rpc:eval_everywhere(Nodes, Module, Function, Args),
|
||||
ok.
|
||||
|
||||
-spec join(node()) -> ok | {error, any()}.
|
||||
%%%===================================================================
|
||||
%%% Backend dependent API
|
||||
%%%===================================================================
|
||||
-spec get_nodes() -> [node()].
|
||||
get_nodes() ->
|
||||
Mod = get_mod(),
|
||||
Mod:get_nodes().
|
||||
|
||||
-spec get_known_nodes() -> [node()].
|
||||
get_known_nodes() ->
|
||||
Mod = get_mod(),
|
||||
Mod:get_known_nodes().
|
||||
|
||||
-spec join(node()) -> ok | {error, any()}.
|
||||
join(Node) ->
|
||||
case {node(), net_adm:ping(Node)} of
|
||||
{Node, _} ->
|
||||
{error, {not_master, Node}};
|
||||
{_, pong} ->
|
||||
application:stop(ejabberd),
|
||||
application:stop(mnesia),
|
||||
mnesia:delete_schema([node()]),
|
||||
application:start(mnesia),
|
||||
mnesia:change_config(extra_db_nodes, [Node]),
|
||||
mnesia:change_table_copy_type(schema, node(), disc_copies),
|
||||
spawn(fun() ->
|
||||
lists:foreach(fun(Table) ->
|
||||
Type = call(Node, mnesia, table_info, [Table, storage_type]),
|
||||
mnesia:add_table_copy(Table, node(), Type)
|
||||
end, mnesia:system_info(tables)--[schema])
|
||||
end),
|
||||
application:start(ejabberd);
|
||||
_ ->
|
||||
{error, {no_ping, Node}}
|
||||
end.
|
||||
Mod = get_mod(),
|
||||
Mod:join(Node).
|
||||
|
||||
-spec leave(node()) -> ok | {error, any()}.
|
||||
|
||||
leave(Node) ->
|
||||
case {node(), net_adm:ping(Node)} of
|
||||
{Node, _} ->
|
||||
Cluster = get_nodes()--[Node],
|
||||
leave(Cluster, Node);
|
||||
{_, pong} ->
|
||||
rpc:call(Node, ?MODULE, leave, [Node], 10000);
|
||||
{_, pang} ->
|
||||
case mnesia:del_table_copy(schema, Node) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, Reason} -> {error, Reason}
|
||||
end
|
||||
end.
|
||||
leave([], Node) ->
|
||||
{error, {no_cluster, Node}};
|
||||
leave([Master|_], Node) ->
|
||||
application:stop(ejabberd),
|
||||
application:stop(mnesia),
|
||||
call(Master, mnesia, del_table_copy, [schema, Node]),
|
||||
spawn(fun() ->
|
||||
mnesia:delete_schema([node()]),
|
||||
erlang:halt(0)
|
||||
end),
|
||||
ok.
|
||||
Mod = get_mod(),
|
||||
Mod:leave(Node).
|
||||
|
||||
-spec node_id() -> binary().
|
||||
node_id() ->
|
||||
integer_to_binary(erlang:phash2(node())).
|
||||
Mod = get_mod(),
|
||||
Mod:node_id().
|
||||
|
||||
-spec get_node_by_id(binary()) -> node().
|
||||
get_node_by_id(Hash) ->
|
||||
try binary_to_integer(Hash) of
|
||||
I -> match_node_id(I)
|
||||
catch _:_ ->
|
||||
node()
|
||||
get_node_by_id(ID) ->
|
||||
Mod = get_mod(),
|
||||
Mod:get_node_by_id(ID).
|
||||
|
||||
-spec send(dst(), term()) -> boolean().
|
||||
send(Dst, Msg) ->
|
||||
IsLocal = case Dst of
|
||||
{_, Node} -> Node == node();
|
||||
Pid when is_pid(Pid) -> node(Pid) == node();
|
||||
Name when is_atom(Name) -> true;
|
||||
_ -> false
|
||||
end,
|
||||
if IsLocal ->
|
||||
erlang:send(Dst, Msg),
|
||||
true;
|
||||
true ->
|
||||
Mod = get_mod(),
|
||||
Mod:send(Dst, Msg)
|
||||
end.
|
||||
|
||||
-spec wait_for_sync(timeout()) -> ok | {error, any()}.
|
||||
wait_for_sync(Timeout) ->
|
||||
Mod = get_mod(),
|
||||
Mod:wait_for_sync(Timeout).
|
||||
|
||||
-spec subscribe() -> ok.
|
||||
subscribe() ->
|
||||
subscribe(self()).
|
||||
|
||||
-spec subscribe(dst()) -> ok.
|
||||
subscribe(Proc) ->
|
||||
Mod = get_mod(),
|
||||
Mod:subscribe(Proc).
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server API
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
Ticktime = ejabberd_config:get_option(net_ticktime, 60),
|
||||
Nodes = ejabberd_config:get_option(cluster_nodes, []),
|
||||
net_kernel:set_net_ticktime(Ticktime),
|
||||
lists:foreach(fun(Node) ->
|
||||
net_kernel:connect_node(Node)
|
||||
end, Nodes),
|
||||
Mod = get_mod(),
|
||||
case Mod:init() of
|
||||
ok ->
|
||||
Mod:subscribe(?MODULE),
|
||||
{ok, #state{}};
|
||||
{error, Reason} ->
|
||||
{stop, Reason}
|
||||
end.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({node_up, Node}, State) ->
|
||||
?INFO_MSG("Node ~s has joined", [Node]),
|
||||
{noreply, State};
|
||||
handle_info({node_down, Node}, State) ->
|
||||
?INFO_MSG("Node ~s has left", [Node]),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-spec match_node_id(integer()) -> node().
|
||||
match_node_id(I) ->
|
||||
match_node_id(I, get_nodes()).
|
||||
get_mod() ->
|
||||
Backend = ejabberd_config:get_option(cluster_backend, mnesia),
|
||||
list_to_atom("ejabberd_cluster_" ++ atom_to_list(Backend)).
|
||||
|
||||
-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().
|
||||
rpc_timeout() ->
|
||||
timer:seconds(ejabberd_config:get_option(rpc_timeout, 5)).
|
||||
|
||||
opt_type(net_ticktime) ->
|
||||
fun (P) when is_integer(P), P > 0 -> P end;
|
||||
opt_type(cluster_nodes) ->
|
||||
fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
|
||||
opt_type(rpc_timeout) ->
|
||||
fun (T) when is_integer(T), T > 0 -> T end;
|
||||
opt_type(cluster_backend) ->
|
||||
fun (T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
opt_type(_) ->
|
||||
[rpc_timeout, cluster_backend, cluster_nodes, net_ticktime].
|
||||
|
||||
@@ -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().
|
||||
+2
-273
@@ -221,7 +221,6 @@
|
||||
get_command_format/1,
|
||||
get_command_format/2,
|
||||
get_command_format/3,
|
||||
get_command_policy_and_scope/1,
|
||||
get_command_definition/1,
|
||||
get_command_definition/2,
|
||||
get_tags_commands/0,
|
||||
@@ -230,11 +229,6 @@
|
||||
register_commands/1,
|
||||
unregister_commands/1,
|
||||
expose_commands/1,
|
||||
execute_command/2,
|
||||
execute_command/3,
|
||||
execute_command/4,
|
||||
execute_command/5,
|
||||
execute_command/6,
|
||||
opt_type/1,
|
||||
get_commands_spec/0,
|
||||
get_commands_definition/0,
|
||||
@@ -361,6 +355,8 @@ expose_commands(Commands) ->
|
||||
Commands),
|
||||
|
||||
case ejabberd_config:add_option(commands, [{add_commands, Names}]) of
|
||||
ok ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
{error, Reason};
|
||||
{atomic, Result} ->
|
||||
@@ -427,17 +423,6 @@ get_command_format(Name, Auth, Version) ->
|
||||
{Args, Result}
|
||||
end.
|
||||
|
||||
-spec get_command_policy_and_scope(atom()) -> {ok, open|user|admin|restricted, [oauth_scope()]} | {error, command_not_found}.
|
||||
|
||||
%% @doc return command policy.
|
||||
get_command_policy_and_scope(Name) ->
|
||||
case get_command_definition(Name) of
|
||||
#ejabberd_commands{policy = Policy} = Cmd ->
|
||||
{ok, Policy, cmd_scope(Cmd)};
|
||||
command_not_found ->
|
||||
{error, command_not_found}
|
||||
end.
|
||||
|
||||
%% The oauth scopes for a command are the command name itself,
|
||||
%% also might include either 'ejabberd:user' or 'ejabberd:admin'
|
||||
cmd_scope(#ejabberd_commands{policy = Policy, name = Name}) ->
|
||||
@@ -503,129 +488,6 @@ execute_command2(Name, Arguments, CallerInfo, Version) ->
|
||||
throw({error, access_rules_unauthorized})
|
||||
end.
|
||||
|
||||
%% @spec (Name::atom(), Arguments) -> ResultTerm
|
||||
%% where
|
||||
%% Arguments = [any()]
|
||||
%% @doc Execute a command.
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data |
|
||||
%% no_auth_provided | access_rules_unauthorized
|
||||
execute_command(Name, Arguments) ->
|
||||
execute_command(Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
-spec execute_command(atom(),
|
||||
[any()],
|
||||
integer() |
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin
|
||||
) -> any().
|
||||
|
||||
%% @spec (Name::atom(), Arguments, integer() | Auth) -> ResultTerm
|
||||
%% where
|
||||
%% Auth = {User::string(), Server::string(), Password::string(),
|
||||
%% Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Arguments = [any()]
|
||||
%%
|
||||
%% @doc Execute a command in a given API version
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data |
|
||||
%% no_auth_provided
|
||||
execute_command(Name, Arguments, Version) when is_integer(Version) ->
|
||||
execute_command([], noauth, Name, Arguments, Version);
|
||||
execute_command(Name, Arguments, Auth) ->
|
||||
execute_command([], Auth, Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) ->
|
||||
%% ResultTerm | {error, Error}
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
|
||||
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Arguments = [any()]
|
||||
%%
|
||||
%% @doc Execute a command
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
|
||||
execute_command(AccessCommands, Auth, Name, Arguments) ->
|
||||
execute_command(AccessCommands, Auth, Name, Arguments, ?DEFAULT_VERSION).
|
||||
|
||||
-spec execute_command([{atom(), [atom()], [any()]}] | undefined,
|
||||
{binary(), binary(), binary(), boolean()} |
|
||||
noauth | admin,
|
||||
atom(),
|
||||
[any()],
|
||||
integer()
|
||||
) -> any().
|
||||
|
||||
%% @spec (AccessCommands, Auth, Name::atom(), Arguments, integer()) -> ResultTerm
|
||||
%% where
|
||||
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
|
||||
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
|
||||
%% | noauth
|
||||
%% | admin
|
||||
%% Arguments = [any()]
|
||||
%%
|
||||
%% @doc Execute a command in a given API version
|
||||
%% Can return the following exceptions:
|
||||
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided | access_rules_unauthorized
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, #{}).
|
||||
|
||||
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, CallerInfo) ->
|
||||
Auth = case is_admin(Name, Auth1, CallerInfo) of
|
||||
true -> admin;
|
||||
false -> Auth1
|
||||
end,
|
||||
TokenJID = oauth_token_user(Auth1),
|
||||
Command = get_command_definition(Name, Version),
|
||||
AccessCommands = get_all_access_commands(AccessCommands1),
|
||||
|
||||
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments, CallerInfo) of
|
||||
ok -> execute_check_policy(Auth, TokenJID, Command, Arguments)
|
||||
end.
|
||||
|
||||
|
||||
execute_check_policy(
|
||||
_Auth, _JID, #ejabberd_commands{policy = open} = Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_policy(
|
||||
noauth, _JID, Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_policy(
|
||||
_Auth, _JID, #ejabberd_commands{policy = restricted} = Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_policy(
|
||||
_Auth, JID, #ejabberd_commands{policy = admin} = Command, Arguments) ->
|
||||
execute_check_access(JID, Command, Arguments);
|
||||
execute_check_policy(
|
||||
admin, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
|
||||
execute_check_access(JID, Command, Arguments);
|
||||
execute_check_policy(
|
||||
{User, Server, _, _}, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
|
||||
execute_check_access(JID, Command, [User, Server | Arguments]).
|
||||
|
||||
execute_check_access(_FromJID, #ejabberd_commands{access = []} = Command, Arguments) ->
|
||||
do_execute_command(Command, Arguments);
|
||||
execute_check_access(undefined, _Command, _Arguments) ->
|
||||
throw({error, access_rules_unauthorized});
|
||||
execute_check_access(FromJID, #ejabberd_commands{access = AccessRefs} = Command, Arguments) ->
|
||||
%% TODO Review: Do we have smarter / better way to check rule on other Host than global ?
|
||||
Host = global,
|
||||
Rules = lists:map(
|
||||
fun({Mod, AccessName, Default}) ->
|
||||
gen_mod:get_module_opt(Host, Mod, AccessName, Default);
|
||||
(Default) ->
|
||||
Default
|
||||
end, AccessRefs),
|
||||
case acl:any_rules_allowed(Host, Rules, FromJID) of
|
||||
true ->
|
||||
do_execute_command(Command, Arguments);
|
||||
false ->
|
||||
throw({error, access_rules_unauthorized})
|
||||
end.
|
||||
|
||||
do_execute_command(Command, Arguments) ->
|
||||
Module = Command#ejabberd_commands.module,
|
||||
@@ -672,58 +534,6 @@ get_tags_commands(Version) ->
|
||||
%% Access verification
|
||||
%% -----------------------------
|
||||
|
||||
%% @spec (AccessCommands, Auth, Method, Command, Arguments) -> ok
|
||||
%% where
|
||||
%% AccessCommands = [ {Access, CommandNames, Arguments} ]
|
||||
%% Auth = {User::string(), Server::string(), Password::string()} | noauth
|
||||
%% Method = atom()
|
||||
%% Arguments = [any()]
|
||||
%% @doc Check access is allowed to that command.
|
||||
%% At least one AccessCommand must be satisfied.
|
||||
%% It may throw {error, Error} where:
|
||||
%% Error = account_unprivileged | invalid_account_data
|
||||
check_access_commands([], _Auth, _Method, _Command, _Arguments, _CallerInfo) ->
|
||||
ok;
|
||||
check_access_commands(AccessCommands, Auth, Method, Command1, Arguments, CallerInfo) ->
|
||||
Command =
|
||||
case {Command1#ejabberd_commands.policy, Auth} of
|
||||
{user, {_, _, _, _}} ->
|
||||
Command1;
|
||||
{user, _} ->
|
||||
Command1#ejabberd_commands{
|
||||
args = [{user, binary}, {server, binary} |
|
||||
Command1#ejabberd_commands.args]};
|
||||
_ ->
|
||||
Command1
|
||||
end,
|
||||
AccessCommandsAllowed =
|
||||
lists:filter(
|
||||
fun({Access, Commands, ArgumentRestrictions}) ->
|
||||
case check_access(Command, Access, Auth, CallerInfo) of
|
||||
true ->
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
end;
|
||||
({Access, Commands}) ->
|
||||
ArgumentRestrictions = [],
|
||||
case check_access(Command, Access, Auth, CallerInfo) of
|
||||
true ->
|
||||
check_access_command(Commands, Command,
|
||||
ArgumentRestrictions,
|
||||
Method, Arguments);
|
||||
false ->
|
||||
false
|
||||
end
|
||||
end,
|
||||
AccessCommands),
|
||||
case AccessCommandsAllowed of
|
||||
[] -> throw({error, account_unprivileged});
|
||||
L when is_list(L) -> ok
|
||||
end.
|
||||
|
||||
-spec check_auth(ejabberd_commands(), noauth) -> noauth_provided;
|
||||
(ejabberd_commands(),
|
||||
{binary(), binary(), binary(), boolean()}) ->
|
||||
@@ -746,80 +556,6 @@ check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
|
||||
_ -> throw({error, invalid_account_data})
|
||||
end.
|
||||
|
||||
check_access(Command, ?POLICY_ACCESS, _, _)
|
||||
when Command#ejabberd_commands.policy == open ->
|
||||
true;
|
||||
check_access(_Command, _Access, admin, _) ->
|
||||
true;
|
||||
check_access(_Command, _Access, {_User, _Server, _, true}, _) ->
|
||||
false;
|
||||
check_access(Command, Access, Auth, CallerInfo)
|
||||
when Access =/= ?POLICY_ACCESS;
|
||||
Command#ejabberd_commands.policy == open;
|
||||
Command#ejabberd_commands.policy == user ->
|
||||
case check_auth(Command, Auth) of
|
||||
{ok, User, Server} ->
|
||||
check_access2(Access, CallerInfo#{usr => jid:split(jid:make(User, Server))}, Server);
|
||||
no_auth_provided ->
|
||||
case Command#ejabberd_commands.policy of
|
||||
user ->
|
||||
false;
|
||||
_ ->
|
||||
check_access2(Access, CallerInfo, global)
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
check_access(_Command, _Access, _Auth, _CallerInfo) ->
|
||||
false.
|
||||
|
||||
check_access2(?POLICY_ACCESS, _CallerInfo, _Server) ->
|
||||
true;
|
||||
check_access2(Access, AccessInfo, Server) ->
|
||||
%% Check this user has access permission
|
||||
case acl:access_matches(Access, AccessInfo, Server) of
|
||||
allow -> true;
|
||||
deny -> false
|
||||
end.
|
||||
|
||||
check_access_command(Commands, Command, ArgumentRestrictions,
|
||||
Method, Arguments) ->
|
||||
case Commands==all orelse lists:member(Method, Commands) of
|
||||
true -> check_access_arguments(Command, ArgumentRestrictions,
|
||||
Arguments);
|
||||
false -> false
|
||||
end.
|
||||
|
||||
check_access_arguments(Command, ArgumentRestrictions, Arguments) ->
|
||||
ArgumentsTagged = tag_arguments(Command#ejabberd_commands.args, Arguments),
|
||||
lists:all(
|
||||
fun({ArgName, ArgAllowedValue}) ->
|
||||
%% If the call uses the argument, check the value is acceptable
|
||||
case lists:keysearch(ArgName, 1, ArgumentsTagged) of
|
||||
{value, {ArgName, ArgValue}} -> ArgValue == ArgAllowedValue;
|
||||
false -> true
|
||||
end
|
||||
end, ArgumentRestrictions).
|
||||
|
||||
tag_arguments(ArgsDefs, Args) ->
|
||||
lists:zipwith(
|
||||
fun({ArgName, _ArgType}, ArgValue) ->
|
||||
{ArgName, ArgValue}
|
||||
end,
|
||||
ArgsDefs,
|
||||
Args).
|
||||
|
||||
|
||||
%% Get commands for all version
|
||||
get_all_access_commands(AccessCommands) ->
|
||||
get_access_commands(AccessCommands, ?DEFAULT_VERSION).
|
||||
|
||||
get_access_commands(undefined, Version) ->
|
||||
Cmds = get_exposed_commands(Version),
|
||||
[{?POLICY_ACCESS, Cmds, []}];
|
||||
get_access_commands(AccessCommands, _Version) ->
|
||||
AccessCommands.
|
||||
|
||||
get_exposed_commands() ->
|
||||
get_exposed_commands(?DEFAULT_VERSION).
|
||||
get_exposed_commands(Version) ->
|
||||
@@ -854,13 +590,6 @@ expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds) when is_list(L
|
||||
[Command|Acc]
|
||||
end, [], L).
|
||||
|
||||
oauth_token_user(noauth) ->
|
||||
undefined;
|
||||
oauth_token_user(admin) ->
|
||||
undefined;
|
||||
oauth_token_user({User, Server, _, _}) ->
|
||||
jid:make(User, Server).
|
||||
|
||||
is_admin(_Name, admin, _Extra) ->
|
||||
true;
|
||||
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
|
||||
|
||||
@@ -87,9 +87,6 @@ md_tag(strong, V) ->
|
||||
md_tag(_, V) ->
|
||||
V.
|
||||
|
||||
unbinarize(binary) -> string;
|
||||
unbinarize(Other) -> Other.
|
||||
|
||||
perl_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
|
||||
[?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
|
||||
perl_gen({Name, string}, Str, _Indent, HTMLOutput) ->
|
||||
@@ -252,7 +249,7 @@ json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput) ->
|
||||
{200, json_gen(ResultDesc, Result, Indent, HTMLOutput)};
|
||||
{{Name0, _}, _} ->
|
||||
{200, [Indent, ?OP_L("{"), ?STR_A(Name0), ?OP_L(": "),
|
||||
json_gen(ResultDesc, Result, Indent, HTMLOutput), Indent, ?OP_L("}")]}
|
||||
json_gen(ResultDesc, Result, Indent, HTMLOutput), ?OP_L("}")]}
|
||||
end,
|
||||
CodeStr = case Code of
|
||||
200 -> <<" 200 OK">>;
|
||||
@@ -340,48 +337,62 @@ gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc,
|
||||
end
|
||||
end.
|
||||
|
||||
format_type({list, {_, {tuple, Els}}}) ->
|
||||
io_lib:format("[~s]", [format_type({tuple, Els})]);
|
||||
format_type({list, El}) ->
|
||||
io_lib:format("[~s]", [format_type(El)]);
|
||||
format_type({tuple, Els}) ->
|
||||
Args = [format_type(El) || El <- Els],
|
||||
io_lib:format("{~s}", [string:join(Args, ", ")]);
|
||||
format_type({Name, Type}) ->
|
||||
io_lib:format("~s::~s", [Name, format_type(Type)]);
|
||||
format_type(binary) ->
|
||||
"string";
|
||||
format_type(atom) ->
|
||||
"string";
|
||||
format_type(Type) ->
|
||||
io_lib:format("~p", [Type]).
|
||||
|
||||
gen_param(Name, Type, undefined, HTMLOutput) ->
|
||||
[?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>,
|
||||
?RAW(io_lib:format("~p", [unbinarize(Type)]))])];
|
||||
[?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))])];
|
||||
gen_param(Name, Type, Desc, HTMLOutput) ->
|
||||
[?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>,
|
||||
?RAW(io_lib:format("~p", [unbinarize(Type)]))]),
|
||||
[?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))]),
|
||||
?TAG(dd, ?RAW(Desc))].
|
||||
|
||||
gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
|
||||
args=Args, args_desc=ArgsDesc,
|
||||
result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
|
||||
LDesc = case LongDesc of
|
||||
"" -> Desc;
|
||||
_ -> LongDesc
|
||||
end,
|
||||
ArgsText = case ArgsDesc of
|
||||
none ->
|
||||
[?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput)
|
||||
|| {AName, Type} <- Args])];
|
||||
_ ->
|
||||
[?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput)
|
||||
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])]
|
||||
end,
|
||||
ResultText = case Result of
|
||||
{res,rescode} ->
|
||||
[?TAG(dl, [gen_param(res, integer,
|
||||
"Status code (0 on success, 1 otherwise)",
|
||||
HTMLOutput)])];
|
||||
{res,restuple} ->
|
||||
[?TAG(dl, [gen_param(res, string,
|
||||
"Raw result string",
|
||||
HTMLOutput)])];
|
||||
{RName, Type} ->
|
||||
case ResultDesc of
|
||||
none ->
|
||||
[?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
|
||||
_ ->
|
||||
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
|
||||
end
|
||||
end,
|
||||
|
||||
try
|
||||
LDesc = case LongDesc of
|
||||
"" -> Desc;
|
||||
_ -> LongDesc
|
||||
end,
|
||||
ArgsText = case ArgsDesc of
|
||||
none ->
|
||||
[?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput)
|
||||
|| {AName, Type} <- Args])];
|
||||
_ ->
|
||||
[?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput)
|
||||
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])]
|
||||
end,
|
||||
ResultText = case Result of
|
||||
{res,rescode} ->
|
||||
[?TAG(dl, [gen_param(res, integer,
|
||||
"Status code (0 on success, 1 otherwise)",
|
||||
HTMLOutput)])];
|
||||
{res,restuple} ->
|
||||
[?TAG(dl, [gen_param(res, string,
|
||||
"Raw result string",
|
||||
HTMLOutput)])];
|
||||
{RName, Type} ->
|
||||
case ResultDesc of
|
||||
none ->
|
||||
[?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
|
||||
_ ->
|
||||
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
|
||||
end
|
||||
end,
|
||||
|
||||
[?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
|
||||
?TAG(p, ?RAW(LDesc)),
|
||||
?TAG(h2, <<"Arguments:">>), ArgsText,
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
-author('ecestari@process-one.net').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
-export([start/1, start_link/1, init/1, handle_event/3,
|
||||
handle_sync_event/4, code_change/4, handle_info/3,
|
||||
@@ -75,13 +75,13 @@
|
||||
-export_type([ws_socket/0]).
|
||||
|
||||
start(WS) ->
|
||||
gen_fsm:start(?MODULE, [WS], ?FSMOPTS).
|
||||
p1_fsm:start(?MODULE, [WS], ?FSMOPTS).
|
||||
|
||||
start_link(WS) ->
|
||||
gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
|
||||
p1_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
|
||||
|
||||
send_xml({http_ws, FsmRef, _IP}, Packet) ->
|
||||
case catch gen_fsm:sync_send_all_state_event(FsmRef,
|
||||
case catch p1_fsm:sync_send_all_state_event(FsmRef,
|
||||
{send_xml, Packet},
|
||||
15000)
|
||||
of
|
||||
@@ -93,7 +93,7 @@ send_xml({http_ws, FsmRef, _IP}, Packet) ->
|
||||
setopts({http_ws, FsmRef, _IP}, Opts) ->
|
||||
case lists:member({active, once}, Opts) of
|
||||
true ->
|
||||
gen_fsm:send_all_state_event(FsmRef,
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{activate, self()});
|
||||
_ -> ok
|
||||
end.
|
||||
@@ -105,11 +105,11 @@ peername({http_ws, _FsmRef, IP}) -> {ok, IP}.
|
||||
controlling_process(_Socket, _Pid) -> ok.
|
||||
|
||||
become_controller(FsmRef, C2SPid) ->
|
||||
gen_fsm:send_all_state_event(FsmRef,
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{become_controller, C2SPid}).
|
||||
|
||||
close({http_ws, FsmRef, _IP}) ->
|
||||
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
|
||||
catch p1_fsm:sync_send_all_state_event(FsmRef, close).
|
||||
|
||||
socket_handoff(LocalPath, Request, Socket, SockMod, Buf, Opts) ->
|
||||
ejabberd_websocket:socket_handoff(LocalPath, Request, Socket, SockMod,
|
||||
@@ -241,6 +241,7 @@ handle_info(PingPong, StateName, StateData) when PingPong == ping orelse
|
||||
StateData2#state{pong_expected = false}};
|
||||
handle_info({timeout, Timer, _}, _StateName,
|
||||
#state{timer = Timer} = StateData) ->
|
||||
?DEBUG("Closing websocket connection from hitting inactivity timeout", []),
|
||||
{stop, normal, StateData};
|
||||
handle_info({timeout, Timer, _}, StateName,
|
||||
#state{ping_timer = Timer, ws = {_, WsPid}} = StateData) ->
|
||||
@@ -253,6 +254,7 @@ handle_info({timeout, Timer, _}, StateName,
|
||||
{next_state, StateName,
|
||||
StateData#state{ping_timer = PingTimer, pong_expected = true}};
|
||||
true ->
|
||||
?DEBUG("Closing websocket connection from missing pongs", []),
|
||||
{stop, normal, StateData}
|
||||
end;
|
||||
handle_info(_, StateName, StateData) ->
|
||||
|
||||
@@ -109,6 +109,7 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
{ok, Socket} ->
|
||||
%% Inform my parent that this port was opened succesfully
|
||||
proc_lib:init_ack({ok, self()}),
|
||||
application:ensure_started(ejabberd),
|
||||
start_module_sup(Port, Module),
|
||||
?INFO_MSG("Start accepting UDP connections at ~s for ~p",
|
||||
[format_portip(PortIP), Module]),
|
||||
@@ -134,6 +135,7 @@ init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
|
||||
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
|
||||
%% Inform my parent that this port was opened succesfully
|
||||
proc_lib:init_ack({ok, self()}),
|
||||
application:ensure_started(ejabberd),
|
||||
start_module_sup(Port, Module),
|
||||
?INFO_MSG("Start accepting TCP connections at ~s for ~p",
|
||||
[format_portip(PortIP), Module]),
|
||||
|
||||
@@ -151,6 +151,9 @@ do_start() ->
|
||||
application:set_env(lager, crash_log_size, LogRotateSize),
|
||||
application:set_env(lager, crash_log_count, LogRotateCount),
|
||||
ejabberd:start_app(lager),
|
||||
lists:foreach(fun(Handler) ->
|
||||
lager:set_loghwm(Handler, LogRateLimit)
|
||||
end, gen_event:which_handlers(lager_event)),
|
||||
ok.
|
||||
|
||||
%% @spec () -> ok
|
||||
|
||||
@@ -68,8 +68,6 @@ init([]) ->
|
||||
_ -> ok
|
||||
end,
|
||||
ejabberd:start_app(mnesia, permanent),
|
||||
?DEBUG("Waiting for Mnesia tables synchronization...", []),
|
||||
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity),
|
||||
Schema = read_schema_file(),
|
||||
{ok, #state{schema = Schema}};
|
||||
false ->
|
||||
|
||||
+2
-24
@@ -50,7 +50,7 @@
|
||||
config_reloaded/0,
|
||||
opt_type/1]).
|
||||
|
||||
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
|
||||
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
|
||||
@@ -96,14 +96,6 @@ get_commands_spec() ->
|
||||
policy = restricted,
|
||||
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}}
|
||||
},
|
||||
#ejabberd_commands{name = oauth_list_scopes, tags = [oauth],
|
||||
desc = "List scopes that can be granted, and commands",
|
||||
longdesc = "List scopes that can be granted to tokens generated through the command line, together with the commands they allow",
|
||||
module = ?MODULE, function = oauth_list_scopes,
|
||||
args = [],
|
||||
policy = restricted,
|
||||
result = {scopes, {list, {scope, {tuple, [{scope, string}, {commands, string}]}}}}
|
||||
},
|
||||
#ejabberd_commands{name = oauth_revoke_token, tags = [oauth],
|
||||
desc = "Revoke authorization for a token (only Mnesia)",
|
||||
module = ?MODULE, function = oauth_revoke_token,
|
||||
@@ -143,9 +135,6 @@ oauth_revoke_token(Token) ->
|
||||
ok = mnesia:dirty_delete(oauth_token, list_to_binary(Token)),
|
||||
oauth_list_tokens().
|
||||
|
||||
oauth_list_scopes() ->
|
||||
[ {Scope, string:join([atom_to_list(Cmd) || Cmd <- Cmds], ",")} || {Scope, Cmds} <- dict:to_list(get_cmd_scopes())].
|
||||
|
||||
config_reloaded() ->
|
||||
DBMod = get_db_backend(),
|
||||
case init_cache(DBMod) of
|
||||
@@ -240,17 +229,6 @@ verify_resowner_scope(_, _, _) ->
|
||||
{error, badscope}.
|
||||
|
||||
|
||||
get_cmd_scopes() ->
|
||||
ScopeMap = lists:foldl(fun(Cmd, Accum) ->
|
||||
case ejabberd_commands:get_command_policy_and_scope(Cmd) of
|
||||
{ok, Policy, Scopes} when Policy =/= restricted ->
|
||||
lists:foldl(fun(Scope, Accum2) ->
|
||||
dict:append(Scope, Cmd, Accum2)
|
||||
end, Accum, Scopes);
|
||||
_ -> Accum
|
||||
end end, dict:new(), ejabberd_commands:get_exposed_commands()),
|
||||
ScopeMap.
|
||||
|
||||
%% This is callback for oauth tokens generated through the command line. Only open and admin commands are
|
||||
%% made available.
|
||||
%verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) ->
|
||||
@@ -755,7 +733,7 @@ css() ->
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.container > .section {
|
||||
.container > .section {
|
||||
background: #424A55;
|
||||
}
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ terminate(_Reason,
|
||||
State) ->
|
||||
close_stream(XMLStreamState),
|
||||
if C2SPid /= undefined ->
|
||||
gen_fsm:send_event(C2SPid, closed);
|
||||
p1_fsm:send_event(C2SPid, closed);
|
||||
true -> ok
|
||||
end,
|
||||
catch (State#state.sock_mod):close(State#state.socket),
|
||||
@@ -272,7 +272,7 @@ process_data([Element | Els],
|
||||
element(1, Element) == xmlstreamend ->
|
||||
if C2SPid == undefined -> State;
|
||||
true ->
|
||||
catch gen_fsm:send_event(C2SPid,
|
||||
catch p1_fsm:send_event(C2SPid,
|
||||
element_wrapper(Element)),
|
||||
process_data(Els, State)
|
||||
end;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
-module(ejabberd_regexp).
|
||||
|
||||
-compile([export_all]).
|
||||
-export([exec/2, run/2, split/2, replace/3, greplace/3, sh_to_awk/1]).
|
||||
|
||||
exec({ReM, ReF, ReA}, {RgM, RgF, RgA}) ->
|
||||
try apply(ReM, ReF, ReA) catch
|
||||
|
||||
@@ -149,7 +149,7 @@ init([]) ->
|
||||
lists:foreach(
|
||||
fun (Pid) -> erlang:monitor(process, Pid) end,
|
||||
mnesia:dirty_select(route,
|
||||
[{{route, '_', '$1', '_'}, [], ['$1']}])),
|
||||
[{#route{pid = '$1', _ = '_'}, [], ['$1']}])),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
|
||||
+18
-20
@@ -39,7 +39,7 @@
|
||||
remove_connection/2, start_connection/2, start_connection/3,
|
||||
dirty_get_connections/0, allow_host/2,
|
||||
incoming_s2s_number/0, outgoing_s2s_number/0,
|
||||
stop_all_connections/0,
|
||||
stop_s2s_connections/0,
|
||||
clean_temporarily_blocked_table/0,
|
||||
list_temporarily_blocked_hosts/0,
|
||||
external_host_overloaded/1, is_temporarly_blocked/1,
|
||||
@@ -546,25 +546,23 @@ parent_domains(Domain) ->
|
||||
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{
|
||||
name = incoming_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
name = incoming_s2s_number, tags = [stats, s2s],
|
||||
desc = "Number of incoming s2s connections on the node",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = incoming_s2s_number,
|
||||
args = [], result = {s2s_incoming, integer}},
|
||||
policy = admin,
|
||||
module = ?MODULE, function = incoming_s2s_number,
|
||||
args = [], result = {s2s_incoming, integer}},
|
||||
#ejabberd_commands{
|
||||
name = outgoing_s2s_number,
|
||||
tags = [stats, s2s],
|
||||
name = outgoing_s2s_number, tags = [stats, s2s],
|
||||
desc = "Number of outgoing s2s connections on the node",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = outgoing_s2s_number,
|
||||
args = [], result = {s2s_outgoing, integer}},
|
||||
#ejabberd_commands{name = stop_all_connections,
|
||||
tags = [s2s],
|
||||
desc = "Stop all outgoing and incoming connections",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = stop_all_connections,
|
||||
args = [], result = {res, rescode}}].
|
||||
policy = admin,
|
||||
module = ?MODULE, function = outgoing_s2s_number,
|
||||
args = [], result = {s2s_outgoing, integer}},
|
||||
#ejabberd_commands{
|
||||
name = stop_s2s_connections, tags = [s2s],
|
||||
desc = "Stop all s2s outgoing and incoming connections",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = stop_s2s_connections,
|
||||
args = [], result = {res, rescode}}].
|
||||
|
||||
%% TODO Move those stats commands to ejabberd stats command ?
|
||||
incoming_s2s_number() ->
|
||||
@@ -580,8 +578,8 @@ supervisor_count(Supervisor) ->
|
||||
length(Result)
|
||||
end.
|
||||
|
||||
-spec stop_all_connections() -> ok.
|
||||
stop_all_connections() ->
|
||||
-spec stop_s2s_connections() -> ok.
|
||||
stop_s2s_connections() ->
|
||||
lists:foreach(
|
||||
fun({_Id, Pid, _Type, _Module}) ->
|
||||
supervisor:terminate_child(ejabberd_s2s_in_sup, Pid)
|
||||
@@ -684,7 +682,7 @@ complete_s2s_info([Connection | T], Type, Result) ->
|
||||
-spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}].
|
||||
|
||||
get_s2s_state(S2sPid) ->
|
||||
Infos = case gen_fsm:sync_send_all_state_event(S2sPid,
|
||||
Infos = case p1_fsm:sync_send_all_state_event(S2sPid,
|
||||
get_state_infos)
|
||||
of
|
||||
{state_infos, Is} -> [{status, open} | Is];
|
||||
|
||||
+40
-12
@@ -66,6 +66,8 @@
|
||||
user_resources/2,
|
||||
kick_user/2,
|
||||
get_session_pid/3,
|
||||
get_session_sid/3,
|
||||
get_session_sids/2,
|
||||
get_user_info/2,
|
||||
get_user_info/3,
|
||||
get_user_ip/3,
|
||||
@@ -292,15 +294,31 @@ close_session_unset_presence(SID, User, Server,
|
||||
-spec get_session_pid(binary(), binary(), binary()) -> none | pid().
|
||||
|
||||
get_session_pid(User, Server, Resource) ->
|
||||
case get_session_sid(User, Server, Resource) of
|
||||
{_, PID} -> PID;
|
||||
none -> none
|
||||
end.
|
||||
|
||||
-spec get_session_sid(binary(), binary(), binary()) -> none | sid().
|
||||
|
||||
get_session_sid(User, Server, Resource) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(get_sessions(Mod, LUser, LServer, LResource)) of
|
||||
[#session{sid = {_, Pid}}] -> Pid;
|
||||
[#session{sid = SID}] -> SID;
|
||||
_ -> none
|
||||
end.
|
||||
|
||||
-spec get_session_sids(binary(), binary()) -> [sid()].
|
||||
|
||||
get_session_sids(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = get_sm_backend(LServer),
|
||||
online(get_sessions(Mod, LUser, LServer)).
|
||||
|
||||
-spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
|
||||
|
||||
set_offline_info(SID, User, Server, Resource, Info) ->
|
||||
@@ -471,9 +489,13 @@ host_up(Host) ->
|
||||
-spec host_down(binary()) -> ok.
|
||||
host_down(Host) ->
|
||||
Mod = get_sm_backend(Host),
|
||||
Err = case ejabberd_cluster:get_nodes() of
|
||||
[Node] when Node == node() -> xmpp:serr_system_shutdown();
|
||||
_ -> xmpp:serr_reset()
|
||||
end,
|
||||
lists:foreach(
|
||||
fun(#session{sid = {_, Pid}}) when node(Pid) == node() ->
|
||||
ejabberd_c2s:send(Pid, xmpp:serr_system_shutdown()),
|
||||
ejabberd_c2s:send(Pid, Err),
|
||||
ejabberd_c2s:stop(Pid);
|
||||
(_) ->
|
||||
ok
|
||||
@@ -957,30 +979,36 @@ cache_nodes(Mod, LServer) ->
|
||||
%%% ejabberd commands
|
||||
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{name = connected_users,
|
||||
tags = [session],
|
||||
[#ejabberd_commands{name = connected_users, tags = [session],
|
||||
desc = "List all established sessions",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = connected_users, args = [],
|
||||
result_desc = "List of users sessions",
|
||||
result_example = [<<"user1@example.com">>, <<"user2@example.com">>],
|
||||
result = {connected_users, {list, {sessions, string}}}},
|
||||
#ejabberd_commands{name = connected_users_number,
|
||||
tags = [session, stats],
|
||||
#ejabberd_commands{name = connected_users_number, tags = [session, stats],
|
||||
desc = "Get the number of established sessions",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = connected_users_number,
|
||||
result_example = 2,
|
||||
args = [], result = {num_sessions, integer}},
|
||||
#ejabberd_commands{name = user_resources,
|
||||
tags = [session],
|
||||
#ejabberd_commands{name = user_resources, tags = [session],
|
||||
desc = "List user's connected resources",
|
||||
policy = user,
|
||||
policy = admin,
|
||||
module = ?MODULE, function = user_resources,
|
||||
args = [],
|
||||
args = [{user, binary}, {host, binary}],
|
||||
args_desc = ["User name", "Server name"],
|
||||
args_example = [<<"user1">>, <<"example.com">>],
|
||||
result_example = [<<"tka1">>, <<"Gajim">>, <<"mobile-app">>],
|
||||
result = {resources, {list, {resource, string}}}},
|
||||
#ejabberd_commands{name = kick_user,
|
||||
tags = [session],
|
||||
#ejabberd_commands{name = kick_user, tags = [session],
|
||||
desc = "Disconnect user's active sessions",
|
||||
module = ?MODULE, function = kick_user,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
args_desc = ["User name", "Server name"],
|
||||
args_example = [<<"user1">>, <<"example.com">>],
|
||||
result_desc = "Number of resources that were kicked",
|
||||
result_example = 3,
|
||||
result = {num_resources, integer}}].
|
||||
|
||||
-spec connected_users() -> [binary()].
|
||||
|
||||
+14
-16
@@ -29,9 +29,7 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-define(GEN_FSM, p1_fsm).
|
||||
|
||||
-behaviour(?GEN_FSM).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, start_link/2,
|
||||
@@ -113,11 +111,11 @@
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
(?GEN_FSM):start(ejabberd_sql, [Host],
|
||||
p1_fsm:start(ejabberd_sql, [Host],
|
||||
fsm_limit_opts() ++ (?FSMOPTS)).
|
||||
|
||||
start_link(Host, StartInterval) ->
|
||||
(?GEN_FSM):start_link(ejabberd_sql,
|
||||
p1_fsm:start_link(ejabberd_sql,
|
||||
[Host, StartInterval],
|
||||
fsm_limit_opts() ++ (?FSMOPTS)).
|
||||
|
||||
@@ -160,7 +158,7 @@ sql_call(Host, Msg) ->
|
||||
case ejabberd_sql_sup:get_random_pid(Host) of
|
||||
none -> {error, <<"Unknown Host">>};
|
||||
Pid ->
|
||||
(?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg,
|
||||
p1_fsm:sync_send_event(Pid,{sql_cmd, Msg,
|
||||
p1_time_compat:monotonic_time(milli_seconds)},
|
||||
query_timeout(Host))
|
||||
end;
|
||||
@@ -168,7 +166,7 @@ sql_call(Host, Msg) ->
|
||||
end.
|
||||
|
||||
keep_alive(Host, PID) ->
|
||||
(?GEN_FSM):sync_send_event(PID,
|
||||
p1_fsm:sync_send_event(PID,
|
||||
{sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
|
||||
p1_time_compat:monotonic_time(milli_seconds)},
|
||||
query_timeout(Host)).
|
||||
@@ -280,7 +278,7 @@ init([Host, StartInterval]) ->
|
||||
keep_alive, [Host, self()])
|
||||
end,
|
||||
[DBType | _] = db_opts(Host),
|
||||
(?GEN_FSM):send_event(self(), connect),
|
||||
p1_fsm:send_event(self(), connect),
|
||||
ejabberd_sql_sup:add_pid(Host, self()),
|
||||
QueueType = case ejabberd_config:get_option({sql_queue_type, Host}) of
|
||||
undefined ->
|
||||
@@ -313,7 +311,7 @@ connecting(connect, #state{host = Host} = State) ->
|
||||
PendingRequests =
|
||||
p1_queue:dropwhile(
|
||||
fun(Req) ->
|
||||
?GEN_FSM:send_event(self(), Req),
|
||||
p1_fsm:send_event(self(), Req),
|
||||
true
|
||||
end, State#state.pending_requests),
|
||||
State1 = State#state{db_ref = Ref,
|
||||
@@ -325,7 +323,7 @@ connecting(connect, #state{host = Host} = State) ->
|
||||
"Retry after: ~p seconds",
|
||||
[State#state.db_type, Reason,
|
||||
State#state.start_interval div 1000]),
|
||||
(?GEN_FSM):send_event_after(State#state.start_interval,
|
||||
p1_fsm:send_event_after(State#state.start_interval,
|
||||
connect),
|
||||
{next_state, connecting, State}
|
||||
end;
|
||||
@@ -337,7 +335,7 @@ connecting(Event, State) ->
|
||||
connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
|
||||
_Timestamp},
|
||||
From, State) ->
|
||||
(?GEN_FSM):reply(From,
|
||||
p1_fsm:reply(From,
|
||||
{error, <<"SQL connection failed">>}),
|
||||
{next_state, connecting, State};
|
||||
connecting({sql_cmd, Command, Timestamp} = Req, From,
|
||||
@@ -350,7 +348,7 @@ connecting({sql_cmd, Command, Timestamp} = Req, From,
|
||||
catch error:full ->
|
||||
Q = p1_queue:dropwhile(
|
||||
fun({sql_cmd, _, To, _Timestamp}) ->
|
||||
(?GEN_FSM):reply(
|
||||
p1_fsm:reply(
|
||||
To, {error, <<"SQL connection failed">>}),
|
||||
true
|
||||
end, State#state.pending_requests),
|
||||
@@ -393,7 +391,7 @@ code_change(_OldVsn, StateName, State, _Extra) ->
|
||||
%% monitoring the connection)
|
||||
handle_info({'DOWN', _MonitorRef, process, _Pid, _Info},
|
||||
_StateName, State) ->
|
||||
(?GEN_FSM):send_event(self(), connect),
|
||||
p1_fsm:send_event(self(), connect),
|
||||
{next_state, connecting, State};
|
||||
handle_info(Info, StateName, State) ->
|
||||
?WARNING_MSG("unexpected info in ~p: ~p",
|
||||
@@ -734,16 +732,16 @@ sql_query_to_iolist(SQLQuery) ->
|
||||
abort_on_driver_error({error, <<"query timed out">>} =
|
||||
Reply,
|
||||
From) ->
|
||||
(?GEN_FSM):reply(From, Reply),
|
||||
p1_fsm:reply(From, Reply),
|
||||
{stop, timeout, get(?STATE_KEY)};
|
||||
abort_on_driver_error({error,
|
||||
<<"Failed sending data on socket", _/binary>>} =
|
||||
Reply,
|
||||
From) ->
|
||||
(?GEN_FSM):reply(From, Reply),
|
||||
p1_fsm:reply(From, Reply),
|
||||
{stop, closed, get(?STATE_KEY)};
|
||||
abort_on_driver_error(Reply, From) ->
|
||||
(?GEN_FSM):reply(From, Reply),
|
||||
p1_fsm:reply(From, Reply),
|
||||
{next_state, session_established, get(?STATE_KEY)}.
|
||||
|
||||
%% == pure ODBC code
|
||||
|
||||
@@ -41,6 +41,12 @@ init([]) ->
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_hooks]},
|
||||
Cluster = {ejabberd_cluster,
|
||||
{ejabberd_cluster, start_link, []},
|
||||
permanent,
|
||||
5000,
|
||||
worker,
|
||||
[ejabberd_cluster]},
|
||||
SystemMonitor =
|
||||
{ejabberd_system_monitor,
|
||||
{ejabberd_system_monitor, start_link, []},
|
||||
@@ -152,6 +158,7 @@ init([]) ->
|
||||
permanent, 5000, worker, [ejabberd_pkix]},
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
[Hooks,
|
||||
Cluster,
|
||||
CyrSASL,
|
||||
Translation,
|
||||
AccessPerms,
|
||||
|
||||
+3
-2
@@ -58,6 +58,7 @@ modules() ->
|
||||
mod_offline,
|
||||
mod_privacy,
|
||||
mod_private,
|
||||
mod_pubsub,
|
||||
mod_roster,
|
||||
mod_shared_roster,
|
||||
mod_vcard].
|
||||
@@ -80,8 +81,8 @@ export(Server, Output, Module) ->
|
||||
case export(LServer, Table, IO, ConvertFun) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Failed export for module ~p: ~p",
|
||||
[Module, Reason])
|
||||
?ERROR_MSG("Failed export for module ~p and table ~p: ~p",
|
||||
[Module, Table, Reason])
|
||||
end
|
||||
end, Module:export(Server)),
|
||||
close_output(Output, IO).
|
||||
|
||||
+15
-15
@@ -63,7 +63,7 @@
|
||||
%%% active_bind - sent bind() request and waiting for response
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -148,7 +148,7 @@
|
||||
start_link(Name) ->
|
||||
Reg_name = misc:binary_to_atom(<<"eldap_",
|
||||
Name/binary>>),
|
||||
gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
|
||||
p1_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
|
||||
|
||||
-spec start_link(binary(), [binary()], inet:port_number(), binary(),
|
||||
binary(), tlsopts()) -> any().
|
||||
@@ -156,7 +156,7 @@ start_link(Name) ->
|
||||
start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
|
||||
Reg_name = misc:binary_to_atom(<<"eldap_",
|
||||
Name/binary>>),
|
||||
gen_fsm:start_link({local, Reg_name}, ?MODULE,
|
||||
p1_fsm:start_link({local, Reg_name}, ?MODULE,
|
||||
[Hosts, Port, Rootdn, Passwd, Opts], []).
|
||||
|
||||
-spec get_status(handle()) -> any().
|
||||
@@ -166,7 +166,7 @@ start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
|
||||
%%% --------------------------------------------------------------------
|
||||
get_status(Handle) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_all_state_event(Handle1, get_status).
|
||||
p1_fsm:sync_send_all_state_event(Handle1, get_status).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
%%% Shutdown connection (and process) asynchronous.
|
||||
@@ -175,7 +175,7 @@ get_status(Handle) ->
|
||||
|
||||
close(Handle) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:send_all_state_event(Handle1, close).
|
||||
p1_fsm:send_all_state_event(Handle1, close).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
%%% Add an entry. The entry field MUST NOT exist for the AddRequest
|
||||
@@ -192,7 +192,7 @@ close(Handle) ->
|
||||
%%% --------------------------------------------------------------------
|
||||
add(Handle, Entry, Attributes) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1,
|
||||
p1_fsm:sync_send_event(Handle1,
|
||||
{add, Entry, add_attrs(Attributes)}, ?CALL_TIMEOUT).
|
||||
|
||||
%%% Do sanity check !
|
||||
@@ -216,7 +216,7 @@ add_attrs(Attrs) ->
|
||||
%%% --------------------------------------------------------------------
|
||||
delete(Handle, Entry) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {delete, Entry},
|
||||
p1_fsm:sync_send_event(Handle1, {delete, Entry},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
@@ -234,7 +234,7 @@ delete(Handle, Entry) ->
|
||||
|
||||
modify(Handle, Object, Mods) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {modify, Object, Mods},
|
||||
p1_fsm:sync_send_event(Handle1, {modify, Object, Mods},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
%%%
|
||||
@@ -274,7 +274,7 @@ m(Operation, Type, Values) ->
|
||||
|
||||
modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1,
|
||||
p1_fsm:sync_send_event(Handle1,
|
||||
{modify_dn, Entry, NewRDN, bool_p(DelOldRDN),
|
||||
optional(NewSup)},
|
||||
?CALL_TIMEOUT).
|
||||
@@ -283,7 +283,7 @@ modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
|
||||
|
||||
modify_passwd(Handle, DN, Passwd) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1,
|
||||
p1_fsm:sync_send_event(Handle1,
|
||||
{modify_passwd, DN, Passwd}, ?CALL_TIMEOUT).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
@@ -298,7 +298,7 @@ modify_passwd(Handle, DN, Passwd) ->
|
||||
|
||||
bind(Handle, RootDN, Passwd) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
|
||||
p1_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
%%% Sanity checks !
|
||||
@@ -356,7 +356,7 @@ search(Handle, L) when is_list(L) ->
|
||||
|
||||
call_search(Handle, A) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {search, A},
|
||||
p1_fsm:sync_send_event(Handle1, {search, A},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
-spec parse_search_args(search_args()) -> eldap_search().
|
||||
@@ -637,7 +637,7 @@ active(Event, From, S) ->
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_event/3
|
||||
%% Called when gen_fsm:send_all_state_event/2 is invoked.
|
||||
%% Called when p1_fsm:send_all_state_event/2 is invoked.
|
||||
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData}
|
||||
@@ -680,7 +680,7 @@ handle_info({Tag, _Socket, Data}, StateName, S)
|
||||
case catch recvd_packet(Data, S) of
|
||||
{response, Response, RequestType} ->
|
||||
NewS = case Response of
|
||||
{reply, Reply, To, S1} -> gen_fsm:reply(To, Reply), S1;
|
||||
{reply, Reply, To, S1} -> p1_fsm:reply(To, Reply), S1;
|
||||
{ok, S1} -> S1
|
||||
end,
|
||||
if StateName == active_bind andalso
|
||||
@@ -709,7 +709,7 @@ handle_info({timeout, Timer, {cmd_timeout, Id}},
|
||||
StateName, S) ->
|
||||
case cmd_timeout(Timer, Id, S) of
|
||||
{reply, To, Reason, NewS} ->
|
||||
gen_fsm:reply(To, Reason),
|
||||
p1_fsm:reply(To, Reason),
|
||||
{next_state, StateName, NewS};
|
||||
{error, _Reason} -> {next_state, StateName, S}
|
||||
end;
|
||||
|
||||
+48
-20
@@ -80,16 +80,18 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{name = modules_update_specs,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Update the module source code from Git",
|
||||
longdesc = "A connection to Internet is required",
|
||||
module = ?MODULE, function = update,
|
||||
args = [],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = modules_available,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "List the contributed modules available to install",
|
||||
module = ?MODULE, function = available_command,
|
||||
result_desc = "List of tuples with module name and description",
|
||||
result_example = [{mod_cron, "Execute scheduled commands"},
|
||||
{mod_rest, "ReST frontend"}],
|
||||
args = [],
|
||||
result = {modules, {list,
|
||||
{module, {tuple,
|
||||
@@ -97,9 +99,11 @@ get_commands_spec() ->
|
||||
{summary, string}]}}}}},
|
||||
#ejabberd_commands{name = modules_installed,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "List the contributed modules already installed",
|
||||
module = ?MODULE, function = installed_command,
|
||||
result_desc = "List of tuples with module name and description",
|
||||
result_example = [{mod_cron, "Execute scheduled commands"},
|
||||
{mod_rest, "ReST frontend"}],
|
||||
args = [],
|
||||
result = {modules, {list,
|
||||
{module, {tuple,
|
||||
@@ -107,46 +111,64 @@ get_commands_spec() ->
|
||||
{summary, string}]}}}}},
|
||||
#ejabberd_commands{name = module_install,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Compile, install and start an available contributed module",
|
||||
module = ?MODULE, function = install,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = module_uninstall,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Uninstall a contributed module",
|
||||
module = ?MODULE, function = uninstall,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = module_upgrade,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Upgrade the running code of an installed module",
|
||||
longdesc = "In practice, this uninstalls and installs the module",
|
||||
module = ?MODULE, function = upgrade,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = module_check,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Check the contributed module repository compliance",
|
||||
module = ?MODULE, function = check,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, rescode}}
|
||||
].
|
||||
%% -- public modules functions
|
||||
|
||||
update() ->
|
||||
add_sources(?REPOS),
|
||||
Contrib = maps:put(?REPOS, [], maps:new()),
|
||||
Jungles = lists:foldl(fun({Package, Spec}, Acc) ->
|
||||
Repo = proplists:get_value(url, Spec, ""),
|
||||
Mods = maps:get(Repo, Acc, []),
|
||||
maps:put(Repo, [Package|Mods], Acc)
|
||||
end, Contrib, modules_spec(sources_dir(), "*/*")),
|
||||
Repos = maps:fold(fun(Repo, _Mods, Acc) ->
|
||||
Update = add_sources(Repo),
|
||||
?INFO_MSG("Update packages from repo ~s: ~p", [Repo, Update]),
|
||||
case Update of
|
||||
ok -> Acc;
|
||||
Error -> [{repository, Repo, Error}|Acc]
|
||||
end
|
||||
end, [], Jungles),
|
||||
Res = lists:foldl(fun({Package, Spec}, Acc) ->
|
||||
Path = proplists:get_value(url, Spec, ""),
|
||||
Update = add_sources(Package, Path),
|
||||
Repo = proplists:get_value(url, Spec, ""),
|
||||
Update = add_sources(Package, Repo),
|
||||
?INFO_MSG("Update package ~s: ~p", [Package, Update]),
|
||||
case Update of
|
||||
ok -> Acc;
|
||||
Error -> [Error|Acc]
|
||||
Error -> [{Package, Repo, Error}|Acc]
|
||||
end
|
||||
end, [], modules_spec(sources_dir(), "*")),
|
||||
end, Repos, modules_spec(sources_dir(), "*")),
|
||||
case Res of
|
||||
[] -> ok;
|
||||
[Error|_] -> Error
|
||||
@@ -538,7 +560,9 @@ compile_result(Results) ->
|
||||
compile_options() ->
|
||||
[verbose, report_errors, report_warnings]
|
||||
++ [{i, filename:join(app_dir(App), "include")}
|
||||
|| App <- [fast_xml, xmpp, p1_utils, ejabberd]].
|
||||
|| App <- [fast_xml, xmpp, p1_utils, ejabberd]]
|
||||
++ [{i, filename:join(mod_dir(Mod), "include")}
|
||||
|| Mod <- installed()].
|
||||
|
||||
app_dir(App) ->
|
||||
case code:lib_dir(App) of
|
||||
@@ -553,6 +577,10 @@ app_dir(App) ->
|
||||
Dir
|
||||
end.
|
||||
|
||||
mod_dir({Package, Spec}) ->
|
||||
Default = filename:join(modules_dir(), Package),
|
||||
proplists:get_value(path, Spec, Default).
|
||||
|
||||
compile_erlang_file(Dest, File) ->
|
||||
compile_erlang_file(Dest, File, compile_options()).
|
||||
|
||||
|
||||
+16
-1
@@ -34,7 +34,8 @@
|
||||
stop_child/1, stop_child/2, config_reloaded/0]).
|
||||
-export([start_module/2, start_module/3,
|
||||
stop_module/2, stop_module_keep_config/2,
|
||||
get_opt/2, get_opt/3, get_opt_host/3, opt_type/1, is_equal_opt/4,
|
||||
get_opt/2, get_opt/3, get_opt_host/3,
|
||||
get_opt_hosts/3, opt_type/1, is_equal_opt/4,
|
||||
get_module_opt/3, get_module_opt/4, get_module_opt_host/3,
|
||||
loaded_modules/1, loaded_modules_with_opts/1,
|
||||
get_hosts/2, get_module_proc/2, is_loaded/2, is_loaded_elsewhere/2,
|
||||
@@ -441,6 +442,20 @@ get_opt_host(Host, Opts, Default) ->
|
||||
Val = get_opt(host, Opts, Default),
|
||||
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
|
||||
|
||||
-spec get_opt_hosts(binary(), opts(), binary()) -> [binary()].
|
||||
|
||||
get_opt_hosts(Host, Opts, Default) ->
|
||||
Vals = case get_opt(host, Opts, undefined) of
|
||||
undefined ->
|
||||
case get_opt(hosts, Opts, []) of
|
||||
[] -> [Default];
|
||||
L -> L
|
||||
end;
|
||||
Val ->
|
||||
[Val]
|
||||
end,
|
||||
[ejabberd_regexp:greplace(V, <<"@HOST@">>, Host) || V <- Vals].
|
||||
|
||||
-spec get_validators(binary(), module(), opts()) -> dict:dict() | undef.
|
||||
get_validators(Host, Module, Opts) ->
|
||||
try Module:mod_opt_type('') of
|
||||
|
||||
+14
-2
@@ -32,8 +32,8 @@
|
||||
hex_to_bin/1, hex_to_base64/1, expand_keyword/3,
|
||||
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
|
||||
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
|
||||
encode_pid/1, decode_pid/2, compile_exprs/2, join_atoms/2,
|
||||
try_read_file/1]).
|
||||
now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2,
|
||||
compile_exprs/2, join_atoms/2, try_read_file/1]).
|
||||
|
||||
%% Deprecated functions
|
||||
-export([decode_base64/1, encode_base64/1]).
|
||||
@@ -127,6 +127,18 @@ expr_to_term(Expr) ->
|
||||
term_to_expr(Term) ->
|
||||
list_to_binary(io_lib:print(Term)).
|
||||
|
||||
-spec now_to_usec(erlang:timestamp()) -> non_neg_integer().
|
||||
now_to_usec({MSec, Sec, USec}) ->
|
||||
(MSec*1000000 + Sec)*1000000 + USec.
|
||||
|
||||
-spec usec_to_now(non_neg_integer()) -> erlang:timestamp().
|
||||
usec_to_now(Int) ->
|
||||
Secs = Int div 1000000,
|
||||
USec = Int rem 1000000,
|
||||
MSec = Secs div 1000000,
|
||||
Sec = Secs rem 1000000,
|
||||
{MSec, Sec, USec}.
|
||||
|
||||
l2i(I) when is_integer(I) -> I;
|
||||
l2i(L) when is_binary(L) -> binary_to_integer(L).
|
||||
|
||||
|
||||
+123
-17
@@ -303,6 +303,9 @@ get_commands_spec() ->
|
||||
desc = "List of users logged in host with their statuses",
|
||||
module = ?MODULE, function = status_list,
|
||||
args = [{host, binary}, {status, binary}],
|
||||
args_example = [<<"myserver.com">>, <<"dnd">>],
|
||||
args_desc = ["Server name", "Status type to check"],
|
||||
result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}],
|
||||
result = {users, {list,
|
||||
{userstatus, {tuple, [
|
||||
{user, string},
|
||||
@@ -316,6 +319,9 @@ get_commands_spec() ->
|
||||
desc = "List of logged users with this status",
|
||||
module = ?MODULE, function = status_list,
|
||||
args = [{status, binary}],
|
||||
args_example = [<<"dnd">>],
|
||||
args_desc = ["Status type to check"],
|
||||
result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}],
|
||||
result = {users, {list,
|
||||
{userstatus, {tuple, [
|
||||
{user, string},
|
||||
@@ -330,6 +336,8 @@ get_commands_spec() ->
|
||||
desc = "List all established sessions and their information",
|
||||
module = ?MODULE, function = connected_users_info,
|
||||
args = [],
|
||||
result_example = [{"user1@myserver.com/tka", "c2s", "127.0.0.1",
|
||||
40092, 8, "ejabberd@localhost", 28}],
|
||||
result = {connected_users_info,
|
||||
{list,
|
||||
{sessions, {tuple,
|
||||
@@ -346,6 +354,9 @@ get_commands_spec() ->
|
||||
tags = [session],
|
||||
desc = "Get the list of established sessions in a vhost",
|
||||
module = ?MODULE, function = connected_users_vhost,
|
||||
args_example = [<<"myexample.com">>],
|
||||
args_desc = ["Server name"],
|
||||
result_example = [<<"user1@myserver.com/tka">>, <<"user2@localhost/tka">>],
|
||||
args = [{host, binary}],
|
||||
result = {connected_users_vhost, {list, {sessions, string}}}},
|
||||
#ejabberd_commands{name = user_sessions_info,
|
||||
@@ -353,6 +364,10 @@ get_commands_spec() ->
|
||||
desc = "Get information about all sessions of a user",
|
||||
module = ?MODULE, function = user_sessions_info,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
args_example = [<<"peter">>, <<"myserver.com">>],
|
||||
args_desc = ["User name", "Server name"],
|
||||
result_example = [{"c2s", "127.0.0.1", 42656,8, "ejabberd@localhost",
|
||||
231, <<"dnd">>, <<"tka">>, <<>>}],
|
||||
result = {sessions_info,
|
||||
{list,
|
||||
{session, {tuple,
|
||||
@@ -385,6 +400,9 @@ get_commands_spec() ->
|
||||
"defined by the user client.",
|
||||
module = ?MODULE, function = get_presence,
|
||||
args = [{user, binary}, {server, binary}],
|
||||
args_example = [<<"peter">>, <<"myexample.com">>],
|
||||
args_desc = ["User name", "Server name"],
|
||||
result_example = {<<"user1@myserver.com/tka">>, <<"dnd">>, <<"Busy">>},
|
||||
result =
|
||||
{presence,
|
||||
{tuple,
|
||||
@@ -398,24 +416,40 @@ get_commands_spec() ->
|
||||
{resource, binary}, {type, binary},
|
||||
{show, binary}, {status, binary},
|
||||
{priority, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"tka1">>,
|
||||
<<"available">>,<<"away">>,<<"BB">>, <<"7">>],
|
||||
args_desc = ["User name", "Server name", "Resource",
|
||||
"Type: available, error, probe...",
|
||||
"Show: away, chat, dnd, xa.", "Status text",
|
||||
"Priority, provide this value as an integer"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = set_nickname, tags = [vcard],
|
||||
desc = "Set nickname in a user's vCard",
|
||||
module = ?MODULE, function = set_nickname,
|
||||
args = [{user, binary}, {host, binary}, {nickname, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"User 1">>],
|
||||
args_desc = ["User name", "Server name", "Nickname"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = get_vcard, tags = [vcard],
|
||||
desc = "Get content from a vCard field",
|
||||
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
|
||||
module = ?MODULE, function = get_vcard,
|
||||
args = [{user, binary}, {host, binary}, {name, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"NICKNAME">>],
|
||||
args_desc = ["User name", "Server name", "Field name"],
|
||||
result_example = "User 1",
|
||||
result_desc = "Field content",
|
||||
result = {content, string}},
|
||||
#ejabberd_commands{name = get_vcard2, tags = [vcard],
|
||||
desc = "Get content from a vCard field",
|
||||
desc = "Get content from a vCard subfield",
|
||||
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
|
||||
module = ?MODULE, function = get_vcard,
|
||||
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"N">>, <<"FAMILY">>],
|
||||
args_desc = ["User name", "Server name", "Field name", "Subfield name"],
|
||||
result_example = "Schubert",
|
||||
result_desc = "Field content",
|
||||
result = {content, string}},
|
||||
#ejabberd_commands{name = get_vcard2_multi, tags = [vcard],
|
||||
desc = "Get multiple contents from a vCard field",
|
||||
@@ -429,12 +463,16 @@ get_commands_spec() ->
|
||||
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
|
||||
module = ?MODULE, function = set_vcard,
|
||||
args = [{user, binary}, {host, binary}, {name, binary}, {content, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>, <<"URL">>, <<"www.example.com">>],
|
||||
args_desc = ["User name", "Server name", "Field name", "Value"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = set_vcard2, tags = [vcard],
|
||||
desc = "Set content in a vCard subfield",
|
||||
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
|
||||
module = ?MODULE, function = set_vcard,
|
||||
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {content, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"TEL">>, <<"NUMBER">>, <<"123456">>],
|
||||
args_desc = ["User name", "Server name", "Field name", "Subfield name", "Value"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = set_vcard2_multi, tags = [vcard],
|
||||
desc = "Set multiple contents in a vCard subfield",
|
||||
@@ -451,6 +489,10 @@ get_commands_spec() ->
|
||||
{user, binary}, {server, binary},
|
||||
{nick, binary}, {group, binary},
|
||||
{subs, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>,
|
||||
<<"User 2">>, <<"Friends">>, <<"both">>],
|
||||
args_desc = ["User name", "Server name", "Contact user name", "Contact server name",
|
||||
"Nickname", "Group", "Subscription"],
|
||||
result = {res, rescode}},
|
||||
%%{"", "subs= none, from, to or both"},
|
||||
%%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
|
||||
@@ -460,6 +502,8 @@ get_commands_spec() ->
|
||||
module = ?MODULE, function = delete_rosteritem,
|
||||
args = [{localuser, binary}, {localserver, binary},
|
||||
{user, binary}, {server, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>],
|
||||
args_desc = ["User name", "Server name", "Contact user name", "Contact server name"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = process_rosteritems, tags = [roster],
|
||||
desc = "List/delete rosteritems that match filter (only Mnesia)",
|
||||
@@ -514,8 +558,14 @@ get_commands_spec() ->
|
||||
]}}}}},
|
||||
#ejabberd_commands{name = push_roster, tags = [roster],
|
||||
desc = "Push template roster from file to a user",
|
||||
longdesc = "The text file must contain an erlang term: a list "
|
||||
"of tuples with username, servername, group and nick. Example:\n"
|
||||
"[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
|
||||
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
|
||||
module = ?MODULE, function = push_roster,
|
||||
args = [{file, binary}, {user, binary}, {host, binary}],
|
||||
args_example = [<<"/home/ejabberd/roster.txt">>, <<"user1">>, <<"localhost">>],
|
||||
args_desc = ["File path", "User name", "Server name"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = push_roster_all, tags = [roster],
|
||||
desc = "Push template roster from file to all those users",
|
||||
@@ -525,11 +575,15 @@ get_commands_spec() ->
|
||||
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
|
||||
module = ?MODULE, function = push_roster_all,
|
||||
args = [{file, binary}],
|
||||
args_example = [<<"/home/ejabberd/roster.txt">>],
|
||||
args_desc = ["File path"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = push_alltoall, tags = [roster],
|
||||
desc = "Add all the users to all the users of Host in Group",
|
||||
module = ?MODULE, function = push_alltoall,
|
||||
args = [{host, binary}, {group, binary}],
|
||||
args_example = [<<"myserver.com">>,<<"Everybody">>],
|
||||
args_desc = ["Server name", "Group name"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = get_last, tags = [last],
|
||||
@@ -538,6 +592,10 @@ get_commands_spec() ->
|
||||
"2017-02-23T22:25:28.063062Z ONLINE",
|
||||
module = ?MODULE, function = get_last,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>],
|
||||
args_desc = ["User name", "Server name"],
|
||||
result_example = {<<"2017-06-30T14:32:16.060684Z">>, "ONLINE"},
|
||||
result_desc = "Last activity timestamp and status",
|
||||
result = {last_activity,
|
||||
{tuple, [{timestamp, string},
|
||||
{status, string}
|
||||
@@ -548,17 +606,24 @@ get_commands_spec() ->
|
||||
"1970-01-01 00:00:00 UTC, for example: date +%s",
|
||||
module = mod_last, function = store_last_info,
|
||||
args = [{user, binary}, {host, binary}, {timestamp, integer}, {status, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>, 1500045311, <<"GoSleeping">>],
|
||||
args_desc = ["User name", "Server name", "Number of seconds since epoch", "Status message"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = private_get, tags = [private],
|
||||
desc = "Get some information from a user private storage",
|
||||
module = ?MODULE, function = private_get,
|
||||
args = [{user, binary}, {host, binary}, {element, binary}, {ns, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,<<"storage">>, <<"storage:rosternotes">>],
|
||||
args_desc = ["User name", "Server name", "Element name", "Namespace"],
|
||||
result = {res, string}},
|
||||
#ejabberd_commands{name = private_set, tags = [private],
|
||||
desc = "Set to the user private storage",
|
||||
module = ?MODULE, function = private_set,
|
||||
args = [{user, binary}, {host, binary}, {element, binary}],
|
||||
args_example = [<<"user1">>,<<"myserver.com">>,
|
||||
<<"<query xmlns='jabber:iq:private'> <storage xmlns='storage:rosternotes'/></query>">>],
|
||||
args_desc = ["User name", "Server name", "XML storage element"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = srg_create, tags = [shared_roster_group],
|
||||
@@ -568,41 +633,63 @@ get_commands_spec() ->
|
||||
"put \\ \" around the argument and\nseparate the "
|
||||
"identifiers with \\ \\ n\n"
|
||||
"For example:\n"
|
||||
" ejabberdctl srg_create group3 localhost "
|
||||
" ejabberdctl srg_create group3 myserver.com "
|
||||
"name desc \\\"group1\\\\ngroup2\\\"",
|
||||
module = ?MODULE, function = srg_create,
|
||||
args = [{group, binary}, {host, binary},
|
||||
{name, binary}, {description, binary}, {display, binary}],
|
||||
args_example = [<<"group3">>, <<"myserver.com">>, <<"Group3">>,
|
||||
<<"Third group">>, <<"group1\\\\ngroup2">>],
|
||||
args_desc = ["Group identifier", "Group server name", "Group name",
|
||||
"Group description", "Groups to display"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = srg_delete, tags = [shared_roster_group],
|
||||
desc = "Delete a Shared Roster Group",
|
||||
module = ?MODULE, function = srg_delete,
|
||||
args = [{group, binary}, {host, binary}],
|
||||
args_example = [<<"group3">>, <<"myserver.com">>],
|
||||
args_desc = ["Group identifier", "Group server name"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = srg_list, tags = [shared_roster_group],
|
||||
desc = "List the Shared Roster Groups in Host",
|
||||
module = ?MODULE, function = srg_list,
|
||||
args = [{host, binary}],
|
||||
args_example = [<<"myserver.com">>],
|
||||
args_desc = ["Server name"],
|
||||
result_example = [<<"group1">>, <<"group2">>],
|
||||
result_desc = "List of group identifiers",
|
||||
result = {groups, {list, {id, string}}}},
|
||||
#ejabberd_commands{name = srg_get_info, tags = [shared_roster_group],
|
||||
desc = "Get info of a Shared Roster Group",
|
||||
module = ?MODULE, function = srg_get_info,
|
||||
args = [{group, binary}, {host, binary}],
|
||||
args_example = [<<"group3">>, <<"myserver.com">>],
|
||||
args_desc = ["Group identifier", "Group server name"],
|
||||
result_example = [{<<"name">>, "Group 3"}, {<<"displayed_groups">>, "group1"}],
|
||||
result_desc = "List of group informations, as key and value",
|
||||
result = {informations, {list, {information, {tuple, [{key, string}, {value, string}]}}}}},
|
||||
#ejabberd_commands{name = srg_get_members, tags = [shared_roster_group],
|
||||
desc = "Get members of a Shared Roster Group",
|
||||
module = ?MODULE, function = srg_get_members,
|
||||
args = [{group, binary}, {host, binary}],
|
||||
args_example = [<<"group3">>, <<"myserver.com">>],
|
||||
args_desc = ["Group identifier", "Group server name"],
|
||||
result_example = [<<"user1@localhost">>, <<"user2@localhost">>],
|
||||
result_desc = "List of group identifiers",
|
||||
result = {members, {list, {member, string}}}},
|
||||
#ejabberd_commands{name = srg_user_add, tags = [shared_roster_group],
|
||||
desc = "Add the JID user@host to the Shared Roster Group",
|
||||
module = ?MODULE, function = srg_user_add,
|
||||
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
|
||||
args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
|
||||
args_desc = ["Username", "User server name", "Group identifier", "Group server name"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = srg_user_del, tags = [shared_roster_group],
|
||||
desc = "Delete this JID user@host from the Shared Roster Group",
|
||||
module = ?MODULE, function = srg_user_del,
|
||||
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
|
||||
args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
|
||||
args_desc = ["Username", "User server name", "Group identifier", "Group server name"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = get_offline_count,
|
||||
@@ -611,27 +698,42 @@ get_commands_spec() ->
|
||||
policy = user,
|
||||
module = mod_offline, function = count_offline_messages,
|
||||
args = [],
|
||||
result_example = 5,
|
||||
result_desc = "Number",
|
||||
result = {value, integer}},
|
||||
#ejabberd_commands{name = send_message, tags = [stanza],
|
||||
desc = "Send a message to a local or remote bare of full JID",
|
||||
module = ?MODULE, function = send_message,
|
||||
args = [{type, binary}, {from, binary}, {to, binary},
|
||||
{subject, binary}, {body, binary}],
|
||||
args_example = [<<"headline">>, <<"admin@localhost">>, <<"user1@localhost">>,
|
||||
<<"Restart">>, <<"In 5 minutes">>],
|
||||
args_desc = ["Message type: normal, chat, headline", "Sender JID",
|
||||
"Receiver JID", "Subject, or empty string", "Body"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = send_stanza_c2s, tags = [stanza],
|
||||
desc = "Send a stanza as if sent from a c2s session",
|
||||
module = ?MODULE, function = send_stanza_c2s,
|
||||
args = [{user, binary}, {host, binary}, {resource, binary}, {stanza, binary}],
|
||||
args_example = [<<"admin">>, <<"myserver.com">>, <<"bot">>,
|
||||
<<"<message to='user1@localhost'><ext attr='value'/></message>">>],
|
||||
args_desc = ["Username", "Server name", "Resource", "Stanza"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = send_stanza, tags = [stanza],
|
||||
desc = "Send a stanza; provide From JID and valid To JID",
|
||||
module = ?MODULE, function = send_stanza,
|
||||
args = [{from, binary}, {to, binary}, {stanza, binary}],
|
||||
args_example = [<<"admin@localhost">>, <<"user1@localhost">>,
|
||||
<<"<message><ext attr='value'/></message>">>],
|
||||
args_desc = ["Sender JID", "Destination JID", "Stanza"],
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = privacy_set, tags = [stanza],
|
||||
desc = "Send a IQ set privacy stanza for a local account",
|
||||
module = ?MODULE, function = privacy_set,
|
||||
args = [{user, binary}, {host, binary}, {xmlquery, binary}],
|
||||
args_example = [<<"user1">>, <<"myserver.com">>,
|
||||
<<"<query xmlns='jabber:iq:privacy'>...">>],
|
||||
args_desc = ["Username", "Server name", "Query XML element"],
|
||||
result = {res, rescode}},
|
||||
|
||||
#ejabberd_commands{name = stats, tags = [stats],
|
||||
@@ -639,12 +741,20 @@ get_commands_spec() ->
|
||||
policy = admin,
|
||||
module = ?MODULE, function = stats,
|
||||
args = [{name, binary}],
|
||||
args_example = [<<"registeredusers">>],
|
||||
args_desc = ["Statistic name"],
|
||||
result_example = 6,
|
||||
result_desc = "Integer statistic value",
|
||||
result = {stat, integer}},
|
||||
#ejabberd_commands{name = stats_host, tags = [stats],
|
||||
desc = "Get statistical value for this host: registeredusers onlineusers",
|
||||
policy = admin,
|
||||
module = ?MODULE, function = stats,
|
||||
args = [{name, binary}, {host, binary}],
|
||||
args_example = [<<"registeredusers">>, <<"example.com">>],
|
||||
args_desc = ["Statistic name", "Server JID"],
|
||||
result_example = 6,
|
||||
result_desc = "Integer statistic value",
|
||||
result = {stat, integer}}
|
||||
].
|
||||
|
||||
@@ -952,7 +1062,7 @@ connected_users_info() ->
|
||||
|
||||
connected_users_vhost(Host) ->
|
||||
USRs = ejabberd_sm:get_vh_session_list(Host),
|
||||
[ [U, $@, S, $/, R] || {U, S, R} <- USRs].
|
||||
[ jid:encode(jid:make(USR)) || USR <- USRs].
|
||||
|
||||
%% Code copied from ejabberd_sm.erl and customized
|
||||
dirty_get_sessions_list2() ->
|
||||
@@ -1002,20 +1112,16 @@ set_presence(User, Host, Resource, Type, Show, Status, Priority0) ->
|
||||
Priority = if is_integer(Priority0) -> Priority0;
|
||||
true -> binary_to_integer(Priority0)
|
||||
end,
|
||||
case ejabberd_sm:get_session_pid(User, Host, Resource) of
|
||||
none ->
|
||||
error;
|
||||
Pid ->
|
||||
From = jid:make(User, Host, Resource),
|
||||
To = jid:make(User, Host),
|
||||
Presence = #presence{from = From, to = To,
|
||||
type = misc:binary_to_atom(Type),
|
||||
show = misc:binary_to_atom(Show),
|
||||
status = xmpp:mk_text(Status),
|
||||
priority = Priority},
|
||||
Pid ! {route, Presence},
|
||||
ok
|
||||
end.
|
||||
Pres = #presence{
|
||||
from = jid:make(User, Host, Resource),
|
||||
to = jid:make(User, Host),
|
||||
type = misc:binary_to_atom(Type),
|
||||
status = xmpp:mk_text(Status),
|
||||
show = misc:binary_to_atom(Show),
|
||||
priority = Priority,
|
||||
sub_els = []},
|
||||
Ref = ejabberd_sm:get_session_pid(User, Host, Resource),
|
||||
ejabberd_c2s:set_presence(Ref, Pres).
|
||||
|
||||
user_sessions_info(User, Host) ->
|
||||
CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
|
||||
|
||||
+24
-17
@@ -43,7 +43,7 @@
|
||||
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(state, {host = <<"">> :: binary()}).
|
||||
-record(state, {hosts = [] :: [binary()]}).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_mod API
|
||||
@@ -62,7 +62,9 @@ depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(_) -> [host].
|
||||
mod_opt_type(hosts) ->
|
||||
fun(L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(_) -> [host, hosts].
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
@@ -77,10 +79,13 @@ mod_opt_type(_) -> [host].
|
||||
%%--------------------------------------------------------------------
|
||||
init([Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts,
|
||||
Hosts = gen_mod:get_opt_hosts(Host, Opts,
|
||||
<<"echo.@HOST@">>),
|
||||
ejabberd_router:register_route(MyHost, Host),
|
||||
{ok, #state{host = MyHost}}.
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
ejabberd_router:register_route(H, Host)
|
||||
end, Hosts),
|
||||
{ok, #state{hosts = Hosts}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
||||
@@ -101,17 +106,19 @@ handle_call(stop, _From, State) ->
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast({reload, Host, NewOpts, OldOpts}, State) ->
|
||||
NewMyHost = gen_mod:get_opt_host(Host, NewOpts,
|
||||
<<"echo.@HOST@">>),
|
||||
OldMyHost = gen_mod:get_opt_host(Host, OldOpts,
|
||||
<<"echo.@HOST@">>),
|
||||
if NewMyHost /= OldMyHost ->
|
||||
ejabberd_router:register_route(NewMyHost, Host),
|
||||
ejabberd_router:unregister_route(OldMyHost);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State#state{host = NewMyHost}};
|
||||
NewMyHosts = gen_mod:get_opt_hosts(Host, NewOpts,
|
||||
<<"echo.@HOST@">>),
|
||||
OldMyHosts = gen_mod:get_opt_hosts(Host, OldOpts,
|
||||
<<"echo.@HOST@">>),
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
ejabberd_router:unregister_route(H)
|
||||
end, OldMyHosts -- NewMyHosts),
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
ejabberd_router:register_route(H, Host)
|
||||
end, NewMyHosts -- OldMyHosts),
|
||||
{noreply, State#state{hosts = NewMyHosts}};
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
@@ -147,7 +154,7 @@ handle_info(_Info, State) -> {noreply, State}.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, State) ->
|
||||
ejabberd_router:unregister_route(State#state.host), ok.
|
||||
lists:foreach(fun ejabberd_router:unregister_route/1, State#state.hosts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
|
||||
+10
-2
@@ -538,9 +538,17 @@ json_error(HTTPCode, JSONCode, Message) ->
|
||||
|
||||
log(Call, Args, {Addr, Port}) ->
|
||||
AddrS = misc:ip_to_list({Addr, Port}),
|
||||
?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]);
|
||||
?INFO_MSG("API call ~s ~p from ~s:~p", [Call, hide_sensitive_args(Args), AddrS, Port]);
|
||||
log(Call, Args, IP) ->
|
||||
?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
|
||||
?INFO_MSG("API call ~s ~p (~p)", [Call, hide_sensitive_args(Args), IP]).
|
||||
|
||||
hide_sensitive_args(Args=[_H|_T]) ->
|
||||
lists:map( fun({<<"password">>, Password}) -> {<<"password">>, ejabberd_config:may_hide_data(Password)};
|
||||
({<<"newpass">>,NewPassword}) -> {<<"newpass">>, ejabberd_config:may_hide_data(NewPassword)};
|
||||
(E) -> E end,
|
||||
Args);
|
||||
hide_sensitive_args(NonListArgs) ->
|
||||
NonListArgs.
|
||||
|
||||
permission_addon() ->
|
||||
Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access, none),
|
||||
|
||||
@@ -59,8 +59,13 @@
|
||||
-define(HTTP_ERR_FILE_NOT_FOUND,
|
||||
{-1, 404, [], <<"Not found">>}).
|
||||
|
||||
-define(REQUEST_AUTH_HEADERS,
|
||||
[{<<"WWW-Authenticate">>, <<"Basic realm=\"ejabberd\"">>}]).
|
||||
|
||||
-define(HTTP_ERR_FORBIDDEN,
|
||||
{-1, 403, [], <<"Forbidden">>}).
|
||||
-define(HTTP_ERR_REQUEST_AUTH,
|
||||
{-1, 401, ?REQUEST_AUTH_HEADERS, <<"Unauthorized">>}).
|
||||
|
||||
-define(DEFAULT_CONTENT_TYPE,
|
||||
<<"application/octet-stream">>).
|
||||
@@ -317,12 +322,17 @@ serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentT
|
||||
false
|
||||
end,
|
||||
case CanProceed of
|
||||
false ->
|
||||
?HTTP_ERR_REQUEST_AUTH;
|
||||
true ->
|
||||
FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
|
||||
case file:read_file_info(FileName) of
|
||||
{error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND;
|
||||
{error, enotdir} -> ?HTTP_ERR_FILE_NOT_FOUND;
|
||||
{error, eacces} -> ?HTTP_ERR_FORBIDDEN;
|
||||
{error, enoent} ->
|
||||
?HTTP_ERR_FILE_NOT_FOUND;
|
||||
{error, enotdir} ->
|
||||
?HTTP_ERR_FILE_NOT_FOUND;
|
||||
{error, eacces} ->
|
||||
?HTTP_ERR_FORBIDDEN;
|
||||
{ok, #file_info{type = directory}} -> serve_index(FileName,
|
||||
DirectoryIndices,
|
||||
CustomHeaders,
|
||||
|
||||
+12
-8
@@ -94,7 +94,7 @@
|
||||
|
||||
-record(state,
|
||||
{server_host :: binary(),
|
||||
host :: binary(),
|
||||
hosts :: [binary()],
|
||||
name :: binary(),
|
||||
access :: atom(),
|
||||
max_size :: pos_integer() | infinity,
|
||||
@@ -151,6 +151,8 @@ stop(ServerHost) ->
|
||||
|
||||
mod_opt_type(host) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(name) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(access) ->
|
||||
@@ -194,7 +196,7 @@ mod_opt_type(rm_on_unregister) ->
|
||||
mod_opt_type(thumbnail) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) ->
|
||||
[host, name, access, max_size, secret_length, jid_in_url, file_mode,
|
||||
[host, hosts, name, access, max_size, secret_length, jid_in_url, file_mode,
|
||||
dir_mode, docroot, put_url, get_url, service_url, custom_headers,
|
||||
rm_on_unregister, thumbnail].
|
||||
|
||||
@@ -211,7 +213,7 @@ depends(_Host, _Opts) ->
|
||||
|
||||
init([ServerHost, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"upload.@HOST@">>),
|
||||
Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"upload.@HOST@">>),
|
||||
Name = gen_mod:get_opt(name, Opts, <<"HTTP File Upload">>),
|
||||
Access = gen_mod:get_opt(access, Opts, local),
|
||||
MaxSize = gen_mod:get_opt(max_size, Opts, 104857600),
|
||||
@@ -244,8 +246,11 @@ init([ServerHost, Opts]) ->
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_router:register_route(Host, ServerHost),
|
||||
{ok, #state{server_host = ServerHost, host = Host, name = Name,
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ejabberd_router:register_route(Host, ServerHost)
|
||||
end, Hosts),
|
||||
{ok, #state{server_host = ServerHost, hosts = Hosts, name = Name,
|
||||
access = Access, max_size = MaxSize,
|
||||
secret_length = SecretLength, jid_in_url = JIDinURL,
|
||||
file_mode = FileMode, dir_mode = DirMode,
|
||||
@@ -324,10 +329,9 @@ handle_info(Info, State) ->
|
||||
|
||||
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
|
||||
|
||||
terminate(Reason, #state{server_host = ServerHost, host = Host}) ->
|
||||
terminate(Reason, #state{server_host = ServerHost, hosts = Hosts}) ->
|
||||
?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]),
|
||||
ejabberd_router:unregister_route(Host),
|
||||
ok.
|
||||
lists:foreach(fun ejabberd_router:unregister_route/1, Hosts).
|
||||
|
||||
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
|
||||
|
||||
|
||||
+38
-24
@@ -58,7 +58,7 @@
|
||||
[<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>,
|
||||
<<"utf-8">>, <<"utf-8+latin-1">>]).
|
||||
|
||||
-record(state, {host = <<"">> :: binary(),
|
||||
-record(state, {hosts = [] :: [binary()],
|
||||
server_host = <<"">> :: binary(),
|
||||
access = all :: atom()}).
|
||||
|
||||
@@ -99,8 +99,7 @@ depends(_Host, _Opts) ->
|
||||
init([Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
ejabberd:start_app(iconv),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts,
|
||||
<<"irc.@HOST@">>),
|
||||
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"irc.@HOST@">>),
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
Access = gen_mod:get_opt(access, Opts, all),
|
||||
@@ -108,10 +107,13 @@ init([Host, Opts]) ->
|
||||
[named_table, public,
|
||||
{keypos, #irc_connection.jid_server_host}]),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
register_hooks(MyHost, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host),
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
register_hooks(MyHost, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host)
|
||||
end, MyHosts),
|
||||
{ok,
|
||||
#state{host = MyHost, server_host = Host,
|
||||
#state{hosts = MyHosts, server_host = Host,
|
||||
access = Access}}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
@@ -133,8 +135,8 @@ handle_call(stop, _From, State) ->
|
||||
%% Description: Handling cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
|
||||
NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"irc.@HOST@">>),
|
||||
OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"irc.@HOST@">>),
|
||||
NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"irc.@HOST@">>),
|
||||
OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"irc.@HOST@">>),
|
||||
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE),
|
||||
@@ -145,20 +147,26 @@ handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
|
||||
register_hooks(NewHost, NewIQDisc);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if NewHost /= OldHost ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
unregister_hooks(OldHost);
|
||||
if (NewIQDisc /= OldIQDisc) ->
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
register_hooks(NewHost, NewIQDisc)
|
||||
end, NewHosts -- (NewHosts -- OldHosts));
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
register_hooks(NewHost, NewIQDisc)
|
||||
end, NewHosts -- OldHosts),
|
||||
lists:foreach(
|
||||
fun(OldHost) ->
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
unregister_hooks(OldHost)
|
||||
end, OldHosts -- NewHosts),
|
||||
Access = gen_mod:get_opt(access, NewOpts, all),
|
||||
{noreply, State#state{host = NewHost, access = Access}};
|
||||
{noreply, State#state{hosts = NewHosts, access = Access}};
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
@@ -170,9 +178,10 @@ handle_cast(Msg, State) ->
|
||||
%% Description: Handling all non call/cast messages
|
||||
%%--------------------------------------------------------------------
|
||||
handle_info({route, Packet},
|
||||
#state{host = Host, server_host = ServerHost,
|
||||
access = Access} =
|
||||
#state{server_host = ServerHost, access = Access} =
|
||||
State) ->
|
||||
To = xmpp:get_to(Packet),
|
||||
Host = To#jid.lserver,
|
||||
case catch do_route(Host, ServerHost, Access, Packet) of
|
||||
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
|
||||
_ -> ok
|
||||
@@ -187,9 +196,12 @@ handle_info(_Info, State) -> {noreply, State}.
|
||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
||||
%% The return value is ignored.
|
||||
%%--------------------------------------------------------------------
|
||||
terminate(_Reason, #state{host = MyHost}) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
unregister_hooks(MyHost).
|
||||
terminate(_Reason, #state{hosts = MyHosts}) ->
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
unregister_hooks(MyHost)
|
||||
end, MyHosts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
@@ -975,8 +987,10 @@ mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(default_encoding) ->
|
||||
fun iolist_to_binary/1;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(_) ->
|
||||
[access, db_type, default_encoding, host].
|
||||
[access, db_type, default_encoding, host, hosts].
|
||||
|
||||
-spec extract_ident(stanza()) -> binary().
|
||||
extract_ident(Packet) ->
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start_link/12, start/13, route_chan/4,
|
||||
@@ -91,7 +91,7 @@ start(From, Host, ServerHost, Server, Username,
|
||||
|
||||
start_link(From, Host, Server, Username, Encoding, Port,
|
||||
Password, Ident, RemoteAddr, RealName, WebircPassword, Mod) ->
|
||||
gen_fsm:start_link(?MODULE,
|
||||
p1_fsm:start_link(?MODULE,
|
||||
[From, Host, Server, Username, Encoding, Port, Password,
|
||||
Ident, RemoteAddr, RealName, WebircPassword, Mod],
|
||||
?FSMOPTS).
|
||||
@@ -109,7 +109,7 @@ start_link(From, Host, Server, Username, Encoding, Port,
|
||||
%%----------------------------------------------------------------------
|
||||
init([From, Host, Server, Username, Encoding, Port,
|
||||
Password, Ident, RemoteAddr, RealName, WebircPassword, Mod]) ->
|
||||
gen_fsm:send_event(self(), init),
|
||||
p1_fsm:send_event(self(), init),
|
||||
{ok, open_socket,
|
||||
#state{mod = Mod,
|
||||
encoding = Encoding, port = Port, password = Password,
|
||||
@@ -628,11 +628,11 @@ handle_info({tcp, _Socket, Data}, StateName,
|
||||
StateData#state{inbuf = NewBuf}};
|
||||
handle_info({tcp_closed, _Socket}, StateName,
|
||||
StateData) ->
|
||||
gen_fsm:send_event(self(), closed),
|
||||
p1_fsm:send_event(self(), closed),
|
||||
{next_state, StateName, StateData};
|
||||
handle_info({tcp_error, _Socket, _Reason}, StateName,
|
||||
StateData) ->
|
||||
gen_fsm:send_event(self(), closed),
|
||||
p1_fsm:send_event(self(), closed),
|
||||
{next_state, StateName, StateData}.
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
|
||||
+8
-14
@@ -434,8 +434,9 @@ message_is_archived(false, #{jid := JID}, Pkt) ->
|
||||
delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
|
||||
TypeBin == <<"groupchat">>;
|
||||
TypeBin == <<"all">> ->
|
||||
CurrentTime = p1_time_compat:system_time(micro_seconds),
|
||||
Diff = Days * 24 * 60 * 60 * 1000000,
|
||||
TimeStamp = usec_to_now(p1_time_compat:system_time(micro_seconds) - Diff),
|
||||
TimeStamp = misc:usec_to_now(CurrentTime - Diff),
|
||||
Type = misc:binary_to_atom(TypeBin),
|
||||
DBTypes = lists:usort(
|
||||
lists:map(
|
||||
@@ -830,7 +831,7 @@ select(_LServer, JidRequestor, JidArchive, Query, RSM,
|
||||
Msgs =
|
||||
lists:flatmap(
|
||||
fun({Nick, Pkt, _HaveSubject, Now, _Size}) ->
|
||||
TS = now_to_usec(Now),
|
||||
TS = misc:now_to_usec(Now),
|
||||
case match_interval(Now, Start, End) and
|
||||
match_rsm(Now, RSM) of
|
||||
true ->
|
||||
@@ -979,24 +980,14 @@ match_interval(Now, Start, End) ->
|
||||
(Now >= Start) and (Now =< End).
|
||||
|
||||
match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> ->
|
||||
Now1 = (catch usec_to_now(binary_to_integer(ID))),
|
||||
Now1 = (catch misc:usec_to_now(binary_to_integer(ID))),
|
||||
Now > Now1;
|
||||
match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> ->
|
||||
Now1 = (catch usec_to_now(binary_to_integer(ID))),
|
||||
Now1 = (catch misc:usec_to_now(binary_to_integer(ID))),
|
||||
Now < Now1;
|
||||
match_rsm(_Now, _) ->
|
||||
true.
|
||||
|
||||
now_to_usec({MSec, Sec, USec}) ->
|
||||
(MSec*1000000 + Sec)*1000000 + USec.
|
||||
|
||||
usec_to_now(Int) ->
|
||||
Secs = Int div 1000000,
|
||||
USec = Int rem 1000000,
|
||||
MSec = Secs div 1000000,
|
||||
Sec = Secs rem 1000000,
|
||||
{MSec, Sec, USec}.
|
||||
|
||||
get_jids(undefined) ->
|
||||
[];
|
||||
get_jids(Js) ->
|
||||
@@ -1008,6 +999,9 @@ get_commands_spec() ->
|
||||
longdesc = "Valid message TYPEs: "
|
||||
"\"chat\", \"groupchat\", \"all\".",
|
||||
module = ?MODULE, function = delete_old_messages,
|
||||
args_desc = ["Type of messages to delete (chat, groupchat, all)",
|
||||
"Days to keep messages"],
|
||||
args_example = [<<"all">>, 31],
|
||||
args = [{type, binary}, {days, integer}],
|
||||
result = {res, rescode}}].
|
||||
|
||||
|
||||
+55
-48
@@ -46,7 +46,7 @@
|
||||
?NS_MIX_NODES_CONFIG]).
|
||||
|
||||
-record(state, {server_host :: binary(),
|
||||
host :: binary()}).
|
||||
hosts :: [binary()]}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -124,36 +124,39 @@ process_iq(#iq{lang = Lang} = IQ) ->
|
||||
%%%===================================================================
|
||||
init([ServerHost, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
ConfigTab = gen_mod:get_module_proc(Host, config),
|
||||
ets:new(ConfigTab, [named_table]),
|
||||
ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_MIX_0, ?MODULE, process_iq, IQDisc),
|
||||
ejabberd_router:register_route(Host, ServerHost),
|
||||
{ok, #state{server_host = ServerHost, host = Host}}.
|
||||
Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"mix.@HOST@">>),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ConfigTab = gen_mod:get_module_proc(Host, config),
|
||||
ets:new(ConfigTab, [named_table]),
|
||||
ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
|
||||
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
|
||||
?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
||||
?NS_MIX_0, ?MODULE, process_iq, IQDisc),
|
||||
ejabberd_router:register_route(Host, ServerHost)
|
||||
end, Hosts),
|
||||
{ok, #state{server_host = ServerHost, hosts = Hosts}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
@@ -180,22 +183,24 @@ handle_info({route, Packet}, State) ->
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, #state{host = Host}) ->
|
||||
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
|
||||
ejabberd_router:unregister_route(Host),
|
||||
ok.
|
||||
terminate(_Reason, #state{hosts = Hosts}) ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
|
||||
ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
|
||||
ejabberd_router:unregister_route(Host)
|
||||
end, Hosts).
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
@@ -316,4 +321,6 @@ depends(_Host, _Opts) ->
|
||||
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(_) -> [host, iqdisc].
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(_) -> [host, hosts, iqdisc].
|
||||
|
||||
+53
-35
@@ -75,7 +75,7 @@
|
||||
-include("mod_muc.hrl").
|
||||
|
||||
-record(state,
|
||||
{host = <<"">> :: binary(),
|
||||
{hosts = [] :: [binary()],
|
||||
server_host = <<"">> :: binary(),
|
||||
access = {none, none, none, none} :: {atom(), atom(), atom(), atom()},
|
||||
history_size = 20 :: non_neg_integer(),
|
||||
@@ -151,8 +151,9 @@ room_destroyed(Host, Room, Pid, ServerHost) ->
|
||||
%% If Opts = default, the default room options are used.
|
||||
%% Else use the passed options as defined in mod_muc_room.
|
||||
create_room(Host, Name, From, Nick, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:call(Proc, {create, Name, From, Nick, Opts}).
|
||||
ServerHost = ejabberd_router:host_of_route(Host),
|
||||
Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
|
||||
gen_server:call(Proc, {create, Name, Host, From, Nick, Opts}).
|
||||
|
||||
store_room(ServerHost, Host, Name, Opts) ->
|
||||
LServer = jid:nameprep(ServerHost),
|
||||
@@ -225,22 +226,26 @@ get_online_rooms_by_user(ServerHost, LUser, LServer) ->
|
||||
init([Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
#state{access = Access, host = MyHost,
|
||||
#state{access = Access, hosts = MyHosts,
|
||||
history_size = HistorySize, queue_type = QueueType,
|
||||
room_shaper = RoomShaper} = State = init_state(Host, Opts),
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
RMod = gen_mod:ram_db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, [{host, MyHost}|Opts]),
|
||||
RMod:init(Host, [{host, MyHost}|Opts]),
|
||||
register_iq_handlers(MyHost, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host),
|
||||
load_permanent_rooms(MyHost, Host, Access, HistorySize, RoomShaper, QueueType),
|
||||
Mod:init(Host, [{hosts, MyHosts}|Opts]),
|
||||
RMod:init(Host, [{hosts, MyHosts}|Opts]),
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
register_iq_handlers(MyHost, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host),
|
||||
load_permanent_rooms(MyHost, Host, Access, HistorySize,
|
||||
RoomShaper, QueueType)
|
||||
end, MyHosts),
|
||||
{ok, State}.
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State};
|
||||
handle_call({create, Room, From, Nick, Opts}, _From,
|
||||
#state{host = Host, server_host = ServerHost,
|
||||
handle_call({create, Room, Host, From, Nick, Opts}, _From,
|
||||
#state{server_host = ServerHost,
|
||||
access = Access, default_room_opts = DefOpts,
|
||||
history_size = HistorySize, queue_type = QueueType,
|
||||
room_shaper = RoomShaper} = State) ->
|
||||
@@ -256,51 +261,59 @@ handle_call({create, Room, From, Nick, Opts}, _From,
|
||||
Nick, NewOpts, QueueType),
|
||||
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
|
||||
RMod:register_online_room(ServerHost, Room, Host, Pid),
|
||||
ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]),
|
||||
{reply, ok, State}.
|
||||
|
||||
handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{host = OldHost}) ->
|
||||
handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{hosts = OldHosts}) ->
|
||||
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE),
|
||||
NewRMod = gen_mod:ram_db_mod(ServerHost, NewOpts, ?MODULE),
|
||||
OldMod = gen_mod:db_mod(ServerHost, OldOpts, ?MODULE),
|
||||
OldRMod = gen_mod:ram_db_mod(ServerHost, OldOpts, ?MODULE),
|
||||
#state{host = NewHost} = NewState = init_state(ServerHost, NewOpts),
|
||||
#state{hosts = NewHosts} = NewState = init_state(ServerHost, NewOpts),
|
||||
if NewMod /= OldMod ->
|
||||
NewMod:init(ServerHost, [{host, NewHost}|NewOpts]);
|
||||
NewMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if NewRMod /= OldRMod ->
|
||||
NewRMod:init(ServerHost, [{host, NewHost}|NewOpts]);
|
||||
NewRMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
|
||||
register_iq_handlers(NewHost, NewIQDisc);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if NewHost /= OldHost ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
unregister_iq_handlers(OldHost);
|
||||
if (NewIQDisc /= OldIQDisc) ->
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
register_iq_handlers(NewHost, NewIQDisc)
|
||||
end, NewHosts -- (NewHosts -- OldHosts));
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
register_iq_handlers(NewHost, NewIQDisc)
|
||||
end, NewHosts -- OldHosts),
|
||||
lists:foreach(
|
||||
fun(OldHost) ->
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
unregister_iq_handlers(OldHost)
|
||||
end, OldHosts -- NewHosts),
|
||||
{noreply, NewState};
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({route, Packet},
|
||||
#state{host = Host, server_host = ServerHost,
|
||||
#state{server_host = ServerHost,
|
||||
access = Access, default_room_opts = DefRoomOpts,
|
||||
history_size = HistorySize, queue_type = QueueType,
|
||||
max_rooms_discoitems = MaxRoomsDiscoItems,
|
||||
room_shaper = RoomShaper} = State) ->
|
||||
From = xmpp:get_from(Packet),
|
||||
To = xmpp:get_to(Packet),
|
||||
Host = To#jid.lserver,
|
||||
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
|
||||
From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems,
|
||||
QueueType) of
|
||||
@@ -319,9 +332,12 @@ handle_info(Info, State) ->
|
||||
?ERROR_MSG("unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, #state{host = MyHost}) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
unregister_iq_handlers(MyHost).
|
||||
terminate(_Reason, #state{hosts = MyHosts}) ->
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
unregister_iq_handlers(MyHost)
|
||||
end, MyHosts).
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
@@ -329,8 +345,8 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
init_state(Host, Opts) ->
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts,
|
||||
<<"conference.@HOST@">>),
|
||||
MyHosts = gen_mod:get_opt_hosts(Host, Opts,
|
||||
<<"conference.@HOST@">>),
|
||||
Access = gen_mod:get_opt(access, Opts, all),
|
||||
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
|
||||
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
|
||||
@@ -341,7 +357,7 @@ init_state(Host, Opts) ->
|
||||
QueueType = gen_mod:get_opt(queue_type, Opts,
|
||||
ejabberd_config:default_queue_type(Host)),
|
||||
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
|
||||
#state{host = MyHost,
|
||||
#state{hosts = MyHosts,
|
||||
server_host = Host,
|
||||
access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
|
||||
default_room_opts = DefRoomOpts,
|
||||
@@ -667,7 +683,7 @@ iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM
|
||||
{error, timeout | notfound}.
|
||||
get_room_disco_item({Name, Host, Pid}, Query) ->
|
||||
RoomJID = jid:make(Name, Host),
|
||||
try gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
|
||||
try p1_fsm:sync_send_all_state_event(Pid, Query, 100) of
|
||||
{item, Desc} ->
|
||||
{ok, #disco_item{jid = RoomJID, name = Desc}};
|
||||
false ->
|
||||
@@ -683,7 +699,7 @@ get_subscribed_rooms(ServerHost, Host, From) ->
|
||||
BareFrom = jid:remove_resource(From),
|
||||
lists:flatmap(
|
||||
fun({Name, _, Pid}) ->
|
||||
case gen_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
|
||||
case p1_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
|
||||
true -> [jid:make(Name, Host)];
|
||||
false -> []
|
||||
end;
|
||||
@@ -765,7 +781,7 @@ process_iq_register_set(ServerHost, Host, From,
|
||||
broadcast_service_message(ServerHost, Host, Msg) ->
|
||||
lists:foreach(
|
||||
fun({_, _, Pid}) ->
|
||||
gen_fsm:send_all_state_event(
|
||||
p1_fsm:send_all_state_event(
|
||||
Pid, {service_message, Msg})
|
||||
end, get_online_rooms(ServerHost, Host)).
|
||||
|
||||
@@ -850,6 +866,8 @@ mod_opt_type(ram_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(history_size) ->
|
||||
fun (I) when is_integer(I), I >= 0 -> I end;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(max_room_desc) ->
|
||||
fun (infinity) -> infinity;
|
||||
(I) when is_integer(I), I > 0 -> I
|
||||
@@ -943,7 +961,7 @@ mod_opt_type({default_room_options, presence_broadcast}) ->
|
||||
end;
|
||||
mod_opt_type(_) ->
|
||||
[access, access_admin, access_create, access_persistent,
|
||||
db_type, ram_db_type, history_size, host,
|
||||
db_type, ram_db_type, history_size, host, hosts,
|
||||
max_room_desc, max_room_id, max_room_name,
|
||||
max_rooms_discoitems, max_user_conferences, max_users,
|
||||
max_users_admin_threshold, max_users_presence,
|
||||
|
||||
+10
-10
@@ -597,7 +597,7 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
|
||||
destroy_room(Name, Service) ->
|
||||
case mod_muc:find_online_room(Name, Service) of
|
||||
{ok, Pid} ->
|
||||
gen_fsm:send_all_state_event(Pid, destroy),
|
||||
p1_fsm:send_all_state_event(Pid, destroy),
|
||||
ok;
|
||||
error ->
|
||||
error
|
||||
@@ -716,11 +716,11 @@ get_rooms(ServerHost) ->
|
||||
end, Hosts).
|
||||
|
||||
get_room_config(Room_pid) ->
|
||||
{ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_config),
|
||||
{ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_config),
|
||||
R.
|
||||
|
||||
get_room_state(Room_pid) ->
|
||||
{ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_state),
|
||||
{ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_state),
|
||||
R.
|
||||
|
||||
%%---------------
|
||||
@@ -786,7 +786,7 @@ find_serverhost(Host, ServerHosts) ->
|
||||
ServerHost.
|
||||
|
||||
act_on_room(destroy, {N, H, Pid}, SH) ->
|
||||
gen_fsm:send_all_state_event(
|
||||
p1_fsm:send_all_state_event(
|
||||
Pid, {destroy, <<"Room destroyed by rooms_unused_destroy.">>}),
|
||||
mod_muc:room_destroyed(H, N, Pid, SH),
|
||||
mod_muc:forget_room(SH, H, N);
|
||||
@@ -888,7 +888,7 @@ change_room_option(Name, Service, OptionString, ValueString) ->
|
||||
{Option, Value} = format_room_option(OptionString, ValueString),
|
||||
Config = get_room_config(Pid),
|
||||
Config2 = change_option(Option, Value, Config),
|
||||
{ok, _} = gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}),
|
||||
{ok, _} = p1_fsm:sync_send_all_state_event(Pid, {change_config, Config2}),
|
||||
ok
|
||||
end.
|
||||
|
||||
@@ -983,7 +983,7 @@ get_room_affiliations(Name, Service) ->
|
||||
case mod_muc:find_online_room(Name, Service) of
|
||||
{ok, Pid} ->
|
||||
%% Get the PID of the online room, then request its state
|
||||
{ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, get_state),
|
||||
{ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, get_state),
|
||||
Affiliations = ?DICT:to_list(StateData#state.affiliations),
|
||||
lists:map(
|
||||
fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
|
||||
@@ -1012,7 +1012,7 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
|
||||
case mod_muc:find_online_room(Name, Service) of
|
||||
{ok, Pid} ->
|
||||
%% Get the PID for the online room so we can get the state of the room
|
||||
{ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}),
|
||||
{ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}),
|
||||
mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)),
|
||||
ok;
|
||||
error ->
|
||||
@@ -1035,7 +1035,7 @@ subscribe_room(User, Nick, Room, Nodes) ->
|
||||
UserJID ->
|
||||
case get_room_pid(Name, Host) of
|
||||
Pid when is_pid(Pid) ->
|
||||
case gen_fsm:sync_send_all_state_event(
|
||||
case p1_fsm:sync_send_all_state_event(
|
||||
Pid,
|
||||
{muc_subscribe, UserJID, Nick, NodeList}) of
|
||||
{ok, SubscribedNodes} ->
|
||||
@@ -1062,7 +1062,7 @@ unsubscribe_room(User, Room) ->
|
||||
UserJID ->
|
||||
case get_room_pid(Name, Host) of
|
||||
Pid when is_pid(Pid) ->
|
||||
case gen_fsm:sync_send_all_state_event(
|
||||
case p1_fsm:sync_send_all_state_event(
|
||||
Pid,
|
||||
{muc_unsubscribe, UserJID}) of
|
||||
ok ->
|
||||
@@ -1085,7 +1085,7 @@ unsubscribe_room(User, Room) ->
|
||||
get_subscribers(Name, Host) ->
|
||||
case get_room_pid(Name, Host) of
|
||||
Pid when is_pid(Pid) ->
|
||||
{ok, JIDList} = gen_fsm:sync_send_all_state_event(Pid, get_subscribers),
|
||||
{ok, JIDList} = p1_fsm:sync_send_all_state_event(Pid, get_subscribers),
|
||||
[jid:encode(jid:remove_resource(J)) || J <- JIDList];
|
||||
_ ->
|
||||
throw({error, "The room does not exist"})
|
||||
|
||||
+1
-1
@@ -1142,7 +1142,7 @@ get_room_state(RoomName, MucService) ->
|
||||
-spec get_room_state(pid()) -> mod_muc_room:state().
|
||||
|
||||
get_room_state(RoomPid) ->
|
||||
{ok, R} = gen_fsm:sync_send_all_state_event(RoomPid,
|
||||
{ok, R} = p1_fsm:sync_send_all_state_event(RoomPid,
|
||||
get_state),
|
||||
R.
|
||||
|
||||
|
||||
@@ -296,7 +296,7 @@ import(_LServer, <<"muc_registered">>,
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
init([Host, Opts]) ->
|
||||
MyHost = proplists:get_value(host, Opts),
|
||||
MyHosts = proplists:get_value(hosts, Opts),
|
||||
case gen_mod:db_mod(Host, Opts, mod_muc) of
|
||||
?MODULE ->
|
||||
ejabberd_mnesia:create(?MODULE, muc_room,
|
||||
@@ -318,7 +318,10 @@ init([Host, Opts]) ->
|
||||
{type, ordered_set},
|
||||
{attributes, record_info(fields, muc_online_room)}]),
|
||||
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
|
||||
clean_table_from_bad_node(node(), MyHost),
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
clean_table_from_bad_node(node(), MyHost)
|
||||
end, MyHosts),
|
||||
mnesia:subscribe(system);
|
||||
_ ->
|
||||
ok
|
||||
|
||||
@@ -60,7 +60,7 @@ restore_room(_LServer, Host, Name) ->
|
||||
forget_room(_LServer, Host, Name) ->
|
||||
{atomic, ejabberd_riak:delete(muc_room, {Name, Host})}.
|
||||
|
||||
can_use_nick(LServer, Host, JID, Nick) ->
|
||||
can_use_nick(_LServer, Host, JID, Nick) ->
|
||||
{LUser, LServer, _} = jid:tolower(JID),
|
||||
LUS = {LUser, LServer},
|
||||
case ejabberd_riak:get_by_index(muc_registered,
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start_link/10,
|
||||
@@ -94,23 +94,23 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
||||
Creator, Nick, DefRoomOpts, QueueType) ->
|
||||
gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
|
||||
?FSMOPTS).
|
||||
|
||||
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
|
||||
gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Opts, QueueType],
|
||||
?FSMOPTS).
|
||||
|
||||
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
|
||||
Creator, Nick, DefRoomOpts, QueueType) ->
|
||||
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
|
||||
?FSMOPTS).
|
||||
|
||||
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
|
||||
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
|
||||
RoomShaper, Opts, QueueType],
|
||||
?FSMOPTS).
|
||||
|
||||
@@ -703,7 +703,7 @@ terminate(Reason, _StateName, StateData) ->
|
||||
-spec route(pid(), stanza()) -> ok.
|
||||
route(Pid, Packet) ->
|
||||
#jid{lresource = Nick} = xmpp:get_to(Packet),
|
||||
gen_fsm:send_event(Pid, {route, Nick, Packet}).
|
||||
p1_fsm:send_event(Pid, {route, Nick, Packet}).
|
||||
|
||||
-spec process_groupchat_message(message(), state()) -> fsm_next().
|
||||
process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData) ->
|
||||
@@ -4004,6 +4004,7 @@ tab_add_online_user(JID, StateData) ->
|
||||
Room = StateData#state.room,
|
||||
Host = StateData#state.host,
|
||||
ServerHost = StateData#state.server_host,
|
||||
ejabberd_hooks:run(join_room, ServerHost, [ServerHost, Room, Host, JID]),
|
||||
mod_muc:register_online_user(ServerHost, jid:tolower(JID), Room, Host).
|
||||
|
||||
-spec tab_remove_online_user(jid(), state()) -> any().
|
||||
@@ -4011,6 +4012,7 @@ tab_remove_online_user(JID, StateData) ->
|
||||
Room = StateData#state.room,
|
||||
Host = StateData#state.host,
|
||||
ServerHost = StateData#state.server_host,
|
||||
ejabberd_hooks:run(leave_room, ServerHost, [ServerHost, Room, Host, JID]),
|
||||
mod_muc:unregister_online_user(ServerHost, jid:tolower(JID), Room, Host).
|
||||
|
||||
-spec tab_count_user(jid(), state()) -> non_neg_integer().
|
||||
|
||||
+4
-2
@@ -410,9 +410,11 @@ decode_item(#privacy_item{order = Order,
|
||||
match_presence_out = MatchPresenceOut}
|
||||
end.
|
||||
|
||||
-spec c2s_copy_session(ejabberd_c2s:state(), c2s_state()) -> c2s_state().
|
||||
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
|
||||
c2s_copy_session(State, #{privacy_active_list := List}) ->
|
||||
State#{privacy_active_list => List}.
|
||||
State#{privacy_active_list => List};
|
||||
c2s_copy_session(State, _) ->
|
||||
State.
|
||||
|
||||
-spec user_send_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
|
||||
user_send_packet({#iq{type = Type,
|
||||
|
||||
+3
-1
@@ -112,6 +112,8 @@ depends(_Host, _Opts) ->
|
||||
|
||||
mod_opt_type(access) -> fun acl:access_rules_validator/1;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun(L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(hostname) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(ip) ->
|
||||
fun (S) ->
|
||||
@@ -131,7 +133,7 @@ mod_opt_type(ram_db_type) ->
|
||||
mod_opt_type(Opt) ->
|
||||
case mod_proxy65_stream:listen_opt_type(Opt) of
|
||||
Opts when is_list(Opts) ->
|
||||
[access, host, hostname, ip, name, port,
|
||||
[access, host, hosts, hostname, ip, name, port,
|
||||
max_connections, ram_db_type] ++ Opts;
|
||||
Fun ->
|
||||
Fun
|
||||
|
||||
+55
-40
@@ -43,7 +43,7 @@
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_proxy65_service).
|
||||
|
||||
-record(state, {myhost = <<"">> :: binary()}).
|
||||
-record(state, {myhosts = [] :: [binary()]}).
|
||||
|
||||
%%%------------------------
|
||||
%%% gen_server callbacks
|
||||
@@ -61,24 +61,27 @@ reload(Host, NewOpts, OldOpts) ->
|
||||
init([Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, <<"proxy.@HOST@">>),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
|
||||
?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
|
||||
?MODULE, process_bytestreams, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host),
|
||||
{ok, #state{myhost = MyHost}}.
|
||||
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"proxy.@HOST@">>),
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
|
||||
?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
|
||||
?MODULE, process_bytestreams, IQDisc),
|
||||
ejabberd_router:register_route(MyHost, Host)
|
||||
end, MyHosts),
|
||||
{ok, #state{myhosts = MyHosts}}.
|
||||
|
||||
terminate(_Reason, #state{myhost = MyHost}) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS).
|
||||
terminate(_Reason, #state{myhosts = MyHosts}) ->
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
unregister_handlers(MyHost)
|
||||
end, MyHosts).
|
||||
|
||||
handle_info({route, #iq{} = Packet}, State) ->
|
||||
ejabberd_router:process_iq(Packet),
|
||||
@@ -89,33 +92,29 @@ handle_call(_Request, _From, State) ->
|
||||
{reply, ok, State}.
|
||||
|
||||
handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
|
||||
NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"proxy.@HOST@">>),
|
||||
OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"proxy.@HOST@">>),
|
||||
NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"proxy.@HOST@">>),
|
||||
OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"proxy.@HOST@">>),
|
||||
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, NewIQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, NewIQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_VCARD,
|
||||
?MODULE, process_vcard, NewIQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_BYTESTREAMS,
|
||||
?MODULE, process_bytestreams, NewIQDisc);
|
||||
if (NewIQDisc /= OldIQDisc) ->
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
register_handlers(NewHost, NewIQDisc)
|
||||
end, NewHosts -- (NewHosts -- OldHosts));
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
if NewHost /= OldHost ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_BYTESTREAMS);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State#state{myhost = NewHost}};
|
||||
lists:foreach(
|
||||
fun(NewHost) ->
|
||||
ejabberd_router:register_route(NewHost, ServerHost),
|
||||
register_handlers(NewHost, NewIQDisc)
|
||||
end, NewHosts -- OldHosts),
|
||||
lists:foreach(
|
||||
fun(OldHost) ->
|
||||
ejabberd_router:unregister_route(OldHost),
|
||||
unregister_handlers(OldHost)
|
||||
end, OldHosts -- NewHosts),
|
||||
{noreply, State#state{myhosts = NewHosts}};
|
||||
handle_cast(Msg, State) ->
|
||||
?WARNING_MSG("unexpected cast: ~p", [Msg]),
|
||||
{noreply, State}.
|
||||
@@ -276,3 +275,19 @@ get_my_ip() ->
|
||||
|
||||
max_connections(ServerHost) ->
|
||||
gen_mod:get_module_opt(ServerHost, mod_proxy65, max_connections, infinity).
|
||||
|
||||
register_handlers(Host, IQDisc) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
|
||||
?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS,
|
||||
?MODULE, process_bytestreams, IQDisc).
|
||||
|
||||
unregister_handlers(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS).
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
-author('xram@jabber.ru').
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% gen_fsm callbacks.
|
||||
-export([init/1, handle_event/3, handle_sync_event/4,
|
||||
@@ -75,7 +75,7 @@ start({gen_tcp, Socket}, Opts1) ->
|
||||
[Socket, Host, Opts]).
|
||||
|
||||
start_link(Socket, Host, Opts) ->
|
||||
gen_fsm:start_link(?MODULE, [Socket, Host, Opts], []).
|
||||
p1_fsm:start_link(?MODULE, [Socket, Host, Opts], []).
|
||||
|
||||
init([Socket, Host, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
@@ -106,9 +106,9 @@ socket_type() -> raw.
|
||||
stop(StreamPid) -> StreamPid ! stop.
|
||||
|
||||
activate({P1, J1}, {P2, J2}) ->
|
||||
case catch {gen_fsm:sync_send_all_state_event(P1,
|
||||
case catch {p1_fsm:sync_send_all_state_event(P1,
|
||||
get_socket),
|
||||
gen_fsm:sync_send_all_state_event(P2, get_socket)}
|
||||
p1_fsm:sync_send_all_state_event(P2, get_socket)}
|
||||
of
|
||||
{S1, S2} when is_port(S1), is_port(S2) ->
|
||||
P1 ! {activate, P2, S2, J1, J2},
|
||||
@@ -197,7 +197,7 @@ handle_info({tcp, _S, Data}, StateName, StateData)
|
||||
when StateName /= wait_for_activation ->
|
||||
erlang:cancel_timer(StateData#state.timer),
|
||||
TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop),
|
||||
gen_fsm:send_event(self(), Data),
|
||||
p1_fsm:send_event(self(), Data),
|
||||
{next_state, StateName, StateData#state{timer = TRef}};
|
||||
%% Activation message.
|
||||
handle_info({activate, PeerPid, PeerSocket, IJid, TJid},
|
||||
|
||||
+97
-85
@@ -89,7 +89,7 @@
|
||||
%% API and gen_server callbacks
|
||||
-export([start/2, stop/1, init/1,
|
||||
handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3, depends/2]).
|
||||
terminate/2, code_change/3, depends/2, export/1]).
|
||||
|
||||
-export([send_loop/1, mod_opt_type/1]).
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
-record(state,
|
||||
{
|
||||
server_host,
|
||||
host,
|
||||
hosts,
|
||||
access,
|
||||
pep_mapping = [],
|
||||
ignore_pep_from_offline = true,
|
||||
@@ -205,7 +205,7 @@
|
||||
-type(state() ::
|
||||
#state{
|
||||
server_host :: binary(),
|
||||
host :: mod_pubsub:hostPubsub(),
|
||||
hosts :: [mod_pubsub:hostPubsub()],
|
||||
access :: atom(),
|
||||
pep_mapping :: [{binary(), binary()}],
|
||||
ignore_pep_from_offline :: boolean(),
|
||||
@@ -243,42 +243,63 @@ stop(Host) ->
|
||||
init([ServerHost, Opts]) ->
|
||||
process_flag(trap_exit, true),
|
||||
?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]),
|
||||
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
|
||||
ejabberd_router:register_route(Host, ServerHost),
|
||||
Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"pubsub.@HOST@">>),
|
||||
Access = gen_mod:get_opt(access_createnode, Opts, all),
|
||||
PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, true),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(ServerHost)),
|
||||
LastItemCache = gen_mod:get_opt(last_item_cache, Opts, false),
|
||||
MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, ?MAXITEMS),
|
||||
MaxSubsNode = gen_mod:get_opt(max_subscriptions_node, Opts),
|
||||
case gen_mod:db_type(ServerHost, ?MODULE) of
|
||||
mnesia -> pubsub_index:init(Host, ServerHost, Opts);
|
||||
_ -> ok
|
||||
end,
|
||||
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
|
||||
DefaultModule = plugin(Host, hd(Plugins)),
|
||||
BaseOptions = DefaultModule:options(),
|
||||
DefaultNodeCfg = filter_node_options(
|
||||
gen_mod:get_opt(default_node_config, Opts, []),
|
||||
BaseOptions),
|
||||
ejabberd_mnesia:create(?MODULE, pubsub_last_item,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, pubsub_last_item)}]),
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
T = gen_mod:get_module_proc(H, config),
|
||||
ets:new(T, [set, named_table]),
|
||||
ets:insert(T, {nodetree, NodeTree}),
|
||||
ets:insert(T, {plugins, Plugins}),
|
||||
ets:insert(T, {last_item_cache, LastItemCache}),
|
||||
ets:insert(T, {max_items_node, MaxItemsNode}),
|
||||
ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
|
||||
ets:insert(T, {default_node_config, DefaultNodeCfg}),
|
||||
ets:insert(T, {pep_mapping, PepMapping}),
|
||||
ets:insert(T, {ignore_pep_from_offline, PepOffline}),
|
||||
ets:insert(T, {host, Host}),
|
||||
ets:insert(T, {access, Access})
|
||||
end, [Host, ServerHost]),
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, pubsub_last_item)}]),
|
||||
AllPlugins =
|
||||
lists:flatmap(
|
||||
fun(Host) ->
|
||||
ejabberd_router:register_route(Host, ServerHost),
|
||||
case gen_mod:db_type(ServerHost, ?MODULE) of
|
||||
mnesia -> pubsub_index:init(Host, ServerHost, Opts);
|
||||
_ -> ok
|
||||
end,
|
||||
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
|
||||
DefaultModule = plugin(Host, hd(Plugins)),
|
||||
BaseOptions = DefaultModule:options(),
|
||||
DefaultNodeCfg = filter_node_options(
|
||||
gen_mod:get_opt(default_node_config, Opts, []),
|
||||
BaseOptions),
|
||||
lists:foreach(
|
||||
fun(H) ->
|
||||
T = gen_mod:get_module_proc(H, config),
|
||||
try
|
||||
ets:new(T, [set, named_table]),
|
||||
ets:insert(T, {nodetree, NodeTree}),
|
||||
ets:insert(T, {plugins, Plugins}),
|
||||
ets:insert(T, {last_item_cache, LastItemCache}),
|
||||
ets:insert(T, {max_items_node, MaxItemsNode}),
|
||||
ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
|
||||
ets:insert(T, {default_node_config, DefaultNodeCfg}),
|
||||
ets:insert(T, {pep_mapping, PepMapping}),
|
||||
ets:insert(T, {ignore_pep_from_offline, PepOffline}),
|
||||
ets:insert(T, {host, Host}),
|
||||
ets:insert(T, {access, Access})
|
||||
catch error:badarg when H == ServerHost ->
|
||||
ok
|
||||
end
|
||||
end, [Host, ServerHost]),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB,
|
||||
?MODULE, process_pubsub, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER,
|
||||
?MODULE, process_pubsub_owner, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
|
||||
?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
|
||||
?MODULE, process_commands, IQDisc),
|
||||
Plugins
|
||||
end, Hosts),
|
||||
ejabberd_hooks:add(sm_remove_connection_hook, ServerHost,
|
||||
?MODULE, on_user_offline, 75),
|
||||
ejabberd_hooks:add(disco_local_identity, ServerHost,
|
||||
@@ -297,19 +318,7 @@ init([ServerHost, Opts]) ->
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:add(c2s_handle_info, ServerHost,
|
||||
?MODULE, c2s_handle_info, 50),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
|
||||
?MODULE, process_disco_info, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
|
||||
?MODULE, process_disco_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB,
|
||||
?MODULE, process_pubsub, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER,
|
||||
?MODULE, process_pubsub_owner, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
|
||||
?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
|
||||
?MODULE, process_commands, IQDisc),
|
||||
case lists:member(?PEPNODE, Plugins) of
|
||||
case lists:member(?PEPNODE, AllPlugins) of
|
||||
true ->
|
||||
ejabberd_hooks:add(caps_add, ServerHost,
|
||||
?MODULE, caps_add, 80),
|
||||
@@ -328,20 +337,19 @@ init([ServerHost, Opts]) ->
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
{_, State} = init_send_loop(ServerHost),
|
||||
{_, State} = init_send_loop(ServerHost, Hosts),
|
||||
{ok, State}.
|
||||
|
||||
init_send_loop(ServerHost) ->
|
||||
init_send_loop(ServerHost, Hosts) ->
|
||||
NodeTree = config(ServerHost, nodetree),
|
||||
Plugins = config(ServerHost, plugins),
|
||||
LastItemCache = config(ServerHost, last_item_cache),
|
||||
MaxItemsNode = config(ServerHost, max_items_node),
|
||||
PepMapping = config(ServerHost, pep_mapping),
|
||||
PepOffline = config(ServerHost, ignore_pep_from_offline),
|
||||
Host = config(ServerHost, host),
|
||||
Access = config(ServerHost, access),
|
||||
DBType = gen_mod:db_type(ServerHost, ?MODULE),
|
||||
State = #state{host = Host, server_host = ServerHost,
|
||||
State = #state{hosts = Hosts, server_host = ServerHost,
|
||||
access = Access, pep_mapping = PepMapping,
|
||||
ignore_pep_from_offline = PepOffline,
|
||||
last_item_cache = LastItemCache,
|
||||
@@ -419,8 +427,8 @@ get_subscribed(User, Server) ->
|
||||
send_loop(State) ->
|
||||
receive
|
||||
{presence, JID, _Pid} ->
|
||||
Host = State#state.host,
|
||||
ServerHost = State#state.server_host,
|
||||
Host = host(State#state.server_host),
|
||||
DBType = State#state.db_type,
|
||||
LJID = jid:tolower(JID),
|
||||
BJID = jid:remove_resource(LJID),
|
||||
@@ -461,7 +469,7 @@ send_loop(State) ->
|
||||
send_loop(State);
|
||||
{presence, User, Server, Resources, JID} ->
|
||||
spawn(fun() ->
|
||||
Host = State#state.host,
|
||||
Host = host(State#state.server_host),
|
||||
Owner = jid:remove_resource(jid:tolower(JID)),
|
||||
lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
|
||||
case match_option(Options, send_last_published_item, on_sub_and_presence) of
|
||||
@@ -546,13 +554,11 @@ disco_identity(Host, Node, From) ->
|
||||
case get_allowed_items_call(Host, Nidx, From, Type,
|
||||
Options, Owners) of
|
||||
{result, _} ->
|
||||
{result, [#identity{category = <<"pubsub">>,
|
||||
type = <<"pep">>},
|
||||
#identity{category = <<"pubsub">>,
|
||||
type = <<"leaf">>,
|
||||
{result, [#identity{category = <<"pubsub">>, type = <<"pep">>},
|
||||
#identity{category = <<"pubsub">>, type = <<"leaf">>,
|
||||
name = case get_option(Options, title) of
|
||||
false -> <<>>;
|
||||
[Title] -> Title
|
||||
Title -> Title
|
||||
end}]};
|
||||
_ ->
|
||||
{result, []}
|
||||
@@ -586,8 +592,7 @@ disco_features(Host, Node, From) ->
|
||||
Type, Options, Owners) of
|
||||
{result, _} ->
|
||||
{result,
|
||||
[?NS_PUBSUB |
|
||||
[feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
|
||||
[?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
|
||||
_ ->
|
||||
{result, []}
|
||||
end
|
||||
@@ -620,7 +625,7 @@ disco_items(Host, <<>>, From) ->
|
||||
jid = jid:make(Host),
|
||||
name = case get_option(Options, title) of
|
||||
false -> <<>>;
|
||||
[Title] -> Title
|
||||
Title -> Title
|
||||
end} | Acc];
|
||||
_ ->
|
||||
Acc
|
||||
@@ -679,26 +684,20 @@ caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} =
|
||||
presence(Host, {presence, U, S, [R], JID}).
|
||||
|
||||
-spec presence_probe(jid(), jid(), pid()) -> ok.
|
||||
presence_probe(#jid{luser = U, lserver = S, lresource = R} = JID, JID, Pid) ->
|
||||
presence(S, {presence, JID, Pid}),
|
||||
presence(S, {presence, U, S, [R], JID});
|
||||
presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) ->
|
||||
%% ignore presence_probe from my other ressources
|
||||
%% to not get duplicated last items
|
||||
ok;
|
||||
presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = S} = JID, _Pid) ->
|
||||
presence(S, {presence, U, S, [R], JID});
|
||||
presence_probe(_Host, _JID, _Pid) ->
|
||||
presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, Pid) ->
|
||||
presence(S, {presence, From, Pid}),
|
||||
presence(S, {presence, From#jid.luser, S, [From#jid.lresource], To});
|
||||
presence_probe(_From, _To, _Pid) ->
|
||||
%% ignore presence_probe from remote contacts,
|
||||
%% those are handled via caps_add
|
||||
ok.
|
||||
|
||||
presence(ServerHost, Presence) ->
|
||||
{SendLoop, _} = case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
|
||||
undefined -> init_send_loop(ServerHost);
|
||||
Pid -> {Pid, undefined}
|
||||
end,
|
||||
SendLoop ! Presence,
|
||||
gen_mod:get_module_proc(ServerHost, ?LOOPNAME) ! Presence,
|
||||
ok.
|
||||
|
||||
%% -------
|
||||
@@ -864,7 +863,7 @@ handle_info(_Info, State) ->
|
||||
%%--------------------------------------------------------------------
|
||||
%% @private
|
||||
terminate(_Reason,
|
||||
#state{host = Host, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
|
||||
#state{hosts = Hosts, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
|
||||
case lists:member(?PEPNODE, Plugins) of
|
||||
true ->
|
||||
ejabberd_hooks:delete(caps_add, ServerHost,
|
||||
@@ -902,20 +901,23 @@ terminate(_Reason,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:delete(c2s_handle_info, ServerHost,
|
||||
?MODULE, c2s_handle_info, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
|
||||
case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
|
||||
undefined ->
|
||||
?ERROR_MSG("~s process is dead, pubsub was broken", [?LOOPNAME]);
|
||||
Pid ->
|
||||
Pid ! stop
|
||||
end,
|
||||
terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
|
||||
ejabberd_router:unregister_route(Host).
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
|
||||
terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
|
||||
ejabberd_router:unregister_route(Host)
|
||||
end, Hosts).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
||||
@@ -1723,7 +1725,7 @@ delete_node(Host, Node, Owner) ->
|
||||
%%<li>The node does not support subscriptions.</li>
|
||||
%%<li>The node does not exist.</li>
|
||||
%%</ul>
|
||||
-spec subscribe_node(host(), binary(), jid(), binary(), [{binary(), [binary()]}]) ->
|
||||
-spec subscribe_node(host(), binary(), jid(), jid(), [{binary(), [binary()]}]) ->
|
||||
{result, pubsub()} | {error, stanza_error()}.
|
||||
subscribe_node(Host, Node, From, JID, Configuration) ->
|
||||
SubModule = subscription_plugin(Host),
|
||||
@@ -2152,9 +2154,14 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) ->
|
||||
node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]).
|
||||
|
||||
get_last_items(Host, Type, Nidx, LJID, Count) ->
|
||||
case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of
|
||||
{result, Items} -> Items;
|
||||
_ -> []
|
||||
case get_cached_item(Host, Nidx) of
|
||||
undefined ->
|
||||
case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of
|
||||
{result, Items} -> Items;
|
||||
_ -> []
|
||||
end;
|
||||
LastItem ->
|
||||
[LastItem]
|
||||
end.
|
||||
|
||||
%% @doc <p>Resend the items of a node to the user.</p>
|
||||
@@ -3871,9 +3878,14 @@ purge_offline(Host, LJID, Node) ->
|
||||
Error
|
||||
end.
|
||||
|
||||
export(Server) ->
|
||||
pubsub_db_sql:export(Server).
|
||||
|
||||
mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(ignore_pep_from_offline) ->
|
||||
fun (A) when is_boolean(A) -> A end;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
@@ -3892,7 +3904,7 @@ mod_opt_type(pep_mapping) ->
|
||||
mod_opt_type(plugins) ->
|
||||
fun (A) when is_list(A) -> A end;
|
||||
mod_opt_type(_) ->
|
||||
[access_createnode, db_type, host,
|
||||
[access_createnode, db_type, host, hosts,
|
||||
ignore_pep_from_offline, iqdisc, last_item_cache,
|
||||
max_items_node, nodetree, pep_mapping, plugins,
|
||||
max_subscriptions_node, default_node_config].
|
||||
|
||||
@@ -0,0 +1,596 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_push.erl
|
||||
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%% Purpose : Push Notifications (XEP-0357)
|
||||
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 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(mod_push).
|
||||
-author('holger@zedat.fu-berlin.de').
|
||||
-protocol({xep, 357, '0.2'}).
|
||||
|
||||
-behavior(gen_mod).
|
||||
|
||||
%% gen_mod callbacks.
|
||||
-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2]).
|
||||
|
||||
%% ejabberd_hooks callbacks.
|
||||
-export([disco_sm_features/5, c2s_session_pending/1, c2s_copy_session/2,
|
||||
c2s_handle_cast/2, c2s_stanza/3, mam_message/6, offline_message/1,
|
||||
remove_user/2]).
|
||||
|
||||
%% gen_iq_handler callback.
|
||||
-export([process_iq/1]).
|
||||
|
||||
%% ejabberd command.
|
||||
-export([get_commands_spec/0, delete_old_sessions/1]).
|
||||
|
||||
%% API (used by mod_push_keepalive).
|
||||
-export([notify/1, notify/3, notify/5]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_commands.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-define(PUSH_CACHE, push_cache).
|
||||
|
||||
-type c2s_state() :: ejabberd_c2s:state().
|
||||
-type timestamp() :: erlang:timestamp().
|
||||
-type push_session() :: {timestamp(), ljid(), binary(), xdata()}.
|
||||
|
||||
-callback init(binary(), gen_mod:opts())
|
||||
-> any().
|
||||
-callback store_session(binary(), binary(), timestamp(), jid(), binary(),
|
||||
xdata())
|
||||
-> {ok, push_session()} | error.
|
||||
-callback lookup_session(binary(), binary(), jid(), binary())
|
||||
-> {ok, push_session()} | error.
|
||||
-callback lookup_session(binary(), binary(), timestamp())
|
||||
-> {ok, push_session()} | error.
|
||||
-callback lookup_sessions(binary(), binary(), jid())
|
||||
-> {ok, [push_session()]} | error.
|
||||
-callback lookup_sessions(binary(), binary())
|
||||
-> {ok, [push_session()]} | error.
|
||||
-callback lookup_sessions(binary())
|
||||
-> {ok, [push_session()]} | error.
|
||||
-callback delete_session(binary(), binary(), timestamp())
|
||||
-> ok | error.
|
||||
-callback delete_old_sessions(binary() | global, erlang:timestamp())
|
||||
-> any().
|
||||
-callback use_cache(binary())
|
||||
-> boolean().
|
||||
-callback cache_nodes(binary())
|
||||
-> [node()].
|
||||
|
||||
-optional_callbacks([use_cache/1, cache_nodes/1]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_mod callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec start(binary(), gen_mod:opts()) -> ok.
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
|
||||
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
||||
Mod:init(Host, Opts),
|
||||
init_cache(Mod, Host, Opts),
|
||||
register_iq_handlers(Host, IQDisc),
|
||||
register_hooks(Host),
|
||||
ejabberd_commands:register_commands(get_commands_spec()).
|
||||
|
||||
-spec stop(binary()) -> ok.
|
||||
stop(Host) ->
|
||||
unregister_hooks(Host),
|
||||
unregister_iq_handlers(Host),
|
||||
ejabberd_commands:unregister_commands(get_commands_spec()).
|
||||
|
||||
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
|
||||
reload(Host, NewOpts, OldOpts) ->
|
||||
NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
|
||||
OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
|
||||
if NewMod /= OldMod ->
|
||||
NewMod:init(Host, NewOpts);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts,
|
||||
gen_iq_handler:iqdisc(Host)) of
|
||||
{false, IQDisc, _} ->
|
||||
register_iq_handlers(Host, IQDisc);
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
||||
depends(_Host, _Opts) ->
|
||||
[].
|
||||
|
||||
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
mod_opt_type(db_type) ->
|
||||
fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
fun(I) when is_integer(I), I > 0 -> I;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(iqdisc) ->
|
||||
fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(_) ->
|
||||
[db_type, cache_life_time, cache_size, use_cache, cache_missed, iqdisc].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% ejabberd command callback.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec get_commands_spec() -> [ejabberd_commands()].
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{name = delete_old_push_sessions, tags = [purge],
|
||||
desc = "Remove push sessions older than DAYS",
|
||||
module = ?MODULE, function = delete_old_sessions,
|
||||
args = [{days, integer}],
|
||||
result = {res, rescode}}].
|
||||
|
||||
-spec delete_old_sessions(non_neg_integer()) -> ok | any().
|
||||
delete_old_sessions(Days) ->
|
||||
CurrentTime = p1_time_compat:system_time(micro_seconds),
|
||||
Diff = Days * 24 * 60 * 60 * 1000000,
|
||||
TimeStamp = misc:usec_to_now(CurrentTime - Diff),
|
||||
DBTypes = lists:usort(
|
||||
lists:map(
|
||||
fun(Host) ->
|
||||
case gen_mod:db_type(Host, ?MODULE) of
|
||||
sql -> {sql, Host};
|
||||
Other -> {Other, global}
|
||||
end
|
||||
end, ?MYHOSTS)),
|
||||
Results = lists:map(
|
||||
fun({DBType, Host}) ->
|
||||
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
||||
Mod:delete_old_sessions(Host, TimeStamp)
|
||||
end, DBTypes),
|
||||
ets_cache:clear(?PUSH_CACHE, ejabberd_cluster:get_nodes()),
|
||||
case lists:filter(fun(Res) -> Res /= ok end, Results) of
|
||||
[] ->
|
||||
?INFO_MSG("Deleted push sessions older than ~B days", [Days]),
|
||||
ok;
|
||||
[NotOk | _] ->
|
||||
?ERROR_MSG("Error while deleting old push sessions: ~p", [NotOk]),
|
||||
NotOk
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Register/unregister hooks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec register_hooks(binary()) -> ok.
|
||||
register_hooks(Host) ->
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||
disco_sm_features, 50),
|
||||
ejabberd_hooks:add(c2s_session_pending, Host, ?MODULE,
|
||||
c2s_session_pending, 50),
|
||||
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE,
|
||||
c2s_handle_cast, 50),
|
||||
ejabberd_hooks:add(c2s_handle_send, Host, ?MODULE,
|
||||
c2s_stanza, 50),
|
||||
ejabberd_hooks:add(store_mam_message, Host, ?MODULE,
|
||||
mam_message, 50),
|
||||
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
|
||||
offline_message, 50),
|
||||
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
||||
remove_user, 50).
|
||||
|
||||
-spec unregister_hooks(binary()) -> ok.
|
||||
unregister_hooks(Host) ->
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||
disco_sm_features, 50),
|
||||
ejabberd_hooks:delete(c2s_session_pending, Host, ?MODULE,
|
||||
c2s_session_pending, 50),
|
||||
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE,
|
||||
c2s_handle_cast, 50),
|
||||
ejabberd_hooks:delete(c2s_handle_send, Host, ?MODULE,
|
||||
c2s_stanza, 50),
|
||||
ejabberd_hooks:delete(store_mam_message, Host, ?MODULE,
|
||||
mam_message, 50),
|
||||
ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE,
|
||||
offline_message, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||
remove_user, 50).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Service discovery.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec disco_sm_features(empty | {result, [binary()]} | {error, stanza_error()},
|
||||
jid(), jid(), binary(), binary())
|
||||
-> {result, [binary()]} | {error, stanza_error()}.
|
||||
disco_sm_features(empty, From, To, Node, Lang) ->
|
||||
disco_sm_features({result, []}, From, To, Node, Lang);
|
||||
disco_sm_features({result, OtherFeatures},
|
||||
#jid{luser = U, lserver = S},
|
||||
#jid{luser = U, lserver = S}, <<"">>, _Lang) ->
|
||||
{result, [?NS_PUSH_0 | OtherFeatures]};
|
||||
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% IQ handlers.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec register_iq_handlers(binary(), gen_iq_handler:type()) -> ok.
|
||||
register_iq_handlers(Host, IQDisc) ->
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PUSH_0,
|
||||
?MODULE, process_iq, IQDisc).
|
||||
|
||||
-spec unregister_iq_handlers(binary()) -> ok.
|
||||
unregister_iq_handlers(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUSH_0).
|
||||
|
||||
-spec process_iq(iq()) -> iq().
|
||||
process_iq(#iq{type = get, lang = Lang} = IQ) ->
|
||||
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||
process_iq(#iq{lang = Lang, sub_els = [#push_enable{node = <<>>}]} = IQ) ->
|
||||
Txt = <<"Enabling push without 'node' attribute is not supported">>,
|
||||
xmpp:make_error(IQ, xmpp:err_feature_not_implemented(Txt, Lang));
|
||||
process_iq(#iq{from = #jid{lserver = LServer} = JID,
|
||||
to = #jid{lserver = LServer},
|
||||
sub_els = [#push_enable{jid = PushJID,
|
||||
node = Node,
|
||||
xdata = XData}]} = IQ) ->
|
||||
case enable(JID, PushJID, Node, XData) of
|
||||
ok ->
|
||||
xmpp:make_iq_result(IQ);
|
||||
error ->
|
||||
xmpp:make_error(IQ, xmpp:err_internal_server_error())
|
||||
end;
|
||||
process_iq(#iq{from = #jid{lserver = LServer} = JID,
|
||||
to = #jid{lserver = LServer},
|
||||
sub_els = [#push_disable{jid = PushJID,
|
||||
node = Node}]} = IQ) ->
|
||||
case disable(JID, PushJID, Node) of
|
||||
ok ->
|
||||
xmpp:make_iq_result(IQ);
|
||||
error ->
|
||||
xmpp:make_error(IQ, xmpp:err_item_not_found())
|
||||
end;
|
||||
process_iq(IQ) ->
|
||||
xmpp:make_error(IQ, xmpp:err_not_allowed()).
|
||||
|
||||
-spec enable(jid(), jid(), binary(), xdata()) -> ok | error.
|
||||
enable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
|
||||
PushJID, Node, XData) ->
|
||||
case ejabberd_sm:get_session_sid(LUser, LServer, LResource) of
|
||||
{TS, PID} ->
|
||||
case store_session(LUser, LServer, TS, PushJID, Node, XData) of
|
||||
{ok, _} ->
|
||||
?INFO_MSG("Enabling push notifications for ~s",
|
||||
[jid:encode(JID)]),
|
||||
ejabberd_c2s:cast(PID, push_enable);
|
||||
error ->
|
||||
?ERROR_MSG("Cannot enable push for ~s: database error",
|
||||
[jid:encode(JID)]),
|
||||
error
|
||||
end;
|
||||
none ->
|
||||
?WARNING_MSG("Cannot enable push for ~s: session not found",
|
||||
[jid:encode(JID)]),
|
||||
error
|
||||
end.
|
||||
|
||||
-spec disable(jid(), jid(), binary() | undefined) -> ok | error.
|
||||
disable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
|
||||
PushJID, Node) ->
|
||||
case ejabberd_sm:get_session_sid(LUser, LServer, LResource) of
|
||||
{_TS, PID} ->
|
||||
?INFO_MSG("Disabling push notifications for ~s",
|
||||
[jid:encode(JID)]),
|
||||
ejabberd_c2s:cast(PID, push_disable);
|
||||
none ->
|
||||
?WARNING_MSG("Session not found while disabling push for ~s",
|
||||
[jid:encode(JID)])
|
||||
end,
|
||||
if Node /= undefined ->
|
||||
delete_session(LUser, LServer, PushJID, Node);
|
||||
true ->
|
||||
delete_sessions(LUser, LServer, PushJID)
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Hook callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state().
|
||||
c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
|
||||
_Pkt, _SendResult) ->
|
||||
notify(State),
|
||||
State;
|
||||
c2s_stanza(State, _Pkt, _SendResult) ->
|
||||
State.
|
||||
|
||||
-spec mam_message(message() | drop, binary(), binary(), jid(),
|
||||
chat | groupchat, recv | send) -> message().
|
||||
mam_message(#message{meta = #{push_notified := true}} = Pkt,
|
||||
_LUser, _LServer, _Peer, _Type, _Dir) ->
|
||||
Pkt;
|
||||
mam_message(#message{} = Pkt, LUser, LServer, _Peer, chat, _Dir) ->
|
||||
case lookup_sessions(LUser, LServer) of
|
||||
{ok, [_|_] = Clients} ->
|
||||
case drop_online_sessions(LUser, LServer, Clients) of
|
||||
[_|_] = Clients1 ->
|
||||
?DEBUG("Notifying ~s@~s of MAM message", [LUser, LServer]),
|
||||
notify(LUser, LServer, Clients1);
|
||||
[] ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
xmpp:put_meta(Pkt, push_notified, true);
|
||||
mam_message(Pkt, _LUser, _LServer, _Peer, _Type, _Dir) ->
|
||||
Pkt.
|
||||
|
||||
-spec offline_message({any(), message()}) -> {any(), message()}.
|
||||
offline_message({_Action, #message{meta = #{push_notified := true}}} = Acc) ->
|
||||
Acc;
|
||||
offline_message({Action, #message{to = #jid{luser = LUser,
|
||||
lserver = LServer}} = Pkt}) ->
|
||||
case lookup_sessions(LUser, LServer) of
|
||||
{ok, [_|_] = Clients} ->
|
||||
?DEBUG("Notifying ~s@~s of offline message", [LUser, LServer]),
|
||||
notify(LUser, LServer, Clients);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{Action, xmpp:put_meta(Pkt, push_notified, true)}.
|
||||
|
||||
-spec c2s_session_pending(c2s_state()) -> c2s_state().
|
||||
c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
|
||||
case p1_queue:len(Queue) of
|
||||
Len when Len > 0 ->
|
||||
?DEBUG("Notifying client of unacknowledged messages", []),
|
||||
notify(State),
|
||||
State;
|
||||
0 ->
|
||||
State
|
||||
end;
|
||||
c2s_session_pending(State) ->
|
||||
State.
|
||||
|
||||
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
|
||||
c2s_copy_session(State, #{push_enabled := true}) ->
|
||||
State#{push_enabled => true};
|
||||
c2s_copy_session(State, _) ->
|
||||
State.
|
||||
|
||||
-spec c2s_handle_cast(c2s_state(), any()) -> c2s_state() | {stop, c2s_state()}.
|
||||
c2s_handle_cast(State, push_enable) ->
|
||||
{stop, State#{push_enabled => true}};
|
||||
c2s_handle_cast(State, push_disable) ->
|
||||
{stop, maps:remove(push_enabled, State)};
|
||||
c2s_handle_cast(State, _Msg) ->
|
||||
State.
|
||||
|
||||
-spec remove_user(binary(), binary()) -> ok | error.
|
||||
remove_user(LUser, LServer) ->
|
||||
?INFO_MSG("Removing any push sessions of ~s@~s", [LUser, LServer]),
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
LookupFun = fun() -> Mod:lookup_sessions(LUser, LServer) end,
|
||||
delete_sessions(LUser, LServer, LookupFun, Mod).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Generate push notifications.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec notify(c2s_state()) -> ok.
|
||||
notify(#{jid := #jid{luser = LUser, lserver = LServer}, sid := {TS, _}}) ->
|
||||
case lookup_session(LUser, LServer, TS) of
|
||||
{ok, Client} ->
|
||||
notify(LUser, LServer, [Client]);
|
||||
error ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec notify(binary(), binary(), [push_session()]) -> ok.
|
||||
notify(LUser, LServer, Clients) ->
|
||||
lists:foreach(
|
||||
fun({TS, PushLJID, Node, XData}) ->
|
||||
HandleResponse = fun(#iq{type = result}) ->
|
||||
ok;
|
||||
(#iq{type = error}) ->
|
||||
delete_session(LUser, LServer, TS);
|
||||
(timeout) ->
|
||||
ok % Hmm.
|
||||
end,
|
||||
notify(LServer, PushLJID, Node, XData, HandleResponse)
|
||||
end, Clients).
|
||||
|
||||
-spec notify(binary(), ljid(), binary(), xdata(),
|
||||
fun((iq() | timeout) -> any())) -> ok.
|
||||
notify(LServer, PushLJID, Node, XData, HandleResponse) ->
|
||||
From = jid:make(LServer),
|
||||
Item = #ps_item{xml_els = [xmpp:encode(#push_notification{})]},
|
||||
PubSub = #pubsub{publish = #ps_publish{node = Node, items = [Item]},
|
||||
publish_options = XData},
|
||||
IQ = #iq{type = set,
|
||||
from = From,
|
||||
to = jid:make(PushLJID),
|
||||
id = randoms:get_string(),
|
||||
sub_els = [PubSub]},
|
||||
ejabberd_local:route_iq(IQ, HandleResponse),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec store_session(binary(), binary(), timestamp(), jid(), binary(), xdata())
|
||||
-> {ok, push_session()} | error.
|
||||
store_session(LUser, LServer, TS, PushJID, Node, XData) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
delete_session(LUser, LServer, PushJID, Node),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
|
||||
cache_nodes(Mod, LServer)),
|
||||
ets_cache:update(
|
||||
?PUSH_CACHE,
|
||||
{LUser, LServer, TS}, {ok, {TS, PushJID, Node, XData}},
|
||||
fun() ->
|
||||
Mod:store_session(LUser, LServer, TS, PushJID, Node,
|
||||
XData)
|
||||
end, cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
Mod:store_session(LUser, LServer, TS, PushJID, Node, XData)
|
||||
end.
|
||||
|
||||
-spec lookup_session(binary(), binary(), timestamp())
|
||||
-> {ok, push_session()} | error.
|
||||
lookup_session(LUser, LServer, TS) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:lookup(
|
||||
?PUSH_CACHE, {LUser, LServer, TS},
|
||||
fun() -> Mod:lookup_session(LUser, LServer, TS) end);
|
||||
false ->
|
||||
Mod:lookup_session(LUser, LServer, TS)
|
||||
end.
|
||||
|
||||
-spec lookup_sessions(binary(), binary()) -> {ok, [push_session()]} | error.
|
||||
lookup_sessions(LUser, LServer) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:lookup(
|
||||
?PUSH_CACHE, {LUser, LServer},
|
||||
fun() -> Mod:lookup_sessions(LUser, LServer) end);
|
||||
false ->
|
||||
Mod:lookup_sessions(LUser, LServer)
|
||||
end.
|
||||
|
||||
-spec delete_session(binary(), binary(), timestamp()) -> ok | error.
|
||||
delete_session(LUser, LServer, TS) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
ok = Mod:delete_session(LUser, LServer, TS),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
|
||||
cache_nodes(Mod, LServer)),
|
||||
ets_cache:delete(?PUSH_CACHE, {LUser, LServer, TS},
|
||||
cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec delete_session(binary(), binary(), jid(), binary()) -> ok | error.
|
||||
delete_session(LUser, LServer, PushJID, Node) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
case Mod:lookup_session(LUser, LServer, PushJID, Node) of
|
||||
{ok, {TS, _, _, _}} ->
|
||||
delete_session(LUser, LServer, TS);
|
||||
error ->
|
||||
error
|
||||
end.
|
||||
|
||||
-spec delete_sessions(binary(), binary(), jid()) -> ok | error.
|
||||
delete_sessions(LUser, LServer, PushJID) ->
|
||||
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
||||
LookupFun = fun() -> Mod:lookup_sessions(LUser, LServer, PushJID) end,
|
||||
delete_sessions(LUser, LServer, LookupFun, Mod).
|
||||
|
||||
-spec delete_sessions(binary(), binary(), fun(() -> ok | error), module())
|
||||
-> ok | error.
|
||||
delete_sessions(LUser, LServer, LookupFun, Mod) ->
|
||||
case LookupFun() of
|
||||
{ok, Clients} ->
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
|
||||
cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
lists:foreach(
|
||||
fun({TS, _, _, _}) ->
|
||||
ok = Mod:delete_session(LUser, LServer, TS),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:delete(?PUSH_CACHE,
|
||||
{LUser, LServer, TS},
|
||||
cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end, Clients);
|
||||
error ->
|
||||
error
|
||||
end.
|
||||
|
||||
-spec drop_online_sessions(binary(), binary(), [push_session()])
|
||||
-> [push_session()].
|
||||
drop_online_sessions(LUser, LServer, Clients) ->
|
||||
SessIDs = ejabberd_sm:get_session_sids(LUser, LServer),
|
||||
[Client || {TS, _, _, _} = Client <- Clients,
|
||||
not lists:keyfind(TS, 1, SessIDs)].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Caching.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
|
||||
init_cache(Mod, Host, Opts) ->
|
||||
case use_cache(Mod, Host) of
|
||||
true ->
|
||||
CacheOpts = cache_opts(Host, Opts),
|
||||
ets_cache:new(?PUSH_CACHE, CacheOpts);
|
||||
false ->
|
||||
ets_cache:delete(?PUSH_CACHE)
|
||||
end.
|
||||
|
||||
-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
|
||||
cache_opts(Host, Opts) ->
|
||||
MaxSize = gen_mod:get_opt(
|
||||
cache_size, Opts,
|
||||
ejabberd_config:cache_size(Host)),
|
||||
CacheMissed = gen_mod:get_opt(
|
||||
cache_missed, Opts,
|
||||
ejabberd_config:cache_missed(Host)),
|
||||
LifeTime = case gen_mod:get_opt(
|
||||
cache_life_time, Opts,
|
||||
ejabberd_config:cache_life_time(Host)) of
|
||||
infinity -> infinity;
|
||||
I -> timer:seconds(I)
|
||||
end,
|
||||
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
||||
|
||||
-spec use_cache(module(), binary()) -> boolean().
|
||||
use_cache(Mod, Host) ->
|
||||
case erlang:function_exported(Mod, use_cache, 1) of
|
||||
true -> Mod:use_cache(Host);
|
||||
false ->
|
||||
gen_mod:get_module_opt(
|
||||
Host, ?MODULE, use_cache,
|
||||
ejabberd_config:use_cache(Host))
|
||||
end.
|
||||
|
||||
-spec cache_nodes(module(), binary()) -> [node()].
|
||||
cache_nodes(Mod, Host) ->
|
||||
case erlang:function_exported(Mod, cache_nodes, 1) of
|
||||
true -> Mod:cache_nodes(Host);
|
||||
false -> ejabberd_cluster:get_nodes()
|
||||
end.
|
||||
@@ -0,0 +1,236 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_push_keepalive.erl
|
||||
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%% Purpose : Keep pending XEP-0198 sessions alive with XEP-0357
|
||||
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 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(mod_push_keepalive).
|
||||
-author('holger@zedat.fu-berlin.de').
|
||||
|
||||
-behavior(gen_mod).
|
||||
|
||||
%% gen_mod callbacks.
|
||||
-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2]).
|
||||
|
||||
%% ejabberd_hooks callbacks.
|
||||
-export([c2s_session_pending/1, c2s_session_resumed/1, c2s_copy_session/2,
|
||||
c2s_handle_cast/2, c2s_handle_info/2, c2s_stanza/3]).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-define(PUSH_BEFORE_TIMEOUT_SECS, 120).
|
||||
|
||||
-type c2s_state() :: ejabberd_c2s:state().
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% gen_mod callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec start(binary(), gen_mod:opts()) -> ok.
|
||||
start(Host, Opts) ->
|
||||
case gen_mod:get_opt(wake_on_start, Opts, false) of
|
||||
true ->
|
||||
wake_all(Host);
|
||||
false ->
|
||||
ok
|
||||
end,
|
||||
register_hooks(Host).
|
||||
|
||||
-spec stop(binary()) -> ok.
|
||||
stop(Host) ->
|
||||
unregister_hooks(Host).
|
||||
|
||||
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
|
||||
reload(Host, NewOpts, OldOpts) ->
|
||||
case gen_mod:is_equal_opt(wake_on_start, NewOpts, OldOpts, false) of
|
||||
{false, true, _} ->
|
||||
wake_all(Host);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ok.
|
||||
|
||||
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
||||
depends(_Host, _Opts) ->
|
||||
[{mod_push, hard},
|
||||
{mod_client_state, soft},
|
||||
{mod_stream_mgmt, soft}].
|
||||
|
||||
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
|
||||
mod_opt_type(resume_timeout) ->
|
||||
fun(I) when is_integer(I), I >= 0 -> I;
|
||||
(undefined) -> undefined
|
||||
end;
|
||||
mod_opt_type(wake_on_start) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(wake_on_timeout) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
fun(I) when is_integer(I), I > 0 -> I;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) ->
|
||||
[resume_timeout, wake_on_start, wake_on_timeout, db_type, cache_life_time,
|
||||
cache_size, use_cache, cache_missed, iqdisc].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Register/unregister hooks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec register_hooks(binary()) -> ok.
|
||||
register_hooks(Host) ->
|
||||
ejabberd_hooks:add(c2s_session_pending, Host, ?MODULE,
|
||||
c2s_session_pending, 50),
|
||||
ejabberd_hooks:add(c2s_session_resumed, Host, ?MODULE,
|
||||
c2s_session_resumed, 50),
|
||||
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE,
|
||||
c2s_handle_cast, 40),
|
||||
ejabberd_hooks:add(c2s_handle_info, Host, ?MODULE,
|
||||
c2s_handle_info, 50),
|
||||
ejabberd_hooks:add(c2s_handle_send, Host, ?MODULE,
|
||||
c2s_stanza, 50).
|
||||
|
||||
-spec unregister_hooks(binary()) -> ok.
|
||||
unregister_hooks(Host) ->
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
||||
disco_sm_features, 50),
|
||||
ejabberd_hooks:delete(c2s_session_pending, Host, ?MODULE,
|
||||
c2s_session_pending, 50),
|
||||
ejabberd_hooks:delete(c2s_session_resumed, Host, ?MODULE,
|
||||
c2s_session_resumed, 50),
|
||||
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
|
||||
c2s_copy_session, 50),
|
||||
ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE,
|
||||
c2s_handle_cast, 40),
|
||||
ejabberd_hooks:delete(c2s_handle_info, Host, ?MODULE,
|
||||
c2s_handle_info, 50),
|
||||
ejabberd_hooks:delete(c2s_handle_send, Host, ?MODULE,
|
||||
c2s_stanza, 50).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Hook callbacks.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state().
|
||||
c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
|
||||
_Pkt, _SendResult) ->
|
||||
maybe_restore_resume_timeout(State);
|
||||
c2s_stanza(State, _Pkt, _SendResult) ->
|
||||
State.
|
||||
|
||||
-spec c2s_session_pending(c2s_state()) -> c2s_state().
|
||||
c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
|
||||
case p1_queue:len(Queue) of
|
||||
0 ->
|
||||
State1 = maybe_adjust_resume_timeout(State),
|
||||
maybe_start_wakeup_timer(State1);
|
||||
_ ->
|
||||
State
|
||||
end;
|
||||
c2s_session_pending(State) ->
|
||||
State.
|
||||
|
||||
-spec c2s_session_resumed(c2s_state()) -> c2s_state().
|
||||
c2s_session_resumed(#{push_enabled := true} = State) ->
|
||||
maybe_restore_resume_timeout(State);
|
||||
c2s_session_resumed(State) ->
|
||||
State.
|
||||
|
||||
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
|
||||
c2s_copy_session(State, #{push_enabled := true,
|
||||
push_resume_timeout := ResumeTimeout,
|
||||
push_wake_on_timeout := WakeOnTimeout}) ->
|
||||
State#{push_resume_timeout => ResumeTimeout,
|
||||
push_wake_on_timeout => WakeOnTimeout};
|
||||
c2s_copy_session(State, _) ->
|
||||
State.
|
||||
|
||||
-spec c2s_handle_cast(c2s_state(), any()) -> c2s_state().
|
||||
c2s_handle_cast(#{lserver := LServer} = State, push_enable) ->
|
||||
ResumeTimeout = gen_mod:get_module_opt(LServer, ?MODULE,
|
||||
resume_timeout, 86400),
|
||||
WakeOnTimeout = gen_mod:get_module_opt(LServer, ?MODULE,
|
||||
wake_on_timeout, true),
|
||||
State#{push_resume_timeout => ResumeTimeout,
|
||||
push_wake_on_timeout => WakeOnTimeout};
|
||||
c2s_handle_cast(State, push_disable) ->
|
||||
State1 = maps:remove(push_resume_timeout, State),
|
||||
maps:remove(push_wake_on_timeout, State1);
|
||||
c2s_handle_cast(State, _Msg) ->
|
||||
State.
|
||||
|
||||
-spec c2s_handle_info(c2s_state(), any()) -> c2s_state() | {stop, c2s_state()}.
|
||||
c2s_handle_info(#{push_enabled := true, mgmt_state := pending,
|
||||
jid := JID} = State, {timeout, _, push_keepalive}) ->
|
||||
?INFO_MSG("Waking ~s before session times out", [jid:encode(JID)]),
|
||||
mod_push:notify(State),
|
||||
{stop, State};
|
||||
c2s_handle_info(State, _) ->
|
||||
State.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec maybe_adjust_resume_timeout(c2s_state()) -> c2s_state().
|
||||
maybe_adjust_resume_timeout(#{push_resume_timeout := undefined} = State) ->
|
||||
State;
|
||||
maybe_adjust_resume_timeout(#{push_resume_timeout := Timeout} = State) ->
|
||||
OrigTimeout = mod_stream_mgmt:get_resume_timeout(State),
|
||||
?DEBUG("Adjusting resume timeout to ~B seconds", [Timeout]),
|
||||
State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout),
|
||||
State1#{push_resume_timeout_orig => OrigTimeout}.
|
||||
|
||||
-spec maybe_restore_resume_timeout(c2s_state()) -> c2s_state().
|
||||
maybe_restore_resume_timeout(#{push_resume_timeout_orig := Timeout} = State) ->
|
||||
?DEBUG("Restoring resume timeout to ~B seconds", [Timeout]),
|
||||
State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout),
|
||||
maps:remove(push_resume_timeout_orig, State1);
|
||||
maybe_restore_resume_timeout(State) ->
|
||||
State.
|
||||
|
||||
-spec maybe_start_wakeup_timer(c2s_state()) -> c2s_state().
|
||||
maybe_start_wakeup_timer(#{push_wake_on_timeout := true,
|
||||
push_resume_timeout := ResumeTimeout} = State)
|
||||
when is_integer(ResumeTimeout), ResumeTimeout > ?PUSH_BEFORE_TIMEOUT_SECS ->
|
||||
WakeTimeout = ResumeTimeout - ?PUSH_BEFORE_TIMEOUT_SECS,
|
||||
?DEBUG("Scheduling wake-up timer to fire in ~B seconds", [WakeTimeout]),
|
||||
erlang:start_timer(timer:seconds(WakeTimeout), self(), push_keepalive),
|
||||
State;
|
||||
maybe_start_wakeup_timer(State) ->
|
||||
State.
|
||||
|
||||
-spec wake_all(binary()) -> ok | error.
|
||||
wake_all(LServer) ->
|
||||
?INFO_MSG("Waking all push clients on ~s", [LServer]),
|
||||
Mod = gen_mod:db_mod(LServer, mod_push),
|
||||
case Mod:lookup_sessions(LServer) of
|
||||
{ok, Sessions} ->
|
||||
IgnoreResponse = fun(_) -> ok end,
|
||||
lists:foreach(fun({_, PushLJID, Node, XData}) ->
|
||||
mod_push:notify(LServer, PushLJID, Node,
|
||||
XData, IgnoreResponse)
|
||||
end, Sessions);
|
||||
error ->
|
||||
error
|
||||
end.
|
||||
@@ -0,0 +1,204 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_push_mnesia.erl
|
||||
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%% Purpose : Mnesia backend for Push Notifications (XEP-0357)
|
||||
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 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(mod_push_mnesia).
|
||||
-author('holger@zedat.fu-berlin.de').
|
||||
|
||||
-behavior(mod_push).
|
||||
|
||||
%% API
|
||||
-export([init/2, store_session/6, lookup_session/4, lookup_session/3,
|
||||
lookup_sessions/3, lookup_sessions/2, lookup_sessions/1,
|
||||
delete_session/3, delete_old_sessions/2]).
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("xmpp.hrl").
|
||||
|
||||
-record(push_session,
|
||||
{us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
timestamp = p1_time_compat:timestamp() :: erlang:timestamp(),
|
||||
service = {<<"">>, <<"">>, <<"">>} :: ljid(),
|
||||
node = <<"">> :: binary(),
|
||||
xdata = #xdata{} :: xdata()}).
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%-------------------------------------------------------------------
|
||||
init(_Host, _Opts) ->
|
||||
ejabberd_mnesia:create(?MODULE, push_session,
|
||||
[{disc_only_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, push_session)}]).
|
||||
|
||||
store_session(LUser, LServer, TS, PushJID, Node, XData) ->
|
||||
US = {LUser, LServer},
|
||||
PushLJID = jid:tolower(PushJID),
|
||||
MaxSessions = ejabberd_sm:get_max_user_sessions(LUser, LServer),
|
||||
F = fun() ->
|
||||
if is_integer(MaxSessions) ->
|
||||
enforce_max_sessions(US, MaxSessions - 1);
|
||||
MaxSessions == infinity ->
|
||||
ok
|
||||
end,
|
||||
mnesia:write(#push_session{us = US,
|
||||
timestamp = TS,
|
||||
service = PushLJID,
|
||||
node = Node,
|
||||
xdata = XData})
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
{ok, {TS, PushLJID, Node, XData}};
|
||||
{aborted, E} ->
|
||||
?ERROR_MSG("Cannot store push session for ~s@~s: ~p",
|
||||
[LUser, LServer, E]),
|
||||
error
|
||||
end.
|
||||
|
||||
lookup_session(LUser, LServer, PushJID, Node) ->
|
||||
PushLJID = jid:tolower(PushJID),
|
||||
MatchSpec = ets:fun2ms(
|
||||
fun(#push_session{us = {U, S}, service = P, node = N} = Rec)
|
||||
when U == LUser,
|
||||
S == LServer,
|
||||
P == PushLJID,
|
||||
N == Node ->
|
||||
Rec
|
||||
end),
|
||||
case mnesia:dirty_select(push_session, MatchSpec) of
|
||||
[#push_session{timestamp = TS, xdata = XData}] ->
|
||||
{ok, {TS, PushLJID, Node, XData}};
|
||||
_ ->
|
||||
?DEBUG("No push session found for ~s@~s (~p, ~s)",
|
||||
[LUser, LServer, PushJID, Node]),
|
||||
error
|
||||
end.
|
||||
|
||||
lookup_session(LUser, LServer, TS) ->
|
||||
MatchSpec = ets:fun2ms(
|
||||
fun(#push_session{us = {U, S}, timestamp = T} = Rec)
|
||||
when U == LUser,
|
||||
S == LServer,
|
||||
T == TS ->
|
||||
Rec
|
||||
end),
|
||||
case mnesia:dirty_select(push_session, MatchSpec) of
|
||||
[#push_session{service = PushLJID, node = Node, xdata = XData}] ->
|
||||
{ok, {TS, PushLJID, Node, XData}};
|
||||
_ ->
|
||||
?DEBUG("No push session found for ~s@~s (~p)",
|
||||
[LUser, LServer, TS]),
|
||||
error
|
||||
end.
|
||||
|
||||
lookup_sessions(LUser, LServer, PushJID) ->
|
||||
PushLJID = jid:tolower(PushJID),
|
||||
MatchSpec = ets:fun2ms(
|
||||
fun(#push_session{us = {U, S}, service = P, node = N} = Rec)
|
||||
when U == LUser,
|
||||
S == LServer,
|
||||
P == PushLJID ->
|
||||
Rec
|
||||
end),
|
||||
{ok, mnesia:dirty_select(push_session, MatchSpec)}.
|
||||
|
||||
lookup_sessions(LUser, LServer) ->
|
||||
Records = mnesia:dirty_read(push_session, {LUser, LServer}),
|
||||
Clients = [{TS, PushLJID, Node, XData}
|
||||
|| #push_session{timestamp = TS,
|
||||
service = PushLJID,
|
||||
node = Node,
|
||||
xdata = XData} <- Records],
|
||||
{ok, Clients}.
|
||||
|
||||
lookup_sessions(LServer) ->
|
||||
MatchSpec = ets:fun2ms(
|
||||
fun(#push_session{us = {_U, S},
|
||||
timestamp = TS,
|
||||
service = PushLJID,
|
||||
node = Node,
|
||||
xdata = XData})
|
||||
when S == LServer ->
|
||||
{TS, PushLJID, Node, XData}
|
||||
end),
|
||||
{ok, mnesia:dirty_select(push_session, MatchSpec)}.
|
||||
|
||||
delete_session(LUser, LServer, TS) ->
|
||||
MatchSpec = ets:fun2ms(
|
||||
fun(#push_session{us = {U, S}, timestamp = T} = Rec)
|
||||
when U == LUser,
|
||||
S == LServer,
|
||||
T == TS ->
|
||||
Rec
|
||||
end),
|
||||
F = fun() ->
|
||||
Recs = mnesia:select(push_session, MatchSpec),
|
||||
lists:foreach(fun mnesia:delete_object/1, Recs)
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, E} ->
|
||||
?ERROR_MSG("Cannot delete push seesion of ~s@~s: ~p",
|
||||
[LUser, LServer, E]),
|
||||
error
|
||||
end.
|
||||
|
||||
delete_old_sessions(_LServer, Time) ->
|
||||
DelIfOld = fun(#push_session{timestamp = T} = Rec, ok) when T < Time ->
|
||||
mnesia:delete_object(Rec);
|
||||
(_Rec, ok) ->
|
||||
ok
|
||||
end,
|
||||
F = fun() ->
|
||||
mnesia:foldl(DelIfOld, ok, push_session)
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, E} ->
|
||||
?ERROR_MSG("Cannot delete old push sessions: ~p", [E]),
|
||||
error
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Internal functions.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec enforce_max_sessions({binary(), binary()}, non_neg_integer()) -> ok.
|
||||
enforce_max_sessions({U, S} = US, Max) ->
|
||||
Recs = mnesia:wread({push_session, US}),
|
||||
NumRecs = length(Recs),
|
||||
if NumRecs > Max ->
|
||||
NumOldRecs = NumRecs - Max,
|
||||
Recs1 = lists:keysort(#push_session.timestamp, Recs),
|
||||
Recs2 = lists:reverse(Recs1),
|
||||
OldRecs = lists:sublist(Recs2, Max + 1, NumOldRecs),
|
||||
?INFO_MSG("Disabling ~B old push session(s) of ~s@~s",
|
||||
[NumOldRecs, U, S]),
|
||||
lists:foreach(fun(Rec) -> mnesia:delete_object(Rec) end, OldRecs);
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
+3
-7
@@ -35,7 +35,6 @@
|
||||
-module(mod_roster).
|
||||
|
||||
-protocol({xep, 237, '1.3'}).
|
||||
-protocol({xep, 321, '0.1'}).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
@@ -441,14 +440,13 @@ decode_item(Item, R, Managed) ->
|
||||
end,
|
||||
groups = Item#roster_item.groups}.
|
||||
|
||||
process_iq_set(#iq{from = From, to = To,
|
||||
process_iq_set(#iq{from = _From, to = To,
|
||||
sub_els = [#roster_query{items = [QueryItem]}]} = IQ) ->
|
||||
#jid{user = User, luser = LUser, lserver = LServer} = To,
|
||||
Managed = {From#jid.luser, From#jid.lserver} /= {LUser, LServer},
|
||||
LJID = jid:tolower(QueryItem#roster_item.jid),
|
||||
F = fun () ->
|
||||
Item = get_roster_item(LUser, LServer, LJID),
|
||||
Item2 = decode_item(QueryItem, Item, Managed),
|
||||
Item2 = decode_item(QueryItem, Item, false),
|
||||
Item3 = ejabberd_hooks:run_fold(roster_process_item,
|
||||
LServer, Item2,
|
||||
[LServer]),
|
||||
@@ -1200,8 +1198,6 @@ mod_opt_type(access) ->
|
||||
fun acl:access_rules_validator/1;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(managers) ->
|
||||
fun (B) when is_list(B) -> B end;
|
||||
mod_opt_type(store_current_id) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(versioning) ->
|
||||
@@ -1213,5 +1209,5 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) ->
|
||||
[access, db_type, iqdisc, managers, store_current_id,
|
||||
[access, db_type, iqdisc, store_current_id,
|
||||
versioning, cache_life_time, cache_size, use_cache, cache_missed].
|
||||
|
||||
@@ -27,8 +27,7 @@
|
||||
-ifndef(SIP).
|
||||
-export([]).
|
||||
-else.
|
||||
-define(GEN_FSM, p1_fsm).
|
||||
-behaviour(?GEN_FSM).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% API
|
||||
-export([start/2, start_link/2, route/3, route/4]).
|
||||
@@ -58,10 +57,10 @@ start(LServer, Opts) ->
|
||||
supervisor:start_child(mod_sip_proxy_sup, [LServer, Opts]).
|
||||
|
||||
start_link(LServer, Opts) ->
|
||||
?GEN_FSM:start_link(?MODULE, [LServer, Opts], []).
|
||||
p1_fsm:start_link(?MODULE, [LServer, Opts], []).
|
||||
|
||||
route(SIPMsg, _SIPSock, TrID, Pid) ->
|
||||
?GEN_FSM:send_event(Pid, {SIPMsg, TrID}).
|
||||
p1_fsm:send_event(Pid, {SIPMsg, TrID}).
|
||||
|
||||
route(#sip{hdrs = Hdrs} = Req, LServer, Opts) ->
|
||||
case proplists:get_bool(authenticated, Opts) of
|
||||
|
||||
+42
-12
@@ -33,6 +33,8 @@
|
||||
c2s_unbinded_packet/2, c2s_closed/2, c2s_terminated/2,
|
||||
c2s_handle_send/3, c2s_handle_info/2, c2s_handle_call/3,
|
||||
c2s_handle_recv/3]).
|
||||
%% adjust pending session timeout
|
||||
-export([get_resume_timeout/1, set_resume_timeout/2]).
|
||||
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -235,8 +237,9 @@ c2s_handle_info(#{mgmt_ack_timer := TRef, jid := JID, mod := Mod} = State,
|
||||
[jid:encode(JID)]),
|
||||
State1 = Mod:close(State),
|
||||
{stop, transition_to_pending(State1)};
|
||||
c2s_handle_info(#{mgmt_state := pending, jid := JID, mod := Mod} = State,
|
||||
{timeout, _, pending_timeout}) ->
|
||||
c2s_handle_info(#{mgmt_state := pending,
|
||||
mgmt_pending_timer := TRef, jid := JID, mod := Mod} = State,
|
||||
{timeout, TRef, pending_timeout}) ->
|
||||
?DEBUG("Timed out waiting for resumption of stream for ~s",
|
||||
[jid:encode(JID)]),
|
||||
Mod:stop(State#{mgmt_state => timeout});
|
||||
@@ -282,6 +285,20 @@ c2s_terminated(#{mgmt_state := MgmtState, mgmt_stanzas_in := In, sid := SID,
|
||||
c2s_terminated(State, _Reason) ->
|
||||
State.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Adjust pending session timeout
|
||||
%%%===================================================================
|
||||
-spec get_resume_timeout(state()) -> non_neg_integer().
|
||||
get_resume_timeout(#{mgmt_timeout := Timeout}) ->
|
||||
Timeout.
|
||||
|
||||
-spec set_resume_timeout(state(), non_neg_integer()) -> state().
|
||||
set_resume_timeout(#{mgmt_timeout := Timeout} = State, Timeout) ->
|
||||
State;
|
||||
set_resume_timeout(State, Timeout) ->
|
||||
State1 = restart_pending_timer(State, Timeout),
|
||||
State1#{mgmt_timeout => Timeout}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
@@ -408,8 +425,8 @@ transition_to_pending(#{mgmt_state := active, jid := JID,
|
||||
lserver := LServer, mgmt_timeout := Timeout} = State) ->
|
||||
State1 = cancel_ack_timer(State),
|
||||
?INFO_MSG("Waiting for resumption of stream for ~s", [jid:encode(JID)]),
|
||||
erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout),
|
||||
State2 = State1#{mgmt_state => pending},
|
||||
TRef = erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout),
|
||||
State2 = State1#{mgmt_state => pending, mgmt_pending_timer => TRef},
|
||||
ejabberd_hooks:run_fold(c2s_session_pending, LServer, State2, []);
|
||||
transition_to_pending(State) ->
|
||||
State.
|
||||
@@ -648,20 +665,33 @@ add_resent_delay_info(_State, El, _Time) ->
|
||||
send(#{mod := Mod} = State, Pkt) ->
|
||||
Mod:send(State, Pkt).
|
||||
|
||||
-spec restart_pending_timer(state(), non_neg_integer()) -> state().
|
||||
restart_pending_timer(#{mgmt_pending_timer := TRef} = State, NewTimeout) ->
|
||||
cancel_timer(TRef),
|
||||
NewTRef = erlang:start_timer(timer:seconds(NewTimeout), self(),
|
||||
pending_timeout),
|
||||
State#{mgmt_pending_timer => NewTRef};
|
||||
restart_pending_timer(State, _NewTimeout) ->
|
||||
State.
|
||||
|
||||
-spec cancel_ack_timer(state()) -> state().
|
||||
cancel_ack_timer(#{mgmt_ack_timer := TRef} = State) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive {timeout, TRef, _} -> ok
|
||||
after 0 -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
cancel_timer(TRef),
|
||||
maps:remove(mgmt_ack_timer, State);
|
||||
cancel_ack_timer(State) ->
|
||||
State.
|
||||
|
||||
-spec cancel_timer(reference()) -> ok.
|
||||
cancel_timer(TRef) ->
|
||||
case erlang:cancel_timer(TRef) of
|
||||
false ->
|
||||
receive {timeout, TRef, _} -> ok
|
||||
after 0 -> ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec bounce_message_queue() -> ok.
|
||||
bounce_message_queue() ->
|
||||
receive {route, Pkt} ->
|
||||
|
||||
+45
-37
@@ -67,7 +67,7 @@
|
||||
|
||||
-optional_callbacks([use_cache/1, cache_nodes/1]).
|
||||
|
||||
-record(state, {host :: binary(), server_host :: binary()}).
|
||||
-record(state, {hosts :: [binary()], server_host :: binary()}).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_mod callbacks
|
||||
@@ -95,37 +95,40 @@ init([Host, Opts]) ->
|
||||
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
|
||||
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
||||
get_sm_features, 50),
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts, <<"vjud.@HOST@">>),
|
||||
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
|
||||
Search = gen_mod:get_opt(search, Opts, false),
|
||||
if Search ->
|
||||
ejabberd_hooks:add(
|
||||
disco_local_items, MyHost, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(
|
||||
disco_local_features, MyHost, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(
|
||||
disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
case Mod:is_search_supported(Host) of
|
||||
false ->
|
||||
?WARNING_MSG("vcard search functionality is "
|
||||
"not implemented for ~s backend",
|
||||
[gen_mod:db_type(Host, Opts, ?MODULE)]);
|
||||
true ->
|
||||
ejabberd_router:register_route(MyHost, Host)
|
||||
end;
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
ejabberd_hooks:add(
|
||||
disco_local_items, MyHost, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:add(
|
||||
disco_local_features, MyHost, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:add(
|
||||
disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
|
||||
process_local_iq_items, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(
|
||||
ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
|
||||
process_local_iq_info, IQDisc),
|
||||
case Mod:is_search_supported(Host) of
|
||||
false ->
|
||||
?WARNING_MSG("vcard search functionality is "
|
||||
"not implemented for ~s backend",
|
||||
[gen_mod:db_type(Host, Opts, ?MODULE)]);
|
||||
true ->
|
||||
ejabberd_router:register_route(MyHost, Host)
|
||||
end
|
||||
end, MyHosts);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
{ok, #state{host = MyHost, server_host = Host}}.
|
||||
{ok, #state{hosts = MyHosts, server_host = Host}}.
|
||||
|
||||
handle_call(_Call, _From, State) ->
|
||||
{noreply, State}.
|
||||
@@ -144,21 +147,24 @@ handle_info(Info, State) ->
|
||||
?WARNING_MSG("unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, #state{host = MyHost, server_host = Host}) ->
|
||||
terminate(_Reason, #state{hosts = MyHosts, server_host = Host}) ->
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
|
||||
Mod = gen_mod:db_mod(Host, ?MODULE),
|
||||
Mod:stop(Host),
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO).
|
||||
lists:foreach(
|
||||
fun(MyHost) ->
|
||||
ejabberd_router:unregister_route(MyHost),
|
||||
ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100),
|
||||
ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100),
|
||||
ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO)
|
||||
end, MyHosts).
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
@@ -527,6 +533,8 @@ mod_opt_type(allow_return_all) ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
mod_opt_type(host) -> fun iolist_to_binary/1;
|
||||
mod_opt_type(hosts) ->
|
||||
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
|
||||
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
|
||||
mod_opt_type(matches) ->
|
||||
fun (infinity) -> infinity;
|
||||
@@ -543,6 +551,6 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size ->
|
||||
mod_opt_type(O) when O == use_cache; O == cache_missed ->
|
||||
fun (B) when is_boolean(B) -> B end;
|
||||
mod_opt_type(_) ->
|
||||
[allow_return_all, db_type, host, iqdisc, matches,
|
||||
[allow_return_all, db_type, host, hosts, iqdisc, matches,
|
||||
search, search_all_hosts, cache_life_time, cache_size,
|
||||
use_cache, cache_missed].
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
-record(state,
|
||||
{serverhost = <<"">> :: binary(),
|
||||
myhost = <<"">> :: binary(),
|
||||
myhosts = [] :: [binary()],
|
||||
eldap_id = <<"">> :: binary(),
|
||||
search = false :: boolean(),
|
||||
servers = [] :: [binary()],
|
||||
@@ -351,8 +351,7 @@ default_search_reported() ->
|
||||
{<<"Organization Unit">>, <<"ORGUNIT">>}].
|
||||
|
||||
parse_options(Host, Opts) ->
|
||||
MyHost = gen_mod:get_opt_host(Host, Opts,
|
||||
<<"vjud.@HOST@">>),
|
||||
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
|
||||
Search = gen_mod:get_opt(search, Opts, false),
|
||||
Matches = gen_mod:get_opt(matches, Opts, 30),
|
||||
Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?PROCNAME)),
|
||||
@@ -394,7 +393,7 @@ parse_options(Host, Opts) ->
|
||||
end,
|
||||
SearchReported)
|
||||
++ UIDAttrs),
|
||||
#state{serverhost = Host, myhost = MyHost,
|
||||
#state{serverhost = Host, myhosts = MyHosts,
|
||||
eldap_id = Eldap_ID, search = Search,
|
||||
servers = Cfg#eldap_config.servers,
|
||||
backups = Cfg#eldap_config.backups,
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ create_node(Nidx, Owner) ->
|
||||
|
||||
delete_node(Nodes) ->
|
||||
{result, {_, _, Result}} = node_flat:delete_node(Nodes),
|
||||
{result, {[], Result}}.
|
||||
{result, {default, Result}}.
|
||||
|
||||
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
|
||||
@@ -73,7 +73,7 @@ create_node(Nidx, Owner) ->
|
||||
|
||||
delete_node(Nodes) ->
|
||||
{result, {_, _, Result}} = node_flat_sql:delete_node(Nodes),
|
||||
{result, {[], Result}}.
|
||||
{result, {default, Result}}.
|
||||
|
||||
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
|
||||
SendLast, PresenceSubscription, RosterGroup, Options) ->
|
||||
|
||||
+68
-46
@@ -50,7 +50,7 @@ from_dir(ProsodyDir) ->
|
||||
convert_dir(Path, Host, SubDir)
|
||||
end, ["vcard", "accounts", "roster",
|
||||
"private", "config", "offline",
|
||||
"privacy", "pubsub"])
|
||||
"privacy", "pep", "pubsub"])
|
||||
end, HostDirs);
|
||||
{error, Why} = Err ->
|
||||
?ERROR_MSG("failed to list ~s: ~s",
|
||||
@@ -67,12 +67,24 @@ convert_dir(Path, Host, Type) ->
|
||||
lists:foreach(
|
||||
fun(File) ->
|
||||
FilePath = filename:join(Path, File),
|
||||
case eval_file(FilePath) of
|
||||
{ok, Data} ->
|
||||
Name = iolist_to_binary(filename:rootname(File)),
|
||||
convert_data(Host, Type, Name, Data);
|
||||
Err ->
|
||||
Err
|
||||
case Type of
|
||||
"pep" ->
|
||||
case filelib:is_dir(FilePath) of
|
||||
true ->
|
||||
JID = list_to_binary(File ++ "@" ++ Host),
|
||||
convert_dir(FilePath, JID, "pubsub");
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
case eval_file(FilePath) of
|
||||
{ok, Data} ->
|
||||
Name = iolist_to_binary(filename:rootname(File)),
|
||||
convert_data(url_decode(Host), Type,
|
||||
url_decode(Name), Data);
|
||||
Err ->
|
||||
Err
|
||||
end
|
||||
end
|
||||
end, Files);
|
||||
{error, enoent} ->
|
||||
@@ -212,44 +224,49 @@ convert_data(Host, "privacy", User, [Data]) ->
|
||||
end
|
||||
end, Lists)},
|
||||
mod_privacy:set_list(Priv);
|
||||
convert_data(PubSub, "pubsub", NodeId, [Data]) ->
|
||||
Host = url_decode(PubSub),
|
||||
Node = url_decode(NodeId),
|
||||
Type = node_type(Host, Node),
|
||||
NodeData = convert_node_config(Host, Data),
|
||||
DefaultConfig = mod_pubsub:config(Host, default_node_config, []),
|
||||
Owner = proplists:get_value(owner, NodeData),
|
||||
Options = lists:foldl(
|
||||
fun({_Opt, undefined}, Acc) ->
|
||||
Acc;
|
||||
({Opt, Val}, Acc) ->
|
||||
lists:keystore(Opt, 1, Acc, {Opt, Val})
|
||||
end, DefaultConfig, proplists:get_value(options, NodeData)),
|
||||
case mod_pubsub:tree_action(Host, create_node, [Host, Node, Type, Owner, Options, []]) of
|
||||
{ok, Nidx} ->
|
||||
case mod_pubsub:node_action(Host, Type, create_node, [Nidx, Owner]) of
|
||||
{result, _} ->
|
||||
Access = open, % always allow subscriptions proplists:get_value(access_model, Options),
|
||||
Publish = open, % always allow publications proplists:get_value(publish_model, Options),
|
||||
MaxItems = proplists:get_value(max_items, Options),
|
||||
Affiliations = proplists:get_value(affiliations, NodeData),
|
||||
Subscriptions = proplists:get_value(subscriptions, NodeData),
|
||||
Items = proplists:get_value(items, NodeData),
|
||||
[mod_pubsub:node_action(Host, Type, set_affiliation,
|
||||
[Nidx, Entity, Aff])
|
||||
|| {Entity, Aff} <- Affiliations, Entity =/= Owner],
|
||||
[mod_pubsub:node_action(Host, Type, subscribe_node,
|
||||
[Nidx, jid:make(Entity), Entity, Access, never, [], [], []])
|
||||
|| Entity <- Subscriptions],
|
||||
[mod_pubsub:node_action(Host, Type, publish_item,
|
||||
[Nidx, Publisher, Publish, MaxItems, ItemId, Payload, []])
|
||||
|| {ItemId, Publisher, Payload} <- Items];
|
||||
convert_data(HostStr, "pubsub", Node, [Data]) ->
|
||||
case decode_pubsub_host(HostStr) of
|
||||
Host when is_binary(Host);
|
||||
is_tuple(Host) ->
|
||||
Type = node_type(Host),
|
||||
NodeData = convert_node_config(HostStr, 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 ~p:~n~p",
|
||||
[Node, Host, NodeData]),
|
||||
Error
|
||||
end;
|
||||
Error ->
|
||||
?ERROR_MSG("failed to import pubsub node ~s on host ~s:~n~p",
|
||||
[Node, Host, NodeData]),
|
||||
?ERROR_MSG("failed to import pubsub node: ~p", [Error]),
|
||||
Error
|
||||
end;
|
||||
convert_data(_Host, _Type, _User, _Data) ->
|
||||
@@ -383,10 +400,15 @@ url_decode(<<H, Tail/binary>>, Acc) ->
|
||||
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)).
|
||||
decode_pubsub_host(Host) ->
|
||||
try jid:decode(Host) of
|
||||
#jid{luser = <<>>, lserver = LServer} -> LServer;
|
||||
#jid{luser = LUser, lserver = LServer} -> {LUser, LServer, <<>>}
|
||||
catch _:{bad_jid, _} -> bad_jid
|
||||
end.
|
||||
|
||||
node_type({_U, _S, _R}) -> <<"pep">>;
|
||||
node_type(Host) -> hd(mod_pubsub:plugins(Host)).
|
||||
|
||||
max_items(Config, Default) ->
|
||||
case round(proplists:get_value(<<"max_items">>, Config, Default)) of
|
||||
@@ -422,7 +444,7 @@ 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
|
||||
try jid:decode(proplists:get_value(ItemId, Authors, Host)) of
|
||||
JID ->
|
||||
[El] = deserialize(Item),
|
||||
[{ItemId, JID, El#xmlel.children}]
|
||||
|
||||
@@ -25,12 +25,16 @@
|
||||
|
||||
-module(pubsub_db_sql).
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-author("pablo.polvorin@process-one.net").
|
||||
|
||||
-include("pubsub.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
|
||||
-export([add_subscription/1, read_subscription/1,
|
||||
delete_subscription/1, update_subscription/1]).
|
||||
-export([export/1]).
|
||||
|
||||
%% TODO: Those -spec lines produce errors in old Erlang versions.
|
||||
%% They can be enabled again in ejabberd 3.0 because it uses R12B or higher.
|
||||
@@ -139,3 +143,93 @@ sql_to_integer(N) -> binary_to_integer(N).
|
||||
sql_to_boolean(B) -> B == <<"1">>.
|
||||
|
||||
sql_to_timestamp(T) -> xmpp_util:decode_timestamp(T).
|
||||
|
||||
%% REVIEW:
|
||||
%% * this code takes NODEID from Itemid2, and forgets about Nodeidx
|
||||
%% * this code assumes Payload only contains one xmlelement()
|
||||
%% * PUBLISHER is taken from Creation
|
||||
export(_Server) ->
|
||||
[{pubsub_item,
|
||||
fun(_Host, #pubsub_item{itemid = {Itemid1, NODEID},
|
||||
%nodeidx = _Nodeidx,
|
||||
creation = {{C1, C2, C3}, Cusr},
|
||||
modification = {{M1, M2, M3}, _Musr},
|
||||
payload = Payload}) ->
|
||||
ITEMID = ejabberd_sql:escape(Itemid1),
|
||||
CREATION = ejabberd_sql:escape(list_to_binary(
|
||||
string:join([string:right(integer_to_list(I),6,$0)||I<-[C1,C2,C3]],":"))),
|
||||
MODIFICATION = ejabberd_sql:escape(list_to_binary(
|
||||
string:join([string:right(integer_to_list(I),6,$0)||I<-[M1,M2,M3]],":"))),
|
||||
PUBLISHER = ejabberd_sql:escape(jid:encode(Cusr)),
|
||||
[PayloadEl] = [El || {xmlel,_,_,_} = El <- Payload],
|
||||
PAYLOAD = ejabberd_sql:escape(fxml:element_to_binary(PayloadEl)),
|
||||
[?SQL("delete from pubsub_item where itemid=%(ITEMID)s;"),
|
||||
?SQL("insert into pubsub_item(itemid,nodeid,creation,modification,publisher,payload) \n"
|
||||
" values (%(ITEMID)s, %(NODEID)d, %(CREATION)s,
|
||||
%(MODIFICATION)s, %(PUBLISHER)s, %(PAYLOAD)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end},
|
||||
%% REVIEW:
|
||||
%% * From the mnesia table, the #pubsub_state.items is not used in ODBC
|
||||
%% * Right now AFFILIATION is the first letter of Affiliation
|
||||
%% * Right now SUBSCRIPTIONS expects only one Subscription
|
||||
%% * Right now SUBSCRIPTIONS letter is the first letter of Subscription
|
||||
{pubsub_state,
|
||||
fun(_Host, #pubsub_state{stateid = {Jid, Stateid},
|
||||
%nodeidx = Nodeidx,
|
||||
items = _Items,
|
||||
affiliation = Affiliation,
|
||||
subscriptions = Subscriptions}) ->
|
||||
STATEID = list_to_binary(integer_to_list(Stateid)),
|
||||
JID = ejabberd_sql:escape(jid:encode(Jid)),
|
||||
NODEID = <<"unknown">>, %% TODO: integer_to_list(Nodeidx),
|
||||
AFFILIATION = list_to_binary(string:substr(atom_to_list(Affiliation),1,1)),
|
||||
SUBSCRIPTIONS = list_to_binary(parse_subscriptions(Subscriptions)),
|
||||
[?SQL("delete from pubsub_state where stateid=%(STATEID)s;"),
|
||||
?SQL("insert into pubsub_state(stateid,jid,nodeid,affiliation,subscriptions)\n"
|
||||
" values (%(STATEID)s, %(JID)s, %(NODEID)s, %(AFFILIATION)s, %(SUBSCRIPTIONS)s);")];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end},
|
||||
|
||||
%% REVIEW:
|
||||
%% * Parents is not migrated to PARENTs
|
||||
%% * Probably some option VALs are not correctly represented in mysql
|
||||
{pubsub_node,
|
||||
fun(_Host, #pubsub_node{nodeid = {Hostid, Nodeid},
|
||||
id = Id,
|
||||
parents = _Parents,
|
||||
type = Type,
|
||||
owners = Owners,
|
||||
options = Options}) ->
|
||||
HOST = case Hostid of
|
||||
{U,S,R} -> ejabberd_sql:escape(jid:encode({U,S,R}));
|
||||
_ -> ejabberd_sql:escape(Hostid)
|
||||
end,
|
||||
NODE = ejabberd_sql:escape(Nodeid),
|
||||
PARENT = <<"">>,
|
||||
IdB = integer_to_binary(Id),
|
||||
TYPE = ejabberd_sql:escape(<<Type/binary, "_odbc">>),
|
||||
[?SQL("delete from pubsub_node where nodeid=%(Id)d;"),
|
||||
?SQL("insert into pubsub_node(host,node,nodeid,parent,type) \n"
|
||||
" values (%(HOST)s, %(NODE)s, %(Id)d, %(PARENT)s, %(TYPE)s);"),
|
||||
?SQL("delete from pubsub_node_option where nodeid=%(Id)d;"),
|
||||
[["insert into pubsub_node_option(nodeid,name,val)\n"
|
||||
" values (", IdB, ", '", atom_to_list(Name), "', '",
|
||||
io_lib:format("~p", [Val]), "');\n"] || {Name,Val} <- Options],
|
||||
?SQL("delete from pubsub_node_owner where nodeid=%(Id)d;"),
|
||||
[["insert into pubsub_node_owner(nodeid,owner)\n"
|
||||
" values (", IdB, ", '", jid:encode(Usr), "');\n"] || Usr <- Owners],"\n"];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
parse_subscriptions([]) ->
|
||||
"";
|
||||
parse_subscriptions([{State, Item}]) ->
|
||||
STATE = case State of
|
||||
subscribed -> "s"
|
||||
end,
|
||||
string:join([STATE, Item],":").
|
||||
|
||||
|
||||
@@ -431,6 +431,7 @@ db_tests(DB) when DB == mnesia; DB == redis ->
|
||||
mam_tests:single_cases(),
|
||||
carbons_tests:single_cases(),
|
||||
csi_tests:single_cases(),
|
||||
push_tests:single_cases(),
|
||||
test_unregister]},
|
||||
muc_tests:master_slave_cases(),
|
||||
privacy_tests:master_slave_cases(),
|
||||
@@ -441,7 +442,8 @@ db_tests(DB) when DB == mnesia; DB == redis ->
|
||||
vcard_tests:master_slave_cases(),
|
||||
announce_tests:master_slave_cases(),
|
||||
carbons_tests:master_slave_cases(),
|
||||
csi_tests:master_slave_cases()];
|
||||
csi_tests:master_slave_cases(),
|
||||
push_tests:master_slave_cases()];
|
||||
db_tests(_) ->
|
||||
[{single_user, [sequence],
|
||||
[test_register,
|
||||
@@ -748,25 +750,25 @@ test_component_send(Config) ->
|
||||
disconnect(Config).
|
||||
|
||||
s2s_dialback(Config) ->
|
||||
ejabberd_s2s:stop_all_connections(),
|
||||
ejabberd_s2s:stop_s2s_connections(),
|
||||
ejabberd_config:add_option(s2s_use_starttls, false),
|
||||
ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
|
||||
s2s_ping(Config).
|
||||
|
||||
s2s_optional(Config) ->
|
||||
ejabberd_s2s:stop_all_connections(),
|
||||
ejabberd_s2s:stop_s2s_connections(),
|
||||
ejabberd_config:add_option(s2s_use_starttls, optional),
|
||||
ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
|
||||
s2s_ping(Config).
|
||||
|
||||
s2s_required(Config) ->
|
||||
ejabberd_s2s:stop_all_connections(),
|
||||
ejabberd_s2s:stop_s2s_connections(),
|
||||
ejabberd_config:add_option(s2s_use_starttls, required),
|
||||
ejabberd_config:add_option(domain_certfile, "self-signed-cert.pem"),
|
||||
s2s_ping(Config).
|
||||
|
||||
s2s_required_trusted(Config) ->
|
||||
ejabberd_s2s:stop_all_connections(),
|
||||
ejabberd_s2s:stop_s2s_connections(),
|
||||
ejabberd_config:add_option(s2s_use_starttls, required),
|
||||
ejabberd_config:add_option(domain_certfile, "cert.pem"),
|
||||
s2s_ping(Config).
|
||||
|
||||
@@ -231,7 +231,11 @@ Welcome to this XMPP server."
|
||||
mod_disco: []
|
||||
mod_ping: []
|
||||
mod_proxy65: []
|
||||
mod_push: []
|
||||
mod_push_keepalive: []
|
||||
mod_s2s_dialback: []
|
||||
mod_stream_mgmt:
|
||||
resume_timeout: 3
|
||||
mod_legacy_auth: []
|
||||
mod_register:
|
||||
welcome_message:
|
||||
@@ -290,7 +294,11 @@ Welcome to this XMPP server."
|
||||
mod_disco: []
|
||||
mod_ping: []
|
||||
mod_proxy65: []
|
||||
mod_push: []
|
||||
mod_push_keepalive: []
|
||||
mod_s2s_dialback: []
|
||||
mod_stream_mgmt:
|
||||
resume_timeout: 3
|
||||
mod_legacy_auth: []
|
||||
mod_register:
|
||||
welcome_message:
|
||||
@@ -450,6 +458,8 @@ listen:
|
||||
port: @@web_port@@
|
||||
module: ejabberd_http
|
||||
captcha: true
|
||||
request_handlers:
|
||||
"/api": mod_http_api
|
||||
-
|
||||
port: @@component_port@@
|
||||
module: ejabberd_service
|
||||
@@ -466,6 +476,7 @@ modules:
|
||||
mod_proxy65: []
|
||||
mod_legacy: []
|
||||
mod_muc: []
|
||||
mod_muc_admin: []
|
||||
mod_register:
|
||||
welcome_message:
|
||||
subject: "Welcome!"
|
||||
@@ -488,3 +499,8 @@ outgoing_s2s_port: @@s2s_port@@
|
||||
shaper:
|
||||
fast: 50000
|
||||
normal: 10000
|
||||
|
||||
api_permissions:
|
||||
"public commands":
|
||||
who: all
|
||||
what: "*"
|
||||
|
||||
@@ -3,23 +3,27 @@ import struct
|
||||
|
||||
def read():
|
||||
(pkt_size,) = struct.unpack('>H', sys.stdin.read(2))
|
||||
pkt = sys.stdin.read(pkt_size).split(':')
|
||||
cmd = pkt[0]
|
||||
args_num = len(pkt) - 1
|
||||
if cmd == 'auth' and args_num >= 3:
|
||||
if pkt[1] == "wrong":
|
||||
pkt = sys.stdin.read(pkt_size)
|
||||
cmd = pkt.split(':')[0]
|
||||
if cmd == 'auth':
|
||||
u, s, p = pkt.split(':', 3)[1:]
|
||||
if u == "wrong":
|
||||
write(False)
|
||||
else:
|
||||
write(True)
|
||||
elif cmd == 'isuser' and args_num == 2:
|
||||
elif cmd == 'isuser':
|
||||
u, s = pkt.split(':', 2)[1:]
|
||||
elif cmd == 'setpass':
|
||||
u, s, p = pkt.split(':', 3)[1:]
|
||||
write(True)
|
||||
elif cmd == 'setpass' and args_num >= 3:
|
||||
elif cmd == 'tryregister':
|
||||
u, s, p = pkt.split(':', 3)[1:]
|
||||
write(True)
|
||||
elif cmd == 'tryregister' and args_num >= 3:
|
||||
elif cmd == 'removeuser':
|
||||
u, s = pkt.split(':', 2)[1:]
|
||||
write(True)
|
||||
elif cmd == 'removeuser' and args_num == 2:
|
||||
write(True)
|
||||
elif cmd == 'removeuser3' and args_num >= 3:
|
||||
elif cmd == 'removeuser3':
|
||||
u, s, p = pkt.split(':', 3)[1:]
|
||||
write(True)
|
||||
else:
|
||||
write(False)
|
||||
|
||||
@@ -25,12 +25,15 @@ defmodule EjabberdAdminTest do
|
||||
|
||||
setup_all do
|
||||
:mnesia.start
|
||||
:ejabberd_mnesia.start
|
||||
# For some myterious reason, :ejabberd_commands.init mays
|
||||
# sometimes fails if module is not loaded before
|
||||
{:module, :ejabberd_commands} = Code.ensure_loaded(:ejabberd_commands)
|
||||
:ejabberd_hooks.start_link
|
||||
{:ok, _} = :acl.start_link
|
||||
{:ok, _} = :ejabberd_access_permissions.start_link()
|
||||
:ejabberd_commands.init
|
||||
:ejabberd_admin.start
|
||||
:ejabberd_commands.start_link
|
||||
:ejabberd_admin.start_link
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -41,40 +44,44 @@ defmodule EjabberdAdminTest do
|
||||
test "Logvel can be set and retrieved" do
|
||||
:ejabberd_logger.start()
|
||||
|
||||
assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [1])
|
||||
assert :lager == call_command(:set_loglevel, [1])
|
||||
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'} ==
|
||||
: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'} ==
|
||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
||||
call_command(:get_loglevel, [])
|
||||
|
||||
assert {:wrong_loglevel, 6} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:set_loglevel, [6])
|
||||
catch_throw call_command(:set_loglevel, [6])
|
||||
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'} ==
|
||||
: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'} ==
|
||||
: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'} ==
|
||||
:ejabberd_commands.execute_command(:get_loglevel, [])
|
||||
call_command(:get_loglevel, [])
|
||||
|
||||
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
|
||||
assert :ejabberd_not_running ==
|
||||
elem(:ejabberd_commands.execute_command(:status, []), 0)
|
||||
elem(call_command(:status, []), 0)
|
||||
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
|
||||
:ok = start_module(:stringprep)
|
||||
start_module(:jid)
|
||||
:ejabberd_hooks.start_link
|
||||
:ok = :ejabberd_config.start(["domain1"], [])
|
||||
{:ok, _} = :cyrsasl.start_link
|
||||
cyrstate = :cyrsasl.server_new("domain1", "domain1", "domain1", :ok, &get_password/1,
|
||||
|
||||
@@ -41,16 +41,20 @@ defmodule EjabberdModAdminExtraTest do
|
||||
:jid.start
|
||||
:stringprep.start
|
||||
:mnesia.start
|
||||
:ejabberd_mnesia.start
|
||||
:p1_sha.load_nif
|
||||
:ejabberd_hooks.start_link
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
{:ok, _} = :ejabberd_access_permissions.start_link()
|
||||
:ejabberd_commands.init
|
||||
:ok = :ejabberd_config.start([@domain], [])
|
||||
:acl.start_link
|
||||
:ejabberd_access_permissions.start_link()
|
||||
:ejabberd_commands.start_link
|
||||
:ok = :ejabberd_config.start([@domain], [])
|
||||
:gen_mod.start_link
|
||||
:mod_admin_extra.start(@domain, [])
|
||||
:sel_application.start_app(:moka)
|
||||
{:ok, _pid} = :ejabberd_hooks.start_link
|
||||
:ejabberd_hooks.start_link
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -66,9 +70,9 @@ defmodule EjabberdModAdminExtraTest do
|
||||
test "check_account works" do
|
||||
EjabberdAuthMock.create_user @user, @domain, @password
|
||||
|
||||
assert :ejabberd_commands.execute_command(:check_account, [@user, @domain])
|
||||
refute :ejabberd_commands.execute_command(:check_account, [@user, "bad_domain"])
|
||||
refute :ejabberd_commands.execute_command(:check_account, ["bad_user", @domain])
|
||||
assert call_command(:check_account, [@user, @domain])
|
||||
refute call_command(:check_account, [@user, "bad_domain"])
|
||||
refute call_command(:check_account, ["bad_user", @domain])
|
||||
|
||||
assert :meck.validate :ejabberd_auth
|
||||
end
|
||||
@@ -77,13 +81,13 @@ defmodule EjabberdModAdminExtraTest do
|
||||
|
||||
EjabberdAuthMock.create_user @user, @domain, @password
|
||||
|
||||
assert :ejabberd_commands.execute_command(:check_password,
|
||||
assert call_command(:check_password,
|
||||
[@user, @domain, @password])
|
||||
refute :ejabberd_commands.execute_command(:check_password,
|
||||
refute call_command(:check_password,
|
||||
[@user, @domain, "bad_password"])
|
||||
refute :ejabberd_commands.execute_command(:check_password,
|
||||
refute call_command(:check_password,
|
||||
[@user, "bad_domain", @password])
|
||||
refute :ejabberd_commands.execute_command(:check_password,
|
||||
refute call_command(:check_password,
|
||||
["bad_user", @domain, @password])
|
||||
|
||||
assert :meck.validate :ejabberd_auth
|
||||
@@ -95,21 +99,21 @@ defmodule EjabberdModAdminExtraTest do
|
||||
EjabberdAuthMock.create_user @user, @domain, @password
|
||||
hash = "5F4DCC3B5AA765D61D8327DEB882CF99" # echo -n password|md5
|
||||
|
||||
assert :ejabberd_commands.execute_command(:check_password_hash,
|
||||
assert call_command(:check_password_hash,
|
||||
[@user, @domain, hash, "md5"])
|
||||
refute :ejabberd_commands.execute_command(:check_password_hash,
|
||||
refute call_command(:check_password_hash,
|
||||
[@user, @domain, "bad_hash", "md5"])
|
||||
refute :ejabberd_commands.execute_command(:check_password_hash,
|
||||
refute call_command(:check_password_hash,
|
||||
[@user, "bad_domain", hash, "md5"])
|
||||
refute :ejabberd_commands.execute_command(:check_password_hash,
|
||||
refute call_command(:check_password_hash,
|
||||
["bad_user", @domain, hash, "md5"])
|
||||
|
||||
hash = "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8" # echo -n password|shasum
|
||||
assert :ejabberd_commands.execute_command(:check_password_hash,
|
||||
assert call_command(:check_password_hash,
|
||||
[@user, @domain, hash, "sha"])
|
||||
|
||||
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"])
|
||||
|
||||
assert :meck.validate :ejabberd_auth
|
||||
@@ -119,14 +123,14 @@ defmodule EjabberdModAdminExtraTest do
|
||||
test "set_password works" do
|
||||
EjabberdAuthMock.create_user @user, @domain, @password
|
||||
|
||||
assert :ejabberd_commands.execute_command(:change_password,
|
||||
assert call_command(:change_password,
|
||||
[@user, @domain, "new_password"])
|
||||
refute :ejabberd_commands.execute_command(:check_password,
|
||||
refute call_command(:check_password,
|
||||
[@user, @domain, @password])
|
||||
assert :ejabberd_commands.execute_command(:check_password,
|
||||
assert call_command(:check_password,
|
||||
[@user, @domain, "new_password"])
|
||||
assert {:not_found, 'unknown_user'} ==
|
||||
catch_throw :ejabberd_commands.execute_command(:change_password,
|
||||
catch_throw call_command(:change_password,
|
||||
["bad_user", @domain,
|
||||
@password])
|
||||
assert :meck.validate :ejabberd_auth
|
||||
@@ -135,23 +139,23 @@ defmodule EjabberdModAdminExtraTest do
|
||||
###################### Sessions
|
||||
|
||||
test "num_resources works" do
|
||||
assert 0 == :ejabberd_commands.execute_command(:num_resources,
|
||||
assert 0 == call_command(:num_resources,
|
||||
[@user, @domain])
|
||||
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource
|
||||
assert 1 == :ejabberd_commands.execute_command(:num_resources,
|
||||
assert 1 == call_command(:num_resources,
|
||||
[@user, @domain])
|
||||
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
|
||||
assert 2 == :ejabberd_commands.execute_command(:num_resources,
|
||||
assert 2 == call_command(:num_resources,
|
||||
[@user, @domain])
|
||||
|
||||
EjabberdSmMock.connect_resource @user<>"1", @domain, @resource
|
||||
assert 2 == :ejabberd_commands.execute_command(:num_resources,
|
||||
assert 2 == call_command(:num_resources,
|
||||
[@user, @domain])
|
||||
|
||||
EjabberdSmMock.disconnect_resource @user, @domain, @resource
|
||||
assert 1 == :ejabberd_commands.execute_command(:num_resources,
|
||||
assert 1 == call_command(:num_resources,
|
||||
[@user, @domain])
|
||||
|
||||
assert :meck.validate :ejabberd_sm
|
||||
@@ -163,14 +167,14 @@ defmodule EjabberdModAdminExtraTest do
|
||||
EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
|
||||
|
||||
assert :bad_argument ==
|
||||
elem(catch_throw(:ejabberd_commands.execute_command(:resource_num,
|
||||
elem(catch_throw(call_command(:resource_num,
|
||||
[@user, @domain, 0])), 0)
|
||||
assert @resource<>"1" ==
|
||||
:ejabberd_commands.execute_command(:resource_num, [@user, @domain, 1])
|
||||
call_command(:resource_num, [@user, @domain, 1])
|
||||
assert @resource<>"3" ==
|
||||
:ejabberd_commands.execute_command(:resource_num, [@user, @domain, 3])
|
||||
call_command(:resource_num, [@user, @domain, 3])
|
||||
assert :bad_argument ==
|
||||
elem(catch_throw(:ejabberd_commands.execute_command(:resource_num,
|
||||
elem(catch_throw(call_command(:resource_num,
|
||||
[@user, @domain, 4])), 0)
|
||||
assert :meck.validate :ejabberd_sm
|
||||
end
|
||||
@@ -184,7 +188,7 @@ defmodule EjabberdModAdminExtraTest do
|
||||
assert 1 == length EjabberdSmMock.get_session @user, @domain, @resource<>"2"
|
||||
|
||||
assert :ok ==
|
||||
:ejabberd_commands.execute_command(:kick_session,
|
||||
call_command(:kick_session,
|
||||
[@user, @domain,
|
||||
@resource<>"2", "kick"])
|
||||
|
||||
@@ -199,18 +203,18 @@ defmodule EjabberdModAdminExtraTest do
|
||||
test "get_last works" do
|
||||
|
||||
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<>"2"
|
||||
|
||||
assert {_, 'ONLINE'} =
|
||||
:ejabberd_commands.execute_command(:get_last, [@user, @domain])
|
||||
call_command(:get_last, [@user, @domain])
|
||||
|
||||
EjabberdSmMock.disconnect_resource @user, @domain, @resource<>"1"
|
||||
|
||||
assert {_, 'ONLINE'} =
|
||||
:ejabberd_commands.execute_command(:get_last, [@user, @domain])
|
||||
call_command(:get_last, [@user, @domain])
|
||||
|
||||
now = {megasecs, secs, _microsecs} = :os.timestamp
|
||||
timestamp = megasecs * 1000000 + secs
|
||||
@@ -221,7 +225,7 @@ defmodule EjabberdModAdminExtraTest do
|
||||
"~w-~.2.0w-~.2.0wT~.2.0w:~.2.0w:~.2.0wZ",
|
||||
[year, month, day, hour, minute, second]))
|
||||
assert {result, ""} ==
|
||||
:ejabberd_commands.execute_command(:get_last, [@user, @domain])
|
||||
call_command(:get_last, [@user, @domain])
|
||||
|
||||
assert :meck.validate :mod_last
|
||||
end
|
||||
@@ -238,7 +242,7 @@ defmodule EjabberdModAdminExtraTest do
|
||||
assert [] == ModRosterMock.get_roster(@user, @domain)
|
||||
|
||||
assert :ok ==
|
||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
||||
call_command(:add_rosteritem, [@user, @domain,
|
||||
@user<>"1", @domain,
|
||||
"nick1",
|
||||
"group1",
|
||||
@@ -261,7 +265,7 @@ defmodule EjabberdModAdminExtraTest do
|
||||
{:item, {@user<>"1", @domain, ""}, :both}])
|
||||
|
||||
assert :ok ==
|
||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
||||
call_command(:add_rosteritem, [@user, @domain,
|
||||
@user<>"2", @domain,
|
||||
"nick2",
|
||||
"group2",
|
||||
@@ -278,7 +282,7 @@ defmodule EjabberdModAdminExtraTest do
|
||||
{:item, {@user<>"2", @domain, ""}, :both}])
|
||||
|
||||
|
||||
:ejabberd_commands.execute_command(:delete_rosteritem, [@user, @domain,
|
||||
call_command(:delete_rosteritem, [@user, @domain,
|
||||
@user<>"1", @domain])
|
||||
result = ModRosterMock.get_roster(@user, @domain)
|
||||
assert 1 == length result
|
||||
@@ -296,7 +300,7 @@ defmodule EjabberdModAdminExtraTest do
|
||||
[jid,
|
||||
{:item, {@user<>"1", @domain, ""}, :none}])
|
||||
|
||||
:ejabberd_commands.execute_command(:delete_rosteritem, [@user, @domain,
|
||||
call_command(:delete_rosteritem, [@user, @domain,
|
||||
@user<>"2", @domain])
|
||||
|
||||
# Check that the item roster user2 was pushed with subscription
|
||||
@@ -321,39 +325,47 @@ defmodule EjabberdModAdminExtraTest do
|
||||
|
||||
test "get_roster works" do
|
||||
assert [] == ModRosterMock.get_roster(@user, @domain)
|
||||
assert [] == :ejabberd_commands.execute_command(:get_roster, [@user, @domain],
|
||||
assert [] == call_command(:get_roster, [@user, @domain],
|
||||
:admin)
|
||||
|
||||
assert :ok ==
|
||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
||||
call_command(:add_rosteritem, [@user, @domain,
|
||||
@user<>"1", @domain,
|
||||
"nick1",
|
||||
"group1",
|
||||
"both"])
|
||||
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 ==
|
||||
:ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
|
||||
call_command(:add_rosteritem, [@user, @domain,
|
||||
@user<>"2", @domain,
|
||||
"nick2",
|
||||
"group2",
|
||||
"none"])
|
||||
result = :ejabberd_commands.execute_command(:get_roster, [@user, @domain], :admin)
|
||||
result = call_command(:get_roster, [@user, @domain], :admin)
|
||||
assert 2 == length result
|
||||
assert Enum.member?(result, {@user<>"1@"<>@domain, "", 'both', 'none', "group1"})
|
||||
assert Enum.member?(result, {@user<>"2@"<>@domain, "", 'none', 'none', "group2"})
|
||||
|
||||
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?
|
||||
# test "kick_user works" do
|
||||
# assert 0 == :ejabberd_commands.execute_command(:num_resources,
|
||||
# assert 0 == call_command(:num_resources,
|
||||
# [@user, @domain])
|
||||
# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"1")
|
||||
# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"2")
|
||||
# assert 2 ==
|
||||
# :ejabberd_commands.execute_command(:kick_user, [@user, @domain])
|
||||
# assert 0 == :ejabberd_commands.execute_command(:num_resources,
|
||||
# call_command(:kick_user, [@user, @domain])
|
||||
# assert 0 == call_command(:num_resources,
|
||||
# [@user, @domain])
|
||||
# assert :meck.validate :ejabberd_sm
|
||||
# end
|
||||
|
||||
@@ -45,10 +45,11 @@ defmodule ModHttpApiMockTest do
|
||||
:jid.start
|
||||
:mnesia.start
|
||||
:ejabberd_mnesia.start
|
||||
:stringprep.start
|
||||
:stringprep.start
|
||||
:ejabberd_hooks.start_link
|
||||
:ejabberd_config.start([@domain], [])
|
||||
{:ok, _} = :ejabberd_access_permissions.start_link()
|
||||
:ejabberd_commands.init
|
||||
:ejabberd_commands.start_link
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
@@ -73,18 +74,12 @@ defmodule ModHttpApiMockTest do
|
||||
fn (@acommand, %{usr: {@user, @domain, _}}, @version) ->
|
||||
{[], {:res, :rescode}}
|
||||
end)
|
||||
:meck.expect(:ejabberd_commands, :get_command_policy_and_scope,
|
||||
fn (@acommand) -> {:ok, :user, [:erlang.atom_to_binary(@acommand,:utf8)]} end)
|
||||
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
||||
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
||||
fn () -> [@acommand] end)
|
||||
:meck.expect(:ejabberd_commands, :execute_command2,
|
||||
fn (@acommand, [], %{usr: {@user, @domain, _}}, @version) ->
|
||||
:ok
|
||||
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]}]])
|
||||
|
||||
@@ -130,9 +125,7 @@ defmodule ModHttpApiMockTest do
|
||||
fn (@acommand, %{usr: {@user, @domain, _}}, @version) ->
|
||||
{[], {:res, :rescode}}
|
||||
end)
|
||||
:meck.expect(:ejabberd_commands, :get_command_policy_and_scope,
|
||||
fn (@acommand) -> {:ok, :user, [:erlang.atom_to_binary(@acommand,:utf8), "ejabberd:user"]} end)
|
||||
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
||||
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
||||
fn () -> [@acommand] end)
|
||||
:meck.expect(:ejabberd_commands, :execute_command2,
|
||||
fn (@acommand, [], %{usr: {@user, @domain, _}, oauth_scope: ["ejabberd:user"]}, @version) ->
|
||||
@@ -142,11 +135,6 @@ defmodule ModHttpApiMockTest do
|
||||
(@acommand, [], %{usr: {@user, @domain, _}, oauth_scope: _}, @version) ->
|
||||
throw({:error, :access_rules_unauthorized})
|
||||
end)
|
||||
:meck.expect(:ejabberd_commands, :execute_command,
|
||||
fn (:undefined, {@user, @domain, {:oauth, _token}, false},
|
||||
@acommand, [], @version, _) ->
|
||||
:ok
|
||||
end)
|
||||
|
||||
|
||||
# Correct OAuth call using specific scope
|
||||
@@ -231,15 +219,8 @@ defmodule ModHttpApiMockTest do
|
||||
fn (@acommand, {@user, @domain, {:oauth, _token}, false}, @version) ->
|
||||
{[], {:res, :rescode}}
|
||||
end)
|
||||
:meck.expect(:ejabberd_commands, :get_command_policy_and_scope,
|
||||
fn (@acommand) -> {:ok, :user, [:erlang.atom_to_binary(@acommand,:utf8), "ejabberd:user"]} end)
|
||||
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
||||
:meck.expect(:ejabberd_commands, :get_exposed_commands,
|
||||
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
|
||||
:meck.expect(:acl, :match_rule, fn(_Server, _Access, _Jid) -> :allow end)
|
||||
|
||||
@@ -31,9 +31,11 @@ defmodule ModHttpApiTest do
|
||||
:ok = :mnesia.start
|
||||
:ejabberd_mnesia.start
|
||||
:stringprep.start
|
||||
:ejabberd_hooks.start_link
|
||||
:ok = :ejabberd_config.start(["localhost"], [])
|
||||
:acl.start_link
|
||||
{:ok, _} = :ejabberd_access_permissions.start_link()
|
||||
:ok = :ejabberd_commands.init
|
||||
{:ok, _} = :ejabberd_commands.start_link
|
||||
:ok = :ejabberd_commands.register_commands(cmds)
|
||||
on_exit fn ->
|
||||
:meck.unload
|
||||
@@ -42,7 +44,7 @@ defmodule ModHttpApiTest do
|
||||
|
||||
test "We can expose several commands to API at a time" do
|
||||
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()
|
||||
assert Enum.member?(commands, :open_cmd)
|
||||
assert Enum.member?(commands, :user_cmd)
|
||||
@@ -58,14 +60,14 @@ defmodule ModHttpApiTest do
|
||||
# This related to the commands config file option
|
||||
test "Attempting to access a command that is not exposed as HTTP API returns 403" do
|
||||
setup_mocks()
|
||||
:ejabberd_commands.expose_commands([])
|
||||
assert :ok == :ejabberd_commands.expose_commands([])
|
||||
request = request(method: :POST, ip: {{127,0,0,1},50000}, data: "[]")
|
||||
{403, _, _} = :mod_http_api.process(["open_cmd"], request)
|
||||
end
|
||||
|
||||
test "Call to user, admin or restricted commands without authentication are rejected" do
|
||||
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: "[]")
|
||||
{403, _, _} = :mod_http_api.process(["user_cmd"], request)
|
||||
{403, _, _} = :mod_http_api.process(["admin_cmd"], request)
|
||||
|
||||
@@ -23,6 +23,7 @@ defmodule ModRosterMock do
|
||||
|
||||
require Record
|
||||
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__
|
||||
|
||||
@@ -37,6 +38,13 @@ defmodule ModRosterMock do
|
||||
|
||||
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.start(domain, [])
|
||||
end
|
||||
@@ -92,6 +100,11 @@ defmodule ModRosterMock do
|
||||
:moka.load(roster_mock0)
|
||||
|
||||
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,
|
||||
fn (_host, _opts) ->
|
||||
{:none}
|
||||
|
||||
+28
-1
@@ -53,7 +53,8 @@ single_cases() ->
|
||||
single_test(service_vcard),
|
||||
single_test(configure_non_existent),
|
||||
single_test(cancel_configure_non_existent),
|
||||
single_test(service_subscriptions)]}.
|
||||
single_test(service_subscriptions),
|
||||
single_test(set_room_affiliation)]}.
|
||||
|
||||
service_presence_error(Config) ->
|
||||
Service = muc_jid(Config),
|
||||
@@ -242,6 +243,32 @@ service_subscriptions(Config) ->
|
||||
end, Rooms),
|
||||
disconnect(Config).
|
||||
|
||||
set_room_affiliation(Config) ->
|
||||
#jid{server = RoomService} = muc_jid(Config),
|
||||
RoomName = <<"set_room_affiliation">>,
|
||||
RoomJID = jid:make(RoomName, RoomService),
|
||||
MyJID = my_jid(Config),
|
||||
PeerJID = jid:remove_resource(?config(slave, Config)),
|
||||
|
||||
ct:pal("joining room ~p", [RoomJID]),
|
||||
ok = join_new(Config, RoomJID),
|
||||
|
||||
ct:pal("setting affiliation in room ~p to 'member' for ~p", [RoomJID, PeerJID]),
|
||||
ServerHost = ?config(server_host, Config),
|
||||
WebPort = ct:get_config(web_port, 5280),
|
||||
RequestURL = "http://" ++ ServerHost ++ ":" ++ integer_to_list(WebPort) ++ "/api/set_room_affiliation",
|
||||
Headers = [{"X-Admin", "true"}],
|
||||
ContentType = "application/json",
|
||||
Body = jiffy:encode(#{name => RoomName, service => RoomService, jid => jid:encode(PeerJID), affiliation => member}),
|
||||
{ok, {{_, 200, _}, _, _}} = httpc:request(post, {RequestURL, Headers, ContentType, Body}, [], []),
|
||||
|
||||
#message{id = _, from = RoomJID, to = MyJID, sub_els = [
|
||||
#muc_user{items = [
|
||||
#muc_item{affiliation = member, role = none, jid = PeerJID}]}]} = recv_message(Config),
|
||||
|
||||
ok = leave(Config, RoomJID),
|
||||
disconnect(Config).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Master-slave tests
|
||||
%%%===================================================================
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 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(push_tests).
|
||||
|
||||
%% API
|
||||
-compile(export_all).
|
||||
-import(suite, [close_socket/1, connect/1, disconnect/1, get_event/1,
|
||||
get_features/2, make_iq_result/1, my_jid/1, put_event/2, recv/1,
|
||||
recv_iq/1, recv_message/1, self_presence/2, send/2, send_recv/2,
|
||||
server_jid/1]).
|
||||
|
||||
-include("suite.hrl").
|
||||
|
||||
-define(PUSH_NODE, <<"d3v1c3">>).
|
||||
-define(PUSH_XDATA_FIELDS,
|
||||
[#xdata_field{var = <<"FORM_TYPE">>,
|
||||
values = [?NS_PUBSUB_PUBLISH_OPTIONS]},
|
||||
#xdata_field{var = <<"secret">>,
|
||||
values = [<<"c0nf1d3nt14l">>]}]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
%%%===================================================================
|
||||
%%% Single user tests
|
||||
%%%===================================================================
|
||||
single_cases() ->
|
||||
{push_single, [sequence],
|
||||
[single_test(feature_enabled),
|
||||
single_test(unsupported_iq)]}.
|
||||
|
||||
feature_enabled(Config) ->
|
||||
BareMyJID = jid:remove_resource(my_jid(Config)),
|
||||
Features = get_features(Config, BareMyJID),
|
||||
true = lists:member(?NS_PUSH_0, Features),
|
||||
disconnect(Config).
|
||||
|
||||
unsupported_iq(Config) ->
|
||||
PushJID = my_jid(Config),
|
||||
lists:foreach(
|
||||
fun(SubEl) ->
|
||||
#iq{type = error} =
|
||||
send_recv(Config, #iq{type = get, sub_els = [SubEl]})
|
||||
end, [#push_enable{jid = PushJID}, #push_disable{jid = PushJID}]),
|
||||
disconnect(Config).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Master-slave tests
|
||||
%%%===================================================================
|
||||
master_slave_cases() ->
|
||||
{push_master_slave, [sequence],
|
||||
[master_slave_test(sm),
|
||||
master_slave_test(offline),
|
||||
master_slave_test(mam)]}.
|
||||
|
||||
sm_master(Config) ->
|
||||
ct:comment("Waiting for the slave to close the socket"),
|
||||
peer_down = get_event(Config),
|
||||
ct:comment("Waiting a bit in order to test the keepalive feature"),
|
||||
ct:sleep(5000), % Without mod_push_keepalive, the session would time out.
|
||||
ct:comment("Sending message to the slave"),
|
||||
send_test_message(Config),
|
||||
ct:comment("Handling push notification"),
|
||||
handle_notification(Config),
|
||||
ct:comment("Receiving bounced message from the slave"),
|
||||
#message{type = error} = recv_message(Config),
|
||||
ct:comment("Closing the connection"),
|
||||
disconnect(Config).
|
||||
|
||||
sm_slave(Config) ->
|
||||
ct:comment("Enabling push notifications"),
|
||||
ok = enable_push(Config),
|
||||
ct:comment("Enabling stream management"),
|
||||
ok = enable_sm(Config),
|
||||
ct:comment("Closing the socket"),
|
||||
close_socket(Config).
|
||||
|
||||
offline_master(Config) ->
|
||||
ct:comment("Waiting for the slave to be ready"),
|
||||
ready = get_event(Config),
|
||||
ct:comment("Sending message to the slave"),
|
||||
send_test_message(Config), % No push notification, slave is online.
|
||||
ct:comment("Waiting for the slave to disconnect"),
|
||||
peer_down = get_event(Config),
|
||||
ct:comment("Sending message to offline storage"),
|
||||
send_test_message(Config),
|
||||
ct:comment("Handling push notification for offline message"),
|
||||
handle_notification(Config),
|
||||
ct:comment("Closing the connection"),
|
||||
disconnect(Config).
|
||||
|
||||
offline_slave(Config) ->
|
||||
ct:comment("Re-enabling push notifications"),
|
||||
ok = enable_push(Config),
|
||||
ct:comment("Letting the master know that we're ready"),
|
||||
put_event(Config, ready),
|
||||
ct:comment("Receiving message from the master"),
|
||||
recv_test_message(Config),
|
||||
ct:comment("Closing the connection"),
|
||||
disconnect(Config).
|
||||
|
||||
mam_master(Config) ->
|
||||
ct:comment("Waiting for the slave to be ready"),
|
||||
ready = get_event(Config),
|
||||
ct:comment("Sending message to the slave"),
|
||||
send_test_message(Config),
|
||||
ct:comment("Handling push notification for MAM message"),
|
||||
handle_notification(Config),
|
||||
ct:comment("Closing the connection"),
|
||||
disconnect(Config).
|
||||
|
||||
mam_slave(Config) ->
|
||||
self_presence(Config, available),
|
||||
ct:comment("Receiving message from offline storage"),
|
||||
recv_test_message(Config),
|
||||
ct:comment("Re-enabling push notifications"),
|
||||
ok = enable_push(Config),
|
||||
ct:comment("Enabling MAM"),
|
||||
ok = enable_mam(Config),
|
||||
ct:comment("Letting the master know that we're ready"),
|
||||
put_event(Config, ready),
|
||||
ct:comment("Receiving message from the master"),
|
||||
recv_test_message(Config),
|
||||
ct:comment("Waiting for the master to disconnect"),
|
||||
peer_down = get_event(Config),
|
||||
ct:comment("Disabling push notifications"),
|
||||
ok = disable_push(Config),
|
||||
ct:comment("Closing the connection and cleaning up"),
|
||||
clean(disconnect(Config)).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
single_test(T) ->
|
||||
list_to_atom("push_" ++ atom_to_list(T)).
|
||||
|
||||
master_slave_test(T) ->
|
||||
{list_to_atom("push_" ++ atom_to_list(T)), [parallel],
|
||||
[list_to_atom("push_" ++ atom_to_list(T) ++ "_master"),
|
||||
list_to_atom("push_" ++ atom_to_list(T) ++ "_slave")]}.
|
||||
|
||||
enable_sm(Config) ->
|
||||
send(Config, #sm_enable{xmlns = ?NS_STREAM_MGMT_3, resume = true}),
|
||||
case recv(Config) of
|
||||
#sm_enabled{resume = true} ->
|
||||
ok;
|
||||
#sm_failed{reason = Reason} ->
|
||||
Reason
|
||||
end.
|
||||
|
||||
enable_mam(Config) ->
|
||||
case send_recv(
|
||||
Config, #iq{type = set, sub_els = [#mam_prefs{xmlns = ?NS_MAM_1,
|
||||
default = always}]}) of
|
||||
#iq{type = result} ->
|
||||
ok;
|
||||
#iq{type = error} = Err ->
|
||||
xmpp:get_error(Err)
|
||||
end.
|
||||
|
||||
enable_push(Config) ->
|
||||
%% Usually, the push JID would be a server JID (such as push.example.com).
|
||||
%% We specify the peer's full user JID instead, so the push notifications
|
||||
%% will be sent to the peer.
|
||||
PushJID = ?config(peer, Config),
|
||||
XData = #xdata{type = submit, fields = ?PUSH_XDATA_FIELDS},
|
||||
case send_recv(
|
||||
Config, #iq{type = set,
|
||||
sub_els = [#push_enable{jid = PushJID,
|
||||
node = ?PUSH_NODE,
|
||||
xdata = XData}]}) of
|
||||
#iq{type = result, sub_els = []} ->
|
||||
ok;
|
||||
#iq{type = error} = Err ->
|
||||
xmpp:get_error(Err)
|
||||
end.
|
||||
|
||||
disable_push(Config) ->
|
||||
PushJID = ?config(peer, Config),
|
||||
case send_recv(
|
||||
Config, #iq{type = set,
|
||||
sub_els = [#push_disable{jid = PushJID,
|
||||
node = ?PUSH_NODE}]}) of
|
||||
#iq{type = result, sub_els = []} ->
|
||||
ok;
|
||||
#iq{type = error} = Err ->
|
||||
xmpp:get_error(Err)
|
||||
end.
|
||||
|
||||
send_test_message(Config) ->
|
||||
Peer = ?config(peer, Config),
|
||||
Msg = #message{to = Peer, body = [#text{data = <<"test">>}]},
|
||||
send(Config, Msg).
|
||||
|
||||
recv_test_message(Config) ->
|
||||
Peer = ?config(peer, Config),
|
||||
#message{from = Peer,
|
||||
body = [#text{data = <<"test">>}]} = recv_message(Config).
|
||||
|
||||
handle_notification(Config) ->
|
||||
From = server_jid(Config),
|
||||
Item = #ps_item{xml_els = [xmpp:encode(#push_notification{})]},
|
||||
Publish = #ps_publish{node = ?PUSH_NODE, items = [Item]},
|
||||
XData = #xdata{type = submit, fields = ?PUSH_XDATA_FIELDS},
|
||||
PubSub = #pubsub{publish = Publish, publish_options = XData},
|
||||
IQ = #iq{type = set, from = From, sub_els = [PubSub]} = recv_iq(Config),
|
||||
send(Config, make_iq_result(IQ)).
|
||||
|
||||
clean(Config) ->
|
||||
{U, S, _} = jid:tolower(my_jid(Config)),
|
||||
mod_push:remove_user(U, S),
|
||||
mod_mam:remove_user(U, S),
|
||||
Config.
|
||||
+130
-59
@@ -33,10 +33,13 @@ sub get_deps {
|
||||
return \%deps;
|
||||
}
|
||||
my (%info_updates, %top_deps_updates, %sub_deps_updates, @operations);
|
||||
my $epoch = 1;
|
||||
|
||||
sub top_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";
|
||||
croak "Unable to extract floating_deps" unless $config =~ /\{floating_deps, \[(.*?)\]/s;
|
||||
|
||||
@@ -49,7 +52,9 @@ sub top_deps {
|
||||
}
|
||||
|
||||
sub update_deps_repos {
|
||||
my ($force) = @_;
|
||||
my $deps = top_deps();
|
||||
$epoch++;
|
||||
mkdir(".deps-update") unless -d ".deps-update";
|
||||
for my $dep (keys %{$deps}) {
|
||||
my $dd = ".deps-update/$dep";
|
||||
@@ -58,7 +63,7 @@ sub update_deps_repos {
|
||||
my $repo = $deps->{$dep}->{repo};
|
||||
$repo =~ s!^https?://github.com/!git\@github.com:!;
|
||||
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...";
|
||||
system("git", "-C", $dd, "pull");
|
||||
touch($dd)
|
||||
@@ -68,7 +73,9 @@ sub update_deps_repos {
|
||||
|
||||
sub 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();
|
||||
for my $dep (keys %{$deps}) {
|
||||
my $rc = ".deps-update/$dep/rebar.config";
|
||||
@@ -94,7 +101,9 @@ sub rev_deps_helper {
|
||||
|
||||
sub 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();
|
||||
for my $dep (keys %$sub_deps) {
|
||||
$rev_deps{$_}->{direct}->{$dep} = 1 for keys %{$sub_deps->{$dep}};
|
||||
@@ -113,13 +122,46 @@ sub update_changelog {
|
||||
my $reason = join "\n", map {"* $_"} @reasons;
|
||||
my $content = slurp($cl);
|
||||
if (not $content =~ /^# Version $version/) {
|
||||
$content = "# Version $version\n\n$reason\n\n$content"
|
||||
$content = "# Version $version\n\n$reason\n\n$content";
|
||||
} else {
|
||||
$content =~ s/(# Version $version\n\n)/$1$reason\n/;
|
||||
}
|
||||
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 {
|
||||
my ($dep, $version) = @_;
|
||||
my $app = ".deps-update/$dep/src/$dep.app.src";
|
||||
@@ -177,7 +219,9 @@ sub cmp_ver {
|
||||
|
||||
sub deps_git_info {
|
||||
state %info;
|
||||
if (not %info) {
|
||||
state $my_epoch = $epoch;
|
||||
if (not %info or $my_epoch != $epoch) {
|
||||
$my_epoch = $epoch;
|
||||
my $deps = top_deps();
|
||||
for my $dep (keys %{$deps}) {
|
||||
my $dir = ".deps-update/$dep";
|
||||
@@ -281,6 +325,7 @@ sub git_push {
|
||||
|
||||
update_deps_repos();
|
||||
|
||||
MAIN:
|
||||
while (1) {
|
||||
my $top_deps = top_deps();
|
||||
my $git_info = deps_git_info();
|
||||
@@ -308,6 +353,7 @@ while (1) {
|
||||
my $cmd = show_commands($old_deps ? (U => "Update dependency") : (),
|
||||
$changed_deps ? (T => "Tag new release") : (),
|
||||
@operations ? (A => "Apply changes") : (),
|
||||
R => "Refresh repositiories",
|
||||
E => "Exit");
|
||||
last if $cmd eq "E";
|
||||
|
||||
@@ -333,6 +379,9 @@ while (1) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($cmd eq "R") {
|
||||
update_deps_repos(1);
|
||||
}
|
||||
if ($cmd eq "T") {
|
||||
while (1) {
|
||||
my @deps_to_tag;
|
||||
@@ -356,63 +405,85 @@ while (1) {
|
||||
}
|
||||
}
|
||||
|
||||
my $changelog_updated = 0;
|
||||
|
||||
if ($cmd eq "A") {
|
||||
$top_deps = top_deps();
|
||||
$git_info = deps_git_info();
|
||||
my $sub_deps = sub_deps();
|
||||
APPLY: {
|
||||
$top_deps = top_deps();
|
||||
$git_info = deps_git_info();
|
||||
my $sub_deps = sub_deps();
|
||||
|
||||
for my $dep (keys %$top_deps) {
|
||||
for my $sdep (keys %{$sub_deps->{$dep}}) {
|
||||
next if not defined $top_deps->{$sdep} or
|
||||
$sub_deps->{$dep}->{$sdep}->{commit} eq $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}.", [$sdep, $top_deps->{$sdep}->{commit}]);
|
||||
}
|
||||
}
|
||||
|
||||
%info_updates = ();
|
||||
%top_deps_updates = ();
|
||||
%sub_deps_updates = ();
|
||||
|
||||
$top_deps = top_deps();
|
||||
$git_info = deps_git_info();
|
||||
$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 $cmd = show_commands(A => "Apply", E => "Exit");
|
||||
if ($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});
|
||||
for my $dep (keys %$top_deps) {
|
||||
for my $sdep (keys %{$sub_deps->{$dep}}) {
|
||||
next if not defined $top_deps->{$sdep} or
|
||||
$sub_deps->{$dep}->{$sdep}->{commit} eq $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}.",
|
||||
[ $sdep, $top_deps->{$sdep}->{commit} ]);
|
||||
}
|
||||
}
|
||||
last;
|
||||
|
||||
%info_updates = ();
|
||||
%top_deps_updates = ();
|
||||
%sub_deps_updates = ();
|
||||
|
||||
$top_deps = top_deps();
|
||||
$git_info = deps_git_info();
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user