Compare commits
394 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d576902bd4 | |||
| a3da27e917 | |||
| 3f015c829c | |||
| 2732c8f6fc | |||
| 3192687334 | |||
| 126653e01b | |||
| ce7acafe37 | |||
| 216a0c97b9 | |||
| 3c8308bb8d | |||
| 1c6aa5e84e | |||
| 609a1d07cf | |||
| 8c026582ab | |||
| 81df1ae3af | |||
| 22435ca562 | |||
| f464189819 | |||
| 89e504c55f | |||
| c1d3d1318e | |||
| d120e0ad91 | |||
| c17ec50e3a | |||
| 368ba3fc55 | |||
| cd098c5adc | |||
| 221e58fff0 | |||
| 9b043ae276 | |||
| abc09054e5 | |||
| 07a193d4dc | |||
| cf09ed2df2 | |||
| 0cc1ae0a6a | |||
| e1efd29156 | |||
| 05feab35c4 | |||
| 2198fbce97 | |||
| b8ab80d1f3 | |||
| 30e5c9bd3e | |||
| 251756de00 | |||
| 3e987d3bae | |||
| 138cc25355 | |||
| a15f186253 | |||
| 59ec3d36f4 | |||
| d7250111ce | |||
| adfb924808 | |||
| 48f2adde98 | |||
| c378ea403e | |||
| 692ccd2e20 | |||
| 5882c9b456 | |||
| db41643bea | |||
| cb076924cc | |||
| cce4056040 | |||
| 7ad525b542 | |||
| 5bf64381cb | |||
| f435d0a103 | |||
| e4d21c1941 | |||
| 5414cbe821 | |||
| e6b1521b29 | |||
| 2a7d9d93c8 | |||
| dfd2045523 | |||
| 9e35af54e0 | |||
| d87151aee6 | |||
| 4ecd8a0780 | |||
| 803c31f760 | |||
| 978c92f5e1 | |||
| 32397aa0c3 | |||
| 1a58a201f8 | |||
| 2acbf4625b | |||
| 7566d254e4 | |||
| 68dee8cbb3 | |||
| f7e8d287d5 | |||
| 30bca124f4 | |||
| d7891a5562 | |||
| 1d396b4716 | |||
| 7f3fceb432 | |||
| 81581f7794 | |||
| 47175adc74 | |||
| 350827f8f4 | |||
| 05c2995c7a | |||
| 2fa6e2fd90 | |||
| ba9a79c89c | |||
| 13ad754ecc | |||
| 0b02d42836 | |||
| ee0a8d2966 | |||
| 793ca45dda | |||
| 6e20e9bcf9 | |||
| b8d2a72333 | |||
| 0760c7273c | |||
| 9bd099013f | |||
| 68fb12153e | |||
| e19d1e9571 | |||
| 1820b4f63b | |||
| 64150cc7c5 | |||
| 63aabed320 | |||
| fd7bf7fed3 | |||
| 7a90cda8ff | |||
| 35eeaa5869 | |||
| 024713a441 | |||
| 32fbfe1981 | |||
| 9c5427e0c2 | |||
| 8f5a1c4a2a | |||
| 7d3609d954 | |||
| fc7ba53c37 | |||
| a96d72330d | |||
| 7d626b4f5c | |||
| e903348dd3 | |||
| bee251d928 | |||
| c658907331 | |||
| 3fec782494 | |||
| 52525eb76d | |||
| 8679cfd2f3 | |||
| 25af3fb029 | |||
| 2bceebc39d | |||
| 92532a0d66 | |||
| b673539a2a | |||
| e1aaa1c99d | |||
| aa9eb001d0 | |||
| 101e808124 | |||
| 766b7c65a6 | |||
| 0516a3dc0e | |||
| 3d185c0fb8 | |||
| 7815164216 | |||
| 65f4094804 | |||
| 06450f4a82 | |||
| ce0beb550c | |||
| 96c0483533 | |||
| 1274bcdba9 | |||
| 5dcc97c85a | |||
| 6deceeec2e | |||
| 9edcbadd60 | |||
| f65492e27f | |||
| 1df61a82c8 | |||
| 73509686f1 | |||
| 93e521d65e | |||
| 50511fcff7 | |||
| 5e26190b98 | |||
| f22bd6eb46 | |||
| 0ba6c78ed0 | |||
| 636d68e0a9 | |||
| 7e6d1c24c2 | |||
| 67918b17d3 | |||
| 51fa438340 | |||
| 35a11526f9 | |||
| 0cfec92c14 | |||
| e5f64bc24a | |||
| 5c48ba4609 | |||
| 3ca62a797a | |||
| b66dab1313 | |||
| 58110e4bc1 | |||
| ea96615460 | |||
| b8c26671c4 | |||
| 2e88d001d6 | |||
| 1a0db3de3a | |||
| 250876ea1a | |||
| 66510c1d78 | |||
| d6f1d3df5b | |||
| 72dbb6e7c1 | |||
| b1082a96c9 | |||
| ed17586cf0 | |||
| 26b8dd75f7 | |||
| c3473c2077 | |||
| e216654c52 | |||
| cc3391cc1c | |||
| b8d56a7c11 | |||
| 1bb9e83501 | |||
| de1a66dfbe | |||
| 95613a11ab | |||
| 2cd193f97c | |||
| 33a9d6a3c3 | |||
| fdb863ce70 | |||
| 43fc29873e | |||
| e216a54ead | |||
| d4cdc3a246 | |||
| f6bdc6fdb2 | |||
| 8f25baada6 | |||
| aaef1a14b4 | |||
| 22e8f5fd51 | |||
| 88ab787ba6 | |||
| eb9faffadd | |||
| 3b0eee785f | |||
| 5ef542a638 | |||
| ffdaff3740 | |||
| 56d273477e | |||
| a35b9dd9cc | |||
| c69720a1ab | |||
| a58de70f06 | |||
| 800965a957 | |||
| fcf672c50e | |||
| b66e369a1d | |||
| 9dd03c873c | |||
| e42bb47ce3 | |||
| dc7fa076d7 | |||
| 960cf495c6 | |||
| 0f12804a49 | |||
| 3c7c71cfa6 | |||
| aac190255b | |||
| 85a08a087b | |||
| 1bfb0ab39c | |||
| 4ef1cdec12 | |||
| 0534678028 | |||
| 70606d7f1a | |||
| bb39ecbc08 | |||
| 54e6e1a5fb | |||
| 5bb7a0b0db | |||
| 976a8c9cc9 | |||
| a095477b4c | |||
| 950aca380c | |||
| 985b0a1933 | |||
| a7841ed486 | |||
| a11e833a98 | |||
| 62ee051c6e | |||
| 5424ead01d | |||
| 96d385bf82 | |||
| 8e2258b16a | |||
| f87b46f454 | |||
| 5418b37314 | |||
| 6353a06a5d | |||
| 58b9077b51 | |||
| 10fcfa860a | |||
| 5f2dcc51ce | |||
| 0aa64381ff | |||
| 6c8b037422 | |||
| d63ea000c7 | |||
| 5e148df0a9 | |||
| d8f05acb67 | |||
| d6f4c99243 | |||
| 63b6e0d381 | |||
| 8c1568ff93 | |||
| 7b5895c90d | |||
| 115cb23bd8 | |||
| 62806607bf | |||
| b25b5c2f98 | |||
| 444c385f23 | |||
| ee8bbccb2a | |||
| 66237abd35 | |||
| 0973a8d6c1 | |||
| fbead19c88 | |||
| baf574d6c4 | |||
| e3c801f1f5 | |||
| f773edcb98 | |||
| 2a73068aac | |||
| 5971eb3da0 | |||
| 04fd5567a7 | |||
| 4f8c132b53 | |||
| a98685e0bb | |||
| 12733bd21b | |||
| f6767ed061 | |||
| de10a7a8ce | |||
| 5081a180fa | |||
| a576f3a6d1 | |||
| 3201f8e513 | |||
| 92003fa4dc | |||
| 818d9c8c42 | |||
| cbe6553baa | |||
| 5d3870faa3 | |||
| 2d8ce266bd | |||
| 0042f18c1f | |||
| 0982a9bc3c | |||
| 50327a0cfc | |||
| 5802062746 | |||
| 69de1780a0 | |||
| 6e8895f9e9 | |||
| e93762a720 | |||
| 268065e5c4 | |||
| d7878ef131 | |||
| 908bedeaa6 | |||
| 504860f065 | |||
| 3a96d72a7f | |||
| d88e4d495f | |||
| 66a4e405e0 | |||
| 0a77b9f43e | |||
| 470669fa6b | |||
| af29fb21df | |||
| be50d57ddd | |||
| 35d19b32f4 | |||
| 654d907dcf | |||
| b013c29c7e | |||
| 0ed23980a6 | |||
| a78862e05e | |||
| bcb44ccb6f | |||
| 736a182544 | |||
| 97bb1250ba | |||
| b0b7ac101c | |||
| 3e35d44b0f | |||
| 6691c59a7a | |||
| 1391d5a304 | |||
| 8f595b58a7 | |||
| 5bdc6c0822 | |||
| 1925b94131 | |||
| fc794b680a | |||
| a71065fcda | |||
| 7165196211 | |||
| f782955c06 | |||
| 3f13396d73 | |||
| 061d5f2380 | |||
| 2d17a2850c | |||
| cc58ce6301 | |||
| d3c8fb7705 | |||
| 9fe16a29e1 | |||
| 31a3cc7b10 | |||
| a8dc5f80d1 | |||
| 81d9770d4f | |||
| cdb191bb48 | |||
| 633b68db11 | |||
| e890525788 | |||
| 6b8c61b3a2 | |||
| 4849ac9781 | |||
| cd18d3d8a7 | |||
| a0908ba393 | |||
| 5d7a704ca5 | |||
| cee90a886e | |||
| 6b6d07745d | |||
| bf2a2f291f | |||
| 8368a0850a | |||
| 01a2c9fe12 | |||
| 6aefd24eb3 | |||
| 3241c2506b | |||
| 48d6ae1def | |||
| f2dc8c0442 | |||
| b174e2c9c6 | |||
| fb17c1b99f | |||
| e790e66f47 | |||
| c64e77a08c | |||
| cf53d834e9 | |||
| a2a4a4970e | |||
| 31fa36003f | |||
| cd1c41e448 | |||
| 54cc49bc70 | |||
| fe662c1a0a | |||
| fddd6110e0 | |||
| 2b63d07329 | |||
| b82b93f8f0 | |||
| 7129aebe76 | |||
| 2bcf822637 | |||
| 6658c0d386 | |||
| 0b93cb7ece | |||
| b51e2aa213 | |||
| d18d99e8ec | |||
| 9b8364b6c8 | |||
| 959419419f | |||
| 2988d84cfb | |||
| c0eb85ce53 | |||
| 56a4bf8f3c | |||
| 9cc332d6b3 | |||
| 120682ec8b | |||
| a67b3dc6a6 | |||
| f4c98f635b | |||
| 069bf6dec6 | |||
| a59bef1afe | |||
| d0f3696596 | |||
| c923bb5c10 | |||
| 9a93acc62a | |||
| 8770fc98e1 | |||
| 18433e289f | |||
| 168712ebbd | |||
| 44ae6bcc83 | |||
| 02790b105e | |||
| d88c08e074 | |||
| 9937cb48fd | |||
| 5444475b1d | |||
| 264a40f217 | |||
| a26f90a346 | |||
| ca9d04ba6b | |||
| 4e86a71ab2 | |||
| 3682888655 | |||
| 3adf720bc1 | |||
| d7a999eaf5 | |||
| 64333f69ea | |||
| b8a7720986 | |||
| 7c9415356d | |||
| c97aade33e | |||
| ab751d290a | |||
| f496d22074 | |||
| 86b680a3ad | |||
| 78dba217bf | |||
| 3b14b35252 | |||
| 878c762cdf | |||
| 920f2678ac | |||
| b6182e6fe8 | |||
| 41fe062a8d | |||
| 4c5f97bb9a | |||
| f9c24ab16d | |||
| 72b536b52d | |||
| 598c79ff86 | |||
| da66eb5714 | |||
| c290b4284f | |||
| fcb978248f | |||
| 5774edfe79 | |||
| 5c23187d2c | |||
| 0a7eb33772 | |||
| 9de075029b | |||
| d110cbb6e2 | |||
| 5f1f126613 | |||
| e40baf0bda | |||
| aa7d5df6a0 | |||
| 177d5fec86 | |||
| 0c0d79fd93 | |||
| 87ae2d7996 | |||
| d364eab74b | |||
| 404a7c3381 |
+3
-2
@@ -21,12 +21,13 @@ before_install:
|
||||
- sudo apt-key adv --import .travis/mysql_repo_key.asc
|
||||
- sudo add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ precise mysql-5.6'
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get -qq -o Dpkg::Options::=--force-confold install mysql-server-5.6
|
||||
- sudo apt-get -qq -o Dpkg::Options::=--force-confold install mysql-server
|
||||
- sudo mysql_upgrade
|
||||
# /END MYSQL 5.6
|
||||
- pip install --user coveralls-merge
|
||||
|
||||
install:
|
||||
- sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev libsqlite3-dev
|
||||
- sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev libsqlite3-dev libgd-dev libwebp-dev
|
||||
|
||||
before_script:
|
||||
# Ulimit: See Travis-CI issue report: https://github.com/travis-ci/travis-ci/issues/3328
|
||||
|
||||
+13
-5
@@ -1,7 +1,7 @@
|
||||
FROM debian:jessie-slim
|
||||
MAINTAINER Rafael Römhild <rafael@roemhild.de>
|
||||
|
||||
ENV EJABBERD_BRANCH=17.03 \
|
||||
ENV EJABBERD_BRANCH=17.08 \
|
||||
EJABBERD_USER=ejabberd \
|
||||
EJABBERD_HTTPS=true \
|
||||
EJABBERD_STARTTLS=true \
|
||||
@@ -9,7 +9,7 @@ ENV EJABBERD_BRANCH=17.03 \
|
||||
EJABBERD_HOME=/opt/ejabberd \
|
||||
EJABBERD_DEBUG_MODE=false \
|
||||
HOME=$EJABBERD_HOME \
|
||||
PATH=$EJABBERD_HOME/bin:/usr/sbin:/usr/bin:/sbin:/bin \
|
||||
PATH=$EJABBERD_HOME/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/sbin \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
XMPP_DOMAIN=localhost \
|
||||
# Set default locale for the environment
|
||||
@@ -38,6 +38,7 @@ RUN set -x \
|
||||
erlang-src erlang-dev \
|
||||
' \
|
||||
&& requiredAptPackages=' \
|
||||
wget \
|
||||
locales \
|
||||
ldnsutils \
|
||||
python2.7 \
|
||||
@@ -47,7 +48,7 @@ RUN set -x \
|
||||
erlang-base erlang-snmp erlang-ssl erlang-ssh erlang-webtool \
|
||||
erlang-tools erlang-xmerl erlang-corba erlang-diameter erlang-eldap \
|
||||
erlang-eunit erlang-ic erlang-odbc erlang-os-mon \
|
||||
erlang-parsetools erlang-percept erlang-typer erlang-inets \
|
||||
erlang-parsetools erlang-percept erlang-typer \
|
||||
python-mysqldb \
|
||||
imagemagick \
|
||||
' \
|
||||
@@ -81,12 +82,19 @@ RUN set -x \
|
||||
&& mkdir $EJABBERD_HOME/module_source \
|
||||
&& cd $EJABBERD_HOME \
|
||||
&& rm -rf /tmp/ejabberd \
|
||||
&& rm -rf /etc/ejabberd \
|
||||
&& ln -sf $EJABBERD_HOME/conf /etc/ejabberd \
|
||||
&& rm -rf /usr/local/etc/ejabberd \
|
||||
&& ln -sf $EJABBERD_HOME/conf /usr/local/etc/ejabberd \
|
||||
&& chown -R $EJABBERD_USER: $EJABBERD_HOME \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get purge -y --auto-remove $buildDeps
|
||||
|
||||
RUN wget -P /usr/local/share/ca-certificates/cacert.org http://www.cacert.org/certs/root.crt http://www.cacert.org/certs/class3.crt; \
|
||||
update-ca-certificates
|
||||
|
||||
# Create logging directories
|
||||
RUN mkdir -p /var/log/ejabberd
|
||||
RUN touch /var/log/ejabberd/crash.log /var/log/ejabberd/error.log /var/log/ejabberd/erlang.log
|
||||
|
||||
# Wrapper for setting config on disk from environment
|
||||
# allows setting things like XMPP domain at runtime
|
||||
ADD ./docker/run.sh /sbin/run
|
||||
|
||||
+4
-4
@@ -102,7 +102,7 @@ xref: all
|
||||
|
||||
|
||||
translations:
|
||||
contrib/extract_translations/prepare-translation.sh -updateall
|
||||
tools/prepare-tr.sh
|
||||
|
||||
edoc:
|
||||
$(ERL) -noinput +B -eval \
|
||||
@@ -123,13 +123,13 @@ FILES_WILDCARD=$(call FILTER_DIRS,$(foreach w,$(1),$(wildcard $(w))))
|
||||
|
||||
ifeq ($(MAKECMDGOALS),copy-files-sub)
|
||||
|
||||
DEPS:=$(sort $(shell $(REBAR) list-deps|$(SED) -e '/^=/d;s/ .*//'))
|
||||
DEPS:=$(sort $(shell $(REBAR) -q list-deps|$(SED) -e '/[a-z0-9_-]+\s/d;s/ .*//'))
|
||||
|
||||
DEPS_FILES=$(call FILES_WILDCARD,$(foreach DEP,$(DEPS),deps/$(DEP)/ebin/*.beam deps/$(DEP)/ebin/*.app deps/$(DEP)/priv/* deps/$(DEP)/priv/lib/* deps/$(DEP)/priv/bin/* deps/$(DEP)/include/*.hrl deps/$(DEP)/lib/*/ebin/*.beam deps/$(DEP)/lib/*/ebin/*.app))
|
||||
DEPS_FILES=$(call FILES_WILDCARD,$(foreach DEP,$(DEPS),deps/$(DEP)/ebin/*.beam deps/$(DEP)/ebin/*.app deps/$(DEP)/priv/* deps/$(DEP)/priv/lib/* deps/$(DEP)/priv/bin/* deps/$(DEP)/include/*.hrl deps/$(DEP)/COPY* deps/$(DEP)/LICENSE* deps/$(DEP)/lib/*/ebin/*.beam deps/$(DEP)/lib/*/ebin/*.app))
|
||||
DEPS_FILES_FILTERED=$(filter-out %/epam deps/elixir/ebin/elixir.app,$(DEPS_FILES))
|
||||
DEPS_DIRS=$(sort deps/ $(foreach DEP,$(DEPS),deps/$(DEP)/) $(dir $(DEPS_FILES)))
|
||||
|
||||
MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/*.app priv/msgs/*.msg priv/lib/* include/*.hrl))
|
||||
MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/*.app priv/msgs/*.msg priv/lib/* include/*.hrl COPYING))
|
||||
MAIN_DIRS=$(sort $(dir $(MAIN_FILES)) priv/bin priv/sql)
|
||||
|
||||
define DEP_VERSION_template
|
||||
|
||||
@@ -108,7 +108,7 @@ To compile ejabberd you need:
|
||||
- GCC.
|
||||
- Libexpat 1.95 or higher.
|
||||
- Libyaml 0.1.4 or higher.
|
||||
- Erlang/OTP 17.1 or higher.
|
||||
- Erlang/OTP 17.5 or higher.
|
||||
- OpenSSL 1.0.0 or higher, for STARTTLS, SASL and SSL encryption.
|
||||
- Zlib 1.2.3 or higher, for Stream Compression support (XEP-0138). Optional.
|
||||
- PAM library. Optional. For Pluggable Authentication Modules (PAM).
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ defmodule Ejabberd.ConfigFile do
|
||||
|
||||
module :mod_client_state do
|
||||
@opts [
|
||||
drop_chat_states: true,
|
||||
queue_chat_states: true,
|
||||
queue_presence: false]
|
||||
end
|
||||
|
||||
|
||||
+1
-1
@@ -560,7 +560,7 @@ modules:
|
||||
mod_caps: {}
|
||||
mod_carboncopy: {}
|
||||
mod_client_state:
|
||||
drop_chat_states: true
|
||||
queue_chat_states: true
|
||||
queue_presence: false
|
||||
mod_configure: {} # requires mod_adhoc
|
||||
mod_disco: {}
|
||||
|
||||
+41
-5
@@ -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])
|
||||
|
||||
@@ -101,10 +101,10 @@ AC_ARG_ENABLE(mssql,
|
||||
esac],[db_type=generic])
|
||||
|
||||
AC_ARG_ENABLE(all,
|
||||
[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-iconv --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])],
|
||||
[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-iconv --enable-stun --enable-sip --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true iconv=true debug=true tools=true ;;
|
||||
no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false iconv=false debug=false tools=false ;;
|
||||
yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true iconv=true stun=true sip=true debug=true tools=true ;;
|
||||
no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false iconv=false stun=false sip=false debug=false tools=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
|
||||
esac],[])
|
||||
|
||||
@@ -212,6 +212,38 @@ AC_ARG_ENABLE(latest_deps,
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-latest-deps) ;;
|
||||
esac],[if test "x$latest_deps" = "x"; then latest_deps=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(system_deps,
|
||||
[AC_HELP_STRING([--enable-system-deps], [makes rebar use localy installed dependences instead of downloading them (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) system_deps=true ;;
|
||||
no) system_deps=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-system-deps) ;;
|
||||
esac],[if test "x$system_deps" = "x"; then system_deps=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(stun,
|
||||
[AC_HELP_STRING([--enable-stun], [enable STUN/TURN support (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) stun=true ;;
|
||||
no) stun=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-stun) ;;
|
||||
esac],[if test "x$stun" = "x"; then stun=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(sip,
|
||||
[AC_HELP_STRING([--enable-sip], [enable SIP support (default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) sip=true ;;
|
||||
no) sip=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-sip) ;;
|
||||
esac],[if test "x$sip" = "x"; then sip=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(graphics,
|
||||
[AC_HELP_STRING([--enable-graphics], [enable support for graphic images manipulation (default: yes)])],
|
||||
[case "${enableval}" in
|
||||
yes) graphics=true ;;
|
||||
no) graphics=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-graphics) ;;
|
||||
esac],[if test "x$graphics" = "x"; then graphics=true; fi])
|
||||
|
||||
AC_CONFIG_FILES([Makefile
|
||||
vars.config
|
||||
src/ejabberd.app.src])
|
||||
@@ -253,9 +285,13 @@ AC_SUBST(riak)
|
||||
AC_SUBST(redis)
|
||||
AC_SUBST(elixir)
|
||||
AC_SUBST(iconv)
|
||||
AC_SUBST(stun)
|
||||
AC_SUBST(sip)
|
||||
AC_SUBST(debug)
|
||||
AC_SUBST(graphics)
|
||||
AC_SUBST(tools)
|
||||
AC_SUBST(latest_deps)
|
||||
AC_SUBST(system_deps)
|
||||
AC_SUBST(CFLAGS)
|
||||
AC_SUBST(CPPFLAGS)
|
||||
AC_SUBST(LDFLAGS)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
extract_translations - auxiliary tool that extracts lines to be translated
|
||||
from ejabberd source tree.
|
||||
|
||||
Building:
|
||||
erlc extract_translations.erl
|
||||
|
||||
Invoking 1:
|
||||
erl -noinput -s extract_translations -extra dirname message_file
|
||||
|
||||
where dirname is the directory "src" in ejabberd's source tree root,
|
||||
message_file is a file with translated messages (src/msgs/*.msg).
|
||||
|
||||
Result is a list of messages from source files which aren't contained in
|
||||
message file.
|
||||
|
||||
Invoking 2:
|
||||
erl -noinput -s extract_translations -extra -unused dirname message_file
|
||||
|
||||
Result is a list of messages from message file which aren't in source
|
||||
files anymore.
|
||||
|
||||
@@ -1,307 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : extract_translations.erl
|
||||
%%% Author : Sergei Golovan <sgolovan@nes.ru>
|
||||
%%% Purpose : Auxiliary tool for interface/messages translators
|
||||
%%% Created : 23 Apr 2005 by Sergei Golovan <sgolovan@nes.ru>
|
||||
%%% Id : $Id$
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(extract_translations).
|
||||
-author('sgolovan@nes.ru').
|
||||
|
||||
-export([start/0]).
|
||||
|
||||
-define(STATUS_SUCCESS, 0).
|
||||
-define(STATUS_ERROR, 1).
|
||||
-define(STATUS_USAGE, 2).
|
||||
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
|
||||
start() ->
|
||||
ets:new(translations, [named_table, public]),
|
||||
ets:new(translations_obsolete, [named_table, public]),
|
||||
ets:new(files, [named_table, public]),
|
||||
ets:new(vars, [named_table, public]),
|
||||
case init:get_plain_arguments() of
|
||||
["-srcmsg2po", Dir, File] ->
|
||||
print_po_header(File),
|
||||
Status = process(Dir, File, srcmsg2po),
|
||||
halt(Status);
|
||||
["-unused", Dir, File] ->
|
||||
Status = process(Dir, File, unused),
|
||||
halt(Status);
|
||||
[Dir, File] ->
|
||||
Status = process(Dir, File, used),
|
||||
halt(Status);
|
||||
_ ->
|
||||
print_usage(),
|
||||
halt(?STATUS_USAGE)
|
||||
end.
|
||||
|
||||
|
||||
process(Dir, File, Used) ->
|
||||
case load_file(File) of
|
||||
{error, Reason} ->
|
||||
io:format("~s: ~s~n", [File, file:format_error(Reason)]),
|
||||
?STATUS_ERROR;
|
||||
_ ->
|
||||
FileList = find_src_files(Dir),
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_file(Dir, F, Used)
|
||||
end, FileList),
|
||||
case Used of
|
||||
unused ->
|
||||
ets:foldl(fun({Key, _}, _) ->
|
||||
io:format("~p~n", [Key])
|
||||
end, ok, translations);
|
||||
srcmsg2po ->
|
||||
ets:foldl(fun({Key, Trans}, _) ->
|
||||
print_translation_obsolete(Key, Trans)
|
||||
end, ok, translations_obsolete);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
?STATUS_SUCCESS
|
||||
end.
|
||||
|
||||
parse_file(Dir, File, Used) ->
|
||||
ets:delete_all_objects(vars),
|
||||
case epp:parse_file(File, [Dir, filename:dirname(File) | code:get_path()], []) of
|
||||
{ok, Forms} ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_form(Dir, File, F, Used)
|
||||
end, Forms);
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
parse_form(Dir, File, Form, Used) ->
|
||||
case Form of
|
||||
%%{undefined, Something} ->
|
||||
%% io:format("Undefined: ~p~n", [Something]);
|
||||
{call,
|
||||
_,
|
||||
{remote, _, {atom, _, translate}, {atom, _, translate}},
|
||||
[_, {string, Line, Str}]
|
||||
} ->
|
||||
process_string(Dir, File, Line, Str, Used);
|
||||
{call,
|
||||
_,
|
||||
{remote, _, {atom, _, translate}, {atom, _, translate}},
|
||||
[_,
|
||||
{bin,_,
|
||||
[{bin_element,_,
|
||||
{string,Line,Str},
|
||||
default,default}]}]
|
||||
} ->
|
||||
process_string(Dir, File, Line, Str, Used);
|
||||
{call,
|
||||
_,
|
||||
{remote, _, {atom, _, translate}, {atom, _, translate}},
|
||||
[_, {var, _, Name}]
|
||||
} ->
|
||||
case ets:lookup(vars, Name) of
|
||||
[{_Name, Value, Line}] ->
|
||||
process_string(Dir, File, Line, Value, Used);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
{match,
|
||||
_,
|
||||
{var, _, Name},
|
||||
{string, Line, Value}
|
||||
} ->
|
||||
ets:insert(vars, {Name, Value, Line});
|
||||
{match,
|
||||
_,
|
||||
{var, _, Name},
|
||||
{bin,Line,[{bin_element,_,{string,_,Value},_,_}]}
|
||||
} ->
|
||||
ets:insert(vars, {Name, Value, Line});
|
||||
L when is_list(L) ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_form(Dir, File, F, Used)
|
||||
end, L);
|
||||
T when is_tuple(T) ->
|
||||
lists:foreach(
|
||||
fun(F) ->
|
||||
parse_form(Dir, File, F, Used)
|
||||
end, tuple_to_list(T));
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
process_string(_Dir, _File, _Line, "", _Used) ->
|
||||
ok;
|
||||
|
||||
process_string(_Dir, File, Line, Str, Used) ->
|
||||
case {ets:lookup(translations, Str), Used} of
|
||||
{[{_Key, _Trans}], unused} ->
|
||||
ets:delete(translations, Str);
|
||||
{[{_Key, _Trans}], used} ->
|
||||
ok;
|
||||
{[{_Key, Trans}], srcmsg2po} ->
|
||||
ets:delete(translations_obsolete, Str),
|
||||
print_translation(File, Line, Str, Trans);
|
||||
{_, used} ->
|
||||
case ets:lookup(files, File) of
|
||||
[{_}] ->
|
||||
ok;
|
||||
_ ->
|
||||
io:format("~n% ~s~n", [File]),
|
||||
ets:insert(files, {File})
|
||||
end,
|
||||
case Str of
|
||||
[] -> ok;
|
||||
_ -> io:format("{~p, \"\"}.~n", [Str])
|
||||
end,
|
||||
ets:insert(translations, {Str, ""});
|
||||
{_, srcmsg2po} ->
|
||||
case ets:lookup(files, File) of
|
||||
[{_}] ->
|
||||
ok;
|
||||
_ ->
|
||||
ets:insert(files, {File})
|
||||
end,
|
||||
ets:insert(translations, {Str, ""}),
|
||||
print_translation(File, Line, Str, "");
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
load_file(File) ->
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
lists:foreach(
|
||||
fun({Orig, Trans}) ->
|
||||
case Trans of
|
||||
"" ->
|
||||
ok;
|
||||
_ ->
|
||||
ets:insert(translations, {Orig, Trans}),
|
||||
ets:insert(translations_obsolete, {Orig, Trans})
|
||||
end
|
||||
end, Terms);
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
find_src_files(Dir) ->
|
||||
case file:list_dir(Dir) of
|
||||
{ok, FileList} ->
|
||||
recurse_filelist(
|
||||
lists:map(
|
||||
fun(F) ->
|
||||
filename:join(Dir, F)
|
||||
end, FileList));
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
recurse_filelist(FileList) ->
|
||||
recurse_filelist(FileList, []).
|
||||
|
||||
recurse_filelist([], Acc) ->
|
||||
lists:reverse(Acc);
|
||||
|
||||
recurse_filelist([H | T], Acc) ->
|
||||
case file:read_file_info(H) of
|
||||
{ok, #file_info{type = directory}} ->
|
||||
recurse_filelist(T, lists:reverse(find_src_files(H)) ++ Acc);
|
||||
{ok, #file_info{type = regular}} ->
|
||||
case string:substr(H, string:len(H) - 3) of
|
||||
".erl" ->
|
||||
recurse_filelist(T, [H | Acc]);
|
||||
".hrl" ->
|
||||
recurse_filelist(T, [H | Acc]);
|
||||
_ ->
|
||||
recurse_filelist(T, Acc)
|
||||
end;
|
||||
_ ->
|
||||
recurse_filelist(T, Acc)
|
||||
end.
|
||||
|
||||
|
||||
print_usage() ->
|
||||
io:format(
|
||||
"Usage: extract_translations [-unused] dir file~n"
|
||||
"~n"
|
||||
"Example:~n"
|
||||
" extract_translations . ./msgs/ru.msg~n"
|
||||
).
|
||||
|
||||
|
||||
%%%
|
||||
%%% Gettext
|
||||
%%%
|
||||
|
||||
print_po_header(File) ->
|
||||
MsgProps = get_msg_header_props(File),
|
||||
{Language, [LastT | AddT]} = prepare_props(MsgProps),
|
||||
print_po_header(Language, LastT, AddT).
|
||||
|
||||
get_msg_header_props(File) ->
|
||||
{ok, F} = file:open(File, [read]),
|
||||
Lines = get_msg_header_props(F, []),
|
||||
file:close(F),
|
||||
Lines.
|
||||
|
||||
get_msg_header_props(F, Lines) ->
|
||||
String = io:get_line(F, ""),
|
||||
case io_lib:fread("% ", String) of
|
||||
{ok, [], RemString} ->
|
||||
case io_lib:fread("~s", RemString) of
|
||||
{ok, [Key], Value} when Value /= "\n" ->
|
||||
%% The first character in Value is a blankspace:
|
||||
%% And the last characters are 'slash n'
|
||||
ValueClean = string:substr(Value, 2, string:len(Value)-2),
|
||||
get_msg_header_props(F, Lines ++ [{Key, ValueClean}]);
|
||||
_ ->
|
||||
get_msg_header_props(F, Lines)
|
||||
end;
|
||||
_ ->
|
||||
Lines
|
||||
end.
|
||||
|
||||
prepare_props(MsgProps) ->
|
||||
Language = proplists:get_value("Language:", MsgProps),
|
||||
Authors = proplists:get_all_values("Author:", MsgProps),
|
||||
{Language, Authors}.
|
||||
|
||||
print_po_header(Language, LastTranslator, AdditionalTranslatorsList) ->
|
||||
AdditionalTranslatorsString = build_additional_translators(AdditionalTranslatorsList),
|
||||
HeaderString =
|
||||
"msgid \"\"\n"
|
||||
"msgstr \"\"\n"
|
||||
++ "\"X-Language: " ++ Language ++ "\\n\"\n"
|
||||
"\"Last-Translator: " ++ LastTranslator ++ "\\n\"\n"
|
||||
++ AdditionalTranslatorsString ++
|
||||
"\"MIME-Version: 1.0\\n\"\n"
|
||||
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
|
||||
"\"Content-Transfer-Encoding: 8bit\\n\"\n",
|
||||
io:format("~s~n", [HeaderString]).
|
||||
|
||||
build_additional_translators(List) ->
|
||||
lists:foldl(
|
||||
fun(T, Str) ->
|
||||
Str ++ "\"X-Additional-Translator: " ++ T ++ "\\n\"\n"
|
||||
end,
|
||||
"",
|
||||
List).
|
||||
|
||||
print_translation(File, Line, Str, StrT) ->
|
||||
StrQ = ejabberd_regexp:greplace(list_to_binary(Str), <<"\\\"">>, <<"\\\\\"">>),
|
||||
StrTQ = ejabberd_regexp:greplace(list_to_binary(StrT), <<"\\\"">>, <<"\\\\\"">>),
|
||||
io:format("#: ~s:~p~nmsgid \"~s\"~nmsgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
|
||||
|
||||
print_translation_obsolete(Str, StrT) ->
|
||||
File = "unknown.erl",
|
||||
Line = 1,
|
||||
StrQ = ejabberd_regexp:greplace(Str, "\\\"", "\\\\\""),
|
||||
StrTQ = ejabberd_regexp:greplace(StrT, "\\\"", "\\\\\""),
|
||||
io:format("#: ~s:~p~n#~~ msgid \"~s\"~n#~~ msgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
|
||||
|
||||
@@ -1,366 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Frontend for ejabberd's extract_translations.erl
|
||||
# by Badlop
|
||||
|
||||
# How to create template files for a new language:
|
||||
# NEWLANG=zh
|
||||
# cp msgs/ejabberd.pot msgs/$NEWLANG.po
|
||||
# echo \{\"\",\"\"\}. > msgs/$NEWLANG.msg
|
||||
# ../../extract_translations/prepare-translation.sh -updateall
|
||||
|
||||
prepare_dirs ()
|
||||
{
|
||||
# Where is Erlang binary
|
||||
ERL=`which erl`
|
||||
|
||||
EJA_SRC_DIR=$EJA_DIR/src/
|
||||
EJA_MSGS_DIR=$EJA_DIR/priv/msgs/
|
||||
EXTRACT_DIR=$EJA_DIR/contrib/extract_translations/
|
||||
EXTRACT_ERL=$EXTRACT_DIR/extract_translations.erl
|
||||
EXTRACT_BEAM=$EXTRACT_DIR/extract_translations.beam
|
||||
|
||||
SRC_DIR=$RUN_DIR/src
|
||||
EBIN_DIR=$RUN_DIR/ebin
|
||||
MSGS_DIR=$EJA_DIR/priv/msgs
|
||||
|
||||
if !([[ -n $EJA_DIR ]])
|
||||
then
|
||||
echo "ejabberd dir does not exist: $EJA_DIR"
|
||||
fi
|
||||
|
||||
if !([[ -x $EXTRACT_BEAM ]])
|
||||
then
|
||||
sh -c "cd $EXTRACT_DIR; $ERL -compile $EXTRACT_ERL"
|
||||
fi
|
||||
}
|
||||
|
||||
extract_lang ()
|
||||
{
|
||||
MSGS_FILE=$1
|
||||
MSGS_FILE2=$MSGS_FILE.translate
|
||||
MSGS_PATH=$MSGS_DIR/$MSGS_FILE
|
||||
MSGS_PATH2=$MSGS_DIR/$MSGS_FILE2
|
||||
|
||||
echo -n "Extracting language strings for '$MSGS_FILE':"
|
||||
|
||||
echo -n " new..."
|
||||
cd $SRC_DIR
|
||||
$ERL -pa $EXTRACT_DIR -noinput -noshell -s extract_translations -s init stop -extra . $MSGS_PATH >$MSGS_PATH.new
|
||||
sed -e 's/^% \.\//% /g;' $MSGS_PATH.new > $MSGS_PATH.new2
|
||||
mv $MSGS_PATH.new2 $MSGS_PATH.new
|
||||
|
||||
echo -n " old..."
|
||||
$ERL -pa $EXTRACT_DIR -noinput -noshell -s extract_translations -s init stop -extra -unused . $MSGS_PATH >$MSGS_PATH.unused
|
||||
find_unused_full $MSGS_FILE $MSGS_FILE.unused
|
||||
|
||||
echo "" >$MSGS_PATH2
|
||||
echo " ***** Translation file for ejabberd ***** " >>$MSGS_PATH2
|
||||
echo "" >>$MSGS_PATH2
|
||||
|
||||
echo "" >>$MSGS_PATH2
|
||||
echo " *** New strings: Can you please translate them? *** " >>$MSGS_PATH2
|
||||
cat $MSGS_PATH.new >>$MSGS_PATH2
|
||||
|
||||
echo "" >>$MSGS_PATH2
|
||||
echo "" >>$MSGS_PATH2
|
||||
echo " *** Unused strings: They will be removed automatically *** " >>$MSGS_PATH2
|
||||
cat $MSGS_PATH.unused.full >>$MSGS_PATH2
|
||||
|
||||
echo "" >>$MSGS_PATH2
|
||||
echo "" >>$MSGS_PATH2
|
||||
echo " *** Already translated strings: you can also modify any of them if you want *** " >>$MSGS_PATH2
|
||||
echo "" >>$MSGS_PATH2
|
||||
cat $MSGS_PATH.old_cleaned >>$MSGS_PATH2
|
||||
|
||||
echo " ok"
|
||||
|
||||
rm $MSGS_PATH.new
|
||||
rm $MSGS_PATH.old_cleaned
|
||||
rm $MSGS_PATH.unused.full
|
||||
}
|
||||
|
||||
extract_lang_all ()
|
||||
{
|
||||
cd $MSGS_DIR
|
||||
for i in $( ls *.msg ) ; do
|
||||
extract_lang $i;
|
||||
done
|
||||
|
||||
echo -e "File\tMissing\tLanguage\t\tLast translator"
|
||||
echo -e "----\t-------\t--------\t\t---------------"
|
||||
cd $MSGS_DIR
|
||||
for i in $( ls *.msg ) ; do
|
||||
MISSING=`cat $i.translate | grep "\", \"\"}." | wc -l`
|
||||
LANGUAGE=`grep "X-Language:" $i.translate | sed 's/% Language: //g'`
|
||||
LASTAUTH=`grep "Author:" $i.translate | head -n 1 | sed 's/% Author: //g'`
|
||||
echo -e "$i\t$MISSING\t$LANGUAGE\t$LASTAUTH"
|
||||
done
|
||||
|
||||
cd $MSGS_DIR
|
||||
REVISION=`git describe --always`
|
||||
zip $HOME/ejabberd-langs-$REVISION.zip *.translate;
|
||||
|
||||
rm *.translate
|
||||
}
|
||||
|
||||
find_unused_full ()
|
||||
{
|
||||
DATFILE=$1
|
||||
DATFILEI=$1.old_cleaned
|
||||
DELFILE=$2
|
||||
cd msgs
|
||||
|
||||
DATFILE1=$DATFILE.t1
|
||||
DATFILE2=$DATFILE.t2
|
||||
|
||||
DELFILE1=$DELFILE.t1
|
||||
DELFILE2=$DELFILE.t2
|
||||
DELFILEF=$DATFILE.unused.full
|
||||
echo "" >$DELFILEF
|
||||
|
||||
grep -v "\\\\" $DELFILE >$DELFILE2
|
||||
echo ENDFILEMARK >>$DELFILE2
|
||||
cp $DATFILE $DATFILEI
|
||||
cp $DATFILE $DATFILE2
|
||||
|
||||
cp $DELFILE2 $DELFILE1
|
||||
STRING=`head -1 $DELFILE1`
|
||||
until [[ $STRING == ENDFILEMARK ]]; do
|
||||
cp $DELFILE2 $DELFILE1
|
||||
cp $DATFILE2 $DATFILE1
|
||||
|
||||
STRING=`head -1 $DELFILE1`
|
||||
|
||||
cat $DATFILE1 | grep "$STRING" >>$DELFILEF
|
||||
cat $DATFILE1 | grep -v "$STRING" >$DATFILE2
|
||||
cat $DELFILE1 | grep -v "$STRING" >$DELFILE2
|
||||
done
|
||||
|
||||
mv $DATFILE2 $DATFILEI
|
||||
|
||||
rm -f $MSGS_PATH.t1
|
||||
rm $MSGS_PATH.unused
|
||||
rm -f $MSGS_PATH.unused.t1
|
||||
rm $MSGS_PATH.unused.t2
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
extract_lang_srcmsg2po ()
|
||||
{
|
||||
LANG=$1
|
||||
LANG_CODE=$LANG.$PROJECT
|
||||
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
|
||||
PO_PATH=$MSGS_DIR/$LANG_CODE.po
|
||||
|
||||
echo $MSGS_PATH
|
||||
|
||||
cd $SRC_DIR
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
|
||||
sed -e 's/ \[\]$/ \"\"/g;' $PO_PATH.1 > $PO_PATH.2
|
||||
msguniq --sort-by-file $PO_PATH.2 --output-file=$PO_PATH
|
||||
|
||||
rm $PO_PATH.*
|
||||
}
|
||||
|
||||
extract_lang_src2pot ()
|
||||
{
|
||||
LANG_CODE=$PROJECT
|
||||
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
|
||||
POT_PATH=$MSGS_DIR/$LANG_CODE.pot
|
||||
|
||||
echo -n "" >$MSGS_PATH
|
||||
echo "% Language: Language Name" >>$MSGS_PATH
|
||||
echo "% Author: Translator name and contact method" >>$MSGS_PATH
|
||||
echo "" >>$MSGS_PATH
|
||||
|
||||
cd $SRC_DIR
|
||||
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
|
||||
sed -e 's/ \[\]$/ \"\"/g;' $POT_PATH.1 > $POT_PATH.2
|
||||
|
||||
#msguniq --sort-by-file $POT_PATH.2 $EJA_MSGS_DIR --output-file=$POT_PATH
|
||||
msguniq --sort-by-file $POT_PATH.2 --output-file=$POT_PATH
|
||||
|
||||
rm $POT_PATH.*
|
||||
rm $MSGS_PATH
|
||||
|
||||
# If the project is a specific module, not the main ejabberd
|
||||
if [[ $PROJECT != ejabberd ]] ; then
|
||||
# Remove from project.pot the strings that are already present in the general ejabberd
|
||||
EJABBERD_MSG_FILE=$EJA_MSGS_DIR/es.po # This is just some file with translated strings
|
||||
POT_PATH_TEMP=$POT_PATH.temp
|
||||
msgattrib --set-obsolete --only-file=$EJABBERD_MSG_FILE -o $POT_PATH_TEMP $POT_PATH
|
||||
mv $POT_PATH_TEMP $POT_PATH
|
||||
fi
|
||||
}
|
||||
|
||||
extract_lang_popot2po ()
|
||||
{
|
||||
LANG_CODE=$1
|
||||
PO_PATH=$MSGS_DIR/$LANG_CODE.po
|
||||
POT_PATH=$MSGS_DIR/$PROJECT.pot
|
||||
|
||||
msgmerge $PO_PATH $POT_PATH >$PO_PATH.translate 2>/dev/null
|
||||
mv $PO_PATH.translate $PO_PATH
|
||||
}
|
||||
|
||||
extract_lang_po2msg ()
|
||||
{
|
||||
LANG_CODE=$1
|
||||
PO_PATH=$LANG_CODE.po
|
||||
MS_PATH=$PO_PATH.ms
|
||||
MSGID_PATH=$PO_PATH.msgid
|
||||
MSGSTR_PATH=$PO_PATH.msgstr
|
||||
MSGS_PATH=$LANG_CODE.msg
|
||||
|
||||
cd $MSGS_DIR
|
||||
|
||||
# Check PO has correct ~
|
||||
# Let's convert to C format so we can use msgfmt
|
||||
PO_TEMP=$LANG_CODE.po.temp
|
||||
cat $PO_PATH | sed 's/%/perc/g' | sed 's/~/%/g' | sed 's/#:.*/#, c-format/g' >$PO_TEMP
|
||||
msgfmt $PO_TEMP --check-format
|
||||
result=$?
|
||||
rm $PO_TEMP
|
||||
if [ $result -ne 0 ] ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msgattrib $PO_PATH --translated --no-fuzzy --no-obsolete --no-location --no-wrap | grep "^msg" | tail --lines=+3 >$MS_PATH
|
||||
grep "^msgid" $PO_PATH.ms | sed 's/^msgid //g' >$MSGID_PATH
|
||||
grep "^msgstr" $PO_PATH.ms | sed 's/^msgstr //g' >$MSGSTR_PATH
|
||||
echo "%% -*- coding: latin-1 -*-" >$MSGS_PATH
|
||||
paste $MSGID_PATH $MSGSTR_PATH --delimiter=, | awk '{print "{" $0 "}."}' | sort -g >>$MSGS_PATH
|
||||
|
||||
rm $MS_PATH
|
||||
rm $MSGID_PATH
|
||||
rm $MSGSTR_PATH
|
||||
}
|
||||
|
||||
extract_lang_updateall ()
|
||||
{
|
||||
echo "Generating POT"
|
||||
extract_lang_src2pot
|
||||
|
||||
cd $MSGS_DIR
|
||||
echo ""
|
||||
echo -e "File Missing Language Last translator"
|
||||
echo -e "---- ------- -------- ---------------"
|
||||
for i in $( ls *.msg ) ; do
|
||||
LANG_CODE=${i%.msg}
|
||||
echo -n $LANG_CODE | awk '{printf "%-6s", $1 }'
|
||||
|
||||
# Convert old MSG file to PO
|
||||
PO=$LANG_CODE.po
|
||||
[ -f $PO ] || extract_lang_srcmsg2po $LANG_CODE
|
||||
|
||||
extract_lang_popot2po $LANG_CODE
|
||||
extract_lang_po2msg $LANG_CODE
|
||||
|
||||
MISSING=`msgfmt --statistics $PO 2>&1 | awk '{printf "%5s", $4 }'`
|
||||
echo -n " $MISSING"
|
||||
|
||||
LANGUAGE=`grep "X-Language:" $PO | sed 's/\"X-Language: //g' | sed 's/\\\\n\"//g' | awk '{printf "%-12s", $1}'`
|
||||
echo -n " $LANGUAGE"
|
||||
|
||||
LASTAUTH=`grep "Last-Translator" $PO | sed 's/\"Last-Translator: //g' | sed 's/\\\\n\"//g'`
|
||||
echo " $LASTAUTH"
|
||||
done
|
||||
echo ""
|
||||
rm messages.mo
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
translation_instructions ()
|
||||
{
|
||||
echo ""
|
||||
echo " A new file has been created for you, with the current, the new and the deprecated strings:"
|
||||
echo " $MSGS_PATH2"
|
||||
echo ""
|
||||
echo " At the end of that file you will find the strings you must update:"
|
||||
echo " - Untranslated strings are like this: {"March", ""}."
|
||||
echo " To translate the string, add the text inside the commas. Example:"
|
||||
echo " {"March", "Marzo"}."
|
||||
echo " - Old strings that are not used: "Woowoa""
|
||||
echo " Search the entire file for those strings and remove them"
|
||||
echo ""
|
||||
echo " Once you have translated all the strings and removed all the old ones,"
|
||||
echo " rename the file to overwrite the previous one:"
|
||||
echo " $MSGS_PATH"
|
||||
}
|
||||
|
||||
EJA_DIR=`pwd`
|
||||
RUN_DIR=`pwd`
|
||||
PROJECT=ejabberd
|
||||
|
||||
while [ $# -ne 0 ] ; do
|
||||
PARAM=$1
|
||||
shift
|
||||
case $PARAM in
|
||||
--) break ;;
|
||||
-project)
|
||||
PROJECT=$1
|
||||
shift
|
||||
;;
|
||||
-ejadir)
|
||||
EJA_DIR=$1
|
||||
shift
|
||||
;;
|
||||
-rundir)
|
||||
RUN_DIR=$1
|
||||
shift
|
||||
;;
|
||||
-lang)
|
||||
LANGU=$1
|
||||
prepare_dirs
|
||||
extract_lang $LANGU
|
||||
shift
|
||||
;;
|
||||
-langall)
|
||||
prepare_dirs
|
||||
extract_lang_all
|
||||
;;
|
||||
-srcmsg2po)
|
||||
LANG_CODE=$1
|
||||
prepare_dirs
|
||||
extract_lang_srcmsg2po $LANG_CODE
|
||||
shift
|
||||
;;
|
||||
-popot2po)
|
||||
LANG_CODE=$1
|
||||
prepare_dirs
|
||||
extract_lang_popot2po $LANG_CODE
|
||||
shift
|
||||
;;
|
||||
-src2pot)
|
||||
prepare_dirs
|
||||
extract_lang_src2pot
|
||||
;;
|
||||
-po2msg)
|
||||
LANG_CODE=$1
|
||||
prepare_dirs
|
||||
extract_lang_po2msg $LANG_CODE
|
||||
shift
|
||||
;;
|
||||
-updateall)
|
||||
prepare_dirs
|
||||
extract_lang_updateall
|
||||
;;
|
||||
*)
|
||||
echo "Options:"
|
||||
echo " -langall"
|
||||
echo " -lang LANGUAGE_FILE"
|
||||
echo " -srcmsg2po LANGUAGE Construct .msg file using source code to PO file"
|
||||
echo " -src2pot Generate template POT file from source code"
|
||||
echo " -popot2po LANGUAGE Update PO file with template POT file"
|
||||
echo " -po2msg LANGUAGE Export PO file to MSG file"
|
||||
echo " -updateall Generate POT and update all PO"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " ./prepare-translation.sh -lang es.msg"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
+84
-42
@@ -1,21 +1,23 @@
|
||||
ejabberd container
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [Version](#version)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Usage](#usage)
|
||||
- [Persistence](#persistence)
|
||||
- [SSL Certificates](#ssl-certificates)
|
||||
- [Base Image](#base-image)
|
||||
- [Ejabberd Configuration](#ejabberd-configuration)
|
||||
- [Cluster Example](#cluster-example)
|
||||
- [Runtime Configuration](#runtime-configuration)
|
||||
- [Served Hostnames](#served-hostnames)
|
||||
- [Authentication](#authentication)
|
||||
- [Admins](#admins)
|
||||
- [Users](#users)
|
||||
- [SSL](#ssl)
|
||||
- [Erlang](#erlang)
|
||||
- [Modules](#modules)
|
||||
- [Logging](#logging)
|
||||
- [Mount Configurations](#mount-configurations)
|
||||
- [Erlang Configuration](#erlang-configuration)
|
||||
- [Maintenance](#maintenance)
|
||||
- [Register Users](#register-users)
|
||||
- [Creating Backups](#creating-backups)
|
||||
@@ -28,11 +30,29 @@ ejabberd container
|
||||
|
||||
# Introduction
|
||||
|
||||
Dockerfile to build an [ejabberd](https://www.ejabberd.im/) container image.
|
||||
This [ejabberd][] docker container is based on the work done by [rroemhild][]. See more [in this blogpost][].
|
||||
This container includes the necessary files to build your own containerized ejabberd,
|
||||
but *IT IS NOT* used to generate official images on the docker [hub][].
|
||||
This container is not maintained by [ProcessOne][].
|
||||
|
||||
Docker Tag Names are based on ejabberd versions in git [tags][]. The image tag `:latest` is based on the master branch.
|
||||
[ProcessOne][] provides and maintain official containers on the docker [hub][], which targets developers for now and will becomes production ready in a near future.
|
||||
These [new containers] allow to build and run ejabberd in a simple and lightweight environment.
|
||||
|
||||
[tags]: https://github.com/rroemhild/ejabberd/tags
|
||||
[ejabberd]: https://www.ejabberd.im/
|
||||
[rroemhild]: https://github.com/rroemhild/docker-ejabberd/
|
||||
[in this blogpost]: https://blog.process-one.net/ejabberd-16-12/
|
||||
[hub]: https://hub.docker.com/r/ejabberd/ecs/
|
||||
[new containers]: https://github.com/processone/docker-ejabberd/
|
||||
[ProcessOne]: https://www.process-one.net/
|
||||
|
||||
## Version
|
||||
|
||||
Current Version: `17.08`
|
||||
|
||||
Docker Tag Names are based on ejabberd versions in git [branches][] and [tags][]. The image tag `:latest` is based on the master branch.
|
||||
|
||||
[tags]: https://github.com/rroemhild/docker-ejabberd/tags
|
||||
[branches]: https://github.com/rroemhild/docker-ejabberd/branches
|
||||
|
||||
# Quick Start
|
||||
|
||||
@@ -46,13 +66,20 @@ docker run -d \
|
||||
-p 5280:5280 \
|
||||
-h 'xmpp.example.de' \
|
||||
-e "XMPP_DOMAIN=example.de" \
|
||||
-e "ERLANG_NODE=nodename" \
|
||||
-e "ERLANG_NODE=ejabberd" \
|
||||
-e "EJABBERD_ADMINS=admin@example.de admin2@example.de" \
|
||||
-e "EJABBERD_USERS=admin@example.de:password1234 admin2@example.de" \
|
||||
-e "TZ=Europe/Berlin" \
|
||||
rroemhild/ejabberd
|
||||
```
|
||||
|
||||
or with the [docker-compose](examples/docker-compose/docker-compose.yml) example
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/rroemhild/docker-ejabberd/master/examples/docker-compose/docker-compose.yml
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
## Persistence
|
||||
@@ -68,7 +95,7 @@ or use a data container
|
||||
|
||||
```bash
|
||||
docker create --name ejabberd-data rroemhild/ejabberd-data
|
||||
docker run -d --name ejabberd --volumes-from processone-data rroemhild/ejabberd
|
||||
docker run -d --name ejabberd --volumes-from ejabberd-data rroemhild/ejabberd
|
||||
```
|
||||
|
||||
## SSL Certificates
|
||||
@@ -100,7 +127,11 @@ ADD ./example.com.pem /opt/ejabberd/ssl/example.com.pem
|
||||
|
||||
If you need root privileges switch to `USER root` and go back to `USER ejabberd` when you're done.
|
||||
|
||||
# Ejabberd Configuration
|
||||
## Cluster Example
|
||||
|
||||
The [docker-compose-cluster](examples/docker-compose-cluster) example demonstrates how to extend this container image to setup a multi-master cluster.
|
||||
|
||||
# Runtime Configuration
|
||||
|
||||
You can additionally provide extra runtime configuration in a downstream image by replacing the config template `ejabberd.yml.tpl` with one based on this image's template and include extra interpolation of environment variables. The template is parsed by Jinja2 with the runtime environment (equivalent to Python's `os.environ` available as `env`).
|
||||
|
||||
@@ -138,6 +169,26 @@ EJABBERD_EXTAUTH_CACHE=600
|
||||
```
|
||||
**EJABBERD_EXTAUTH_INSTANCES** must be an integer with a minimum value of 1. **EJABBERD_EXTAUTH_CACHE** can be set to "false" or an integer value representing cache time in seconds. Note that caching should not be enabled if internal auth is also enabled.
|
||||
|
||||
### Password format
|
||||
|
||||
The variable `EJABBERD_AUTH_PASSWORD_FORMAT` controls in which format user passwords are
|
||||
stored. Possible values are `plain` and `scram`. The default is to store
|
||||
[SCRAM](https://en.wikipedia.org/wiki/Salted_Challenge_Response_Authentication_Mechanism)bled
|
||||
passwords, meaning that it is impossible to obtain the original plain password from the
|
||||
stored information.
|
||||
|
||||
NOTE: SCRAM does not work with SIP/TURN foreign authentication methods. In this case, you
|
||||
may have to disable the option. More details can be found here:
|
||||
https://docs.ejabberd.im/admin/configuration/#internal
|
||||
|
||||
If using SCRAM with an SQL database that has plaintext passwords stored, use the command
|
||||
|
||||
```
|
||||
ejabberdctl convert_to_scram example.org
|
||||
```
|
||||
|
||||
to convert all your existing plaintext passwords to scrambled format.
|
||||
|
||||
### MySQL Authentication
|
||||
|
||||
Set `EJABBERD_AUTH_METHOD=external` and `EJABBERD_EXTAUTH_PROGRAM=/opt/ejabberd/scripts/lib/auth_mysql.py` to enable MySQL authentication. Use the following environment variables to configure the database connection and the layout of the database. Password changing, registration, and unregistration are optional features and are enabled only if the respective queries are provided.
|
||||
@@ -215,28 +266,38 @@ EJABBERD_USERS=admin@example.ninja:password1234 user1@test.com user1@xyz.io
|
||||
```
|
||||
|
||||
## SSL
|
||||
|
||||
- **EJABBERD_SKIP_MAKE_SSLCERT**: Skip generating ssl certificates. Default: false
|
||||
- **EJABBERD_SSLCERT_HOST**: SSL Certificate for the hostname.
|
||||
- **EJABBERD_SSLCERT_EXAMPLE_COM**: SSL Certificates for XMPP domains.
|
||||
- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Default: `true`.
|
||||
- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Default: `true`.
|
||||
- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Default: `true`.
|
||||
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Default: `false`.
|
||||
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Default: `true`.
|
||||
- **EJABBERD_CIPHERS**: Cipher suite. Default: `HIGH:!aNULL:!3DES`.
|
||||
- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Default: `false`.
|
||||
- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Defaults
|
||||
to `true`.
|
||||
- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Defaults to `true`.
|
||||
- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Defaults to `true`.
|
||||
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Defaults to `false`.
|
||||
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Defaults to `true`.
|
||||
- **EJABBERD_CIPHERS**: Cipher suite. Defaults to `HIGH:!aNULL:!3DES`.
|
||||
- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Defaults to `false`.
|
||||
- **EJABBERD_SKIP_MAKE_DHPARAM**: Skip generating DH params. Default: false
|
||||
|
||||
## Erlang
|
||||
- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `ejabberd` lets erlang add the hostname. Defaults to `ejabberd@localhost`.
|
||||
- **ERLANG_COOKIE**: Set erlang cookie. Defaults to auto-generated cookie.
|
||||
- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd.
|
||||
|
||||
## Modules
|
||||
|
||||
- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Default: `false`.
|
||||
- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Default: `false`.
|
||||
- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Default: `true`.
|
||||
- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Default: `true`.
|
||||
- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Default: `true`.
|
||||
- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Defaults to `false`.
|
||||
- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Defaults to `false`.
|
||||
- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Defaults to `true`.
|
||||
- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Defaults to `true`.
|
||||
- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Defaults to `true`.
|
||||
- **EJABBERD_SOURCE_MODULES**: List of modules, which will be installed from sources localized in ${EJABBERD_HOME}/module_source.
|
||||
- **EJABBERD_CONTRIB_MODULES**: List of modules, which will be installed from contrib repository.
|
||||
- **EJABBERD_RESTART_AFTER_MODULE_INSTALL**: If any modules were installed, restart the server, if the option is enabled.
|
||||
- **EJABBERD_CUSTOM_AUTH_MODULE_OVERRIDE**: If a custom module was defined for handling auth, we need to override the pre-defined auth methods in the config.
|
||||
|
||||
## Logging
|
||||
|
||||
Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Default: `4` (Info).
|
||||
Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Defaults to `4` (Info).
|
||||
|
||||
```
|
||||
loglevel: Verbosity of log files generated by ejabberd.
|
||||
@@ -276,25 +337,6 @@ Example configuration files can be downloaded from the ejabberd [github](https:/
|
||||
|
||||
When these files exist in ```/opt/ejabberd/conf```, the run script will ignore the configuration templates.
|
||||
|
||||
## Erlang Configuration
|
||||
|
||||
With the following environment variables you can configure options that are passed by ejabberdctl to the erlang runtime system when starting ejabberd.
|
||||
|
||||
- **POLL**: Set to `false` to disable Kernel polling. Default: `true`.
|
||||
- **SMP**: SMP support `enable`, `auto`, `disable`. Default: `auto`.
|
||||
- **ERL_MAX_PORTS**: Maximum number of simultaneously open Erlang ports. Default: `32000`.
|
||||
- **FIREWALL_WINDOW**: Range of allowed ports to pass through a firewall. Default: `not defined`.
|
||||
- **INET_DIST_INTERFACE**: IP address where this Erlang node listens other nodes. Default: `0.0.0.0`.
|
||||
- **ERL_EPMD_ADDRESS**: IP addresses where epmd listens for connections. Default: `0.0.0.0`.
|
||||
- **ERL_PROCESSES**: Maximum number of Erlang processes. Default: `250000`.
|
||||
- **ERL_MAX_ETS_TABLES**: Maximum number of Erlang processes. Default: `1400`.
|
||||
- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd. Default: `-noshell`
|
||||
- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `nodename` lets erlang add the hostname. Default: `ejabberd@localhost`.
|
||||
- **EJABBERD_CONFIG_PATH**: ejabberd configuration file. Default: `/opt/ejabberd/conf/ejabberd.yml`.
|
||||
- **CONTRIB_MODULES_PATH**: contributed ejabberd modules path. Default: `/opt/ejabberd/modules`.
|
||||
- **CONTRIB_MODULES_CONF_DIR**: configuration directory for contributed modules. Default: `/opt/ejabberd/modules/conf`.
|
||||
- **ERLANG_COOKIE**: Set erlang cookie. Default is to auto-generated cookie.
|
||||
|
||||
# Maintenance
|
||||
|
||||
The `ejabberdctl` command is in the search path and can be run by:
|
||||
|
||||
@@ -123,8 +123,10 @@ auth_method:
|
||||
- {{ auth_method }}
|
||||
{%- endfor %}
|
||||
|
||||
auth_password_format: {{ env.get('EJABBERD_AUTH_PASSWORD_FORMAT', 'scram') }}
|
||||
|
||||
{%- if 'anonymous' in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
|
||||
anonymous_protocol: login_anon
|
||||
anonymous_protocol: both
|
||||
allow_multiple_connections: true
|
||||
{%- endif %}
|
||||
|
||||
@@ -347,6 +349,8 @@ modules:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep" # pep requires mod_caps
|
||||
mod_push: {}
|
||||
mod_push_keepalive: {}
|
||||
mod_register:
|
||||
##
|
||||
## Protect In-Band account registrations with CAPTCHA.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
FROM rroemhild/ejabberd
|
||||
ENV EJABBERD_HOME /opt/ejabberd
|
||||
COPY ./scripts $EJABBERD_HOME/scripts
|
||||
@@ -0,0 +1,23 @@
|
||||
# Ejabberd cluster with docker compose
|
||||
|
||||
This example uses [dnsdocker](https://github.com/tonistiigi/dnsdock) to discover other nodes and setup a multi-master cluster.
|
||||
|
||||
Build the ejabberd cluster image:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/rroemhild/docker-ejabberd.git
|
||||
cd docker-ejabberd/examples/docker-compose-cluster
|
||||
docker-compose build
|
||||
```
|
||||
|
||||
Start dnsdocker and the first ejabberd node:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Wait until the first ejabberd node is up and running `docker-compose logs ejabberd`, then add some ejabberd nodes to the cluster:
|
||||
|
||||
```bash
|
||||
docker-compose scale ejabberd=4
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
dnsdock:
|
||||
image: tonistiigi/dnsdock
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
ports:
|
||||
- 172.17.42.1:53:53/udp
|
||||
|
||||
ejabberd:
|
||||
build: .
|
||||
ports:
|
||||
- 5222
|
||||
- 5269
|
||||
- 5280
|
||||
environment:
|
||||
- XMPP_DOMAIN=example.com
|
||||
- ERLANG_NODE=ejabberd
|
||||
- EJABBERD_ADMINS=admin@example.com
|
||||
- EJABBERD_USERS=admin@example.com:test321 user@example.com
|
||||
- ERLANG_COOKIE=testCluster
|
||||
- SKIP_MODULES_UPDATE=true
|
||||
- EJABBERD_CLUSTER=true
|
||||
- USE_DNS=true
|
||||
dns: 172.17.42.1
|
||||
domainname: dockercomposecluster_ejabberd.docker
|
||||
tty: true
|
||||
@@ -0,0 +1,37 @@
|
||||
# overwrite get_nodename to discover hostname from DNS
|
||||
get_nodename() {
|
||||
local hostname=${HOSTNAME}
|
||||
|
||||
# get hostname from dns
|
||||
if ( is_true ${USE_DNS} ); then
|
||||
# wait for dns registration
|
||||
sleep 1
|
||||
|
||||
nodename=$(discover_dns_hostname ${HOSTIP})
|
||||
|
||||
is_set ${nodename} \
|
||||
&& hostname=${nodename}
|
||||
fi
|
||||
|
||||
echo $hostname
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# discover hostname from dns with a reverse lookup
|
||||
discover_dns_hostname() {
|
||||
local hostip=$1
|
||||
|
||||
# try to get the hostname from dns
|
||||
local dnsname=$(drill -x ${hostip} \
|
||||
| grep PTR \
|
||||
| awk '{print $5}' \
|
||||
| grep -E "^[a-zA-Z0-9]+([-._]?[a-zA-Z0-9]+)*.[a-zA-Z]+\.$" \
|
||||
| cut -d '.' -f1 \
|
||||
| tail -1)
|
||||
|
||||
is_set ${dnsname} \
|
||||
&& echo ${dnsname}
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source "${EJABBERD_HOME}/scripts/lib/base_config.sh"
|
||||
source "${EJABBERD_HOME}/scripts/lib/config.sh"
|
||||
source "${EJABBERD_HOME}/scripts/lib/base_functions.sh"
|
||||
source "${EJABBERD_HOME}/scripts/lib/functions.sh"
|
||||
|
||||
|
||||
get_cluster_node_from_dns() {
|
||||
local cluster_host=$(drill ${DOMAINNAME} \
|
||||
| grep ${DOMAINNAME} \
|
||||
| grep -v ${HOSTIP} \
|
||||
| awk '{print $5}' \
|
||||
| grep -v "^$" \
|
||||
| head -1)
|
||||
echo $(discover_dns_hostname ${cluster_host})
|
||||
}
|
||||
|
||||
|
||||
file_exist ${FIRST_START_DONE_FILE} \
|
||||
&& exit 0
|
||||
|
||||
|
||||
join_cluster $(get_cluster_node_from_dns)
|
||||
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1 @@
|
||||
# simple docker-compose example
|
||||
@@ -0,0 +1,11 @@
|
||||
ejabberd:
|
||||
image: rroemhild/ejabberd
|
||||
ports:
|
||||
- 5222:5222
|
||||
- 5269:5269
|
||||
- 5280:5280
|
||||
environment:
|
||||
- ERLANG_NODE=ejabberd
|
||||
- XMPP_DOMAIN=example.com xyz.io
|
||||
- EJABBERD_ADMINS=admin@example.com
|
||||
- EJABBERD_USERS=admin@example.com:password4321 user1@xyz.io
|
||||
@@ -3,7 +3,7 @@ readonly HOSTNAME=$(hostname -f)
|
||||
readonly DOMAINNAME=$(hostname -d)
|
||||
|
||||
readonly ERLANGCOOKIEFILE="${EJABBERD_HOME}/.erlang.cookie"
|
||||
readonly EJABBERDCTL="/sbin/ejabberdctl"
|
||||
readonly EJABBERDCTL="/usr/local/sbin/ejabberdctl"
|
||||
readonly CONFIGFILE="${EJABBERD_HOME}/conf/ejabberd.yml"
|
||||
readonly CONFIGTEMPLATE="${EJABBERD_HOME}/conf/ejabberd.yml.tpl"
|
||||
readonly CTLCONFIGFILE="${EJABBERD_HOME}/conf/ejabberdctl.cfg"
|
||||
@@ -11,7 +11,7 @@ readonly CTLCONFIGTEMPLATE="${EJABBERD_HOME}/conf/ejabberdctl.cfg.tpl"
|
||||
readonly SSLCERTDIR="${EJABBERD_HOME}/ssl"
|
||||
readonly SSLCERTHOST="${SSLCERTDIR}/host.pem"
|
||||
readonly SSLDHPARAM="${SSLCERTDIR}/dh.pem"
|
||||
readonly LOGDIR="/var/log/ejabberd"
|
||||
readonly LOGDIR="/usr/local/var/log/ejabberd"
|
||||
readonly FIRST_START_DONE_FILE="/${EJABBERD_HOME}/first-start-done"
|
||||
readonly CLUSTER_NODE_FILE="/${EJABBERD_HOME}/cluster-done"
|
||||
|
||||
|
||||
+19
-8
@@ -111,8 +111,10 @@ hosts:
|
||||
## 'CERTFILE': "/path/to/xmpp.pem"
|
||||
## 'CIPHERS': "ECDH:DH:!3DES:!aNULL:!eNULL:!MEDIUM@STRENGTH"
|
||||
## 'TLSOPTS':
|
||||
## - "no_sslv2"
|
||||
## - "no_sslv3"
|
||||
## - "no_tlsv1"
|
||||
## - "no_tlsv1_1"
|
||||
## - "cipher_server_preference"
|
||||
## - "no_compression"
|
||||
## 'DHFILE': "/path/to/dhparams.pem" # generated with: openssl dhparam -out dhparams.pem 2048
|
||||
@@ -158,11 +160,11 @@ listen:
|
||||
ip: "::"
|
||||
module: ejabberd_http
|
||||
request_handlers:
|
||||
"/websocket": ejabberd_http_ws
|
||||
"/ws": ejabberd_http_ws
|
||||
"/bosh": mod_bosh
|
||||
"/api": mod_http_api
|
||||
## "/pub/archive": mod_http_fileserver
|
||||
web_admin: true
|
||||
http_bind: true
|
||||
## register: true
|
||||
captcha: true
|
||||
##
|
||||
@@ -231,7 +233,7 @@ listen:
|
||||
|
||||
##
|
||||
## s2s_use_starttls: Enable STARTTLS for S2S connections.
|
||||
## Allowed values are: false optional required required_trusted
|
||||
## Allowed values are: false, optional or required
|
||||
## You must specify a certificate file.
|
||||
##
|
||||
## s2s_use_starttls: required
|
||||
@@ -265,12 +267,12 @@ listen:
|
||||
## Outgoing S2S options
|
||||
##
|
||||
## Preferred address families (which to try first) and connect timeout
|
||||
## in milliseconds.
|
||||
## in seconds.
|
||||
##
|
||||
## outgoing_s2s_families:
|
||||
## - ipv4
|
||||
## - ipv6
|
||||
## outgoing_s2s_timeout: 10000
|
||||
## outgoing_s2s_timeout: 190
|
||||
|
||||
###. ==============
|
||||
###' AUTHENTICATION
|
||||
@@ -487,6 +489,8 @@ acl:
|
||||
loopback:
|
||||
ip:
|
||||
- "127.0.0.0/8"
|
||||
- "::1/128"
|
||||
- "::FFFF:127.0.0.1/128"
|
||||
|
||||
##
|
||||
## Bad XMPP servers
|
||||
@@ -589,14 +593,14 @@ api_permissions:
|
||||
who:
|
||||
- access:
|
||||
- allow:
|
||||
- ip: "127.0.0.1/8"
|
||||
- acl: loopback
|
||||
- acl: admin
|
||||
- oauth:
|
||||
- scope: "ejabberd:admin"
|
||||
- access:
|
||||
- allow:
|
||||
- ip: "127.0.0.1/8"
|
||||
- acl: admin
|
||||
- acl: loopback
|
||||
- acl: admin
|
||||
what:
|
||||
- "*"
|
||||
- "!stop"
|
||||
@@ -723,6 +727,8 @@ modules:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep" # pep requires mod_caps
|
||||
mod_push: {}
|
||||
mod_push_keepalive: {}
|
||||
## mod_register:
|
||||
##
|
||||
## Protect In-Band account registrations with CAPTCHA.
|
||||
@@ -762,6 +768,11 @@ modules:
|
||||
mod_time: {}
|
||||
mod_vcard:
|
||||
search: false
|
||||
mod_vcard_xupdate: {}
|
||||
## Convert all avatars posted by Android clients from WebP to JPEG
|
||||
mod_avatar:
|
||||
convert:
|
||||
webp: jpeg
|
||||
mod_version: {}
|
||||
mod_stream_mgmt: {}
|
||||
## Non-SASL Authentication (XEP-0078) is now disabled by default
|
||||
|
||||
+175
-306
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
# define default configuration
|
||||
POLL=true
|
||||
@@ -7,138 +7,93 @@ ERL_MAX_PORTS=32000
|
||||
ERL_PROCESSES=250000
|
||||
ERL_MAX_ETS_TABLES=1400
|
||||
FIREWALL_WINDOW=""
|
||||
INET_DIST_INTERFACE=""
|
||||
ERLANG_NODE=ejabberd@localhost
|
||||
|
||||
# define default environment variables
|
||||
SCRIPT_DIR=`cd ${0%/*} && pwd`
|
||||
ERL={{erl}}
|
||||
IEX={{bindir}}/iex
|
||||
EPMD={{epmd}}
|
||||
INSTALLUSER={{installuser}}
|
||||
ERL_LIBS={{libdir}}
|
||||
ERL="{{erl}}"
|
||||
IEX="{{bindir}}/iex"
|
||||
EPMD="{{epmd}}"
|
||||
INSTALLUSER="{{installuser}}"
|
||||
|
||||
# check the proper system user is used if defined
|
||||
if [ "$INSTALLUSER" != "" ] ; then
|
||||
EXEC_CMD="false"
|
||||
for GID in `id -G`; do
|
||||
if [ $GID -eq 0 ] ; then
|
||||
INSTALLUSER_HOME=$(getent passwd "$INSTALLUSER" | cut -d: -f6)
|
||||
if [ -n "$INSTALLUSER_HOME" ] && [ ! -d "$INSTALLUSER_HOME" ] ; then
|
||||
mkdir -p "$INSTALLUSER_HOME"
|
||||
chown "$INSTALLUSER" "$INSTALLUSER_HOME"
|
||||
fi
|
||||
EXEC_CMD="su $INSTALLUSER -c"
|
||||
# check the proper system user is used
|
||||
case $(id -un) in
|
||||
"$INSTALLUSER")
|
||||
EXEC_CMD="as_current_user"
|
||||
;;
|
||||
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
|
||||
done
|
||||
if [ `id -g` -eq `id -g $INSTALLUSER` ] ; then
|
||||
EXEC_CMD="bash -c"
|
||||
fi
|
||||
if [ "$EXEC_CMD" = "false" ] ; then
|
||||
echo "This command can only be run by root or the user $INSTALLUSER" >&2
|
||||
exit 4
|
||||
fi
|
||||
else
|
||||
EXEC_CMD="bash -c"
|
||||
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
|
||||
declare -a ARGS=()
|
||||
while [ $# -ne 0 ] ; do
|
||||
PARAM="$1"
|
||||
shift
|
||||
case $PARAM in
|
||||
--) break ;;
|
||||
--no-timeout) EJABBERD_NO_TIMEOUT="--no-timeout" ;;
|
||||
--node) ERLANG_NODE_ARG=$1 ; shift ;;
|
||||
--config-dir) ETC_DIR="$1" ; shift ;;
|
||||
--config) EJABBERD_CONFIG_PATH="$1" ; shift ;;
|
||||
--ctl-config) EJABBERDCTL_CONFIG_PATH="$1" ; shift ;;
|
||||
--logs) LOGS_DIR="$1" ; shift ;;
|
||||
--spool) SPOOL_DIR="$1" ; shift ;;
|
||||
*) ARGS=("${ARGS[@]}" "$PARAM") ;;
|
||||
for arg; do
|
||||
case $arg in
|
||||
-n|--node) ERLANG_NODE_ARG=$2; shift;;
|
||||
-s|--spool) SPOOL_DIR=$2; shift;;
|
||||
-l|--logs) LOGS_DIR=$2; shift;;
|
||||
-f|--config) EJABBERD_CONFIG_PATH=$2; shift;;
|
||||
-c|--ctl-config) EJABBERDCTL_CONFIG_PATH=$2; shift;;
|
||||
-d|--config-dir) ETC_DIR=$2; shift;;
|
||||
-t|--no-timeout) NO_TIMEOUT="--no-timeout";;
|
||||
--) :;;
|
||||
*) break;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Define ejabberd variable if they have not been defined from the command line
|
||||
if [ "$ETC_DIR" = "" ] ; then
|
||||
ETC_DIR={{sysconfdir}}/ejabberd
|
||||
fi
|
||||
if [ "$EJABBERDCTL_CONFIG_PATH" = "" ] ; then
|
||||
EJABBERDCTL_CONFIG_PATH=$ETC_DIR/ejabberdctl.cfg
|
||||
fi
|
||||
if [ -f "$EJABBERDCTL_CONFIG_PATH" ] ; then
|
||||
. "$EJABBERDCTL_CONFIG_PATH"
|
||||
fi
|
||||
if [ "$EJABBERD_CONFIG_PATH" = "" ] ; then
|
||||
EJABBERD_CONFIG_PATH=$ETC_DIR/ejabberd.yml
|
||||
fi
|
||||
if [ "$LOGS_DIR" = "" ] ; then
|
||||
LOGS_DIR={{localstatedir}}/log/ejabberd
|
||||
fi
|
||||
if [ "$SPOOL_DIR" = "" ] ; then
|
||||
SPOOL_DIR={{localstatedir}}/lib/ejabberd
|
||||
fi
|
||||
if [ "$EJABBERD_DOC_PATH" = "" ] ; then
|
||||
EJABBERD_DOC_PATH={{docdir}}
|
||||
fi
|
||||
if [ "$ERLANG_NODE_ARG" != "" ] ; then
|
||||
ERLANG_NODE=$ERLANG_NODE_ARG
|
||||
fi
|
||||
if [ "{{release}}" != "true" -a "$EJABBERD_BIN_PATH" = "" ] ; then
|
||||
EJABBERD_BIN_PATH={{libdir}}/ejabberd/priv/bin
|
||||
fi
|
||||
EJABBERD_LOG_PATH=$LOGS_DIR/ejabberd.log
|
||||
DATETIME=`date "+%Y%m%d-%H%M%S"`
|
||||
ERL_CRASH_DUMP=$LOGS_DIR/erl_crash_$DATETIME.dump
|
||||
ERL_INETRC=$ETC_DIR/inetrc
|
||||
# define ejabberd variables if not already defined from the command line
|
||||
: "${ETC_DIR:="{{sysconfdir}}/ejabberd"}"
|
||||
: "${LOGS_DIR:="{{localstatedir}}/log/ejabberd"}"
|
||||
: "${SPOOL_DIR:="{{localstatedir}}/lib/ejabberd"}"
|
||||
: "${EJABBERD_CONFIG_PATH:="$ETC_DIR/ejabberd.yml"}"
|
||||
: "${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR/ejabberdctl.cfg"}"
|
||||
[ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
|
||||
[ -n "$ERLANG_NODE_ARG" ] && ERLANG_NODE="$ERLANG_NODE_ARG"
|
||||
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && S="-s"
|
||||
: "${EJABBERD_DOC_PATH:="{{docdir}}"}"
|
||||
: "${EJABBERD_LOG_PATH:="$LOGS_DIR/ejabberd.log"}"
|
||||
|
||||
# define mnesia options
|
||||
MNESIA_OPTS="-mnesia dir \"\\\"$SPOOL_DIR\\\"\" $MNESIA_OPTIONS"
|
||||
# define erl parameters
|
||||
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
|
||||
KERNEL_OPTS=""
|
||||
if [ "$FIREWALL_WINDOW" != "" ] ; then
|
||||
KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
|
||||
if [ -n "$FIREWALL_WINDOW" ] ; then
|
||||
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="$(echo $INET_DIST_INTERFACE | sed 's/\./,/g')"
|
||||
if [ "$INET_DIST_INTERFACE" != "$INET_DIST_INTERFACE2" ] ; then
|
||||
INET_DIST_INTERFACE2="{$INET_DIST_INTERFACE2}"
|
||||
if [ -n "$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 [ -n "$INET_DIST_INTERFACE2" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2"
|
||||
fi
|
||||
KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_use_interface \"${INET_DIST_INTERFACE2}\""
|
||||
fi
|
||||
if [ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] ; then
|
||||
NAME="-sname"
|
||||
else
|
||||
NAME="-name"
|
||||
fi
|
||||
IEXNAME="-$NAME"
|
||||
ERL_LIBS={{libdir}}
|
||||
ERL_CRASH_DUMP="$LOGS_DIR"/erl_crash_$(date "+%Y%m%d-%H%M%S").dump
|
||||
ERL_INETRC="$ETC_DIR"/inetrc
|
||||
|
||||
# define ejabberd environment parameters
|
||||
if [ "$EJABBERD_CONFIG_PATH" != "${EJABBERD_CONFIG_PATH%.yml}" ] ; then
|
||||
rate=$(sed '/^[ ]*log_rate_limit/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
|
||||
rotate=$(sed '/^[ ]*log_rotate_size/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
|
||||
count=$(sed '/^[ ]*log_rotate_count/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
|
||||
date=$(sed '/^[ ]*log_rotate_date/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
|
||||
else
|
||||
rate=$(sed '/^[ ]*log_rate_limit/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
|
||||
rotate=$(sed '/^[ ]*log_rotate_size/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
|
||||
count=$(sed '/^[ ]*log_rotate_count/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
|
||||
date=$(sed '/^[ ]*log_rotate_date/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
|
||||
fi
|
||||
[ -z "$rate" ] || EJABBERD_OPTS="log_rate_limit $rate"
|
||||
[ -z "$rotate" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_size $rotate"
|
||||
[ -z "$count" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_count $count"
|
||||
[ -z "$date" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_date '$date'"
|
||||
[ -z "$EJABBERD_OPTS" ] || EJABBERD_OPTS="-ejabberd ${EJABBERD_OPTS}"
|
||||
|
||||
[ -d "$SPOOL_DIR" ] || $EXEC_CMD "mkdir -p $SPOOL_DIR"
|
||||
cd "$SPOOL_DIR"
|
||||
# define ejabberd parameters
|
||||
EJABBERD_OPTS="$EJABBERD_OPTS\
|
||||
$(sed '/^log_rate_limit/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
|
||||
$(sed '/^log_rotate_size/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
|
||||
$(sed '/^log_rotate_count/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
|
||||
$(sed '/^log_rotate_date/!d;s/:[ \t]*\(.[^ ]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")"
|
||||
[ -n "$EJABBERD_OPTS" ] && EJABBERD_OPTS="-ejabberd $EJABBERD_OPTS"
|
||||
EJABBERD_OPTS="-mnesia dir \"$SPOOL_DIR\" $MNESIA_OPTIONS $EJABBERD_OPTS -s ejabberd"
|
||||
|
||||
# export global variables
|
||||
export EJABBERD_CONFIG_PATH
|
||||
export EJABBERD_LOG_PATH
|
||||
export EJABBERD_BIN_PATH
|
||||
export EJABBERD_DOC_PATH
|
||||
export EJABBERD_PID_PATH
|
||||
export ERL_CRASH_DUMP
|
||||
@@ -150,116 +105,26 @@ export CONTRIB_MODULES_PATH
|
||||
export CONTRIB_MODULES_CONF_DIR
|
||||
export ERL_LIBS
|
||||
|
||||
shell_escape_str()
|
||||
# run command either directly or via su $INSTALLUSER
|
||||
exec_cmd()
|
||||
{
|
||||
if test $# -eq 0; then
|
||||
printf '"" '
|
||||
else
|
||||
shell_escape "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
shell_escape()
|
||||
{
|
||||
local RES=()
|
||||
for i in "$@"; do
|
||||
if test -z "$i"; then
|
||||
printf '"" '
|
||||
else
|
||||
printf '%q ' "$i"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# start server
|
||||
start()
|
||||
{
|
||||
check_start
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$ERLANG_NODE\"` \
|
||||
-noinput -detached \
|
||||
$MNESIA_OPTS \
|
||||
$KERNEL_OPTS \
|
||||
$EJABBERD_OPTS \
|
||||
-s ejabberd \
|
||||
$ERLANG_OPTS \
|
||||
`shell_escape \"${ARGS[@]}\" \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
}
|
||||
|
||||
# attach to server
|
||||
debug()
|
||||
{
|
||||
debugwarning
|
||||
NID=$(uid debug)
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
|
||||
-remsh $ERLANG_NODE \
|
||||
-hidden \
|
||||
$KERNEL_OPTS \
|
||||
$ERLANG_OPTS \
|
||||
`shell_escape \"${ARGS[@]}\" \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
}
|
||||
|
||||
# attach to server using Elixir
|
||||
iexdebug()
|
||||
{
|
||||
debugwarning
|
||||
# Elixir shell is hidden as default
|
||||
NID=$(uid debug)
|
||||
CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"$NID\"` \
|
||||
-remsh $ERLANG_NODE \
|
||||
--erl `shell_escape \"$KERNEL_OPTS\"` \
|
||||
--erl `shell_escape \"$ERLANG_OPTS\"` \
|
||||
--erl `shell_escape \"${ARGS[@]}\"` \
|
||||
--erl `shell_escape_str \"$@\"`"
|
||||
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
|
||||
}
|
||||
|
||||
# start interactive server
|
||||
live()
|
||||
{
|
||||
livewarning
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"${ERLANG_NODE}\"` \
|
||||
$MNESIA_OPTS \
|
||||
$KERNEL_OPTS \
|
||||
$EJABBERD_OPTS \
|
||||
-s ejabberd \
|
||||
$ERLANG_OPTS \
|
||||
`shell_escape \"${ARGS[@]}\" \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
}
|
||||
|
||||
# start interactive server with Elixir
|
||||
iexlive()
|
||||
{
|
||||
livewarning
|
||||
echo $@
|
||||
CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"${ERLANG_NODE}\"` \
|
||||
--erl \"-mnesia dir \\\"$SPOOL_DIR\\\"\" \
|
||||
--erl \"`shell_escape \"$KERNEL_OPTS\"`\" \
|
||||
--erl \"`shell_escape \"$EJABBERD_OPTS\"`\" \
|
||||
--app ejabberd \
|
||||
--erl `shell_escape \"$ERLANG_OPTS\"` \
|
||||
--erl `shell_escape \"${ARGS[@]}\"` \
|
||||
--erl `shell_escape_str \"$@\"`"
|
||||
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
|
||||
}
|
||||
|
||||
# start server in the foreground
|
||||
foreground()
|
||||
{
|
||||
check_start
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$ERLANG_NODE\"` \
|
||||
-noinput \
|
||||
$MNESIA_OPTS \
|
||||
$KERNEL_OPTS \
|
||||
$EJABBERD_OPTS \
|
||||
-s ejabberd \
|
||||
$ERLANG_OPTS \
|
||||
`shell_escape \"${ARGS[@]}\" \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
case $EXEC_CMD in
|
||||
as_install_user) su -c '"$0" "$@"' "$INSTALLUSER" -- "$@" ;;
|
||||
as_current_user) "$@" ;;
|
||||
esac
|
||||
}
|
||||
exec_erl()
|
||||
{
|
||||
NODE=$1; shift
|
||||
exec_cmd "$ERL" ${S:--}name "$NODE" $ERLANG_OPTS "$@"
|
||||
}
|
||||
exec_iex()
|
||||
{
|
||||
NODE=$1; shift
|
||||
exec_cmd "$IEX" -${S:--}name "$NODE" --erl "$ERLANG_OPTS" "$@"
|
||||
}
|
||||
|
||||
# usage
|
||||
debugwarning()
|
||||
{
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
@@ -279,14 +144,13 @@ debugwarning()
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press return to continue"
|
||||
read foo
|
||||
read -r
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
livewarning()
|
||||
{
|
||||
check_start
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo ""
|
||||
@@ -303,37 +167,11 @@ livewarning()
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press return to continue"
|
||||
read foo
|
||||
read -r
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
etop()
|
||||
{
|
||||
NID=$(uid top)
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME $NID \
|
||||
-hidden -s etop -s erlang halt -output text -node $ERLANG_NODE"
|
||||
}
|
||||
|
||||
ping()
|
||||
{
|
||||
[ -z "$1" ] && PEER=${ERLANG_NODE} || PEER=$1
|
||||
if [ "$PEER" = "${PEER%.*}" ] ; then
|
||||
PING_NAME="-sname"
|
||||
PING_NODE=$(hostname -s)
|
||||
else
|
||||
PING_NAME="-name"
|
||||
PING_NODE=$(hostname)
|
||||
fi
|
||||
NID=$(uid ping ${PING_NODE})
|
||||
$EXEC_CMD "$ERL \
|
||||
$PING_NAME $NID \
|
||||
-hidden $KERNEL_OPTS $ERLANG_OPTS \
|
||||
-eval 'io:format(\"~p~n\",[net_adm:ping('\"'\"'$PEER'\"'\"')])' \
|
||||
-s erlang halt -output text -noinput"
|
||||
}
|
||||
|
||||
help()
|
||||
{
|
||||
echo ""
|
||||
@@ -355,33 +193,16 @@ help()
|
||||
echo ""
|
||||
}
|
||||
|
||||
# common control function
|
||||
ctl()
|
||||
{
|
||||
NID=$(uid ctl)
|
||||
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
|
||||
-noinput -hidden $KERNEL_OPTS -s ejabberd_ctl \
|
||||
-extra `shell_escape \"$ERLANG_NODE\"` $EJABBERD_NO_TIMEOUT \
|
||||
`shell_escape \"$@\"`"
|
||||
$EXEC_CMD "$CMD"
|
||||
result=$?
|
||||
case $result in
|
||||
2) help;;
|
||||
3) help;;
|
||||
*) :;;
|
||||
esac
|
||||
return $result
|
||||
}
|
||||
|
||||
# dynamic node name helper
|
||||
uid()
|
||||
{
|
||||
uuid=$(uuidgen 2>/dev/null)
|
||||
[ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && uuid=$(</proc/sys/kernel/random/uuid)
|
||||
[ -z "$uuid" ] && uuid=$(printf "%X" $RANDOM$(date +%M%S)$$)
|
||||
[ -z "$uuid" ] && [ -f /proc/sys/kernel/random/uuid ] && uuid=$(cat /proc/sys/kernel/random/uuid)
|
||||
[ -z "$uuid" ] && uuid=$(printf "%X" "${RANDOM:-$$}$(date +%M%S)")
|
||||
uuid=${uuid%%-*}
|
||||
[ $# -eq 0 ] && echo ${uuid}-${ERLANG_NODE}
|
||||
[ $# -eq 1 ] && echo ${uuid}-${1}-${ERLANG_NODE}
|
||||
[ $# -eq 2 ] && echo ${uuid}-${1}@${2}
|
||||
[ $# -eq 0 ] && echo "${uuid}-${ERLANG_NODE}"
|
||||
[ $# -eq 1 ] && echo "${uuid}-${1}-${ERLANG_NODE}"
|
||||
[ $# -eq 2 ] && echo "${uuid}-${1}@${2}"
|
||||
}
|
||||
|
||||
# stop epmd if there is no other running node
|
||||
@@ -391,56 +212,104 @@ stop_epmd()
|
||||
}
|
||||
|
||||
# make sure node not already running and node name unregistered
|
||||
# if all ok, ensure runtime directory exists and make it current directory
|
||||
check_start()
|
||||
{
|
||||
"$EPMD" -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
|
||||
ps ux | grep -v grep | grep -q " $ERLANG_NODE " && {
|
||||
pgrep -f "$ERLANG_NODE" >/dev/null && {
|
||||
echo "ERROR: The ejabberd node '$ERLANG_NODE' is already running."
|
||||
exit 4
|
||||
} || {
|
||||
ps ux | grep -v grep | grep -q beam && {
|
||||
echo "ERROR: The ejabberd node '$ERLANG_NODE' is registered,"
|
||||
echo " but no related beam process has been found."
|
||||
echo "Shutdown all other erlang nodes, and call 'epmd -kill'."
|
||||
exit 5
|
||||
} || {
|
||||
"$EPMD" -kill >/dev/null
|
||||
}
|
||||
}
|
||||
pgrep beam >/dev/null && {
|
||||
echo "ERROR: The ejabberd node '$ERLANG_NODE' is registered,"
|
||||
echo " but no related beam process has been found."
|
||||
echo "Shutdown all other erlang nodes, and call 'epmd -kill'."
|
||||
exit 5
|
||||
}
|
||||
"$EPMD" -kill >/dev/null
|
||||
}
|
||||
}
|
||||
|
||||
# allow sync calls
|
||||
wait_for_status()
|
||||
wait_status()
|
||||
{
|
||||
# args: status try delay
|
||||
# return: 0 OK, 1 KO
|
||||
timeout=$2
|
||||
timeout="$2"
|
||||
status=4
|
||||
while [ $status -ne $1 ] ; do
|
||||
sleep $3
|
||||
timeout=`expr $timeout - 1`
|
||||
[ $timeout -eq 0 ] && {
|
||||
status=$1
|
||||
} || {
|
||||
ctl status > /dev/null
|
||||
status=$?
|
||||
}
|
||||
while [ "$status" -ne "$1" ] ; do
|
||||
sleep "$3"
|
||||
timeout=$((timeout - 1))
|
||||
if [ $timeout -eq 0 ] ; then
|
||||
status="$1"
|
||||
else
|
||||
exec_erl "$(uid ctl)" -hidden -noinput -s ejabberd_ctl \
|
||||
-extra "$ERLANG_NODE" $NO_TIMEOUT status > /dev/null
|
||||
status="$?"
|
||||
fi
|
||||
done
|
||||
[ $timeout -eq 0 ] && return 1 || return 0
|
||||
[ $timeout -gt 0 ]
|
||||
}
|
||||
|
||||
# main handler
|
||||
case "${ARGS[0]}" in
|
||||
'start') start;;
|
||||
'debug') debug;;
|
||||
'iexdebug') iexdebug;;
|
||||
'live') live;;
|
||||
'iexlive') iexlive;;
|
||||
'foreground') foreground;;
|
||||
'ping'*) ping ${ARGS[1]};;
|
||||
'etop') etop;;
|
||||
'started') wait_for_status 0 30 2;; # wait 30x2s before timeout
|
||||
'stopped') wait_for_status 3 30 2 && stop_epmd;; # wait 30x2s before timeout
|
||||
*) ctl "${ARGS[@]}";;
|
||||
# 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)
|
||||
check_start
|
||||
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -noinput -detached
|
||||
;;
|
||||
foreground)
|
||||
check_start
|
||||
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -noinput
|
||||
;;
|
||||
live)
|
||||
livewarning
|
||||
check_start
|
||||
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS
|
||||
;;
|
||||
debug)
|
||||
debugwarning
|
||||
exec_erl "$(uid debug)" -hidden -remsh "$ERLANG_NODE"
|
||||
;;
|
||||
etop)
|
||||
exec_erl "$(uid top)" -hidden -node "$ERLANG_NODE" -s etop \
|
||||
-s erlang halt -output text
|
||||
;;
|
||||
iexdebug)
|
||||
debugwarning
|
||||
exec_iex "$(uid debug)" --remsh "$ERLANG_NODE"
|
||||
;;
|
||||
iexlive)
|
||||
livewarning
|
||||
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS" --app ejabberd
|
||||
;;
|
||||
ping)
|
||||
PEER=${2:-$ERLANG_NODE}
|
||||
[ "$PEER" = "${PEER%.*}" ] && PS="-s"
|
||||
exec_cmd "$ERL" ${PS:--}name "$(uid ping "$(hostname $PS)")" $ERLANG_OPTS \
|
||||
-noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"'$PEER'"')])' \
|
||||
-s erlang halt -output text
|
||||
;;
|
||||
started)
|
||||
wait_status 0 30 2 # wait 30x2s before timeout
|
||||
;;
|
||||
stopped)
|
||||
wait_status 3 30 2 && stop_epmd # wait 30x2s before timeout
|
||||
;;
|
||||
*)
|
||||
exec_erl "$(uid ctl)" -hidden -noinput -s ejabberd_ctl \
|
||||
-extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
|
||||
result=$?
|
||||
case $result in
|
||||
2|3) help;;
|
||||
*) :;;
|
||||
esac
|
||||
exit $result
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -47,3 +47,5 @@
|
||||
|
||||
-define(HEADER(CType),
|
||||
[CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
|
||||
|
||||
-define(BOSH_CACHE, bosh_cache).
|
||||
|
||||
@@ -33,9 +33,9 @@
|
||||
|
||||
-define(SQL_DIR, filename:join(["priv", "sql"])).
|
||||
|
||||
-define(CONFIG_PATH, <<"ejabberd.cfg">>).
|
||||
-define(CONFIG_PATH, <<"ejabberd.yml">>).
|
||||
|
||||
-define(LOG_PATH, <<"ejabberd.log">>).
|
||||
-define(LOG_PATH, "ejabberd.log").
|
||||
|
||||
-define(EJABBERD_URI, <<"http://www.process-one.net/en/ejabberd/">>).
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
-define(ROUTES_CACHE, routes_cache).
|
||||
|
||||
-type local_hint() :: integer() | {apply, atom(), atom()}.
|
||||
|
||||
-record(route, {domain :: binary() | '_',
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
-ifndef(EJABBERD_SM_HRL).
|
||||
-define(EJABBERD_SM_HRL, true).
|
||||
|
||||
-define(SM_CACHE, sm_cache).
|
||||
|
||||
-record(session, {sid, usr, us, priority, info = []}).
|
||||
-record(session_counter, {vhost, count}).
|
||||
-type sid() :: {erlang:timestamp(), pid()}.
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-type matchspec_atom() :: '_' | '$1' | '$2' | '$3'.
|
||||
-type matchspec_atom() :: '_' | '$1' | '$2' | '$3' | '$4'.
|
||||
-record(carboncopy, {us :: {binary(), binary()} | matchspec_atom(),
|
||||
resource :: binary() | matchspec_atom(),
|
||||
version :: binary() | matchspec_atom()}).
|
||||
version :: binary() | matchspec_atom(),
|
||||
node = node() :: node() | matchspec_atom()}).
|
||||
|
||||
-define(CARBONCOPY_CACHE, carboncopy_cache).
|
||||
|
||||
@@ -38,11 +38,3 @@
|
||||
-type listitem_type() :: none | jid | group | subscription.
|
||||
-type listitem_value() :: none | both | from | to | jid:ljid() | binary().
|
||||
-type listitem_action() :: allow | deny.
|
||||
|
||||
-record(userlist, {name = none :: none | binary(),
|
||||
list = [] :: [listitem()],
|
||||
needdb = false :: boolean()}).
|
||||
|
||||
-type userlist() :: #userlist{}.
|
||||
|
||||
-export_type([userlist/0]).
|
||||
|
||||
@@ -104,13 +104,6 @@
|
||||
).
|
||||
%% @type affiliation() = 'none' | 'owner' | 'publisher' | 'publish-only' | 'member' | 'outcast'.
|
||||
|
||||
-type(subscription() :: 'none'
|
||||
| 'pending'
|
||||
| 'unconfigured'
|
||||
| 'subscribed'
|
||||
).
|
||||
%% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'.
|
||||
|
||||
-type(accessModel() :: 'open'
|
||||
| 'presence'
|
||||
| 'roster'
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-define(T(S), <<S>>).
|
||||
@@ -3,18 +3,18 @@ defmodule Ejabberd.Mixfile do
|
||||
|
||||
def project do
|
||||
[app: :ejabberd,
|
||||
version: "17.03.0",
|
||||
description: description,
|
||||
elixir: "~> 1.3",
|
||||
version: "17.9.0",
|
||||
description: description(),
|
||||
elixir: "~> 1.4",
|
||||
elixirc_paths: ["lib"],
|
||||
compile_path: ".",
|
||||
compilers: [:asn1] ++ Mix.compilers,
|
||||
erlc_options: erlc_options,
|
||||
erlc_options: erlc_options(),
|
||||
erlc_paths: ["asn1", "src"],
|
||||
# Elixir tests are starting the part of ejabberd they need
|
||||
aliases: [test: "test --no-start"],
|
||||
package: package,
|
||||
deps: deps]
|
||||
package: package(),
|
||||
deps: deps()]
|
||||
end
|
||||
|
||||
def description do
|
||||
@@ -29,7 +29,7 @@ defmodule Ejabberd.Mixfile do
|
||||
included_applications: [:lager, :mnesia, :inets, :p1_utils, :cache_tab,
|
||||
:fast_tls, :stringprep, :fast_xml, :xmpp,
|
||||
:stun, :fast_yaml, :esip, :jiffy, :p1_oauth2]
|
||||
++ cond_apps]
|
||||
++ cond_apps()]
|
||||
end
|
||||
|
||||
defp erlc_options do
|
||||
@@ -39,7 +39,7 @@ defmodule Ejabberd.Mixfile do
|
||||
end
|
||||
|
||||
defp deps do
|
||||
[{:lager, "~> 3.2"},
|
||||
[{:lager, "~> 3.4.0"},
|
||||
{:p1_utils, "~> 1.0"},
|
||||
{:fast_xml, "~> 1.1"},
|
||||
{:xmpp, "~> 1.1"},
|
||||
@@ -53,7 +53,7 @@ defmodule Ejabberd.Mixfile do
|
||||
{:p1_oauth2, "~> 0.6.1"},
|
||||
{:distillery, "~> 1.0"},
|
||||
{:ex_doc, ">= 0.0.0", only: :dev}]
|
||||
++ cond_deps
|
||||
++ cond_deps()
|
||||
end
|
||||
|
||||
defp deps_include(deps) do
|
||||
@@ -89,7 +89,7 @@ defmodule Ejabberd.Mixfile do
|
||||
app
|
||||
end
|
||||
|
||||
def package do
|
||||
defp package do
|
||||
[# These are the default files included in the package
|
||||
files: ["lib", "src", "priv", "mix.exs", "include", "README.md", "COPYING"],
|
||||
maintainers: ["ProcessOne"],
|
||||
@@ -100,7 +100,7 @@ defmodule Ejabberd.Mixfile do
|
||||
"ProcessOne" => "http://www.process-one.net/"}]
|
||||
end
|
||||
|
||||
def vars do
|
||||
defp vars do
|
||||
case :file.consult("vars.config") do
|
||||
{:ok,config} -> config
|
||||
_ -> [zlib: true, iconv: true]
|
||||
@@ -108,7 +108,7 @@ defmodule Ejabberd.Mixfile do
|
||||
end
|
||||
|
||||
defp config(key) do
|
||||
case vars[key] do
|
||||
case vars()[key] do
|
||||
nil -> false
|
||||
value -> value
|
||||
end
|
||||
@@ -142,7 +142,7 @@ defmodule Mix.Tasks.Compile.Asn1 do
|
||||
end)
|
||||
end
|
||||
|
||||
def manifests, do: [manifest]
|
||||
def manifests, do: [manifest()]
|
||||
defp manifest, do: Path.join(Mix.Project.manifest_path, @manifest)
|
||||
|
||||
def clean, do: Erlang.clean(manifest())
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
%{"cache_tab": {:hex, :cache_tab, "1.0.7", "8e2c958c0c2178e6c015aa7340c761521dc71b642192a5a7887f4aeffa29c7b1", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"distillery": {:hex, :distillery, "1.2.2", "d5a52920cbe2378c8a21dfc83b526b4225944b9dce7bf170fe5f5cddda81ffb3", [:mix], []},
|
||||
"earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []},
|
||||
%{"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.11", "eeb0b1cbb64d56201dd6abb09126afab1e96da2418e833cba6c55f890f2f6649", [:rebar3], [{:fast_tls, "1.0.11", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}, {:stun, "1.0.10", [hex: :stun, optional: false]}]},
|
||||
"ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
|
||||
"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.11", "8ccff4b68e6bb79b91c689da8cf92ec1006a575d2b6a09ac1ed5f9bf4724a39a", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_xml": {:hex, :fast_xml, "1.1.22", "7eb81a738218541208fa3a126ee36197fb0346852d8c12ad678039e539e019df", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"fast_yaml": {:hex, :fast_yaml, "1.0.9", "1bf41a576d3eedcb690499350994932340908b4968832adcec4b55152d4e5f20", [:rebar3], [{:p1_utils, "1.0.7", [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.4", "faa4ac6755567a2806c7bee2cf26fc318016794ffc25481f7230db5f9d025dd8", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"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.2.4", "a6deb74dae7927f46bd13255268308ef03eb206ec784a94eaf7c1c0f3b811615", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]},
|
||||
"p1_mysql": {:hex, :p1_mysql, "1.0.2", "893a99415f98ce8b6ad014ef950d4e878895787b6c8333587f1e506f831571e0", [: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.2", "27d3137e0b0098808d9c60bf197344669ed1107ed47ce4af2254099a62ccc27e", [:rebar3], []},
|
||||
"p1_utils": {:hex, :p1_utils, "1.0.7", "030adbce8935f1b87aaedfdb037d3127cc671ee3e1904b394e6dde9e449d6979", [: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.8", "870d72db031796177261af88d1e6eb081dc314ad217377d441e5ea3c8504a310", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"stun": {:hex, :stun, "1.0.10", "9fa83d4c5a76ca5ed3b536852ea00f3fbd2023241559ed6cb23a4ada62183b44", [:rebar3], [{:fast_tls, "1.0.11", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
|
||||
"xmpp": {:hex, :xmpp, "1.1.9", "3548dc09faa414ee437c5db53a24af691724cb984b73af832d547c83f50313b9", [:rebar3], [{:fast_xml, "1.1.22", [hex: :fast_xml, optional: false]}, {:stringprep, "1.0.8", [hex: :stringprep, optional: false]}]}}
|
||||
"stringprep": {:hex, :stringprep, "1.0.9", "9182ba39931cd1db528b8883cad0d63530abe2bf21835d26cec2f9af8bc00be0", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, 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]}]}}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
-module(override_deps_versions).
|
||||
-export([preprocess/2, 'pre_update-deps'/2, new_replace/1, new_replace/0]).
|
||||
|
||||
preprocess(Config, _Dirs) ->
|
||||
update_deps(Config).
|
||||
|
||||
update_deps(Config) ->
|
||||
LocalDeps = rebar_config:get_local(Config, deps, []),
|
||||
TopDeps = case rebar_config:get_xconf(Config, top_deps, []) of
|
||||
[] -> LocalDeps;
|
||||
Val -> Val
|
||||
end,
|
||||
Config2 = rebar_config:set_xconf(Config, top_deps, TopDeps),
|
||||
NewDeps = lists:map(fun({Name, _, _} = Dep) ->
|
||||
case lists:keyfind(Name, 1, TopDeps) of
|
||||
false -> Dep;
|
||||
TopDep -> TopDep
|
||||
end
|
||||
end, LocalDeps),
|
||||
%io:format("LD ~p~n", [LocalDeps]),
|
||||
%io:format("TD ~p~n", [TopDeps]),
|
||||
|
||||
Config3 = rebar_config:set(Config2, deps, NewDeps),
|
||||
{ok, Config3, []}.
|
||||
|
||||
|
||||
'pre_update-deps'(Config, _Dirs) ->
|
||||
{ok, Config2, _} = update_deps(Config),
|
||||
|
||||
case code:is_loaded(old_rebar_config) of
|
||||
false ->
|
||||
{_, Beam, _} = code:get_object_code(rebar_config),
|
||||
NBeam = rename(Beam, old_rebar_config),
|
||||
code:load_binary(old_rebar_config, "blank", NBeam),
|
||||
replace_mod(Beam);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{ok, Config2}.
|
||||
|
||||
new_replace() ->
|
||||
old_rebar_config:new().
|
||||
new_replace(Config) ->
|
||||
NC = old_rebar_config:new(Config),
|
||||
{ok, Conf, _} = update_deps(NC),
|
||||
Conf.
|
||||
|
||||
replace_mod(Beam) ->
|
||||
{ok, {_, [{exports, Exports}]}} = beam_lib:chunks(Beam, [exports]),
|
||||
Funcs = lists:filtermap(
|
||||
fun({module_info, _}) ->
|
||||
false;
|
||||
({Name, Arity}) ->
|
||||
Args = args(Arity),
|
||||
Call = case Name of
|
||||
new ->
|
||||
[erl_syntax:application(
|
||||
erl_syntax:abstract(override_deps_versions),
|
||||
erl_syntax:abstract(new_replace),
|
||||
Args)];
|
||||
_ ->
|
||||
[erl_syntax:application(
|
||||
erl_syntax:abstract(old_rebar_config),
|
||||
erl_syntax:abstract(Name),
|
||||
Args)]
|
||||
end,
|
||||
{true, erl_syntax:function(erl_syntax:abstract(Name),
|
||||
[erl_syntax:clause(Args, none,
|
||||
Call)])}
|
||||
end, Exports),
|
||||
Forms0 = ([erl_syntax:attribute(erl_syntax:abstract(module),
|
||||
[erl_syntax:abstract(rebar_config)])]
|
||||
++ Funcs),
|
||||
Forms = [erl_syntax:revert(Form) || Form <- Forms0],
|
||||
%io:format("--------------------------------------------------~n"
|
||||
% "~s~n",
|
||||
% [[erl_pp:form(Form) || Form <- Forms]]),
|
||||
{ok, Mod, Bin} = compile:forms(Forms, [report, export_all]),
|
||||
code:purge(rebar_config),
|
||||
{module, Mod} = code:load_binary(rebar_config, "mock", Bin).
|
||||
|
||||
|
||||
args(0) ->
|
||||
[];
|
||||
args(N) ->
|
||||
[arg(N) | args(N-1)].
|
||||
|
||||
arg(N) ->
|
||||
erl_syntax:variable(list_to_atom("A"++integer_to_list(N))).
|
||||
|
||||
rename(BeamBin0, Name) ->
|
||||
BeamBin = replace_in_atab(BeamBin0, Name),
|
||||
update_form_size(BeamBin).
|
||||
|
||||
%% Replace the first atom of the atom table with the new name
|
||||
replace_in_atab(<<"Atom", CnkSz0:32, Cnk:CnkSz0/binary, Rest/binary>>, Name) ->
|
||||
replace_first_atom(<<"Atom">>, Cnk, CnkSz0, Rest, latin1, Name);
|
||||
replace_in_atab(<<"AtU8", CnkSz0:32, Cnk:CnkSz0/binary, Rest/binary>>, Name) ->
|
||||
replace_first_atom(<<"AtU8">>, Cnk, CnkSz0, Rest, unicode, Name);
|
||||
replace_in_atab(<<C, Rest/binary>>, Name) ->
|
||||
<<C, (replace_in_atab(Rest, Name))/binary>>.
|
||||
|
||||
replace_first_atom(CnkName, Cnk, CnkSz0, Rest, Encoding, Name) ->
|
||||
<<NumAtoms:32, NameSz0:8, _Name0:NameSz0/binary, CnkRest/binary>> = Cnk,
|
||||
NumPad0 = num_pad_bytes(CnkSz0),
|
||||
<<_:NumPad0/unit:8, NextCnks/binary>> = Rest,
|
||||
NameBin = atom_to_binary(Name, Encoding),
|
||||
NameSz = byte_size(NameBin),
|
||||
CnkSz = CnkSz0 + NameSz - NameSz0,
|
||||
NumPad = num_pad_bytes(CnkSz),
|
||||
<<CnkName/binary, CnkSz:32, NumAtoms:32, NameSz:8, NameBin:NameSz/binary,
|
||||
CnkRest/binary, 0:NumPad/unit:8, NextCnks/binary>>.
|
||||
|
||||
|
||||
%% Calculate the number of padding bytes that have to be added for the
|
||||
%% BinSize to be an even multiple of ?beam_num_bytes_alignment.
|
||||
num_pad_bytes(BinSize) ->
|
||||
case 4 - (BinSize rem 4) of
|
||||
4 -> 0;
|
||||
N -> N
|
||||
end.
|
||||
|
||||
%% Update the size within the top-level form
|
||||
update_form_size(<<"FOR1", _OldSz:32, Rest/binary>> = Bin) ->
|
||||
Sz = size(Bin) - 8,
|
||||
<<"FOR1", Sz:32, Rest/binary>>.
|
||||
@@ -0,0 +1,32 @@
|
||||
-module(override_opts).
|
||||
-export([preprocess/2]).
|
||||
|
||||
override_opts(override, Config, Opts) ->
|
||||
lists:foldl(fun({Opt, Value}, Conf) ->
|
||||
rebar_config:set(Conf, Opt, Value)
|
||||
end, Config, Opts);
|
||||
override_opts(add, Config, Opts) ->
|
||||
lists:foldl(fun({Opt, Value}, Conf) ->
|
||||
V = rebar_config:get_local(Conf, Opt, []),
|
||||
rebar_config:set(Conf, Opt, [Value | V])
|
||||
end, Config, Opts).
|
||||
|
||||
preprocess(Config, _Dirs) ->
|
||||
Overrides = rebar_config:get_local(Config, overrides, []),
|
||||
TopOverrides = case rebar_config:get_xconf(Config, top_overrides, []) of
|
||||
[] -> Overrides;
|
||||
Val -> Val
|
||||
end,
|
||||
Config2 = rebar_config:set_xconf(Config, top_overrides, TopOverrides),
|
||||
Config3 = case rebar_app_utils:load_app_file(Config2, _Dirs) of
|
||||
{ok, C, AppName, _AppData} ->
|
||||
lists:foldl(fun({Type, AppName2, Opts}, Conf1) when
|
||||
AppName2 == AppName ->
|
||||
override_opts(Type, Conf1, Opts);
|
||||
(_, Conf2) ->
|
||||
Conf2
|
||||
end, C, TopOverrides);
|
||||
_ ->
|
||||
Config2
|
||||
end,
|
||||
{ok, Config3, []}.
|
||||
+33
-25
@@ -18,39 +18,41 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.2.1"}}},
|
||||
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.8"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.7"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.11"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.8"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.21"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.9"}}},
|
||||
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.10"}}},
|
||||
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.11"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.9"}}},
|
||||
{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.10"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.11"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.16"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.10"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.24"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.15"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.11"}}},
|
||||
{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.15"}}}},
|
||||
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.16"}}}},
|
||||
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
|
||||
{tag, "1.0.2"}}}},
|
||||
{tag, "1.0.4"}}}},
|
||||
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
|
||||
{tag, "1.1.2"}}}},
|
||||
{tag, "1.1.4"}}}},
|
||||
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
|
||||
{tag, "1.1.5"}}}},
|
||||
{if_var_true, pam, {epam, ".*", {git, "https://github.com/processone/epam",
|
||||
{tag, "1.0.2"}}}},
|
||||
{tag, "1.0.3"}}}},
|
||||
{if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
|
||||
{tag, "1.0.2"}}}},
|
||||
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client",
|
||||
{tag, "2.4.1"}}}},
|
||||
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/processone/riak-erlang-client.git",
|
||||
{tag, {if_version_above, "19", "develop", "2.5.3"}}}}},
|
||||
{if_var_true, graphics, {eimp, ".*", {git, "https://github.com/processone/eimp.git", {tag, "1.0.1"}}}},
|
||||
%% Elixir support, needed to run tests
|
||||
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
|
||||
{tag, {if_version_above, "17", "v1.2.6", "v1.1.1"}}}}},
|
||||
{tag, {if_version_above, "17", "v1.4.4", "v1.1.1"}}}}},
|
||||
%% TODO: When modules are fully migrated to new structure and mix, we will not need anymore rebar_elixir_plugin
|
||||
{if_not_rebar3, {if_var_true, elixir, {rebar_elixir_plugin, ".*",
|
||||
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}}},
|
||||
{if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv",
|
||||
{tag, "1.0.4"}}}},
|
||||
{tag, "1.0.6"}}}},
|
||||
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
|
||||
{tag, "0.8.4"}}}},
|
||||
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
|
||||
@@ -64,18 +66,19 @@
|
||||
stringprep,
|
||||
fast_xml,
|
||||
esip,
|
||||
luerl,
|
||||
stun,
|
||||
fast_yaml,
|
||||
xmpp,
|
||||
p1_utils,
|
||||
p1_mysql,
|
||||
p1_pgsql,
|
||||
p1_oauth2,
|
||||
epam,
|
||||
ezlib,
|
||||
eimp,
|
||||
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"},
|
||||
@@ -84,12 +87,17 @@
|
||||
{i, "deps/p1_utils/include"},
|
||||
{if_var_false, debug, no_debug_info},
|
||||
{if_var_true, debug, debug_info},
|
||||
{if_var_true, sip, {d, 'SIP'}},
|
||||
{if_var_true, stun, {d, 'STUN'}},
|
||||
{if_var_true, graphics, {d, 'GRAPHICS'}},
|
||||
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
|
||||
{if_var_match, db_type, mssql, {d, 'mssql'}},
|
||||
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
|
||||
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}},
|
||||
{if_version_above, "18", {d, 'STRONG_RAND_BYTES'}},
|
||||
{if_version_above, "17", {d, 'GB_SETS_ITERATOR_FROM'}},
|
||||
{if_have_fun, {crypto, strong_rand_bytes, 1}, {d, 'STRONG_RAND_BYTES'}},
|
||||
{if_have_fun, {rand, uniform, 1}, {d, 'RAND_UNIFORM'}},
|
||||
{if_have_fun, {gb_sets, iterator_from, 2}, {d, 'GB_SETS_ITERATOR_FROM'}},
|
||||
{if_have_fun, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}},
|
||||
{if_var_true, hipe, native},
|
||||
{src_dirs, [asn1, src,
|
||||
{if_var_true, tools, tools},
|
||||
@@ -99,7 +107,7 @@
|
||||
|
||||
{if_rebar3, {plugins, [rebar3_hex, {provider_asn1, "0.2.0"}]}}.
|
||||
{if_not_rebar3, {plugins, [
|
||||
deps_erl_opts,
|
||||
deps_erl_opts, override_deps_versions, override_opts,
|
||||
{if_var_true, elixir, rebar_elixir_compiler},
|
||||
{if_var_true, elixir, rebar_exunit}
|
||||
]}}.
|
||||
@@ -145,16 +153,16 @@
|
||||
{post_hook_configure, [{"fast_tls", []},
|
||||
{"stringprep", []},
|
||||
{"fast_yaml", []},
|
||||
{"esip", []},
|
||||
{if_var_true, sip, {"esip", []}},
|
||||
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
|
||||
{if_var_true, pam, {"epam", []}},
|
||||
{if_var_true, zlib, {"ezlib", []}},
|
||||
{if_var_true, graphics, {"eimp", []}},
|
||||
{if_var_true, iconv, {"iconv", []}}]}.
|
||||
|
||||
{port_env, [{"CFLAGS", "-g -O2 -Wall"}]}.
|
||||
|
||||
{port_specs, [{"priv/lib/jid.so", ["c_src/jid.c"]}]}.
|
||||
|
||||
%% Local Variables:
|
||||
%% mode: erlang
|
||||
%% End:
|
||||
|
||||
+21
-9
@@ -23,10 +23,12 @@ Vars = case file:consult(filename:join([filename:dirname(SCRIPT),"vars.config"])
|
||||
Terms;
|
||||
_Err ->
|
||||
[]
|
||||
end ++ [{cflags, "-g -O2 -Wall"}, {cppflags, "-g -O2 -Wall"}, {ldflags, ""}],
|
||||
end ++ [{cflags, "-g -O2 -Wall"}, {cppflags, "-g -O2 -Wall"},
|
||||
{ldflags, ""}, {system_deps, false}],
|
||||
{cflags, CFlags} = lists:keyfind(cflags, 1, Vars),
|
||||
{cppflags, CPPFlags} = lists:keyfind(cppflags, 1, Vars),
|
||||
{ldflags, LDFlags} = lists:keyfind(ldflags, 1, Vars),
|
||||
{system_deps, SystemDeps} = lists:keyfind(system_deps, 1, Vars),
|
||||
|
||||
GetCfg0 = fun(F, Cfg, [Key | Tail], Default) ->
|
||||
Val = case lists:keyfind(Key, 1, Cfg) of
|
||||
@@ -141,6 +143,15 @@ ProcessVars = fun(_F, [], Acc) ->
|
||||
_ ->
|
||||
F(F, Tail, Acc)
|
||||
end;
|
||||
(F, [{if_have_fun, MFA, Value} | Tail], Acc) ->
|
||||
{Mod, Fun, Arity} = MFA,
|
||||
code:ensure_loaded(Mod),
|
||||
case erlang:function_exported(Mod, Fun, Arity) of
|
||||
true ->
|
||||
F(F, Tail, ProcessSingleVar(F, Value, Acc));
|
||||
false ->
|
||||
F(F, Tail, Acc)
|
||||
end;
|
||||
(F, [Other1 | Tail1], Acc) ->
|
||||
F(F, Tail1, [F(F, Other1, []) | Acc]);
|
||||
(F, Val, Acc) when is_tuple(Val) ->
|
||||
@@ -244,9 +255,9 @@ CtParams = fun(CompileOpts) ->
|
||||
|
||||
GenDepConfigureLine =
|
||||
fun(DepPath, Flags) ->
|
||||
["sh -c 'if test ! -f ",DepPath,"config.status -o ",
|
||||
"config.status -nt ",DepPath,"config.status; ",
|
||||
"then (cd ", DepPath, " && ",
|
||||
["sh -c 'if test ! -f config.status -o ",
|
||||
"../../config.status -nt config.status; ",
|
||||
"then (",
|
||||
"CFLAGS=\"", CFlags,"\" ",
|
||||
"CPPFLAGS=\"", CPPFlags, "\" "
|
||||
"LDFLAGS=\"", LDFlags, "\"",
|
||||
@@ -258,8 +269,8 @@ GenDepsConfigure =
|
||||
fun(Hooks) ->
|
||||
lists:map(fun({Pkg, Flags}) ->
|
||||
DepPath = ResolveDepPath("deps/" ++ Pkg ++ "/"),
|
||||
{'compile',
|
||||
lists:flatten(GenDepConfigureLine(DepPath, Flags))}
|
||||
{add, list_to_atom(Pkg), [{pre_hooks, {'compile',
|
||||
lists:flatten(GenDepConfigureLine(DepPath, Flags))}}]}
|
||||
end, Hooks)
|
||||
end,
|
||||
|
||||
@@ -315,7 +326,7 @@ Rules = [
|
||||
AppendList([{coveralls, ".*", {git, "https://github.com/markusn/coveralls-erl.git", "master"}}]), []},
|
||||
{[post_hooks], [cover_enabled], os:getenv("TRAVIS") == "true",
|
||||
AppendList2(TravisPostHooks), [], false},
|
||||
{[pre_hooks], [post_hook_configure], true,
|
||||
{[overrides], [post_hook_configure], true,
|
||||
AppendList2(GenDepsConfigure), [], []},
|
||||
{[ct_extra_params], [eunit_compile_opts], true,
|
||||
AppendStr2(CtParams), "", []},
|
||||
@@ -327,11 +338,12 @@ Rules = [
|
||||
ProcessFloatingDeps, [], []},
|
||||
{[deps], IsRebar3,
|
||||
Rebar3DepsFilter, []},
|
||||
{[deps], os:getenv("USE_GLOBAL_DEPS") /= false,
|
||||
{[deps], SystemDeps /= false,
|
||||
GlobalDepsFilter, []}
|
||||
],
|
||||
|
||||
Config = FilterConfig(FilterConfig, ProcessVars(ProcessVars, CONFIG, []), Rules),
|
||||
Config = [{plugin_dir, filename:join([filename:dirname(SCRIPT),"plugins"])}]++
|
||||
FilterConfig(FilterConfig, ProcessVars(ProcessVars, CONFIG, []), Rules),
|
||||
|
||||
%io:format("ejabberd configuration:~n ~p~n", [Config]),
|
||||
|
||||
|
||||
@@ -116,12 +116,6 @@ CREATE TABLE vcard (
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE vcard_xupdate (
|
||||
username text PRIMARY KEY,
|
||||
hash text NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE vcard_search (
|
||||
username text NOT NULL,
|
||||
lusername text PRIMARY KEY,
|
||||
|
||||
@@ -470,16 +470,6 @@ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW
|
||||
CREATE INDEX [vcard_search_lorgunit] ON [vcard_search] (lorgunit)
|
||||
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);
|
||||
|
||||
CREATE TABLE [dbo].[vcard_xupdate] (
|
||||
[username] [varchar] (250) NOT NULL,
|
||||
[hash] [text] NOT NULL,
|
||||
[created_at] [datetime] NOT NULL DEFAULT GETDATE(),
|
||||
CONSTRAINT [vcard_xupdate_PRIMARY] PRIMARY KEY CLUSTERED
|
||||
(
|
||||
[username] ASC
|
||||
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
||||
) TEXTIMAGE_ON [PRIMARY];
|
||||
|
||||
ALTER TABLE [dbo].[pubsub_item] WITH CHECK ADD CONSTRAINT [pubsub_item_ibfk_1] FOREIGN KEY([nodeid])
|
||||
REFERENCES [dbo].[pubsub_node] ([nodeid])
|
||||
ON DELETE CASCADE;
|
||||
|
||||
+1
-7
@@ -102,7 +102,7 @@ CREATE TABLE archive (
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE FULLTEXT INDEX i_text ON archive(txt);
|
||||
CREATE INDEX i_username USING BTREE ON archive(username);
|
||||
CREATE INDEX i_username_timestamp USING BTREE ON archive(username,timestamp);
|
||||
CREATE INDEX i_timestamp USING BTREE ON archive(timestamp);
|
||||
CREATE INDEX i_peer USING BTREE ON archive(peer);
|
||||
CREATE INDEX i_bare_peer USING BTREE ON archive(bare_peer);
|
||||
@@ -121,12 +121,6 @@ CREATE TABLE vcard (
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE vcard_xupdate (
|
||||
username varchar(191) PRIMARY KEY,
|
||||
hash text NOT NULL,
|
||||
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE vcard_search (
|
||||
username varchar(191) NOT NULL,
|
||||
lusername varchar(191) PRIMARY KEY,
|
||||
|
||||
@@ -120,12 +120,6 @@ CREATE TABLE vcard (
|
||||
created_at TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE vcard_xupdate (
|
||||
username text PRIMARY KEY,
|
||||
hash text NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE vcard_search (
|
||||
username text NOT NULL,
|
||||
lusername text PRIMARY KEY,
|
||||
|
||||
+45
-27
@@ -92,8 +92,6 @@ init([]) ->
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, access)}]),
|
||||
mnesia:add_table_copy(acl, node(), ram_copies),
|
||||
mnesia:add_table_copy(access, node(), ram_copies),
|
||||
ejabberd_hooks:add(config_reloaded, ?MODULE, load_from_config, 20),
|
||||
load_from_config(),
|
||||
{ok, #state{}}.
|
||||
@@ -201,13 +199,13 @@ load_from_config() ->
|
||||
lists:foreach(
|
||||
fun(Host) ->
|
||||
ACLs = ejabberd_config:get_option(
|
||||
{acl, Host}, fun(V) -> V end, []),
|
||||
{acl, Host}, []),
|
||||
AccessRules = ejabberd_config:get_option(
|
||||
{access, Host}, fun(V) -> V end, []),
|
||||
{access, Host}, []),
|
||||
AccessRulesNew = ejabberd_config:get_option(
|
||||
{access_rules, Host}, fun(V) -> V end, []),
|
||||
{access_rules, Host}, []),
|
||||
ShaperRules = ejabberd_config:get_option(
|
||||
{shaper_rules, Host}, fun(V) -> V end, []),
|
||||
{shaper_rules, Host}, []),
|
||||
lists:foreach(
|
||||
fun({ACLName, SpecList}) ->
|
||||
lists:foreach(
|
||||
@@ -268,24 +266,42 @@ normalize_spec(Spec) ->
|
||||
case Spec of
|
||||
all -> all;
|
||||
none -> none;
|
||||
{acl, N} -> {acl, N};
|
||||
{user, {U, S}} -> {user, {nodeprep(U), nameprep(S)}};
|
||||
{user, U} -> {user, split_user_server(U, fun nodeprep/1, fun nameprep/1)};
|
||||
{shared_group, {G, H}} -> {shared_group, {b(G), nameprep(H)}};
|
||||
{shared_group, G} -> {shared_group, split_user_server(G, fun b/1, fun nameprep/1)};
|
||||
{user_regexp, {UR, S}} -> {user_regexp, {b(UR), nameprep(S)}};
|
||||
{user_regexp, UR} -> {user_regexp, split_user_server(UR, fun b/1, fun nameprep/1)};
|
||||
{node_regexp, {UR, SR}} -> {node_regexp, {b(UR), b(SR)}};
|
||||
{user_glob, {UR, S}} -> {user_glob, {b(UR), nameprep(S)}};
|
||||
{user_glob, UR} -> {user_glob, split_user_server(UR, fun b/1, fun nameprep/1)};
|
||||
{node_glob, {UR, SR}} -> {node_glob, {b(UR), b(SR)}};
|
||||
{server, S} -> {server, nameprep(S)};
|
||||
{resource, R} -> {resource, resourceprep(R)};
|
||||
{server_regexp, SR} -> {server_regexp, b(SR)};
|
||||
{resource_regexp, R} -> {resource_regexp, b(R)};
|
||||
{server_glob, S} -> {server_glob, b(S)};
|
||||
{resource_glob, R} -> {resource_glob, b(R)};
|
||||
{ip, {Net, Mask}} -> {ip, {Net, Mask}};
|
||||
{acl, N} when is_atom(N) ->
|
||||
{acl, N};
|
||||
{user, {U, S}} when is_binary(U), is_binary(S) ->
|
||||
{user, {nodeprep(U), nameprep(S)}};
|
||||
{user, U} when is_binary(U) ->
|
||||
{user, split_user_server(U, fun nodeprep/1, fun nameprep/1)};
|
||||
{shared_group, {G, H}} when is_binary(G), is_binary(H) ->
|
||||
{shared_group, {b(G), nameprep(H)}};
|
||||
{shared_group, G} when is_binary(G) ->
|
||||
{shared_group, split_user_server(G, fun b/1, fun nameprep/1)};
|
||||
{user_regexp, {UR, S}} when is_binary(UR), is_binary(S) ->
|
||||
{user_regexp, {b(UR), nameprep(S)}};
|
||||
{user_regexp, UR} when is_binary(UR) ->
|
||||
{user_regexp, split_user_server(UR, fun b/1, fun nameprep/1)};
|
||||
{node_regexp, {UR, SR}} when is_binary(UR), is_binary(SR) ->
|
||||
{node_regexp, {b(UR), b(SR)}};
|
||||
{user_glob, {UR, S}} when is_binary(UR), is_binary(S) ->
|
||||
{user_glob, {b(UR), nameprep(S)}};
|
||||
{user_glob, UR} when is_binary(UR) ->
|
||||
{user_glob, split_user_server(UR, fun b/1, fun nameprep/1)};
|
||||
{node_glob, {UR, SR}} when is_binary(UR), is_binary(SR) ->
|
||||
{node_glob, {b(UR), b(SR)}};
|
||||
{server, S} when is_binary(S) ->
|
||||
{server, nameprep(S)};
|
||||
{resource, R} when is_binary(R) ->
|
||||
{resource, resourceprep(R)};
|
||||
{server_regexp, SR} when is_binary(SR) ->
|
||||
{server_regexp, b(SR)};
|
||||
{resource_regexp, R} when is_binary(R) ->
|
||||
{resource_regexp, b(R)};
|
||||
{server_glob, S} when is_binary(S) ->
|
||||
{server_glob, b(S)};
|
||||
{resource_glob, R} when is_binary(R) ->
|
||||
{resource_glob, b(R)};
|
||||
{ip, {Net, Mask}} when is_binary(Net), is_integer(Mask) ->
|
||||
{ip, {Net, Mask}};
|
||||
{ip, S} ->
|
||||
case parse_ip_netmask(b(S)) of
|
||||
{ok, Net, Mask} ->
|
||||
@@ -293,7 +309,9 @@ normalize_spec(Spec) ->
|
||||
error ->
|
||||
?INFO_MSG("Invalid network address: ~p", [S]),
|
||||
none
|
||||
end
|
||||
end;
|
||||
BadVal ->
|
||||
throw({<<"Invalid acl value">>, BadVal})
|
||||
end.
|
||||
|
||||
-spec any_rules_allowed(global | binary(), [access_name()],
|
||||
@@ -607,7 +625,7 @@ access_rules_validator(Rules0) ->
|
||||
(deny) -> true;
|
||||
(_) -> false
|
||||
end),
|
||||
throw({replace_with, Rules}).
|
||||
Rules.
|
||||
|
||||
|
||||
shaper_rules_validator(Name) when is_atom(Name) ->
|
||||
@@ -618,7 +636,7 @@ shaper_rules_validator(Rules0) ->
|
||||
(V2) when is_integer(V2) -> true;
|
||||
(_) -> false
|
||||
end),
|
||||
throw({replace_with, Rules}).
|
||||
Rules.
|
||||
|
||||
access_shaper_rules_validator([{Type, Acls} = Rule | Rest], RuleTypeCheck) ->
|
||||
case RuleTypeCheck(Type) of
|
||||
|
||||
+16
-24
@@ -55,7 +55,7 @@
|
||||
check_password :: check_password_fun(),
|
||||
auth_module :: atom(),
|
||||
host = <<"">> :: binary(),
|
||||
hostfqdn = <<"">> :: binary() | [binary()]}).
|
||||
hostfqdn = [] :: [binary()]}).
|
||||
|
||||
start(_Opts) ->
|
||||
Fqdn = get_local_fqdn(),
|
||||
@@ -204,8 +204,6 @@ is_digesturi_valid(DigestURICase, JabberDomain,
|
||||
false
|
||||
end.
|
||||
|
||||
is_host_fqdn(Host, Fqdn) when is_binary(Fqdn) ->
|
||||
Host == Fqdn;
|
||||
is_host_fqdn(_Host, []) ->
|
||||
false;
|
||||
is_host_fqdn(Host, [Fqdn | _FqdnTail]) when Host == Fqdn ->
|
||||
@@ -214,26 +212,13 @@ is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
|
||||
is_host_fqdn(Host, FqdnTail).
|
||||
|
||||
get_local_fqdn() ->
|
||||
case catch get_local_fqdn2() of
|
||||
Str when is_binary(Str) -> Str;
|
||||
List when is_list(List) -> List;
|
||||
_ ->
|
||||
<<"unknown-fqdn, please configure fqdn "
|
||||
"option in ejabberd.yml!">>
|
||||
end.
|
||||
|
||||
get_local_fqdn2() ->
|
||||
case ejabberd_config:get_option(
|
||||
fqdn, fun(X) -> X end) of
|
||||
ConfiguredFqdn when is_binary(ConfiguredFqdn) ->
|
||||
ConfiguredFqdn;
|
||||
[A | _] = ConfiguredFqdns when is_binary(A) ->
|
||||
ConfiguredFqdns;
|
||||
undefined ->
|
||||
{ok, Hostname} = inet:gethostname(),
|
||||
{ok, {hostent, Fqdn, _, _, _, _}} =
|
||||
inet:gethostbyname(Hostname),
|
||||
list_to_binary(Fqdn)
|
||||
case ejabberd_config:get_option(fqdn) of
|
||||
undefined ->
|
||||
{ok, Hostname} = inet:gethostname(),
|
||||
{ok, {hostent, Fqdn, _, _, _, _}} = inet:gethostbyname(Hostname),
|
||||
[list_to_binary(Fqdn)];
|
||||
Fqdn ->
|
||||
Fqdn
|
||||
end.
|
||||
|
||||
hex(S) ->
|
||||
@@ -275,5 +260,12 @@ response(KeyVals, User, Passwd, Nonce, AuthzId,
|
||||
":", (hex((erlang:md5(A2))))/binary>>,
|
||||
hex((erlang:md5(T))).
|
||||
|
||||
opt_type(fqdn) -> fun iolist_to_binary/1;
|
||||
-spec opt_type(fqdn) -> fun((binary() | [binary()]) -> [binary()]);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(fqdn) ->
|
||||
fun(FQDN) when is_binary(FQDN) ->
|
||||
[FQDN];
|
||||
(FQDNs) when is_list(FQDNs) ->
|
||||
[iolist_to_binary(FQDN) || FQDN <- FQDNs]
|
||||
end;
|
||||
opt_type(_) -> [fqdn].
|
||||
|
||||
+14
-6
@@ -111,7 +111,11 @@ mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
{error, saslprep_failed, UserName};
|
||||
true ->
|
||||
{StoredKey, ServerKey, Salt, IterationCount} =
|
||||
if is_tuple(Pass) -> Pass;
|
||||
if is_record(Pass, scram) ->
|
||||
{base64:decode(Pass#scram.storedkey),
|
||||
base64:decode(Pass#scram.serverkey),
|
||||
base64:decode(Pass#scram.salt),
|
||||
Pass#scram.iterationcount};
|
||||
true ->
|
||||
TempSalt =
|
||||
randoms:bytes(?SALT_LENGTH),
|
||||
@@ -128,14 +132,14 @@ mech_step(#state{step = 2} = State, ClientIn) ->
|
||||
str:substr(ClientIn,
|
||||
str:str(ClientIn, <<"n=">>)),
|
||||
ServerNonce =
|
||||
misc:encode_base64(randoms:bytes(?NONCE_LENGTH)),
|
||||
base64:encode(randoms:bytes(?NONCE_LENGTH)),
|
||||
ServerFirstMessage =
|
||||
iolist_to_binary(
|
||||
["r=",
|
||||
ClientNonce,
|
||||
ServerNonce,
|
||||
",", "s=",
|
||||
misc:encode_base64(Salt),
|
||||
base64:encode(Salt),
|
||||
",", "i=",
|
||||
integer_to_list(IterationCount)]),
|
||||
{continue, ServerFirstMessage,
|
||||
@@ -161,7 +165,9 @@ mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
ClientProofAttribute] ->
|
||||
case parse_attribute(GS2ChannelBindingAttribute) of
|
||||
{$c, CVal} ->
|
||||
ChannelBindingSupport = binary:at(misc:decode_base64(CVal), 0),
|
||||
ChannelBindingSupport = try binary:first(base64:decode(CVal))
|
||||
catch _:badarg -> 0
|
||||
end,
|
||||
if (ChannelBindingSupport == $n)
|
||||
or (ChannelBindingSupport == $y) ->
|
||||
Nonce = <<(State#state.client_nonce)/binary,
|
||||
@@ -170,7 +176,9 @@ mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
{$r, CompareNonce} when CompareNonce == Nonce ->
|
||||
case parse_attribute(ClientProofAttribute) of
|
||||
{$p, ClientProofB64} ->
|
||||
ClientProof = misc:decode_base64(ClientProofB64),
|
||||
ClientProof = try base64:decode(ClientProofB64)
|
||||
catch _:badarg -> <<>>
|
||||
end,
|
||||
AuthMessage = iolist_to_binary(
|
||||
[State#state.auth_message,
|
||||
",",
|
||||
@@ -191,7 +199,7 @@ mech_step(#state{step = 4} = State, ClientIn) ->
|
||||
{auth_module, State#state.auth_module},
|
||||
{authzid, State#state.username}],
|
||||
<<"v=",
|
||||
(misc:encode_base64(ServerSignature))/binary>>};
|
||||
(base64:encode(ServerSignature))/binary>>};
|
||||
true -> {error, not_authorized, State#state.username}
|
||||
end;
|
||||
_ -> {error, bad_attribute}
|
||||
|
||||
@@ -239,8 +239,7 @@ get_definitions(#state{definitions = none, fragments_generators = Gens} = State)
|
||||
[{acl,{acl,admin}},
|
||||
{oauth,[<<"ejabberd:admin">>],[{acl,{acl,admin}}]}],
|
||||
{all, [start, stop]}}}],
|
||||
ApiPerms = ejabberd_config:get_option(api_permissions, fun(A) -> A end,
|
||||
DefaultOptions),
|
||||
ApiPerms = ejabberd_config:get_option(api_permissions, DefaultOptions),
|
||||
AllCommands = ejabberd_commands:get_commands_definition(),
|
||||
Frags = lists:foldl(
|
||||
fun({_Name, Generator}, Acc) ->
|
||||
@@ -334,7 +333,7 @@ command_matches_patterns(C, [_ | Tail]) ->
|
||||
%%%===================================================================
|
||||
|
||||
parse_api_permissions(Data) when is_list(Data) ->
|
||||
throw({replace_with, [parse_api_permission(Name, Args) || {Name, Args} <- Data]}).
|
||||
[parse_api_permission(Name, Args) || {Name, Args} <- Data].
|
||||
|
||||
parse_api_permission(Name, Args0) ->
|
||||
Args = lists:flatten(Args0),
|
||||
@@ -374,8 +373,6 @@ parse_who(Name, Defs, ParseOauth) when is_list(Defs) ->
|
||||
throw:{invalid_syntax, Msg} ->
|
||||
report_error(<<"Invalid access rule: '~s' used inside 'who' section for api_permission '~s'">>,
|
||||
[Msg, Name]);
|
||||
throw:{replace_with, NVal} ->
|
||||
{access, NVal};
|
||||
error:_ ->
|
||||
report_error(<<"Invalid access rule '~p' used inside 'who' section for api_permission '~s'">>,
|
||||
[Val, Name])
|
||||
|
||||
+16
-6
@@ -54,6 +54,7 @@
|
||||
dump_to_textfile/1, dump_to_textfile/2,
|
||||
mnesia_change_nodename/4,
|
||||
restore/1, % Still used by some modules
|
||||
clear_cache/0,
|
||||
get_commands_spec/0
|
||||
]).
|
||||
%% gen_server callbacks
|
||||
@@ -137,7 +138,7 @@ get_commands_spec() ->
|
||||
desc = "Get the current loglevel",
|
||||
module = ejabberd_logger, function = get,
|
||||
result_desc = "Tuple with the log level number, its keyword and description",
|
||||
result_example = {4, <<"info">>, <<"Info">>},
|
||||
result_example = {4, info, <<"Info">>},
|
||||
args = [],
|
||||
result = {leveltuple, {tuple, [{levelnumber, integer},
|
||||
{levelatom, atom},
|
||||
@@ -302,6 +303,7 @@ get_commands_spec() ->
|
||||
|
||||
#ejabberd_commands{name = export2sql, tags = [mnesia],
|
||||
desc = "Export virtual host information from Mnesia tables to SQL file",
|
||||
longdesc = "Configure the modules to use SQL, then call this command.",
|
||||
module = ejd2sql, function = export,
|
||||
args_desc = ["Vhost", "Full path to the destination SQL file"],
|
||||
args_example = ["example.com", "/var/lib/ejabberd/example.com.sql"],
|
||||
@@ -360,7 +362,11 @@ get_commands_spec() ->
|
||||
module = ?MODULE, function = install_fallback_mnesia,
|
||||
args_desc = ["Full path to the fallback file"],
|
||||
args_example = ["/var/lib/ejabberd/database.fallback"],
|
||||
args = [{file, string}], result = {res, restuple}}
|
||||
args = [{file, string}], result = {res, restuple}},
|
||||
#ejabberd_commands{name = clear_cache, tags = [server],
|
||||
desc = "Clear database cache on all nodes",
|
||||
module = ?MODULE, function = clear_cache,
|
||||
args = [], result = {res, rescode}}
|
||||
].
|
||||
|
||||
|
||||
@@ -473,9 +479,9 @@ update_module(ModuleNameString) ->
|
||||
|
||||
register(User, Host, Password) ->
|
||||
case ejabberd_auth:try_register(User, Host, Password) of
|
||||
{atomic, ok} ->
|
||||
ok ->
|
||||
{ok, io_lib:format("User ~s@~s successfully registered", [User, Host])};
|
||||
{atomic, exists} ->
|
||||
{error, exists} ->
|
||||
Msg = io_lib:format("User ~s@~s already registered", [User, Host]),
|
||||
{error, conflict, 10090, Msg};
|
||||
{error, Reason} ->
|
||||
@@ -489,7 +495,7 @@ unregister(User, Host) ->
|
||||
{ok, ""}.
|
||||
|
||||
registered_users(Host) ->
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
Users = ejabberd_auth:get_users(Host),
|
||||
SUsers = lists:sort(Users),
|
||||
lists:map(fun({U, _S}) -> U end, SUsers).
|
||||
|
||||
@@ -611,7 +617,7 @@ restore(Path) ->
|
||||
%% Obsolete tables or tables created by module who are no longer used are not
|
||||
%% restored and are ignored.
|
||||
keep_tables() ->
|
||||
lists:flatten([acl, passwd, config, local_config,
|
||||
lists:flatten([acl, passwd, config,
|
||||
keep_modules_tables()]).
|
||||
|
||||
%% Returns the list of modules tables in use, according to the list of actually
|
||||
@@ -759,3 +765,7 @@ mnesia_change_nodename(FromString, ToString, Source, Target) ->
|
||||
{[Other], Acc}
|
||||
end,
|
||||
mnesia:traverse_backup(Source, Target, Convert, switched).
|
||||
|
||||
clear_cache() ->
|
||||
Nodes = ejabberd_cluster:get_nodes(),
|
||||
lists:foreach(fun(T) -> ets_cache:clear(T, Nodes) end, ets_cache:all()).
|
||||
|
||||
+15
-53
@@ -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").
|
||||
@@ -46,19 +45,19 @@ start(normal, _Args) ->
|
||||
start_apps(),
|
||||
start_elixir_application(),
|
||||
ejabberd:check_app(ejabberd),
|
||||
db_init(),
|
||||
setup_if_elixir_conf_used(),
|
||||
ejabberd_config:start(),
|
||||
set_settings_from_config(),
|
||||
ejabberd_mnesia:start(),
|
||||
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]),
|
||||
lists:foreach(fun erlang:garbage_collect/1, processes()),
|
||||
{ok, SupPid};
|
||||
Err ->
|
||||
Err
|
||||
@@ -87,40 +86,6 @@ stop(_State) ->
|
||||
%%% Internal functions
|
||||
%%%
|
||||
|
||||
db_init() ->
|
||||
ejabberd_config:env_binary_to_list(mnesia, dir),
|
||||
MyNode = node(),
|
||||
DbNodes = mnesia:system_info(db_nodes),
|
||||
case lists:member(MyNode, DbNodes) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
?CRITICAL_MSG("Node name mismatch: I'm [~s], "
|
||||
"the database is owned by ~p", [MyNode, DbNodes]),
|
||||
?CRITICAL_MSG("Either set ERLANG_NODE in ejabberdctl.cfg "
|
||||
"or change node name in Mnesia", []),
|
||||
erlang:error(node_name_mismatch)
|
||||
end,
|
||||
case mnesia:system_info(extra_db_nodes) of
|
||||
[] ->
|
||||
mnesia:create_schema([node()]);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd:start_app(mnesia, permanent),
|
||||
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity).
|
||||
|
||||
connect_nodes() ->
|
||||
Nodes = ejabberd_config:get_option(
|
||||
cluster_nodes,
|
||||
fun(Ns) ->
|
||||
true = lists:all(fun is_atom/1, Ns),
|
||||
Ns
|
||||
end, []),
|
||||
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
|
||||
@@ -163,13 +128,6 @@ delete_pid_file() ->
|
||||
file:delete(PidFilename)
|
||||
end.
|
||||
|
||||
set_settings_from_config() ->
|
||||
Ticktime = ejabberd_config:get_option(
|
||||
net_ticktime,
|
||||
opt_type(net_ticktime),
|
||||
60),
|
||||
net_kernel:set_net_ticktime(Ticktime).
|
||||
|
||||
file_queue_init() ->
|
||||
QueueDir = case ejabberd_config:queue_dir() of
|
||||
undefined ->
|
||||
@@ -184,16 +142,12 @@ start_apps() ->
|
||||
crypto:start(),
|
||||
ejabberd:start_app(sasl),
|
||||
ejabberd:start_app(ssl),
|
||||
ejabberd:start_app(p1_utils),
|
||||
ejabberd:start_app(fast_yaml),
|
||||
ejabberd:start_app(fast_tls),
|
||||
ejabberd:start_app(xmpp),
|
||||
ejabberd:start_app(cache_tab).
|
||||
|
||||
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].
|
||||
ejabberd:start_app(cache_tab),
|
||||
start_eimp().
|
||||
|
||||
setup_if_elixir_conf_used() ->
|
||||
case ejabberd_config:is_using_elixir_config() of
|
||||
@@ -217,3 +171,11 @@ start_elixir_application() ->
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-ifdef(GRAPHICS).
|
||||
start_eimp() ->
|
||||
ejabberd:start_app(eimp).
|
||||
-else.
|
||||
start_eimp() ->
|
||||
ok.
|
||||
-endif.
|
||||
|
||||
+595
-284
File diff suppressed because it is too large
Load Diff
@@ -26,9 +26,11 @@
|
||||
-module(ejabberd_auth_anonymous).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
-behaviour(ejabberd_auth).
|
||||
-author('mickael.remond@process-one.net').
|
||||
|
||||
-export([start/1,
|
||||
stop/1,
|
||||
allow_anonymous/1,
|
||||
is_sasl_anonymous_enabled/1,
|
||||
is_login_anonymous_enabled/1,
|
||||
@@ -38,15 +40,9 @@
|
||||
unregister_connection/3
|
||||
]).
|
||||
|
||||
-export([login/2, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password_s/2,
|
||||
get_password/2, get_password/3, is_user_exists/2,
|
||||
remove_user/2, remove_user/3, store_type/0,
|
||||
plain_password_required/0, opt_type/1]).
|
||||
-export([login/2, check_password/4, user_exists/2,
|
||||
get_users/2, count_users/2, store_type/1,
|
||||
plain_password_required/1, opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -59,6 +55,12 @@ start(Host) ->
|
||||
?MODULE, unregister_connection, 100),
|
||||
ok.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(sm_register_connection_hook, Host,
|
||||
?MODULE, register_connection, 100),
|
||||
ejabberd_hooks:delete(sm_remove_connection_hook, Host,
|
||||
?MODULE, unregister_connection, 100).
|
||||
|
||||
%% Return true if anonymous is allowed for host or false otherwise
|
||||
allow_anonymous(Host) ->
|
||||
lists:member(?MODULE, ejabberd_auth:auth_modules(Host)).
|
||||
@@ -93,21 +95,12 @@ is_login_anonymous_enabled(Host) ->
|
||||
%% Return the anonymous protocol to use: sasl_anon|login_anon|both
|
||||
%% defaults to login_anon
|
||||
anonymous_protocol(Host) ->
|
||||
ejabberd_config:get_option(
|
||||
{anonymous_protocol, Host},
|
||||
fun(sasl_anon) -> sasl_anon;
|
||||
(login_anon) -> login_anon;
|
||||
(both) -> both
|
||||
end,
|
||||
sasl_anon).
|
||||
ejabberd_config:get_option({anonymous_protocol, Host}, sasl_anon).
|
||||
|
||||
%% Return true if multiple connections have been allowed in the config file
|
||||
%% defaults to false
|
||||
allow_multiple_connections(Host) ->
|
||||
ejabberd_config:get_option(
|
||||
{allow_multiple_connections, Host},
|
||||
fun(V) when is_boolean(V) -> V end,
|
||||
false).
|
||||
ejabberd_config:get_option({allow_multiple_connections, Host}, false).
|
||||
|
||||
anonymous_user_exist(User, Server) ->
|
||||
lists:any(
|
||||
@@ -140,17 +133,9 @@ unregister_connection(_SID,
|
||||
%% ---------------------------------
|
||||
%% Specific anonymous auth functions
|
||||
%% ---------------------------------
|
||||
|
||||
%% When anonymous login is enabled, check the password for permenant users
|
||||
%% before allowing access
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
check_password(User, AuthzId, Server, Password, undefined,
|
||||
undefined).
|
||||
|
||||
check_password(User, _AuthzId, Server, _Password, _Digest,
|
||||
_DigestGen) ->
|
||||
check_password(User, _AuthzId, Server, _Password) ->
|
||||
case
|
||||
ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
|
||||
ejabberd_auth:user_exists_in_other_modules(?MODULE,
|
||||
User, Server)
|
||||
of
|
||||
%% If user exists in other module, reject anonnymous authentication
|
||||
@@ -174,69 +159,25 @@ login(User, Server) ->
|
||||
end
|
||||
end.
|
||||
|
||||
%% When anonymous login is enabled, check that the user is permanent before
|
||||
%% changing its password
|
||||
set_password(User, Server, _Password) ->
|
||||
case anonymous_user_exist(User, Server) of
|
||||
true -> ok;
|
||||
false -> {error, not_allowed}
|
||||
end.
|
||||
get_users(Server, _) ->
|
||||
[{U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Server)].
|
||||
|
||||
%% When anonymous login is enabled, check if permanent users are allowed on
|
||||
%% the server:
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
count_users(Server, Opts) ->
|
||||
length(get_users(Server, Opts)).
|
||||
|
||||
dirty_get_registered_users() -> [].
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
[{U, S}
|
||||
|| {U, S, _R}
|
||||
<- ejabberd_sm:get_vh_session_list(Server)].
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
length(get_vh_registered_users(Server)).
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
%% Return password of permanent user or false for anonymous users
|
||||
get_password(User, Server) ->
|
||||
get_password(User, Server, <<"">>).
|
||||
|
||||
get_password(User, Server, DefaultValue) ->
|
||||
case anonymous_user_exist(User, Server) or
|
||||
login(User, Server)
|
||||
of
|
||||
%% We return the default value if the user is anonymous
|
||||
true -> DefaultValue;
|
||||
%% We return the permanent user password otherwise
|
||||
false -> false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false ->
|
||||
<<"">>;
|
||||
Password ->
|
||||
Password
|
||||
end.
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
user_exists(User, Server) ->
|
||||
anonymous_user_exist(User, Server).
|
||||
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
plain_password_required(_) ->
|
||||
false.
|
||||
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
|
||||
plain_password_required() -> false.
|
||||
|
||||
store_type() ->
|
||||
plain.
|
||||
store_type(_) ->
|
||||
external.
|
||||
|
||||
-spec opt_type(allow_multiple_connection) -> fun((boolean()) -> boolean());
|
||||
(anonymous_protocol) -> fun((sasl_anon | login_anon | both) ->
|
||||
sasl_anon | login_anon | both);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(allow_multiple_connections) ->
|
||||
fun (V) when is_boolean(V) -> V end;
|
||||
opt_type(anonymous_protocol) ->
|
||||
|
||||
+29
-256
@@ -32,14 +32,8 @@
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
-export([start/1, stop/1, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, plain_password_required/0,
|
||||
opt_type/1]).
|
||||
try_register/3, user_exists/2, remove_user/2,
|
||||
store_type/1, plain_password_required/1, opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -48,283 +42,62 @@
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
Cmd = ejabberd_config:get_option(
|
||||
{extauth_program, Host},
|
||||
fun(V) ->
|
||||
binary_to_list(iolist_to_binary(V))
|
||||
end,
|
||||
"extauth"),
|
||||
extauth:start(Host, Cmd),
|
||||
check_cache_last_options(Host),
|
||||
ejabberd_auth_mnesia:start(Host).
|
||||
Cmd = ejabberd_config:get_option({extauth_program, Host}, "extauth"),
|
||||
extauth:start(Host, Cmd).
|
||||
|
||||
stop(Host) ->
|
||||
extauth:stop(Host),
|
||||
ejabberd_auth_mnesia:stop(Host).
|
||||
extauth:stop(Host).
|
||||
|
||||
check_cache_last_options(Server) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> no_cache;
|
||||
{true, _CacheTime} ->
|
||||
case get_mod_last_configured(Server) of
|
||||
no_mod_last ->
|
||||
?ERROR_MSG("In host ~p extauth is used, extauth_cache "
|
||||
"is enabled but mod_last is not enabled.",
|
||||
[Server]),
|
||||
no_cache;
|
||||
_ -> cache
|
||||
end
|
||||
end.
|
||||
plain_password_required(_) -> true.
|
||||
|
||||
plain_password_required() -> true.
|
||||
|
||||
store_type() -> external.
|
||||
store_type(_) -> external.
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false ->
|
||||
check_password_extauth(User, AuthzId, Server, Password);
|
||||
{true, CacheTime} ->
|
||||
check_password_cache(User, AuthzId, Server, Password,
|
||||
CacheTime)
|
||||
end
|
||||
check_password_extauth(User, AuthzId, Server, Password)
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, _Digest,
|
||||
_DigestGen) ->
|
||||
check_password(User, AuthzId, Server, Password).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
case extauth:set_password(User, Server, Password) of
|
||||
true ->
|
||||
set_password_mnesia(User, Server, Password), ok;
|
||||
_ -> {error, unknown_problem}
|
||||
true -> ok;
|
||||
_ -> {error, db_failure}
|
||||
end.
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> try_register_extauth(User, Server, Password);
|
||||
{true, _CacheTime} ->
|
||||
try_register_external_cache(User, Server, Password)
|
||||
end.
|
||||
extauth:try_register(User, Server, Password).
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
ejabberd_auth_mnesia:dirty_get_registered_users().
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
ejabberd_auth_mnesia:get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users(Server, Data) ->
|
||||
ejabberd_auth_mnesia:get_vh_registered_users(Server,
|
||||
Data).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
ejabberd_auth_mnesia:get_vh_registered_users_number(Server).
|
||||
|
||||
get_vh_registered_users_number(Server, Data) ->
|
||||
ejabberd_auth_mnesia:get_vh_registered_users_number(Server,
|
||||
Data).
|
||||
|
||||
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
|
||||
get_password(User, Server) ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, CacheTime} ->
|
||||
get_password_cache(User, Server, CacheTime)
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
case get_password(User, Server) of
|
||||
false -> <<"">>;
|
||||
Other -> Other
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
try extauth:is_user_exists(User, Server) of
|
||||
Res -> Res
|
||||
user_exists(User, Server) ->
|
||||
try extauth:user_exists(User, Server) of
|
||||
Res -> Res
|
||||
catch
|
||||
_:Error -> {error, Error}
|
||||
_:Error ->
|
||||
?ERROR_MSG("external authentication program failure: ~p",
|
||||
[Error]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
case extauth:remove_user(User, Server) of
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_mnesia:remove_user(User, Server)
|
||||
end
|
||||
false -> {error, not_allowed};
|
||||
true -> ok
|
||||
end.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
case extauth:remove_user(User, Server, Password) of
|
||||
false -> false;
|
||||
true ->
|
||||
case get_cache_option(Server) of
|
||||
false -> false;
|
||||
{true, _CacheTime} ->
|
||||
ejabberd_auth_mnesia:remove_user(User, Server,
|
||||
Password)
|
||||
end
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% Extauth cache management
|
||||
%%%
|
||||
|
||||
%% @spec (Host::string()) -> false | {true, CacheTime::integer()}
|
||||
get_cache_option(Host) ->
|
||||
case ejabberd_config:get_option(
|
||||
{extauth_cache, Host},
|
||||
fun(false) -> undefined;
|
||||
(I) when is_integer(I), I >= 0 -> I
|
||||
end) of
|
||||
undefined -> false;
|
||||
CacheTime -> {true, CacheTime}
|
||||
end.
|
||||
|
||||
%% @spec (User, AuthzId, Server, Password) -> true | false
|
||||
check_password_extauth(User, _AuthzId, Server, Password) ->
|
||||
extauth:check_password(User, Server, Password) andalso
|
||||
Password /= <<"">>.
|
||||
|
||||
%% @spec (User, Server, Password) -> true | false
|
||||
try_register_extauth(User, Server, Password) ->
|
||||
extauth:try_register(User, Server, Password).
|
||||
|
||||
check_password_cache(User, AuthzId, Server, Password, 0) ->
|
||||
check_password_external_cache(User, AuthzId, Server, Password);
|
||||
check_password_cache(User, AuthzId, Server, Password,
|
||||
CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online ->
|
||||
check_password_mnesia(User, AuthzId, Server, Password);
|
||||
never ->
|
||||
check_password_external_cache(User, AuthzId, Server, Password);
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled "
|
||||
"but mod_last is not enabled in that "
|
||||
"host",
|
||||
[]),
|
||||
check_password_external_cache(User, AuthzId, Server, Password);
|
||||
TimeStamp ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
%% If no need to refresh, check password against Mnesia
|
||||
true ->
|
||||
case check_password_mnesia(User, AuthzId, Server, Password) of
|
||||
%% If password valid in Mnesia, accept it
|
||||
true -> true;
|
||||
%% Else (password nonvalid in Mnesia), check in extauth and cache result
|
||||
false ->
|
||||
check_password_external_cache(User, AuthzId, Server, Password)
|
||||
end;
|
||||
%% Else (need to refresh), check in extauth and cache result
|
||||
false ->
|
||||
check_password_external_cache(User, AuthzId, Server, Password)
|
||||
end
|
||||
end.
|
||||
|
||||
get_password_mnesia(User, Server) ->
|
||||
ejabberd_auth_mnesia:get_password(User, Server).
|
||||
|
||||
-spec get_password_cache(User::binary(), Server::binary(), CacheTime::integer()) -> Password::string() | false.
|
||||
get_password_cache(User, Server, CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
online -> get_password_mnesia(User, Server);
|
||||
never -> false;
|
||||
mod_last_required ->
|
||||
?ERROR_MSG("extauth is used, extauth_cache is enabled "
|
||||
"but mod_last is not enabled in that "
|
||||
"host",
|
||||
[]),
|
||||
false;
|
||||
TimeStamp ->
|
||||
case is_fresh_enough(TimeStamp, CacheTime) of
|
||||
true -> get_password_mnesia(User, Server);
|
||||
false -> false
|
||||
end
|
||||
end.
|
||||
|
||||
%% Check the password using extauth; if success then cache it
|
||||
check_password_external_cache(User, AuthzId, Server, Password) ->
|
||||
case check_password_extauth(User, AuthzId, Server, Password) of
|
||||
true ->
|
||||
set_password_mnesia(User, Server, Password), true;
|
||||
false -> false
|
||||
end.
|
||||
|
||||
%% Try to register using extauth; if success then cache it
|
||||
try_register_external_cache(User, Server, Password) ->
|
||||
case try_register_extauth(User, Server, Password) of
|
||||
{atomic, ok} = R ->
|
||||
set_password_mnesia(User, Server, Password), R;
|
||||
_ -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
%% @spec (User, AuthzId, Server, Password) -> true | false
|
||||
check_password_mnesia(User, AuthzId, Server, Password) ->
|
||||
ejabberd_auth_mnesia:check_password(User, AuthzId, Server,
|
||||
Password).
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
|
||||
set_password_mnesia(User, Server, Password) ->
|
||||
%% @spec (TimeLast, CacheTime) -> true | false
|
||||
%% TimeLast = online | never | integer()
|
||||
%% CacheTime = integer() | false
|
||||
ejabberd_auth_mnesia:set_password(User, Server,
|
||||
Password).
|
||||
|
||||
is_fresh_enough(TimeStampLast, CacheTime) ->
|
||||
Now = p1_time_compat:system_time(seconds),
|
||||
TimeStampLast + CacheTime > Now.
|
||||
|
||||
%% Code copied from mod_configure.erl
|
||||
%% Code copied from web/ejabberd_web_admin.erl
|
||||
%% TODO: Update time format to XEP-0202: Entity Time
|
||||
-spec(get_last_access(User::binary(), Server::binary()) -> (online | never | mod_last_required | integer())).
|
||||
get_last_access(User, Server) ->
|
||||
case ejabberd_sm:get_user_resources(User, Server) of
|
||||
[] ->
|
||||
case get_last_info(User, Server) of
|
||||
mod_last_required -> mod_last_required;
|
||||
not_found -> never;
|
||||
{ok, Timestamp, _Status} -> Timestamp
|
||||
end;
|
||||
_ -> online
|
||||
end.
|
||||
%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required
|
||||
|
||||
get_last_info(User, Server) ->
|
||||
case get_mod_last_enabled(Server) of
|
||||
mod_last -> mod_last:get_last_info(User, Server);
|
||||
no_mod_last -> mod_last_required
|
||||
end.
|
||||
|
||||
%% @spec (Server) -> mod_last | no_mod_last
|
||||
get_mod_last_enabled(Server) ->
|
||||
case gen_mod:is_loaded(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
get_mod_last_configured(Server) ->
|
||||
case is_configured(Server, mod_last) of
|
||||
true -> mod_last;
|
||||
false -> no_mod_last
|
||||
end.
|
||||
|
||||
is_configured(Host, Module) ->
|
||||
Os = ejabberd_config:get_option({modules, Host},
|
||||
fun(M) when is_list(M) -> M end),
|
||||
lists:keymember(Module, 1, Os).
|
||||
|
||||
-spec opt_type(extauth_cache) -> fun((false | non_neg_integer()) ->
|
||||
false | non_neg_integer());
|
||||
(extauth_program) -> fun((binary()) -> string());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(extauth_cache) ->
|
||||
fun (false) -> undefined;
|
||||
?WARNING_MSG("option 'extauth_cache' is deprecated and has no effect, "
|
||||
"use authentication or global cache configuration "
|
||||
"options: auth_use_cache, auth_cache_life_time, "
|
||||
"use_cache, cache_life_time, and so on", []),
|
||||
fun (false) -> false;
|
||||
(I) when is_integer(I), I >= 0 -> I
|
||||
end;
|
||||
opt_type(extauth_program) ->
|
||||
|
||||
+31
-98
@@ -37,13 +37,9 @@
|
||||
handle_cast/2, terminate/2, code_change/3]).
|
||||
|
||||
-export([start/1, stop/1, start_link/1, set_password/3,
|
||||
check_password/4, check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, plain_password_required/0,
|
||||
check_password/4, user_exists/2,
|
||||
get_users/2, count_users/2,
|
||||
store_type/1, plain_password_required/1,
|
||||
opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -112,9 +108,9 @@ init(Host) ->
|
||||
State#state.password, State#state.tls_options),
|
||||
{ok, State}.
|
||||
|
||||
plain_password_required() -> true.
|
||||
plain_password_required(_) -> true.
|
||||
|
||||
store_type() -> external.
|
||||
store_type(_) -> external.
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
@@ -129,60 +125,34 @@ check_password(User, AuthzId, Server, Password) ->
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, _Digest,
|
||||
_DigestGen) ->
|
||||
check_password(User, AuthzId, Server, Password).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
case find_user_dn(User, State) of
|
||||
false -> {error, user_not_found};
|
||||
false -> {error, notfound};
|
||||
DN ->
|
||||
eldap_pool:modify_passwd(State#state.eldap_id, DN,
|
||||
Password)
|
||||
case eldap_pool:modify_passwd(State#state.eldap_id, DN,
|
||||
Password) of
|
||||
ok -> ok;
|
||||
_Err -> {error, db_failure}
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {error, not_allowed}
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(ldap),
|
||||
lists:flatmap(fun (Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end,
|
||||
Servers).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
case catch get_vh_registered_users_ldap(Server) of
|
||||
get_users(Server, []) ->
|
||||
case catch get_users_ldap(Server) of
|
||||
{'EXIT', _} -> [];
|
||||
Result -> Result
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
length(get_vh_registered_users(Server)).
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
get_password(_User, _Server) -> false.
|
||||
|
||||
get_password_s(_User, _Server) -> <<"">>.
|
||||
count_users(Server, Opts) ->
|
||||
length(get_users(Server, Opts)).
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
case catch is_user_exists_ldap(User, Server) of
|
||||
{'EXIT', Error} -> {error, Error};
|
||||
user_exists(User, Server) ->
|
||||
case catch user_exists_ldap(User, Server) of
|
||||
{'EXIT', _Error} -> {error, db_failure};
|
||||
Result -> Result
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% Internal functions
|
||||
%%%----------------------------------------------------------------------
|
||||
@@ -199,7 +169,7 @@ check_password_ldap(User, Server, Password) ->
|
||||
end
|
||||
end.
|
||||
|
||||
get_vh_registered_users_ldap(Server) ->
|
||||
get_users_ldap(Server) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
UIDs = State#state.uids,
|
||||
Eldap_ID = State#state.eldap_id,
|
||||
@@ -248,7 +218,7 @@ get_vh_registered_users_ldap(Server) ->
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
is_user_exists_ldap(User, Server) ->
|
||||
user_exists_ldap(User, Server) ->
|
||||
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
|
||||
case find_user_dn(User, State) of
|
||||
false -> false;
|
||||
@@ -364,24 +334,11 @@ parse_options(Host) ->
|
||||
Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
|
||||
Bind_Eldap_ID = misc:atom_to_binary(
|
||||
gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
|
||||
UIDsTemp = gen_mod:get_opt(
|
||||
{ldap_uids, Host}, [],
|
||||
fun(Us) ->
|
||||
lists:map(
|
||||
fun({U, P}) ->
|
||||
{iolist_to_binary(U),
|
||||
iolist_to_binary(P)};
|
||||
({U}) ->
|
||||
{iolist_to_binary(U)};
|
||||
(U) ->
|
||||
{iolist_to_binary(U)}
|
||||
end, lists:flatten(Us))
|
||||
end, [{<<"uid">>, <<"%u">>}]),
|
||||
UIDsTemp = ejabberd_config:get_option(
|
||||
{ldap_uids, Host}, [{<<"uid">>, <<"%u">>}]),
|
||||
UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
|
||||
SubFilter = eldap_utils:generate_subfilter(UIDs),
|
||||
UserFilter = case gen_mod:get_opt(
|
||||
{ldap_filter, Host}, [],
|
||||
fun check_filter/1, <<"">>) of
|
||||
UserFilter = case ejabberd_config:get_option({ldap_filter, Host}, <<"">>) of
|
||||
<<"">> ->
|
||||
SubFilter;
|
||||
F ->
|
||||
@@ -390,20 +347,8 @@ parse_options(Host) ->
|
||||
SearchFilter = eldap_filter:do_sub(UserFilter,
|
||||
[{<<"%u">>, <<"*">>}]),
|
||||
{DNFilter, DNFilterAttrs} =
|
||||
gen_mod:get_opt({ldap_dn_filter, Host}, [],
|
||||
fun([{DNF, DNFA}]) ->
|
||||
NewDNFA = case DNFA of
|
||||
undefined ->
|
||||
[];
|
||||
_ ->
|
||||
[iolist_to_binary(A)
|
||||
|| A <- DNFA]
|
||||
end,
|
||||
NewDNF = check_filter(DNF),
|
||||
{NewDNF, NewDNFA}
|
||||
end, {undefined, []}),
|
||||
LocalFilter = gen_mod:get_opt(
|
||||
{ldap_local_filter, Host}, [], fun(V) -> V end),
|
||||
ejabberd_config:get_option({ldap_dn_filter, Host}, {undefined, []}),
|
||||
LocalFilter = ejabberd_config:get_option({ldap_local_filter, Host}),
|
||||
#state{host = Host, eldap_id = Eldap_ID,
|
||||
bind_eldap_id = Bind_Eldap_ID,
|
||||
servers = Cfg#eldap_config.servers,
|
||||
@@ -418,31 +363,19 @@ parse_options(Host) ->
|
||||
sfilter = SearchFilter, lfilter = LocalFilter,
|
||||
dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}.
|
||||
|
||||
check_filter(F) ->
|
||||
NewF = iolist_to_binary(F),
|
||||
{ok, _} = eldap_filter:parse(NewF),
|
||||
NewF.
|
||||
|
||||
-spec opt_type(ldap_dn_filter) -> fun(([{binary(), binary()}]) ->
|
||||
[{binary(), binary()}]);
|
||||
(ldap_local_filter) -> fun((any()) -> any());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(ldap_dn_filter) ->
|
||||
fun ([{DNF, DNFA}]) ->
|
||||
NewDNFA = case DNFA of
|
||||
undefined -> [];
|
||||
_ -> [iolist_to_binary(A) || A <- DNFA]
|
||||
end,
|
||||
NewDNF = check_filter(DNF),
|
||||
NewDNF = eldap_utils:check_filter(DNF),
|
||||
{NewDNF, NewDNFA}
|
||||
end;
|
||||
opt_type(ldap_filter) -> fun check_filter/1;
|
||||
opt_type(ldap_local_filter) -> fun (V) -> V end;
|
||||
opt_type(ldap_uids) ->
|
||||
fun (Us) ->
|
||||
lists:map(fun ({U, P}) ->
|
||||
{iolist_to_binary(U), iolist_to_binary(P)};
|
||||
({U}) -> {iolist_to_binary(U)};
|
||||
(U) -> {iolist_to_binary(U)}
|
||||
end,
|
||||
lists:flatten(Us))
|
||||
end;
|
||||
opt_type(_) ->
|
||||
[ldap_dn_filter, ldap_filter, ldap_local_filter,
|
||||
ldap_uids].
|
||||
[ldap_dn_filter, ldap_local_filter].
|
||||
|
||||
+183
-413
@@ -27,21 +27,16 @@
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
-export([start/1, stop/1, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2, init_db/0,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, export/1, import/2,
|
||||
plain_password_required/0, opt_type/1]).
|
||||
-export([start/1, stop/1, set_password/3, try_register/3,
|
||||
get_users/2, init_db/0,
|
||||
count_users/2, get_password/2,
|
||||
remove_user/2, store_type/1, export/1, import/2,
|
||||
plain_password_required/1, use_cache/1]).
|
||||
-export([need_transform/1, transform/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -53,16 +48,12 @@
|
||||
-record(reg_users_counter, {vhost = <<"">> :: binary(),
|
||||
count = 0 :: integer() | '$1'}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
init_db(),
|
||||
update_table(),
|
||||
update_reg_users_counter_table(Host),
|
||||
maybe_alert_password_scrammed_without_option(),
|
||||
ok.
|
||||
|
||||
stop(_Host) ->
|
||||
@@ -70,14 +61,14 @@ stop(_Host) ->
|
||||
|
||||
init_db() ->
|
||||
ejabberd_mnesia:create(?MODULE, passwd,
|
||||
[{disc_copies, [node()]},
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes, record_info(fields, passwd)}]),
|
||||
ejabberd_mnesia:create(?MODULE, reg_users_counter,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, reg_users_counter)}]).
|
||||
|
||||
update_reg_users_counter_table(Server) ->
|
||||
Set = get_vh_registered_users(Server),
|
||||
Set = get_users(Server, []),
|
||||
Size = length(Set),
|
||||
LServer = jid:nameprep(Server),
|
||||
F = fun () ->
|
||||
@@ -86,419 +77,201 @@ update_reg_users_counter_table(Server) ->
|
||||
end,
|
||||
mnesia:sync_dirty(F).
|
||||
|
||||
plain_password_required() ->
|
||||
is_scrammed().
|
||||
|
||||
store_type() ->
|
||||
case is_scrammed() of
|
||||
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
|
||||
true -> scram %% allows: PLAIN SCRAM
|
||||
use_cache(Host) ->
|
||||
case mnesia:table_info(passwd, storage_type) of
|
||||
disc_only_copies ->
|
||||
ejabberd_config:get_option(
|
||||
{auth_use_cache, Host},
|
||||
ejabberd_config:use_cache(Host));
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Password}] when is_binary(Password) ->
|
||||
Password /= <<"">>;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
plain_password_required(Server) ->
|
||||
store_type(Server) == scram.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[#passwd{password = Passwd}] when is_binary(Passwd) ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
[#passwd{password = Scram}] when is_record(Scram, scram) ->
|
||||
Passwd = misc:decode_base64(Scram#scram.storedkey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
store_type(Server) ->
|
||||
ejabberd_auth:password_format(Server).
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% ok | {error, invalid_jid}
|
||||
set_password(User, Server, Password) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
LPassword = jid:resourceprep(Password),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
LPassword == error ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
F = fun () ->
|
||||
Password2 = case is_scrammed() and is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
mnesia:write(#passwd{us = US, password = Password2})
|
||||
end,
|
||||
{atomic, ok} = mnesia:transaction(F),
|
||||
ok
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {error, not_allowed} | {error, Reason}
|
||||
try_register(User, Server, PasswordList) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Password = if is_list(PasswordList); is_binary(PasswordList) ->
|
||||
iolist_to_binary(PasswordList);
|
||||
true -> PasswordList
|
||||
end,
|
||||
LPassword = jid:resourceprep(Password),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
(LPassword == error) and not is_record(Password, scram) ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
F = fun () ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[] ->
|
||||
Password2 = case is_scrammed() and
|
||||
is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
mnesia:write(#passwd{us = US,
|
||||
password = Password2}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, 1),
|
||||
ok;
|
||||
[_E] -> exists
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F)
|
||||
end.
|
||||
|
||||
%% Get all registered users in Mnesia
|
||||
dirty_get_registered_users() ->
|
||||
mnesia:dirty_all_keys(passwd).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
mnesia:dirty_select(passwd,
|
||||
[{#passwd{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}], ['$1']}]).
|
||||
|
||||
get_vh_registered_users(Server,
|
||||
[{from, Start}, {to, End}])
|
||||
when is_integer(Start) and is_integer(End) ->
|
||||
get_vh_registered_users(Server,
|
||||
[{limit, End - Start + 1}, {offset, Start}]);
|
||||
get_vh_registered_users(Server,
|
||||
[{limit, Limit}, {offset, Offset}])
|
||||
when is_integer(Limit) and is_integer(Offset) ->
|
||||
case get_vh_registered_users(Server) of
|
||||
[] -> [];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
get_vh_registered_users(Server, [{prefix, Prefix}])
|
||||
when is_binary(Prefix) ->
|
||||
Set = [{U, S}
|
||||
|| {U, S} <- get_vh_registered_users(Server),
|
||||
str:prefix(Prefix, U)],
|
||||
lists:keysort(1, Set);
|
||||
get_vh_registered_users(Server,
|
||||
[{prefix, Prefix}, {from, Start}, {to, End}])
|
||||
when is_binary(Prefix) and is_integer(Start) and
|
||||
is_integer(End) ->
|
||||
get_vh_registered_users(Server,
|
||||
[{prefix, Prefix}, {limit, End - Start + 1},
|
||||
{offset, Start}]);
|
||||
get_vh_registered_users(Server,
|
||||
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
|
||||
when is_binary(Prefix) and is_integer(Limit) and
|
||||
is_integer(Offset) ->
|
||||
case [{U, S}
|
||||
|| {U, S} <- get_vh_registered_users(Server),
|
||||
str:prefix(Prefix, U)]
|
||||
of
|
||||
[] -> [];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Query = mnesia:dirty_select(reg_users_counter,
|
||||
[{#reg_users_counter{vhost = LServer,
|
||||
count = '$1'},
|
||||
[], ['$1']}]),
|
||||
case Query of
|
||||
[Count] -> Count;
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server,
|
||||
[{prefix, Prefix}])
|
||||
when is_binary(Prefix) ->
|
||||
Set = [{U, S}
|
||||
|| {U, S} <- get_vh_registered_users(Server),
|
||||
str:prefix(Prefix, U)],
|
||||
length(Set);
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
get_password(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
{misc:decode_base64(Scram#scram.storedkey),
|
||||
misc:decode_base64(Scram#scram.serverkey),
|
||||
misc:decode_base64(Scram#scram.salt),
|
||||
Scram#scram.iterationcount};
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read(passwd, US) of
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
<<"">>;
|
||||
_ -> <<"">>
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
case catch mnesia:dirty_read({passwd, US}) of
|
||||
[] -> false;
|
||||
[_] -> true;
|
||||
Other -> {error, Other}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> ok
|
||||
%% @doc Remove user.
|
||||
%% Note: it returns ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
US = {User, Server},
|
||||
F = fun () ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter, LServer,
|
||||
-1)
|
||||
mnesia:write(#passwd{us = US, password = Password})
|
||||
end,
|
||||
mnesia:transaction(F),
|
||||
ok.
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
|
||||
%% @doc Remove user if the provided password is correct.
|
||||
remove_user(User, Server, Password) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
try_register(User, Server, Password) ->
|
||||
US = {User, Server},
|
||||
F = fun () ->
|
||||
case mnesia:read({passwd, US}) of
|
||||
[#passwd{password = Password}]
|
||||
when is_binary(Password) ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter, LServer,
|
||||
-1),
|
||||
ok;
|
||||
[#passwd{password = Scram}]
|
||||
when is_record(Scram, scram) ->
|
||||
case is_password_scram_valid(Password, Scram) of
|
||||
true ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter,
|
||||
LServer, -1),
|
||||
ok;
|
||||
false -> not_allowed
|
||||
end;
|
||||
_ -> not_exists
|
||||
[] ->
|
||||
mnesia:write(#passwd{us = US, password = Password}),
|
||||
mnesia:dirty_update_counter(reg_users_counter, Server, 1),
|
||||
ok;
|
||||
[_] ->
|
||||
{error, exists}
|
||||
end
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} -> ok;
|
||||
{atomic, Res} -> Res;
|
||||
_ -> bad_request
|
||||
{atomic, Res} ->
|
||||
Res;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, passwd),
|
||||
case mnesia:table_info(passwd, attributes) of
|
||||
Fields ->
|
||||
convert_to_binary(Fields),
|
||||
maybe_scram_passwords(),
|
||||
ok;
|
||||
_ ->
|
||||
?INFO_MSG("Recreating passwd table", []),
|
||||
mnesia:transform_table(passwd, ignore, Fields)
|
||||
end.
|
||||
get_users(Server, []) ->
|
||||
mnesia:dirty_select(passwd,
|
||||
[{#passwd{us = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, Server}], ['$1']}]);
|
||||
get_users(Server, [{from, Start}, {to, End}])
|
||||
when is_integer(Start) and is_integer(End) ->
|
||||
get_users(Server, [{limit, End - Start + 1}, {offset, Start}]);
|
||||
get_users(Server, [{limit, Limit}, {offset, Offset}])
|
||||
when is_integer(Limit) and is_integer(Offset) ->
|
||||
case get_users(Server, []) of
|
||||
[] ->
|
||||
[];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
get_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) ->
|
||||
Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)],
|
||||
lists:keysort(1, Set);
|
||||
get_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}])
|
||||
when is_binary(Prefix) and is_integer(Start) and is_integer(End) ->
|
||||
get_users(Server, [{prefix, Prefix}, {limit, End - Start + 1},
|
||||
{offset, Start}]);
|
||||
get_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
|
||||
when is_binary(Prefix) and is_integer(Limit) and is_integer(Offset) ->
|
||||
case [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)] of
|
||||
[] ->
|
||||
[];
|
||||
Users ->
|
||||
Set = lists:keysort(1, Users),
|
||||
L = length(Set),
|
||||
Start = if Offset < 1 -> 1;
|
||||
Offset > L -> L;
|
||||
true -> Offset
|
||||
end,
|
||||
lists:sublist(Set, Start, Limit)
|
||||
end;
|
||||
get_users(Server, _) ->
|
||||
get_users(Server, []).
|
||||
|
||||
convert_to_binary(Fields) ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
passwd, Fields, set,
|
||||
fun(#passwd{us = {U, _}}) -> U end,
|
||||
fun(#passwd{us = {U, S}, password = Pass} = R) ->
|
||||
NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
|
||||
NewPass = case Pass of
|
||||
#scram{storedkey = StoredKey,
|
||||
serverkey = ServerKey,
|
||||
salt = Salt} ->
|
||||
Pass#scram{
|
||||
storedkey = iolist_to_binary(StoredKey),
|
||||
serverkey = iolist_to_binary(ServerKey),
|
||||
salt = iolist_to_binary(Salt)};
|
||||
_ ->
|
||||
iolist_to_binary(Pass)
|
||||
end,
|
||||
R#passwd{us = NewUS, password = NewPass}
|
||||
end).
|
||||
count_users(Server, []) ->
|
||||
case mnesia:dirty_select(
|
||||
reg_users_counter,
|
||||
[{#reg_users_counter{vhost = Server, count = '$1'},
|
||||
[], ['$1']}]) of
|
||||
[Count] -> Count;
|
||||
_ -> 0
|
||||
end;
|
||||
count_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) ->
|
||||
Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)],
|
||||
length(Set);
|
||||
count_users(Server, _) ->
|
||||
count_users(Server, []).
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
%% The passwords are stored scrammed in the table either if the option says so,
|
||||
%% or if at least the first password is scrammed.
|
||||
is_scrammed() ->
|
||||
OptionScram = is_option_scram(),
|
||||
FirstElement = mnesia:dirty_read(passwd,
|
||||
mnesia:dirty_first(passwd)),
|
||||
case {OptionScram, FirstElement} of
|
||||
{true, _} -> true;
|
||||
{false, [#passwd{password = Scram}]}
|
||||
when is_record(Scram, scram) ->
|
||||
true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
is_option_scram() ->
|
||||
scram ==
|
||||
ejabberd_config:get_option({auth_password_format, ?MYNAME},
|
||||
fun(V) -> V end).
|
||||
|
||||
maybe_alert_password_scrammed_without_option() ->
|
||||
case is_scrammed() andalso not is_option_scram() of
|
||||
true ->
|
||||
?ERROR_MSG("Some passwords were stored in the database "
|
||||
"as SCRAM, but 'auth_password_format' "
|
||||
"is not configured 'scram'. The option "
|
||||
"will now be considered to be 'scram'.",
|
||||
[]);
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
maybe_scram_passwords() ->
|
||||
case is_scrammed() of
|
||||
true -> scram_passwords();
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
scram_passwords() ->
|
||||
?INFO_MSG("Converting the stored passwords into "
|
||||
"SCRAM bits",
|
||||
[]),
|
||||
Fun = fun (#passwd{us = {U, S}, password = Password} = P)
|
||||
when is_binary(Password) ->
|
||||
case jid:resourceprep(Password) of
|
||||
error ->
|
||||
?ERROR_MSG(
|
||||
"SASLprep failed for "
|
||||
"password of user ~s@~s",
|
||||
[U, S]),
|
||||
P;
|
||||
_ ->
|
||||
Scram = password_to_scram(Password),
|
||||
P#passwd{password = Scram}
|
||||
end;
|
||||
(P) ->
|
||||
P
|
||||
end,
|
||||
Fields = record_info(fields, passwd),
|
||||
mnesia:transform_table(passwd, Fun, Fields).
|
||||
|
||||
password_to_scram(Password) ->
|
||||
password_to_scram(Password,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT).
|
||||
|
||||
password_to_scram(Password, IterationCount) ->
|
||||
Salt = randoms:bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
ServerKey = scram:server_key(SaltedPassword),
|
||||
#scram{storedkey = misc:encode_base64(StoredKey),
|
||||
serverkey = misc:encode_base64(ServerKey),
|
||||
salt = misc:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
case jid:resourceprep(Password) of
|
||||
error ->
|
||||
false;
|
||||
get_password(User, Server) ->
|
||||
case mnesia:dirty_read(passwd, {User, Server}) of
|
||||
[#passwd{password = Password}] ->
|
||||
{ok, Password};
|
||||
_ ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = misc:decode_base64(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
misc:decode_base64(Scram#scram.storedkey) == StoredKey
|
||||
error
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
US = {User, Server},
|
||||
F = fun () ->
|
||||
mnesia:delete({passwd, US}),
|
||||
mnesia:dirty_update_counter(reg_users_counter, Server, -1),
|
||||
ok
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, ok} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||
{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
|
||||
scram ->
|
||||
?INFO_MSG("Passwords in Mnesia table 'passwd' "
|
||||
"will be SCRAM'ed", []),
|
||||
true;
|
||||
plain ->
|
||||
false
|
||||
end;
|
||||
is_record(Pass, scram) ->
|
||||
case store_type(S) of
|
||||
scram ->
|
||||
false;
|
||||
plain ->
|
||||
?WARNING_MSG("Some passwords were stored in the database "
|
||||
"as SCRAM, but 'auth_password_format' "
|
||||
"is not configured as 'scram': some "
|
||||
"authentication mechanisms such as DIGEST-MD5 "
|
||||
"would *fail*", []),
|
||||
false
|
||||
end;
|
||||
is_list(U) orelse is_list(S) orelse is_list(Pass) ->
|
||||
?INFO_MSG("Mnesia table 'passwd' will be converted to binary", []),
|
||||
true
|
||||
end.
|
||||
|
||||
transform(#passwd{us = {U, S}, password = Pass} = R)
|
||||
when is_list(U) orelse is_list(S) orelse is_list(Pass) ->
|
||||
NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
|
||||
NewPass = case Pass of
|
||||
#scram{storedkey = StoredKey,
|
||||
serverkey = ServerKey,
|
||||
salt = Salt} ->
|
||||
Pass#scram{
|
||||
storedkey = iolist_to_binary(StoredKey),
|
||||
serverkey = iolist_to_binary(ServerKey),
|
||||
salt = iolist_to_binary(Salt)};
|
||||
_ ->
|
||||
iolist_to_binary(Pass)
|
||||
end,
|
||||
transform(R#passwd{us = NewUS, password = NewPass});
|
||||
transform(#passwd{us = {U, S}, password = Password} = P)
|
||||
when is_binary(Password) ->
|
||||
case store_type(S) of
|
||||
scram ->
|
||||
case jid:resourceprep(Password) of
|
||||
error ->
|
||||
?ERROR_MSG("SASLprep failed for password of user ~s@~s",
|
||||
[U, S]),
|
||||
P;
|
||||
_ ->
|
||||
Scram = ejabberd_auth:password_to_scram(Password),
|
||||
P#passwd{password = Scram}
|
||||
end;
|
||||
plain ->
|
||||
P
|
||||
end;
|
||||
transform(#passwd{password = Password} = P)
|
||||
when is_record(Password, scram) ->
|
||||
P.
|
||||
|
||||
export(_Server) ->
|
||||
[{passwd,
|
||||
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
|
||||
@@ -525,6 +298,3 @@ export(_Server) ->
|
||||
import(LServer, [LUser, Password, _TimeStamp]) ->
|
||||
mnesia:dirty_write(
|
||||
#passwd{us = {LUser, LServer}, password = Password}).
|
||||
|
||||
opt_type(auth_password_format) -> fun (V) -> V end;
|
||||
opt_type(_) -> [auth_password_format].
|
||||
|
||||
+12
-52
@@ -30,14 +30,8 @@
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
-export([start/1, stop/1, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, plain_password_required/0,
|
||||
-export([start/1, stop/1, check_password/4,
|
||||
user_exists/2, store_type/1, plain_password_required/1,
|
||||
opt_type/1]).
|
||||
|
||||
start(_Host) ->
|
||||
@@ -46,13 +40,6 @@ start(_Host) ->
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
set_password(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, _Digest,
|
||||
_DigestGen) ->
|
||||
check_password(User, AuthzId, Server, Password).
|
||||
|
||||
check_password(User, AuthzId, Host, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
@@ -70,26 +57,7 @@ check_password(User, AuthzId, Host, Password) ->
|
||||
end
|
||||
end.
|
||||
|
||||
try_register(_User, _Server, _Password) ->
|
||||
{error, not_allowed}.
|
||||
|
||||
dirty_get_registered_users() -> [].
|
||||
|
||||
get_vh_registered_users(_Host) -> [].
|
||||
|
||||
get_vh_registered_users(_Host, _) -> [].
|
||||
|
||||
get_vh_registered_users_number(_Host) -> 0.
|
||||
|
||||
get_vh_registered_users_number(_Host, _) -> 0.
|
||||
|
||||
get_password(_User, _Server) -> false.
|
||||
|
||||
get_password_s(_User, _Server) -> <<"">>.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
%% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed
|
||||
is_user_exists(User, Host) ->
|
||||
user_exists(User, Host) ->
|
||||
Service = get_pam_service(Host),
|
||||
UserInfo = case get_pam_userinfotype(Host) of
|
||||
username -> User;
|
||||
@@ -97,34 +65,26 @@ is_user_exists(User, Host) ->
|
||||
end,
|
||||
case catch epam:acct_mgmt(Service, UserInfo) of
|
||||
true -> true;
|
||||
_ -> false
|
||||
false -> false;
|
||||
_Err -> {error, db_failure}
|
||||
end.
|
||||
|
||||
remove_user(_User, _Server) -> {error, not_allowed}.
|
||||
plain_password_required(_) -> true.
|
||||
|
||||
remove_user(_User, _Server, _Password) -> not_allowed.
|
||||
|
||||
plain_password_required() -> true.
|
||||
|
||||
store_type() -> external.
|
||||
store_type(_) -> external.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
get_pam_service(Host) ->
|
||||
ejabberd_config:get_option(
|
||||
{pam_service, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>).
|
||||
ejabberd_config:get_option({pam_service, Host}, <<"ejabberd">>).
|
||||
|
||||
get_pam_userinfotype(Host) ->
|
||||
ejabberd_config:get_option(
|
||||
{pam_userinfotype, Host},
|
||||
fun(username) -> username;
|
||||
(jid) -> jid
|
||||
end,
|
||||
username).
|
||||
ejabberd_config:get_option({pam_userinfotype, Host}, username).
|
||||
|
||||
-spec opt_type(pam_service) -> fun((binary()) -> binary());
|
||||
(pam_userinfotype) -> fun((username | jid) -> username | jid);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(pam_service) -> fun iolist_to_binary/1;
|
||||
opt_type(pam_userinfotype) ->
|
||||
fun (username) -> username;
|
||||
|
||||
+33
-238
@@ -27,22 +27,15 @@
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, stop/1, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, export/1, import/2,
|
||||
plain_password_required/0, opt_type/1]).
|
||||
-export([start/1, stop/1, set_password/3, try_register/3,
|
||||
get_users/2, count_users/2,
|
||||
get_password/2, remove_user/2, store_type/1, export/1, import/2,
|
||||
plain_password_required/1]).
|
||||
-export([passwd_schema/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -51,260 +44,65 @@
|
||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
password = <<"">> :: binary() | scram() | '_'}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
start(_Host) ->
|
||||
ok.
|
||||
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
case is_scrammed() of
|
||||
false -> false;
|
||||
true -> true
|
||||
end.
|
||||
plain_password_required(Server) ->
|
||||
store_type(Server) == scram.
|
||||
|
||||
store_type() ->
|
||||
case is_scrammed() of
|
||||
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
|
||||
true -> scram %% allows: PLAIN SCRAM
|
||||
end.
|
||||
store_type(Server) ->
|
||||
ejabberd_auth:password_format(Server).
|
||||
|
||||
passwd_schema() ->
|
||||
{record_info(fields, passwd), #passwd{}}.
|
||||
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}} when is_binary(Password) ->
|
||||
Password /= <<"">>;
|
||||
{ok, #passwd{password = Scram}} when is_record(Scram, scram) ->
|
||||
is_password_scram_valid(Password, Scram);
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end.
|
||||
|
||||
check_password(User, AuthzId, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
Passwd = misc:decode_base64(Scram#scram.storedkey),
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
LPassword = jid:resourceprep(Password),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
LPassword == error ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
Password2 = case is_scrammed() and is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
ok = ejabberd_riak:put(#passwd{us = US, password = Password2},
|
||||
passwd_schema(),
|
||||
[{'2i', [{<<"host">>, LServer}]}])
|
||||
ejabberd_riak:put(#passwd{us = {User, Server}, password = Password},
|
||||
passwd_schema(),
|
||||
[{'2i', [{<<"host">>, Server}]}]).
|
||||
|
||||
try_register(User, Server, Password) ->
|
||||
US = {User, Server},
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), US) of
|
||||
{error, notfound} ->
|
||||
ejabberd_riak:put(#passwd{us = US, password = Password},
|
||||
passwd_schema(),
|
||||
[{'2i', [{<<"host">>, Server}]}]);
|
||||
{ok, _} ->
|
||||
{error, exists};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
try_register(User, Server, PasswordList) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Password = if is_list(PasswordList); is_binary(PasswordList) ->
|
||||
iolist_to_binary(PasswordList);
|
||||
true -> PasswordList
|
||||
end,
|
||||
LPassword = jid:resourceprep(Password),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
LPassword == error and not is_record(Password, scram) ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), US) of
|
||||
{error, notfound} ->
|
||||
Password2 = case is_scrammed() and
|
||||
is_binary(Password)
|
||||
of
|
||||
true -> password_to_scram(Password);
|
||||
false -> Password
|
||||
end,
|
||||
{atomic, ejabberd_riak:put(
|
||||
#passwd{us = US,
|
||||
password = Password2},
|
||||
passwd_schema(),
|
||||
[{'2i', [{<<"host">>, LServer}]}])};
|
||||
{ok, _} ->
|
||||
exists;
|
||||
Err ->
|
||||
{atomic, Err}
|
||||
end
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
lists:flatmap(
|
||||
fun(Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end, ejabberd_config:get_vh_by_auth_method(riak)).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, LServer) of
|
||||
get_users(Server, _) ->
|
||||
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, Server) of
|
||||
{ok, Users} ->
|
||||
Users;
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:count_by_index(passwd, <<"host">>, LServer) of
|
||||
count_users(Server, _) ->
|
||||
case ejabberd_riak:count_by_index(passwd, <<"host">>, Server) of
|
||||
{ok, N} ->
|
||||
N;
|
||||
_ ->
|
||||
0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
get_password(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}}
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
{misc:decode_base64(Scram#scram.storedkey),
|
||||
misc:decode_base64(Scram#scram.serverkey),
|
||||
misc:decode_base64(Scram#scram.salt),
|
||||
Scram#scram.iterationcount};
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}}
|
||||
when is_binary(Password) ->
|
||||
Password;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
<<"">>;
|
||||
_ -> <<"">>
|
||||
end.
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{error, notfound} -> false;
|
||||
{ok, _} -> true;
|
||||
Err -> Err
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {User, Server}) of
|
||||
{ok, Password} ->
|
||||
{ok, Password};
|
||||
{error, _} ->
|
||||
error
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
ejabberd_riak:delete(passwd, {LUser, LServer}),
|
||||
ok.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{ok, #passwd{password = Password}}
|
||||
when is_binary(Password) ->
|
||||
ejabberd_riak:delete(passwd, {LUser, LServer}),
|
||||
ok;
|
||||
{ok, #passwd{password = Scram}}
|
||||
when is_record(Scram, scram) ->
|
||||
case is_password_scram_valid(Password, Scram) of
|
||||
true ->
|
||||
ejabberd_riak:delete(passwd, {LUser, LServer}),
|
||||
ok;
|
||||
false -> not_allowed
|
||||
end;
|
||||
_ -> not_exists
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
is_scrammed() ->
|
||||
scram ==
|
||||
ejabberd_config:get_option({auth_password_format, ?MYNAME},
|
||||
fun(V) -> V end).
|
||||
|
||||
password_to_scram(Password) ->
|
||||
password_to_scram(Password,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT).
|
||||
|
||||
password_to_scram(Password, IterationCount) ->
|
||||
Salt = randoms:bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
ServerKey = scram:server_key(SaltedPassword),
|
||||
#scram{storedkey = misc:encode_base64(StoredKey),
|
||||
serverkey = misc:encode_base64(ServerKey),
|
||||
salt = misc:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
case jid:resourceprep(Password) of
|
||||
error ->
|
||||
false;
|
||||
_ ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = misc:decode_base64(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
misc:decode_base64(Scram#scram.storedkey) == StoredKey
|
||||
end.
|
||||
ejabberd_riak:delete(passwd, {User, Server}).
|
||||
|
||||
export(_Server) ->
|
||||
[{passwd,
|
||||
@@ -320,6 +118,3 @@ export(_Server) ->
|
||||
import(LServer, [LUser, Password, _TimeStamp]) ->
|
||||
Passwd = #passwd{us = {LUser, LServer}, password = Password},
|
||||
ejabberd_riak:put(Passwd, passwd_schema(), [{'2i', [{<<"host">>, LServer}]}]).
|
||||
|
||||
opt_type(auth_password_format) -> fun (V) -> V end;
|
||||
opt_type(_) -> [auth_password_format].
|
||||
|
||||
+172
-391
@@ -27,20 +27,14 @@
|
||||
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-export([start/1, stop/1, set_password/3, check_password/4,
|
||||
check_password/6, try_register/3,
|
||||
dirty_get_registered_users/0, get_vh_registered_users/1,
|
||||
get_vh_registered_users/2,
|
||||
get_vh_registered_users_number/1,
|
||||
get_vh_registered_users_number/2, get_password/2,
|
||||
get_password_s/2, is_user_exists/2, remove_user/2,
|
||||
remove_user/3, store_type/0, plain_password_required/0,
|
||||
-export([start/1, stop/1, set_password/3, try_register/3,
|
||||
get_users/2, count_users/2, get_password/2,
|
||||
remove_user/2, store_type/1, plain_password_required/1,
|
||||
convert_to_scram/1, opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -56,399 +50,84 @@ start(_Host) -> ok.
|
||||
|
||||
stop(_Host) -> ok.
|
||||
|
||||
plain_password_required() ->
|
||||
case is_scrammed() of
|
||||
false -> false;
|
||||
true -> true
|
||||
end.
|
||||
plain_password_required(Server) ->
|
||||
store_type(Server) == scram.
|
||||
|
||||
store_type() ->
|
||||
case is_scrammed() of
|
||||
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
|
||||
true -> scram %% allows: PLAIN SCRAM
|
||||
end.
|
||||
store_type(Server) ->
|
||||
ejabberd_auth:password_format(Server).
|
||||
|
||||
%% @spec (User, AuthzId, Server, Password) -> true | false | {error, Error}
|
||||
check_password(User, AuthzId, Server, Password) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
false;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
false;
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
try sql_queries:get_password_scram(LServer, LUser) of
|
||||
{selected,
|
||||
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
|
||||
Scram =
|
||||
#scram{storedkey = StoredKey,
|
||||
serverkey = ServerKey,
|
||||
salt = Salt,
|
||||
iterationcount = IterationCount},
|
||||
is_password_scram_valid_stored(Password, Scram, LUser, LServer);
|
||||
{selected, []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:_ ->
|
||||
false %% Typical error is database not accessible
|
||||
end;
|
||||
false ->
|
||||
try sql_queries:get_password(LServer, LUser) of
|
||||
{selected, [{Password}]} ->
|
||||
Password /= <<"">>;
|
||||
{selected, [{_Password2}]} ->
|
||||
false; %% Password is not correct
|
||||
{selected, []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:_ ->
|
||||
false %% Typical error is database not accessible
|
||||
end
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, AuthzId, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
|
||||
check_password(User, AuthzId, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
if AuthzId /= <<>> andalso AuthzId /= User ->
|
||||
false;
|
||||
true ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
false;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
false;
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
false ->
|
||||
try sql_queries:get_password(LServer, LUser) of
|
||||
%% Account exists, check if password is valid
|
||||
{selected, [{Passwd}]} ->
|
||||
DigRes = if Digest /= <<"">> ->
|
||||
Digest == DigestGen(Passwd);
|
||||
true -> false
|
||||
end,
|
||||
if DigRes -> true;
|
||||
true -> (Passwd == Password) and (Password /= <<"">>)
|
||||
end;
|
||||
{selected, []} ->
|
||||
false; %% Account does not exist
|
||||
{error, _Error} ->
|
||||
false %% Typical error is that table doesn't exist
|
||||
catch
|
||||
_:_ ->
|
||||
false %% Typical error is database not accessible
|
||||
end;
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User::string(), Server::string(), Password::string()) ->
|
||||
%% ok | {error, invalid_jid}
|
||||
set_password(User, Server, Password) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
LPassword = jid:resourceprep(Password),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
{error, invalid_jid};
|
||||
LPassword == error ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
Scram = password_to_scram(Password),
|
||||
case catch sql_queries:set_password_scram_t(
|
||||
LServer,
|
||||
LUser,
|
||||
Scram#scram.storedkey,
|
||||
Scram#scram.serverkey,
|
||||
Scram#scram.salt,
|
||||
Scram#scram.iterationcount
|
||||
)
|
||||
of
|
||||
{atomic, ok} -> ok;
|
||||
Other -> {error, Other}
|
||||
end;
|
||||
false ->
|
||||
case catch sql_queries:set_password_t(LServer,
|
||||
LUser, Password)
|
||||
of
|
||||
{atomic, ok} -> ok;
|
||||
Other -> {error, Other}
|
||||
end
|
||||
end
|
||||
F = fun() ->
|
||||
if is_record(Password, scram) ->
|
||||
set_password_scram_t(
|
||||
User,
|
||||
Password#scram.storedkey, Password#scram.serverkey,
|
||||
Password#scram.salt, Password#scram.iterationcount);
|
||||
true ->
|
||||
set_password_t(User, Password)
|
||||
end
|
||||
end,
|
||||
case ejabberd_sql:sql_transaction(Server, F) of
|
||||
{atomic, _} ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("failed to write to SQL table: ~p", [Reason]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
|
||||
try_register(User, Server, Password) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
LPassword = jid:resourceprep(Password),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
{error, invalid_jid};
|
||||
LPassword == error and not is_record(Password, scram) ->
|
||||
{error, invalid_password};
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
Scram = case is_record(Password, scram) of
|
||||
true -> Password;
|
||||
false -> password_to_scram(Password)
|
||||
end,
|
||||
case catch sql_queries:add_user_scram(
|
||||
LServer,
|
||||
LUser,
|
||||
Scram#scram.storedkey,
|
||||
Scram#scram.serverkey,
|
||||
Scram#scram.salt,
|
||||
Scram#scram.iterationcount
|
||||
) of
|
||||
{updated, 1} -> {atomic, ok};
|
||||
_ -> {atomic, exists}
|
||||
end;
|
||||
false ->
|
||||
case catch sql_queries:add_user(LServer, LUser,
|
||||
Password) of
|
||||
{updated, 1} -> {atomic, ok};
|
||||
_ -> {atomic, exists}
|
||||
end
|
||||
end
|
||||
Res = if is_record(Password, scram) ->
|
||||
add_user_scram(
|
||||
Server, User,
|
||||
Password#scram.storedkey, Password#scram.serverkey,
|
||||
Password#scram.salt, Password#scram.iterationcount);
|
||||
true ->
|
||||
add_user(Server, User, Password)
|
||||
end,
|
||||
case Res of
|
||||
{updated, 1} -> ok;
|
||||
_ -> {error, exists}
|
||||
end.
|
||||
|
||||
dirty_get_registered_users() ->
|
||||
Servers = ejabberd_config:get_vh_by_auth_method(sql),
|
||||
lists:flatmap(fun (Server) ->
|
||||
get_vh_registered_users(Server)
|
||||
end,
|
||||
Servers).
|
||||
|
||||
get_vh_registered_users(Server) ->
|
||||
case jid:nameprep(Server) of
|
||||
error -> [];
|
||||
<<>> -> [];
|
||||
LServer ->
|
||||
case catch sql_queries:list_users(LServer) of
|
||||
{selected, Res} ->
|
||||
[{U, LServer} || {U} <- Res];
|
||||
_ -> []
|
||||
end
|
||||
get_users(Server, Opts) ->
|
||||
case list_users(Server, Opts) of
|
||||
{selected, Res} ->
|
||||
[{U, Server} || {U} <- Res];
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, Opts) ->
|
||||
case jid:nameprep(Server) of
|
||||
error -> [];
|
||||
<<>> -> [];
|
||||
LServer ->
|
||||
case catch sql_queries:list_users(LServer, Opts) of
|
||||
{selected, Res} ->
|
||||
[{U, LServer} || {U} <- Res];
|
||||
_ -> []
|
||||
end
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
case jid:nameprep(Server) of
|
||||
error -> 0;
|
||||
<<>> -> 0;
|
||||
LServer ->
|
||||
case catch sql_queries:users_number(LServer) of
|
||||
{selected, [{Res}]} ->
|
||||
Res;
|
||||
_ -> 0
|
||||
end
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, Opts) ->
|
||||
case jid:nameprep(Server) of
|
||||
error -> 0;
|
||||
<<>> -> 0;
|
||||
LServer ->
|
||||
case catch sql_queries:users_number(LServer, Opts) of
|
||||
{selected, [{Res}]} ->
|
||||
Res;
|
||||
_Other -> 0
|
||||
end
|
||||
count_users(Server, Opts) ->
|
||||
case users_number(Server, Opts) of
|
||||
{selected, [{Res}]} ->
|
||||
Res;
|
||||
_Other -> 0
|
||||
end.
|
||||
|
||||
get_password(User, Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
false;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
false;
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
case catch sql_queries:get_password_scram(
|
||||
LServer, LUser) of
|
||||
{selected,
|
||||
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
|
||||
{misc:decode_base64(StoredKey),
|
||||
misc:decode_base64(ServerKey),
|
||||
misc:decode_base64(Salt),
|
||||
IterationCount};
|
||||
_ -> false
|
||||
end;
|
||||
false ->
|
||||
case catch sql_queries:get_password(LServer, LUser)
|
||||
of
|
||||
{selected, [{Password}]} -> Password;
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
case get_password_scram(Server, User) of
|
||||
{selected, [{Password, <<>>, <<>>, 0}]} ->
|
||||
{ok, Password};
|
||||
{selected, [{StoredKey, ServerKey, Salt, IterationCount}]} ->
|
||||
{ok, #scram{storedkey = StoredKey,
|
||||
serverkey = ServerKey,
|
||||
salt = Salt,
|
||||
iterationcount = IterationCount}};
|
||||
{selected, []} ->
|
||||
error;
|
||||
Err ->
|
||||
?ERROR_MSG("Failed to read password for user ~s@~s: ~p",
|
||||
[User, Server, Err]),
|
||||
error
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
<<"">>;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
<<"">>;
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
false ->
|
||||
case catch sql_queries:get_password(LServer, LUser) of
|
||||
{selected, [{Password}]} -> Password;
|
||||
_ -> <<"">>
|
||||
end;
|
||||
true -> <<"">>
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> true | false | {error, Error}
|
||||
is_user_exists(User, Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
false;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
false;
|
||||
true ->
|
||||
try sql_queries:get_password(LServer, LUser) of
|
||||
{selected, [{_Password}]} ->
|
||||
true; %% Account exists
|
||||
{selected, []} ->
|
||||
false; %% Account does not exist
|
||||
{error, Error} -> {error, Error}
|
||||
catch
|
||||
_:B -> {error, B}
|
||||
end
|
||||
end.
|
||||
|
||||
%% @spec (User, Server) -> ok | error
|
||||
%% @doc Remove user.
|
||||
%% Note: it may return ok even if there was some problem removing the user.
|
||||
remove_user(User, Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
error;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
error;
|
||||
true ->
|
||||
catch sql_queries:del_user(LServer, LUser),
|
||||
ok
|
||||
end.
|
||||
|
||||
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
|
||||
%% @doc Remove user if the provided password is correct.
|
||||
remove_user(User, Server, Password) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LUser = jid:nodeprep(User),
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
error;
|
||||
(LUser == <<>>) or (LServer == <<>>) ->
|
||||
error;
|
||||
true ->
|
||||
case is_scrammed() of
|
||||
true ->
|
||||
case check_password(User, <<"">>, Server, Password) of
|
||||
true ->
|
||||
remove_user(User, Server),
|
||||
ok;
|
||||
false -> not_allowed
|
||||
end;
|
||||
false ->
|
||||
F = fun () ->
|
||||
Result = sql_queries:del_user_return_password(
|
||||
LServer, LUser, Password),
|
||||
case Result of
|
||||
{selected, [{Password}]} -> ok;
|
||||
{selected, []} -> not_exists;
|
||||
_ -> not_allowed
|
||||
end
|
||||
end,
|
||||
{atomic, Result} = sql_queries:sql_transaction(
|
||||
LServer, F),
|
||||
Result
|
||||
end
|
||||
end.
|
||||
|
||||
%%%
|
||||
%%% SCRAM
|
||||
%%%
|
||||
|
||||
is_scrammed() ->
|
||||
scram ==
|
||||
ejabberd_config:get_option({auth_password_format, ?MYNAME},
|
||||
fun(V) -> V end).
|
||||
|
||||
password_to_scram(Password) ->
|
||||
password_to_scram(Password,
|
||||
?SCRAM_DEFAULT_ITERATION_COUNT).
|
||||
|
||||
password_to_scram(Password, IterationCount) ->
|
||||
Salt = randoms:bytes(?SALT_LENGTH),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
ServerKey = scram:server_key(SaltedPassword),
|
||||
#scram{storedkey = misc:encode_base64(StoredKey),
|
||||
serverkey = misc:encode_base64(ServerKey),
|
||||
salt = misc:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid_stored(Pass, {scram,Pass,<<>>,<<>>,0}, LUser, LServer) ->
|
||||
?INFO_MSG("Apparently, SQL auth method and scram password formatting are "
|
||||
"enabled, but the password of user '~s' in the 'users' table is not "
|
||||
"scrammed. You may want to execute this command: "
|
||||
"ejabberdctl convert_to_scram ~s", [LUser, LServer]),
|
||||
false;
|
||||
is_password_scram_valid_stored(Password, Scram, _, _) ->
|
||||
is_password_scram_valid(Password, Scram).
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
case jid:resourceprep(Password) of
|
||||
error ->
|
||||
false;
|
||||
_ ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = misc:decode_base64(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
misc:decode_base64(Scram#scram.storedkey) == StoredKey
|
||||
case del_user(Server, User) of
|
||||
{updated, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to delete user ~s@~s: ~p",
|
||||
[User, Server, Err]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
-define(BATCH_SIZE, 1000).
|
||||
@@ -463,6 +142,105 @@ set_password_scram_t(LUser,
|
||||
"salt=%(Salt)s",
|
||||
"iterationcount=%(IterationCount)d"]).
|
||||
|
||||
set_password_t(LUser, Password) ->
|
||||
?SQL_UPSERT_T(
|
||||
"users",
|
||||
["!username=%(LUser)s",
|
||||
"password=%(Password)s"]).
|
||||
|
||||
get_password_scram(LServer, LUser) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d"
|
||||
" from users"
|
||||
" where username=%(LUser)s")).
|
||||
|
||||
add_user_scram(LServer, LUser,
|
||||
StoredKey, ServerKey, Salt, IterationCount) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("insert into users(username, password, serverkey, salt, "
|
||||
"iterationcount) "
|
||||
"values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
|
||||
" %(Salt)s, %(IterationCount)d)")).
|
||||
|
||||
add_user(LServer, LUser, Password) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("insert into users(username, password) "
|
||||
"values (%(LUser)s, %(Password)s)")).
|
||||
|
||||
del_user(LServer, LUser) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("delete from users where username=%(LUser)s")).
|
||||
|
||||
list_users(LServer, []) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(username)s from users"));
|
||||
list_users(LServer, [{from, Start}, {to, End}])
|
||||
when is_integer(Start) and is_integer(End) ->
|
||||
list_users(LServer,
|
||||
[{limit, End - Start + 1}, {offset, Start - 1}]);
|
||||
list_users(LServer,
|
||||
[{prefix, Prefix}, {from, Start}, {to, End}])
|
||||
when is_binary(Prefix) and is_integer(Start) and
|
||||
is_integer(End) ->
|
||||
list_users(LServer,
|
||||
[{prefix, Prefix}, {limit, End - Start + 1},
|
||||
{offset, Start - 1}]);
|
||||
list_users(LServer, [{limit, Limit}, {offset, Offset}])
|
||||
when is_integer(Limit) and is_integer(Offset) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(username)s from users "
|
||||
"order by username "
|
||||
"limit %(Limit)d offset %(Offset)d"));
|
||||
list_users(LServer,
|
||||
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
|
||||
when is_binary(Prefix) and is_integer(Limit) and
|
||||
is_integer(Offset) ->
|
||||
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
|
||||
SPrefix2 = <<SPrefix/binary, $%>>,
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(username)s from users "
|
||||
"where username like %(SPrefix2)s escape '^' "
|
||||
"order by username "
|
||||
"limit %(Limit)d offset %(Offset)d")).
|
||||
|
||||
users_number(LServer) ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
fun(pgsql, _) ->
|
||||
case
|
||||
ejabberd_config:get_option(
|
||||
{pgsql_users_number_estimate, LServer}, false) of
|
||||
true ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(reltuples :: bigint)d from pg_class"
|
||||
" where oid = 'users'::regclass::oid"));
|
||||
_ ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(count(*))d from users"))
|
||||
end;
|
||||
(_Type, _) ->
|
||||
ejabberd_sql:sql_query_t(
|
||||
?SQL("select @(count(*))d from users"))
|
||||
end).
|
||||
|
||||
users_number(LServer, [{prefix, Prefix}])
|
||||
when is_binary(Prefix) ->
|
||||
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
|
||||
SPrefix2 = <<SPrefix/binary, $%>>,
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(count(*))d from users "
|
||||
"where username like %(SPrefix2)s escape '^'"));
|
||||
users_number(LServer, []) ->
|
||||
users_number(LServer).
|
||||
|
||||
convert_to_scram(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
if
|
||||
@@ -489,7 +267,7 @@ convert_to_scram(Server) ->
|
||||
"password of user ~s@~s",
|
||||
[LUser, LServer]);
|
||||
_ ->
|
||||
Scram = password_to_scram(Password),
|
||||
Scram = ejabberd_auth:password_to_scram(Password),
|
||||
set_password_scram_t(
|
||||
LUser,
|
||||
Scram#scram.storedkey,
|
||||
@@ -502,7 +280,7 @@ convert_to_scram(Server) ->
|
||||
Err -> {bad_reply, Err}
|
||||
end
|
||||
end,
|
||||
case sql_queries:sql_transaction(LServer, F) of
|
||||
case ejabberd_sql:sql_transaction(LServer, F) of
|
||||
{atomic, ok} -> ok;
|
||||
{atomic, continue} -> convert_to_scram(Server);
|
||||
{atomic, Error} -> {error, Error};
|
||||
@@ -510,5 +288,8 @@ convert_to_scram(Server) ->
|
||||
end
|
||||
end.
|
||||
|
||||
opt_type(auth_password_format) -> fun (V) -> V end;
|
||||
opt_type(_) -> [auth_password_format].
|
||||
-spec opt_type(pgsql_users_number_estimate) -> fun((boolean()) -> boolean());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(pgsql_users_number_estimate) ->
|
||||
fun (V) when is_boolean(V) -> V end;
|
||||
opt_type(_) -> [pgsql_users_number_estimate].
|
||||
|
||||
+23
-35
@@ -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);
|
||||
@@ -299,10 +297,7 @@ init([#body{attrs = Attrs}, IP, SID]) ->
|
||||
XMPPVer = get_attr('xmpp:version', Attrs),
|
||||
XMPPDomain = get_attr(to, Attrs),
|
||||
{InBuf, Opts} = case gen_mod:get_module_opt(
|
||||
XMPPDomain,
|
||||
mod_bosh, prebind,
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
false) of
|
||||
XMPPDomain, mod_bosh, prebind, false) of
|
||||
true ->
|
||||
JID = make_random_jid(XMPPDomain),
|
||||
{buf_new(XMPPDomain), [{jid, JID} | Opts2]};
|
||||
@@ -315,12 +310,9 @@ init([#body{attrs = Attrs}, IP, SID]) ->
|
||||
Opts),
|
||||
Inactivity = gen_mod:get_module_opt(XMPPDomain,
|
||||
mod_bosh, max_inactivity,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
?DEFAULT_INACTIVITY),
|
||||
MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat,
|
||||
fun(unlimited) -> unlimited;
|
||||
(N) when is_integer(N), N>0 -> N
|
||||
end, unlimited),
|
||||
unlimited),
|
||||
ShapedReceivers = buf_new(XMPPDomain, ?MAX_SHAPED_REQUESTS_QUEUE_LEN),
|
||||
State = #state{host = XMPPDomain, sid = SID, ip = IP,
|
||||
xmpp_ver = XMPPVer, el_ibuf = InBuf,
|
||||
@@ -366,7 +358,6 @@ wait_for_session(#body{attrs = Attrs} = Req, From,
|
||||
end,
|
||||
MaxPause = gen_mod:get_module_opt(State#state.host,
|
||||
mod_bosh, max_pause,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
?DEFAULT_MAXPAUSE),
|
||||
Resp = #body{attrs =
|
||||
[{sid, State#state.sid}, {wait, Wait},
|
||||
@@ -578,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,
|
||||
@@ -605,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, _}, _} ->
|
||||
@@ -637,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),
|
||||
@@ -651,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}.
|
||||
@@ -660,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;
|
||||
@@ -683,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.
|
||||
@@ -722,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
|
||||
@@ -1039,12 +1030,9 @@ buf_new(Host) ->
|
||||
buf_new(Host, unlimited).
|
||||
|
||||
buf_new(Host, Limit) ->
|
||||
QueueType = case gen_mod:get_module_opt(
|
||||
Host, mod_bosh, queue_type,
|
||||
mod_bosh:mod_opt_type(queue_type)) of
|
||||
undefined -> ejabberd_config:default_queue_type(Host);
|
||||
T -> T
|
||||
end,
|
||||
QueueType = gen_mod:get_module_opt(
|
||||
Host, mod_bosh, queue_type,
|
||||
ejabberd_config:default_queue_type(Host)),
|
||||
p1_queue:new(QueueType, Limit).
|
||||
|
||||
buf_in(Xs, Buf) ->
|
||||
@@ -1063,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) ->
|
||||
|
||||
+272
-197
@@ -29,7 +29,7 @@
|
||||
%% ejabberd_socket callbacks
|
||||
-export([start/2, start_link/2, socket_type/0]).
|
||||
%% ejabberd_config callbacks
|
||||
-export([opt_type/1, transform_listen_option/2]).
|
||||
-export([opt_type/1, listen_opt_type/1, transform_listen_option/2]).
|
||||
%% xmpp_stream_in callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
@@ -46,14 +46,15 @@
|
||||
reject_unauthenticated_packet/2, process_closed/2,
|
||||
process_terminated/2, process_info/2]).
|
||||
%% API
|
||||
-export([get_presence/1, get_subscription/2, get_subscribed/1,
|
||||
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]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
|
||||
-define(SETS, gb_sets).
|
||||
|
||||
@@ -66,7 +67,10 @@
|
||||
start(SockData, Opts) ->
|
||||
case proplists:get_value(supervisor, Opts, true) of
|
||||
true ->
|
||||
supervisor:start_child(ejabberd_c2s_sup, [SockData, Opts]);
|
||||
case supervisor:start_child(ejabberd_c2s_sup, [SockData, Opts]) of
|
||||
{ok, undefined} -> ignore;
|
||||
Res -> Res
|
||||
end;
|
||||
_ ->
|
||||
xmpp_stream_in:start(?MODULE, [SockData, Opts],
|
||||
ejabberd_config:fsm_limit_opts(Opts))
|
||||
@@ -86,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).
|
||||
|
||||
@@ -93,33 +101,27 @@ reply(Ref, Reply) ->
|
||||
get_presence(Ref) ->
|
||||
call(Ref, get_presence, 1000).
|
||||
|
||||
-spec get_subscription(jid() | ljid(), state()) -> both | from | to | none.
|
||||
get_subscription(#jid{} = From, State) ->
|
||||
get_subscription(jid:tolower(From), State);
|
||||
get_subscription(LFrom, #{pres_f := PresF, pres_t := PresT}) ->
|
||||
LBFrom = jid:remove_resource(LFrom),
|
||||
F = ?SETS:is_element(LFrom, PresF) orelse ?SETS:is_element(LBFrom, PresF),
|
||||
T = ?SETS:is_element(LFrom, PresT) orelse ?SETS:is_element(LBFrom, PresT),
|
||||
if F and T -> both;
|
||||
F -> from;
|
||||
T -> to;
|
||||
true -> none
|
||||
end.
|
||||
-spec set_presence(pid(), presence()) -> ok.
|
||||
set_presence(Ref, Pres) ->
|
||||
call(Ref, {set_presence, Pres}, 1000).
|
||||
|
||||
-spec get_subscribed(pid()) -> [ljid()].
|
||||
%% Return list of all available resources of contacts
|
||||
get_subscribed(Ref) ->
|
||||
call(Ref, get_subscribed, 1000).
|
||||
-spec resend_presence(pid()) -> ok.
|
||||
resend_presence(Pid) ->
|
||||
resend_presence(Pid, undefined).
|
||||
|
||||
-spec resend_presence(pid(), jid() | undefined) -> ok.
|
||||
resend_presence(Pid, To) ->
|
||||
route(Pid, {resend_presence, To}).
|
||||
|
||||
-spec close(pid()) -> ok;
|
||||
(state()) -> state().
|
||||
close(Ref) ->
|
||||
xmpp_stream_in:close(Ref).
|
||||
|
||||
-spec close(pid(), boolean()) -> ok;
|
||||
(state(), boolean()) -> state().
|
||||
close(Ref, SendTrailer) ->
|
||||
xmpp_stream_in:close(Ref, SendTrailer).
|
||||
-spec close(pid(), atom()) -> ok;
|
||||
(state(), atom()) -> state().
|
||||
close(Ref, Reason) ->
|
||||
xmpp_stream_in:close(Ref, Reason).
|
||||
|
||||
-spec stop(pid()) -> ok;
|
||||
(state()) -> no_return().
|
||||
@@ -183,8 +185,7 @@ host_down(Host) ->
|
||||
copy_state(#{owner := Owner} = NewState,
|
||||
#{jid := JID, resource := Resource, sid := {Time, _},
|
||||
auth_module := AuthModule, lserver := LServer,
|
||||
pres_t := PresT, pres_a := PresA,
|
||||
pres_f := PresF} = OldState) ->
|
||||
pres_a := PresA} = OldState) ->
|
||||
State1 = case OldState of
|
||||
#{pres_last := Pres, pres_timestamp := PresTS} ->
|
||||
NewState#{pres_last => Pres, pres_timestamp => PresTS};
|
||||
@@ -196,8 +197,7 @@ copy_state(#{owner := Owner} = NewState,
|
||||
conn => Conn,
|
||||
sid => {Time, Owner},
|
||||
auth_module => AuthModule,
|
||||
pres_t => PresT, pres_a => PresA,
|
||||
pres_f => PresF},
|
||||
pres_a => PresA},
|
||||
ejabberd_hooks:run_fold(c2s_copy_session, LServer, State2, [OldState]).
|
||||
|
||||
-spec open_session(state()) -> {ok, state()} | state().
|
||||
@@ -238,10 +238,17 @@ process_info(#{lserver := LServer} = State, {route, Packet}) ->
|
||||
true ->
|
||||
State1
|
||||
end;
|
||||
process_info(State, force_update_presence) ->
|
||||
process_info(#{jid := JID} = State, {resend_presence, To}) ->
|
||||
case maps:get(pres_last, State, error) of
|
||||
error -> State;
|
||||
Pres -> process_self_presence(State, Pres)
|
||||
Pres when To == undefined ->
|
||||
process_self_presence(State, Pres);
|
||||
Pres when To#jid.luser == JID#jid.luser andalso
|
||||
To#jid.lserver == JID#jid.lserver andalso
|
||||
To#jid.lresource == <<"">> ->
|
||||
process_self_presence(State, Pres);
|
||||
Pres ->
|
||||
process_presence_out(State, xmpp:set_to(Pres, To))
|
||||
end;
|
||||
process_info(State, Info) ->
|
||||
?WARNING_MSG("got unexpected info: ~p", [Info]),
|
||||
@@ -258,7 +265,8 @@ reject_unauthenticated_packet(State, _Pkt) ->
|
||||
process_closed(State, Reason) ->
|
||||
stop(State#{stop_reason => Reason}).
|
||||
|
||||
process_terminated(#{sockmod := SockMod, socket := Socket, jid := JID} = State,
|
||||
process_terminated(#{sid := SID, sockmod := SockMod, socket := Socket,
|
||||
jid := JID, user := U, server := S, resource := R} = State,
|
||||
Reason) ->
|
||||
Status = format_reason(State, Reason),
|
||||
?INFO_MSG("(~s) Closing c2s session for ~s: ~s",
|
||||
@@ -269,8 +277,11 @@ process_terminated(#{sockmod := SockMod, socket := Socket, jid := JID} = State,
|
||||
status = xmpp:mk_text(Status),
|
||||
from = JID,
|
||||
to = jid:remove_resource(JID)},
|
||||
ejabberd_sm:close_session_unset_presence(SID, U, S, R,
|
||||
Status),
|
||||
broadcast_presence_unavailable(State, Pres);
|
||||
false ->
|
||||
ejabberd_sm:close_session(SID, U, S, R),
|
||||
State
|
||||
end,
|
||||
bounce_message_queue(),
|
||||
@@ -286,48 +297,45 @@ process_terminated(State, _Reason) ->
|
||||
%%%===================================================================
|
||||
%%% xmpp_stream_in callbacks
|
||||
%%%===================================================================
|
||||
tls_options(#{lserver := LServer, tls_options := DefaultOpts}) ->
|
||||
TLSOpts1 = case ejabberd_config:get_option(
|
||||
{c2s_certfile, LServer},
|
||||
fun iolist_to_binary/1,
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, LServer},
|
||||
fun iolist_to_binary/1)) 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(
|
||||
{domain_certfile, LServer},
|
||||
ejabberd_config:get_option(
|
||||
{c2s_certfile, LServer})) of
|
||||
undefined -> DefaultOpts;
|
||||
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
|
||||
{certfile, CertFile})
|
||||
end
|
||||
end,
|
||||
TLSOpts2 = case ejabberd_config:get_option(
|
||||
{c2s_ciphers, LServer},
|
||||
fun iolist_to_binary/1) of
|
||||
{c2s_ciphers, LServer}) of
|
||||
undefined -> TLSOpts1;
|
||||
Ciphers -> lists:keystore(ciphers, 1, TLSOpts1,
|
||||
{ciphers, Ciphers})
|
||||
end,
|
||||
TLSOpts3 = case ejabberd_config:get_option(
|
||||
{c2s_protocol_options, LServer},
|
||||
fun (Options) -> str:join(Options, <<$|>>) end) of
|
||||
{c2s_protocol_options, LServer}) of
|
||||
undefined -> TLSOpts2;
|
||||
ProtoOpts -> lists:keystore(protocol_options, 1, TLSOpts2,
|
||||
{protocol_options, ProtoOpts})
|
||||
end,
|
||||
TLSOpts4 = case ejabberd_config:get_option(
|
||||
{c2s_dhfile, LServer},
|
||||
fun iolist_to_binary/1) of
|
||||
{c2s_dhfile, LServer}) of
|
||||
undefined -> TLSOpts3;
|
||||
DHFile -> lists:keystore(dhfile, 1, TLSOpts3,
|
||||
{dhfile, DHFile})
|
||||
end,
|
||||
TLSOpts5 = case ejabberd_config:get_option(
|
||||
{c2s_cafile, LServer},
|
||||
fun iolist_to_binary/1) of
|
||||
{c2s_cafile, LServer}) of
|
||||
undefined -> TLSOpts4;
|
||||
CAFile -> lists:keystore(cafile, 1, TLSOpts4,
|
||||
{cafile, CAFile})
|
||||
end,
|
||||
case ejabberd_config:get_option(
|
||||
{c2s_tls_compression, LServer},
|
||||
fun(B) when is_boolean(B) -> B end) of
|
||||
case ejabberd_config:get_option({c2s_tls_compression, LServer}) of
|
||||
undefined -> TLSOpts5;
|
||||
false -> [compression_none | TLSOpts5];
|
||||
true -> lists:delete(compression_none, TLSOpts5)
|
||||
@@ -356,13 +364,7 @@ authenticated_stream_features(#{lserver := LServer}) ->
|
||||
ejabberd_hooks:run_fold(c2s_post_auth_features, LServer, [], [LServer]).
|
||||
|
||||
sasl_mechanisms(Mechs, #{lserver := LServer}) ->
|
||||
Mechs1 = ejabberd_config:get_option(
|
||||
{disable_sasl_mechanisms, LServer},
|
||||
fun(V) when is_list(V) ->
|
||||
lists:map(fun(M) -> str:to_upper(M) end, V);
|
||||
(V) ->
|
||||
[str:to_upper(V)]
|
||||
end, []),
|
||||
Mechs1 = ejabberd_config:get_option({disable_sasl_mechanisms, LServer}, []),
|
||||
Mechs2 = case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer) of
|
||||
true -> Mechs1;
|
||||
false -> [<<"ANONYMOUS">>|Mechs1]
|
||||
@@ -400,20 +402,16 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang,
|
||||
allow ->
|
||||
State1 = open_session(State#{resource => Resource,
|
||||
sid => ejabberd_sm:make_sid()}),
|
||||
LBJID = jid:remove_resource(jid:tolower(JID)),
|
||||
PresF = ?SETS:add_element(LBJID, maps:get(pres_f, State1)),
|
||||
PresT = ?SETS:add_element(LBJID, maps:get(pres_t, State1)),
|
||||
State2 = State1#{pres_f => PresF, pres_t => PresT},
|
||||
State3 = ejabberd_hooks:run_fold(
|
||||
c2s_session_opened, LServer, State2, []),
|
||||
State2 = ejabberd_hooks:run_fold(
|
||||
c2s_session_opened, LServer, State1, []),
|
||||
?INFO_MSG("(~s) Opened c2s session for ~s",
|
||||
[SockMod:pp(Socket), jid:encode(JID)]),
|
||||
{ok, State3};
|
||||
{ok, State2};
|
||||
deny ->
|
||||
ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]),
|
||||
?INFO_MSG("(~s) Forbidden c2s session for ~s",
|
||||
[SockMod:pp(Socket), jid:encode(JID)]),
|
||||
Txt = <<"Denied by ACL">>,
|
||||
Txt = <<"Access denied by service policy">>,
|
||||
{error, xmpp:err_not_allowed(Txt, Lang), State}
|
||||
end
|
||||
end.
|
||||
@@ -500,36 +498,29 @@ handle_send(Pkt, Result, #{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(c2s_handle_send, LServer, State, [Pkt, Result]).
|
||||
|
||||
init([State, Opts]) ->
|
||||
Access = gen_mod:get_opt(access, Opts, fun acl:access_rules_validator/1, all),
|
||||
Shaper = gen_mod:get_opt(shaper, Opts, fun acl:shaper_rules_validator/1, none),
|
||||
Access = proplists:get_value(access, Opts, all),
|
||||
Shaper = proplists:get_value(shaper, Opts, none),
|
||||
TLSOpts1 = lists:filter(
|
||||
fun({certfile, _}) -> true;
|
||||
({ciphers, _}) -> true;
|
||||
({dhfile, _}) -> true;
|
||||
({cafile, _}) -> true;
|
||||
({protocol_options, _}) -> true;
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
TLSOpts2 = case lists:keyfind(protocol_options, 1, Opts) of
|
||||
false -> TLSOpts1;
|
||||
{_, OptString} ->
|
||||
ProtoOpts = str:join(OptString, <<$|>>),
|
||||
[{protocol_options, ProtoOpts}|TLSOpts1]
|
||||
end,
|
||||
TLSOpts3 = case proplists:get_bool(tls_compression, Opts) of
|
||||
false -> [compression_none | TLSOpts2];
|
||||
true -> TLSOpts2
|
||||
TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
|
||||
false -> [compression_none | TLSOpts1];
|
||||
true -> TLSOpts1
|
||||
end,
|
||||
TLSEnabled = proplists:get_bool(starttls, Opts),
|
||||
TLSRequired = proplists:get_bool(starttls_required, Opts),
|
||||
TLSVerify = proplists:get_bool(tls_verify, Opts),
|
||||
Zlib = proplists:get_bool(zlib, Opts),
|
||||
State1 = State#{tls_options => TLSOpts3,
|
||||
State1 = State#{tls_options => TLSOpts2,
|
||||
tls_required => TLSRequired,
|
||||
tls_enabled => TLSEnabled,
|
||||
tls_verify => TLSVerify,
|
||||
pres_a => ?SETS:new(),
|
||||
pres_f => ?SETS:new(),
|
||||
pres_t => ?SETS:new(),
|
||||
zlib => Zlib,
|
||||
lang => ?MYLANG,
|
||||
server => ?MYNAME,
|
||||
@@ -547,9 +538,9 @@ handle_call(get_presence, From, #{jid := JID} = State) ->
|
||||
end,
|
||||
reply(From, Pres),
|
||||
State;
|
||||
handle_call(get_subscribed, From, #{pres_f := PresF} = State) ->
|
||||
reply(From, ?SETS:to_list(PresF)),
|
||||
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]).
|
||||
@@ -560,17 +551,6 @@ handle_cast(Msg, #{lserver := LServer} = State) ->
|
||||
handle_info(Info, #{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(c2s_handle_info, LServer, State, [Info]).
|
||||
|
||||
terminate(Reason, #{sid := SID,
|
||||
user := U, server := S, resource := R,
|
||||
lserver := LServer} = State) ->
|
||||
case maps:is_key(pres_last, State) of
|
||||
true ->
|
||||
Status = format_reason(State, Reason),
|
||||
ejabberd_sm:close_session_unset_presence(SID, U, S, R, Status);
|
||||
false ->
|
||||
ejabberd_sm:close_session(SID, U, S, R)
|
||||
end,
|
||||
ejabberd_hooks:run_fold(c2s_terminated, LServer, State, [Reason]);
|
||||
terminate(Reason, #{lserver := LServer} = State) ->
|
||||
ejabberd_hooks:run_fold(c2s_terminated, LServer, State, [Reason]).
|
||||
|
||||
@@ -615,36 +595,36 @@ process_message_in(State, #message{type = T} = Msg) ->
|
||||
|
||||
-spec process_presence_in(state(), presence()) -> {boolean(), state()}.
|
||||
process_presence_in(#{lserver := LServer, pres_a := PresA} = State0,
|
||||
#presence{from = From, to = To, type = T} = Pres) ->
|
||||
#presence{from = From, type = T} = Pres) ->
|
||||
State = ejabberd_hooks:run_fold(c2s_presence_in, LServer, State0, [Pres]),
|
||||
case T of
|
||||
probe ->
|
||||
NewState = add_to_pres_a(State, From),
|
||||
route_probe_reply(From, To, NewState),
|
||||
{false, NewState};
|
||||
route_probe_reply(From, State),
|
||||
{false, State};
|
||||
error ->
|
||||
A = ?SETS:del_element(jid:tolower(From), PresA),
|
||||
{true, State#{pres_a => A}};
|
||||
_ ->
|
||||
case privacy_check_packet(State, Pres, in) of
|
||||
allow ->
|
||||
NewState = add_to_pres_a(State, From),
|
||||
{true, NewState};
|
||||
{true, State};
|
||||
deny ->
|
||||
{false, State}
|
||||
end
|
||||
end.
|
||||
|
||||
-spec route_probe_reply(jid(), jid(), state()) -> ok.
|
||||
route_probe_reply(From, To, #{lserver := LServer, pres_f := PresF,
|
||||
pres_last := LastPres,
|
||||
pres_timestamp := TS} = State) ->
|
||||
LFrom = jid:tolower(From),
|
||||
LBFrom = jid:remove_resource(LFrom),
|
||||
case ?SETS:is_element(LFrom, PresF)
|
||||
orelse ?SETS:is_element(LBFrom, PresF) of
|
||||
true ->
|
||||
%% To is my JID
|
||||
-spec route_probe_reply(jid(), state()) -> ok.
|
||||
route_probe_reply(From, #{jid := To,
|
||||
pres_last := LastPres,
|
||||
pres_timestamp := TS} = State) ->
|
||||
{LUser, LServer, LResource} = jid:tolower(To),
|
||||
IsAnotherResource = case jid:tolower(From) of
|
||||
{LUser, LServer, R} when R /= LResource -> true;
|
||||
_ -> false
|
||||
end,
|
||||
Subscription = get_subscription(To, From),
|
||||
if IsAnotherResource orelse
|
||||
Subscription == both orelse Subscription == from ->
|
||||
Packet = xmpp_util:add_delay_info(LastPres, To, TS),
|
||||
case privacy_check_packet(State, Packet, out) of
|
||||
deny ->
|
||||
@@ -653,19 +633,12 @@ route_probe_reply(From, To, #{lserver := LServer, pres_f := PresF,
|
||||
ejabberd_hooks:run(presence_probe_hook,
|
||||
LServer,
|
||||
[From, To, self()]),
|
||||
%% Don't route a presence probe to oneself
|
||||
case From == To of
|
||||
false ->
|
||||
ejabberd_router:route(
|
||||
xmpp:set_from_to(Packet, To, From));
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
ejabberd_router:route(xmpp:set_from_to(Packet, To, From))
|
||||
end;
|
||||
false ->
|
||||
true ->
|
||||
ok
|
||||
end;
|
||||
route_probe_reply(_, _, _) ->
|
||||
route_probe_reply(_, _) ->
|
||||
ok.
|
||||
|
||||
-spec process_presence_out(state(), presence()) -> state().
|
||||
@@ -681,13 +654,11 @@ process_presence_out(#{user := User, server := Server, lserver := LServer,
|
||||
send_error(State, Pres, Err);
|
||||
allow when Type == subscribe; Type == subscribed;
|
||||
Type == unsubscribe; Type == unsubscribed ->
|
||||
Access = gen_mod:get_module_opt(LServer, mod_roster, access,
|
||||
fun(A) when is_atom(A) -> A end,
|
||||
all),
|
||||
Access = gen_mod:get_module_opt(LServer, mod_roster, access, all),
|
||||
MyBareJID = jid:remove_resource(JID),
|
||||
case acl:match_rule(LServer, Access, MyBareJID) of
|
||||
deny ->
|
||||
ErrText = <<"Denied by ACL">>,
|
||||
ErrText = <<"Access denied by service policy">>,
|
||||
Err = xmpp:err_forbidden(ErrText, Lang),
|
||||
send_error(State, Pres, Err);
|
||||
allow ->
|
||||
@@ -703,11 +674,22 @@ process_presence_out(#{user := User, server := Server, lserver := LServer,
|
||||
State;
|
||||
allow ->
|
||||
ejabberd_router:route(Pres),
|
||||
A = case Type of
|
||||
available -> ?SETS:add_element(LTo, PresA);
|
||||
unavailable -> ?SETS:del_element(LTo, PresA)
|
||||
end,
|
||||
State#{pres_a => A}
|
||||
LBareTo = jid:remove_resource(LTo),
|
||||
LBareFrom = jid:remove_resource(jid:tolower(From)),
|
||||
if LBareTo /= LBareFrom ->
|
||||
Subscription = get_subscription(From, To),
|
||||
if Subscription /= both andalso Subscription /= from ->
|
||||
A = case Type of
|
||||
available -> ?SETS:add_element(LTo, PresA);
|
||||
unavailable -> ?SETS:del_element(LTo, PresA)
|
||||
end,
|
||||
State#{pres_a => A};
|
||||
true ->
|
||||
State
|
||||
end;
|
||||
true ->
|
||||
State
|
||||
end
|
||||
end.
|
||||
|
||||
-spec process_self_presence(state(), presence()) -> state().
|
||||
@@ -744,24 +726,81 @@ update_priority(#{ip := IP, conn := Conn, auth_module := AuthMod,
|
||||
ejabberd_sm:set_presence(SID, U, S, R, Priority, Pres, Info).
|
||||
|
||||
-spec broadcast_presence_unavailable(state(), presence()) -> state().
|
||||
broadcast_presence_unavailable(#{pres_a := PresA} = State, Pres) ->
|
||||
JIDs = filter_blocked(State, Pres, PresA),
|
||||
broadcast_presence_unavailable(#{jid := JID, pres_a := PresA} = State, Pres) ->
|
||||
#jid{luser = LUser, lserver = LServer} = JID,
|
||||
BareJID = jid:remove_resource(JID),
|
||||
Items1 = ejabberd_hooks:run_fold(roster_get, LServer,
|
||||
[], [{LUser, LServer}]),
|
||||
Items2 = ?SETS:fold(
|
||||
fun(LJID, Items) ->
|
||||
[#roster{jid = LJID, subscription = from}|Items]
|
||||
end, Items1, PresA),
|
||||
JIDs = lists:foldl(
|
||||
fun(#roster{jid = LJID, subscription = Sub}, Tos)
|
||||
when Sub == both orelse Sub == from ->
|
||||
To = jid:make(LJID),
|
||||
P = xmpp:set_to(Pres, jid:make(LJID)),
|
||||
case privacy_check_packet(State, P, out) of
|
||||
allow -> [To|Tos];
|
||||
deny -> Tos
|
||||
end;
|
||||
(_, Tos) ->
|
||||
Tos
|
||||
end, [BareJID], Items2),
|
||||
route_multiple(State, JIDs, Pres),
|
||||
State#{pres_a => ?SETS:new()}.
|
||||
|
||||
-spec broadcast_presence_available(state(), presence(), boolean()) -> state().
|
||||
broadcast_presence_available(#{pres_a := PresA, pres_f := PresF,
|
||||
pres_t := PresT, jid := JID} = State,
|
||||
broadcast_presence_available(#{jid := JID} = State,
|
||||
Pres, _FromUnavailable = true) ->
|
||||
Probe = #presence{from = JID, type = probe},
|
||||
TJIDs = filter_blocked(State, Probe, PresT),
|
||||
FJIDs = filter_blocked(State, Pres, PresF),
|
||||
#jid{luser = LUser, lserver = LServer} = JID,
|
||||
BareJID = jid:remove_resource(JID),
|
||||
Items = ejabberd_hooks:run_fold(roster_get, LServer,
|
||||
[], [{LUser, LServer}]),
|
||||
{FJIDs, TJIDs} =
|
||||
lists:foldl(
|
||||
fun(#roster{jid = LJID, subscription = Sub}, {F, T}) ->
|
||||
To = jid:make(LJID),
|
||||
F1 = if Sub == both orelse Sub == from ->
|
||||
Pres1 = xmpp:set_to(Pres, To),
|
||||
case privacy_check_packet(State, Pres1, out) of
|
||||
allow -> [To|F];
|
||||
deny -> F
|
||||
end;
|
||||
true -> F
|
||||
end,
|
||||
T1 = if Sub == both orelse Sub == to ->
|
||||
Probe1 = xmpp:set_to(Probe, To),
|
||||
case privacy_check_packet(State, Probe1, out) of
|
||||
allow -> [To|T];
|
||||
deny -> T
|
||||
end;
|
||||
true -> T
|
||||
end,
|
||||
{F1, T1}
|
||||
end, {[BareJID], [BareJID]}, Items),
|
||||
route_multiple(State, TJIDs, Probe),
|
||||
route_multiple(State, FJIDs, Pres),
|
||||
State#{pres_a => ?SETS:union(PresA, PresF)};
|
||||
broadcast_presence_available(#{pres_a := PresA, pres_f := PresF} = State,
|
||||
State;
|
||||
broadcast_presence_available(#{jid := JID} = State,
|
||||
Pres, _FromUnavailable = false) ->
|
||||
JIDs = filter_blocked(State, Pres, ?SETS:intersection(PresA, PresF)),
|
||||
#jid{luser = LUser, lserver = LServer} = JID,
|
||||
BareJID = jid:remove_resource(JID),
|
||||
Items = ejabberd_hooks:run_fold(
|
||||
roster_get, LServer, [], [{LUser, LServer}]),
|
||||
JIDs = lists:foldl(
|
||||
fun(#roster{jid = LJID, subscription = Sub}, Tos)
|
||||
when Sub == both orelse Sub == from ->
|
||||
To = jid:make(LJID),
|
||||
P = xmpp:set_to(Pres, jid:make(LJID)),
|
||||
case privacy_check_packet(State, P, out) of
|
||||
allow -> [To|Tos];
|
||||
deny -> Tos
|
||||
end;
|
||||
(_, Tos) ->
|
||||
Tos
|
||||
end, [BareJID], Items),
|
||||
route_multiple(State, JIDs, Pres),
|
||||
State.
|
||||
|
||||
@@ -789,45 +828,32 @@ get_priority_from_presence(#presence{priority = Prio}) ->
|
||||
_ -> Prio
|
||||
end.
|
||||
|
||||
-spec filter_blocked(state(), presence(), ?SETS:set()) -> [jid()].
|
||||
filter_blocked(#{jid := From} = State, Pres, LJIDSet) ->
|
||||
?SETS:fold(
|
||||
fun(LJID, Acc) ->
|
||||
To = jid:make(LJID),
|
||||
Pkt = xmpp:set_from_to(Pres, From, To),
|
||||
case privacy_check_packet(State, Pkt, out) of
|
||||
allow -> [To|Acc];
|
||||
deny -> Acc
|
||||
end
|
||||
end, [], LJIDSet).
|
||||
|
||||
-spec route_multiple(state(), [jid()], stanza()) -> ok.
|
||||
route_multiple(#{lserver := LServer}, JIDs, Pkt) ->
|
||||
From = xmpp:get_from(Pkt),
|
||||
ejabberd_router_multicast:route_multicast(From, LServer, JIDs, Pkt).
|
||||
|
||||
get_subscription(#jid{luser = LUser, lserver = LServer}, JID) ->
|
||||
{Subscription, _} = ejabberd_hooks:run_fold(
|
||||
roster_get_jid_info, LServer, {none, []},
|
||||
[LUser, LServer, JID]),
|
||||
Subscription.
|
||||
|
||||
-spec resource_conflict_action(binary(), binary(), binary()) ->
|
||||
{accept_resource, binary()} | closenew.
|
||||
resource_conflict_action(U, S, R) ->
|
||||
OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of
|
||||
true ->
|
||||
ejabberd_config:get_option(
|
||||
{resource_conflict, S},
|
||||
fun(setresource) -> setresource;
|
||||
(closeold) -> closeold;
|
||||
(closenew) -> closenew;
|
||||
(acceptnew) -> acceptnew
|
||||
end);
|
||||
{resource_conflict, S}, acceptnew);
|
||||
false ->
|
||||
acceptnew
|
||||
end,
|
||||
Option = case OptionRaw of
|
||||
setresource -> setresource;
|
||||
closeold ->
|
||||
acceptnew; %% ejabberd_sm will close old session
|
||||
closeold -> acceptnew; %% ejabberd_sm will close old session
|
||||
closenew -> closenew;
|
||||
acceptnew -> acceptnew;
|
||||
_ -> acceptnew %% default ejabberd behavior
|
||||
acceptnew -> acceptnew
|
||||
end,
|
||||
case Option of
|
||||
acceptnew -> {accept_resource, R};
|
||||
@@ -867,13 +893,17 @@ get_conn_type(State) ->
|
||||
-spec fix_from_to(xmpp_element(), state()) -> stanza().
|
||||
fix_from_to(Pkt, #{jid := JID}) when ?is_stanza(Pkt) ->
|
||||
#jid{luser = U, lserver = S, lresource = R} = JID,
|
||||
From = xmpp:get_from(Pkt),
|
||||
From1 = case jid:tolower(From) of
|
||||
{U, S, R} -> JID;
|
||||
{U, S, _} -> jid:replace_resource(JID, From#jid.resource);
|
||||
_ -> From
|
||||
end,
|
||||
xmpp:set_from_to(Pkt, From1, JID);
|
||||
case xmpp:get_from(Pkt) of
|
||||
undefined ->
|
||||
Pkt;
|
||||
From ->
|
||||
From1 = case jid:tolower(From) of
|
||||
{U, S, R} -> JID;
|
||||
{U, S, _} -> jid:replace_resource(JID, From#jid.resource);
|
||||
_ -> From
|
||||
end,
|
||||
xmpp:set_from_to(Pkt, From1, JID)
|
||||
end;
|
||||
fix_from_to(Pkt, _State) ->
|
||||
Pkt.
|
||||
|
||||
@@ -886,30 +916,6 @@ change_shaper(#{shaper := ShaperName, ip := IP, lserver := LServer,
|
||||
LServer),
|
||||
xmpp_stream_in:change_shaper(State, Shaper).
|
||||
|
||||
-spec add_to_pres_a(state(), jid()) -> state().
|
||||
add_to_pres_a(#{pres_a := PresA, pres_f := PresF} = State, From) ->
|
||||
LFrom = jid:tolower(From),
|
||||
LBFrom = jid:remove_resource(LFrom),
|
||||
case (?SETS):is_element(LFrom, PresA) orelse
|
||||
(?SETS):is_element(LBFrom, PresA) of
|
||||
true ->
|
||||
State;
|
||||
false ->
|
||||
case (?SETS):is_element(LFrom, PresF) of
|
||||
true ->
|
||||
A = (?SETS):add_element(LFrom, PresA),
|
||||
State#{pres_a => A};
|
||||
false ->
|
||||
case (?SETS):is_element(LBFrom, PresF) of
|
||||
true ->
|
||||
A = (?SETS):add_element(LBFrom, PresA),
|
||||
State#{pres_a => A};
|
||||
false ->
|
||||
State
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
-spec format_reason(state(), term()) -> binary().
|
||||
format_reason(#{stop_reason := Reason}, _) ->
|
||||
xmpp_stream_in:format_error(Reason);
|
||||
@@ -925,11 +931,20 @@ format_reason(_, _) ->
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
opt_type(domain_certfile) -> fun iolist_to_binary/1;
|
||||
opt_type(c2s_certfile) -> fun iolist_to_binary/1;
|
||||
-type resource_conflict() :: setresource | closeold | closenew | acceptnew.
|
||||
-spec opt_type(c2s_certfile) -> fun((binary()) -> binary());
|
||||
(c2s_ciphers) -> fun((binary()) -> binary());
|
||||
(c2s_dhfile) -> fun((binary()) -> binary());
|
||||
(c2s_cafile) -> fun((binary()) -> binary());
|
||||
(c2s_protocol_options) -> fun(([binary()]) -> binary());
|
||||
(c2s_tls_compression) -> fun((boolean()) -> boolean());
|
||||
(resource_conflict) -> fun((resource_conflict()) -> resource_conflict());
|
||||
(disable_sasl_mechanisms) -> fun((binary() | [binary()]) -> [binary()]);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(c2s_certfile) -> fun misc:try_read_file/1;
|
||||
opt_type(c2s_ciphers) -> fun iolist_to_binary/1;
|
||||
opt_type(c2s_dhfile) -> fun iolist_to_binary/1;
|
||||
opt_type(c2s_cafile) -> fun iolist_to_binary/1;
|
||||
opt_type(c2s_dhfile) -> fun misc:try_read_file/1;
|
||||
opt_type(c2s_cafile) -> fun misc:try_read_file/1;
|
||||
opt_type(c2s_protocol_options) ->
|
||||
fun (Options) -> str:join(Options, <<"|">>) end;
|
||||
opt_type(c2s_tls_compression) ->
|
||||
@@ -948,6 +963,66 @@ opt_type(disable_sasl_mechanisms) ->
|
||||
(V) -> [str:to_upper(V)]
|
||||
end;
|
||||
opt_type(_) ->
|
||||
[domain_certfile, c2s_certfile, c2s_ciphers, c2s_cafile,
|
||||
[c2s_certfile, c2s_ciphers, c2s_cafile,
|
||||
c2s_protocol_options, c2s_tls_compression, resource_conflict,
|
||||
disable_sasl_mechanisms].
|
||||
|
||||
-spec listen_opt_type(access) -> fun((any()) -> any());
|
||||
(shaper) -> fun((any()) -> any());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(cafile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(tls) -> fun((boolean()) -> boolean());
|
||||
(starttls) -> fun((boolean()) -> boolean());
|
||||
(tls_verify) -> fun((boolean()) -> boolean());
|
||||
(zlib) -> fun((boolean()) -> boolean());
|
||||
(supervisor) -> fun((boolean()) -> boolean());
|
||||
(max_stanza_size) -> fun((timeout()) -> timeout());
|
||||
(max_fsm_queue) -> fun((timeout()) -> timeout());
|
||||
(stream_management) -> fun((boolean()) -> boolean());
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(access) -> fun acl:access_rules_validator/1;
|
||||
listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
|
||||
listen_opt_type(certfile) ->
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(ciphers) -> opt_type(c2s_ciphers);
|
||||
listen_opt_type(dhfile) -> opt_type(c2s_dhfile);
|
||||
listen_opt_type(cafile) -> opt_type(c2s_cafile);
|
||||
listen_opt_type(protocol_options) -> opt_type(c2s_protocol_options);
|
||||
listen_opt_type(tls_compression) -> opt_type(c2s_tls_compression);
|
||||
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(starttls) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(starttls_required) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(tls_verify) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(zlib) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(max_stanza_size) ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
listen_opt_type(max_fsm_queue) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
%% The following hack should be removed in future releases: it is intended
|
||||
%% for backward compatibility with ejabberd 17.01 or older
|
||||
listen_opt_type(stream_management) ->
|
||||
?WARNING_MSG("listening option 'stream_management' is deprecated: "
|
||||
"use mod_stream_mgmt module", []),
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(O) ->
|
||||
case mod_stream_mgmt:mod_opt_type(O) of
|
||||
L when is_list(L) ->
|
||||
[access, shaper, certfile, ciphers, dhfile, cafile,
|
||||
protocol_options, tls, tls_compression, starttls,
|
||||
starttls_required, tls_verify, zlib, max_fsm_queue];
|
||||
VFun ->
|
||||
?WARNING_MSG("listening option '~s' is deprecated: use '~s' "
|
||||
"option from mod_stream_mgmt module", [O, O]),
|
||||
VFun
|
||||
end.
|
||||
|
||||
@@ -26,24 +26,20 @@
|
||||
|
||||
-module(ejabberd_c2s_config).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-author('mremond@process-one.net').
|
||||
|
||||
-export([get_c2s_limits/0, opt_type/1]).
|
||||
-export([get_c2s_limits/0]).
|
||||
|
||||
%% Get first c2s configuration limitations to apply it to other c2s
|
||||
%% connectors.
|
||||
get_c2s_limits() ->
|
||||
case ejabberd_config:get_option(listen, fun(V) -> V end) of
|
||||
undefined -> [];
|
||||
C2SFirstListen ->
|
||||
case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
|
||||
false -> [];
|
||||
{value, {_Port, ejabberd_c2s, Opts}} ->
|
||||
select_opts_values(Opts)
|
||||
end
|
||||
C2SFirstListen = ejabberd_config:get_option(listen, []),
|
||||
case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
|
||||
false -> [];
|
||||
{value, {_Port, ejabberd_c2s, Opts}} ->
|
||||
select_opts_values(Opts)
|
||||
end.
|
||||
|
||||
%% Only get access, shaper and max_stanza_size values
|
||||
|
||||
select_opts_values(Opts) ->
|
||||
@@ -65,6 +61,3 @@ select_opts_values([{max_stanza_size, Value} | Opts],
|
||||
[{max_stanza_size, Value} | SelectedValues]);
|
||||
select_opts_values([_Opt | Opts], SelectedValues) ->
|
||||
select_opts_values(Opts, SelectedValues).
|
||||
|
||||
opt_type(listen) -> fun (V) -> V end;
|
||||
opt_type(_) -> [listen].
|
||||
|
||||
+20
-32
@@ -350,12 +350,7 @@ do_create_image(Key) ->
|
||||
end.
|
||||
|
||||
get_prog_name() ->
|
||||
case ejabberd_config:get_option(
|
||||
captcha_cmd,
|
||||
fun(FileName) ->
|
||||
F = iolist_to_binary(FileName),
|
||||
if F /= <<"">> -> F end
|
||||
end) of
|
||||
case ejabberd_config:get_option(captcha_cmd) of
|
||||
undefined ->
|
||||
?DEBUG("The option captcha_cmd is not configured, "
|
||||
"but some module wants to use the CAPTCHA "
|
||||
@@ -367,10 +362,7 @@ get_prog_name() ->
|
||||
end.
|
||||
|
||||
get_url(Str) ->
|
||||
CaptchaHost = ejabberd_config:get_option(
|
||||
captcha_host,
|
||||
fun iolist_to_binary/1,
|
||||
<<"">>),
|
||||
CaptchaHost = ejabberd_config:get_option(captcha_host, <<"">>),
|
||||
case str:tokens(CaptchaHost, <<":">>) of
|
||||
[Host] ->
|
||||
<<"http://", Host/binary, "/captcha/", Str/binary>>;
|
||||
@@ -395,39 +387,32 @@ get_transfer_protocol(PortString) ->
|
||||
get_captcha_transfer_protocol(PortListeners).
|
||||
|
||||
get_port_listeners(PortNumber) ->
|
||||
AllListeners = ejabberd_config:get_option(listen, fun(V) -> V end),
|
||||
lists:filter(fun (Listener) when is_list(Listener) ->
|
||||
case proplists:get_value(port, Listener) of
|
||||
PortNumber -> true;
|
||||
_ -> false
|
||||
end;
|
||||
(_) -> false
|
||||
end,
|
||||
AllListeners).
|
||||
AllListeners = ejabberd_config:get_option(listen, []),
|
||||
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).
|
||||
|
||||
is_limited(undefined) -> false;
|
||||
is_limited(Limiter) ->
|
||||
case ejabberd_config:get_option(
|
||||
captcha_limit,
|
||||
fun(I) when is_integer(I), I > 0 -> I end) of
|
||||
case ejabberd_config:get_option(captcha_limit) of
|
||||
undefined -> false;
|
||||
Int ->
|
||||
case catch gen_server:call(?MODULE,
|
||||
@@ -538,6 +523,10 @@ clean_treap(Treap, CleanPriority) ->
|
||||
now_priority() ->
|
||||
-p1_time_compat:system_time(micro_seconds).
|
||||
|
||||
-spec opt_type(captcha_cmd) -> fun((binary()) -> binary());
|
||||
(captcha_host) -> fun((binary()) -> binary());
|
||||
(captcha_limit) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(captcha_cmd) ->
|
||||
fun (FileName) ->
|
||||
F = iolist_to_binary(FileName), if F /= <<"">> -> F end
|
||||
@@ -545,6 +534,5 @@ opt_type(captcha_cmd) ->
|
||||
opt_type(captcha_host) -> fun iolist_to_binary/1;
|
||||
opt_type(captcha_limit) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(listen) -> fun (V) -> V end;
|
||||
opt_type(_) ->
|
||||
[captcha_cmd, captcha_host, captcha_limit, listen].
|
||||
[captcha_cmd, captcha_host, captcha_limit].
|
||||
|
||||
+152
-85
@@ -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,119 +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]).
|
||||
-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.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Backend dependent API
|
||||
%%%===================================================================
|
||||
-spec get_nodes() -> [node()].
|
||||
get_nodes() ->
|
||||
Mod = get_mod(),
|
||||
Mod:get_nodes().
|
||||
|
||||
-spec get_known_nodes() -> [node()].
|
||||
get_known_nodes() ->
|
||||
Mod = get_mod(),
|
||||
Mod:get_known_nodes().
|
||||
|
||||
-spec join(node()) -> ok | {error, any()}.
|
||||
|
||||
join(Node) ->
|
||||
case {node(), net_adm:ping(Node)} of
|
||||
{Node, _} ->
|
||||
{error, {not_master, Node}};
|
||||
{_, pong} ->
|
||||
application:stop(ejabberd),
|
||||
application:stop(mnesia),
|
||||
mnesia:delete_schema([node()]),
|
||||
application:start(mnesia),
|
||||
mnesia:change_config(extra_db_nodes, [Node]),
|
||||
mnesia:change_table_copy_type(schema, node(), disc_copies),
|
||||
spawn(fun() ->
|
||||
lists:foreach(fun(Table) ->
|
||||
Type = call(Node, mnesia, table_info, [Table, storage_type]),
|
||||
mnesia:add_table_copy(Table, node(), Type)
|
||||
end, mnesia:system_info(tables)--[schema])
|
||||
end),
|
||||
application:start(ejabberd);
|
||||
_ ->
|
||||
{error, {no_ping, Node}}
|
||||
end.
|
||||
Mod = get_mod(),
|
||||
Mod:join(Node).
|
||||
|
||||
-spec leave(node()) -> ok | {error, any()}.
|
||||
|
||||
leave(Node) ->
|
||||
case {node(), net_adm:ping(Node)} of
|
||||
{Node, _} ->
|
||||
Cluster = get_nodes()--[Node],
|
||||
leave(Cluster, Node);
|
||||
{_, pong} ->
|
||||
rpc:call(Node, ?MODULE, leave, [Node], 10000);
|
||||
{_, pang} ->
|
||||
case mnesia:del_table_copy(schema, Node) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, Reason} -> {error, Reason}
|
||||
end
|
||||
end.
|
||||
leave([], Node) ->
|
||||
{error, {no_cluster, Node}};
|
||||
leave([Master|_], Node) ->
|
||||
application:stop(ejabberd),
|
||||
application:stop(mnesia),
|
||||
call(Master, mnesia, del_table_copy, [schema, Node]),
|
||||
spawn(fun() ->
|
||||
mnesia:delete_schema([node()]),
|
||||
erlang:halt(0)
|
||||
end),
|
||||
ok.
|
||||
Mod = get_mod(),
|
||||
Mod:leave(Node).
|
||||
|
||||
-spec node_id() -> binary().
|
||||
node_id() ->
|
||||
integer_to_binary(erlang:phash2(node())).
|
||||
Mod = get_mod(),
|
||||
Mod:node_id().
|
||||
|
||||
-spec get_node_by_id(binary()) -> node().
|
||||
get_node_by_id(Hash) ->
|
||||
try binary_to_integer(Hash) of
|
||||
I -> match_node_id(I)
|
||||
catch _:_ ->
|
||||
node()
|
||||
get_node_by_id(ID) ->
|
||||
Mod = get_mod(),
|
||||
Mod:get_node_by_id(ID).
|
||||
|
||||
-spec send(dst(), term()) -> boolean().
|
||||
send(Dst, Msg) ->
|
||||
IsLocal = case Dst of
|
||||
{_, Node} -> Node == node();
|
||||
Pid when is_pid(Pid) -> node(Pid) == node();
|
||||
Name when is_atom(Name) -> true;
|
||||
_ -> false
|
||||
end,
|
||||
if IsLocal ->
|
||||
erlang:send(Dst, Msg),
|
||||
true;
|
||||
true ->
|
||||
Mod = get_mod(),
|
||||
Mod:send(Dst, Msg)
|
||||
end.
|
||||
|
||||
-spec wait_for_sync(timeout()) -> ok | {error, any()}.
|
||||
wait_for_sync(Timeout) ->
|
||||
Mod = get_mod(),
|
||||
Mod:wait_for_sync(Timeout).
|
||||
|
||||
-spec subscribe() -> ok.
|
||||
subscribe() ->
|
||||
subscribe(self()).
|
||||
|
||||
-spec subscribe(dst()) -> ok.
|
||||
subscribe(Proc) ->
|
||||
Mod = get_mod(),
|
||||
Mod:subscribe(Proc).
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server API
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
Ticktime = ejabberd_config:get_option(net_ticktime, 60),
|
||||
Nodes = ejabberd_config:get_option(cluster_nodes, []),
|
||||
net_kernel:set_net_ticktime(Ticktime),
|
||||
lists:foreach(fun(Node) ->
|
||||
net_kernel:connect_node(Node)
|
||||
end, Nodes),
|
||||
Mod = get_mod(),
|
||||
case Mod:init() of
|
||||
ok ->
|
||||
Mod:subscribe(?MODULE),
|
||||
{ok, #state{}};
|
||||
{error, Reason} ->
|
||||
{stop, Reason}
|
||||
end.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({node_up, Node}, State) ->
|
||||
?INFO_MSG("Node ~s has joined", [Node]),
|
||||
{noreply, State};
|
||||
handle_info({node_down, Node}, State) ->
|
||||
?INFO_MSG("Node ~s has left", [Node]),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-spec match_node_id(integer()) -> node().
|
||||
match_node_id(I) ->
|
||||
match_node_id(I, get_nodes()).
|
||||
get_mod() ->
|
||||
Backend = ejabberd_config:get_option(cluster_backend, mnesia),
|
||||
list_to_atom("ejabberd_cluster_" ++ atom_to_list(Backend)).
|
||||
|
||||
-spec match_node_id(integer(), [node()]) -> node().
|
||||
match_node_id(I, [Node|Nodes]) ->
|
||||
case erlang:phash2(Node) of
|
||||
I -> Node;
|
||||
_ -> match_node_id(I, Nodes)
|
||||
end;
|
||||
match_node_id(_I, []) ->
|
||||
node().
|
||||
rpc_timeout() ->
|
||||
timer:seconds(ejabberd_config:get_option(rpc_timeout, 5)).
|
||||
|
||||
opt_type(net_ticktime) ->
|
||||
fun (P) when is_integer(P), P > 0 -> P end;
|
||||
opt_type(cluster_nodes) ->
|
||||
fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
|
||||
opt_type(rpc_timeout) ->
|
||||
fun (T) when is_integer(T), T > 0 -> T end;
|
||||
opt_type(cluster_backend) ->
|
||||
fun (T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
opt_type(_) ->
|
||||
[rpc_timeout, cluster_backend, cluster_nodes, net_ticktime].
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_cluster_mnesia.erl
|
||||
%%% Author : Christophe Romain <christophe.romain@process-one.net>
|
||||
%%% Purpose : Ejabberd clustering management via Mnesia
|
||||
%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_cluster_mnesia).
|
||||
-behaviour(ejabberd_cluster).
|
||||
|
||||
%% API
|
||||
-export([init/0, get_nodes/0, join/1, leave/1,
|
||||
get_known_nodes/0, node_id/0, get_node_by_id/1,
|
||||
send/2, wait_for_sync/1, subscribe/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-spec init() -> ok.
|
||||
init() ->
|
||||
ok.
|
||||
|
||||
-spec get_nodes() -> [node()].
|
||||
|
||||
get_nodes() ->
|
||||
mnesia:system_info(running_db_nodes).
|
||||
|
||||
-spec get_known_nodes() -> [node()].
|
||||
|
||||
get_known_nodes() ->
|
||||
lists:usort(mnesia:system_info(db_nodes)
|
||||
++ mnesia:system_info(extra_db_nodes)).
|
||||
|
||||
-spec join(node()) -> ok | {error, any()}.
|
||||
|
||||
join(Node) ->
|
||||
case {node(), net_adm:ping(Node)} of
|
||||
{Node, _} ->
|
||||
{error, {not_master, Node}};
|
||||
{_, pong} ->
|
||||
application:stop(ejabberd),
|
||||
application:stop(mnesia),
|
||||
mnesia:delete_schema([node()]),
|
||||
application:start(mnesia),
|
||||
mnesia:change_config(extra_db_nodes, [Node]),
|
||||
mnesia:change_table_copy_type(schema, node(), disc_copies),
|
||||
spawn(fun() ->
|
||||
lists:foreach(fun(Table) ->
|
||||
Type = ejabberd_cluster:call(
|
||||
Node, mnesia, table_info, [Table, storage_type]),
|
||||
mnesia:add_table_copy(Table, node(), Type)
|
||||
end, mnesia:system_info(tables)--[schema])
|
||||
end),
|
||||
application:start(ejabberd);
|
||||
_ ->
|
||||
{error, {no_ping, Node}}
|
||||
end.
|
||||
|
||||
-spec leave(node()) -> ok | {error, any()}.
|
||||
|
||||
leave(Node) ->
|
||||
case {node(), net_adm:ping(Node)} of
|
||||
{Node, _} ->
|
||||
Cluster = get_nodes()--[Node],
|
||||
leave(Cluster, Node);
|
||||
{_, pong} ->
|
||||
rpc:call(Node, ?MODULE, leave, [Node], 10000);
|
||||
{_, pang} ->
|
||||
case mnesia:del_table_copy(schema, Node) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, Reason} -> {error, Reason}
|
||||
end
|
||||
end.
|
||||
leave([], Node) ->
|
||||
{error, {no_cluster, Node}};
|
||||
leave([Master|_], Node) ->
|
||||
application:stop(ejabberd),
|
||||
application:stop(mnesia),
|
||||
ejabberd_cluster:call(Master, mnesia, del_table_copy, [schema, Node]),
|
||||
spawn(fun() ->
|
||||
mnesia:delete_schema([node()]),
|
||||
erlang:halt(0)
|
||||
end),
|
||||
ok.
|
||||
|
||||
-spec node_id() -> binary().
|
||||
node_id() ->
|
||||
integer_to_binary(erlang:phash2(node())).
|
||||
|
||||
-spec get_node_by_id(binary()) -> node().
|
||||
get_node_by_id(Hash) ->
|
||||
try binary_to_integer(Hash) of
|
||||
I -> match_node_id(I)
|
||||
catch _:_ ->
|
||||
node()
|
||||
end.
|
||||
|
||||
-spec send({atom(), node()}, term()) -> boolean().
|
||||
send(Dst, Msg) ->
|
||||
erlang:send(Dst, Msg).
|
||||
|
||||
-spec wait_for_sync(timeout()) -> ok.
|
||||
wait_for_sync(Timeout) ->
|
||||
?INFO_MSG("Waiting for Mnesia synchronization to complete", []),
|
||||
mnesia:wait_for_tables(mnesia:system_info(local_tables), Timeout),
|
||||
ok.
|
||||
|
||||
-spec subscribe(_) -> ok.
|
||||
subscribe(_) ->
|
||||
ok.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-spec match_node_id(integer()) -> node().
|
||||
match_node_id(I) ->
|
||||
match_node_id(I, get_nodes()).
|
||||
|
||||
-spec match_node_id(integer(), [node()]) -> node().
|
||||
match_node_id(I, [Node|Nodes]) ->
|
||||
case erlang:phash2(Node) of
|
||||
I -> Node;
|
||||
_ -> match_node_id(I, Nodes)
|
||||
end;
|
||||
match_node_id(_I, []) ->
|
||||
node().
|
||||
+9
-285
@@ -211,6 +211,7 @@
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-define(DEFAULT_VERSION, 1000000).
|
||||
|
||||
@@ -220,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,
|
||||
@@ -229,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,
|
||||
@@ -297,7 +292,6 @@ init([]) ->
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, ejabberd_commands)},
|
||||
{type, bag}]),
|
||||
mnesia:add_table_copy(ejabberd_commands, node(), ram_copies),
|
||||
register_commands(get_commands_spec()),
|
||||
ejabberd_access_permissions:register_permission_addon(?MODULE, fun permission_addon/0),
|
||||
{ok, #state{}}.
|
||||
@@ -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, fun(A) -> A end, 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,87 +556,10 @@ 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) ->
|
||||
Opts0 = ejabberd_config:get_option(
|
||||
commands,
|
||||
fun(V) when is_list(V) -> V end,
|
||||
[]),
|
||||
Opts0 = ejabberd_config:get_option(commands, []),
|
||||
Opts = lists:map(fun(V) when is_tuple(V) -> [V]; (V) -> V end, Opts0),
|
||||
CommandsList = list_commands_policy(Version),
|
||||
OpenCmds = [N || {N, _, _, open} <- CommandsList],
|
||||
@@ -857,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) ->
|
||||
@@ -877,10 +603,7 @@ is_admin(Name, Auth, Extra) ->
|
||||
_ ->
|
||||
{Extra, global}
|
||||
end,
|
||||
AdminAccess = ejabberd_config:get_option(
|
||||
commands_admin_access,
|
||||
fun(V) -> V end,
|
||||
none),
|
||||
AdminAccess = ejabberd_config:get_option(commands_admin_access, none),
|
||||
case acl:access_matches(AdminAccess, ACLInfo, Server) of
|
||||
allow ->
|
||||
case catch check_auth(get_command_definition(Name), Auth) of
|
||||
@@ -894,11 +617,12 @@ is_admin(Name, Auth, Extra) ->
|
||||
permission_addon() ->
|
||||
[{<<"'commands' option compatibility shim">>,
|
||||
{[],
|
||||
[{access, ejabberd_config:get_option(commands_admin_access,
|
||||
fun(V) -> V end,
|
||||
none)}],
|
||||
[{access, ejabberd_config:get_option(commands_admin_access, none)}],
|
||||
{get_exposed_commands(), []}}}].
|
||||
|
||||
-spec opt_type(commands_admin_access) -> fun((any()) -> any());
|
||||
(commands) -> fun((list()) -> list());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(commands_admin_access) -> fun acl:access_rules_validator/1;
|
||||
opt_type(commands) ->
|
||||
fun(V) when is_list(V) -> V end;
|
||||
|
||||
@@ -69,9 +69,9 @@ list_join_with([El|Tail], M) ->
|
||||
end, [El], Tail)).
|
||||
|
||||
md_tag(dt, V) ->
|
||||
[<<"\n">>, V, <<"\n">>];
|
||||
[<<"- ">>, V];
|
||||
md_tag(dd, V) ->
|
||||
[<<"\n: ">>, V, <<"\n">>];
|
||||
[<<" : ">>, V, <<"\n">>];
|
||||
md_tag(li, V) ->
|
||||
[<<"- ">>, V, <<"\n">>];
|
||||
md_tag(pre, V) ->
|
||||
@@ -87,14 +87,6 @@ md_tag(strong, V) ->
|
||||
md_tag(_, V) ->
|
||||
V.
|
||||
|
||||
|
||||
%% rescode_to_int(ok) ->
|
||||
%% 0;
|
||||
%% rescode_to_int(true) ->
|
||||
%% 0;
|
||||
%% rescode_to_int(_) ->
|
||||
%% 1.
|
||||
|
||||
perl_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
|
||||
[?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
|
||||
perl_gen({Name, string}, Str, _Indent, HTMLOutput) ->
|
||||
@@ -257,7 +249,7 @@ json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput) ->
|
||||
{200, json_gen(ResultDesc, Result, Indent, HTMLOutput)};
|
||||
{{Name0, _}, _} ->
|
||||
{200, [Indent, ?OP_L("{"), ?STR_A(Name0), ?OP_L(": "),
|
||||
json_gen(ResultDesc, Result, Indent, HTMLOutput), Indent, ?OP_L("}")]}
|
||||
json_gen(ResultDesc, Result, Indent, HTMLOutput), ?OP_L("}")]}
|
||||
end,
|
||||
CodeStr = case Code of
|
||||
200 -> <<" 200 OK">>;
|
||||
@@ -324,55 +316,94 @@ gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc,
|
||||
case lists:member(<<"xmlrpc">>, Langs) of true -> ?TAG(li, ?TAG(pre, XML)); _ -> [] end,
|
||||
case lists:member(<<"json">>, Langs) of true -> ?TAG(li, ?TAG(pre, JSON)); _ -> [] end])];
|
||||
true ->
|
||||
[<<"\n">>, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end,
|
||||
case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end,
|
||||
case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end,
|
||||
case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end,
|
||||
<<"{: .code-samples-labels}\n">>,
|
||||
case lists:member(<<"java">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end,
|
||||
case lists:member(<<"perl">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end,
|
||||
case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
|
||||
case lists:member(<<"json">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
|
||||
<<"{: .code-samples-tabs}\n\n">>]
|
||||
case Langs of
|
||||
Val when length(Val) == 0 orelse length(Val) == 1 ->
|
||||
[case lists:member(<<"java">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end,
|
||||
case lists:member(<<"perl">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end,
|
||||
case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
|
||||
case lists:member(<<"json">>, Langs) of true -> [<<"\n">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
|
||||
<<"\n\n">>];
|
||||
_ ->
|
||||
[<<"\n">>, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end,
|
||||
case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end,
|
||||
case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end,
|
||||
case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end,
|
||||
<<"{: .code-samples-labels}\n">>,
|
||||
case lists:member(<<"java">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end,
|
||||
case lists:member(<<"perl">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end,
|
||||
case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
|
||||
case lists:member(<<"json">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
|
||||
<<"{: .code-samples-tabs}\n\n">>]
|
||||
end
|
||||
end.
|
||||
|
||||
format_type({list, {_, {tuple, Els}}}) ->
|
||||
io_lib:format("[~s]", [format_type({tuple, Els})]);
|
||||
format_type({list, El}) ->
|
||||
io_lib:format("[~s]", [format_type(El)]);
|
||||
format_type({tuple, Els}) ->
|
||||
Args = [format_type(El) || El <- Els],
|
||||
io_lib:format("{~s}", [string:join(Args, ", ")]);
|
||||
format_type({Name, Type}) ->
|
||||
io_lib:format("~s::~s", [Name, format_type(Type)]);
|
||||
format_type(binary) ->
|
||||
"string";
|
||||
format_type(atom) ->
|
||||
"string";
|
||||
format_type(Type) ->
|
||||
io_lib:format("~p", [Type]).
|
||||
|
||||
gen_param(Name, Type, undefined, HTMLOutput) ->
|
||||
[?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))])];
|
||||
gen_param(Name, Type, Desc, HTMLOutput) ->
|
||||
[?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))]),
|
||||
?TAG(dd, ?RAW(Desc))].
|
||||
|
||||
gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
|
||||
args=Args, args_desc=ArgsDesc,
|
||||
result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
|
||||
LDesc = case LongDesc of
|
||||
"" -> Desc;
|
||||
_ -> LongDesc
|
||||
end,
|
||||
ArgsText = case ArgsDesc of
|
||||
none ->
|
||||
[?TAG(ul, "args-list", lists:map(fun({AName, Type}) ->
|
||||
[?TAG(li, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>,
|
||||
?RAW(io_lib:format("~p", [Type]))])]
|
||||
end, Args))];
|
||||
_ ->
|
||||
[?TAG(dl, "args-list", lists:map(fun({{AName, Type}, ADesc}) ->
|
||||
[?TAG(dt, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>,
|
||||
?RAW(io_lib:format("~p", [Type]))]),
|
||||
?TAG(dd, ?RAW(ADesc))]
|
||||
end, lists:zip(Args, ArgsDesc)))]
|
||||
end,
|
||||
ResultText = case ResultDesc of
|
||||
none ->
|
||||
[?RAW(io_lib:format("~p", [Result]))];
|
||||
_ ->
|
||||
[?TAG(dl, [
|
||||
?TAG(dt, io_lib:format("~p", [Result])),
|
||||
?TAG_R(dd, ResultDesc)])]
|
||||
end,
|
||||
try
|
||||
LDesc = case LongDesc of
|
||||
"" -> Desc;
|
||||
_ -> LongDesc
|
||||
end,
|
||||
ArgsText = case ArgsDesc of
|
||||
none ->
|
||||
[?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput)
|
||||
|| {AName, Type} <- Args])];
|
||||
_ ->
|
||||
[?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput)
|
||||
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])]
|
||||
end,
|
||||
ResultText = case Result of
|
||||
{res,rescode} ->
|
||||
[?TAG(dl, [gen_param(res, integer,
|
||||
"Status code (0 on success, 1 otherwise)",
|
||||
HTMLOutput)])];
|
||||
{res,restuple} ->
|
||||
[?TAG(dl, [gen_param(res, string,
|
||||
"Raw result string",
|
||||
HTMLOutput)])];
|
||||
{RName, Type} ->
|
||||
case ResultDesc of
|
||||
none ->
|
||||
[?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
|
||||
_ ->
|
||||
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
|
||||
end
|
||||
end,
|
||||
|
||||
[?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
|
||||
?TAG(p, ?RAW(LDesc)),
|
||||
?TAG(h2, <<"Arguments:">>),
|
||||
ArgsText,
|
||||
?TAG(h2, <<"Result:">>),
|
||||
ResultText,
|
||||
?TAG(h2, <<"Examples:">>),
|
||||
gen_calls(Cmd, HTMLOutput, Langs)].
|
||||
[?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
|
||||
?TAG(p, ?RAW(LDesc)),
|
||||
?TAG(h2, <<"Arguments:">>), ArgsText,
|
||||
?TAG(h2, <<"Result:">>), ResultText,
|
||||
?TAG(h2, <<"Examples:">>), gen_calls(Cmd, HTMLOutput, Langs)]
|
||||
catch
|
||||
_:Ex ->
|
||||
throw(iolist_to_binary(io_lib:format(
|
||||
<<"Error when generating documentation for command '~p': ~p">>,
|
||||
[Name, Ex])))
|
||||
end.
|
||||
|
||||
find_commands_definitions() ->
|
||||
case code:lib_dir(ejabberd, ebin) of
|
||||
|
||||
+228
-253
@@ -27,33 +27,37 @@
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, load_file/1, reload_file/0, read_file/1,
|
||||
get_option/2, get_option/3, add_option/2, has_option/1,
|
||||
get_vh_by_auth_method/1, is_file_readable/1,
|
||||
get_version/0, get_myhosts/0, get_mylang/0,
|
||||
get_option/1, get_option/2, add_option/2, has_option/1,
|
||||
get_vh_by_auth_method/1,
|
||||
get_version/0, get_myhosts/0, get_mylang/0, get_lang/1,
|
||||
get_ejabberd_config_path/0, is_using_elixir_config/0,
|
||||
prepare_opt_val/4, convert_table_to_binary/5,
|
||||
transform_options/1, collect_options/1,
|
||||
prepare_opt_val/4, transform_options/1, collect_options/1,
|
||||
convert_to_yaml/1, convert_to_yaml/2, v_db/2,
|
||||
env_binary_to_list/2, opt_type/1, may_hide_data/1,
|
||||
is_elixir_enabled/0, v_dbs/1, v_dbs_mods/1,
|
||||
default_db/1, default_db/2, default_ram_db/1, default_ram_db/2,
|
||||
default_queue_type/1, queue_dir/0, fsm_limit_opts/1]).
|
||||
default_queue_type/1, queue_dir/0, fsm_limit_opts/1,
|
||||
use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1]).
|
||||
|
||||
-export([start/2]).
|
||||
|
||||
%% The following functions are deprecated.
|
||||
-export([add_global_option/2, add_local_option/2,
|
||||
get_global_option/2, get_local_option/2,
|
||||
get_global_option/3, get_local_option/3]).
|
||||
get_global_option/3, get_local_option/3,
|
||||
get_option/3]).
|
||||
-export([is_file_readable/1]).
|
||||
|
||||
-deprecated([{add_global_option, 2}, {add_local_option, 2},
|
||||
{get_global_option, 2}, {get_local_option, 2},
|
||||
{get_global_option, 3}, {get_local_option, 3}]).
|
||||
{get_global_option, 3}, {get_local_option, 3},
|
||||
{get_option, 3}, {is_file_readable, 1}]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_config.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
-callback opt_type(atom()) -> function() | [atom()].
|
||||
|
||||
@@ -65,8 +69,10 @@
|
||||
%% @type macro_value() = term().
|
||||
|
||||
start() ->
|
||||
mnesia_init(),
|
||||
ConfigFile = get_ejabberd_config_path(),
|
||||
?INFO_MSG("Loading configuration from ~s", [ConfigFile]),
|
||||
p1_options:start_link(ejabberd_options),
|
||||
p1_options:start_link(ejabberd_db_modules),
|
||||
State1 = load_file(ConfigFile),
|
||||
UnixTime = p1_time_compat:system_time(seconds),
|
||||
SharedKey = case erlang:get_cookie() of
|
||||
@@ -99,23 +105,11 @@ hosts_to_start(State) ->
|
||||
%% At the moment, these functions are mainly used to setup unit tests.
|
||||
-spec start(Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok.
|
||||
start(Hosts, Opts) ->
|
||||
mnesia_init(),
|
||||
p1_options:start_link(ejabberd_options),
|
||||
p1_options:start_link(ejabberd_db_modules),
|
||||
set_opts(set_hosts_in_options(Hosts, #state{opts = Opts})),
|
||||
ok.
|
||||
|
||||
mnesia_init() ->
|
||||
case catch mnesia:table_info(local_config, storage_type) of
|
||||
disc_copies ->
|
||||
mnesia:delete_table(local_config);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
ejabberd_mnesia:create(?MODULE, local_config,
|
||||
[{ram_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, local_config)}]),
|
||||
mnesia:add_table_copy(local_config, node(), ram_copies).
|
||||
|
||||
%% @doc Get the filename of the ejabberd configuration file.
|
||||
%% The filename can be specified with: erl -config "/path/to/ejabberd.yml".
|
||||
%% It can also be specified with the environtment variable EJABBERD_CONFIG_PATH.
|
||||
@@ -186,7 +180,10 @@ read_file(File, Opts) ->
|
||||
load_file(File) ->
|
||||
State0 = read_file(File),
|
||||
State1 = hosts_to_start(State0),
|
||||
validate_opts(State1).
|
||||
AllMods = get_modules(),
|
||||
init_module_db_table(AllMods),
|
||||
ModOpts = get_modules_with_options(AllMods),
|
||||
validate_opts(State1, ModOpts).
|
||||
|
||||
-spec reload_file() -> ok.
|
||||
|
||||
@@ -315,8 +312,6 @@ consult(File) ->
|
||||
case file:consult(File) of
|
||||
{ok, Terms} ->
|
||||
{ok, Terms};
|
||||
{error, enoent} ->
|
||||
{error, enoent};
|
||||
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
|
||||
{error, describe_config_problem(File, Reason, LineNumber)};
|
||||
{error, Reason} ->
|
||||
@@ -763,35 +758,21 @@ append_option({Opt, Host}, Val, State) ->
|
||||
|
||||
set_opts(State) ->
|
||||
Opts = State#state.opts,
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
fun({node_start, _}) -> ok;
|
||||
({shared_key, _}) -> ok;
|
||||
(Key) -> mnesia:delete({local_config, Key})
|
||||
end, mnesia:all_keys(local_config)),
|
||||
lists:foreach(fun mnesia:write/1, Opts)
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, _} ->
|
||||
set_log_level();
|
||||
{aborted,{no_exists,Table}} ->
|
||||
MnesiaDirectory = mnesia:system_info(directory),
|
||||
?CRITICAL_MSG("Error reading Mnesia database spool files:~n"
|
||||
"The Mnesia database couldn't read the spool file for the table '~p'.~n"
|
||||
"ejabberd needs read and write access in the directory:~n ~s~n"
|
||||
"Maybe the problem is a change in the computer hostname,~n"
|
||||
"or a change in the Erlang node name, which is currently:~n ~p~n"
|
||||
"Check the ejabberd guide for details about changing the~n"
|
||||
"computer hostname or Erlang node name.~n",
|
||||
[Table, MnesiaDirectory, node()]),
|
||||
exit("Error reading Mnesia database")
|
||||
end.
|
||||
ets:select_delete(ejabberd_options,
|
||||
ets:fun2ms(
|
||||
fun({{node_start, _}, _}) -> false;
|
||||
({{shared_key, _}, _}) -> false;
|
||||
(_) -> true
|
||||
end)),
|
||||
lists:foreach(
|
||||
fun(#local_config{key = {Opt, Host}, value = Val}) ->
|
||||
p1_options:insert(ejabberd_options, Opt, Host, Val)
|
||||
end, Opts),
|
||||
p1_options:compile(ejabberd_options),
|
||||
set_log_level().
|
||||
|
||||
set_log_level() ->
|
||||
Level = ejabberd_config:get_option(
|
||||
loglevel,
|
||||
fun(P) when P>=0, P=<5 -> P end,
|
||||
4),
|
||||
Level = get_option(loglevel, 4),
|
||||
ejabberd_logger:set(Level).
|
||||
|
||||
add_global_option(Opt, Val) ->
|
||||
@@ -802,11 +783,9 @@ add_local_option(Opt, Val) ->
|
||||
|
||||
add_option(Opt, Val) when is_atom(Opt) ->
|
||||
add_option({Opt, global}, Val);
|
||||
add_option(Opt, Val) ->
|
||||
mnesia:transaction(fun() ->
|
||||
mnesia:write(#local_config{key = Opt,
|
||||
value = Val})
|
||||
end).
|
||||
add_option({Opt, Host}, Val) ->
|
||||
p1_options:insert(ejabberd_options, Opt, Host, Val),
|
||||
p1_options:compile(ejabberd_options).
|
||||
|
||||
-spec prepare_opt_val(any(), any(), check_fun(), any()) -> any().
|
||||
|
||||
@@ -843,97 +822,126 @@ prepare_opt_val(Opt, Val, F, Default) ->
|
||||
|
||||
-spec get_global_option(any(), check_fun()) -> any().
|
||||
|
||||
get_global_option(Opt, F) ->
|
||||
get_option(Opt, F, undefined).
|
||||
get_global_option(Opt, _) ->
|
||||
get_option(Opt, undefined).
|
||||
|
||||
-spec get_global_option(any(), check_fun(), any()) -> any().
|
||||
|
||||
get_global_option(Opt, F, Default) ->
|
||||
get_option(Opt, F, Default).
|
||||
get_global_option(Opt, _, Default) ->
|
||||
get_option(Opt, Default).
|
||||
|
||||
-spec get_local_option(any(), check_fun()) -> any().
|
||||
|
||||
get_local_option(Opt, F) ->
|
||||
get_option(Opt, F, undefined).
|
||||
get_local_option(Opt, _) ->
|
||||
get_option(Opt, undefined).
|
||||
|
||||
-spec get_local_option(any(), check_fun(), any()) -> any().
|
||||
|
||||
get_local_option(Opt, F, Default) ->
|
||||
get_option(Opt, F, Default).
|
||||
get_local_option(Opt, _, Default) ->
|
||||
get_option(Opt, Default).
|
||||
|
||||
-spec get_option(any(), check_fun()) -> any().
|
||||
|
||||
get_option(Opt, F) ->
|
||||
get_option(Opt, F, undefined).
|
||||
-spec get_option(any()) -> any().
|
||||
get_option(Opt) ->
|
||||
get_option(Opt, undefined).
|
||||
|
||||
-spec get_option(any(), check_fun(), any()) -> any().
|
||||
get_option(Opt, _, Default) ->
|
||||
get_option(Opt, Default).
|
||||
|
||||
get_option(Opt, F, Default) when is_atom(Opt) ->
|
||||
get_option({Opt, global}, F, Default);
|
||||
get_option(Opt, F, Default) ->
|
||||
case Opt of
|
||||
{O, global} when is_atom(O) -> ok;
|
||||
{O, H} when is_atom(O), is_binary(H) -> ok;
|
||||
_ -> ?WARNING_MSG("Option ~p has invalid (outdated?) format. "
|
||||
"This is likely a bug", [Opt])
|
||||
end,
|
||||
case ets:lookup(local_config, Opt) of
|
||||
[#local_config{value = Val}] ->
|
||||
prepare_opt_val(Opt, Val, F, Default);
|
||||
_ ->
|
||||
case Opt of
|
||||
{Key, Host} when Host /= global ->
|
||||
get_option({Key, global}, F, Default);
|
||||
_ ->
|
||||
Default
|
||||
end
|
||||
-spec get_option(any(), check_fun() | any()) -> any().
|
||||
get_option(Opt, F) when is_function(F) ->
|
||||
get_option(Opt, undefined);
|
||||
get_option(Opt, Default) when is_atom(Opt) ->
|
||||
get_option({Opt, global}, Default);
|
||||
get_option(Opt, Default) ->
|
||||
{Key, Host} = case Opt of
|
||||
{O, global} when is_atom(O) -> Opt;
|
||||
{O, H} when is_atom(O), is_binary(H) -> Opt;
|
||||
_ ->
|
||||
?WARNING_MSG("Option ~p has invalid (outdated?) "
|
||||
"format. This is likely a bug", [Opt]),
|
||||
{undefined, global}
|
||||
end,
|
||||
case ejabberd_options:is_known(Key) of
|
||||
true ->
|
||||
case ejabberd_options:Key(Host) of
|
||||
{ok, Val} -> Val;
|
||||
undefined -> Default
|
||||
end;
|
||||
false ->
|
||||
Default
|
||||
end.
|
||||
|
||||
-spec has_option(atom() | {atom(), global | binary()}) -> any().
|
||||
has_option(Opt) ->
|
||||
get_option(Opt, fun(_) -> true end, false).
|
||||
get_option(Opt) /= undefined.
|
||||
|
||||
init_module_db_table(Modules) ->
|
||||
catch ets:new(module_db, [named_table, public, bag,
|
||||
{read_concurrency, true}]),
|
||||
%% Dirty hack for mod_pubsub
|
||||
ets:insert(module_db, {mod_pubsub, mnesia}),
|
||||
ets:insert(module_db, {mod_pubsub, sql}),
|
||||
p1_options:insert(ejabberd_db_modules, mod_pubsub, mnesia, true),
|
||||
p1_options:insert(ejabberd_db_modules, mod_pubsub, sql, true),
|
||||
lists:foreach(
|
||||
fun(M) ->
|
||||
case re:split(atom_to_list(M), "_", [{return, list}]) of
|
||||
[_] ->
|
||||
ok;
|
||||
Parts ->
|
||||
[Suffix|T] = lists:reverse(Parts),
|
||||
BareMod = string:join(lists:reverse(T), "_"),
|
||||
ets:insert(module_db, {list_to_atom(BareMod),
|
||||
list_to_atom(Suffix)})
|
||||
[H|T] = lists:reverse(Parts),
|
||||
Suffix = list_to_atom(H),
|
||||
BareMod = list_to_atom(string:join(lists:reverse(T), "_")),
|
||||
case is_behaviour(BareMod, M) of
|
||||
true ->
|
||||
p1_options:insert(ejabberd_db_modules,
|
||||
BareMod, Suffix, true);
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end
|
||||
end, Modules).
|
||||
end, Modules),
|
||||
p1_options:compile(ejabberd_db_modules).
|
||||
|
||||
is_behaviour(Behav, Mod) ->
|
||||
try Mod:module_info(attributes) of
|
||||
[] ->
|
||||
%% Stripped module?
|
||||
true;
|
||||
Attrs ->
|
||||
lists:any(
|
||||
fun({behaviour, L}) -> lists:member(Behav, L);
|
||||
({behavior, L}) -> lists:member(Behav, L);
|
||||
(_) -> false
|
||||
end, Attrs)
|
||||
catch _:_ ->
|
||||
true
|
||||
end.
|
||||
|
||||
-spec v_db(module(), atom()) -> atom().
|
||||
|
||||
v_db(Mod, internal) -> v_db(Mod, mnesia);
|
||||
v_db(Mod, odbc) -> v_db(Mod, sql);
|
||||
v_db(Mod, Type) ->
|
||||
case ets:match_object(module_db, {Mod, Type}) of
|
||||
[_|_] -> Type;
|
||||
[] -> erlang:error(badarg)
|
||||
case ejabberd_db_modules:is_known(Mod) of
|
||||
true ->
|
||||
case ejabberd_db_modules:Mod(Type) of
|
||||
{ok, _} -> Type;
|
||||
_ -> erlang:error(badarg)
|
||||
end;
|
||||
false ->
|
||||
erlang:error(badarg)
|
||||
end.
|
||||
|
||||
-spec v_dbs(module()) -> [atom()].
|
||||
|
||||
v_dbs(Mod) ->
|
||||
lists:flatten(ets:match(module_db, {Mod, '$1'})).
|
||||
ejabberd_db_modules:get_scope(Mod).
|
||||
|
||||
-spec v_dbs_mods(module()) -> [module()].
|
||||
|
||||
v_dbs_mods(Mod) ->
|
||||
lists:map(fun([M]) ->
|
||||
lists:map(fun(M) ->
|
||||
binary_to_atom(<<(atom_to_binary(Mod, utf8))/binary, "_",
|
||||
(atom_to_binary(M, utf8))/binary>>, utf8)
|
||||
end, ets:match(module_db, {Mod, '$1'})).
|
||||
end, v_dbs(Mod)).
|
||||
|
||||
-spec default_db(module()) -> atom().
|
||||
default_db(Module) ->
|
||||
@@ -953,7 +961,7 @@ default_ram_db(Host, Module) ->
|
||||
|
||||
-spec default_db(default_db | default_ram_db, binary() | global, module()) -> atom().
|
||||
default_db(Opt, Host, Module) ->
|
||||
case get_option({Opt, Host}, fun(T) when is_atom(T) -> T end) of
|
||||
case get_option({Opt, Host}) of
|
||||
undefined ->
|
||||
mnesia;
|
||||
DBType ->
|
||||
@@ -967,37 +975,40 @@ default_db(Opt, Host, Module) ->
|
||||
end
|
||||
end.
|
||||
|
||||
get_modules_with_options() ->
|
||||
get_modules() ->
|
||||
{ok, Mods} = application:get_key(ejabberd, modules),
|
||||
ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
|
||||
AllMods = [?MODULE|ExtMods++Mods],
|
||||
init_module_db_table(AllMods),
|
||||
ExtMods ++ Mods.
|
||||
|
||||
get_modules_with_options(Modules) ->
|
||||
lists:foldl(
|
||||
fun(Mod, D) ->
|
||||
case catch Mod:opt_type('') of
|
||||
Opts when is_list(Opts) ->
|
||||
lists:foldl(
|
||||
fun(Opt, Acc) ->
|
||||
dict:append(Opt, Mod, Acc)
|
||||
end, D, Opts);
|
||||
{'EXIT', {undef, _}} ->
|
||||
case is_behaviour(?MODULE, Mod) orelse Mod == ?MODULE of
|
||||
true ->
|
||||
try Mod:opt_type('') of
|
||||
Opts when is_list(Opts) ->
|
||||
lists:foldl(
|
||||
fun(Opt, Acc) ->
|
||||
dict:append(Opt, Mod, Acc)
|
||||
end, D, Opts)
|
||||
catch _:undef ->
|
||||
D
|
||||
end;
|
||||
false ->
|
||||
D
|
||||
end
|
||||
end, dict:new(), AllMods).
|
||||
end, dict:new(), Modules).
|
||||
|
||||
validate_opts(#state{opts = Opts} = State) ->
|
||||
ModOpts = get_modules_with_options(),
|
||||
validate_opts(#state{opts = Opts} = State, ModOpts) ->
|
||||
NewOpts = lists:filtermap(
|
||||
fun(#local_config{key = {Opt, _Host}, value = Val} = In) ->
|
||||
case dict:find(Opt, ModOpts) of
|
||||
{ok, [Mod|_]} ->
|
||||
VFun = Mod:opt_type(Opt),
|
||||
try VFun(Val) of
|
||||
_ ->
|
||||
true
|
||||
catch {replace_with, NewVal} ->
|
||||
{true, In#local_config{value = NewVal}};
|
||||
{invalid_syntax, Error} ->
|
||||
NewVal ->
|
||||
{true, In#local_config{value = NewVal}}
|
||||
catch {invalid_syntax, Error} ->
|
||||
?ERROR_MSG("ignoring option '~s' with "
|
||||
"invalid value: ~p: ~s",
|
||||
[Opt, Val, Error]),
|
||||
@@ -1020,23 +1031,21 @@ validate_opts(#state{opts = Opts} = State) ->
|
||||
|
||||
%% Return the list of hosts with a given auth method
|
||||
get_vh_by_auth_method(AuthMethod) ->
|
||||
Cfgs = mnesia:dirty_match_object(local_config,
|
||||
#local_config{key = {auth_method, '_'},
|
||||
_ = '_'}),
|
||||
lists:flatmap(
|
||||
fun(#local_config{key = {auth_method, Host}, value = M}) ->
|
||||
Methods = if not is_list(M) -> [M];
|
||||
true -> M
|
||||
end,
|
||||
case lists:member(AuthMethod, Methods) of
|
||||
true when Host == global ->
|
||||
get_myhosts();
|
||||
true ->
|
||||
[Host];
|
||||
false ->
|
||||
[]
|
||||
end
|
||||
end, Cfgs).
|
||||
Hosts = ejabberd_options:get_scope(auth_method),
|
||||
get_vh_by_auth_method(AuthMethod, Hosts, []).
|
||||
|
||||
get_vh_by_auth_method(Method, [Host|Hosts], Result) ->
|
||||
Methods = get_option({auth_method, Host}, []),
|
||||
case lists:member(Method, Methods) of
|
||||
true when Host == global ->
|
||||
get_myhosts();
|
||||
true ->
|
||||
get_vh_by_auth_method(Method, Hosts, [Host|Result]);
|
||||
false ->
|
||||
get_vh_by_auth_method(Method, Hosts, Result)
|
||||
end;
|
||||
get_vh_by_auth_method(_, [], Result) ->
|
||||
Result.
|
||||
|
||||
%% @spec (Path::string()) -> true | false
|
||||
is_file_readable(Path) ->
|
||||
@@ -1060,15 +1069,16 @@ get_version() ->
|
||||
-spec get_myhosts() -> [binary()].
|
||||
|
||||
get_myhosts() ->
|
||||
get_option(hosts, fun(V) -> V end).
|
||||
get_option(hosts).
|
||||
|
||||
-spec get_mylang() -> binary().
|
||||
|
||||
get_mylang() ->
|
||||
get_option(
|
||||
language,
|
||||
fun iolist_to_binary/1,
|
||||
<<"en">>).
|
||||
get_lang(global).
|
||||
|
||||
-spec get_lang(global | binary()) -> binary().
|
||||
get_lang(Host) ->
|
||||
get_option({language, Host}, <<"en">>).
|
||||
|
||||
replace_module(mod_announce_odbc) -> {mod_announce, sql};
|
||||
replace_module(mod_blocking_odbc) -> {mod_blocking, sql};
|
||||
@@ -1083,7 +1093,7 @@ replace_module(mod_roster_odbc) -> {mod_roster, sql};
|
||||
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql};
|
||||
replace_module(mod_vcard_odbc) -> {mod_vcard, sql};
|
||||
replace_module(mod_vcard_ldap) -> {mod_vcard, ldap};
|
||||
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, sql};
|
||||
replace_module(mod_vcard_xupdate_odbc) -> mod_vcard_xupdate;
|
||||
replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql};
|
||||
replace_module(mod_http_bind) -> mod_bosh;
|
||||
replace_module(Module) ->
|
||||
@@ -1204,7 +1214,6 @@ transform_terms(Terms) ->
|
||||
%% We could check all ejabberd beams, but this
|
||||
%% slows down start-up procedure :(
|
||||
Mods = [mod_register,
|
||||
mod_last,
|
||||
ejabberd_s2s,
|
||||
ejabberd_listener,
|
||||
ejabberd_sql_sup,
|
||||
@@ -1313,6 +1322,10 @@ transform_options(Opt, Opts) when Opt == override_global;
|
||||
Opt == override_acls ->
|
||||
?WARNING_MSG("Ignoring '~s' option which has no effect anymore", [Opt]),
|
||||
Opts;
|
||||
transform_options({node_start, {_, _, _} = Now}, Opts) ->
|
||||
?WARNING_MSG("Old 'node_start' format detected. This is still supported "
|
||||
"but it is better to fix your config.", []),
|
||||
[{node_start, now_to_seconds(Now)}|Opts];
|
||||
transform_options({host_config, Host, HOpts}, Opts) ->
|
||||
{AddOpts, HOpts1} =
|
||||
lists:mapfoldl(
|
||||
@@ -1336,94 +1349,6 @@ transform_options({include_config_file, _, _} = Opt, Opts) ->
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
-spec convert_table_to_binary(atom(), [atom()], atom(),
|
||||
fun(), fun()) -> ok.
|
||||
|
||||
convert_table_to_binary(Tab, Fields, Type, DetectFun, ConvertFun) ->
|
||||
case is_table_still_list(Tab, DetectFun) of
|
||||
true ->
|
||||
?INFO_MSG("Converting '~s' table from strings to binaries.", [Tab]),
|
||||
TmpTab = list_to_atom(atom_to_list(Tab) ++ "_tmp_table"),
|
||||
catch mnesia:delete_table(TmpTab),
|
||||
case ejabberd_mnesia:create(?MODULE, TmpTab,
|
||||
[{disc_only_copies, [node()]},
|
||||
{type, Type},
|
||||
{local_content, true},
|
||||
{record_name, Tab},
|
||||
{attributes, Fields}]) of
|
||||
{atomic, ok} ->
|
||||
mnesia:transform_table(Tab, ignore, Fields),
|
||||
case mnesia:transaction(
|
||||
fun() ->
|
||||
mnesia:write_lock_table(TmpTab),
|
||||
mnesia:foldl(
|
||||
fun(R, _) ->
|
||||
NewR = ConvertFun(R),
|
||||
mnesia:dirty_write(TmpTab, NewR)
|
||||
end, ok, Tab)
|
||||
end) of
|
||||
{atomic, ok} ->
|
||||
mnesia:clear_table(Tab),
|
||||
case mnesia:transaction(
|
||||
fun() ->
|
||||
mnesia:write_lock_table(Tab),
|
||||
mnesia:foldl(
|
||||
fun(R, _) ->
|
||||
mnesia:dirty_write(R)
|
||||
end, ok, TmpTab)
|
||||
end) of
|
||||
{atomic, ok} ->
|
||||
mnesia:delete_table(TmpTab);
|
||||
Err ->
|
||||
report_and_stop(Tab, Err)
|
||||
end;
|
||||
Err ->
|
||||
report_and_stop(Tab, Err)
|
||||
end;
|
||||
Err ->
|
||||
report_and_stop(Tab, Err)
|
||||
end;
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
is_table_still_list(Tab, DetectFun) ->
|
||||
is_table_still_list(Tab, DetectFun, mnesia:dirty_first(Tab)).
|
||||
|
||||
is_table_still_list(_Tab, _DetectFun, '$end_of_table') ->
|
||||
false;
|
||||
is_table_still_list(Tab, DetectFun, Key) ->
|
||||
Rs = mnesia:dirty_read(Tab, Key),
|
||||
Res = lists:foldl(fun(_, true) ->
|
||||
true;
|
||||
(_, false) ->
|
||||
false;
|
||||
(R, _) ->
|
||||
case DetectFun(R) of
|
||||
'$next' ->
|
||||
'$next';
|
||||
El ->
|
||||
is_list(El)
|
||||
end
|
||||
end, '$next', Rs),
|
||||
case Res of
|
||||
true ->
|
||||
true;
|
||||
false ->
|
||||
false;
|
||||
'$next' ->
|
||||
is_table_still_list(Tab, DetectFun, mnesia:dirty_next(Tab, Key))
|
||||
end.
|
||||
|
||||
report_and_stop(Tab, Err) ->
|
||||
ErrTxt = lists:flatten(
|
||||
io_lib:format(
|
||||
"Failed to convert '~s' table to binary: ~p",
|
||||
[Tab, Err])),
|
||||
?CRITICAL_MSG(ErrTxt, []),
|
||||
timer:sleep(1000),
|
||||
halt(string:substr(ErrTxt, 1, 199)).
|
||||
|
||||
emit_deprecation_warning(Module, NewModule, DBType) ->
|
||||
?WARNING_MSG("Module ~s is deprecated, use ~s with 'db_type: ~s'"
|
||||
" instead", [Module, NewModule, DBType]).
|
||||
@@ -1437,14 +1362,32 @@ emit_deprecation_warning(Module, NewModule) ->
|
||||
[Module, NewModule])
|
||||
end.
|
||||
|
||||
-spec now_to_seconds(erlang:timestamp()) -> non_neg_integer().
|
||||
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
|
||||
MegaSecs * 1000000 + Secs.
|
||||
|
||||
-spec opt_type(hide_sensitive_log_data) -> fun((boolean()) -> boolean());
|
||||
(hosts) -> fun(([binary()]) -> [binary()]);
|
||||
(language) -> fun((binary()) -> binary());
|
||||
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
|
||||
(default_db) -> fun((atom()) -> atom());
|
||||
(default_ram_db) -> fun((atom()) -> atom());
|
||||
(loglevel) -> fun((0..5) -> 0..5);
|
||||
(queue_dir) -> fun((binary()) -> binary());
|
||||
(queue_type) -> fun((ram | file) -> ram | file);
|
||||
(use_cache) -> fun((boolean()) -> boolean());
|
||||
(cache_size) -> fun((timeout()) -> timeout());
|
||||
(cache_missed) -> fun((boolean()) -> boolean());
|
||||
(cache_life_time) -> fun((timeout()) -> timeout());
|
||||
(domain_certfile) -> fun((binary()) -> binary());
|
||||
(shared_key) -> fun((binary()) -> binary());
|
||||
(node_start) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(hide_sensitive_log_data) ->
|
||||
fun (H) when is_boolean(H) -> H end;
|
||||
opt_type(hosts) ->
|
||||
fun(L) when is_list(L) ->
|
||||
lists:map(
|
||||
fun(H) ->
|
||||
iolist_to_binary(H)
|
||||
end, L)
|
||||
fun(L) ->
|
||||
[iolist_to_binary(H) || H <- L]
|
||||
end;
|
||||
opt_type(language) ->
|
||||
fun iolist_to_binary/1;
|
||||
@@ -1460,18 +1403,35 @@ opt_type(queue_dir) ->
|
||||
fun iolist_to_binary/1;
|
||||
opt_type(queue_type) ->
|
||||
fun(ram) -> ram; (file) -> file end;
|
||||
opt_type(use_cache) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
opt_type(cache_size) ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(infinity) -> infinity;
|
||||
(unlimited) -> infinity
|
||||
end;
|
||||
opt_type(cache_missed) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
opt_type(cache_life_time) ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(infinity) -> infinity;
|
||||
(unlimited) -> infinity
|
||||
end;
|
||||
opt_type(domain_certfile) ->
|
||||
fun misc:try_read_file/1;
|
||||
opt_type(shared_key) ->
|
||||
fun iolist_to_binary/1;
|
||||
opt_type(node_start) ->
|
||||
fun(I) when is_integer(I), I>=0 -> I end;
|
||||
opt_type(_) ->
|
||||
[hide_sensitive_log_data, hosts, language, max_fsm_queue,
|
||||
default_db, default_ram_db, queue_type, queue_dir, loglevel].
|
||||
default_db, default_ram_db, queue_type, queue_dir, loglevel,
|
||||
use_cache, cache_size, cache_missed, cache_life_time,
|
||||
domain_certfile, shared_key, node_start].
|
||||
|
||||
-spec may_hide_data(any()) -> any().
|
||||
may_hide_data(Data) ->
|
||||
case ejabberd_config:get_option(
|
||||
hide_sensitive_log_data,
|
||||
fun(false) -> false;
|
||||
(true) -> true
|
||||
end,
|
||||
false) of
|
||||
case get_option(hide_sensitive_log_data, false) of
|
||||
false ->
|
||||
Data;
|
||||
true ->
|
||||
@@ -1484,9 +1444,7 @@ fsm_limit_opts(Opts) ->
|
||||
{_, I} when is_integer(I), I>0 ->
|
||||
[{max_queue, I}];
|
||||
false ->
|
||||
case get_option(
|
||||
max_fsm_queue,
|
||||
fun(I) when is_integer(I), I>0 -> I end) of
|
||||
case get_option(max_fsm_queue) of
|
||||
undefined -> [];
|
||||
N -> [{max_queue, N}]
|
||||
end
|
||||
@@ -1494,8 +1452,25 @@ fsm_limit_opts(Opts) ->
|
||||
|
||||
-spec queue_dir() -> binary() | undefined.
|
||||
queue_dir() ->
|
||||
get_option(queue_dir, opt_type(queue_dir)).
|
||||
get_option(queue_dir).
|
||||
|
||||
-spec default_queue_type(binary()) -> ram | file.
|
||||
default_queue_type(Host) ->
|
||||
get_option({queue_type, Host}, opt_type(queue_type), ram).
|
||||
get_option({queue_type, Host}, ram).
|
||||
|
||||
-spec use_cache(binary() | global) -> boolean().
|
||||
use_cache(Host) ->
|
||||
get_option({use_cache, Host}, true).
|
||||
|
||||
-spec cache_size(binary() | global) -> pos_integer() | infinity.
|
||||
cache_size(Host) ->
|
||||
get_option({cache_size, Host}, 1000).
|
||||
|
||||
-spec cache_missed(binary() | global) -> boolean().
|
||||
cache_missed(Host) ->
|
||||
get_option({cache_missed, Host}, true).
|
||||
|
||||
-spec cache_life_time(binary() | global) -> pos_integer() | infinity.
|
||||
%% NOTE: the integer value returned is in *seconds*
|
||||
cache_life_time(Host) ->
|
||||
get_option({cache_life_time, Host}, 3600).
|
||||
|
||||
@@ -289,8 +289,7 @@ process2(Args, AccessCommands, Auth, Version) ->
|
||||
end.
|
||||
|
||||
get_accesscommands() ->
|
||||
ejabberd_config:get_option(ejabberdctl_access_commands,
|
||||
fun(V) when is_list(V) -> V end, []).
|
||||
ejabberd_config:get_option(ejabberdctl_access_commands, []).
|
||||
|
||||
%%-----------------------------
|
||||
%% Command calling
|
||||
@@ -876,6 +875,8 @@ print(Format, Args) ->
|
||||
%% ["aaaa bbb ccc"].
|
||||
|
||||
|
||||
-spec opt_type(ejabberdctl_access_commands) -> fun((list()) -> list());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(ejabberdctl_access_commands) ->
|
||||
fun (V) when is_list(V) -> V end;
|
||||
opt_type(_) -> [ejabberdctl_access_commands].
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @doc
|
||||
%%% This is a stub module which will be replaced during
|
||||
%%% configuration load via p1_options:compile/1
|
||||
%%% The only purpose of this file is to shut up xref/dialyzer
|
||||
%%% @end
|
||||
%%% Created : 27 Apr 2017 by Evgeny Khramtsov <ekhramtsov@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_db_modules).
|
||||
|
||||
%% API
|
||||
-export([is_known/1, get_scope/1]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
is_known(_) ->
|
||||
false.
|
||||
|
||||
get_scope(_) ->
|
||||
[].
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
+80
-42
@@ -32,7 +32,7 @@
|
||||
%% External exports
|
||||
-export([start/2, start_link/2, become_controller/1,
|
||||
socket_type/0, receive_headers/1, url_encode/1,
|
||||
transform_listen_option/2]).
|
||||
transform_listen_option/2, listen_opt_type/1]).
|
||||
|
||||
-export([init/2, opt_type/1]).
|
||||
|
||||
@@ -100,23 +100,15 @@ init({SockMod, Socket}, Opts) ->
|
||||
TLSOpts1 = lists:filter(fun ({certfile, _}) -> true;
|
||||
({ciphers, _}) -> true;
|
||||
({dhfile, _}) -> true;
|
||||
({protocol_options, _}) -> true;
|
||||
(_) -> false
|
||||
end,
|
||||
Opts),
|
||||
TLSOpts2 = case lists:keysearch(protocol_options, 1, Opts) of
|
||||
{value, {_, O}} ->
|
||||
[_|ProtocolOptions] = lists:foldl(
|
||||
fun(X, Acc) -> X ++ Acc end, [],
|
||||
[["|" | binary_to_list(Opt)] || Opt <- O, is_binary(Opt)]
|
||||
),
|
||||
[{protocol_options, iolist_to_binary(ProtocolOptions)} | TLSOpts1];
|
||||
_ -> TLSOpts1
|
||||
TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
|
||||
false -> [compression_none | TLSOpts1];
|
||||
true -> TLSOpts1
|
||||
end,
|
||||
TLSOpts3 = case proplists:get_bool(tls_compression, Opts) of
|
||||
false -> [compression_none | TLSOpts2];
|
||||
true -> TLSOpts2
|
||||
end,
|
||||
TLSOpts = [verify_none | TLSOpts3],
|
||||
TLSOpts = [verify_none | TLSOpts2],
|
||||
{SockMod1, Socket1} = if TLSEnabled ->
|
||||
inet:setopts(Socket, [{recbuf, 8192}]),
|
||||
{ok, TLSSocket} = fast_tls:tcp_to_tls(Socket,
|
||||
@@ -144,33 +136,15 @@ init({SockMod, Socket}, Opts) ->
|
||||
true -> [{[], ejabberd_xmlrpc}];
|
||||
false -> []
|
||||
end,
|
||||
DefinedHandlers = gen_mod:get_opt(
|
||||
request_handlers, Opts,
|
||||
fun(Hs) ->
|
||||
Hs1 = lists:map(fun
|
||||
({Mod, Path}) when is_atom(Mod) -> {Path, Mod};
|
||||
({Path, Mod}) -> {Path, Mod}
|
||||
end, Hs),
|
||||
|
||||
Hs2 = [{str:tokens(
|
||||
iolist_to_binary(Path), <<"/">>),
|
||||
Mod} || {Path, Mod} <- Hs1],
|
||||
[{Path,
|
||||
case Mod of
|
||||
mod_http_bind -> mod_bosh;
|
||||
_ -> Mod
|
||||
end} || {Path, Mod} <- Hs2]
|
||||
end, []),
|
||||
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
|
||||
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
|
||||
Admin ++ Bind ++ XMLRPC,
|
||||
?DEBUG("S: ~p~n", [RequestHandlers]),
|
||||
|
||||
DefaultHost = gen_mod:get_opt(default_host, Opts, fun(A) -> A end, undefined),
|
||||
DefaultHost = proplists:get_value(default_host, Opts),
|
||||
{ok, RE} = re:compile(<<"^(?:\\[(.*?)\\]|(.*?))(?::(\\d+))?$">>),
|
||||
|
||||
CustomHeaders = gen_mod:get_opt(custom_headers, Opts,
|
||||
fun expand_custom_headers/1,
|
||||
[]),
|
||||
CustomHeaders = proplists:get_value(custom_headers, Opts, []),
|
||||
|
||||
?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
|
||||
State = #state{sockmod = SockMod1,
|
||||
@@ -521,12 +495,7 @@ analyze_ip_xff(IP, [], _Host) -> IP;
|
||||
analyze_ip_xff({IPLast, Port}, XFF, Host) ->
|
||||
[ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++
|
||||
[misc:ip_to_list(IPLast)],
|
||||
TrustedProxies = ejabberd_config:get_option(
|
||||
{trusted_proxies, Host},
|
||||
fun(all) -> all;
|
||||
(TPs) ->
|
||||
[iolist_to_binary(TP) || TP <- TPs]
|
||||
end, []),
|
||||
TrustedProxies = ejabberd_config:get_option({trusted_proxies, Host}, []),
|
||||
IPClient = case is_ipchain_trusted(ProxiesIPs,
|
||||
TrustedProxies)
|
||||
of
|
||||
@@ -801,7 +770,9 @@ code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
|
||||
|
||||
-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
|
||||
parse_auth(<<"Basic ", Auth64/binary>>) ->
|
||||
Auth = misc:decode_base64(Auth64),
|
||||
Auth = try base64:decode(Auth64)
|
||||
catch _:badarg -> <<>>
|
||||
end,
|
||||
%% Auth should be a string with the format: user@server:password
|
||||
%% Note that password can contain additional characters '@' and ':'
|
||||
case str:chr(Auth, $:) of
|
||||
@@ -930,7 +901,74 @@ transform_listen_option({request_handlers, Hs}, Opts) ->
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
-spec opt_type(trusted_proxies) -> fun((all | [binary()]) -> all | [binary()]);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(trusted_proxies) ->
|
||||
fun (all) -> all;
|
||||
(TPs) -> [iolist_to_binary(TP) || TP <- TPs] end;
|
||||
opt_type(_) -> [trusted_proxies].
|
||||
|
||||
-spec listen_opt_type(tls) -> fun((boolean()) -> boolean());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(captcha) -> fun((boolean()) -> boolean());
|
||||
(register) -> fun((boolean()) -> boolean());
|
||||
(web_admin) -> fun((boolean()) -> boolean());
|
||||
(http_bind) -> fun((boolean()) -> boolean());
|
||||
(xmlrpc) -> fun((boolean()) -> boolean());
|
||||
(request_handlers) -> fun(([{binary(), atom()}]) ->
|
||||
[{binary(), atom()}]);
|
||||
(default_host) -> fun((binary()) -> binary());
|
||||
(custom_headers) -> fun(([{binary(), binary()}]) ->
|
||||
[{binary(), binary()}]);
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(tls) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(certfile) ->
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(ciphers) ->
|
||||
fun iolist_to_binary/1;
|
||||
listen_opt_type(dhfile) ->
|
||||
fun misc:try_read_file/1;
|
||||
listen_opt_type(protocol_options) ->
|
||||
fun(Options) -> str:join(Options, <<"|">>) end;
|
||||
listen_opt_type(tls_compression) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(captcha) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(register) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(web_admin) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(http_bind) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(xmlrpc) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(request_handlers) ->
|
||||
fun(Hs) ->
|
||||
Hs1 = lists:map(fun
|
||||
({Mod, Path}) when is_atom(Mod) -> {Path, Mod};
|
||||
({Path, Mod}) -> {Path, Mod}
|
||||
end, Hs),
|
||||
Hs2 = [{str:tokens(
|
||||
iolist_to_binary(Path), <<"/">>),
|
||||
Mod} || {Path, Mod} <- Hs1],
|
||||
[{Path,
|
||||
case Mod of
|
||||
mod_http_bind -> mod_bosh;
|
||||
_ -> Mod
|
||||
end} || {Path, Mod} <- Hs2]
|
||||
end;
|
||||
listen_opt_type(default_host) ->
|
||||
fun(A) -> A end;
|
||||
listen_opt_type(custom_headers) ->
|
||||
fun expand_custom_headers/1;
|
||||
listen_opt_type(_) ->
|
||||
%% TODO
|
||||
fun(A) -> A end.
|
||||
|
||||
+16
-10
@@ -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,19 +75,25 @@
|
||||
-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) ->
|
||||
gen_fsm:sync_send_all_state_event(FsmRef,
|
||||
{send_xml, Packet}).
|
||||
case catch p1_fsm:sync_send_all_state_event(FsmRef,
|
||||
{send_xml, Packet},
|
||||
15000)
|
||||
of
|
||||
{'EXIT', {timeout, _}} -> {error, timeout};
|
||||
{'EXIT', _} -> {error, einval};
|
||||
Res -> Res
|
||||
end.
|
||||
|
||||
setopts({http_ws, FsmRef, _IP}, Opts) ->
|
||||
case lists:member({active, once}, Opts) of
|
||||
true ->
|
||||
gen_fsm:send_all_state_event(FsmRef,
|
||||
p1_fsm:send_all_state_event(FsmRef,
|
||||
{activate, self()});
|
||||
_ -> ok
|
||||
end.
|
||||
@@ -99,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,
|
||||
@@ -123,11 +129,9 @@ init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
|
||||
Opts = ejabberd_c2s_config:get_c2s_limits() ++ SOpts,
|
||||
PingInterval = ejabberd_config:get_option(
|
||||
{websocket_ping_interval, ?MYNAME},
|
||||
fun(I) when is_integer(I), I>=0 -> I end,
|
||||
?PING_INTERVAL) * 1000,
|
||||
WSTimeout = ejabberd_config:get_option(
|
||||
{websocket_timeout, ?MYNAME},
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
?WEBSOCKET_TIMEOUT) * 1000,
|
||||
Socket = {http_ws, self(), IP},
|
||||
?DEBUG("Client connected through websocket ~p",
|
||||
@@ -237,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) ->
|
||||
@@ -249,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) ->
|
||||
|
||||
+64
-112
@@ -49,24 +49,19 @@ init(_) ->
|
||||
{ok, {{one_for_one, 10, 1}, listeners_childspec()}}.
|
||||
|
||||
listeners_childspec() ->
|
||||
case ejabberd_config:get_option(listen, fun validate_cfg/1) of
|
||||
undefined ->
|
||||
[];
|
||||
Ls ->
|
||||
Specs = lists:map(
|
||||
fun({Port, Module, Opts}) ->
|
||||
maybe_start_sip(Module),
|
||||
ets:insert(?MODULE, {Port, Module, Opts}),
|
||||
{Port,
|
||||
{?MODULE, start, [Port, Module, Opts]},
|
||||
transient,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[?MODULE]}
|
||||
end, Ls),
|
||||
report_duplicated_portips(Ls),
|
||||
Specs
|
||||
end.
|
||||
Ls = ejabberd_config:get_option(listen, []),
|
||||
Specs = lists:map(
|
||||
fun({Port, Module, Opts}) ->
|
||||
ets:insert(?MODULE, {Port, Module, Opts}),
|
||||
{Port,
|
||||
{?MODULE, start, [Port, Module, Opts]},
|
||||
transient,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[?MODULE]}
|
||||
end, Ls),
|
||||
report_duplicated_portips(Ls),
|
||||
Specs.
|
||||
|
||||
start_listeners() ->
|
||||
lists:foreach(
|
||||
@@ -86,22 +81,16 @@ report_duplicated_portips(L) ->
|
||||
end.
|
||||
|
||||
start(Port, Module, Opts) ->
|
||||
NewOpts = validate_module_options(Module, Opts),
|
||||
%% Check if the module is an ejabberd listener or an independent listener
|
||||
case Module:socket_type() of
|
||||
independent -> Module:start_listener(Port, Opts);
|
||||
_ -> start_dependent(Port, Module, Opts)
|
||||
independent -> Module:start_listener(Port, NewOpts);
|
||||
_ -> start_dependent(Port, Module, NewOpts)
|
||||
end.
|
||||
|
||||
%% @spec(Port, Module, Opts) -> {ok, Pid} | {error, ErrorMessage}
|
||||
start_dependent(Port, Module, Opts) ->
|
||||
try check_listener_options(Opts) of
|
||||
ok ->
|
||||
proc_lib:start_link(?MODULE, init, [Port, Module, Opts])
|
||||
catch
|
||||
throw:{error, Error} ->
|
||||
?ERROR_MSG(Error, []),
|
||||
{error, Error}
|
||||
end.
|
||||
proc_lib:start_link(?MODULE, init, [Port, Module, Opts]).
|
||||
|
||||
init(PortIP, Module, RawOpts) ->
|
||||
{Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
|
||||
@@ -120,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]),
|
||||
@@ -145,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]),
|
||||
@@ -313,7 +304,7 @@ accept(ListenSocket, Module, Opts, Interval) ->
|
||||
ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
|
||||
PPort, inet_parse:ntoa(Addr), Port]);
|
||||
_ ->
|
||||
ok
|
||||
gen_tcp:close(Socket)
|
||||
end,
|
||||
accept(ListenSocket, Module, Opts, NewInterval);
|
||||
{error, Reason} ->
|
||||
@@ -360,7 +351,6 @@ start_listener2(Port, Module, Opts) ->
|
||||
%% It is only required to start the supervisor in some cases.
|
||||
%% But it doesn't hurt to attempt to start it for any listener.
|
||||
%% So, it's normal (and harmless) that in most cases this call returns: {error, {already_started, pid()}}
|
||||
maybe_start_sip(Module),
|
||||
start_listener_sup(Port, Module, Opts).
|
||||
|
||||
start_module_sup(_Port, Module) ->
|
||||
@@ -384,7 +374,7 @@ start_listener_sup(Port, Module, Opts) ->
|
||||
supervisor:start_child(?MODULE, ChildSpec).
|
||||
|
||||
stop_listeners() ->
|
||||
Ports = ejabberd_config:get_option(listen, fun validate_cfg/1),
|
||||
Ports = ejabberd_config:get_option(listen, []),
|
||||
lists:foreach(
|
||||
fun({PortIpNetp, Module, _Opts}) ->
|
||||
delete_listener(PortIpNetp, Module)
|
||||
@@ -408,17 +398,6 @@ add_listener(PortIP, Module, Opts) ->
|
||||
PortIP1 = {Port, IPT, Proto},
|
||||
case start_listener(PortIP1, Module, Opts) of
|
||||
{ok, _Pid} ->
|
||||
Ports = case ejabberd_config:get_option(
|
||||
listen, fun validate_cfg/1) of
|
||||
undefined ->
|
||||
[];
|
||||
Ls ->
|
||||
Ls
|
||||
end,
|
||||
Ports1 = lists:keydelete(PortIP1, 1, Ports),
|
||||
Ports2 = [{PortIP1, Module, Opts} | Ports1],
|
||||
Ports3 = lists:map(fun transform_option/1, Ports2),
|
||||
ejabberd_config:add_option(listen, Ports3),
|
||||
ok;
|
||||
{error, {already_started, _Pid}} ->
|
||||
{error, {already_started, PortIP}};
|
||||
@@ -440,29 +419,10 @@ delete_listener(PortIP, Module) ->
|
||||
delete_listener(PortIP, Module, Opts) ->
|
||||
{Port, IPT, _, _, Proto, _} = parse_listener_portip(PortIP, Opts),
|
||||
PortIP1 = {Port, IPT, Proto},
|
||||
Ports = case ejabberd_config:get_option(
|
||||
listen, fun validate_cfg/1) of
|
||||
undefined ->
|
||||
[];
|
||||
Ls ->
|
||||
Ls
|
||||
end,
|
||||
Ports1 = lists:keydelete(PortIP1, 1, Ports),
|
||||
Ports2 = lists:map(fun transform_option/1, Ports1),
|
||||
ejabberd_config:add_option(listen, Ports2),
|
||||
stop_listener(PortIP1, Module).
|
||||
|
||||
|
||||
maybe_start_sip(esip_socket) ->
|
||||
ejabberd:start_app(esip);
|
||||
maybe_start_sip(_) ->
|
||||
ok.
|
||||
|
||||
config_reloaded() ->
|
||||
New = case ejabberd_config:get_option(listen, fun validate_cfg/1) of
|
||||
undefined -> [];
|
||||
Ls -> Ls
|
||||
end,
|
||||
New = ejabberd_config:get_option(listen, []),
|
||||
Old = ets:tab2list(?MODULE),
|
||||
lists:foreach(
|
||||
fun({PortIP, Module, _Opts}) ->
|
||||
@@ -491,48 +451,6 @@ config_reloaded() ->
|
||||
%%%
|
||||
%%% Check options
|
||||
%%%
|
||||
|
||||
check_listener_options(Opts) ->
|
||||
case includes_deprecated_ssl_option(Opts) of
|
||||
false -> ok;
|
||||
true ->
|
||||
Error = "There is a problem with your ejabberd configuration file: "
|
||||
"the option 'ssl' for listening sockets is no longer available."
|
||||
" To get SSL encryption use the option 'tls'.",
|
||||
throw({error, Error})
|
||||
end,
|
||||
case certfile_readable(Opts) of
|
||||
true -> ok;
|
||||
{false, Path} ->
|
||||
ErrorText = "There is a problem in the configuration: "
|
||||
"the specified file is not readable: ",
|
||||
throw({error, ErrorText ++ Path})
|
||||
end,
|
||||
ok.
|
||||
|
||||
%% Parse the options of the socket,
|
||||
%% and return if the deprecated option 'ssl' is included
|
||||
%% @spec (Opts) -> true | false
|
||||
includes_deprecated_ssl_option(Opts) ->
|
||||
case lists:keysearch(ssl, 1, Opts) of
|
||||
{value, {ssl, _SSLOpts}} ->
|
||||
true;
|
||||
_ ->
|
||||
lists:member(ssl, Opts)
|
||||
end.
|
||||
|
||||
%% @spec (Opts) -> true | {false, Path::string()}
|
||||
certfile_readable(Opts) ->
|
||||
case proplists:lookup(certfile, Opts) of
|
||||
none -> true;
|
||||
{certfile, Path} ->
|
||||
PathS = binary_to_list(Path),
|
||||
case ejabberd_config:is_file_readable(PathS) of
|
||||
true -> true;
|
||||
false -> {false, PathS}
|
||||
end
|
||||
end.
|
||||
|
||||
get_proto(Opts) ->
|
||||
case proplists:get_value(proto, Opts) of
|
||||
undefined ->
|
||||
@@ -654,6 +572,47 @@ transform_options({listen, LOpts}, Opts) ->
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
-spec validate_module_options(module(), [{atom(), any()}]) -> [{atom(), any()}].
|
||||
validate_module_options(Module, Opts) ->
|
||||
try Module:listen_opt_type('') of
|
||||
_ ->
|
||||
lists:filtermap(
|
||||
fun({Opt, Val}) ->
|
||||
case validate_module_option(Module, Opt, Val) of
|
||||
{ok, NewVal} -> {true, {Opt, NewVal}};
|
||||
error -> false
|
||||
end
|
||||
end, Opts)
|
||||
catch _:undef ->
|
||||
?WARNING_MSG("module '~s' doesn't export listen_opt_type/1",
|
||||
[Module]),
|
||||
Opts
|
||||
end.
|
||||
|
||||
-spec validate_module_option(module(), atom(), any()) -> {ok, any()} | error.
|
||||
validate_module_option(Module, Opt, Val) ->
|
||||
case Module:listen_opt_type(Opt) of
|
||||
VFun when is_function(VFun) ->
|
||||
try VFun(Val) of
|
||||
NewVal -> {ok, NewVal}
|
||||
catch {invalid_syntax, Error} ->
|
||||
?ERROR_MSG("ignoring listen option '~s' with "
|
||||
"invalid value: ~p: ~s",
|
||||
[Opt, Val, Error]),
|
||||
error;
|
||||
_:_ ->
|
||||
?ERROR_MSG("ignoring listen option '~s' with "
|
||||
"invalid value: ~p",
|
||||
[Opt, Val]),
|
||||
error
|
||||
end;
|
||||
KnownOpts when is_list(KnownOpts) ->
|
||||
?ERROR_MSG("unknown listen option '~s' for '~s' will be likely "
|
||||
"ignored, available options are: ~s",
|
||||
[Opt, Module, misc:join_atoms(KnownOpts, <<", ">>)]),
|
||||
{ok, Val}
|
||||
end.
|
||||
|
||||
-type transport() :: udp | tcp.
|
||||
-type port_ip_transport() :: inet:port_number() |
|
||||
{inet:port_number(), transport()} |
|
||||
@@ -675,7 +634,7 @@ validate_cfg(L) ->
|
||||
true = ?IS_TRANSPORT(T),
|
||||
{{Port, IP, T}, Mod, Opts};
|
||||
({module, Mod}, {Port, _, Opts}) ->
|
||||
{Port, prepare_mod(Mod), Opts};
|
||||
{Port, Mod, Opts};
|
||||
(Opt, {Port, Mod, Opts}) ->
|
||||
{Port, Mod, [Opt|Opts]}
|
||||
end, {{5222, all_zero_ip(LOpts), tcp}, ejabberd_c2s, []}, LOpts)
|
||||
@@ -694,13 +653,6 @@ prepare_ip(IP) when is_list(IP) ->
|
||||
prepare_ip(IP) when is_binary(IP) ->
|
||||
prepare_ip(binary_to_list(IP)).
|
||||
|
||||
prepare_mod(ejabberd_sip) ->
|
||||
prepare_mod(sip);
|
||||
prepare_mod(sip) ->
|
||||
esip_socket;
|
||||
prepare_mod(Mod) when is_atom(Mod) ->
|
||||
Mod.
|
||||
|
||||
all_zero_ip(Opts) ->
|
||||
case proplists:get_bool(inet6, Opts) of
|
||||
true -> {0,0,0,0,0,0,0,0};
|
||||
|
||||
@@ -207,7 +207,6 @@ init([]) ->
|
||||
ejabberd_mnesia:create(?MODULE, iq_response,
|
||||
[{ram_copies, [node()]},
|
||||
{attributes, record_info(fields, iq_response)}]),
|
||||
mnesia:add_table_copy(iq_response, node(), ram_copies),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
|
||||
@@ -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
|
||||
@@ -160,7 +163,7 @@ reopen_log() ->
|
||||
|
||||
%% @spec () -> ok
|
||||
rotate_log() ->
|
||||
lager_crash_log ! rotate,
|
||||
catch lager_crash_log ! rotate,
|
||||
lists:foreach(
|
||||
fun({lager_file_backend, File}) ->
|
||||
whereis(lager_event) ! {rotate, File};
|
||||
|
||||
+380
-96
@@ -30,130 +30,281 @@
|
||||
|
||||
-module(ejabberd_mnesia).
|
||||
-author('christophe.romain@process-one.net').
|
||||
-export([create/3, reset/2, update/2]).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start/0, create/3, update/2, transform/2, transform/3,
|
||||
dump_schema/0]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-define(STORAGE_TYPES, [disc_copies, disc_only_copies, ram_copies]).
|
||||
-define(NEED_RESET, [local_content, type]).
|
||||
|
||||
-include("logger.hrl").
|
||||
|
||||
create(Module, Name, TabDef)
|
||||
when is_atom(Module), is_atom(Name), is_list(TabDef) ->
|
||||
Path = os:getenv("EJABBERD_SCHEMA_PATH"),
|
||||
Schema = schema(Path, Module, Name, TabDef),
|
||||
-record(state, {tables = #{} :: map(),
|
||||
schema = [] :: [{atom(), [{atom(), any()}]}]}).
|
||||
|
||||
start() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
-spec create(module(), atom(), list()) -> any().
|
||||
create(Module, Name, TabDef) ->
|
||||
gen_server:call(?MODULE, {create, Module, Name, TabDef},
|
||||
%% Huge timeout is need to have enough
|
||||
%% time to transform huge tables
|
||||
timer:minutes(30)).
|
||||
|
||||
init([]) ->
|
||||
ejabberd_config:env_binary_to_list(mnesia, dir),
|
||||
MyNode = node(),
|
||||
DbNodes = mnesia:system_info(db_nodes),
|
||||
case lists:member(MyNode, DbNodes) of
|
||||
true ->
|
||||
case mnesia:system_info(extra_db_nodes) of
|
||||
[] -> mnesia:create_schema([node()]);
|
||||
_ -> 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 ->
|
||||
?CRITICAL_MSG("Node name mismatch: I'm [~s], "
|
||||
"the database is owned by ~p", [MyNode, DbNodes]),
|
||||
?CRITICAL_MSG("Either set ERLANG_NODE in ejabberdctl.cfg "
|
||||
"or change node name in Mnesia", []),
|
||||
{stop, node_name_mismatch}
|
||||
end.
|
||||
|
||||
handle_call({create, Module, Name, TabDef}, _From, State) ->
|
||||
case maps:get(Name, State#state.tables, undefined) of
|
||||
{TabDef, Result} ->
|
||||
{reply, Result, State};
|
||||
_ ->
|
||||
Result = do_create(Module, Name, TabDef, State#state.schema),
|
||||
Tables = maps:put(Name, {TabDef, Result}, State#state.tables),
|
||||
{reply, Result, State#state{tables = Tables}}
|
||||
end;
|
||||
handle_call(_Request, _From, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
do_create(Module, Name, TabDef, TabDefs) ->
|
||||
code:ensure_loaded(Module),
|
||||
Schema = schema(Name, TabDef, TabDefs),
|
||||
{attributes, Attrs} = lists:keyfind(attributes, 1, Schema),
|
||||
case catch mnesia:table_info(Name, attributes) of
|
||||
{'EXIT', _} ->
|
||||
mnesia_op(create_table, [Name, TabDef]);
|
||||
create(Name, TabDef);
|
||||
Attrs ->
|
||||
case need_reset(Name, Schema) of
|
||||
true -> reset(Name, Schema);
|
||||
false -> update(Name, Attrs, Schema)
|
||||
true ->
|
||||
reset(Name, Schema);
|
||||
false ->
|
||||
case update(Name, Attrs, Schema) of
|
||||
{atomic, ok} ->
|
||||
transform(Module, Name, Attrs, Attrs);
|
||||
Err ->
|
||||
Err
|
||||
end
|
||||
end;
|
||||
OldAttrs ->
|
||||
Fun = case lists:member({transform,1}, Module:module_info(exports)) of
|
||||
true -> fun(Old) -> Module:transform(Old) end;
|
||||
false -> fun(Old) -> transform(OldAttrs, Attrs, Old) end
|
||||
end,
|
||||
mnesia_op(transform_table, [Name, Fun, Attrs])
|
||||
transform(Module, Name, OldAttrs, Attrs)
|
||||
end.
|
||||
|
||||
reset(Name, TabDef)
|
||||
when is_atom(Name), is_list(TabDef) ->
|
||||
reset(Name, TabDef) ->
|
||||
?INFO_MSG("Deleting Mnesia table '~s'", [Name]),
|
||||
mnesia_op(delete_table, [Name]),
|
||||
mnesia_op(create_table, [Name, TabDef]).
|
||||
create(Name, TabDef).
|
||||
|
||||
update(Name, TabDef)
|
||||
when is_atom(Name), is_list(TabDef) ->
|
||||
update(Name, TabDef) ->
|
||||
{attributes, Attrs} = lists:keyfind(attributes, 1, TabDef),
|
||||
update(Name, Attrs, TabDef).
|
||||
|
||||
update(Name, Attrs, TabDef) ->
|
||||
Storage = case catch mnesia:table_info(Name, storage_type) of
|
||||
{'EXIT', _} -> unknown;
|
||||
Type -> Type
|
||||
end,
|
||||
NewStorage = lists:foldl(
|
||||
fun({Key, _}, Acc) ->
|
||||
case lists:member(Key, ?STORAGE_TYPES) of
|
||||
true -> Key;
|
||||
false -> Acc
|
||||
end
|
||||
end, Storage, TabDef),
|
||||
R1 = [mnesia_op(change_table_copy_type, [Name, node(), NewStorage])
|
||||
|| Storage=/=NewStorage],
|
||||
CurIndexes = [lists:nth(N-1, Attrs) || N<-mnesia:table_info(Name, index)],
|
||||
NewIndexes = proplists:get_value(index, TabDef, []),
|
||||
R2 = [mnesia_op(del_table_index, [Name, Attr])
|
||||
|| Attr <- CurIndexes--NewIndexes],
|
||||
R3 = [mnesia_op(add_table_index, [Name, Attr])
|
||||
|| Attr <- NewIndexes--CurIndexes],
|
||||
lists:foldl(
|
||||
fun({atomic, ok}, Acc) -> Acc;
|
||||
(Error, _Acc) -> Error
|
||||
end, {atomic, ok}, R1++R2++R3).
|
||||
case change_table_copy_type(Name, TabDef) of
|
||||
{atomic, ok} ->
|
||||
CurrIndexes = [lists:nth(N-1, Attrs) ||
|
||||
N <- mnesia:table_info(Name, index)],
|
||||
NewIndexes = proplists:get_value(index, TabDef, []),
|
||||
case delete_indexes(Name, CurrIndexes -- NewIndexes) of
|
||||
{atomic, ok} ->
|
||||
add_indexes(Name, NewIndexes -- CurrIndexes);
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
change_table_copy_type(Name, TabDef) ->
|
||||
CurrType = mnesia:table_info(Name, storage_type),
|
||||
NewType = case lists:filter(fun is_storage_type_option/1, TabDef) of
|
||||
[{Type, _}|_] -> Type;
|
||||
[] -> CurrType
|
||||
end,
|
||||
if NewType /= CurrType ->
|
||||
?INFO_MSG("Changing Mnesia table '~s' from ~s to ~s",
|
||||
[Name, CurrType, NewType]),
|
||||
mnesia_op(change_table_copy_type, [Name, node(), NewType]);
|
||||
true ->
|
||||
{atomic, ok}
|
||||
end.
|
||||
|
||||
delete_indexes(Name, [Index|Indexes]) ->
|
||||
?INFO_MSG("Deleting index '~s' from Mnesia table '~s'", [Index, Name]),
|
||||
case mnesia_op(del_table_index, [Name, Index]) of
|
||||
{atomic, ok} ->
|
||||
delete_indexes(Name, Indexes);
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
delete_indexes(_Name, []) ->
|
||||
{atomic, ok}.
|
||||
|
||||
add_indexes(Name, [Index|Indexes]) ->
|
||||
?INFO_MSG("Adding index '~s' to Mnesia table '~s'", [Index, Name]),
|
||||
case mnesia_op(add_table_index, [Name, Index]) of
|
||||
{atomic, ok} ->
|
||||
add_indexes(Name, Indexes);
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
add_indexes(_Name, []) ->
|
||||
{atomic, ok}.
|
||||
|
||||
%
|
||||
% utilities
|
||||
%
|
||||
|
||||
schema(false, Module, _Name, TabDef) ->
|
||||
?DEBUG("No custom ~s schema path", [Module]),
|
||||
TabDef;
|
||||
schema(Path, Module, Name, TabDef) ->
|
||||
File = filename:join(Path, atom_to_list(Module)++".mnesia"),
|
||||
case parse(File) of
|
||||
{ok, CustomDefs} ->
|
||||
case lists:keyfind(Name, 1, CustomDefs) of
|
||||
{Name, CustomDef} ->
|
||||
?INFO_MSG("Using custom ~s schema for table ~s",
|
||||
[Module, Name]),
|
||||
merge(TabDef, CustomDef);
|
||||
_ ->
|
||||
TabDef
|
||||
end;
|
||||
schema(Name, Default, Schema) ->
|
||||
case lists:keyfind(Name, 1, Schema) of
|
||||
{_, Custom} ->
|
||||
TabDefs = merge(Custom, Default),
|
||||
?DEBUG("Using custom schema for table '~s': ~p",
|
||||
[Name, TabDefs]),
|
||||
TabDefs;
|
||||
false ->
|
||||
?DEBUG("No custom Mnesia schema for table '~s' found",
|
||||
[Name]),
|
||||
Default
|
||||
end.
|
||||
|
||||
read_schema_file() ->
|
||||
File = schema_path(),
|
||||
case fast_yaml:decode_from_file(File, [plain_as_atom]) of
|
||||
{ok, [Defs|_]} ->
|
||||
?INFO_MSG("Using custom Mnesia schema from ~s", [File]),
|
||||
lists:flatmap(
|
||||
fun({Tab, Opts}) ->
|
||||
case validate_schema_opts(File, Opts) of
|
||||
{ok, NewOpts} ->
|
||||
[{Tab, lists:ukeysort(1, NewOpts)}];
|
||||
error ->
|
||||
[]
|
||||
end
|
||||
end, Defs);
|
||||
{ok, []} ->
|
||||
?WARNING_MSG("Mnesia schema file ~s is empty", [File]),
|
||||
[];
|
||||
{error, enoent} ->
|
||||
?DEBUG("No custom ~s schema path", [Module]),
|
||||
TabDef;
|
||||
{error, Error} ->
|
||||
?ERROR_MSG("Can not use custom ~s schema for table ~s: ~p",
|
||||
[Module, Name, Error]),
|
||||
TabDef
|
||||
?DEBUG("No custom Mnesia schema file found", []),
|
||||
[];
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("Failed to read Mnesia schema file ~s: ~s",
|
||||
[File, fast_yaml:format_error(Reason)]),
|
||||
[]
|
||||
end.
|
||||
|
||||
merge(TabDef, CustomDef) ->
|
||||
{CustomKeys, _} = lists:unzip(CustomDef),
|
||||
CleanDef = lists:foldl(
|
||||
fun(Elem, Acc) ->
|
||||
case lists:member(Elem, ?STORAGE_TYPES) of
|
||||
true ->
|
||||
lists:foldl(
|
||||
fun(Key, CleanAcc) ->
|
||||
lists:keydelete(Key, 1, CleanAcc)
|
||||
end, Acc, ?STORAGE_TYPES);
|
||||
false ->
|
||||
Acc
|
||||
end
|
||||
end, TabDef, CustomKeys),
|
||||
lists:ukeymerge(1,
|
||||
lists:ukeysort(1, CustomDef),
|
||||
lists:ukeysort(1, CleanDef)).
|
||||
|
||||
parse(File) ->
|
||||
case file:consult(File) of
|
||||
{ok, Terms} -> parse(Terms, []);
|
||||
Error -> Error
|
||||
validate_schema_opts(File, Opts) ->
|
||||
try {ok, lists:map(
|
||||
fun({storage_type, Type}) when Type == ram_copies;
|
||||
Type == disc_copies;
|
||||
Type == disc_only_copies ->
|
||||
{Type, [node()]};
|
||||
({storage_type, _} = Opt) ->
|
||||
erlang:error({invalid_value, Opt});
|
||||
({local_content, Bool}) when is_boolean(Bool) ->
|
||||
{local_content, Bool};
|
||||
({local_content, _} = Opt) ->
|
||||
erlang:error({invalid_value, Opt});
|
||||
({type, Type}) when Type == set;
|
||||
Type == ordered_set;
|
||||
Type == bag ->
|
||||
{type, Type};
|
||||
({type, _} = Opt) ->
|
||||
erlang:error({invalid_value, Opt});
|
||||
({attributes, Attrs} = Opt) ->
|
||||
try lists:all(fun is_atom/1, Attrs) of
|
||||
true -> {attributes, Attrs};
|
||||
false -> erlang:error({invalid_value, Opt})
|
||||
catch _:_ -> erlang:error({invalid_value, Opt})
|
||||
end;
|
||||
({index, Indexes} = Opt) ->
|
||||
try lists:all(fun is_atom/1, Indexes) of
|
||||
true -> {index, Indexes};
|
||||
false -> erlang:error({invalid_value, Opt})
|
||||
catch _:_ -> erlang:error({invalid_value, Opt})
|
||||
end;
|
||||
(Opt) ->
|
||||
erlang:error({unknown_option, Opt})
|
||||
end, Opts)}
|
||||
catch _:{invalid_value, {Opt, Val}} ->
|
||||
?ERROR_MSG("Mnesia schema ~s is incorrect: invalid value ~p of "
|
||||
"option '~s'", [File, Val, Opt]),
|
||||
error;
|
||||
_:{unknown_option, Opt} ->
|
||||
?ERROR_MSG("Mnesia schema ~s is incorrect: unknown option ~p",
|
||||
[File, Opt]),
|
||||
error
|
||||
end.
|
||||
parse([], Acc) ->
|
||||
{ok, lists:reverse(Acc)};
|
||||
parse([{Name, Storage, TabDef}|Tail], Acc)
|
||||
when is_atom(Name), is_atom(Storage), is_list(TabDef) ->
|
||||
NewDef = case lists:member(Storage, ?STORAGE_TYPES) of
|
||||
true -> [{Storage, [node()]} | TabDef];
|
||||
false -> TabDef
|
||||
end,
|
||||
parse(Tail, [{Name, NewDef} | Acc]);
|
||||
parse([Other|_], _) ->
|
||||
{error, {invalid, Other}}.
|
||||
|
||||
create(Name, TabDef) ->
|
||||
?INFO_MSG("Creating Mnesia table '~s'", [Name]),
|
||||
case mnesia_op(create_table, [Name, TabDef]) of
|
||||
{atomic, ok} ->
|
||||
add_table_copy(Name);
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
%% The table MUST exist, otherwise the function would fail
|
||||
add_table_copy(Name) ->
|
||||
Type = mnesia:table_info(Name, storage_type),
|
||||
Nodes = mnesia:table_info(Name, Type),
|
||||
case lists:member(node(), Nodes) of
|
||||
true ->
|
||||
{atomic, ok};
|
||||
false ->
|
||||
mnesia_op(add_table_copy, [Name, node(), Type])
|
||||
end.
|
||||
|
||||
merge(Custom, Default) ->
|
||||
NewDefault = case lists:any(fun is_storage_type_option/1, Custom) of
|
||||
true ->
|
||||
lists:filter(
|
||||
fun(O) ->
|
||||
not is_storage_type_option(O)
|
||||
end, Default);
|
||||
false ->
|
||||
Default
|
||||
end,
|
||||
lists:ukeymerge(1, Custom, lists:ukeysort(1, NewDefault)).
|
||||
|
||||
need_reset(Table, TabDef) ->
|
||||
ValuesF = [mnesia:table_info(Table, Key) || Key <- ?NEED_RESET],
|
||||
@@ -164,7 +315,61 @@ need_reset(Table, TabDef) ->
|
||||
({_, _}, _) -> true
|
||||
end, false, lists:zip(ValuesF, ValuesT)).
|
||||
|
||||
transform(OldAttrs, Attrs, Old) ->
|
||||
transform(Module, Name) ->
|
||||
try mnesia:table_info(Name, attributes) of
|
||||
Attrs ->
|
||||
transform(Module, Name, Attrs, Attrs)
|
||||
catch _:{aborted, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
transform(Module, Name, NewAttrs) ->
|
||||
try mnesia:table_info(Name, attributes) of
|
||||
OldAttrs ->
|
||||
transform(Module, Name, OldAttrs, NewAttrs)
|
||||
catch _:{aborted, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
transform(Module, Name, Attrs, Attrs) ->
|
||||
case need_transform(Module, Name) of
|
||||
true ->
|
||||
?INFO_MSG("Transforming table '~s', this may take a while", [Name]),
|
||||
transform_table(Module, Name);
|
||||
false ->
|
||||
{atomic, ok}
|
||||
end;
|
||||
transform(Module, Name, OldAttrs, NewAttrs) ->
|
||||
Fun = case erlang:function_exported(Module, transform, 1) of
|
||||
true -> transform_fun(Module, Name);
|
||||
false -> fun(Old) -> do_transform(OldAttrs, NewAttrs, Old) end
|
||||
end,
|
||||
mnesia_op(transform_table, [Name, Fun, NewAttrs]).
|
||||
|
||||
-spec need_transform(module(), atom()) -> boolean().
|
||||
need_transform(Module, Name) ->
|
||||
case erlang:function_exported(Module, need_transform, 1) of
|
||||
true ->
|
||||
do_need_transform(Module, Name, mnesia:dirty_first(Name));
|
||||
false ->
|
||||
false
|
||||
end.
|
||||
|
||||
do_need_transform(_Module, _Name, '$end_of_table') ->
|
||||
false;
|
||||
do_need_transform(Module, Name, Key) ->
|
||||
Objs = mnesia:dirty_read(Name, Key),
|
||||
case lists:foldl(
|
||||
fun(_, true) -> true;
|
||||
(Obj, _) -> Module:need_transform(Obj)
|
||||
end, undefined, Objs) of
|
||||
true -> true;
|
||||
false -> false;
|
||||
_ ->
|
||||
do_need_transform(Module, Name, mnesia:dirty_next(Name, Key))
|
||||
end.
|
||||
|
||||
do_transform(OldAttrs, Attrs, Old) ->
|
||||
[Name|OldValues] = tuple_to_list(Old),
|
||||
Before = lists:zip(OldAttrs, OldValues),
|
||||
After = lists:foldl(
|
||||
@@ -177,6 +382,56 @@ transform(OldAttrs, Attrs, Old) ->
|
||||
{Attrs, NewRecord} = lists:unzip(After),
|
||||
list_to_tuple([Name|NewRecord]).
|
||||
|
||||
transform_fun(Module, Name) ->
|
||||
fun(Obj) ->
|
||||
try Module:transform(Obj)
|
||||
catch E:R ->
|
||||
StackTrace = erlang:get_stacktrace(),
|
||||
?ERROR_MSG("Failed to transform Mnesia table ~s:~n"
|
||||
"** Record: ~p~n"
|
||||
"** Reason: ~p~n"
|
||||
"** StackTrace: ~p",
|
||||
[Name, Obj, R, StackTrace]),
|
||||
erlang:raise(E, R, StackTrace)
|
||||
end
|
||||
end.
|
||||
|
||||
transform_table(Module, Name) ->
|
||||
Type = mnesia:table_info(Name, type),
|
||||
Attrs = mnesia:table_info(Name, attributes),
|
||||
TmpTab = list_to_atom(atom_to_list(Name) ++ "_backup"),
|
||||
StorageType = if Type == ordered_set -> disc_copies;
|
||||
true -> disc_only_copies
|
||||
end,
|
||||
mnesia:create_table(TmpTab,
|
||||
[{StorageType, [node()]},
|
||||
{type, Type},
|
||||
{local_content, true},
|
||||
{record_name, Name},
|
||||
{attributes, Attrs}]),
|
||||
mnesia:clear_table(TmpTab),
|
||||
Fun = transform_fun(Module, Name),
|
||||
Res = mnesia_op(
|
||||
transaction,
|
||||
[fun() -> do_transform_table(Name, Fun, TmpTab, mnesia:first(Name)) end]),
|
||||
mnesia:delete_table(TmpTab),
|
||||
Res.
|
||||
|
||||
do_transform_table(Name, _Fun, TmpTab, '$end_of_table') ->
|
||||
mnesia:foldl(
|
||||
fun(Obj, _) ->
|
||||
mnesia:write(Name, Obj, write)
|
||||
end, ok, TmpTab);
|
||||
do_transform_table(Name, Fun, TmpTab, Key) ->
|
||||
Next = mnesia:next(Name, Key),
|
||||
Objs = mnesia:read(Name, Key),
|
||||
lists:foreach(
|
||||
fun(Obj) ->
|
||||
mnesia:write(TmpTab, Fun(Obj), write),
|
||||
mnesia:delete_object(Obj)
|
||||
end, Objs),
|
||||
do_transform_table(Name, Fun, TmpTab, Next).
|
||||
|
||||
mnesia_op(Fun, Args) ->
|
||||
case apply(mnesia, Fun, Args) of
|
||||
{atomic, ok} ->
|
||||
@@ -186,3 +441,32 @@ mnesia_op(Fun, Args) ->
|
||||
[Fun, Args, Other]),
|
||||
Other
|
||||
end.
|
||||
|
||||
schema_path() ->
|
||||
Dir = case os:getenv("EJABBERD_MNESIA_SCHEMA") of
|
||||
false -> mnesia:system_info(directory);
|
||||
Path -> Path
|
||||
end,
|
||||
filename:join(Dir, "ejabberd.schema").
|
||||
|
||||
is_storage_type_option({O, _}) ->
|
||||
O == ram_copies orelse O == disc_copies orelse O == disc_only_copies.
|
||||
|
||||
dump_schema() ->
|
||||
File = schema_path(),
|
||||
Schema = lists:flatmap(
|
||||
fun(schema) ->
|
||||
[];
|
||||
(Tab) ->
|
||||
[{Tab, [{storage_type,
|
||||
mnesia:table_info(Tab, storage_type)},
|
||||
{local_content,
|
||||
mnesia:table_info(Tab, local_content)}]}]
|
||||
end, mnesia:system_info(tables)),
|
||||
case file:write_file(File, [fast_yaml:encode(Schema), io_lib:nl()]) of
|
||||
ok ->
|
||||
io:format("Mnesia schema is written to ~s~n", [File]);
|
||||
{error, Reason} ->
|
||||
io:format("Failed to write Mnesia schema to ~s: ~s",
|
||||
[File, file:format_error(Reason)])
|
||||
end.
|
||||
|
||||
+88
-68
File diff suppressed because one or more lines are too long
@@ -25,31 +25,42 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_oauth_mnesia).
|
||||
-behaviour(ejabberd_oauth).
|
||||
|
||||
-export([init/0,
|
||||
store/1,
|
||||
lookup/1,
|
||||
clean/1]).
|
||||
clean/1,
|
||||
use_cache/0]).
|
||||
|
||||
-include("ejabberd_oauth.hrl").
|
||||
|
||||
init() ->
|
||||
ejabberd_mnesia:create(?MODULE, oauth_token,
|
||||
[{disc_copies, [node()]},
|
||||
[{disc_only_copies, [node()]},
|
||||
{attributes,
|
||||
record_info(fields, oauth_token)}]),
|
||||
mnesia:add_table_copy(oauth_token, node(), disc_copies),
|
||||
ok.
|
||||
|
||||
use_cache() ->
|
||||
case mnesia:table_info(oauth_token, storage_type) of
|
||||
disc_only_copies ->
|
||||
ejabberd_config:get_option(
|
||||
oauth_use_cache,
|
||||
ejabberd_config:use_cache(global));
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
store(R) ->
|
||||
mnesia:dirty_write(R).
|
||||
|
||||
lookup(Token) ->
|
||||
case catch mnesia:dirty_read(oauth_token, Token) of
|
||||
[R] ->
|
||||
R;
|
||||
{ok, R};
|
||||
_ ->
|
||||
false
|
||||
error
|
||||
end.
|
||||
|
||||
clean(TS) ->
|
||||
|
||||
+12
-10
@@ -25,6 +25,8 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_oauth_rest).
|
||||
-behaviour(ejabberd_oauth).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-export([init/0,
|
||||
store/1,
|
||||
@@ -58,7 +60,7 @@ store(R) ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to store oauth record ~p: ~p", [R, Err]),
|
||||
{error, Err}
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
lookup(Token) ->
|
||||
@@ -72,27 +74,27 @@ lookup(Token) ->
|
||||
US = {JID#jid.luser, JID#jid.lserver},
|
||||
Scope = proplists:get_value(<<"scope">>, Data, []),
|
||||
Expire = proplists:get_value(<<"expire">>, Data, 0),
|
||||
#oauth_token{token = Token,
|
||||
us = US,
|
||||
scope = Scope,
|
||||
expire = Expire};
|
||||
{ok, #oauth_token{token = Token,
|
||||
us = US,
|
||||
scope = Scope,
|
||||
expire = Expire}};
|
||||
{ok, 404, _Resp} ->
|
||||
false;
|
||||
error;
|
||||
Other ->
|
||||
?ERROR_MSG("Unexpected response for oauth lookup: ~p", [Other]),
|
||||
{error, rest_failed}
|
||||
error
|
||||
end.
|
||||
|
||||
clean(_TS) ->
|
||||
ok.
|
||||
|
||||
path(Path) ->
|
||||
Base = ejabberd_config:get_option(ext_api_path_oauth,
|
||||
fun(X) -> iolist_to_binary(X) end,
|
||||
<<"/oauth">>),
|
||||
Base = ejabberd_config:get_option(ext_api_path_oauth, <<"/oauth">>),
|
||||
<<Base/binary, "/", Path/binary>>.
|
||||
|
||||
|
||||
-spec opt_type(ext_api_path_oauth) -> fun((binary()) -> binary());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(ext_api_path_oauth) ->
|
||||
fun (X) -> iolist_to_binary(X) end;
|
||||
opt_type(_) -> [ext_api_path_oauth].
|
||||
|
||||
+21
-13
@@ -25,7 +25,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_oauth_sql).
|
||||
|
||||
-behaviour(ejabberd_oauth).
|
||||
-compile([{parse_transform, ejabberd_sql_pt}]).
|
||||
|
||||
-export([init/0,
|
||||
@@ -37,6 +37,7 @@
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_sql_pt.hrl").
|
||||
-include("jid.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
init() ->
|
||||
ok.
|
||||
@@ -47,13 +48,20 @@ store(R) ->
|
||||
SJID = jid:encode({User, Server, <<"">>}),
|
||||
Scope = str:join(R#oauth_token.scope, <<" ">>),
|
||||
Expire = R#oauth_token.expire,
|
||||
?SQL_UPSERT(
|
||||
?MYNAME,
|
||||
"oauth_token",
|
||||
["!token=%(Token)s",
|
||||
"jid=%(SJID)s",
|
||||
"scope=%(Scope)s",
|
||||
"expire=%(Expire)d"]).
|
||||
case ?SQL_UPSERT(
|
||||
?MYNAME,
|
||||
"oauth_token",
|
||||
["!token=%(Token)s",
|
||||
"jid=%(SJID)s",
|
||||
"scope=%(Scope)s",
|
||||
"expire=%(Expire)d"]) of
|
||||
ok ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("Failed to write to SQL 'oauth_token' table: ~p",
|
||||
[Err]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
lookup(Token) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
@@ -63,12 +71,12 @@ lookup(Token) ->
|
||||
{selected, [{SJID, Scope, Expire}]} ->
|
||||
JID = jid:decode(SJID),
|
||||
US = {JID#jid.luser, JID#jid.lserver},
|
||||
#oauth_token{token = Token,
|
||||
us = US,
|
||||
scope = str:tokens(Scope, <<" ">>),
|
||||
expire = Expire};
|
||||
{ok, #oauth_token{token = Token,
|
||||
us = US,
|
||||
scope = str:tokens(Scope, <<" ">>),
|
||||
expire = Expire}};
|
||||
_ ->
|
||||
false
|
||||
error
|
||||
end.
|
||||
|
||||
clean(TS) ->
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @doc
|
||||
%%% This is a stub module which will be replaced during
|
||||
%%% configuration load via p1_options:compile/1
|
||||
%%% The only purpose of this file is to shut up xref/dialyzer
|
||||
%%% @end
|
||||
%%% Created : 16 Apr 2017 by Evgeny Khramtsov <ekhramtsov@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_options).
|
||||
|
||||
%% API
|
||||
-export([is_known/1, get_scope/1]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
is_known(_) ->
|
||||
false.
|
||||
|
||||
get_scope(_) ->
|
||||
[].
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
@@ -34,12 +34,9 @@
|
||||
|
||||
-module(ejabberd_piefxis).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
-protocol({xep, 227, '1.0'}).
|
||||
|
||||
-export([import_file/1, export_server/1, export_host/2,
|
||||
opt_type/1]).
|
||||
-export([import_file/1, export_server/1, export_host/2]).
|
||||
|
||||
-define(CHUNK_SIZE, 1024*20). %20k
|
||||
|
||||
@@ -138,7 +135,7 @@ export_host(Dir, FnH, Host) ->
|
||||
{ok, Fd} ->
|
||||
print(Fd, make_piefxis_xml_head()),
|
||||
print(Fd, make_piefxis_host_head(Host)),
|
||||
Users = ejabberd_auth:get_vh_registered_users(Host),
|
||||
Users = ejabberd_auth:get_users(Host),
|
||||
case export_users(Users, Host, Fd) of
|
||||
ok ->
|
||||
print(Fd, make_piefxis_host_tail()),
|
||||
@@ -169,7 +166,7 @@ export_users([], _Server, _Fd) ->
|
||||
export_user(User, Server, Fd) ->
|
||||
Password = ejabberd_auth:get_password_s(User, Server),
|
||||
LServer = jid:nameprep(Server),
|
||||
PasswordFormat = ejabberd_config:get_option({auth_password_format, LServer}, fun(X) -> X end, plain),
|
||||
PasswordFormat = ejabberd_auth:password_format(LServer),
|
||||
Pass = case Password of
|
||||
{_,_,_,_} ->
|
||||
case PasswordFormat of
|
||||
@@ -389,7 +386,7 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
|
||||
#state{server = LServer} = State) ->
|
||||
Name = fxml:get_attr_s(<<"name">>, Attrs),
|
||||
Password = fxml:get_attr_s(<<"password">>, Attrs),
|
||||
PasswordFormat = ejabberd_config:get_option({auth_password_format, LServer}, fun(X) -> X end, plain),
|
||||
PasswordFormat = ejabberd_auth:password_format(LServer),
|
||||
Pass = case PasswordFormat of
|
||||
scram ->
|
||||
case Password of
|
||||
@@ -405,9 +402,9 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
|
||||
stop("Invalid 'user': ~s", [Name]);
|
||||
LUser ->
|
||||
case ejabberd_auth:try_register(LUser, LServer, Pass) of
|
||||
{atomic, _} ->
|
||||
ok ->
|
||||
process_user_els(Els, State#state{user = LUser});
|
||||
Err ->
|
||||
{error, Err} ->
|
||||
stop("Failed to create user '~s': ~p", [Name, Err])
|
||||
end
|
||||
end.
|
||||
@@ -596,7 +593,3 @@ make_xinclude(Fn) ->
|
||||
|
||||
print(Fd, String) ->
|
||||
file:write(Fd, String).
|
||||
|
||||
opt_type(auth_password_format) -> fun (X) -> X end;
|
||||
opt_type(_) -> [auth_password_format].
|
||||
|
||||
|
||||
@@ -0,0 +1,535 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 4 Mar 2017 by Evgeny Khramtsov <ekhramtsov@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_pkix).
|
||||
|
||||
-behaviour(gen_server).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
%% API
|
||||
-export([start_link/0, add_certfile/1, format_error/1, opt_type/1,
|
||||
get_certfile/1, try_certfile/1, route_registered/1]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include_lib("public_key/include/public_key.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jid.hrl").
|
||||
|
||||
-record(state, {validate = true :: boolean(),
|
||||
certs = #{}}).
|
||||
-record(cert_state, {domains = [] :: [binary()]}).
|
||||
|
||||
-type cert() :: #'OTPCertificate'{}.
|
||||
-type priv_key() :: public_key:private_key().
|
||||
-type pub_key() :: #'RSAPublicKey'{} | {integer(), #'Dss-Parms'{}} | #'ECPoint'{}.
|
||||
-type bad_cert_reason() :: cert_expired | invalid_issuer | invalid_signature |
|
||||
name_not_permitted | missing_basic_constraint |
|
||||
invalid_key_usage | selfsigned_peer | unknown_sig_algo |
|
||||
unknown_ca | missing_priv_key.
|
||||
-type bad_cert() :: {bad_cert, bad_cert_reason()}.
|
||||
-type cert_error() :: not_cert | not_der | not_pem | encrypted.
|
||||
-export_type([cert_error/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
-spec add_certfile(filename:filename())
|
||||
-> ok | {error, cert_error() | file:posix()}.
|
||||
add_certfile(Path) ->
|
||||
gen_server:call(?MODULE, {add_certfile, prep_path(Path)}).
|
||||
|
||||
-spec try_certfile(filename:filename()) -> binary().
|
||||
try_certfile(Path0) ->
|
||||
Path = prep_path(Path0),
|
||||
case mk_cert_state(Path, false) of
|
||||
{ok, _} -> Path;
|
||||
{error, _} -> erlang:error(badarg)
|
||||
end.
|
||||
|
||||
route_registered(Route) ->
|
||||
gen_server:call(?MODULE, {route_registered, Route}).
|
||||
|
||||
-spec format_error(cert_error() | file:posix()) -> string().
|
||||
format_error(not_cert) ->
|
||||
"no PEM encoded certificates found";
|
||||
format_error(not_pem) ->
|
||||
"failed to decode from PEM format";
|
||||
format_error(not_der) ->
|
||||
"failed to decode from DER format";
|
||||
format_error(encrypted) ->
|
||||
"encrypted certificate found in the chain";
|
||||
format_error({bad_cert, cert_expired}) ->
|
||||
"certificate is no longer valid as its expiration date has passed";
|
||||
format_error({bad_cert, invalid_issuer}) ->
|
||||
"certificate issuer name does not match the name of the "
|
||||
"issuer certificate in the chain";
|
||||
format_error({bad_cert, invalid_signature}) ->
|
||||
"certificate was not signed by its issuer certificate in the chain";
|
||||
format_error({bad_cert, name_not_permitted}) ->
|
||||
"invalid Subject Alternative Name extension";
|
||||
format_error({bad_cert, missing_basic_constraint}) ->
|
||||
"certificate, required to have the basic constraints extension, "
|
||||
"does not have a basic constraints extension";
|
||||
format_error({bad_cert, invalid_key_usage}) ->
|
||||
"certificate key is used in an invalid way according "
|
||||
"to the key-usage extension";
|
||||
format_error({bad_cert, selfsigned_peer}) ->
|
||||
"self-signed certificate in the chain";
|
||||
format_error({bad_cert, unknown_sig_algo}) ->
|
||||
"certificate is signed using unknown algorithm";
|
||||
format_error({bad_cert, unknown_ca}) ->
|
||||
"certificate is signed by unknown CA";
|
||||
format_error({bad_cert, missing_priv_key}) ->
|
||||
"no matching private key found for certificate in the chain";
|
||||
format_error({bad_cert, Unknown}) ->
|
||||
lists:flatten(io_lib:format("~w", [Unknown]));
|
||||
format_error(Why) ->
|
||||
case file:format_error(Why) of
|
||||
"unknown POSIX error" ->
|
||||
atom_to_list(Why);
|
||||
Reason ->
|
||||
Reason
|
||||
end.
|
||||
|
||||
-spec get_certfile(binary()) -> {ok, binary()} | error.
|
||||
get_certfile(Domain) ->
|
||||
case ejabberd_idna:domain_utf8_to_ascii(Domain) of
|
||||
false ->
|
||||
error;
|
||||
ASCIIDomain ->
|
||||
case ets:lookup(?MODULE, ASCIIDomain) of
|
||||
[] ->
|
||||
case binary:split(ASCIIDomain, <<".">>, [trim]) of
|
||||
[_, Host] ->
|
||||
case ets:lookup(?MODULE, <<"*.", Host/binary>>) of
|
||||
[{_, Path}|_] ->
|
||||
{ok, Path};
|
||||
[] ->
|
||||
error
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
[{_, Path}|_] ->
|
||||
{ok, Path}
|
||||
end
|
||||
end.
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
opt_type(ca_path) ->
|
||||
fun(Path) -> iolist_to_binary(Path) end;
|
||||
opt_type(_) ->
|
||||
[ca_path].
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
process_flag(trap_exit, true),
|
||||
ets:new(?MODULE, [named_table, public, bag]),
|
||||
ejabberd_hooks:add(route_registered, ?MODULE, route_registered, 50),
|
||||
Validate = case os:type() of
|
||||
{win32, _} -> false;
|
||||
_ ->
|
||||
code:ensure_loaded(public_key),
|
||||
erlang:function_exported(
|
||||
public_key, short_name_hash, 1)
|
||||
end,
|
||||
if Validate -> check_ca_dir();
|
||||
true -> ok
|
||||
end,
|
||||
State = #state{validate = Validate},
|
||||
{ok, add_certfiles(State)}.
|
||||
|
||||
handle_call({add_certfile, Path}, _, State) ->
|
||||
{Result, NewState} = add_certfile(Path, State),
|
||||
{reply, Result, NewState};
|
||||
handle_call({route_registered, Host}, _, State) ->
|
||||
NewState = add_certfiles(Host, State),
|
||||
case get_certfile(Host) of
|
||||
{ok, _} -> ok;
|
||||
error ->
|
||||
?WARNING_MSG("No certificate found matching '~s': strictly "
|
||||
"configured clients or servers will reject "
|
||||
"connections with this host", [Host])
|
||||
end,
|
||||
{reply, ok, NewState};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
?WARNING_MSG("unexpected info: ~p", [_Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ejabberd_hooks:delete(route_registered, ?MODULE, route_registered, 50).
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
add_certfiles(State) ->
|
||||
lists:foldl(
|
||||
fun(Host, AccState) ->
|
||||
add_certfiles(Host, AccState)
|
||||
end, State, ejabberd_config:get_myhosts()).
|
||||
|
||||
add_certfiles(Host, State) ->
|
||||
lists:foldl(
|
||||
fun(Opt, AccState) ->
|
||||
case ejabberd_config:get_option({Opt, Host}) of
|
||||
undefined -> AccState;
|
||||
Path ->
|
||||
{_, NewAccState} = add_certfile(Path, AccState),
|
||||
NewAccState
|
||||
end
|
||||
end, State, [c2s_certfile, s2s_certfile, domain_certfile]).
|
||||
|
||||
add_certfile(Path, State) ->
|
||||
case maps:get(Path, State#state.certs, undefined) of
|
||||
#cert_state{} ->
|
||||
{ok, State};
|
||||
undefined ->
|
||||
case mk_cert_state(Path, State#state.validate) of
|
||||
{error, Reason} ->
|
||||
{{error, Reason}, State};
|
||||
{ok, CertState} ->
|
||||
NewCerts = maps:put(Path, CertState, State#state.certs),
|
||||
lists:foreach(
|
||||
fun(Domain) ->
|
||||
ets:insert(?MODULE, {Domain, Path})
|
||||
end, CertState#cert_state.domains),
|
||||
{ok, State#state{certs = NewCerts}}
|
||||
end
|
||||
end.
|
||||
|
||||
mk_cert_state(Path, Validate) ->
|
||||
case check_certfile(Path, Validate) of
|
||||
{ok, Ds} ->
|
||||
{ok, #cert_state{domains = Ds}};
|
||||
{invalid, Ds, {bad_cert, _} = Why} ->
|
||||
?WARNING_MSG("certificate from ~s is invalid: ~s",
|
||||
[Path, format_error(Why)]),
|
||||
{ok, #cert_state{domains = Ds}};
|
||||
{error, Why} = Err ->
|
||||
?ERROR_MSG("failed to read certificate from ~s: ~s",
|
||||
[Path, format_error(Why)]),
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec check_certfile(filename:filename(), boolean())
|
||||
-> {ok, [binary()]} | {invalid, [binary()], bad_cert()} |
|
||||
{error, cert_error() | file:posix()}.
|
||||
check_certfile(Path, Validate) ->
|
||||
try
|
||||
{ok, Data} = file:read_file(Path),
|
||||
{ok, Certs, PrivKeys} = pem_decode(Data),
|
||||
CertPaths = get_cert_paths(Certs),
|
||||
Domains = get_domains(CertPaths),
|
||||
case match_cert_keys(CertPaths, PrivKeys) of
|
||||
{ok, _} ->
|
||||
case validate(CertPaths, Validate) of
|
||||
ok -> {ok, Domains};
|
||||
{error, Why} -> {invalid, Domains, Why}
|
||||
end;
|
||||
{error, Why} ->
|
||||
{invalid, Domains, Why}
|
||||
end
|
||||
catch _:{badmatch, {error, _} = Err} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec pem_decode(binary()) -> {ok, [cert()], [priv_key()]} |
|
||||
{error, cert_error()}.
|
||||
pem_decode(Data) ->
|
||||
try public_key:pem_decode(Data) of
|
||||
PemEntries ->
|
||||
case decode_certs(PemEntries) of
|
||||
{error, _} = Err ->
|
||||
Err;
|
||||
Objects ->
|
||||
case lists:partition(
|
||||
fun(#'OTPCertificate'{}) -> true;
|
||||
(_) -> false
|
||||
end, Objects) of
|
||||
{[], _} ->
|
||||
{error, not_cert};
|
||||
{Certs, PrivKeys} ->
|
||||
{ok, Certs, PrivKeys}
|
||||
end
|
||||
end
|
||||
catch _:_ ->
|
||||
{error, not_pem}
|
||||
end.
|
||||
|
||||
-spec decode_certs([public_key:pem_entry()]) -> {[cert()], [priv_key()]} |
|
||||
{error, not_der | encrypted}.
|
||||
decode_certs(PemEntries) ->
|
||||
try lists:foldr(
|
||||
fun(_, {error, _} = Err) ->
|
||||
Err;
|
||||
({_, _, Flag}, _) when Flag /= not_encrypted ->
|
||||
{error, encrypted};
|
||||
({'Certificate', Der, _}, Acc) ->
|
||||
[public_key:pkix_decode_cert(Der, otp)|Acc];
|
||||
({'PrivateKeyInfo', Der, not_encrypted}, Acc) ->
|
||||
#'PrivateKeyInfo'{privateKeyAlgorithm =
|
||||
#'PrivateKeyInfo_privateKeyAlgorithm'{
|
||||
algorithm = Algo},
|
||||
privateKey = Key} =
|
||||
public_key:der_decode('PrivateKeyInfo', Der),
|
||||
case Algo of
|
||||
?'rsaEncryption' ->
|
||||
[public_key:der_decode(
|
||||
'RSAPrivateKey', iolist_to_binary(Key))|Acc];
|
||||
?'id-dsa' ->
|
||||
[public_key:der_decode(
|
||||
'DSAPrivateKey', iolist_to_binary(Key))|Acc];
|
||||
?'id-ecPublicKey' ->
|
||||
[public_key:der_decode(
|
||||
'ECPrivateKey', iolist_to_binary(Key))|Acc];
|
||||
_ ->
|
||||
Acc
|
||||
end;
|
||||
({Tag, Der, _}, Acc) when Tag == 'RSAPrivateKey';
|
||||
Tag == 'DSAPrivateKey';
|
||||
Tag == 'ECPrivateKey' ->
|
||||
[public_key:der_decode(Tag, Der)|Acc];
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, [], PemEntries)
|
||||
catch _:_ ->
|
||||
{error, not_der}
|
||||
end.
|
||||
|
||||
-spec validate([{path, [cert()]}], boolean()) -> ok | {error, bad_cert()}.
|
||||
validate([{path, Path}|Paths], true) ->
|
||||
case validate_path(Path) of
|
||||
ok ->
|
||||
validate(Paths, true);
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
validate(_, _) ->
|
||||
ok.
|
||||
|
||||
-spec validate_path([cert()]) -> ok | {error, bad_cert()}.
|
||||
validate_path([Cert|_] = Certs) ->
|
||||
case find_local_issuer(Cert) of
|
||||
{ok, IssuerCert} ->
|
||||
try public_key:pkix_path_validation(IssuerCert, Certs, []) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
Err
|
||||
catch error:function_clause ->
|
||||
case erlang:get_stacktrace() of
|
||||
[{public_key, pkix_sign_types, _, _}|_] ->
|
||||
{error, {bad_cert, unknown_sig_algo}};
|
||||
ST ->
|
||||
%% Bug in public_key application
|
||||
erlang:raise(error, function_clause, ST)
|
||||
end
|
||||
end;
|
||||
{error, _} = Err ->
|
||||
case public_key:pkix_is_self_signed(Cert) of
|
||||
true ->
|
||||
{error, {bad_cert, selfsigned_peer}};
|
||||
false ->
|
||||
Err
|
||||
end
|
||||
end.
|
||||
|
||||
-spec ca_dir() -> string().
|
||||
ca_dir() ->
|
||||
ejabberd_config:get_option(ca_path, "/etc/ssl/certs").
|
||||
|
||||
-spec check_ca_dir() -> ok.
|
||||
check_ca_dir() ->
|
||||
case filelib:wildcard(filename:join(ca_dir(), "*.0")) of
|
||||
[] ->
|
||||
Hint = "configuring 'ca_path' option might help",
|
||||
case file:list_dir(ca_dir()) of
|
||||
{error, Why} ->
|
||||
?WARNING_MSG("failed to read CA directory ~s: ~s; ~s",
|
||||
[ca_dir(), file:format_error(Why), Hint]);
|
||||
{ok, _} ->
|
||||
?WARNING_MSG("CA directory ~s doesn't contain "
|
||||
"hashed certificate files; ~s",
|
||||
[ca_dir(), Hint])
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec find_local_issuer(cert()) -> {ok, cert()} | {error, {bad_cert, unknown_ca}}.
|
||||
find_local_issuer(Cert) ->
|
||||
{ok, {_, IssuerID}} = public_key:pkix_issuer_id(Cert, self),
|
||||
Hash = short_name_hash(IssuerID),
|
||||
filelib:fold_files(
|
||||
ca_dir(), Hash ++ "\\.[0-9]+", false,
|
||||
fun(_, {ok, IssuerCert}) ->
|
||||
{ok, IssuerCert};
|
||||
(CertFile, Acc) ->
|
||||
try
|
||||
{ok, Data} = file:read_file(CertFile),
|
||||
{ok, [IssuerCert|_], _} = pem_decode(Data),
|
||||
case public_key:pkix_is_issuer(Cert, IssuerCert) of
|
||||
true ->
|
||||
{ok, IssuerCert};
|
||||
false ->
|
||||
Acc
|
||||
end
|
||||
catch _:{badmatch, {error, Why}} ->
|
||||
?ERROR_MSG("failed to read CA certificate from \"~s\": ~s",
|
||||
[CertFile, format_error(Why)]),
|
||||
Acc
|
||||
end
|
||||
end, {error, {bad_cert, unknown_ca}}).
|
||||
|
||||
-spec match_cert_keys([{path, [cert()]}], [priv_key()])
|
||||
-> {ok, [{cert(), priv_key()}]} | {error, {bad_cert, missing_priv_key}}.
|
||||
match_cert_keys(CertPaths, PrivKeys) ->
|
||||
KeyPairs = [{pubkey_from_privkey(PrivKey), PrivKey} || PrivKey <- PrivKeys],
|
||||
match_cert_keys(CertPaths, KeyPairs, []).
|
||||
|
||||
-spec match_cert_keys([{path, [cert()]}], [{pub_key(), priv_key()}],
|
||||
[{cert(), priv_key()}])
|
||||
-> {ok, [{cert(), priv_key()}]} | {error, {bad_cert, missing_priv_key}}.
|
||||
match_cert_keys([{path, Certs}|CertPaths], KeyPairs, Result) ->
|
||||
[Cert|_] = RevCerts = lists:reverse(Certs),
|
||||
PubKey = pubkey_from_cert(Cert),
|
||||
case lists:keyfind(PubKey, 1, KeyPairs) of
|
||||
false ->
|
||||
{error, {bad_cert, missing_priv_key}};
|
||||
{_, PrivKey} ->
|
||||
match_cert_keys(CertPaths, KeyPairs, [{RevCerts, PrivKey}|Result])
|
||||
end;
|
||||
match_cert_keys([], _, Result) ->
|
||||
{ok, Result}.
|
||||
|
||||
-spec pubkey_from_cert(cert()) -> pub_key().
|
||||
pubkey_from_cert(Cert) ->
|
||||
TBSCert = Cert#'OTPCertificate'.tbsCertificate,
|
||||
PubKeyInfo = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
|
||||
SubjPubKey = PubKeyInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey,
|
||||
case PubKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm of
|
||||
#'PublicKeyAlgorithm'{
|
||||
algorithm = ?rsaEncryption} ->
|
||||
SubjPubKey;
|
||||
#'PublicKeyAlgorithm'{
|
||||
algorithm = ?'id-dsa',
|
||||
parameters = {params, DSSParams}} ->
|
||||
{SubjPubKey, DSSParams};
|
||||
#'PublicKeyAlgorithm'{
|
||||
algorithm = ?'id-ecPublicKey'} ->
|
||||
SubjPubKey
|
||||
end.
|
||||
|
||||
-spec pubkey_from_privkey(priv_key()) -> pub_key().
|
||||
pubkey_from_privkey(#'RSAPrivateKey'{modulus = Modulus,
|
||||
publicExponent = Exp}) ->
|
||||
#'RSAPublicKey'{modulus = Modulus,
|
||||
publicExponent = Exp};
|
||||
pubkey_from_privkey(#'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) ->
|
||||
{Y, #'Dss-Parms'{p = P, q = Q, g = G}};
|
||||
pubkey_from_privkey(#'ECPrivateKey'{publicKey = Key}) ->
|
||||
#'ECPoint'{point = Key}.
|
||||
|
||||
-spec get_domains([{path, [cert()]}]) -> [binary()].
|
||||
get_domains(CertPaths) ->
|
||||
lists:usort(
|
||||
lists:flatmap(
|
||||
fun({path, Certs}) ->
|
||||
Cert = lists:last(Certs),
|
||||
xmpp_stream_pkix:get_cert_domains(Cert)
|
||||
end, CertPaths)).
|
||||
|
||||
-spec get_cert_paths([cert()]) -> [{path, [cert()]}].
|
||||
get_cert_paths(Certs) ->
|
||||
G = digraph:new([acyclic]),
|
||||
lists:foreach(
|
||||
fun(Cert) ->
|
||||
digraph:add_vertex(G, Cert)
|
||||
end, Certs),
|
||||
lists:foreach(
|
||||
fun({Cert1, Cert2}) when Cert1 /= Cert2 ->
|
||||
case public_key:pkix_is_issuer(Cert1, Cert2) of
|
||||
true ->
|
||||
digraph:add_edge(G, Cert1, Cert2);
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
(_) ->
|
||||
ok
|
||||
end, [{Cert1, Cert2} || Cert1 <- Certs, Cert2 <- Certs]),
|
||||
Paths = lists:flatmap(
|
||||
fun(Cert) ->
|
||||
case digraph:in_degree(G, Cert) of
|
||||
0 ->
|
||||
get_cert_path(G, [Cert]);
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end, Certs),
|
||||
digraph:delete(G),
|
||||
Paths.
|
||||
|
||||
get_cert_path(G, [Root|_] = Acc) ->
|
||||
case digraph:out_edges(G, Root) of
|
||||
[] ->
|
||||
[{path, Acc}];
|
||||
Es ->
|
||||
lists:flatmap(
|
||||
fun(E) ->
|
||||
{_, _, V, _} = digraph:edge(G, E),
|
||||
get_cert_path(G, [V|Acc])
|
||||
end, Es)
|
||||
end.
|
||||
|
||||
-spec prep_path(filename:filename()) -> binary().
|
||||
prep_path(Path0) ->
|
||||
case filename:pathtype(Path0) of
|
||||
relative ->
|
||||
{ok, CWD} = file:get_cwd(),
|
||||
iolist_to_binary(filename:join(CWD, Path0));
|
||||
_ ->
|
||||
iolist_to_binary(Path0)
|
||||
end.
|
||||
|
||||
-ifdef(SHORT_NAME_HASH).
|
||||
short_name_hash(IssuerID) ->
|
||||
public_key:short_name_hash(IssuerID).
|
||||
-else.
|
||||
short_name_hash(_) ->
|
||||
"".
|
||||
-endif.
|
||||
@@ -100,13 +100,7 @@ stop_host(Host) ->
|
||||
%% Returns {true, App} if we have configured sql for the given host
|
||||
needs_sql(Host) ->
|
||||
LHost = jid:nameprep(Host),
|
||||
case ejabberd_config:get_option({sql_type, LHost},
|
||||
fun(mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(sqlite) -> sqlite;
|
||||
(mssql) -> mssql;
|
||||
(odbc) -> odbc
|
||||
end, undefined) of
|
||||
case ejabberd_config:get_option({sql_type, LHost}, undefined) of
|
||||
mysql -> {true, p1_mysql};
|
||||
pgsql -> {true, p1_pgsql};
|
||||
sqlite -> {true, sqlite3};
|
||||
@@ -115,6 +109,9 @@ needs_sql(Host) ->
|
||||
undefined -> false
|
||||
end.
|
||||
|
||||
-type sql_type() :: mysql | pgsql | sqlite | mssql | odbc.
|
||||
-spec opt_type(sql_type) -> fun((sql_type()) -> sql_type());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(sql_type) ->
|
||||
fun (mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
|
||||
+40
-30
@@ -31,6 +31,7 @@
|
||||
-define(GEN_SERVER, gen_server).
|
||||
-endif.
|
||||
-behaviour(?GEN_SERVER).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
%% API
|
||||
-export([start_link/4,
|
||||
@@ -41,7 +42,8 @@
|
||||
starttls/2,
|
||||
compress/2,
|
||||
become_controller/2,
|
||||
close/1]).
|
||||
close/1,
|
||||
opt_type/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
@@ -59,9 +61,6 @@
|
||||
xml_stream_state :: fxml_stream:xml_stream_state() | undefined,
|
||||
timeout = infinity:: timeout()}).
|
||||
|
||||
-define(HIBERNATE_TIMEOUT, ejabberd_config:get_option(receiver_hibernate, fun(X) when is_integer(X); X == hibernate-> X end, 90000)).
|
||||
|
||||
|
||||
-spec start_link(inet:socket(), atom(), shaper:shaper(),
|
||||
non_neg_integer() | infinity) -> ignore |
|
||||
{error, any()} |
|
||||
@@ -137,7 +136,7 @@ handle_call({starttls, TLSSocket}, _From, State) ->
|
||||
case fast_tls:recv_data(TLSSocket, <<"">>) of
|
||||
{ok, TLSData} ->
|
||||
{reply, ok,
|
||||
process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
|
||||
process_data(TLSData, NewState), hibernate_timeout()};
|
||||
{error, _} = Err ->
|
||||
{stop, normal, Err, NewState}
|
||||
end;
|
||||
@@ -156,31 +155,31 @@ handle_call({compress, Data}, _From,
|
||||
case ezlib:recv_data(ZlibSocket, <<"">>) of
|
||||
{ok, ZlibData} ->
|
||||
{reply, {ok, ZlibSocket},
|
||||
process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
|
||||
process_data(ZlibData, NewState), hibernate_timeout()};
|
||||
{error, _} = Err ->
|
||||
{stop, normal, Err, NewState}
|
||||
end;
|
||||
handle_call(reset_stream, _From, State) ->
|
||||
NewState = reset_parser(State),
|
||||
Reply = ok,
|
||||
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
|
||||
{reply, Reply, NewState, hibernate_timeout()};
|
||||
handle_call({become_controller, C2SPid}, _From, State) ->
|
||||
XMLStreamState = fxml_stream:new(C2SPid, State#state.max_stanza_size),
|
||||
NewState = State#state{c2s_pid = C2SPid,
|
||||
xml_stream_state = XMLStreamState},
|
||||
activate_socket(NewState),
|
||||
Reply = ok,
|
||||
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
|
||||
{reply, Reply, NewState, hibernate_timeout()};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok, {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
|
||||
Reply = ok, {reply, Reply, State, hibernate_timeout()}.
|
||||
|
||||
handle_cast({change_shaper, Shaper}, State) ->
|
||||
NewShaperState = shaper:new(Shaper),
|
||||
{noreply, State#state{shaper_state = NewShaperState},
|
||||
?HIBERNATE_TIMEOUT};
|
||||
hibernate_timeout()};
|
||||
handle_cast(close, State) -> {stop, normal, State};
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT}.
|
||||
{noreply, State, hibernate_timeout()}.
|
||||
|
||||
handle_info({Tag, _TCPSocket, Data},
|
||||
#state{socket = Socket, sock_mod = SockMod} = State)
|
||||
@@ -191,7 +190,7 @@ handle_info({Tag, _TCPSocket, Data},
|
||||
case fast_tls:recv_data(Socket, Data) of
|
||||
{ok, TLSData} ->
|
||||
{noreply, process_data(TLSData, State),
|
||||
?HIBERNATE_TIMEOUT};
|
||||
hibernate_timeout()};
|
||||
{error, Reason} ->
|
||||
if is_binary(Reason) ->
|
||||
?DEBUG("TLS error = ~s", [Reason]);
|
||||
@@ -204,11 +203,11 @@ handle_info({Tag, _TCPSocket, Data},
|
||||
case ezlib:recv_data(Socket, Data) of
|
||||
{ok, ZlibData} ->
|
||||
{noreply, process_data(ZlibData, State),
|
||||
?HIBERNATE_TIMEOUT};
|
||||
hibernate_timeout()};
|
||||
{error, _Reason} -> {stop, normal, State}
|
||||
end;
|
||||
_ ->
|
||||
{noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT}
|
||||
{noreply, process_data(Data, State), hibernate_timeout()}
|
||||
end;
|
||||
handle_info({Tag, _TCPSocket}, State)
|
||||
when (Tag == tcp_closed) or (Tag == ssl_closed) ->
|
||||
@@ -216,18 +215,18 @@ handle_info({Tag, _TCPSocket}, State)
|
||||
handle_info({Tag, _TCPSocket, Reason}, State)
|
||||
when (Tag == tcp_error) or (Tag == ssl_error) ->
|
||||
case Reason of
|
||||
timeout -> {noreply, State, ?HIBERNATE_TIMEOUT};
|
||||
timeout -> {noreply, State, hibernate_timeout()};
|
||||
_ -> {stop, normal, State}
|
||||
end;
|
||||
handle_info({timeout, _Ref, activate}, State) ->
|
||||
activate_socket(State),
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT};
|
||||
{noreply, State, hibernate_timeout()};
|
||||
handle_info(timeout, State) ->
|
||||
proc_lib:hibernate(?GEN_SERVER, enter_loop,
|
||||
[?MODULE, [], State]),
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT};
|
||||
{noreply, State, hibernate_timeout()};
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State, ?HIBERNATE_TIMEOUT}.
|
||||
{noreply, State, hibernate_timeout()}.
|
||||
|
||||
terminate(_Reason,
|
||||
#state{xml_stream_state = XMLStreamState,
|
||||
@@ -235,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),
|
||||
@@ -249,17 +248,15 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
activate_socket(#state{socket = Socket,
|
||||
sock_mod = SockMod}) ->
|
||||
PeerName = case SockMod of
|
||||
gen_tcp ->
|
||||
inet:setopts(Socket, [{active, once}]),
|
||||
inet:peername(Socket);
|
||||
_ ->
|
||||
SockMod:setopts(Socket, [{active, once}]),
|
||||
SockMod:peername(Socket)
|
||||
end,
|
||||
case PeerName of
|
||||
Res = case SockMod of
|
||||
gen_tcp ->
|
||||
inet:setopts(Socket, [{active, once}]);
|
||||
_ ->
|
||||
SockMod:setopts(Socket, [{active, once}])
|
||||
end,
|
||||
case Res of
|
||||
{error, _Reason} -> self() ! {tcp_closed, Socket};
|
||||
{ok, _} -> ok
|
||||
ok -> ok
|
||||
end.
|
||||
|
||||
%% Data processing for connectors directly generating xmlelement in
|
||||
@@ -275,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;
|
||||
@@ -345,3 +342,16 @@ do_call(Pid, Msg) ->
|
||||
_:_ ->
|
||||
{error, einval}
|
||||
end.
|
||||
|
||||
hibernate_timeout() ->
|
||||
ejabberd_config:get_option(receiver_hibernate, timer:seconds(90)).
|
||||
|
||||
-spec opt_type(receiver_hibernate) -> fun((pos_integer() | hibernate) ->
|
||||
pos_integer() | hibernate);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(receiver_hibernate) ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(hibernate) -> hibernate
|
||||
end;
|
||||
opt_type(_) ->
|
||||
[receiver_hibernate].
|
||||
|
||||
+108
-47
@@ -31,11 +31,12 @@
|
||||
-compile({no_auto_import, [get/1, put/2]}).
|
||||
|
||||
%% API
|
||||
-export([start_link/1, get_proc/1, q/1, qp/1, format_error/1]).
|
||||
-export([start_link/1, get_proc/1, get_connection/1, q/1, qp/1, format_error/1]).
|
||||
%% Commands
|
||||
-export([multi/1, get/1, set/2, del/1,
|
||||
sadd/2, srem/2, smembers/1, sismember/2, scard/1,
|
||||
hget/2, hset/3, hdel/2, hlen/1, hgetall/1, hkeys/1]).
|
||||
hget/2, hset/3, hdel/2, hlen/1, hgetall/1, hkeys/1,
|
||||
subscribe/1, publish/2]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
@@ -53,14 +54,18 @@
|
||||
|
||||
-record(state, {connection :: pid() | undefined,
|
||||
num :: pos_integer(),
|
||||
subscriptions = #{} :: map(),
|
||||
pending_q :: p1_queue:queue()}).
|
||||
|
||||
-type redis_error() :: {error, binary() | timeout | disconnected | overloaded}.
|
||||
-type error_reason() :: binary() | timeout | disconnected | overloaded.
|
||||
-type redis_error() :: {error, error_reason()}.
|
||||
-type redis_reply() :: binary() | [binary()].
|
||||
-type redis_command() :: [binary()].
|
||||
-type redis_pipeline() :: [redis_command()].
|
||||
-type state() :: #state{}.
|
||||
|
||||
-export_type([error_reason/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
@@ -79,11 +84,11 @@ get_connection(I) ->
|
||||
|
||||
-spec q(redis_command()) -> {ok, redis_reply()} | redis_error().
|
||||
q(Command) ->
|
||||
call(get_worker(), {q, Command}, ?MAX_RETRIES).
|
||||
call(get_rnd_id(), {q, Command}, ?MAX_RETRIES).
|
||||
|
||||
-spec qp(redis_pipeline()) -> {ok, [redis_reply()]} | redis_error().
|
||||
qp(Pipeline) ->
|
||||
call(get_worker(), {qp, Pipeline}, ?MAX_RETRIES).
|
||||
call(get_rnd_id(), {qp, Pipeline}, ?MAX_RETRIES).
|
||||
|
||||
-spec multi(fun(() -> any())) -> {ok, [redis_reply()]} | redis_error().
|
||||
multi(F) ->
|
||||
@@ -288,6 +293,30 @@ hkeys(Key) ->
|
||||
erlang:error(transaction_unsupported)
|
||||
end.
|
||||
|
||||
-spec subscribe([binary()]) -> ok | redis_error().
|
||||
subscribe(Channels) ->
|
||||
try ?GEN_SERVER:call(get_proc(1), {subscribe, self(), Channels}, ?CALL_TIMEOUT)
|
||||
catch exit:{Why, {?GEN_SERVER, call, _}} ->
|
||||
Reason = case Why of
|
||||
timeout -> timeout;
|
||||
_ -> disconnected
|
||||
end,
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec publish(iodata(), iodata()) -> {ok, non_neg_integer()} | redis_error() | queued.
|
||||
publish(Channel, Data) ->
|
||||
Cmd = [<<"PUBLISH">>, Channel, Data],
|
||||
case erlang:get(?TR_STACK) of
|
||||
undefined ->
|
||||
case q(Cmd) of
|
||||
{ok, N} -> {ok, binary_to_integer(N)};
|
||||
{error, _} = Err -> Err
|
||||
end;
|
||||
Stack ->
|
||||
tr_enq(Cmd, Stack)
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
@@ -315,6 +344,15 @@ handle_call(connect, From, #state{connection = Pid} = State) ->
|
||||
self() ! connect,
|
||||
handle_call(connect, From, State#state{connection = undefined})
|
||||
end;
|
||||
handle_call({subscribe, Caller, Channels}, _From,
|
||||
#state{connection = Pid, subscriptions = Subs} = State) ->
|
||||
Subs1 = lists:foldl(
|
||||
fun(Channel, Acc) ->
|
||||
Callers = maps:get(Channel, Acc, []) -- [Caller],
|
||||
maps:put(Channel, [Caller|Callers], Acc)
|
||||
end, Subs, Channels),
|
||||
eredis_subscribe(Pid, Channels),
|
||||
{reply, ok, State#state{subscriptions = Subs1}};
|
||||
handle_call(Request, _From, State) ->
|
||||
?WARNING_MSG("unexepected call: ~p", [Request]),
|
||||
{noreply, State}.
|
||||
@@ -326,6 +364,7 @@ handle_info(connect, #state{connection = undefined} = State) ->
|
||||
NewState = case connect(State) of
|
||||
{ok, Connection} ->
|
||||
Q1 = flush_queue(State#state.pending_q),
|
||||
re_subscribe(Connection, State#state.subscriptions),
|
||||
State#state{connection = Connection, pending_q = Q1};
|
||||
{error, _} ->
|
||||
State
|
||||
@@ -342,6 +381,31 @@ handle_info({'EXIT', Pid, _}, State) ->
|
||||
_ ->
|
||||
{noreply, State}
|
||||
end;
|
||||
handle_info({subscribed, Channel, Pid}, State) ->
|
||||
case State#state.connection of
|
||||
Pid ->
|
||||
case maps:is_key(Channel, State#state.subscriptions) of
|
||||
true -> eredis_sub:ack_message(Pid);
|
||||
false ->
|
||||
?WARNING_MSG("got subscription ack for unknown channel ~s",
|
||||
[Channel])
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info({message, Channel, Data, Pid}, State) ->
|
||||
case State#state.connection of
|
||||
Pid ->
|
||||
lists:foreach(
|
||||
fun(Subscriber) ->
|
||||
erlang:send(Subscriber, {redis_message, Channel, Data})
|
||||
end, maps:get(Channel, State#state.subscriptions, [])),
|
||||
eredis_sub:ack_message(Pid);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info(Info, State) ->
|
||||
?WARNING_MSG("unexpected info = ~p", [Info]),
|
||||
{noreply, State}.
|
||||
@@ -357,28 +421,14 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%%===================================================================
|
||||
-spec connect(state()) -> {ok, pid()} | {error, any()}.
|
||||
connect(#state{num = Num}) ->
|
||||
Server = ejabberd_config:get_option(redis_server,
|
||||
fun iolist_to_list/1,
|
||||
"localhost"),
|
||||
Port = ejabberd_config:get_option(redis_port,
|
||||
fun(P) when is_integer(P),
|
||||
P>0, P<65536 ->
|
||||
P
|
||||
end, 6379),
|
||||
DB = ejabberd_config:get_option(redis_db,
|
||||
fun(I) when is_integer(I), I >= 0 ->
|
||||
I
|
||||
end, 0),
|
||||
Pass = ejabberd_config:get_option(redis_password,
|
||||
fun iolist_to_list/1,
|
||||
""),
|
||||
Server = ejabberd_config:get_option(redis_server, "localhost"),
|
||||
Port = ejabberd_config:get_option(redis_port, 6379),
|
||||
DB = ejabberd_config:get_option(redis_db, 0),
|
||||
Pass = ejabberd_config:get_option(redis_password, ""),
|
||||
ConnTimeout = timer:seconds(
|
||||
ejabberd_config:get_option(
|
||||
redis_connect_timeout,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
1)),
|
||||
try case eredis:start_link(Server, Port, DB, Pass,
|
||||
no_reconnect, ConnTimeout) of
|
||||
redis_connect_timeout, 1)),
|
||||
try case do_connect(Num, Server, Port, Pass, DB, ConnTimeout) of
|
||||
{ok, Client} ->
|
||||
?DEBUG("Connection #~p established to Redis at ~s:~p",
|
||||
[Num, Server, Port]),
|
||||
@@ -397,12 +447,24 @@ connect(#state{num = Num}) ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
-spec call({atom(), atom()}, {q, redis_command()}, integer()) ->
|
||||
do_connect(1, Server, Port, Pass, _DB, _ConnTimeout) ->
|
||||
%% First connection in the pool is always a subscriber
|
||||
Res = eredis_sub:start_link(Server, Port, Pass, no_reconnect, infinity, drop),
|
||||
case Res of
|
||||
{ok, Pid} -> eredis_sub:controlling_process(Pid);
|
||||
_ -> ok
|
||||
end,
|
||||
Res;
|
||||
do_connect(_, Server, Port, Pass, DB, ConnTimeout) ->
|
||||
eredis:start_link(Server, Port, DB, Pass, no_reconnect, ConnTimeout).
|
||||
|
||||
-spec call(pos_integer(), {q, redis_command()}, integer()) ->
|
||||
{ok, redis_reply()} | redis_error();
|
||||
({atom(), atom()}, {qp, redis_pipeline()}, integer()) ->
|
||||
(pos_integer(), {qp, redis_pipeline()}, integer()) ->
|
||||
{ok, [redis_reply()]} | redis_error().
|
||||
call({Conn, Parent}, {F, Cmd}, Retries) ->
|
||||
call(I, {F, Cmd}, Retries) ->
|
||||
?DEBUG("redis query: ~p", [Cmd]),
|
||||
Conn = get_connection(I),
|
||||
Res = try eredis:F(Conn, Cmd, ?CALL_TIMEOUT) of
|
||||
{error, Reason} when is_atom(Reason) ->
|
||||
try exit(whereis(Conn), kill) catch _:_ -> ok end,
|
||||
@@ -414,8 +476,8 @@ call({Conn, Parent}, {F, Cmd}, Retries) ->
|
||||
end,
|
||||
case Res of
|
||||
{error, disconnected} when Retries > 0 ->
|
||||
try ?GEN_SERVER:call(Parent, connect, ?CALL_TIMEOUT) of
|
||||
ok -> call({Conn, Parent}, {F, Cmd}, Retries-1);
|
||||
try ?GEN_SERVER:call(get_proc(I), connect, ?CALL_TIMEOUT) of
|
||||
ok -> call(I, {F, Cmd}, Retries-1);
|
||||
{error, _} = Err -> Err
|
||||
catch exit:{Why, {?GEN_SERVER, call, _}} ->
|
||||
Reason1 = case Why of
|
||||
@@ -439,11 +501,9 @@ log_error(Cmd, Reason) ->
|
||||
"** response = ~s",
|
||||
[Cmd, format_error(Reason)]).
|
||||
|
||||
-spec get_worker() -> {atom(), atom()}.
|
||||
get_worker() ->
|
||||
Time = p1_time_compat:system_time(),
|
||||
I = erlang:phash2(Time, ejabberd_redis_sup:get_pool_size()) + 1,
|
||||
{get_connection(I), get_proc(I)}.
|
||||
-spec get_rnd_id() -> pos_integer().
|
||||
get_rnd_id() ->
|
||||
randoms:round_robin(ejabberd_redis_sup:get_pool_size() - 1) + 2.
|
||||
|
||||
-spec get_result([{error, atom() | binary()} | {ok, iodata()}]) ->
|
||||
{ok, [redis_reply()]} | {error, binary()}.
|
||||
@@ -479,10 +539,6 @@ reply(Val) ->
|
||||
_ -> queued
|
||||
end.
|
||||
|
||||
-spec iolist_to_list(iodata()) -> string().
|
||||
iolist_to_list(IOList) ->
|
||||
binary_to_list(iolist_to_binary(IOList)).
|
||||
|
||||
-spec max_fsm_queue() -> pos_integer().
|
||||
max_fsm_queue() ->
|
||||
proplists:get_value(max_queue, fsm_limit_opts(), ?DEFAULT_MAX_QUEUE).
|
||||
@@ -491,14 +547,9 @@ fsm_limit_opts() ->
|
||||
ejabberd_config:fsm_limit_opts([]).
|
||||
|
||||
get_queue_type() ->
|
||||
case ejabberd_config:get_option(
|
||||
redis_queue_type,
|
||||
ejabberd_redis_sup:opt_type(redis_queue_type)) of
|
||||
undefined ->
|
||||
ejabberd_config:default_queue_type(global);
|
||||
Type ->
|
||||
Type
|
||||
end.
|
||||
ejabberd_config:get_option(
|
||||
redis_queue_type,
|
||||
ejabberd_config:default_queue_type(global)).
|
||||
|
||||
-spec flush_queue(p1_queue:queue()) -> p1_queue:queue().
|
||||
flush_queue(Q) ->
|
||||
@@ -531,3 +582,13 @@ clean_queue(Q, CurrTime) ->
|
||||
true ->
|
||||
Q1
|
||||
end.
|
||||
|
||||
re_subscribe(Pid, Subs) ->
|
||||
case maps:keys(Subs) of
|
||||
[] -> ok;
|
||||
Channels -> eredis_subscribe(Pid, Channels)
|
||||
end.
|
||||
|
||||
eredis_subscribe(Pid, Channels) ->
|
||||
?DEBUG("redis query: ~p", [[<<"SUBSCRIBE">>|Channels]]),
|
||||
eredis_sub:subscribe(Pid, Channels).
|
||||
|
||||
+14
-21
@@ -23,6 +23,7 @@
|
||||
-module(ejabberd_redis_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
%% API
|
||||
-export([start_link/0, get_pool_size/0,
|
||||
@@ -107,23 +108,11 @@ is_redis_configured(Host) ->
|
||||
PoolSize = ejabberd_config:has_option({redis_pool_size, Host}),
|
||||
ConnTimeoutConfigured = ejabberd_config:has_option(
|
||||
{redis_connect_timeout, Host}),
|
||||
Modules = ejabberd_config:get_option(
|
||||
{modules, Host},
|
||||
fun(L) when is_list(L) -> L end, []),
|
||||
SMConfigured = ejabberd_config:get_option(
|
||||
{sm_db_type, Host},
|
||||
fun(V) -> V end) == redis,
|
||||
RouterConfigured = ejabberd_config:get_option(
|
||||
{router_db_type, Host},
|
||||
fun(V) -> V end) == redis,
|
||||
ModuleWithRedisDBConfigured =
|
||||
lists:any(
|
||||
fun({Module, Opts}) ->
|
||||
gen_mod:db_type(Host, Opts, Module) == redis
|
||||
end, Modules),
|
||||
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == redis,
|
||||
RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == redis,
|
||||
ServerConfigured or PortConfigured or DBConfigured or PassConfigured or
|
||||
PoolSize or ConnTimeoutConfigured or
|
||||
SMConfigured or RouterConfigured or ModuleWithRedisDBConfigured.
|
||||
SMConfigured or RouterConfigured.
|
||||
|
||||
get_specs() ->
|
||||
lists:map(
|
||||
@@ -133,14 +122,19 @@ get_specs() ->
|
||||
end, lists:seq(1, get_pool_size())).
|
||||
|
||||
get_pool_size() ->
|
||||
ejabberd_config:get_option(
|
||||
redis_pool_size,
|
||||
fun(N) when is_integer(N), N >= 1 -> N end,
|
||||
?DEFAULT_POOL_SIZE).
|
||||
ejabberd_config:get_option(redis_pool_size, ?DEFAULT_POOL_SIZE) + 1.
|
||||
|
||||
iolist_to_list(IOList) ->
|
||||
binary_to_list(iolist_to_binary(IOList)).
|
||||
|
||||
-spec opt_type(redis_connect_timeout) -> fun((pos_integer()) -> pos_integer());
|
||||
(redis_db) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(redis_password) -> fun((binary()) -> binary());
|
||||
(redis_port) -> fun((0..65535) -> 0..65535);
|
||||
(redis_server) -> fun((binary()) -> binary());
|
||||
(redis_pool_size) -> fun((pos_integer()) -> pos_integer());
|
||||
(redis_queue_type) -> fun((ram | file) -> ram | file);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(redis_connect_timeout) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(redis_db) ->
|
||||
@@ -155,5 +149,4 @@ opt_type(redis_queue_type) ->
|
||||
fun(ram) -> ram; (file) -> file end;
|
||||
opt_type(_) ->
|
||||
[redis_connect_timeout, redis_db, redis_password,
|
||||
redis_port, redis_pool_size, redis_server,
|
||||
redis_pool_size, redis_queue_type].
|
||||
redis_port, redis_pool_size, redis_server, redis_queue_type].
|
||||
|
||||
@@ -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
|
||||
|
||||
+42
-59
@@ -30,7 +30,7 @@
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start_link/0, init/1, get_pids/0,
|
||||
transform_options/1, get_random_pid/0, get_random_pid/1,
|
||||
transform_options/1, get_random_pid/0,
|
||||
host_up/1, config_reloaded/0, opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -77,24 +77,23 @@ is_riak_configured() ->
|
||||
lists:any(fun is_riak_configured/1, ?MYHOSTS).
|
||||
|
||||
is_riak_configured(Host) ->
|
||||
ServerConfigured = ejabberd_config:get_option(
|
||||
{riak_server, Host},
|
||||
fun(_) -> true end, false),
|
||||
PortConfigured = ejabberd_config:get_option(
|
||||
{riak_port, Host},
|
||||
fun(_) -> true end, false),
|
||||
ServerConfigured = ejabberd_config:has_option({riak_server, Host}),
|
||||
PortConfigured = ejabberd_config:has_option({riak_port, Host}),
|
||||
StartIntervalConfigured = ejabberd_config:has_option({riak_start_interval, Host}),
|
||||
PoolConfigured = ejabberd_config:has_option({riak_pool_size, Host}),
|
||||
CacertConfigured = ejabberd_config:has_option({riak_cacertfile, Host}),
|
||||
UserConfigured = ejabberd_config:has_option({riak_username, Host}),
|
||||
PassConfigured = ejabberd_config:has_option({riak_password, Host}),
|
||||
AuthConfigured = lists:member(
|
||||
ejabberd_auth_riak,
|
||||
ejabberd_auth:auth_modules(Host)),
|
||||
Modules = ejabberd_config:get_option(
|
||||
{modules, Host},
|
||||
fun(L) when is_list(L) -> L end, []),
|
||||
ModuleWithRiakDBConfigured = lists:any(
|
||||
fun({Module, Opts}) ->
|
||||
gen_mod:db_type(Host, Opts, Module) == riak
|
||||
end, Modules),
|
||||
ServerConfigured or PortConfigured
|
||||
or AuthConfigured or ModuleWithRiakDBConfigured.
|
||||
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == riak,
|
||||
RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == riak,
|
||||
ServerConfigured or PortConfigured or StartIntervalConfigured
|
||||
or PoolConfigured or CacertConfigured
|
||||
or UserConfigured or PassConfigured
|
||||
or SMConfigured or RouterConfigured
|
||||
or AuthConfigured.
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
@@ -140,59 +139,31 @@ get_specs() ->
|
||||
end, lists:seq(1, PoolSize)).
|
||||
|
||||
get_start_interval() ->
|
||||
ejabberd_config:get_option(
|
||||
riak_start_interval,
|
||||
fun(N) when is_integer(N), N >= 1 -> N end,
|
||||
?DEFAULT_RIAK_START_INTERVAL).
|
||||
ejabberd_config:get_option(riak_start_interval, ?DEFAULT_RIAK_START_INTERVAL).
|
||||
|
||||
get_pool_size() ->
|
||||
ejabberd_config:get_option(
|
||||
riak_pool_size,
|
||||
fun(N) when is_integer(N), N >= 1 -> N end,
|
||||
?DEFAULT_POOL_SIZE).
|
||||
ejabberd_config:get_option(riak_pool_size, ?DEFAULT_POOL_SIZE).
|
||||
|
||||
get_riak_server() ->
|
||||
ejabberd_config:get_option(
|
||||
riak_server,
|
||||
fun(S) ->
|
||||
binary_to_list(iolist_to_binary(S))
|
||||
end, ?DEFAULT_RIAK_HOST).
|
||||
ejabberd_config:get_option(riak_server, ?DEFAULT_RIAK_HOST).
|
||||
|
||||
get_riak_cacertfile() ->
|
||||
ejabberd_config:get_option(
|
||||
riak_cacertfile,
|
||||
fun(S) ->
|
||||
binary_to_list(iolist_to_binary(S))
|
||||
end, nil).
|
||||
ejabberd_config:get_option(riak_cacertfile, nil).
|
||||
|
||||
get_riak_username() ->
|
||||
ejabberd_config:get_option(
|
||||
riak_username,
|
||||
fun(S) ->
|
||||
binary_to_list(iolist_to_binary(S))
|
||||
end, nil).
|
||||
ejabberd_config:get_option(riak_username, nil).
|
||||
|
||||
get_riak_password() ->
|
||||
ejabberd_config:get_option(
|
||||
riak_password,
|
||||
fun(S) ->
|
||||
binary_to_list(iolist_to_binary(S))
|
||||
end, nil).
|
||||
ejabberd_config:get_option(riak_password, nil).
|
||||
|
||||
get_riak_port() ->
|
||||
ejabberd_config:get_option(
|
||||
riak_port,
|
||||
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
|
||||
?DEFAULT_RIAK_PORT).
|
||||
ejabberd_config:get_option(riak_port, ?DEFAULT_RIAK_PORT).
|
||||
|
||||
get_pids() ->
|
||||
[ejabberd_riak:get_proc(I) || I <- lists:seq(1, get_pool_size())].
|
||||
|
||||
get_random_pid() ->
|
||||
get_random_pid(p1_time_compat:system_time()).
|
||||
|
||||
get_random_pid(Term) ->
|
||||
I = erlang:phash2(Term, get_pool_size()) + 1,
|
||||
I = randoms:round_robin(get_pool_size()) + 1,
|
||||
ejabberd_riak:get_proc(I).
|
||||
|
||||
transform_options(Opts) ->
|
||||
@@ -203,16 +174,28 @@ transform_options({riak_server, {S, P}}, Opts) ->
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
opt_type(modules) -> fun (L) when is_list(L) -> L end;
|
||||
-spec opt_type(riak_pool_size) -> fun((pos_integer()) -> pos_integer());
|
||||
(riak_port) -> fun((0..65535) -> 0..65535);
|
||||
(riak_server) -> fun((binary()) -> binary());
|
||||
(riak_start_interval) -> fun((pos_integer()) -> pos_integer());
|
||||
(riak_cacertfile) -> fun((binary()) -> binary());
|
||||
(riak_username) -> fun((binary()) -> binary());
|
||||
(riak_password) -> fun((binary()) -> binary());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(riak_pool_size) ->
|
||||
fun (N) when is_integer(N), N >= 1 -> N end;
|
||||
opt_type(riak_port) -> fun (_) -> true end;
|
||||
opt_type(riak_server) -> fun (_) -> true end;
|
||||
opt_type(riak_port) ->
|
||||
fun(P) when is_integer(P), P > 0, P < 65536 -> P end;
|
||||
opt_type(riak_server) ->
|
||||
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
|
||||
opt_type(riak_start_interval) ->
|
||||
fun (N) when is_integer(N), N >= 1 -> N end;
|
||||
opt_type(riak_cacertfile) -> fun iolist_to_binary/1;
|
||||
opt_type(riak_username) -> fun iolist_to_binary/1;
|
||||
opt_type(riak_password) -> fun iolist_to_binary/1;
|
||||
opt_type(riak_cacertfile) ->
|
||||
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
|
||||
opt_type(riak_username) ->
|
||||
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
|
||||
opt_type(riak_password) ->
|
||||
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
|
||||
opt_type(_) ->
|
||||
[modules, riak_pool_size, riak_port, riak_server,
|
||||
[riak_pool_size, riak_port, riak_server,
|
||||
riak_start_interval, riak_cacertfile, riak_username, riak_password].
|
||||
|
||||
+171
-41
@@ -49,7 +49,8 @@
|
||||
get_all_routes/0,
|
||||
is_my_route/1,
|
||||
is_my_host/1,
|
||||
find_routes/0,
|
||||
clean_cache/1,
|
||||
config_reloaded/0,
|
||||
get_backend/0]).
|
||||
|
||||
-export([start_link/0]).
|
||||
@@ -70,12 +71,8 @@
|
||||
-callback register_route(binary(), binary(), local_hint(),
|
||||
undefined | pos_integer(), pid()) -> ok | {error, term()}.
|
||||
-callback unregister_route(binary(), undefined | pos_integer(), pid()) -> ok | {error, term()}.
|
||||
-callback find_routes(binary()) -> [#route{}].
|
||||
-callback find_routes() -> [#route{}].
|
||||
-callback host_of_route(binary()) -> {ok, binary()} | error.
|
||||
-callback is_my_route(binary()) -> boolean().
|
||||
-callback is_my_host(binary()) -> boolean().
|
||||
-callback get_all_routes() -> [binary()].
|
||||
-callback find_routes(binary()) -> {ok, [#route{}]} | {error, any()}.
|
||||
-callback get_all_routes() -> {ok, [binary()]} | {error, any()}.
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
@@ -159,7 +156,9 @@ register_route(Domain, ServerHost, LocalHint, Pid) ->
|
||||
case Mod:register_route(LDomain, LServerHost, LocalHint,
|
||||
get_component_number(LDomain), Pid) of
|
||||
ok ->
|
||||
?DEBUG("Route registered: ~s", [LDomain]);
|
||||
?DEBUG("Route registered: ~s", [LDomain]),
|
||||
ejabberd_hooks:run(route_registered, [LDomain]),
|
||||
delete_cache(Mod, LDomain);
|
||||
{error, Err} ->
|
||||
?ERROR_MSG("Failed to register route ~s: ~p",
|
||||
[LDomain, Err])
|
||||
@@ -186,7 +185,9 @@ unregister_route(Domain, Pid) ->
|
||||
case Mod:unregister_route(
|
||||
LDomain, get_component_number(LDomain), Pid) of
|
||||
ok ->
|
||||
?DEBUG("Route unregistered: ~s", [LDomain]);
|
||||
?DEBUG("Route unregistered: ~s", [LDomain]),
|
||||
ejabberd_hooks:run(route_unregistered, [LDomain]),
|
||||
delete_cache(Mod, LDomain);
|
||||
{error, Err} ->
|
||||
?ERROR_MSG("Failed to unregister route ~s: ~p",
|
||||
[LDomain, Err])
|
||||
@@ -199,15 +200,55 @@ unregister_routes(Domains) ->
|
||||
end,
|
||||
Domains).
|
||||
|
||||
-spec find_routes(binary()) -> [#route{}].
|
||||
find_routes(Domain) ->
|
||||
Mod = get_backend(),
|
||||
case use_cache(Mod) of
|
||||
true ->
|
||||
case ets_cache:lookup(
|
||||
?ROUTES_CACHE, {route, Domain},
|
||||
fun() ->
|
||||
case Mod:find_routes(Domain) of
|
||||
{ok, Rs} when Rs /= [] ->
|
||||
{ok, Rs};
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end) of
|
||||
{ok, Rs} -> Rs;
|
||||
error -> []
|
||||
end;
|
||||
false ->
|
||||
case Mod:find_routes(Domain) of
|
||||
{ok, Rs} -> Rs;
|
||||
_ -> []
|
||||
end
|
||||
end.
|
||||
|
||||
-spec get_all_routes() -> [binary()].
|
||||
get_all_routes() ->
|
||||
Mod = get_backend(),
|
||||
Mod:get_all_routes().
|
||||
|
||||
-spec find_routes() -> [#route{}].
|
||||
find_routes() ->
|
||||
Mod = get_backend(),
|
||||
Mod:find_routes().
|
||||
case use_cache(Mod) of
|
||||
true ->
|
||||
case ets_cache:lookup(
|
||||
?ROUTES_CACHE, routes,
|
||||
fun() ->
|
||||
case Mod:get_all_routes() of
|
||||
{ok, Rs} when Rs /= [] ->
|
||||
{ok, Rs};
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end) of
|
||||
{ok, Rs} -> Rs;
|
||||
error -> []
|
||||
end;
|
||||
false ->
|
||||
case Mod:get_all_routes() of
|
||||
{ok, Rs} -> Rs;
|
||||
_ -> []
|
||||
end
|
||||
end.
|
||||
|
||||
-spec host_of_route(binary()) -> binary().
|
||||
host_of_route(Domain) ->
|
||||
@@ -215,10 +256,11 @@ host_of_route(Domain) ->
|
||||
error ->
|
||||
erlang:error({invalid_domain, Domain});
|
||||
LDomain ->
|
||||
Mod = get_backend(),
|
||||
case Mod:host_of_route(LDomain) of
|
||||
{ok, ServerHost} -> ServerHost;
|
||||
error -> erlang:error({unregistered_route, Domain})
|
||||
case find_routes(LDomain) of
|
||||
[#route{server_host = ServerHost}|_] ->
|
||||
ServerHost;
|
||||
_ ->
|
||||
erlang:error({unregistered_route, Domain})
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -228,8 +270,7 @@ is_my_route(Domain) ->
|
||||
error ->
|
||||
erlang:error({invalid_domain, Domain});
|
||||
LDomain ->
|
||||
Mod = get_backend(),
|
||||
Mod:is_my_route(LDomain)
|
||||
find_routes(LDomain) /= []
|
||||
end.
|
||||
|
||||
-spec is_my_host(binary()) -> boolean().
|
||||
@@ -238,8 +279,10 @@ is_my_host(Domain) ->
|
||||
error ->
|
||||
erlang:error({invalid_domain, Domain});
|
||||
LDomain ->
|
||||
Mod = get_backend(),
|
||||
Mod:is_my_host(LDomain)
|
||||
case find_routes(LDomain) of
|
||||
[#route{server_host = LDomain}|_] -> true;
|
||||
_ -> false
|
||||
end
|
||||
end.
|
||||
|
||||
-spec process_iq(iq()) -> any().
|
||||
@@ -250,12 +293,20 @@ process_iq(#iq{to = To} = IQ) ->
|
||||
ejabberd_sm:process_iq(IQ)
|
||||
end.
|
||||
|
||||
-spec config_reloaded() -> ok.
|
||||
config_reloaded() ->
|
||||
Mod = get_backend(),
|
||||
init_cache(Mod).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([]) ->
|
||||
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
|
||||
Mod = get_backend(),
|
||||
init_cache(Mod),
|
||||
Mod:init(),
|
||||
clean_cache(),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
@@ -273,7 +324,7 @@ handle_info(Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50).
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
@@ -290,8 +341,7 @@ do_route(OrigPacket) ->
|
||||
Packet ->
|
||||
To = xmpp:get_to(Packet),
|
||||
LDstDomain = To#jid.lserver,
|
||||
Mod = get_backend(),
|
||||
case Mod:find_routes(LDstDomain) of
|
||||
case find_routes(LDstDomain) of
|
||||
[] ->
|
||||
ejabberd_s2s:route(Packet);
|
||||
[Route] ->
|
||||
@@ -337,15 +387,11 @@ balancing_route(From, To, Packet, Rs) ->
|
||||
|
||||
-spec get_component_number(binary()) -> pos_integer() | undefined.
|
||||
get_component_number(LDomain) ->
|
||||
ejabberd_config:get_option(
|
||||
{domain_balancing_component_number, LDomain},
|
||||
fun(N) when is_integer(N), N > 1 -> N end,
|
||||
undefined).
|
||||
ejabberd_config:get_option({domain_balancing_component_number, LDomain}).
|
||||
|
||||
-spec get_domain_balancing(jid(), jid(), binary()) -> any().
|
||||
get_domain_balancing(From, To, LDomain) ->
|
||||
case ejabberd_config:get_option(
|
||||
{domain_balancing, LDomain}, fun(D) when is_atom(D) -> D end) of
|
||||
case ejabberd_config:get_option({domain_balancing, LDomain}) of
|
||||
undefined -> p1_time_compat:system_time();
|
||||
random -> p1_time_compat:system_time();
|
||||
source -> jid:tolower(From);
|
||||
@@ -356,16 +402,92 @@ get_domain_balancing(From, To, LDomain) ->
|
||||
|
||||
-spec get_backend() -> module().
|
||||
get_backend() ->
|
||||
DBType = case ejabberd_config:get_option(
|
||||
router_db_type,
|
||||
fun(T) -> ejabberd_config:v_db(?MODULE, T) end) of
|
||||
undefined ->
|
||||
ejabberd_config:default_ram_db(?MODULE);
|
||||
T ->
|
||||
T
|
||||
end,
|
||||
DBType = ejabberd_config:get_option(
|
||||
router_db_type,
|
||||
ejabberd_config:default_ram_db(?MODULE)),
|
||||
list_to_atom("ejabberd_router_" ++ atom_to_list(DBType)).
|
||||
|
||||
-spec cache_nodes(module()) -> [node()].
|
||||
cache_nodes(Mod) ->
|
||||
case erlang:function_exported(Mod, cache_nodes, 0) of
|
||||
true -> Mod:cache_nodes();
|
||||
false -> ejabberd_cluster:get_nodes()
|
||||
end.
|
||||
|
||||
-spec use_cache(module()) -> boolean().
|
||||
use_cache(Mod) ->
|
||||
case erlang:function_exported(Mod, use_cache, 0) of
|
||||
true -> Mod:use_cache();
|
||||
false ->
|
||||
ejabberd_config:get_option(
|
||||
router_use_cache,
|
||||
ejabberd_config:use_cache(global))
|
||||
end.
|
||||
|
||||
-spec delete_cache(module(), binary()) -> ok.
|
||||
delete_cache(Mod, Domain) ->
|
||||
case use_cache(Mod) of
|
||||
true ->
|
||||
ets_cache:delete(?ROUTES_CACHE, {route, Domain}, cache_nodes(Mod)),
|
||||
ets_cache:delete(?ROUTES_CACHE, routes, cache_nodes(Mod));
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec init_cache(module()) -> ok.
|
||||
init_cache(Mod) ->
|
||||
case use_cache(Mod) of
|
||||
true ->
|
||||
ets_cache:new(?ROUTES_CACHE, cache_opts());
|
||||
false ->
|
||||
ets_cache:delete(?ROUTES_CACHE)
|
||||
end.
|
||||
|
||||
-spec cache_opts() -> [proplists:property()].
|
||||
cache_opts() ->
|
||||
MaxSize = ejabberd_config:get_option(
|
||||
router_cache_size,
|
||||
ejabberd_config:cache_size(global)),
|
||||
CacheMissed = ejabberd_config:get_option(
|
||||
router_cache_missed,
|
||||
ejabberd_config:cache_missed(global)),
|
||||
LifeTime = case ejabberd_config:get_option(
|
||||
router_cache_life_time,
|
||||
ejabberd_config:cache_life_time(global)) of
|
||||
infinity -> infinity;
|
||||
I -> timer:seconds(I)
|
||||
end,
|
||||
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
||||
|
||||
-spec clean_cache(node()) -> ok.
|
||||
clean_cache(Node) ->
|
||||
ets_cache:filter(
|
||||
?ROUTES_CACHE,
|
||||
fun(_, error) ->
|
||||
false;
|
||||
(routes, _) ->
|
||||
false;
|
||||
({route, _}, {ok, Rs}) ->
|
||||
not lists:any(
|
||||
fun(#route{pid = Pid}) ->
|
||||
node(Pid) == Node
|
||||
end, Rs)
|
||||
end).
|
||||
|
||||
-spec clean_cache() -> ok.
|
||||
clean_cache() ->
|
||||
ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]).
|
||||
|
||||
-type domain_balancing() :: random | source | destination |
|
||||
bare_source | bare_destination.
|
||||
-spec opt_type(domain_balancing) -> fun((domain_balancing()) -> domain_balancing());
|
||||
(domain_balancing_component_number) -> fun((pos_integer()) -> pos_integer());
|
||||
(router_db_type) -> fun((atom()) -> atom());
|
||||
(router_use_cache) -> fun((boolean()) -> boolean());
|
||||
(router_cache_missed) -> fun((boolean()) -> boolean());
|
||||
(router_cache_size) -> fun((timeout()) -> timeout());
|
||||
(router_cache_life_time) -> fun((timeout()) -> timeout());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(domain_balancing) ->
|
||||
fun (random) -> random;
|
||||
(source) -> source;
|
||||
@@ -376,6 +498,14 @@ opt_type(domain_balancing) ->
|
||||
opt_type(domain_balancing_component_number) ->
|
||||
fun (N) when is_integer(N), N > 1 -> N end;
|
||||
opt_type(router_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
opt_type(O) when O == router_use_cache; O == router_cache_missed ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
opt_type(O) when O == router_cache_size; O == router_cache_life_time ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
opt_type(_) ->
|
||||
[domain_balancing, domain_balancing_component_number,
|
||||
router_db_type].
|
||||
router_db_type, router_use_cache, router_cache_size,
|
||||
router_cache_missed, router_cache_life_time].
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
|
||||
%% API
|
||||
-export([init/0, register_route/5, unregister_route/3, find_routes/1,
|
||||
host_of_route/1, is_my_route/1, is_my_host/1, get_all_routes/0,
|
||||
find_routes/0]).
|
||||
get_all_routes/0, use_cache/0]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_cast/2, handle_call/3, handle_info/2,
|
||||
terminate/2, code_change/3, start_link/0]).
|
||||
@@ -54,6 +53,9 @@ init() ->
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
use_cache() ->
|
||||
false.
|
||||
|
||||
register_route(Domain, ServerHost, LocalHint, undefined, Pid) ->
|
||||
F = fun () ->
|
||||
mnesia:write(#route{domain = Domain,
|
||||
@@ -124,37 +126,15 @@ unregister_route(Domain, _, Pid) ->
|
||||
transaction(F).
|
||||
|
||||
find_routes(Domain) ->
|
||||
mnesia:dirty_read(route, Domain).
|
||||
|
||||
host_of_route(Domain) ->
|
||||
case mnesia:dirty_read(route, Domain) of
|
||||
[#route{server_host = ServerHost}|_] ->
|
||||
{ok, ServerHost};
|
||||
[] ->
|
||||
error
|
||||
end.
|
||||
|
||||
is_my_route(Domain) ->
|
||||
mnesia:dirty_read(route, Domain) /= [].
|
||||
|
||||
is_my_host(Domain) ->
|
||||
case mnesia:dirty_read(route, Domain) of
|
||||
[#route{server_host = Host}|_] ->
|
||||
Host == Domain;
|
||||
[] ->
|
||||
false
|
||||
end.
|
||||
{ok, mnesia:dirty_read(route, Domain)}.
|
||||
|
||||
get_all_routes() ->
|
||||
mnesia:dirty_select(
|
||||
route,
|
||||
ets:fun2ms(
|
||||
fun(#route{domain = Domain, server_host = ServerHost})
|
||||
when Domain /= ServerHost -> Domain
|
||||
end)).
|
||||
|
||||
find_routes() ->
|
||||
ets:tab2list(route).
|
||||
{ok, mnesia:dirty_select(
|
||||
route,
|
||||
ets:fun2ms(
|
||||
fun(#route{domain = Domain, server_host = ServerHost})
|
||||
when Domain /= ServerHost -> Domain
|
||||
end))}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
@@ -165,12 +145,11 @@ init([]) ->
|
||||
[{ram_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, route)}]),
|
||||
mnesia:add_table_copy(route, node(), ram_copies),
|
||||
mnesia:subscribe({table, route, simple}),
|
||||
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) ->
|
||||
@@ -227,7 +206,7 @@ transaction(F) ->
|
||||
ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
|
||||
{error, Reason}
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
-spec update_tables() -> ok.
|
||||
|
||||
@@ -120,7 +120,6 @@ init([]) ->
|
||||
{type, bag},
|
||||
{attributes,
|
||||
record_info(fields, route_multicast)}]),
|
||||
mnesia:add_table_copy(route_multicast, node(), ram_copies),
|
||||
mnesia:subscribe({table, route_multicast, simple}),
|
||||
lists:foreach(
|
||||
fun(Pid) ->
|
||||
|
||||
@@ -22,23 +22,38 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_router_redis).
|
||||
-behaviour(ejabberd_router).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([init/0, register_route/5, unregister_route/3, find_routes/1,
|
||||
host_of_route/1, is_my_route/1, is_my_host/1, get_all_routes/0,
|
||||
find_routes/0]).
|
||||
get_all_routes/0]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_cast/2, handle_call/3, handle_info/2,
|
||||
terminate/2, code_change/3, start_link/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_router.hrl").
|
||||
|
||||
-define(ROUTES_KEY, "ejabberd:routes").
|
||||
-record(state, {}).
|
||||
|
||||
-define(ROUTES_KEY, <<"ejabberd:routes">>).
|
||||
-define(DOMAINS_KEY, <<"ejabberd:domains">>).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init() ->
|
||||
clean_table().
|
||||
Spec = {?MODULE, {?MODULE, start_link, []},
|
||||
transient, 5000, worker, [?MODULE]},
|
||||
case supervisor:start_child(ejabberd_backend_sup, Spec) of
|
||||
{ok, _Pid} -> ok;
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
-spec start_link() -> {ok, pid()} | {error, any()}.
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
register_route(Domain, ServerHost, LocalHint, _, Pid) ->
|
||||
DomKey = domain_key(Domain),
|
||||
@@ -47,7 +62,12 @@ register_route(Domain, ServerHost, LocalHint, _, Pid) ->
|
||||
case ejabberd_redis:multi(
|
||||
fun() ->
|
||||
ejabberd_redis:hset(DomKey, PidKey, T),
|
||||
ejabberd_redis:sadd(?ROUTES_KEY, [Domain])
|
||||
ejabberd_redis:sadd(?DOMAINS_KEY, [Domain]),
|
||||
if Domain /= ServerHost ->
|
||||
ejabberd_redis:sadd(?ROUTES_KEY, [Domain]);
|
||||
true ->
|
||||
ok
|
||||
end
|
||||
end) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
@@ -66,7 +86,8 @@ unregister_route(Domain, _, Pid) ->
|
||||
{ok, _} = ejabberd_redis:multi(
|
||||
fun() ->
|
||||
ejabberd_redis:del([DomKey]),
|
||||
ejabberd_redis:srem(?ROUTES_KEY, [Domain])
|
||||
ejabberd_redis:srem(?ROUTES_KEY, [Domain]),
|
||||
ejabberd_redis:srem(?DOMAINS_KEY, [Domain])
|
||||
end),
|
||||
ok;
|
||||
true ->
|
||||
@@ -83,47 +104,56 @@ find_routes(Domain) ->
|
||||
DomKey = domain_key(Domain),
|
||||
case ejabberd_redis:hgetall(DomKey) of
|
||||
{ok, Vals} ->
|
||||
decode_routes(Domain, Vals);
|
||||
{error, _} ->
|
||||
[]
|
||||
end.
|
||||
|
||||
host_of_route(Domain) ->
|
||||
DomKey = domain_key(Domain),
|
||||
case ejabberd_redis:hgetall(DomKey) of
|
||||
{ok, [{_Pid, Data}|_]} ->
|
||||
{ServerHost, _} = binary_to_term(Data),
|
||||
{ok, ServerHost};
|
||||
{ok, decode_routes(Domain, Vals)};
|
||||
_ ->
|
||||
error
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
is_my_route(Domain) ->
|
||||
case ejabberd_redis:sismember(?ROUTES_KEY, Domain) of
|
||||
{ok, Bool} ->
|
||||
Bool;
|
||||
{error, _} ->
|
||||
false
|
||||
end.
|
||||
|
||||
is_my_host(Domain) ->
|
||||
{ok, Domain} == host_of_route(Domain).
|
||||
|
||||
get_all_routes() ->
|
||||
case ejabberd_redis:smembers(?ROUTES_KEY) of
|
||||
{ok, Routes} ->
|
||||
Routes;
|
||||
{error, _} ->
|
||||
[]
|
||||
{ok, Routes};
|
||||
_ ->
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
find_routes() ->
|
||||
lists:flatmap(fun find_routes/1, get_all_routes()).
|
||||
get_all_domains() ->
|
||||
case ejabberd_redis:smembers(?DOMAINS_KEY) of
|
||||
{ok, Domains} ->
|
||||
{ok, Domains};
|
||||
_ ->
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
clean_table(),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(Info, State) ->
|
||||
?ERROR_MSG("unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
clean_table() ->
|
||||
?DEBUG("Cleaning Redis route entries...", []),
|
||||
lists:foreach(
|
||||
fun(#route{domain = Domain, pid = Pid}) when node(Pid) == node() ->
|
||||
unregister_route(Domain, undefined, Pid);
|
||||
@@ -131,6 +161,20 @@ clean_table() ->
|
||||
ok
|
||||
end, find_routes()).
|
||||
|
||||
find_routes() ->
|
||||
case get_all_domains() of
|
||||
{ok, Domains} ->
|
||||
lists:flatmap(
|
||||
fun(Domain) ->
|
||||
case find_routes(Domain) of
|
||||
{ok, Routes} -> Routes;
|
||||
{error, _} -> []
|
||||
end
|
||||
end, Domains);
|
||||
{error, _} ->
|
||||
[]
|
||||
end.
|
||||
|
||||
domain_key(Domain) ->
|
||||
<<"ejabberd:route:", Domain/binary>>.
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 15 Apr 2017 by Evgeny Khramtsov <ekhramtsov@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_router_riak).
|
||||
-behaviour(ejabberd_router).
|
||||
|
||||
%% API
|
||||
-export([init/0, register_route/5, unregister_route/3, find_routes/1,
|
||||
get_all_routes/0]).
|
||||
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_router.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init() ->
|
||||
clean_table().
|
||||
|
||||
register_route(Domain, ServerHost, LocalHint, _, Pid) ->
|
||||
ejabberd_riak:put(#route{domain = Domain,
|
||||
server_host = ServerHost,
|
||||
local_hint = LocalHint,
|
||||
pid = Pid},
|
||||
route_schema(),
|
||||
[{i, {Domain, Pid}}, {'2i', [{<<"route">>, Domain}]}]).
|
||||
|
||||
unregister_route(Domain, _, Pid) ->
|
||||
ejabberd_riak:delete(route, {Domain, Pid}).
|
||||
|
||||
find_routes(Domain) ->
|
||||
ejabberd_riak:get_by_index(route, route_schema(), <<"route">>, Domain).
|
||||
|
||||
get_all_routes() ->
|
||||
case ejabberd_riak:get(route, route_schema()) of
|
||||
{ok, Routes} ->
|
||||
{ok, lists:flatmap(
|
||||
fun(#route{domain = D, server_host = S}) when D /= S ->
|
||||
[D];
|
||||
(_) ->
|
||||
[]
|
||||
end, Routes)};
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
route_schema() ->
|
||||
{record_info(fields, route), #route{}}.
|
||||
|
||||
clean_table() ->
|
||||
?DEBUG("Cleaning Riak 'route' table...", []),
|
||||
case ejabberd_riak:get(route, route_schema()) of
|
||||
{ok, Routes} ->
|
||||
lists:foreach(
|
||||
fun(#route{domain = Domain, pid = Pid}) ->
|
||||
ejabberd_riak:delete(route, {Domain, Pid})
|
||||
end, Routes);
|
||||
{error, Err} ->
|
||||
?ERROR_MSG("failed to clean Riak 'route' table: ~p", [Err]),
|
||||
Err
|
||||
end.
|
||||
+20
-54
@@ -27,8 +27,7 @@
|
||||
|
||||
%% API
|
||||
-export([init/0, register_route/5, unregister_route/3, find_routes/1,
|
||||
host_of_route/1, is_my_route/1, is_my_host/1, get_all_routes/0,
|
||||
find_routes/0]).
|
||||
get_all_routes/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -40,7 +39,7 @@
|
||||
%%%===================================================================
|
||||
init() ->
|
||||
Node = erlang:atom_to_binary(node(), latin1),
|
||||
?INFO_MSG("Cleaning SQL 'route' table...", []),
|
||||
?DEBUG("Cleaning SQL 'route' table...", []),
|
||||
case ejabberd_sql:sql_query(
|
||||
?MYNAME, ?SQL("delete from route where node=%(Node)s")) of
|
||||
{updated, _} ->
|
||||
@@ -64,18 +63,22 @@ register_route(Domain, ServerHost, LocalHint, _, Pid) ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to update 'route' table: ~p", [Err]),
|
||||
{error, Err}
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
unregister_route(Domain, _, Pid) ->
|
||||
PidS = misc:encode_pid(Pid),
|
||||
Node = erlang:atom_to_binary(node(Pid), latin1),
|
||||
ejabberd_sql:sql_query(
|
||||
?MYNAME,
|
||||
?SQL("delete from route where domain=%(Domain)s "
|
||||
"and pid=%(PidS)s and node=%(Node)s")),
|
||||
%% TODO: return meaningful error
|
||||
ok.
|
||||
case ejabberd_sql:sql_query(
|
||||
?MYNAME,
|
||||
?SQL("delete from route where domain=%(Domain)s "
|
||||
"and pid=%(PidS)s and node=%(Node)s")) of
|
||||
{updated, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to delete from 'route' table: ~p", [Err]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
find_routes(Domain) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
@@ -83,61 +86,24 @@ find_routes(Domain) ->
|
||||
?SQL("select @(server_host)s, @(node)s, @(pid)s, @(local_hint)s "
|
||||
"from route where domain=%(Domain)s")) of
|
||||
{selected, Rows} ->
|
||||
lists:flatmap(
|
||||
fun(Row) ->
|
||||
row_to_route(Domain, Row)
|
||||
end, Rows);
|
||||
{ok, lists:flatmap(
|
||||
fun(Row) ->
|
||||
row_to_route(Domain, Row)
|
||||
end, Rows)};
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'route' table: ~p", [Err]),
|
||||
{error, Err}
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
host_of_route(Domain) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
?MYNAME,
|
||||
?SQL("select @(server_host)s from route where domain=%(Domain)s")) of
|
||||
{selected, [{ServerHost}|_]} ->
|
||||
{ok, ServerHost};
|
||||
{selected, []} ->
|
||||
error;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'route' table: ~p", [Err]),
|
||||
error
|
||||
end.
|
||||
|
||||
is_my_route(Domain) ->
|
||||
case host_of_route(Domain) of
|
||||
{ok, _} -> true;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
is_my_host(Domain) ->
|
||||
{ok, Domain} == host_of_route(Domain).
|
||||
|
||||
get_all_routes() ->
|
||||
case ejabberd_sql:sql_query(
|
||||
?MYNAME,
|
||||
?SQL("select @(domain)s from route where domain <> server_host")) of
|
||||
{selected, Domains} ->
|
||||
[Domain || {Domain} <- Domains];
|
||||
{ok, [Domain || {Domain} <- Domains]};
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'route' table: ~p", [Err]),
|
||||
[]
|
||||
end.
|
||||
|
||||
find_routes() ->
|
||||
case ejabberd_sql:sql_query(
|
||||
?MYNAME,
|
||||
?SQL("select @(domain)s, @(server_host)s, @(node)s, @(pid)s, "
|
||||
"@(local_hint)s from route")) of
|
||||
{selected, Rows} ->
|
||||
lists:flatmap(
|
||||
fun({Domain, ServerHost, Node, Pid, LocalHint}) ->
|
||||
row_to_route(Domain, {ServerHost, Node, Pid, LocalHint})
|
||||
end, Rows);
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'route' table: ~p", [Err]),
|
||||
[]
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
|
||||
+71
-77
@@ -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,
|
||||
@@ -199,46 +199,38 @@ dirty_get_connections() ->
|
||||
-spec tls_options(binary(), [proplists:property()]) -> [proplists:property()].
|
||||
tls_options(LServer, DefaultOpts) ->
|
||||
TLSOpts1 = case ejabberd_config:get_option(
|
||||
{s2s_certfile, LServer},
|
||||
fun iolist_to_binary/1,
|
||||
{domain_certfile, LServer},
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, LServer},
|
||||
fun iolist_to_binary/1)) of
|
||||
{s2s_certfile, LServer})) of
|
||||
undefined -> DefaultOpts;
|
||||
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
|
||||
{certfile, CertFile})
|
||||
end,
|
||||
TLSOpts2 = case ejabberd_config:get_option(
|
||||
{s2s_ciphers, LServer},
|
||||
fun iolist_to_binary/1) of
|
||||
{s2s_ciphers, LServer}) of
|
||||
undefined -> TLSOpts1;
|
||||
Ciphers -> lists:keystore(ciphers, 1, TLSOpts1,
|
||||
{ciphers, Ciphers})
|
||||
end,
|
||||
TLSOpts3 = case ejabberd_config:get_option(
|
||||
{s2s_protocol_options, LServer},
|
||||
fun (Options) -> str:join(Options, <<$|>>) end) of
|
||||
{s2s_protocol_options, LServer}) of
|
||||
undefined -> TLSOpts2;
|
||||
ProtoOpts -> lists:keystore(protocol_options, 1, TLSOpts2,
|
||||
{protocol_options, ProtoOpts})
|
||||
end,
|
||||
TLSOpts4 = case ejabberd_config:get_option(
|
||||
{s2s_dhfile, LServer},
|
||||
fun iolist_to_binary/1) of
|
||||
{s2s_dhfile, LServer}) of
|
||||
undefined -> TLSOpts3;
|
||||
DHFile -> lists:keystore(dhfile, 1, TLSOpts3,
|
||||
{dhfile, DHFile})
|
||||
end,
|
||||
TLSOpts5 = case ejabberd_config:get_option(
|
||||
{s2s_cafile, LServer},
|
||||
fun iolist_to_binary/1) of
|
||||
{s2s_cafile, LServer}) of
|
||||
undefined -> TLSOpts4;
|
||||
CAFile -> lists:keystore(cafile, 1, TLSOpts4,
|
||||
{cafile, CAFile})
|
||||
end,
|
||||
case ejabberd_config:get_option(
|
||||
{s2s_tls_compression, LServer},
|
||||
fun(B) when is_boolean(B) -> B end) of
|
||||
case ejabberd_config:get_option({s2s_tls_compression, LServer}) of
|
||||
undefined -> TLSOpts5;
|
||||
false -> [compression_none | TLSOpts5];
|
||||
true -> lists:delete(compression_none, TLSOpts5)
|
||||
@@ -261,37 +253,21 @@ tls_enabled(LServer) ->
|
||||
|
||||
-spec zlib_enabled(binary()) -> boolean().
|
||||
zlib_enabled(LServer) ->
|
||||
ejabberd_config:get_option(
|
||||
{s2s_zlib, LServer},
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
false).
|
||||
ejabberd_config:get_option({s2s_zlib, LServer}, false).
|
||||
|
||||
-spec use_starttls(binary()) -> boolean() | optional | required | required_trusted.
|
||||
use_starttls(LServer) ->
|
||||
ejabberd_config:get_option(
|
||||
{s2s_use_starttls, LServer},
|
||||
fun(true) -> true;
|
||||
(false) -> false;
|
||||
(optional) -> optional;
|
||||
(required) -> required;
|
||||
(required_trusted) -> required_trusted
|
||||
end, false).
|
||||
ejabberd_config:get_option({s2s_use_starttls, LServer}, false).
|
||||
|
||||
-spec get_idle_timeout(binary()) -> non_neg_integer() | infinity.
|
||||
get_idle_timeout(LServer) ->
|
||||
ejabberd_config:get_option(
|
||||
{s2s_timeout, LServer},
|
||||
fun(I) when is_integer(I), I >= 0 -> timer:seconds(I);
|
||||
(infinity) -> infinity
|
||||
end, timer:minutes(10)).
|
||||
ejabberd_config:get_option({s2s_timeout, LServer}, timer:minutes(10)).
|
||||
|
||||
-spec queue_type(binary()) -> ram | file.
|
||||
queue_type(LServer) ->
|
||||
case ejabberd_config:get_option(
|
||||
{s2s_queue_type, LServer}, opt_type(s2s_queue_type)) of
|
||||
undefined -> ejabberd_config:default_queue_type(LServer);
|
||||
Type -> Type
|
||||
end.
|
||||
ejabberd_config:get_option(
|
||||
{s2s_queue_type, LServer},
|
||||
ejabberd_config:default_queue_type(LServer)).
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
@@ -303,7 +279,6 @@ init([]) ->
|
||||
[{ram_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, s2s)}]),
|
||||
mnesia:add_table_copy(s2s, node(), ram_copies),
|
||||
mnesia:subscribe(system),
|
||||
ejabberd_commands:register_commands(get_commands_spec()),
|
||||
ejabberd_mnesia:create(?MODULE, temporarily_blocked,
|
||||
@@ -394,7 +369,7 @@ do_route(Packet) ->
|
||||
<<"Server connections to local "
|
||||
"subdomains are forbidden">>, Lang);
|
||||
forbidden ->
|
||||
xmpp:err_forbidden(<<"Denied by ACL">>, Lang);
|
||||
xmpp:err_forbidden(<<"Access denied by service policy">>, Lang);
|
||||
internal_server_error ->
|
||||
xmpp:err_internal_server_error()
|
||||
end,
|
||||
@@ -505,9 +480,13 @@ new_connection(MyServer, Server, From, FromTo,
|
||||
end,
|
||||
TRes = mnesia:transaction(F),
|
||||
case TRes of
|
||||
{atomic, Pid} ->
|
||||
ejabberd_s2s_out:connect(Pid),
|
||||
[Pid];
|
||||
{atomic, Pid1} ->
|
||||
if Pid1 == Pid ->
|
||||
ejabberd_s2s_out:connect(Pid);
|
||||
true ->
|
||||
ejabberd_s2s_out:stop(Pid)
|
||||
end,
|
||||
[Pid1];
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("failed to register connection ~s -> ~s: ~p",
|
||||
[MyServer, Server, Reason]),
|
||||
@@ -544,9 +523,7 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
|
||||
-spec is_service(jid(), jid()) -> boolean().
|
||||
is_service(From, To) ->
|
||||
LFromDomain = From#jid.lserver,
|
||||
case ejabberd_config:get_option(
|
||||
{route_subdomains, LFromDomain},
|
||||
fun(s2s) -> s2s; (local) -> local end, local) of
|
||||
case ejabberd_config:get_option({route_subdomains, LFromDomain}, local) of
|
||||
s2s -> % bypass RFC 3920 10.3
|
||||
false;
|
||||
local ->
|
||||
@@ -569,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() ->
|
||||
@@ -603,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)
|
||||
@@ -646,10 +621,7 @@ allow_host(MyServer, S2SHost) ->
|
||||
not is_temporarly_blocked(S2SHost).
|
||||
|
||||
allow_host1(MyHost, S2SHost) ->
|
||||
Rule = ejabberd_config:get_option(
|
||||
{s2s_access, MyHost},
|
||||
fun acl:access_rules_validator/1,
|
||||
all),
|
||||
Rule = ejabberd_config:get_option({s2s_access, MyHost}, all),
|
||||
JID = jid:make(S2SHost),
|
||||
case acl:match_rule(MyHost, Rule, JID) of
|
||||
deny -> false;
|
||||
@@ -710,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];
|
||||
@@ -719,17 +691,30 @@ get_s2s_state(S2sPid) ->
|
||||
end,
|
||||
[{s2s_pid, S2sPid} | Infos].
|
||||
|
||||
-type use_starttls() :: boolean() | optional | required | required_trusted.
|
||||
-spec opt_type(route_subdomains) -> fun((s2s | local) -> s2s | local);
|
||||
(s2s_access) -> fun((any()) -> any());
|
||||
(s2s_certfile) -> fun((binary()) -> binary());
|
||||
(s2s_ciphers) -> fun((binary()) -> binary());
|
||||
(s2s_dhfile) -> fun((binary()) -> binary());
|
||||
(s2s_cafile) -> fun((binary()) -> binary());
|
||||
(s2s_protocol_options) -> fun(([binary()]) -> binary());
|
||||
(s2s_tls_compression) -> fun((boolean()) -> boolean());
|
||||
(s2s_use_starttls) -> fun((use_starttls()) -> use_starttls());
|
||||
(s2s_zlib) -> fun((boolean()) -> boolean());
|
||||
(s2s_timeout) -> fun((timeout()) -> timeout());
|
||||
(s2s_queue_type) -> fun((ram | file) -> ram | file);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(route_subdomains) ->
|
||||
fun (s2s) -> s2s;
|
||||
(local) -> local
|
||||
end;
|
||||
opt_type(s2s_access) ->
|
||||
fun acl:access_rules_validator/1;
|
||||
opt_type(domain_certfile) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_certfile) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_certfile) -> fun misc:try_read_file/1;
|
||||
opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_dhfile) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_cafile) -> fun iolist_to_binary/1;
|
||||
opt_type(s2s_dhfile) -> fun misc:try_read_file/1;
|
||||
opt_type(s2s_cafile) -> fun misc:try_read_file/1;
|
||||
opt_type(s2s_protocol_options) ->
|
||||
fun (Options) -> str:join(Options, <<"|">>) end;
|
||||
opt_type(s2s_tls_compression) ->
|
||||
@@ -741,15 +726,24 @@ opt_type(s2s_use_starttls) ->
|
||||
(false) -> false;
|
||||
(optional) -> optional;
|
||||
(required) -> required;
|
||||
(required_trusted) -> required_trusted
|
||||
(required_trusted) ->
|
||||
?WARNING_MSG("The value 'required_trusted' of option "
|
||||
"'s2s_use_starttls' is deprected and will be "
|
||||
"unsupported in future releases. Instead, "
|
||||
"set it to 'required' and make sure "
|
||||
"mod_s2s_dialback is *NOT* loaded", []),
|
||||
required_trusted
|
||||
end;
|
||||
opt_type(s2s_zlib) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
opt_type(s2s_timeout) ->
|
||||
fun(I) when is_integer(I), I>=0 -> I;
|
||||
(infinity) -> infinity
|
||||
fun(I) when is_integer(I), I >= 0 -> timer:seconds(I);
|
||||
(infinity) -> infinity;
|
||||
(unlimited) -> infinity
|
||||
end;
|
||||
opt_type(s2s_queue_type) ->
|
||||
fun(ram) -> ram; (file) -> file end;
|
||||
opt_type(_) ->
|
||||
[route_subdomains, s2s_access, s2s_certfile,
|
||||
[route_subdomains, s2s_access, s2s_certfile, s2s_zlib,
|
||||
s2s_ciphers, s2s_dhfile, s2s_cafile, s2s_protocol_options,
|
||||
s2s_tls_compression, s2s_use_starttls, s2s_timeout, s2s_queue_type].
|
||||
|
||||
+57
-24
@@ -21,13 +21,12 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_s2s_in).
|
||||
-behaviour(xmpp_stream_in).
|
||||
-behaviour(ejabberd_config).
|
||||
-behaviour(ejabberd_socket).
|
||||
|
||||
%% ejabberd_socket callbacks
|
||||
-export([start/2, start_link/2, socket_type/0]).
|
||||
%% ejabberd_config callbacks
|
||||
-export([opt_type/1]).
|
||||
%% ejabberd_listener callbacks
|
||||
-export([listen_opt_type/1]).
|
||||
%% xmpp_stream_in callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
@@ -42,7 +41,7 @@
|
||||
-export([handle_unexpected_info/2, handle_unexpected_cast/2,
|
||||
reject_unauthenticated_packet/2, process_closed/2]).
|
||||
%% API
|
||||
-export([stop/1, close/1, send/2, update_state/2, establish/1,
|
||||
-export([stop/1, close/1, close/2, send/2, update_state/2, establish/1,
|
||||
host_up/1, host_down/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -58,7 +57,10 @@
|
||||
start(SockData, Opts) ->
|
||||
case proplists:get_value(supervisor, Opts, true) of
|
||||
true ->
|
||||
supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts]);
|
||||
case supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts]) of
|
||||
{ok, undefined} -> ignore;
|
||||
Res -> Res
|
||||
end;
|
||||
_ ->
|
||||
xmpp_stream_in:start(?MODULE, [SockData, Opts],
|
||||
ejabberd_config:fsm_limit_opts(Opts))
|
||||
@@ -71,6 +73,9 @@ start_link(SockData, Opts) ->
|
||||
close(Ref) ->
|
||||
xmpp_stream_in:close(Ref).
|
||||
|
||||
close(Ref, Reason) ->
|
||||
xmpp_stream_in:close(Ref, Reason).
|
||||
|
||||
stop(Ref) ->
|
||||
xmpp_stream_in:stop(Ref).
|
||||
|
||||
@@ -162,7 +167,7 @@ handle_stream_start(_StreamStart, #{lserver := LServer} = State) ->
|
||||
case check_to(jid:make(LServer), State) of
|
||||
false ->
|
||||
send(State, xmpp:serr_host_unknown());
|
||||
true ->
|
||||
true ->
|
||||
ServerHost = ejabberd_router:host_of_route(LServer),
|
||||
State#{server_host => ServerHost}
|
||||
end.
|
||||
@@ -184,7 +189,7 @@ handle_auth_success(RServer, Mech, _AuthModule,
|
||||
[SockMod:pp(Socket), Mech, RServer, LServer,
|
||||
ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
|
||||
State1 = case ejabberd_s2s:allow_host(ServerHost, RServer) of
|
||||
true ->
|
||||
true ->
|
||||
AuthDomains1 = sets:add_element(RServer, AuthDomains),
|
||||
change_shaper(State, RServer),
|
||||
State#{auth_domains => AuthDomains1};
|
||||
@@ -242,25 +247,20 @@ handle_send(Pkt, Result, #{server_host := LServer} = State) ->
|
||||
State, [Pkt, Result]).
|
||||
|
||||
init([State, Opts]) ->
|
||||
Shaper = gen_mod:get_opt(shaper, Opts, fun acl:shaper_rules_validator/1, none),
|
||||
Shaper = proplists:get_value(shaper, Opts, none),
|
||||
TLSOpts1 = lists:filter(
|
||||
fun({certfile, _}) -> true;
|
||||
({ciphers, _}) -> true;
|
||||
({dhfile, _}) -> true;
|
||||
({cafile, _}) -> true;
|
||||
({protocol_options, _}) -> true;
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
TLSOpts2 = case lists:keyfind(protocol_options, 1, Opts) of
|
||||
false -> TLSOpts1;
|
||||
{_, OptString} ->
|
||||
ProtoOpts = str:join(OptString, <<$|>>),
|
||||
[{protocol_options, ProtoOpts}|TLSOpts1]
|
||||
end,
|
||||
TLSOpts3 = case proplists:get_bool(tls_compression, Opts) of
|
||||
false -> [compression_none | TLSOpts2];
|
||||
true -> TLSOpts2
|
||||
TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
|
||||
false -> [compression_none | TLSOpts1];
|
||||
true -> TLSOpts1
|
||||
end,
|
||||
State1 = State#{tls_options => TLSOpts3,
|
||||
State1 = State#{tls_options => TLSOpts2,
|
||||
auth_domains => sets:new(),
|
||||
xmlns => ?NS_SERVER,
|
||||
lang => ?MYLANG,
|
||||
@@ -313,13 +313,13 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
-spec check_from_to(jid(), jid(), state()) -> ok | {error, stream_error()}.
|
||||
check_from_to(From, To, State) ->
|
||||
case check_from(From, State) of
|
||||
true ->
|
||||
true ->
|
||||
case check_to(To, State) of
|
||||
true ->
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
false ->
|
||||
{error, xmpp:serr_host_unknown()}
|
||||
end;
|
||||
end;
|
||||
false ->
|
||||
{error, xmpp:serr_invalid_from()}
|
||||
end.
|
||||
@@ -346,5 +346,38 @@ change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
|
||||
Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)),
|
||||
xmpp_stream_in:change_shaper(State, Shaper).
|
||||
|
||||
opt_type(_) ->
|
||||
[].
|
||||
-spec listen_opt_type(shaper) -> fun((any()) -> any());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(cafile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(tls) -> fun((boolean()) -> boolean());
|
||||
(supervisor) -> fun((boolean()) -> boolean());
|
||||
(max_stanza_type) -> fun((timeout()) -> timeout());
|
||||
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
|
||||
listen_opt_type(certfile) ->
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(ciphers) -> ejabberd_s2s:opt_type(s2s_ciphers);
|
||||
listen_opt_type(dhfile) -> ejabberd_s2s:opt_type(s2s_dhfile);
|
||||
listen_opt_type(cafile) -> ejabberd_s2s:opt_type(s2s_cafile);
|
||||
listen_opt_type(protocol_options) -> ejabberd_s2s:opt_type(s2s_protocol_options);
|
||||
listen_opt_type(tls_compression) -> ejabberd_s2s:opt_type(s2s_tls_compression);
|
||||
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(max_stanza_size) ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
listen_opt_type(max_fsm_queue) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(_) ->
|
||||
[shaper, certfile, ciphers, dhfile, cafile, protocol_options,
|
||||
tls_compression, tls, max_fsm_queue].
|
||||
|
||||
+42
-49
@@ -39,7 +39,7 @@
|
||||
-export([process_auth_result/2, process_closed/2, handle_unexpected_info/2,
|
||||
handle_unexpected_cast/2, process_downgraded/2]).
|
||||
%% API
|
||||
-export([start/3, start_link/3, connect/1, close/1, stop/1, send/2,
|
||||
-export([start/3, start_link/3, connect/1, close/1, close/2, stop/1, send/2,
|
||||
route/2, establish/1, update_state/2, host_up/1, host_down/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
@@ -54,10 +54,13 @@
|
||||
%%%===================================================================
|
||||
start(From, To, Opts) ->
|
||||
case proplists:get_value(supervisor, Opts, true) of
|
||||
true ->
|
||||
supervisor:start_child(ejabberd_s2s_out_sup,
|
||||
[From, To, Opts]);
|
||||
_ ->
|
||||
true ->
|
||||
case supervisor:start_child(ejabberd_s2s_out_sup,
|
||||
[From, To, Opts]) of
|
||||
{ok, undefined} -> ignore;
|
||||
Res -> Res
|
||||
end;
|
||||
_ ->
|
||||
xmpp_stream_out:start(?MODULE, [ejabberd_socket, From, To, Opts],
|
||||
ejabberd_config:fsm_limit_opts([]))
|
||||
end.
|
||||
@@ -75,6 +78,11 @@ connect(Ref) ->
|
||||
close(Ref) ->
|
||||
xmpp_stream_out:close(Ref).
|
||||
|
||||
-spec close(pid(), atom()) -> ok;
|
||||
(state(), atom()) -> state().
|
||||
close(Ref, Reason) ->
|
||||
xmpp_stream_out:close(Ref, Reason).
|
||||
|
||||
-spec stop(pid()) -> ok;
|
||||
(state()) -> no_return().
|
||||
stop(Ref) ->
|
||||
@@ -186,42 +194,21 @@ tls_enabled(#{server := LServer}) ->
|
||||
connect_timeout(#{server := LServer}) ->
|
||||
ejabberd_config:get_option(
|
||||
{outgoing_s2s_timeout, LServer},
|
||||
fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
|
||||
timer:seconds(TimeOut);
|
||||
(infinity) ->
|
||||
infinity
|
||||
end, timer:seconds(10)).
|
||||
timer:seconds(10)).
|
||||
|
||||
default_port(#{server := LServer}) ->
|
||||
ejabberd_config:get_option(
|
||||
{outgoing_s2s_port, LServer},
|
||||
fun(I) when is_integer(I), I > 0, I =< 65536 -> I end,
|
||||
5269).
|
||||
ejabberd_config:get_option({outgoing_s2s_port, LServer}, 5269).
|
||||
|
||||
address_families(#{server := LServer}) ->
|
||||
ejabberd_config:get_option(
|
||||
{outgoing_s2s_families, LServer},
|
||||
fun(Families) ->
|
||||
lists:map(
|
||||
fun(ipv4) -> inet;
|
||||
(ipv6) -> inet6
|
||||
end, Families)
|
||||
end, [inet, inet6]).
|
||||
[inet, inet6]).
|
||||
|
||||
dns_retries(#{server := LServer}) ->
|
||||
ejabberd_config:get_option(
|
||||
{s2s_dns_retries, LServer},
|
||||
fun(I) when is_integer(I), I>=0 -> I end,
|
||||
2).
|
||||
ejabberd_config:get_option({s2s_dns_retries, LServer}, 2).
|
||||
|
||||
dns_timeout(#{server := LServer}) ->
|
||||
ejabberd_config:get_option(
|
||||
{s2s_dns_timeout, LServer},
|
||||
fun(I) when is_integer(I), I>=0 ->
|
||||
timer:seconds(I);
|
||||
(infinity) ->
|
||||
infinity
|
||||
end, timer:seconds(10)).
|
||||
ejabberd_config:get_option({s2s_dns_timeout, LServer}, timer:seconds(10)).
|
||||
|
||||
handle_auth_success(Mech, #{sockmod := SockMod,
|
||||
socket := Socket, ip := IP,
|
||||
@@ -386,11 +373,8 @@ mk_bounce_error(_Lang, _State) ->
|
||||
|
||||
-spec get_delay() -> non_neg_integer().
|
||||
get_delay() ->
|
||||
MaxDelay = ejabberd_config:get_option(
|
||||
s2s_max_retry_delay,
|
||||
fun(I) when is_integer(I), I > 0 -> I end,
|
||||
300),
|
||||
crypto:rand_uniform(1, MaxDelay).
|
||||
MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300),
|
||||
randoms:uniform(MaxDelay).
|
||||
|
||||
-spec set_idle_timeout(state()) -> state().
|
||||
set_idle_timeout(#{on_route := send, server := LServer} = State) ->
|
||||
@@ -458,27 +442,36 @@ maybe_report_huge_timeout(Opt, T) when is_integer(T), T >= 1000 ->
|
||||
maybe_report_huge_timeout(_, _) ->
|
||||
ok.
|
||||
|
||||
-spec opt_type(outgoing_s2s_families) -> fun(([ipv4|ipv6]) -> [inet|inet6]);
|
||||
(outgoing_s2s_port) -> fun((0..65535) -> 0..65535);
|
||||
(outgoing_s2s_timeout) -> fun((timeout()) -> timeout());
|
||||
(s2s_dns_retries) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(s2s_dns_timeout) -> fun((timeout()) -> timeout());
|
||||
(s2s_max_retry_delay) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(outgoing_s2s_families) ->
|
||||
fun (Families) ->
|
||||
true = lists:all(fun (ipv4) -> true;
|
||||
(ipv6) -> true
|
||||
end,
|
||||
Families),
|
||||
Families
|
||||
fun(Families) ->
|
||||
lists:map(
|
||||
fun(ipv4) -> inet;
|
||||
(ipv6) -> inet6
|
||||
end, Families)
|
||||
end;
|
||||
opt_type(outgoing_s2s_port) ->
|
||||
fun (I) when is_integer(I), I > 0, I =< 65536 -> I end;
|
||||
fun (I) when is_integer(I), I > 0, I < 65536 -> I end;
|
||||
opt_type(outgoing_s2s_timeout) ->
|
||||
fun (TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
|
||||
TimeOut;
|
||||
(infinity) -> infinity
|
||||
fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
|
||||
timer:seconds(TimeOut);
|
||||
(unlimited) ->
|
||||
infinity;
|
||||
(infinity) ->
|
||||
infinity
|
||||
end;
|
||||
opt_type(s2s_dns_retries) ->
|
||||
fun (I) when is_integer(I), I >= 0 -> I end;
|
||||
opt_type(s2s_dns_timeout) ->
|
||||
fun (TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
|
||||
TimeOut;
|
||||
(infinity) -> infinity
|
||||
fun(I) when is_integer(I), I>=0 -> timer:seconds(I);
|
||||
(infinity) -> infinity;
|
||||
(unlimited) -> infinity
|
||||
end;
|
||||
opt_type(s2s_max_retry_delay) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
|
||||
+84
-35
@@ -21,15 +21,14 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_service).
|
||||
-behaviour(xmpp_stream_in).
|
||||
-behaviour(ejabberd_config).
|
||||
-behaviour(ejabberd_socket).
|
||||
|
||||
-protocol({xep, 114, '1.6'}).
|
||||
|
||||
%% ejabberd_socket callbacks
|
||||
-export([start/2, start_link/2, socket_type/0]).
|
||||
%% ejabberd_config callbacks
|
||||
-export([opt_type/1, transform_listen_option/2]).
|
||||
-export([start/2, start_link/2, socket_type/0, close/1, close/2]).
|
||||
%% ejabberd_listener callbacks
|
||||
-export([listen_opt_type/1, transform_listen_option/2]).
|
||||
%% xmpp_stream_in callbacks
|
||||
-export([init/1, handle_info/2, terminate/2, code_change/3]).
|
||||
-export([handle_stream_start/2, handle_auth_success/4, handle_auth_failure/4,
|
||||
@@ -63,6 +62,16 @@ socket_type() ->
|
||||
send(Stream, Pkt) ->
|
||||
xmpp_stream_in:send(Stream, Pkt).
|
||||
|
||||
-spec close(pid()) -> ok;
|
||||
(state()) -> state().
|
||||
close(Ref) ->
|
||||
xmpp_stream_in:close(Ref).
|
||||
|
||||
-spec close(pid(), atom()) -> ok;
|
||||
(state(), atom()) -> state().
|
||||
close(Ref, Reason) ->
|
||||
xmpp_stream_in:close(Ref, Reason).
|
||||
|
||||
%%%===================================================================
|
||||
%%% xmpp_stream_in callbacks
|
||||
%%%===================================================================
|
||||
@@ -70,49 +79,33 @@ tls_options(#{tls_options := TLSOptions}) ->
|
||||
TLSOptions.
|
||||
|
||||
init([State, Opts]) ->
|
||||
Access = gen_mod:get_opt(access, Opts, fun acl:access_rules_validator/1, all),
|
||||
Shaper = gen_mod:get_opt(shaper_rule, Opts, fun acl:shaper_rules_validator/1, none),
|
||||
HostOpts = case lists:keyfind(hosts, 1, Opts) of
|
||||
{hosts, HOpts} ->
|
||||
lists:foldl(
|
||||
fun({H, Os}, D) ->
|
||||
P = proplists:get_value(
|
||||
password, Os,
|
||||
str:sha(randoms:bytes(20))),
|
||||
dict:store(H, P, D)
|
||||
end, dict:new(), HOpts);
|
||||
false ->
|
||||
Pass = proplists:get_value(
|
||||
password, Opts,
|
||||
str:sha(randoms:bytes(20))),
|
||||
dict:from_list([{global, Pass}])
|
||||
end,
|
||||
CheckFrom = gen_mod:get_opt(check_from, Opts,
|
||||
fun(Flag) when is_boolean(Flag) -> Flag end,
|
||||
true),
|
||||
Access = proplists:get_value(access, Opts, all),
|
||||
Shaper = proplists:get_value(shaper_rule, Opts, none),
|
||||
GlobalPassword = proplists:get_value(password, Opts, random_password()),
|
||||
HostOpts = proplists:get_value(hosts, Opts, [{global, GlobalPassword}]),
|
||||
HostOpts1 = lists:map(
|
||||
fun({Host, undefined}) -> {Host, GlobalPassword};
|
||||
({Host, Password}) -> {Host, Password}
|
||||
end, HostOpts),
|
||||
CheckFrom = proplists:get_value(check_from, Opts, true),
|
||||
TLSOpts1 = lists:filter(
|
||||
fun({certfile, _}) -> true;
|
||||
({ciphers, _}) -> true;
|
||||
({dhfile, _}) -> true;
|
||||
({cafile, _}) -> true;
|
||||
({protocol_options, _}) -> true;
|
||||
(_) -> false
|
||||
end, Opts),
|
||||
TLSOpts2 = case lists:keyfind(protocol_options, 1, Opts) of
|
||||
false -> TLSOpts1;
|
||||
{_, OptString} ->
|
||||
ProtoOpts = str:join(OptString, <<$|>>),
|
||||
[{protocol_options, ProtoOpts}|TLSOpts1]
|
||||
end,
|
||||
TLSOpts = case proplists:get_bool(tls_compression, Opts) of
|
||||
false -> [compression_none | TLSOpts2];
|
||||
true -> TLSOpts2
|
||||
false -> [compression_none | TLSOpts1];
|
||||
true -> TLSOpts1
|
||||
end,
|
||||
xmpp_stream_in:change_shaper(State, Shaper),
|
||||
State1 = State#{access => Access,
|
||||
xmlns => ?NS_COMPONENT,
|
||||
lang => ?MYLANG,
|
||||
server => ?MYNAME,
|
||||
host_opts => HostOpts,
|
||||
host_opts => dict:from_list(HostOpts1),
|
||||
stream_version => undefined,
|
||||
tls_options => TLSOpts,
|
||||
check_from => CheckFrom},
|
||||
@@ -206,7 +199,7 @@ handle_info({route, Packet}, #{access := Access} = State) ->
|
||||
xmpp_stream_in:send(State, Packet);
|
||||
deny ->
|
||||
Lang = xmpp:get_lang(Packet),
|
||||
Err = xmpp:err_not_allowed(<<"Denied by ACL">>, Lang),
|
||||
Err = xmpp:err_not_allowed(<<"Access denied by service policy">>, Lang),
|
||||
ejabberd_router:route_error(Packet, Err),
|
||||
State
|
||||
end;
|
||||
@@ -244,6 +237,9 @@ check_from(From, #{host_opts := HostOpts}) ->
|
||||
Server = From#jid.lserver,
|
||||
dict:is_key(Server, HostOpts).
|
||||
|
||||
random_password() ->
|
||||
str:sha(randoms:bytes(20)).
|
||||
|
||||
transform_listen_option({hosts, Hosts, O}, Opts) ->
|
||||
case lists:keyfind(hosts, 1, Opts) of
|
||||
{_, PrevHostOpts} ->
|
||||
@@ -262,4 +258,57 @@ transform_listen_option({host, Host, Os}, Opts) ->
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
opt_type(_) -> [].
|
||||
-spec listen_opt_type(access) -> fun((any()) -> any());
|
||||
(shaper_rule) -> fun((any()) -> any());
|
||||
(certfile) -> fun((binary()) -> binary());
|
||||
(ciphers) -> fun((binary()) -> binary());
|
||||
(dhfile) -> fun((binary()) -> binary());
|
||||
(cafile) -> fun((binary()) -> binary());
|
||||
(protocol_options) -> fun(([binary()]) -> binary());
|
||||
(tls_compression) -> fun((boolean()) -> boolean());
|
||||
(tls) -> fun((boolean()) -> boolean());
|
||||
(check_from) -> fun((boolean()) -> boolean());
|
||||
(password) -> fun((boolean()) -> boolean());
|
||||
(hosts) -> fun(([{binary(), [{password, binary()}]}]) ->
|
||||
[{binary(), binary() | undefined}]);
|
||||
(max_stanza_type) -> fun((timeout()) -> timeout());
|
||||
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
listen_opt_type(access) -> fun acl:access_rules_validator/1;
|
||||
listen_opt_type(shaper_rule) -> fun acl:shaper_rules_validator/1;
|
||||
listen_opt_type(certfile) ->
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(ciphers) -> fun iolist_to_binary/1;
|
||||
listen_opt_type(dhfile) -> fun misc:try_read_file/1;
|
||||
listen_opt_type(cafile) -> fun misc:try_read_file/1;
|
||||
listen_opt_type(protocol_options) ->
|
||||
fun(Options) -> str:join(Options, <<"|">>) end;
|
||||
listen_opt_type(tls_compression) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(check_from) -> fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(password) -> fun iolist_to_binary/1;
|
||||
listen_opt_type(hosts) ->
|
||||
fun(HostOpts) ->
|
||||
lists:map(
|
||||
fun({Host, Opts}) ->
|
||||
Password = case proplists:get_value(password, Opts) of
|
||||
undefined -> undefined;
|
||||
P -> iolist_to_binary(P)
|
||||
end,
|
||||
{iolist_to_binary(Host), Password}
|
||||
end, HostOpts)
|
||||
end;
|
||||
listen_opt_type(max_stanza_size) ->
|
||||
fun(I) when is_integer(I) -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
listen_opt_type(max_fsm_queue) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(_) ->
|
||||
[access, shaper_rule, certfile, ciphers, dhfile, cafile, tls,
|
||||
protocol_options, tls_compression, password, hosts, check_from,
|
||||
max_fsm_queue].
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 30 Apr 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2013-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_sip).
|
||||
|
||||
-ifndef(SIP).
|
||||
-include("logger.hrl").
|
||||
-export([socket_type/0, start/2, listen_opt_type/1]).
|
||||
log_error() ->
|
||||
?CRITICAL_MSG("ejabberd is not compiled with SIP support", []).
|
||||
socket_type() ->
|
||||
log_error(),
|
||||
raw.
|
||||
listen_opt_type(_) ->
|
||||
log_error(),
|
||||
[].
|
||||
start(_, _) ->
|
||||
log_error(),
|
||||
{error, sip_not_compiled}.
|
||||
-else.
|
||||
%% API
|
||||
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
|
||||
socket_type/0, listen_opt_type/1]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
tcp_init(Socket, Opts) ->
|
||||
ejabberd:start_app(esip),
|
||||
esip_socket:tcp_init(Socket, Opts).
|
||||
|
||||
udp_init(Socket, Opts) ->
|
||||
ejabberd:start_app(esip),
|
||||
esip_socket:udp_init(Socket, Opts).
|
||||
|
||||
udp_recv(Sock, Addr, Port, Data, Opts) ->
|
||||
esip_socket:udp_recv(Sock, Addr, Port, Data, Opts).
|
||||
|
||||
start(Opaque, Opts) ->
|
||||
esip_socket:start(Opaque, Opts).
|
||||
|
||||
socket_type() ->
|
||||
raw.
|
||||
|
||||
listen_opt_type(certfile) ->
|
||||
fun(S) ->
|
||||
ejabberd_pkix:add_certfile(S),
|
||||
iolist_to_binary(S)
|
||||
end;
|
||||
listen_opt_type(tls) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(_) ->
|
||||
[tls, certfile].
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
-endif.
|
||||
+247
-61
@@ -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,
|
||||
@@ -76,7 +78,9 @@
|
||||
c2s_handle_info/2,
|
||||
host_up/1,
|
||||
host_down/1,
|
||||
make_sid/0
|
||||
make_sid/0,
|
||||
clean_cache/1,
|
||||
config_reloaded/0
|
||||
]).
|
||||
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
@@ -91,13 +95,15 @@
|
||||
-include("ejabberd_sm.hrl").
|
||||
|
||||
-callback init() -> ok | {error, any()}.
|
||||
-callback set_session(#session{}) -> ok.
|
||||
-callback delete_session(binary(), binary(), binary(), sid()) ->
|
||||
{ok, #session{}} | {error, notfound}.
|
||||
-callback set_session(#session{}) -> ok | {error, any()}.
|
||||
-callback delete_session(#session{}) -> ok | {error, any()}.
|
||||
-callback get_sessions() -> [#session{}].
|
||||
-callback get_sessions(binary()) -> [#session{}].
|
||||
-callback get_sessions(binary(), binary()) -> [#session{}].
|
||||
-callback get_sessions(binary(), binary(), binary()) -> [#session{}].
|
||||
-callback get_sessions(binary(), binary()) -> {ok, [#session{}]} | {error, any()}.
|
||||
-callback use_cache(binary()) -> boolean().
|
||||
-callback cache_nodes(binary()) -> [node()].
|
||||
|
||||
-optional_callbacks([use_cache/1, cache_nodes/1]).
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
@@ -158,9 +164,13 @@ close_session(SID, User, Server, Resource) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
Info = case Mod:delete_session(LUser, LServer, LResource, SID) of
|
||||
{ok, #session{info = I}} -> I;
|
||||
{error, notfound} -> []
|
||||
Sessions = get_sessions(Mod, LUser, LServer, LResource),
|
||||
Info = case lists:keyfind(SID, #session.sid, Sessions) of
|
||||
#session{info = I} = Session ->
|
||||
delete_session(Mod, Session),
|
||||
I;
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
JID = jid:make(User, Server, Resource),
|
||||
ejabberd_hooks:run(sm_remove_connection_hook,
|
||||
@@ -170,7 +180,7 @@ close_session(SID, User, Server, Resource) ->
|
||||
subscribe | subscribed | unsubscribe | unsubscribed,
|
||||
binary()) -> boolean() | {stop, false}.
|
||||
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
|
||||
case ejabberd_auth:is_user_exists(User, Server) of
|
||||
case ejabberd_auth:user_exists(User, Server) of
|
||||
true -> Acc;
|
||||
false -> {stop, false}
|
||||
end.
|
||||
@@ -196,14 +206,14 @@ get_user_resources(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = online(Mod:get_sessions(LUser, LServer)),
|
||||
Ss = online(get_sessions(Mod, LUser, LServer)),
|
||||
[element(3, S#session.usr) || S <- clean_session_list(Ss)].
|
||||
|
||||
-spec get_user_present_resources(binary(), binary()) -> [tuple()].
|
||||
|
||||
get_user_present_resources(LUser, LServer) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = online(Mod:get_sessions(LUser, LServer)),
|
||||
Ss = online(get_sessions(Mod, LUser, LServer)),
|
||||
[{S#session.priority, element(3, S#session.usr)}
|
||||
|| S <- clean_session_list(Ss), is_integer(S#session.priority)].
|
||||
|
||||
@@ -214,7 +224,7 @@ get_user_ip(User, Server, Resource) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
case online(get_sessions(Mod, LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
undefined;
|
||||
Ss ->
|
||||
@@ -227,7 +237,7 @@ get_user_info(User, Server) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = online(Mod:get_sessions(LUser, LServer)),
|
||||
Ss = online(get_sessions(Mod, LUser, LServer)),
|
||||
[{LResource, [{node, node(Pid)}|Info]}
|
||||
|| #session{usr = {_, _, LResource},
|
||||
info = Info,
|
||||
@@ -240,7 +250,7 @@ get_user_info(User, Server, Resource) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
case online(get_sessions(Mod, LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
offline;
|
||||
Ss ->
|
||||
@@ -284,15 +294,32 @@ 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(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
[#session{sid = {_, Pid}}] -> Pid;
|
||||
case online(get_sessions(Mod, LUser, LServer, LResource)) of
|
||||
[#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),
|
||||
Sessions = online(get_sessions(Mod, LUser, LServer)),
|
||||
[SID || #session{sid = SID} <- Sessions].
|
||||
|
||||
-spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
|
||||
|
||||
set_offline_info(SID, User, Server, Resource, Info) ->
|
||||
@@ -309,11 +336,11 @@ get_offline_info(Time, User, Server, Resource) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case Mod:get_sessions(LUser, LServer, LResource) of
|
||||
case get_sessions(Mod, LUser, LServer, LResource) of
|
||||
[#session{sid = {Time, _}, info = Info}] ->
|
||||
case proplists:get_bool(offline, Info) of
|
||||
true ->
|
||||
Info;
|
||||
Info;
|
||||
false ->
|
||||
none
|
||||
end;
|
||||
@@ -326,7 +353,7 @@ get_offline_info(Time, User, Server, Resource) ->
|
||||
dirty_get_sessions_list() ->
|
||||
lists:flatmap(
|
||||
fun(Mod) ->
|
||||
[S#session.usr || S <- online(Mod:get_sessions())]
|
||||
[S#session.usr || S <- online(get_sessions(Mod))]
|
||||
end, get_sm_backends()).
|
||||
|
||||
-spec dirty_get_my_sessions_list() -> [#session{}].
|
||||
@@ -334,7 +361,7 @@ dirty_get_sessions_list() ->
|
||||
dirty_get_my_sessions_list() ->
|
||||
lists:flatmap(
|
||||
fun(Mod) ->
|
||||
[S || S <- online(Mod:get_sessions()),
|
||||
[S || S <- online(get_sessions(Mod)),
|
||||
node(element(2, S#session.sid)) == node()]
|
||||
end, get_sm_backends()).
|
||||
|
||||
@@ -343,14 +370,14 @@ dirty_get_my_sessions_list() ->
|
||||
get_vh_session_list(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = get_sm_backend(LServer),
|
||||
[S#session.usr || S <- online(Mod:get_sessions(LServer))].
|
||||
[S#session.usr || S <- online(get_sessions(Mod, LServer))].
|
||||
|
||||
-spec get_all_pids() -> [pid()].
|
||||
|
||||
get_all_pids() ->
|
||||
lists:flatmap(
|
||||
fun(Mod) ->
|
||||
[element(2, S#session.sid) || S <- online(Mod:get_sessions())]
|
||||
[element(2, S#session.sid) || S <- online(get_sessions(Mod))]
|
||||
end, get_sm_backends()).
|
||||
|
||||
-spec get_vh_session_number(binary()) -> non_neg_integer().
|
||||
@@ -358,7 +385,7 @@ get_all_pids() ->
|
||||
get_vh_session_number(Server) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
Mod = get_sm_backend(LServer),
|
||||
length(online(Mod:get_sessions(LServer))).
|
||||
length(online(get_sessions(Mod, LServer))).
|
||||
|
||||
-spec register_iq_handler(binary(), binary(), atom(), atom(), list()) -> ok.
|
||||
|
||||
@@ -387,16 +414,23 @@ c2s_handle_info(#{lang := Lang} = State, {exit, Reason}) ->
|
||||
c2s_handle_info(State, _) ->
|
||||
State.
|
||||
|
||||
-spec config_reloaded() -> ok.
|
||||
config_reloaded() ->
|
||||
init_cache().
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
init([]) ->
|
||||
process_flag(trap_exit, true),
|
||||
init_cache(),
|
||||
lists:foreach(fun(Mod) -> Mod:init() end, get_sm_backends()),
|
||||
clean_cache(),
|
||||
ets:new(sm_iqtable, [named_table, public, {read_concurrency, true}]),
|
||||
ejabberd_hooks:add(host_up, ?MODULE, host_up, 50),
|
||||
ejabberd_hooks:add(host_down, ?MODULE, host_down, 60),
|
||||
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
|
||||
lists:foreach(fun host_up/1, ?MYHOSTS),
|
||||
ejabberd_commands:register_commands(get_commands_spec()),
|
||||
{ok, #state{}}.
|
||||
@@ -432,6 +466,7 @@ terminate(_Reason, _State) ->
|
||||
lists:foreach(fun host_down/1, ?MYHOSTS),
|
||||
ejabberd_hooks:delete(host_up, ?MODULE, host_up, 50),
|
||||
ejabberd_hooks:delete(host_down, ?MODULE, host_down, 60),
|
||||
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 50),
|
||||
ejabberd_commands:unregister_commands(get_commands_spec()),
|
||||
ok.
|
||||
|
||||
@@ -455,12 +490,17 @@ 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
|
||||
end, Mod:get_sessions(Host)),
|
||||
end, get_sessions(Mod, Host)),
|
||||
ejabberd_hooks:delete(c2s_handle_info, Host,
|
||||
ejabberd_sm, c2s_handle_info, 50),
|
||||
ejabberd_hooks:delete(roster_in_subscription, Host,
|
||||
@@ -472,7 +512,7 @@ host_down(Host) ->
|
||||
ejabberd_c2s:host_down(Host).
|
||||
|
||||
-spec set_session(sid(), binary(), binary(), binary(),
|
||||
prio(), info()) -> ok.
|
||||
prio(), info()) -> ok | {error, any()}.
|
||||
|
||||
set_session(SID, User, Server, Resource, Priority, Info) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
@@ -481,8 +521,69 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
|
||||
US = {LUser, LServer},
|
||||
USR = {LUser, LServer, LResource},
|
||||
Mod = get_sm_backend(LServer),
|
||||
Mod:set_session(#session{sid = SID, usr = USR, us = US,
|
||||
priority = Priority, info = Info}).
|
||||
case Mod:set_session(#session{sid = SID, usr = USR, us = US,
|
||||
priority = Priority, info = Info}) of
|
||||
ok ->
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:delete(?SM_CACHE, {LUser, LServer},
|
||||
cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec get_sessions(module()) -> [#session{}].
|
||||
get_sessions(Mod) ->
|
||||
Mod:get_sessions().
|
||||
|
||||
-spec get_sessions(module(), binary()) -> [#session{}].
|
||||
get_sessions(Mod, LServer) ->
|
||||
Mod:get_sessions(LServer).
|
||||
|
||||
-spec get_sessions(module(), binary(), binary()) -> [#session{}].
|
||||
get_sessions(Mod, LUser, LServer) ->
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
case ets_cache:lookup(
|
||||
?SM_CACHE, {LUser, LServer},
|
||||
fun() ->
|
||||
case Mod:get_sessions(LUser, LServer) of
|
||||
{ok, Ss} when Ss /= [] ->
|
||||
{ok, Ss};
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end) of
|
||||
{ok, Sessions} ->
|
||||
Sessions;
|
||||
error ->
|
||||
[]
|
||||
end;
|
||||
false ->
|
||||
case Mod:get_sessions(LUser, LServer) of
|
||||
{ok, Ss} -> Ss;
|
||||
_ -> []
|
||||
end
|
||||
end.
|
||||
|
||||
-spec get_sessions(module(), binary(), binary(), binary()) -> [#session{}].
|
||||
get_sessions(Mod, LUser, LServer, LResource) ->
|
||||
Sessions = get_sessions(Mod, LUser, LServer),
|
||||
[S || S <- Sessions, element(3, S#session.usr) == LResource].
|
||||
|
||||
-spec delete_session(module(), #session{}) -> ok.
|
||||
delete_session(Mod, #session{usr = {LUser, LServer, _}} = Session) ->
|
||||
Mod:delete_session(Session),
|
||||
case use_cache(Mod, LServer) of
|
||||
true ->
|
||||
ets_cache:delete(?SM_CACHE, {LUser, LServer},
|
||||
cache_nodes(Mod, LServer));
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
-spec online([#session{}]) -> [#session{}].
|
||||
|
||||
@@ -505,7 +606,7 @@ do_route(To, Term) ->
|
||||
?DEBUG("broadcasting ~p to ~s", [Term, jid:encode(To)]),
|
||||
{U, S, R} = jid:tolower(To),
|
||||
Mod = get_sm_backend(S),
|
||||
case online(Mod:get_sessions(U, S, R)) of
|
||||
case online(get_sessions(Mod, U, S, R)) of
|
||||
[] ->
|
||||
?DEBUG("dropping broadcast to unavailable resourse: ~p", [Term]);
|
||||
Ss ->
|
||||
@@ -541,7 +642,7 @@ do_route(#presence{from = From, to = To, type = T, status = Status} = Packet)
|
||||
ejabberd_c2s:route(Pid, {route, Packet1});
|
||||
(_) ->
|
||||
ok
|
||||
end, online(Mod:get_sessions(LUser, LServer)));
|
||||
end, online(get_sessions(Mod, LUser, LServer)));
|
||||
false ->
|
||||
ok
|
||||
end;
|
||||
@@ -570,7 +671,7 @@ do_route(Packet) ->
|
||||
To = xmpp:get_to(Packet),
|
||||
{LUser, LServer, LResource} = jid:tolower(To),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(Mod:get_sessions(LUser, LServer, LResource)) of
|
||||
case online(get_sessions(Mod, LUser, LServer, LResource)) of
|
||||
[] ->
|
||||
case Packet of
|
||||
#message{type = T} when T == chat; T == normal ->
|
||||
@@ -618,7 +719,7 @@ route_message(#message{to = To, type = Type} = Packet) ->
|
||||
(P >= 0) and (Type == headline) ->
|
||||
LResource = jid:resourceprep(R),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(Mod:get_sessions(LUser, LServer,
|
||||
case online(get_sessions(Mod, LUser, LServer,
|
||||
LResource)) of
|
||||
[] ->
|
||||
ok; % Race condition
|
||||
@@ -638,7 +739,7 @@ route_message(#message{to = To, type = Type} = Packet) ->
|
||||
end,
|
||||
PrioRes);
|
||||
_ ->
|
||||
case ejabberd_auth:is_user_exists(LUser, LServer) andalso
|
||||
case ejabberd_auth:user_exists(LUser, LServer) andalso
|
||||
is_privacy_allow(Packet) of
|
||||
true ->
|
||||
ejabberd_hooks:run_fold(offline_message_hook,
|
||||
@@ -689,10 +790,10 @@ check_for_sessions_to_replace(User, Server, Resource) ->
|
||||
-spec check_existing_resources(binary(), binary(), binary()) -> ok.
|
||||
check_existing_resources(LUser, LServer, LResource) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = Mod:get_sessions(LUser, LServer, LResource),
|
||||
Ss = get_sessions(Mod, LUser, LServer, LResource),
|
||||
{OnlineSs, OfflineSs} = lists:partition(fun is_online/1, Ss),
|
||||
lists:foreach(fun(#session{sid = S}) ->
|
||||
Mod:delete_session(LUser, LServer, LResource, S)
|
||||
lists:foreach(fun(S) ->
|
||||
delete_session(Mod, S)
|
||||
end, OfflineSs),
|
||||
if OnlineSs == [] -> ok;
|
||||
true ->
|
||||
@@ -716,12 +817,12 @@ get_resource_sessions(User, Server, Resource) ->
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
[S#session.sid || S <- online(Mod:get_sessions(LUser, LServer, LResource))].
|
||||
[S#session.sid || S <- online(get_sessions(Mod, LUser, LServer, LResource))].
|
||||
|
||||
-spec check_max_sessions(binary(), binary()) -> ok | replaced.
|
||||
check_max_sessions(LUser, LServer) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = Mod:get_sessions(LUser, LServer),
|
||||
Ss = get_sessions(Mod, LUser, LServer),
|
||||
{OnlineSs, OfflineSs} = lists:partition(fun is_online/1, Ss),
|
||||
MaxSessions = get_max_user_sessions(LUser, LServer),
|
||||
if length(OnlineSs) =< MaxSessions -> ok;
|
||||
@@ -731,8 +832,7 @@ check_max_sessions(LUser, LServer) ->
|
||||
end,
|
||||
if length(OfflineSs) =< MaxSessions -> ok;
|
||||
true ->
|
||||
#session{sid = SID, usr = {_, _, R}} = lists:min(OfflineSs),
|
||||
Mod:delete_session(LUser, LServer, R, SID)
|
||||
delete_session(Mod, lists:min(OfflineSs))
|
||||
end.
|
||||
|
||||
%% Get the user_max_session setting
|
||||
@@ -779,23 +879,18 @@ process_iq(#iq{}) ->
|
||||
|
||||
force_update_presence({LUser, LServer}) ->
|
||||
Mod = get_sm_backend(LServer),
|
||||
Ss = online(Mod:get_sessions(LUser, LServer)),
|
||||
Ss = online(get_sessions(Mod, LUser, LServer)),
|
||||
lists:foreach(fun (#session{sid = {_, Pid}}) ->
|
||||
ejabberd_c2s:route(Pid, force_update_presence)
|
||||
ejabberd_c2s:resend_presence(Pid)
|
||||
end,
|
||||
Ss).
|
||||
|
||||
-spec get_sm_backend(binary()) -> module().
|
||||
|
||||
get_sm_backend(Host) ->
|
||||
DBType = case ejabberd_config:get_option(
|
||||
DBType = ejabberd_config:get_option(
|
||||
{sm_db_type, Host},
|
||||
fun(T) -> ejabberd_config:v_db(?MODULE, T) end) of
|
||||
undefined ->
|
||||
ejabberd_config:default_ram_db(Host, ?MODULE);
|
||||
T ->
|
||||
T
|
||||
end,
|
||||
ejabberd_config:default_ram_db(Host, ?MODULE)),
|
||||
list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
|
||||
|
||||
-spec get_sm_backends() -> [module()].
|
||||
@@ -811,34 +906,110 @@ get_vh_by_backend(Mod) ->
|
||||
get_sm_backend(Host) == Mod
|
||||
end, ?MYHOSTS).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%% Cache stuff
|
||||
%%--------------------------------------------------------------------
|
||||
-spec init_cache() -> ok.
|
||||
init_cache() ->
|
||||
case use_cache() of
|
||||
true ->
|
||||
ets_cache:new(?SM_CACHE, cache_opts());
|
||||
false ->
|
||||
ets_cache:delete(?SM_CACHE)
|
||||
end.
|
||||
|
||||
-spec cache_opts() -> [proplists:property()].
|
||||
cache_opts() ->
|
||||
MaxSize = ejabberd_config:get_option(
|
||||
sm_cache_size,
|
||||
ejabberd_config:cache_size(global)),
|
||||
CacheMissed = ejabberd_config:get_option(
|
||||
sm_cache_missed,
|
||||
ejabberd_config:cache_missed(global)),
|
||||
LifeTime = case ejabberd_config:get_option(
|
||||
sm_cache_life_time,
|
||||
ejabberd_config:cache_life_time(global)) of
|
||||
infinity -> infinity;
|
||||
I -> timer:seconds(I)
|
||||
end,
|
||||
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
||||
|
||||
-spec clean_cache(node()) -> ok.
|
||||
clean_cache(Node) ->
|
||||
ets_cache:filter(
|
||||
?SM_CACHE,
|
||||
fun(_, error) ->
|
||||
false;
|
||||
(_, {ok, Ss}) ->
|
||||
not lists:any(
|
||||
fun(#session{sid = {_, Pid}}) ->
|
||||
node(Pid) == Node
|
||||
end, Ss)
|
||||
end).
|
||||
|
||||
-spec clean_cache() -> ok.
|
||||
clean_cache() ->
|
||||
ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]).
|
||||
|
||||
-spec use_cache(module(), binary()) -> boolean().
|
||||
use_cache(Mod, LServer) ->
|
||||
case erlang:function_exported(Mod, use_cache, 1) of
|
||||
true -> Mod:use_cache(LServer);
|
||||
false ->
|
||||
ejabberd_config:get_option(
|
||||
{sm_use_cache, LServer},
|
||||
ejabberd_config:use_cache(LServer))
|
||||
end.
|
||||
|
||||
-spec use_cache() -> boolean().
|
||||
use_cache() ->
|
||||
lists:any(
|
||||
fun(Host) ->
|
||||
Mod = get_sm_backend(Host),
|
||||
use_cache(Mod, Host)
|
||||
end, ?MYHOSTS).
|
||||
|
||||
-spec cache_nodes(module(), binary()) -> [node()].
|
||||
cache_nodes(Mod, LServer) ->
|
||||
case erlang:function_exported(Mod, cache_nodes, 1) of
|
||||
true -> Mod:cache_nodes(LServer);
|
||||
false -> ejabberd_cluster:get_nodes()
|
||||
end.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%% 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()].
|
||||
@@ -868,5 +1039,20 @@ kick_user(User, Server) ->
|
||||
make_sid() ->
|
||||
{p1_time_compat:unique_timestamp(), self()}.
|
||||
|
||||
-spec opt_type(sm_db_type) -> fun((atom()) -> atom());
|
||||
(sm_use_cache) -> fun((boolean()) -> boolean());
|
||||
(sm_cache_missed) -> fun((boolean()) -> boolean());
|
||||
(sm_cache_size) -> fun((timeout()) -> timeout());
|
||||
(sm_cache_life_time) -> fun((timeout()) -> timeout());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(sm_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
|
||||
opt_type(_) -> [sm_db_type].
|
||||
opt_type(O) when O == sm_use_cache; O == sm_cache_missed ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
opt_type(O) when O == sm_cache_size; O == sm_cache_life_time ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
opt_type(_) ->
|
||||
[sm_db_type, sm_use_cache, sm_cache_size, sm_cache_missed,
|
||||
sm_cache_life_time].
|
||||
|
||||
+12
-19
@@ -29,12 +29,12 @@
|
||||
|
||||
%% API
|
||||
-export([init/0,
|
||||
use_cache/1,
|
||||
set_session/1,
|
||||
delete_session/4,
|
||||
delete_session/1,
|
||||
get_sessions/0,
|
||||
get_sessions/1,
|
||||
get_sessions/2,
|
||||
get_sessions/3]).
|
||||
get_sessions/2]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
@@ -62,20 +62,17 @@ init() ->
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
-spec use_cache(binary()) -> boolean().
|
||||
use_cache(_LServer) ->
|
||||
false.
|
||||
|
||||
-spec set_session(#session{}) -> ok.
|
||||
set_session(Session) ->
|
||||
mnesia:dirty_write(Session).
|
||||
|
||||
-spec delete_session(binary(), binary(), binary(), sid()) ->
|
||||
{ok, #session{}} | {error, notfound}.
|
||||
delete_session(_LUser, _LServer, _LResource, SID) ->
|
||||
case mnesia:dirty_read(session, SID) of
|
||||
[Session] ->
|
||||
mnesia:dirty_delete(session, SID),
|
||||
{ok, Session};
|
||||
[] ->
|
||||
{error, notfound}
|
||||
end.
|
||||
-spec delete_session(#session{}) -> ok.
|
||||
delete_session(#session{sid = SID}) ->
|
||||
mnesia:dirty_delete(session, SID).
|
||||
|
||||
-spec get_sessions() -> [#session{}].
|
||||
get_sessions() ->
|
||||
@@ -87,13 +84,9 @@ get_sessions(LServer) ->
|
||||
[{#session{usr = '$1', _ = '_'},
|
||||
[{'==', {element, 2, '$1'}, LServer}], ['$_']}]).
|
||||
|
||||
-spec get_sessions(binary(), binary()) -> [#session{}].
|
||||
-spec get_sessions(binary(), binary()) -> {ok, [#session{}]}.
|
||||
get_sessions(LUser, LServer) ->
|
||||
mnesia:dirty_index_read(session, {LUser, LServer}, #session.us).
|
||||
|
||||
-spec get_sessions(binary(), binary(), binary()) -> [#session{}].
|
||||
get_sessions(LUser, LServer, LResource) ->
|
||||
mnesia:dirty_index_read(session, {LUser, LServer, LResource}, #session.usr).
|
||||
{ok, mnesia:dirty_index_read(session, {LUser, LServer}, #session.us)}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
|
||||
+95
-68
@@ -23,63 +23,85 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_sm_redis).
|
||||
|
||||
-behaviour(ejabberd_config).
|
||||
-ifndef(GEN_SERVER).
|
||||
-define(GEN_SERVER, p1_server).
|
||||
-endif.
|
||||
-behaviour(?GEN_SERVER).
|
||||
|
||||
-behaviour(ejabberd_sm).
|
||||
|
||||
-export([init/0, set_session/1, delete_session/4,
|
||||
-export([init/0, set_session/1, delete_session/1,
|
||||
get_sessions/0, get_sessions/1, get_sessions/2,
|
||||
get_sessions/3, opt_type/1]).
|
||||
cache_nodes/1]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_cast/2, handle_call/3, handle_info/2,
|
||||
terminate/2, code_change/3, start_link/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_sm.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-define(SM_KEY, <<"ejabberd:sm">>).
|
||||
-record(state, {}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
-spec init() -> ok | {error, any()}.
|
||||
init() ->
|
||||
clean_table().
|
||||
Spec = {?MODULE, {?MODULE, start_link, []},
|
||||
transient, 5000, worker, [?MODULE]},
|
||||
case supervisor:start_child(ejabberd_backend_sup, Spec) of
|
||||
{ok, _Pid} -> ok;
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
-spec set_session(#session{}) -> ok.
|
||||
-spec start_link() -> {ok, pid()} | {error, any()}.
|
||||
start_link() ->
|
||||
?GEN_SERVER:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
-spec cache_nodes(binary()) -> [node()].
|
||||
cache_nodes(_LServer) ->
|
||||
[node()].
|
||||
|
||||
-spec set_session(#session{}) -> ok | {error, ejabberd_redis:error_reason()}.
|
||||
set_session(Session) ->
|
||||
T = term_to_binary(Session),
|
||||
USKey = us_to_key(Session#session.us),
|
||||
SIDKey = sid_to_key(Session#session.sid),
|
||||
ServKey = server_to_key(element(2, Session#session.us)),
|
||||
USSIDKey = us_sid_to_key(Session#session.us, Session#session.sid),
|
||||
ejabberd_redis:multi(
|
||||
fun() ->
|
||||
ejabberd_redis:hset(USKey, SIDKey, T),
|
||||
ejabberd_redis:hset(ServKey, USSIDKey, T)
|
||||
end),
|
||||
ok.
|
||||
case ejabberd_redis:multi(
|
||||
fun() ->
|
||||
ejabberd_redis:hset(USKey, SIDKey, T),
|
||||
ejabberd_redis:hset(ServKey, USSIDKey, T),
|
||||
ejabberd_redis:publish(
|
||||
?SM_KEY, term_to_binary({delete, Session#session.us}))
|
||||
end) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec delete_session(binary(), binary(), binary(), sid()) ->
|
||||
{ok, #session{}} | {error, notfound}.
|
||||
delete_session(LUser, LServer, _LResource, SID) ->
|
||||
USKey = us_to_key({LUser, LServer}),
|
||||
case ejabberd_redis:hgetall(USKey) of
|
||||
{ok, Vals} ->
|
||||
Ss = decode_session_list(Vals),
|
||||
case lists:keyfind(SID, #session.sid, Ss) of
|
||||
false ->
|
||||
{error, notfound};
|
||||
Session ->
|
||||
SIDKey = sid_to_key(SID),
|
||||
ServKey = server_to_key(element(2, Session#session.us)),
|
||||
USSIDKey = us_sid_to_key(Session#session.us, SID),
|
||||
ejabberd_redis:multi(
|
||||
fun() ->
|
||||
ejabberd_redis:hdel(USKey, [SIDKey]),
|
||||
ejabberd_redis:hdel(ServKey, [USSIDKey])
|
||||
end),
|
||||
{ok, Session}
|
||||
end;
|
||||
{error, _} ->
|
||||
{error, notfound}
|
||||
-spec delete_session(#session{}) -> ok | {error, ejabberd_redis:error_reason()}.
|
||||
delete_session(#session{sid = SID} = Session) ->
|
||||
USKey = us_to_key(Session#session.us),
|
||||
SIDKey = sid_to_key(SID),
|
||||
ServKey = server_to_key(element(2, Session#session.us)),
|
||||
USSIDKey = us_sid_to_key(Session#session.us, SID),
|
||||
case ejabberd_redis:multi(
|
||||
fun() ->
|
||||
ejabberd_redis:hdel(USKey, [SIDKey]),
|
||||
ejabberd_redis:hdel(ServKey, [USSIDKey]),
|
||||
ejabberd_redis:publish(
|
||||
?SM_KEY,
|
||||
term_to_binary({delete, Session#session.us}))
|
||||
end) of
|
||||
{ok, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec get_sessions() -> [#session{}].
|
||||
@@ -99,34 +121,53 @@ get_sessions(LServer) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec get_sessions(binary(), binary()) -> [#session{}].
|
||||
-spec get_sessions(binary(), binary()) -> {ok, [#session{}]} |
|
||||
{error, ejabberd_redis:error_reason()}.
|
||||
get_sessions(LUser, LServer) ->
|
||||
USKey = us_to_key({LUser, LServer}),
|
||||
case ejabberd_redis:hgetall(USKey) of
|
||||
{ok, Vals} ->
|
||||
decode_session_list(Vals);
|
||||
{error, _} ->
|
||||
[]
|
||||
{ok, decode_session_list(Vals)};
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec get_sessions(binary(), binary(), binary()) ->
|
||||
[#session{}].
|
||||
get_sessions(LUser, LServer, LResource) ->
|
||||
USKey = us_to_key({LUser, LServer}),
|
||||
case ejabberd_redis:hgetall(USKey) of
|
||||
{ok, Vals} ->
|
||||
[S || S <- decode_session_list(Vals),
|
||||
element(3, S#session.usr) == LResource];
|
||||
{error, _} ->
|
||||
[]
|
||||
end.
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
ejabberd_redis:subscribe([?SM_KEY]),
|
||||
clean_table(),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({redis_message, ?SM_KEY, Data}, State) ->
|
||||
case binary_to_term(Data) of
|
||||
{delete, Key} ->
|
||||
ets_cache:delete(?SM_CACHE, Key);
|
||||
Msg ->
|
||||
?WARNING_MSG("unexpected redis message: ~p", [Msg])
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info(Info, State) ->
|
||||
?ERROR_MSG("unexpected info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
iolist_to_list(IOList) ->
|
||||
binary_to_list(iolist_to_binary(IOList)).
|
||||
|
||||
us_to_key({LUser, LServer}) ->
|
||||
<<"ejabberd:sm:", LUser/binary, "@", LServer/binary>>.
|
||||
|
||||
@@ -143,7 +184,7 @@ decode_session_list(Vals) ->
|
||||
[binary_to_term(Val) || {_, Val} <- Vals].
|
||||
|
||||
clean_table() ->
|
||||
?INFO_MSG("Cleaning Redis SM table...", []),
|
||||
?DEBUG("Cleaning Redis SM table...", []),
|
||||
try
|
||||
lists:foreach(
|
||||
fun(LServer) ->
|
||||
@@ -169,17 +210,3 @@ clean_table() ->
|
||||
catch _:{badmatch, {error, _}} ->
|
||||
?ERROR_MSG("failed to clean redis c2s sessions", [])
|
||||
end.
|
||||
|
||||
opt_type(redis_connect_timeout) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(redis_db) ->
|
||||
fun (I) when is_integer(I), I >= 0 -> I end;
|
||||
opt_type(redis_password) -> fun iolist_to_list/1;
|
||||
opt_type(redis_port) ->
|
||||
fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
|
||||
opt_type(redis_reconnect_timeout) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(redis_server) -> fun iolist_to_list/1;
|
||||
opt_type(_) ->
|
||||
[redis_connect_timeout, redis_db, redis_password,
|
||||
redis_port, redis_reconnect_timeout, redis_server].
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Created : 15 Apr 2017 by Evgeny Khramtsov <ekhramtsov@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_sm_riak).
|
||||
-behaviour(ejabberd_sm).
|
||||
|
||||
%% API
|
||||
-export([init/0, set_session/1, delete_session/1, get_sessions/0,
|
||||
get_sessions/1, get_sessions/2]).
|
||||
|
||||
-include("ejabberd_sm.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
init() ->
|
||||
clean_table().
|
||||
|
||||
set_session(Session) ->
|
||||
ejabberd_riak:put(Session, session_schema(),
|
||||
[{'2i', [{<<"us">>, Session#session.us}]}]).
|
||||
|
||||
delete_session(Session) ->
|
||||
ejabberd_riak:delete(session, Session#session.sid).
|
||||
|
||||
get_sessions() ->
|
||||
case ejabberd_riak:get(session, session_schema()) of
|
||||
{ok, Ss} -> Ss;
|
||||
{error, _} -> []
|
||||
end.
|
||||
|
||||
get_sessions(LServer) ->
|
||||
[S || S <- get_sessions(), element(2, S#session.us) == LServer].
|
||||
|
||||
get_sessions(U, S) ->
|
||||
ejabberd_riak:get_by_index(session, session_schema(), <<"us">>, {U, S}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
session_schema() ->
|
||||
{record_info(fields, session), #session{}}.
|
||||
|
||||
clean_table() ->
|
||||
%% TODO: not very efficient, rewrite using map-reduce or something
|
||||
?DEBUG("Cleaning Riak 'sm' table...", []),
|
||||
lists:foreach(
|
||||
fun(#session{sid = {_, Pid} = SID}) when node(Pid) == node() ->
|
||||
ejabberd_riak:delete(session, SID);
|
||||
(_) ->
|
||||
ok
|
||||
end, get_sessions()).
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user