Compare commits

...

92 Commits

Author SHA1 Message Date
Christophe Romain 1820b4f63b Temporary remove recent last_item refactor 2017-08-14 09:43:02 +02:00
Evgeniy Khramtsov 63aabed320 Apply URL decoding wherever possible
Fixes #1936
2017-08-13 19:18:19 +03:00
Christophe Romain fd7bf7fed3 Fix typo from 9c5427e0c 2017-08-11 17:54:53 +02:00
Christophe Romain 7a90cda8ff Process on_user_offline only from valid sessions 2017-08-11 12:05:14 +02:00
Evgeniy Khramtsov 35eeaa5869 Fix regression introduced by b82b93f8f0
Fixes #1928
2017-08-11 11:43:16 +03:00
Christophe Romain 024713a441 Remove temporary debug 2017-08-11 10:53:19 +02:00
Christophe Romain 32fbfe1981 Use correct c2s process sending PEP with multi devices 2017-08-11 10:32:36 +02:00
Christophe Romain 9c5427e0c2 PubSub: refactor send_last_items remove send_loop 2017-08-11 10:20:33 +02:00
Christophe Romain 8f5a1c4a2a Merge pull request #1926 from weiss/import-metronome-pep
prosody2ejabberd: Support PEP import
2017-08-11 10:19:17 +02:00
Holger Weiss 7d3609d954 prosody2ejabberd: Support PEP import 2017-08-10 19:49:20 +02:00
Holger Weiss fc7ba53c37 prosody2ejabberd: Remove superfluous 'catch' 2017-08-10 18:54:00 +02:00
Evgeniy Khramtsov a96d72330d Rename remove_room hook back 2017-08-10 14:49:05 +03:00
Christophe Romain 7d626b4f5c Add support of section 4.9.3.16 on rfc6120 2017-08-10 12:17:31 +02:00
Christophe Romain e903348dd3 Fix pubsub send_loop after 3fec7824 2017-08-09 11:34:36 +02:00
badlop bee251d928 Merge pull request #1912 from mathiasertl/master
fix FIREWALL_WINDOW option
2017-08-09 09:48:18 +02:00
Mathias Ertl c658907331 fix typo 2017-08-08 19:09:55 +02:00
Evgeniy Khramtsov 3fec782494 Introduce 'hosts' option
The option can be used as a replacement of 'host' option
when several (sub)domains are needed to be registered for the module.
Note that you cannot combine both 'host' and 'hosts' in the config
because 'host' option is of a higher priority. Example:

mod_pubsub:
   ...
   hosts:
     - "pubsub1.@HOST@"
     - "pubsub2.@HOST@"

Fixes #1883
2017-08-08 17:46:26 +03:00
Christophe Romain 52525eb76d Fix tests for 8679cfd2f 2017-08-07 15:38:17 +02:00
Christophe Romain 8679cfd2f3 Rename stop_all_connections to stop_s2s_connections for consistency 2017-08-07 15:06:07 +02:00
Paweł Chmielowski 25af3fb029 Compile mod_push early as it defines behaviour 2017-08-07 09:32:58 +02:00
Evgeniy Khramtsov 2bceebc39d Get rid of export_all 2017-08-05 21:01:29 +03:00
Evgeniy Khramtsov 92532a0d66 Replace gen_fsm with p1_fsm to avoid warnings in OTP20+ 2017-08-05 20:58:21 +03:00
Holger Weiss b673539a2a Merge remote-tracking branch 'processone/pr/1914'
* processone/pr/1914:
  ejabberd_c2s: Fix priority of 'certfile' option
2017-08-05 19:27:43 +02:00
Holger Weiss e1aaa1c99d ejabberd_c2s: Fix priority of 'certfile' option
Use the 'certfile' listener option rather than a 'domain_certfile' for
ejabberd_c2s listeners that have "tls: true" configured.  A
'domain_certfile' should only be preferred for STARTTLS connections.

Closes #1911.
2017-08-05 18:59:32 +02:00
Mathias Ertl aa9eb001d0 fix FIREWALL_WINDOW option 2017-08-04 18:49:33 +02:00
Badlop 101e808124 Fix warning in previous commit 2017-08-04 13:09:17 +02:00
Christophe Romain 766b7c65a6 Merge pull request #1881 from weiss/push
Support XEP-0357: Push Notifications
2017-08-04 12:58:06 +02:00
Badlop 0516a3dc0e Remove unused 'managers' option, related to the deferred XEP-0321 (#1443) 2017-08-04 12:29:15 +02:00
Christophe Romain 3d185c0fb8 Fix missing validation from 633b68db1 (#1900) 2017-08-04 11:53:32 +02:00
Christophe Romain 7815164216 Add minor details (#1353) 2017-08-04 10:34:13 +02:00
Christophe Romain 65f4094804 Prepare packaging for 17.08 2017-08-03 16:55:56 +02:00
Christophe Romain 06450f4a82 Keep disco#info on PEP compatible with XEP-0060 (#1717) 2017-08-03 15:48:23 +02:00
Christophe Romain ce0beb550c Move pubsub sql export to pubsub_db_sql module 2017-08-03 15:34:01 +02:00
Christophe Romain 96c0483533 Fix Xref from 5dcc97c 2017-08-03 14:54:06 +02:00
Paweł Chmielowski 1274bcdba9 Change policy of user_resources command
This fixes issue #1908
2017-08-03 13:49:06 +02:00
Badlop 5dcc97c85a Preliminary export PubSub data from Mnesia tables to SQL file (#1571) 2017-08-03 10:53:01 +02:00
Christophe Romain 6deceeec2e Merge branch 'master' of github.com:processone/ejabberd 2017-08-02 14:36:43 +02:00
Christophe Romain 9edcbadd60 Fix clustering table reg_users_counter (#1889) 2017-08-02 14:36:32 +02:00
Christophe Romain f65492e27f Merge pull request #1837 from marcphilipp/feature/set_room_affiliation_test
Add integration test for set_room_affiliation
2017-08-02 14:34:30 +02:00
Christophe Romain 1df61a82c8 Merge branch 'master' of github.com:processone/ejabberd 2017-08-02 12:25:50 +02:00
Christophe Romain 73509686f1 Fix getting cached last item (#1814) 2017-08-02 12:24:34 +02:00
Evgeny Khramtsov 93e521d65e Merge pull request #1903 from himawri/fix_pubsub_spec
Fix spec for mod_pubsub:subscribe_node
2017-08-02 13:52:21 +04:00
Jing Sun 50511fcff7 Fix spec for mod_pubsub:subscribe_node 2017-08-02 10:31:42 +02:00
Christophe Romain 5e26190b98 Fix PubSub send last published items (#1572) 2017-08-01 17:00:52 +02:00
Christophe Romain f22bd6eb46 Fix PEP node removal (#1839) 2017-08-01 15:40:34 +02:00
Christophe Romain 0ba6c78ed0 Fix disco#items on PEP service 2017-08-01 15:15:01 +02:00
Christophe Romain 636d68e0a9 Fix PEP node identity (#1717) 2017-08-01 14:41:16 +02:00
Christophe Romain 7e6d1c24c2 Update spec from custom and allow modules dependencies (#1740) 2017-08-01 13:33:14 +02:00
Badlop 67918b17d3 Fix extauth.py so support : in passwords (thanks to jmberg)(#1676) 2017-07-31 22:51:31 +02:00
Paweł Chmielowski 51fa438340 Request basic auth dialog from browser 2017-07-28 16:08:05 +02:00
Christophe Romain 35a11526f9 Revert "Fix get_module_opt call in mod_block_strangers"
This reverts commit e5f64bc24a.
2017-07-27 18:23:10 +02:00
Paweł Chmielowski 0cfec92c14 Generate log messages when websocket is closed due timeouts 2017-07-27 17:53:16 +02:00
Alexey Shchepin e5f64bc24a Fix get_module_opt call in mod_block_strangers 2017-07-27 17:48:41 +02:00
Paweł Chmielowski 5c48ba4609 Set high water mark in lager for all backends 2017-07-27 17:14:03 +02:00
Jérôme Sautret 3ca62a797a Fix nick bug with MUC on riak 2017-07-27 17:06:34 +02:00
Christophe Romain b66dab1313 Add muc related hooks 2017-07-27 17:02:06 +02:00
Christophe Romain 58110e4bc1 Update OTP version check by configure 2017-07-27 15:31:59 +02:00
Marco Adkins ea96615460 Ability to filter passwords from the log in mod_http_api (#1888)
* Ability to filter passwords from the log when creating users through the mod_http_api
2017-07-27 15:30:56 +02:00
Paweł Chmielowski b8c26671c4 Update oauth2 dependency 2017-07-25 17:59:32 +02:00
Christophe Romain 2e88d001d6 Fix errors from 1a0db3d 2017-07-21 11:42:03 +02:00
Badlop 1a0db3de3a Describe commands arguments and results in ejabberd_sm, ext_mod, mod_mam 2017-07-21 11:26:53 +02:00
Badlop 250876ea1a Fix indentation of commands lines 2017-07-21 11:26:47 +02:00
Holger Weiss 66510c1d78 Add mod_push_keepalive
This module tries to keep pending stream management sessions of push
clients alive (as long as the disconnected clients are reachable via
push notifications).
2017-07-21 01:07:36 +02:00
Holger Weiss d6f1d3df5b Support XEP-0357: Push Notifications
Closes #1379.
2017-07-20 20:22:50 +02:00
Christophe Romain 72dbb6e7c1 Fix errors when running ejabberdctl as root 2017-07-20 14:40:50 +02:00
Paweł Chmielowski b1082a96c9 Add ability to update changelog in update-deps-releases 2017-07-20 11:43:25 +02:00
Paweł Chmielowski ed17586cf0 Update cache_tab and xmpp dep 2017-07-20 11:43:06 +02:00
Christophe Romain 26b8dd75f7 Merge pull request #1869 from wahjava/master
Fix ERLANG_OPTS when setting INET_DIST_INTERFACE
2017-07-19 11:33:38 +02:00
Paweł Chmielowski c3473c2077 Update fast_tls 2017-07-19 10:58:10 +02:00
Paweł Chmielowski e216654c52 Don't add indentation after single item result in docs 2017-07-18 17:48:53 +02:00
Christophe Romain cc3391cc1c Use string:join instead of lists:join 2017-07-18 15:43:46 +02:00
Christophe Romain b8d56a7c11 Improve formatting of documented API parameters 2017-07-18 15:01:01 +02:00
Evgeny Khramtsov 1bb9e83501 Merge pull request #1871 from rstgroup/master
Fix old route record in mnesia's route table haven't been remove when restarting in some cases (#1184)
2017-07-17 17:56:39 +04:00
Paweł Dorofiejczyk de1a66dfbe Fix old route record in mnesia's route table haven't been remove when restarting in some cases (#1184) 2017-07-17 15:14:30 +02:00
Ashish SHUKLA 95613a11ab Fix ERLANG_OPTS when setting INET_DIST_INTERFACE 2017-07-15 16:42:47 +05:30
Paweł Chmielowski 2cd193f97c Expand catch block used to report errors in doc generator 2017-07-14 17:18:07 +02:00
Paweł Chmielowski 33a9d6a3c3 Fix args_examples from last commit 2017-07-14 17:17:26 +02:00
Badlop fdb863ce70 Describe even more command arguments and results in mod_admin_extra 2017-07-14 16:43:30 +02:00
Paweł Chmielowski 43fc29873e Add refresh repos option in update-deps-releases 2017-07-14 15:08:13 +02:00
Paweł Chmielowski e216a54ead Update fast_tls 2017-07-14 15:08:13 +02:00
Badlop d4cdc3a246 Fix ERLANG_OPTS end lines when setting FIREWALL_WINDOW (#1856) 2017-07-12 14:43:28 +02:00
Holger Weiss f6bdc6fdb2 mod_privacy: Don't crash while copying c2s state
Don't assume 'privacy_active_list' is set when c2s_copy_session/2 is
called.
2017-07-07 14:28:22 +02:00
Holger Weiss 8f25baada6 mod_privacy: Apply cosmetic change to type spec 2017-07-07 14:25:55 +02:00
Badlop aaef1a14b4 Fix set_presence command to work in recent ejabberd 2017-07-07 10:55:08 +02:00
Evgeniy Khramtsov 22e8f5fd51 Add copyright and fix description for some sources 2017-07-06 21:27:04 +03:00
Paweł Chmielowski 88ab787ba6 Start gen_mod from elixir tests 2017-07-06 17:57:46 +02:00
Paweł Chmielowski eb9faffadd Improve elixir tests 2017-07-06 17:19:22 +02:00
Paweł Chmielowski 3b0eee785f Handle new possible result from ejabberd_config.add_option 2017-07-06 17:19:22 +02:00
Paweł Chmielowski 5ef542a638 Remove tests for old commands interface 2017-07-06 17:19:22 +02:00
Evgeniy Khramtsov ffdaff3740 Make ejabberd_cluster modular
For setting the cluster backend new global option 'cluster_backend' is
introduced. The default and only available value at the moment is 'mnesia'
2017-07-06 15:47:35 +03:00
Paweł Chmielowski 56d273477e Remove old command calling interface 2017-07-06 14:24:25 +02:00
Marc Philipp c69720a1ab Add integration test for set_room_affiliation 2017-07-06 13:33:13 +02:00
81 changed files with 3020 additions and 1837 deletions
+1 -1
View File
@@ -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 \
+4 -1
View File
@@ -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
View File
@@ -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])
+2
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -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"],
+12 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+2 -2
View File
@@ -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;
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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].
+144
View File
@@ -0,0 +1,144 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_cluster_mnesia.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Ejabberd clustering management via Mnesia
%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_cluster_mnesia).
-behaviour(ejabberd_cluster).
%% API
-export([init/0, get_nodes/0, join/1, leave/1,
get_known_nodes/0, node_id/0, get_node_by_id/1,
send/2, wait_for_sync/1, subscribe/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-spec init() -> ok.
init() ->
ok.
-spec get_nodes() -> [node()].
get_nodes() ->
mnesia:system_info(running_db_nodes).
-spec get_known_nodes() -> [node()].
get_known_nodes() ->
lists:usort(mnesia:system_info(db_nodes)
++ mnesia:system_info(extra_db_nodes)).
-spec join(node()) -> ok | {error, any()}.
join(Node) ->
case {node(), net_adm:ping(Node)} of
{Node, _} ->
{error, {not_master, Node}};
{_, pong} ->
application:stop(ejabberd),
application:stop(mnesia),
mnesia:delete_schema([node()]),
application:start(mnesia),
mnesia:change_config(extra_db_nodes, [Node]),
mnesia:change_table_copy_type(schema, node(), disc_copies),
spawn(fun() ->
lists:foreach(fun(Table) ->
Type = ejabberd_cluster:call(
Node, mnesia, table_info, [Table, storage_type]),
mnesia:add_table_copy(Table, node(), Type)
end, mnesia:system_info(tables)--[schema])
end),
application:start(ejabberd);
_ ->
{error, {no_ping, Node}}
end.
-spec leave(node()) -> ok | {error, any()}.
leave(Node) ->
case {node(), net_adm:ping(Node)} of
{Node, _} ->
Cluster = get_nodes()--[Node],
leave(Cluster, Node);
{_, pong} ->
rpc:call(Node, ?MODULE, leave, [Node], 10000);
{_, pang} ->
case mnesia:del_table_copy(schema, Node) of
{atomic, ok} -> ok;
{aborted, Reason} -> {error, Reason}
end
end.
leave([], Node) ->
{error, {no_cluster, Node}};
leave([Master|_], Node) ->
application:stop(ejabberd),
application:stop(mnesia),
ejabberd_cluster:call(Master, mnesia, del_table_copy, [schema, Node]),
spawn(fun() ->
mnesia:delete_schema([node()]),
erlang:halt(0)
end),
ok.
-spec node_id() -> binary().
node_id() ->
integer_to_binary(erlang:phash2(node())).
-spec get_node_by_id(binary()) -> node().
get_node_by_id(Hash) ->
try binary_to_integer(Hash) of
I -> match_node_id(I)
catch _:_ ->
node()
end.
-spec send({atom(), node()}, term()) -> boolean().
send(Dst, Msg) ->
erlang:send(Dst, Msg).
-spec wait_for_sync(timeout()) -> ok.
wait_for_sync(Timeout) ->
?INFO_MSG("Waiting for Mnesia synchronization to complete", []),
mnesia:wait_for_tables(mnesia:system_info(local_tables), Timeout),
ok.
-spec subscribe(_) -> ok.
subscribe(_) ->
ok.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec match_node_id(integer()) -> node().
match_node_id(I) ->
match_node_id(I, get_nodes()).
-spec match_node_id(integer(), [node()]) -> node().
match_node_id(I, [Node|Nodes]) ->
case erlang:phash2(Node) of
I -> Node;
_ -> match_node_id(I, Nodes)
end;
match_node_id(_I, []) ->
node().
+2 -273
View File
@@ -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) ->
+49 -38
View File
@@ -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,
+9 -7
View File
@@ -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) ->
+2
View File
@@ -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]),
+3
View File
@@ -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
-2
View File
@@ -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
View File
@@ -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;
}
+2 -2
View File
@@ -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;
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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),
+13 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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) ->
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+5 -2
View File
@@ -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
+1 -1
View File
@@ -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,
+8 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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).
+5 -5
View File
@@ -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
View File
@@ -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].
+596
View File
@@ -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.
+236
View File
@@ -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.
+204
View File
@@ -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
View File
@@ -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].
+3 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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].
+3 -4
View File
@@ -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
View File
@@ -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) ->
+1 -1
View File
@@ -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
View File
@@ -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}]
+94
View File
@@ -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],":").
+7 -5
View File
@@ -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).
+16
View File
@@ -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: "*"
+15 -11
View File
@@ -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)
+24 -17
View File
@@ -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
-481
View File
@@ -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
-108
View File
@@ -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
+1
View File
@@ -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,
+59 -47
View File
@@ -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
+6 -25
View File
@@ -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)
+6 -4
View File
@@ -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)
+13
View File
@@ -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
View File
@@ -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
%%%===================================================================
+234
View File
@@ -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
View File
@@ -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;
}
}
}
}