Compare commits
308 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1820b4f63b | |||
| 63aabed320 | |||
| fd7bf7fed3 | |||
| 7a90cda8ff | |||
| 35eeaa5869 | |||
| 024713a441 | |||
| 32fbfe1981 | |||
| 9c5427e0c2 | |||
| 8f5a1c4a2a | |||
| 7d3609d954 | |||
| fc7ba53c37 | |||
| a96d72330d | |||
| 7d626b4f5c | |||
| e903348dd3 | |||
| bee251d928 | |||
| c658907331 | |||
| 3fec782494 | |||
| 52525eb76d | |||
| 8679cfd2f3 | |||
| 25af3fb029 | |||
| 2bceebc39d | |||
| 92532a0d66 | |||
| b673539a2a | |||
| e1aaa1c99d | |||
| aa9eb001d0 | |||
| 101e808124 | |||
| 766b7c65a6 | |||
| 0516a3dc0e | |||
| 3d185c0fb8 | |||
| 7815164216 | |||
| 65f4094804 | |||
| 06450f4a82 | |||
| ce0beb550c | |||
| 96c0483533 | |||
| 1274bcdba9 | |||
| 5dcc97c85a | |||
| 6deceeec2e | |||
| 9edcbadd60 | |||
| f65492e27f | |||
| 1df61a82c8 | |||
| 73509686f1 | |||
| 93e521d65e | |||
| 50511fcff7 | |||
| 5e26190b98 | |||
| f22bd6eb46 | |||
| 0ba6c78ed0 | |||
| 636d68e0a9 | |||
| 7e6d1c24c2 | |||
| 67918b17d3 | |||
| 51fa438340 | |||
| 35a11526f9 | |||
| 0cfec92c14 | |||
| e5f64bc24a | |||
| 5c48ba4609 | |||
| 3ca62a797a | |||
| b66dab1313 | |||
| 58110e4bc1 | |||
| ea96615460 | |||
| b8c26671c4 | |||
| 2e88d001d6 | |||
| 1a0db3de3a | |||
| 250876ea1a | |||
| 66510c1d78 | |||
| d6f1d3df5b | |||
| 72dbb6e7c1 | |||
| b1082a96c9 | |||
| ed17586cf0 | |||
| 26b8dd75f7 | |||
| c3473c2077 | |||
| e216654c52 | |||
| cc3391cc1c | |||
| b8d56a7c11 | |||
| 1bb9e83501 | |||
| de1a66dfbe | |||
| 95613a11ab | |||
| 2cd193f97c | |||
| 33a9d6a3c3 | |||
| fdb863ce70 | |||
| 43fc29873e | |||
| e216a54ead | |||
| d4cdc3a246 | |||
| f6bdc6fdb2 | |||
| 8f25baada6 | |||
| aaef1a14b4 | |||
| 22e8f5fd51 | |||
| 88ab787ba6 | |||
| eb9faffadd | |||
| 3b0eee785f | |||
| 5ef542a638 | |||
| ffdaff3740 | |||
| 56d273477e | |||
| 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 |
+2
-1
@@ -21,7 +21,8 @@ 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
|
||||
|
||||
|
||||
+2
-1
@@ -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 \
|
||||
@@ -68,6 +68,7 @@ RUN set -x \
|
||||
&& chmod +x ./autogen.sh \
|
||||
&& ./autogen.sh \
|
||||
&& ./configure --enable-user=$EJABBERD_USER \
|
||||
--prefix=/ \
|
||||
--enable-all \
|
||||
--disable-tools \
|
||||
--disable-pam \
|
||||
|
||||
+2
-2
@@ -125,11 +125,11 @@ ifeq ($(MAKECMDGOALS),copy-files-sub)
|
||||
|
||||
DEPS:=$(sort $(shell $(REBAR) list-deps|$(SED) -e '/^=/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
|
||||
|
||||
+32
-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,30 @@ 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_CONFIG_FILES([Makefile
|
||||
vars.config
|
||||
src/ejabberd.app.src])
|
||||
@@ -253,9 +277,12 @@ AC_SUBST(riak)
|
||||
AC_SUBST(redis)
|
||||
AC_SUBST(elixir)
|
||||
AC_SUBST(iconv)
|
||||
AC_SUBST(stun)
|
||||
AC_SUBST(sip)
|
||||
AC_SUBST(debug)
|
||||
AC_SUBST(tools)
|
||||
AC_SUBST(latest_deps)
|
||||
AC_SUBST(system_deps)
|
||||
AC_SUBST(CFLAGS)
|
||||
AC_SUBST(CPPFLAGS)
|
||||
AC_SUBST(LDFLAGS)
|
||||
|
||||
+1
-1
@@ -68,7 +68,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
|
||||
|
||||
+11
-7
@@ -158,11 +158,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
|
||||
##
|
||||
@@ -265,12 +265,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 +487,8 @@ acl:
|
||||
loopback:
|
||||
ip:
|
||||
- "127.0.0.0/8"
|
||||
- "::1/128"
|
||||
- "::FFFF:127.0.0.1/128"
|
||||
|
||||
##
|
||||
## Bad XMPP servers
|
||||
@@ -589,14 +591,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 +725,8 @@ modules:
|
||||
- "flat"
|
||||
- "hometree"
|
||||
- "pep" # pep requires mod_caps
|
||||
mod_push: {}
|
||||
mod_push_keepalive: {}
|
||||
## mod_register:
|
||||
##
|
||||
## Protect In-Band account registrations with CAPTCHA.
|
||||
|
||||
+152
-282
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
# define default configuration
|
||||
POLL=true
|
||||
@@ -10,135 +10,89 @@ FIREWALL_WINDOW=""
|
||||
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 $1 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"
|
||||
[ "$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#*-}"
|
||||
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}"
|
||||
INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
|
||||
if [ "$INET_DIST_INTERFACE2" != "" ] ; then
|
||||
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2"
|
||||
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 +104,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
|
||||
@@ -286,7 +150,6 @@ debugwarning()
|
||||
|
||||
livewarning()
|
||||
{
|
||||
check_start
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo ""
|
||||
@@ -308,32 +171,6 @@ livewarning()
|
||||
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,28 +192,11 @@ 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" -a -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}
|
||||
@@ -391,6 +211,7 @@ 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%@*} " && {
|
||||
@@ -411,7 +232,7 @@ check_start()
|
||||
}
|
||||
|
||||
# allow sync calls
|
||||
wait_for_status()
|
||||
wait_status()
|
||||
{
|
||||
# args: status try delay
|
||||
# return: 0 OK, 1 KO
|
||||
@@ -420,27 +241,76 @@ wait_for_status()
|
||||
while [ $status -ne $1 ] ; do
|
||||
sleep $3
|
||||
timeout=`expr $timeout - 1`
|
||||
[ $timeout -eq 0 ] && {
|
||||
if [ $timeout -eq 0 ] ; then
|
||||
status=$1
|
||||
} || {
|
||||
ctl status > /dev/null
|
||||
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'
|
||||
|
||||
@@ -3,18 +3,18 @@ defmodule Ejabberd.Mixfile do
|
||||
|
||||
def project do
|
||||
[app: :ejabberd,
|
||||
version: "17.03.0",
|
||||
description: description,
|
||||
elixir: "~> 1.3",
|
||||
version: "17.8.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>>.
|
||||
+26
-22
@@ -18,39 +18,40 @@
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
{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.9"}}},
|
||||
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.10"}}},
|
||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.15"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
|
||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
|
||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.14"}}},
|
||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.10"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.2"}}},
|
||||
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
|
||||
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.14"}}}},
|
||||
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.15"}}}},
|
||||
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
|
||||
{tag, "1.0.2"}}}},
|
||||
{tag, "1.0.3"}}}},
|
||||
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
|
||||
{tag, "1.1.2"}}}},
|
||||
{tag, "1.1.3"}}}},
|
||||
{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"}}}},
|
||||
%% 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.5"}}}},
|
||||
{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 +65,18 @@
|
||||
stringprep,
|
||||
fast_xml,
|
||||
esip,
|
||||
luerl,
|
||||
stun,
|
||||
fast_yaml,
|
||||
xmpp,
|
||||
p1_utils,
|
||||
p1_mysql,
|
||||
p1_pgsql,
|
||||
p1_oauth2,
|
||||
epam,
|
||||
ezlib,
|
||||
iconv]}}.
|
||||
|
||||
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl"]}.
|
||||
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl", "src/mod_push.erl"]}.
|
||||
|
||||
{erl_opts, [nowarn_deprecated_function,
|
||||
{i, "include"},
|
||||
@@ -84,12 +85,15 @@
|
||||
{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, 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, {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 +103,7 @@
|
||||
|
||||
{if_rebar3, {plugins, [rebar3_hex, {provider_asn1, "0.2.0"}]}}.
|
||||
{if_not_rebar3, {plugins, [
|
||||
deps_erl_opts,
|
||||
deps_erl_opts, override_deps_versions,
|
||||
{if_var_true, elixir, rebar_elixir_compiler},
|
||||
{if_var_true, elixir, rebar_exunit}
|
||||
]}}.
|
||||
@@ -145,7 +149,7 @@
|
||||
{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", []}},
|
||||
|
||||
+15
-3
@@ -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) ->
|
||||
@@ -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;
|
||||
|
||||
@@ -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()).
|
||||
|
||||
+5
-52
@@ -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,17 +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].
|
||||
|
||||
setup_if_elixir_conf_used() ->
|
||||
case ejabberd_config:is_using_elixir_config() of
|
||||
true -> 'Elixir.Ejabberd.Config.Store':start_link();
|
||||
|
||||
+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) ->
|
||||
|
||||
+270
-195
@@ -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(
|
||||
{c2s_certfile, LServer},
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, LServer})) of
|
||||
undefined -> DefaultOpts;
|
||||
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
|
||||
{certfile, CertFile})
|
||||
end
|
||||
end,
|
||||
TLSOpts2 = case ejabberd_config:get_option(
|
||||
{c2s_ciphers, LServer},
|
||||
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,15 +402,11 @@ 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",
|
||||
@@ -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,9 +654,7 @@ 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 ->
|
||||
@@ -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
|
||||
|
||||
+226
-252
@@ -27,33 +27,36 @@
|
||||
-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_option/1, get_option/2, 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_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]).
|
||||
|
||||
-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}]).
|
||||
|
||||
-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 +68,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 +104,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 +179,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 +311,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 +757,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 +782,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 +821,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 +960,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 +974,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 +1030,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 +1068,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 +1092,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 +1213,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 +1321,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 +1348,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 +1361,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 +1402,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 +1443,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 +1451,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};
|
||||
|
||||
+378
-96
@@ -30,130 +30,279 @@
|
||||
|
||||
-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),
|
||||
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 +313,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 +380,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 +439,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.
|
||||
|
||||
+87
-67
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.
|
||||
|
||||
%%%===================================================================
|
||||
|
||||
+62
-74
@@ -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,
|
||||
@@ -200,45 +200,37 @@ dirty_get_connections() ->
|
||||
tls_options(LServer, DefaultOpts) ->
|
||||
TLSOpts1 = case ejabberd_config:get_option(
|
||||
{s2s_certfile, LServer},
|
||||
fun iolist_to_binary/1,
|
||||
ejabberd_config:get_option(
|
||||
{domain_certfile, LServer},
|
||||
fun iolist_to_binary/1)) of
|
||||
{domain_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,
|
||||
@@ -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) ->
|
||||
@@ -743,13 +728,16 @@ opt_type(s2s_use_starttls) ->
|
||||
(required) -> required;
|
||||
(required_trusted) -> 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].
|
||||
|
||||
+41
-48
@@ -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,10 +373,7 @@ 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),
|
||||
MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300),
|
||||
crypto:rand_uniform(1, MaxDelay).
|
||||
|
||||
-spec set_idle_timeout(state()) -> 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;
|
||||
|
||||
+83
-34
@@ -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},
|
||||
@@ -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.
|
||||
+246
-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,31 @@ close_session_unset_presence(SID, User, Server,
|
||||
-spec get_session_pid(binary(), binary(), binary()) -> none | pid().
|
||||
|
||||
get_session_pid(User, Server, Resource) ->
|
||||
case get_session_sid(User, Server, Resource) of
|
||||
{_, PID} -> PID;
|
||||
none -> none
|
||||
end.
|
||||
|
||||
-spec get_session_sid(binary(), binary(), binary()) -> none | sid().
|
||||
|
||||
get_session_sid(User, Server, Resource) ->
|
||||
LUser = jid:nodeprep(User),
|
||||
LServer = jid:nameprep(Server),
|
||||
LResource = jid:resourceprep(Resource),
|
||||
Mod = get_sm_backend(LServer),
|
||||
case online(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),
|
||||
online(get_sessions(Mod, LUser, LServer)).
|
||||
|
||||
-spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
|
||||
|
||||
set_offline_info(SID, User, Server, Resource, Info) ->
|
||||
@@ -309,11 +335,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 +352,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 +360,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 +369,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 +384,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 +413,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 +465,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 +489,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 +511,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 +520,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 +605,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 +641,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 +670,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 +718,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 +738,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 +789,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 +816,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 +831,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 +878,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 +905,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 +1038,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()).
|
||||
+18
-46
@@ -31,11 +31,10 @@
|
||||
%% API
|
||||
-export([init/0,
|
||||
set_session/1,
|
||||
delete_session/4,
|
||||
delete_session/1,
|
||||
get_sessions/0,
|
||||
get_sessions/1,
|
||||
get_sessions/2,
|
||||
get_sessions/3]).
|
||||
get_sessions/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("ejabberd_sm.hrl").
|
||||
@@ -48,7 +47,7 @@
|
||||
-spec init() -> ok | {error, any()}.
|
||||
init() ->
|
||||
Node = erlang:atom_to_binary(node(), latin1),
|
||||
?INFO_MSG("Cleaning SQL SM table...", []),
|
||||
?DEBUG("Cleaning SQL SM table...", []),
|
||||
lists:foldl(
|
||||
fun(Host, ok) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
@@ -81,30 +80,21 @@ set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
|
||||
ok ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to update 'sm' table: ~p", [Err])
|
||||
?ERROR_MSG("failed to update 'sm' table: ~p", [Err]),
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
delete_session(_LUser, LServer, _LResource, {Now, Pid}) ->
|
||||
delete_session(#session{usr = {_, LServer, _}, sid = {Now, Pid}}) ->
|
||||
TS = now_to_timestamp(Now),
|
||||
PidS = list_to_binary(erlang:pid_to_list(Pid)),
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(usec)d, @(pid)s, @(node)s, @(username)s,"
|
||||
" @(resource)s, @(priority)s, @(info)s "
|
||||
"from sm where usec=%(TS)d and pid=%(PidS)s")) of
|
||||
{selected, [Row]} ->
|
||||
ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("delete from sm"
|
||||
" where usec=%(TS)d and pid=%(PidS)s")),
|
||||
try {ok, row_to_session(LServer, Row)}
|
||||
catch _:{bad_node, _} -> {error, notfound}
|
||||
end;
|
||||
{selected, []} ->
|
||||
{error, notfound};
|
||||
?SQL("delete from sm where usec=%(TS)d and pid=%(PidS)s")) of
|
||||
{updated, _} ->
|
||||
ok;
|
||||
Err ->
|
||||
?ERROR_MSG("failed to delete from 'sm' table: ~p", [Err]),
|
||||
{error, notfound}
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
get_sessions() ->
|
||||
@@ -137,33 +127,15 @@ get_sessions(LUser, LServer) ->
|
||||
" @(resource)s, @(priority)s, @(info)s from sm"
|
||||
" where username=%(LUser)s")) of
|
||||
{selected, Rows} ->
|
||||
lists:flatmap(
|
||||
fun(Row) ->
|
||||
try [row_to_session(LServer, Row)]
|
||||
catch _:{bad_node, _} -> []
|
||||
end
|
||||
end, Rows);
|
||||
{ok, lists:flatmap(
|
||||
fun(Row) ->
|
||||
try [row_to_session(LServer, Row)]
|
||||
catch _:{bad_node, _} -> []
|
||||
end
|
||||
end, Rows)};
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
|
||||
[]
|
||||
end.
|
||||
|
||||
get_sessions(LUser, LServer, LResource) ->
|
||||
case ejabberd_sql:sql_query(
|
||||
LServer,
|
||||
?SQL("select @(usec)d, @(pid)s, @(node)s, @(username)s,"
|
||||
" @(resource)s, @(priority)s, @(info)s from sm"
|
||||
" where username=%(LUser)s and resource=%(LResource)s")) of
|
||||
{selected, Rows} ->
|
||||
lists:flatmap(
|
||||
fun(Row) ->
|
||||
try [row_to_session(LServer, Row)]
|
||||
catch _:{bad_node, _} -> []
|
||||
end
|
||||
end, Rows);
|
||||
Err ->
|
||||
?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
|
||||
[]
|
||||
{error, db_failure}
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
@@ -176,7 +148,7 @@ timestamp_to_now(I) ->
|
||||
Head = I div 1000000,
|
||||
USec = I rem 1000000,
|
||||
MSec = Head div 1000000,
|
||||
Sec = Head div 1000000,
|
||||
Sec = Head rem 1000000,
|
||||
{MSec, Sec, USec}.
|
||||
|
||||
dec_priority(Prio) ->
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
%% API
|
||||
%%====================================================================
|
||||
-spec start(atom(), sockmod(), socket(), [proplists:property()])
|
||||
-> {ok, pid() | independent} | {error, inet:posix() | any()}.
|
||||
-> {ok, pid() | independent} | {error, inet:posix() | any()} | ignore.
|
||||
start(Module, SockMod, Socket, Opts) ->
|
||||
case Module:socket_type() of
|
||||
independent -> {ok, independent};
|
||||
|
||||
+97
-98
@@ -29,9 +29,7 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-define(GEN_FSM, p1_fsm).
|
||||
|
||||
-behaviour(?GEN_FSM).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, start_link/2,
|
||||
@@ -56,7 +54,7 @@
|
||||
freetds_config/0,
|
||||
odbcinst_config/0,
|
||||
init_mssql/1,
|
||||
keep_alive/1]).
|
||||
keep_alive/2]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
-export([init/1, handle_event/3, handle_sync_event/4,
|
||||
@@ -93,10 +91,6 @@
|
||||
|
||||
-define(MAX_TRANSACTION_RESTARTS, 10).
|
||||
|
||||
-define(TRANSACTION_TIMEOUT, 60000).
|
||||
|
||||
-define(KEEPALIVE_TIMEOUT, 60000).
|
||||
|
||||
-define(KEEPALIVE_QUERY, [<<"SELECT 1;">>]).
|
||||
|
||||
-define(PREPARE_KEY, ejabberd_sql_prepare).
|
||||
@@ -117,11 +111,11 @@
|
||||
%%% API
|
||||
%%%----------------------------------------------------------------------
|
||||
start(Host) ->
|
||||
(?GEN_FSM):start(ejabberd_sql, [Host],
|
||||
p1_fsm:start(ejabberd_sql, [Host],
|
||||
fsm_limit_opts() ++ (?FSMOPTS)).
|
||||
|
||||
start_link(Host, StartInterval) ->
|
||||
(?GEN_FSM):start_link(ejabberd_sql,
|
||||
p1_fsm:start_link(ejabberd_sql,
|
||||
[Host, StartInterval],
|
||||
fsm_limit_opts() ++ (?FSMOPTS)).
|
||||
|
||||
@@ -164,18 +158,18 @@ sql_call(Host, Msg) ->
|
||||
case ejabberd_sql_sup:get_random_pid(Host) of
|
||||
none -> {error, <<"Unknown Host">>};
|
||||
Pid ->
|
||||
(?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg,
|
||||
p1_fsm:sync_send_event(Pid,{sql_cmd, Msg,
|
||||
p1_time_compat:monotonic_time(milli_seconds)},
|
||||
?TRANSACTION_TIMEOUT)
|
||||
query_timeout(Host))
|
||||
end;
|
||||
_State -> nested_op(Msg)
|
||||
end.
|
||||
|
||||
keep_alive(PID) ->
|
||||
(?GEN_FSM):sync_send_event(PID,
|
||||
keep_alive(Host, PID) ->
|
||||
p1_fsm:sync_send_event(PID,
|
||||
{sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
|
||||
p1_time_compat:monotonic_time(milli_seconds)},
|
||||
?KEEPALIVE_TIMEOUT).
|
||||
query_timeout(Host)).
|
||||
|
||||
-spec sql_query_t(sql_query()) -> sql_query_result().
|
||||
|
||||
@@ -198,9 +192,20 @@ abort(Reason) ->
|
||||
restart(Reason) ->
|
||||
throw({aborted, Reason}).
|
||||
|
||||
%% Escape character that will confuse an SQL engine
|
||||
-spec escape_char(char()) -> binary().
|
||||
escape_char($\000) -> <<"\\0">>;
|
||||
escape_char($\n) -> <<"\\n">>;
|
||||
escape_char($\t) -> <<"\\t">>;
|
||||
escape_char($\b) -> <<"\\b">>;
|
||||
escape_char($\r) -> <<"\\r">>;
|
||||
escape_char($') -> <<"''">>;
|
||||
escape_char($") -> <<"\\\"">>;
|
||||
escape_char($\\) -> <<"\\\\">>;
|
||||
escape_char(C) -> <<C>>.
|
||||
|
||||
-spec escape(binary()) -> binary().
|
||||
escape(S) ->
|
||||
<< <<(sql_queries:escape(Char))/binary>> || <<Char>> <= S >>.
|
||||
<< <<(escape_char(Char))/binary>> || <<Char>> <= S >>.
|
||||
|
||||
%% Escape character that will confuse an SQL engine
|
||||
%% Percent and underscore only need to be escaped for pattern matching like
|
||||
@@ -210,7 +215,7 @@ escape_like(S) when is_binary(S) ->
|
||||
escape_like($%) -> <<"\\%">>;
|
||||
escape_like($_) -> <<"\\_">>;
|
||||
escape_like($\\) -> <<"\\\\\\\\">>;
|
||||
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> sql_queries:escape(C).
|
||||
escape_like(C) when is_integer(C), C >= 0, C =< 255 -> escape_char(C).
|
||||
|
||||
escape_like_arg(S) when is_binary(S) ->
|
||||
<< <<(escape_like_arg(C))/binary>> || <<C>> <= S >>;
|
||||
@@ -252,8 +257,7 @@ sqlite_db(Host) ->
|
||||
|
||||
-spec sqlite_file(binary()) -> string().
|
||||
sqlite_file(Host) ->
|
||||
case ejabberd_config:get_option({sql_database, Host},
|
||||
fun iolist_to_binary/1) of
|
||||
case ejabberd_config:get_option({sql_database, Host}) of
|
||||
undefined ->
|
||||
{ok, Cwd} = file:get_cwd(),
|
||||
filename:join([Cwd, "sqlite", atom_to_list(node()),
|
||||
@@ -266,20 +270,17 @@ sqlite_file(Host) ->
|
||||
%%% Callback functions from gen_fsm
|
||||
%%%----------------------------------------------------------------------
|
||||
init([Host, StartInterval]) ->
|
||||
case ejabberd_config:get_option(
|
||||
{sql_keepalive_interval, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end) of
|
||||
case ejabberd_config:get_option({sql_keepalive_interval, Host}) of
|
||||
undefined ->
|
||||
ok;
|
||||
KeepaliveInterval ->
|
||||
timer:apply_interval(KeepaliveInterval * 1000, ?MODULE,
|
||||
keep_alive, [self()])
|
||||
keep_alive, [Host, self()])
|
||||
end,
|
||||
[DBType | _] = db_opts(Host),
|
||||
(?GEN_FSM):send_event(self(), connect),
|
||||
p1_fsm:send_event(self(), connect),
|
||||
ejabberd_sql_sup:add_pid(Host, self()),
|
||||
QueueType = case ejabberd_config:get_option(
|
||||
{sql_queue_type, Host}, opt_type(sql_queue_type)) of
|
||||
QueueType = case ejabberd_config:get_option({sql_queue_type, Host}) of
|
||||
undefined ->
|
||||
ejabberd_config:default_queue_type(Host);
|
||||
Type ->
|
||||
@@ -292,11 +293,11 @@ init([Host, StartInterval]) ->
|
||||
|
||||
connecting(connect, #state{host = Host} = State) ->
|
||||
ConnectRes = case db_opts(Host) of
|
||||
[mysql | Args] -> apply(fun mysql_connect/7, Args);
|
||||
[pgsql | Args] -> apply(fun pgsql_connect/7, Args);
|
||||
[mysql | Args] -> apply(fun mysql_connect/8, Args);
|
||||
[pgsql | Args] -> apply(fun pgsql_connect/8, Args);
|
||||
[sqlite | Args] -> apply(fun sqlite_connect/1, Args);
|
||||
[mssql | Args] -> apply(fun odbc_connect/1, Args);
|
||||
[odbc | Args] -> apply(fun odbc_connect/1, Args)
|
||||
[mssql | Args] -> apply(fun odbc_connect/2, Args);
|
||||
[odbc | Args] -> apply(fun odbc_connect/2, Args)
|
||||
end,
|
||||
case ConnectRes of
|
||||
{ok, Ref} ->
|
||||
@@ -310,7 +311,7 @@ connecting(connect, #state{host = Host} = State) ->
|
||||
PendingRequests =
|
||||
p1_queue:dropwhile(
|
||||
fun(Req) ->
|
||||
?GEN_FSM:send_event(self(), Req),
|
||||
p1_fsm:send_event(self(), Req),
|
||||
true
|
||||
end, State#state.pending_requests),
|
||||
State1 = State#state{db_ref = Ref,
|
||||
@@ -322,7 +323,7 @@ connecting(connect, #state{host = Host} = State) ->
|
||||
"Retry after: ~p seconds",
|
||||
[State#state.db_type, Reason,
|
||||
State#state.start_interval div 1000]),
|
||||
(?GEN_FSM):send_event_after(State#state.start_interval,
|
||||
p1_fsm:send_event_after(State#state.start_interval,
|
||||
connect),
|
||||
{next_state, connecting, State}
|
||||
end;
|
||||
@@ -334,7 +335,7 @@ connecting(Event, State) ->
|
||||
connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
|
||||
_Timestamp},
|
||||
From, State) ->
|
||||
(?GEN_FSM):reply(From,
|
||||
p1_fsm:reply(From,
|
||||
{error, <<"SQL connection failed">>}),
|
||||
{next_state, connecting, State};
|
||||
connecting({sql_cmd, Command, Timestamp} = Req, From,
|
||||
@@ -347,7 +348,7 @@ connecting({sql_cmd, Command, Timestamp} = Req, From,
|
||||
catch error:full ->
|
||||
Q = p1_queue:dropwhile(
|
||||
fun({sql_cmd, _, To, _Timestamp}) ->
|
||||
(?GEN_FSM):reply(
|
||||
p1_fsm:reply(
|
||||
To, {error, <<"SQL connection failed">>}),
|
||||
true
|
||||
end, State#state.pending_requests),
|
||||
@@ -390,7 +391,7 @@ code_change(_OldVsn, StateName, State, _Extra) ->
|
||||
%% monitoring the connection)
|
||||
handle_info({'DOWN', _MonitorRef, process, _Pid, _Info},
|
||||
_StateName, State) ->
|
||||
(?GEN_FSM):send_event(self(), connect),
|
||||
p1_fsm:send_event(self(), connect),
|
||||
{next_state, connecting, State};
|
||||
handle_info(Info, StateName, State) ->
|
||||
?WARNING_MSG("unexpected info in ~p: ~p",
|
||||
@@ -418,8 +419,9 @@ print_state(State) -> State.
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
run_sql_cmd(Command, From, State, Timestamp) ->
|
||||
QueryTimeout = query_timeout(State#state.host),
|
||||
case p1_time_compat:monotonic_time(milli_seconds) - Timestamp of
|
||||
Age when Age < (?TRANSACTION_TIMEOUT) ->
|
||||
Age when Age < QueryTimeout ->
|
||||
put(?NESTING_KEY, ?TOP_LEVEL_TXN),
|
||||
put(?STATE_KEY, State),
|
||||
abort_on_driver_error(outer_op(Command), From);
|
||||
@@ -582,20 +584,21 @@ sql_query_internal(F) when is_function(F) ->
|
||||
sql_query_internal(Query) ->
|
||||
State = get(?STATE_KEY),
|
||||
?DEBUG("SQL: \"~s\"", [Query]),
|
||||
QueryTimeout = query_timeout(State#state.host),
|
||||
Res = case State#state.db_type of
|
||||
odbc ->
|
||||
to_odbc(odbc:sql_query(State#state.db_ref, [Query],
|
||||
(?TRANSACTION_TIMEOUT) - 1000));
|
||||
QueryTimeout - 1000));
|
||||
mssql ->
|
||||
to_odbc(odbc:sql_query(State#state.db_ref, [Query],
|
||||
(?TRANSACTION_TIMEOUT) - 1000));
|
||||
QueryTimeout - 1000));
|
||||
pgsql ->
|
||||
pgsql_to_odbc(pgsql:squery(State#state.db_ref, Query,
|
||||
(?TRANSACTION_TIMEOUT) - 1000));
|
||||
QueryTimeout - 1000));
|
||||
mysql ->
|
||||
R = mysql_to_odbc(p1_mysql_conn:squery(State#state.db_ref,
|
||||
[Query], self(),
|
||||
[{timeout, (?TRANSACTION_TIMEOUT) - 1000},
|
||||
[{timeout, QueryTimeout - 1000},
|
||||
{result_type, binary}])),
|
||||
%% ?INFO_MSG("MySQL, Received result~n~p~n", [R]),
|
||||
R;
|
||||
@@ -729,27 +732,28 @@ sql_query_to_iolist(SQLQuery) ->
|
||||
abort_on_driver_error({error, <<"query timed out">>} =
|
||||
Reply,
|
||||
From) ->
|
||||
(?GEN_FSM):reply(From, Reply),
|
||||
p1_fsm:reply(From, Reply),
|
||||
{stop, timeout, get(?STATE_KEY)};
|
||||
abort_on_driver_error({error,
|
||||
<<"Failed sending data on socket", _/binary>>} =
|
||||
Reply,
|
||||
From) ->
|
||||
(?GEN_FSM):reply(From, Reply),
|
||||
p1_fsm:reply(From, Reply),
|
||||
{stop, closed, get(?STATE_KEY)};
|
||||
abort_on_driver_error(Reply, From) ->
|
||||
(?GEN_FSM):reply(From, Reply),
|
||||
p1_fsm:reply(From, Reply),
|
||||
{next_state, session_established, get(?STATE_KEY)}.
|
||||
|
||||
%% == pure ODBC code
|
||||
|
||||
%% part of init/1
|
||||
%% Open an ODBC database connection
|
||||
odbc_connect(SQLServer) ->
|
||||
odbc_connect(SQLServer, Timeout) ->
|
||||
ejabberd:start_app(odbc),
|
||||
odbc:connect(binary_to_list(SQLServer),
|
||||
[{scrollable_cursors, off},
|
||||
{tuple_row, off},
|
||||
{timeout, Timeout},
|
||||
{binary_strings, on}]).
|
||||
|
||||
%% == Native SQLite code
|
||||
@@ -797,13 +801,15 @@ sqlite_to_odbc(_Host, _) ->
|
||||
|
||||
%% part of init/1
|
||||
%% Open a database connection to PostgreSQL
|
||||
pgsql_connect(Server, Port, DB, Username, Password, Transport, SSLOpts) ->
|
||||
pgsql_connect(Server, Port, DB, Username, Password, ConnectTimeout,
|
||||
Transport, SSLOpts) ->
|
||||
case pgsql:connect([{host, Server},
|
||||
{database, DB},
|
||||
{user, Username},
|
||||
{password, Password},
|
||||
{port, Port},
|
||||
{transport, Transport},
|
||||
{connect_timeout, ConnectTimeout},
|
||||
{as_binary, true}|SSLOpts]) of
|
||||
{ok, Ref} ->
|
||||
pgsql:squery(Ref, [<<"alter database \"">>, DB, <<"\" set ">>,
|
||||
@@ -853,11 +859,12 @@ pgsql_execute_to_odbc(_) -> {updated, undefined}.
|
||||
|
||||
%% part of init/1
|
||||
%% Open a database connection to MySQL
|
||||
mysql_connect(Server, Port, DB, Username, Password, _, _) ->
|
||||
mysql_connect(Server, Port, DB, Username, Password, ConnectTimeout, _, _) ->
|
||||
case p1_mysql_conn:start(binary_to_list(Server), Port,
|
||||
binary_to_list(Username),
|
||||
binary_to_list(Password),
|
||||
binary_to_list(DB), fun log/3)
|
||||
binary_to_list(DB),
|
||||
ConnectTimeout, fun log/3)
|
||||
of
|
||||
{ok, Ref} ->
|
||||
p1_mysql_conn:fetch(
|
||||
@@ -927,54 +934,41 @@ log(Level, Format, Args) ->
|
||||
end.
|
||||
|
||||
db_opts(Host) ->
|
||||
Type = ejabberd_config:get_option({sql_type, Host},
|
||||
fun(mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(sqlite) -> sqlite;
|
||||
(mssql) -> mssql;
|
||||
(odbc) -> odbc
|
||||
end, odbc),
|
||||
Server = ejabberd_config:get_option({sql_server, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"localhost">>),
|
||||
Transport = case ejabberd_config:get_option(
|
||||
{sql_ssl, Host},
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
false) of
|
||||
Type = ejabberd_config:get_option({sql_type, Host}, odbc),
|
||||
Server = ejabberd_config:get_option({sql_server, Host}, <<"localhost">>),
|
||||
Timeout = timer:seconds(
|
||||
ejabberd_config:get_option({sql_connect_timeout, Host}, 5)),
|
||||
Transport = case ejabberd_config:get_option({sql_ssl, Host}, false) of
|
||||
false -> tcp;
|
||||
true -> ssl
|
||||
end,
|
||||
warn_if_ssl_unsupported(Transport, Type),
|
||||
case Type of
|
||||
odbc ->
|
||||
[odbc, Server];
|
||||
[odbc, Server, Timeout];
|
||||
sqlite ->
|
||||
[sqlite, Host];
|
||||
_ ->
|
||||
Port = ejabberd_config:get_option(
|
||||
{sql_port, Host},
|
||||
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
|
||||
case Type of
|
||||
mssql -> ?MSSQL_PORT;
|
||||
mysql -> ?MYSQL_PORT;
|
||||
pgsql -> ?PGSQL_PORT
|
||||
end),
|
||||
DB = ejabberd_config:get_option({sql_database, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>),
|
||||
User = ejabberd_config:get_option({sql_username, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>),
|
||||
Pass = ejabberd_config:get_option({sql_password, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"">>),
|
||||
SSLOpts = get_ssl_opts(Transport, Host),
|
||||
case Type of
|
||||
mssql ->
|
||||
[mssql, <<"DSN=", Host/binary, ";UID=", User/binary,
|
||||
";PWD=", Pass/binary>>];
|
||||
";PWD=", Pass/binary>>, Timeout];
|
||||
_ ->
|
||||
[Type, Server, Port, DB, User, Pass, Transport, SSLOpts]
|
||||
[Type, Server, Port, DB, User, Pass, Timeout, Transport, SSLOpts]
|
||||
end
|
||||
end.
|
||||
|
||||
@@ -986,19 +980,15 @@ warn_if_ssl_unsupported(ssl, Type) ->
|
||||
?WARNING_MSG("SSL connection is not supported for ~s", [Type]).
|
||||
|
||||
get_ssl_opts(ssl, Host) ->
|
||||
Opts1 = case ejabberd_config:get_option({sql_ssl_certfile, Host},
|
||||
fun iolist_to_binary/1) of
|
||||
Opts1 = case ejabberd_config:get_option({sql_ssl_certfile, Host}) of
|
||||
undefined -> [];
|
||||
CertFile -> [{certfile, CertFile}]
|
||||
end,
|
||||
Opts2 = case ejabberd_config:get_option({sql_ssl_cafile, Host},
|
||||
fun iolist_to_binary/1) of
|
||||
Opts2 = case ejabberd_config:get_option({sql_ssl_cafile, Host}) of
|
||||
undefined -> Opts1;
|
||||
CAFile -> [{cacertfile, CAFile}|Opts1]
|
||||
end,
|
||||
case ejabberd_config:get_option({sql_ssl_verify, Host},
|
||||
fun(B) when is_boolean(B) -> B end,
|
||||
false) of
|
||||
case ejabberd_config:get_option({sql_ssl_verify, Host}, false) of
|
||||
true ->
|
||||
case lists:keymember(cacertfile, 1, Opts2) of
|
||||
true ->
|
||||
@@ -1017,16 +1007,9 @@ get_ssl_opts(tcp, _) ->
|
||||
[].
|
||||
|
||||
init_mssql(Host) ->
|
||||
Server = ejabberd_config:get_option({sql_server, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"localhost">>),
|
||||
Port = ejabberd_config:get_option(
|
||||
{sql_port, Host},
|
||||
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
|
||||
?MSSQL_PORT),
|
||||
DB = ejabberd_config:get_option({sql_database, Host},
|
||||
fun iolist_to_binary/1,
|
||||
<<"ejabberd">>),
|
||||
Server = ejabberd_config:get_option({sql_server, Host}, <<"localhost">>),
|
||||
Port = ejabberd_config:get_option({sql_port, Host}, ?MSSQL_PORT),
|
||||
DB = ejabberd_config:get_option({sql_database, Host}, <<"ejabberd">>),
|
||||
FreeTDS = io_lib:fwrite("[~s]~n"
|
||||
"\thost = ~s~n"
|
||||
"\tport = ~p~n"
|
||||
@@ -1087,6 +1070,10 @@ max_fsm_queue() ->
|
||||
fsm_limit_opts() ->
|
||||
ejabberd_config:fsm_limit_opts([]).
|
||||
|
||||
query_timeout(LServer) ->
|
||||
timer:seconds(
|
||||
ejabberd_config:get_option({sql_query_timeout, LServer}, 60)).
|
||||
|
||||
check_error({error, Why} = Err, #sql_query{} = Query) ->
|
||||
?ERROR_MSG("SQL query '~s' at ~p failed: ~p",
|
||||
[Query#sql_query.hash, Query#sql_query.loc, Why]),
|
||||
@@ -1102,6 +1089,20 @@ check_error({error, Why} = Err, Query) ->
|
||||
check_error(Result, _Query) ->
|
||||
Result.
|
||||
|
||||
-spec opt_type(sql_database) -> fun((binary()) -> binary());
|
||||
(sql_keepalive_interval) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_password) -> fun((binary()) -> binary());
|
||||
(sql_port) -> fun((0..65535) -> 0..65535);
|
||||
(sql_server) -> fun((binary()) -> binary());
|
||||
(sql_username) -> fun((binary()) -> binary());
|
||||
(sql_ssl) -> fun((boolean()) -> boolean());
|
||||
(sql_ssl_verify) -> fun((boolean()) -> boolean());
|
||||
(sql_ssl_certfile) -> fun((boolean()) -> boolean());
|
||||
(sql_ssl_cafile) -> fun((boolean()) -> boolean());
|
||||
(sql_query_timeout) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_connect_timeout) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_queue_type) -> fun((ram | file) -> ram | file);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(sql_database) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_keepalive_interval) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
@@ -1109,22 +1110,20 @@ opt_type(sql_password) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_port) ->
|
||||
fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
|
||||
opt_type(sql_server) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_type) ->
|
||||
fun (mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(sqlite) -> sqlite;
|
||||
(mssql) -> mssql;
|
||||
(odbc) -> odbc
|
||||
end;
|
||||
opt_type(sql_username) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_ssl) -> fun(B) when is_boolean(B) -> B end;
|
||||
opt_type(sql_ssl_verify) -> fun(B) when is_boolean(B) -> B end;
|
||||
opt_type(sql_ssl_certfile) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_ssl_cafile) -> fun iolist_to_binary/1;
|
||||
opt_type(sql_ssl_certfile) -> fun ejabberd_pkix:try_certfile/1;
|
||||
opt_type(sql_ssl_cafile) -> fun misc:try_read_file/1;
|
||||
opt_type(sql_query_timeout) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(sql_connect_timeout) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(sql_queue_type) ->
|
||||
fun(ram) -> ram; (file) -> file end;
|
||||
opt_type(_) ->
|
||||
[sql_database, sql_keepalive_interval,
|
||||
sql_password, sql_port, sql_server, sql_type,
|
||||
sql_username, sql_ssl, sql_ssl_verify, sql_ssl_cerfile,
|
||||
sql_ssl_cafile, sql_queue_type].
|
||||
sql_password, sql_port, sql_server,
|
||||
sql_username, sql_ssl, sql_ssl_verify, sql_ssl_certfile,
|
||||
sql_ssl_cafile, sql_queue_type, sql_query_timeout,
|
||||
sql_connect_timeout].
|
||||
|
||||
@@ -53,7 +53,6 @@ start_link(Host) ->
|
||||
[{ram_copies, [node()]}, {type, bag},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, sql_pool)}]),
|
||||
mnesia:add_table_copy(sql_pool, node(), ram_copies),
|
||||
F = fun () -> mnesia:delete({sql_pool, Host}) end,
|
||||
mnesia:ets(F),
|
||||
supervisor:start_link({local,
|
||||
@@ -63,15 +62,8 @@ start_link(Host) ->
|
||||
init([Host]) ->
|
||||
StartInterval = ejabberd_config:get_option(
|
||||
{sql_start_interval, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
?DEFAULT_SQL_START_INTERVAL),
|
||||
Type = ejabberd_config:get_option({sql_type, Host},
|
||||
fun(mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(sqlite) -> sqlite;
|
||||
(mssql) -> mssql;
|
||||
(odbc) -> odbc
|
||||
end, odbc),
|
||||
Type = ejabberd_config:get_option({sql_type, Host}, odbc),
|
||||
PoolSize = get_pool_size(Type, Host),
|
||||
case Type of
|
||||
sqlite ->
|
||||
@@ -99,7 +91,9 @@ get_pids(Host) ->
|
||||
get_random_pid(Host) ->
|
||||
case get_pids(Host) of
|
||||
[] -> none;
|
||||
Pids -> lists:nth(erlang:phash(p1_time_compat:unique_integer(), length(Pids)), Pids)
|
||||
Pids ->
|
||||
I = randoms:round_robin(length(Pids)) + 1,
|
||||
lists:nth(I, Pids)
|
||||
end.
|
||||
|
||||
add_pid(Host, Pid) ->
|
||||
@@ -118,7 +112,6 @@ remove_pid(Host, Pid) ->
|
||||
get_pool_size(SQLType, Host) ->
|
||||
PoolSize = ejabberd_config:get_option(
|
||||
{sql_pool_size, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
case SQLType of
|
||||
sqlite -> 1;
|
||||
_ -> ?DEFAULT_POOL_SIZE
|
||||
@@ -225,16 +218,12 @@ read_lines(Fd, File, Acc) ->
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec opt_type(sql_pool_size) -> fun((pos_integer()) -> pos_integer());
|
||||
(sql_start_interval) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(sql_pool_size) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(sql_start_interval) ->
|
||||
fun (I) when is_integer(I), I > 0 -> I end;
|
||||
opt_type(sql_type) ->
|
||||
fun (mysql) -> mysql;
|
||||
(pgsql) -> pgsql;
|
||||
(sqlite) -> sqlite;
|
||||
(mssql) -> mssql;
|
||||
(odbc) -> odbc
|
||||
end;
|
||||
opt_type(_) ->
|
||||
[sql_pool_size, sql_start_interval, sql_type].
|
||||
[sql_pool_size, sql_start_interval].
|
||||
|
||||
+63
-9
@@ -27,8 +27,23 @@
|
||||
-protocol({rfc, 5766}).
|
||||
-protocol({xep, 176, '1.0'}).
|
||||
|
||||
-ifndef(STUN).
|
||||
-include("logger.hrl").
|
||||
-export([socket_type/0, start/2, listen_opt_type/1]).
|
||||
log_error() ->
|
||||
?CRITICAL_MSG("ejabberd is not compiled with STUN/TURN support", []).
|
||||
socket_type() ->
|
||||
log_error(),
|
||||
raw.
|
||||
listen_opt_type(_) ->
|
||||
log_error(),
|
||||
[].
|
||||
start(_, _) ->
|
||||
log_error(),
|
||||
{error, sip_not_compiled}.
|
||||
-else.
|
||||
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
|
||||
socket_type/0]).
|
||||
socket_type/0, listen_opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -73,14 +88,9 @@ prepare_turn_opts(Opts, _UseTurn = true) ->
|
||||
ok
|
||||
end,
|
||||
AuthFun = fun ejabberd_auth:get_password_s/2,
|
||||
Shaper = gen_mod:get_opt(shaper, Opts,
|
||||
fun(S) when is_atom(S) -> S end,
|
||||
none),
|
||||
AuthType = gen_mod:get_opt(auth_type, Opts,
|
||||
fun(anonymous) -> anonymous;
|
||||
(user) -> user
|
||||
end, user),
|
||||
Realm = case gen_mod:get_opt(auth_realm, Opts, fun iolist_to_binary/1) of
|
||||
Shaper = proplists:get_value(shaper, Opts, none),
|
||||
AuthType = proplists:get_value(auth_type, Opts, user),
|
||||
Realm = case proplists:get_value(auth_realm, Opts) of
|
||||
undefined when AuthType == user ->
|
||||
if NumberOfMyHosts > 1 ->
|
||||
?WARNING_MSG("you have several virtual "
|
||||
@@ -100,3 +110,47 @@ prepare_turn_opts(Opts, _UseTurn = true) ->
|
||||
MaxRate = shaper:get_max_rate(Shaper),
|
||||
Realm ++ [{auth_fun, AuthFun},{shaper, MaxRate} |
|
||||
lists:keydelete(shaper, 1, Opts)].
|
||||
|
||||
listen_opt_type(use_turn) ->
|
||||
fun(B) when is_boolean(B) -> B end;
|
||||
listen_opt_type(turn_ip) ->
|
||||
fun(S) ->
|
||||
{ok, Addr} = inet_parse:ipv4_address(binary_to_list(S)),
|
||||
Addr
|
||||
end;
|
||||
listen_opt_type(shaper) ->
|
||||
fun acl:shaper_rules_validator/1;
|
||||
listen_opt_type(auth_type) ->
|
||||
fun(anonymous) -> anonymous;
|
||||
(user) -> user
|
||||
end;
|
||||
listen_opt_type(auth_realm) ->
|
||||
fun iolist_to_binary/1;
|
||||
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(turn_min_port) ->
|
||||
fun(P) when is_integer(P), P > 0, P =< 65535 -> P end;
|
||||
listen_opt_type(turn_max_port) ->
|
||||
fun(P) when is_integer(P), P > 0, P =< 65535 -> P end;
|
||||
listen_opt_type(turn_max_allocations) ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
listen_opt_type(turn_max_permissions) ->
|
||||
fun(I) when is_integer(I), I>0 -> I;
|
||||
(unlimited) -> infinity;
|
||||
(infinity) -> infinity
|
||||
end;
|
||||
listen_opt_type(server_name) ->
|
||||
fun iolist_to_binary/1;
|
||||
listen_opt_type(_) ->
|
||||
[shaper, auth_type, auth_realm, tls, certfile, turn_min_port,
|
||||
turn_max_port, turn_max_allocations, turn_max_permissions,
|
||||
server_name].
|
||||
-endif.
|
||||
|
||||
@@ -41,6 +41,12 @@ init([]) ->
|
||||
brutal_kill,
|
||||
worker,
|
||||
[ejabberd_hooks]},
|
||||
Cluster = {ejabberd_cluster,
|
||||
{ejabberd_cluster, start_link, []},
|
||||
permanent,
|
||||
5000,
|
||||
worker,
|
||||
[ejabberd_cluster]},
|
||||
SystemMonitor =
|
||||
{ejabberd_system_monitor,
|
||||
{ejabberd_system_monitor, start_link, []},
|
||||
@@ -148,14 +154,18 @@ init([]) ->
|
||||
permanent, 5000, worker, [ejabberd_admin]},
|
||||
CyrSASL = {cyrsasl, {cyrsasl, start_link, []},
|
||||
permanent, 5000, worker, [cyrsasl]},
|
||||
PKIX = {ejabberd_pkix, {ejabberd_pkix, start_link, []},
|
||||
permanent, 5000, worker, [ejabberd_pkix]},
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
[Hooks,
|
||||
Cluster,
|
||||
CyrSASL,
|
||||
Translation,
|
||||
AccessPerms,
|
||||
Ctl,
|
||||
Commands,
|
||||
Admin,
|
||||
PKIX,
|
||||
Listener,
|
||||
SystemMonitor,
|
||||
S2S,
|
||||
|
||||
@@ -53,10 +53,7 @@
|
||||
%% Description: Starts the server
|
||||
%%--------------------------------------------------------------------
|
||||
start_link() ->
|
||||
LH = ejabberd_config:get_option(
|
||||
watchdog_large_heap,
|
||||
fun(I) when is_integer(I), I > 0 -> I end,
|
||||
1000000),
|
||||
LH = ejabberd_config:get_option(watchdog_large_heap, 1000000),
|
||||
Opts = [{large_heap, LH}],
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts,
|
||||
[]).
|
||||
@@ -205,13 +202,7 @@ send_message(From, To, Body, ExtraEls) ->
|
||||
sub_els = ExtraEls}).
|
||||
|
||||
get_admin_jids() ->
|
||||
ejabberd_config:get_option(
|
||||
watchdog_admins,
|
||||
fun(JIDs) ->
|
||||
[jid:tolower(
|
||||
jid:decode(
|
||||
iolist_to_binary(S))) || S <- JIDs]
|
||||
end, []).
|
||||
ejabberd_config:get_option(watchdog_admins, []).
|
||||
|
||||
detailed_info(Pid) ->
|
||||
case process_info(Pid, dictionary) of
|
||||
@@ -339,6 +330,9 @@ process_remote_command([setlh, NewValue]) ->
|
||||
[OldLH, NewLH]);
|
||||
process_remote_command(_) -> throw(unknown_command).
|
||||
|
||||
-spec opt_type(watchdog_admins) -> fun(([binary()]) -> ljid());
|
||||
(watchdog_large_heap) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(watchdog_admins) ->
|
||||
fun (JIDs) ->
|
||||
[jid:tolower(jid:decode(iolist_to_binary(S)))
|
||||
|
||||
+30
-28
File diff suppressed because one or more lines are too long
@@ -152,7 +152,7 @@ handshake(#ws{headers = Headers} = State) ->
|
||||
V ->
|
||||
[<<"Sec-Websocket-Protocol:">>, V, <<"\r\n">>]
|
||||
end,
|
||||
Hash = misc:encode_base64(
|
||||
Hash = base64:encode(
|
||||
crypto:hash(sha, <<Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>)),
|
||||
{State, [<<"HTTP/1.1 101 Switching Protocols\r\n">>,
|
||||
<<"Upgrade: websocket\r\n">>,
|
||||
|
||||
+24
-31
@@ -35,7 +35,7 @@
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-export([start/2, handler/2, process/2, socket_type/0,
|
||||
transform_listen_option/2]).
|
||||
transform_listen_option/2, listen_opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -197,36 +197,7 @@ socket_type() -> raw.
|
||||
%% HTTP interface
|
||||
%% -----------------------------
|
||||
process(_, #request{method = 'POST', data = Data, opts = Opts, ip = {IP, _}}) ->
|
||||
AccessCommandsOpts = gen_mod:get_opt(access_commands, Opts,
|
||||
fun(L) when is_list(L) -> L end,
|
||||
undefined),
|
||||
AccessCommands =
|
||||
case AccessCommandsOpts of
|
||||
undefined -> undefined;
|
||||
_ ->
|
||||
lists:flatmap(
|
||||
fun({Ac, AcOpts}) ->
|
||||
Commands = gen_mod:get_opt(
|
||||
commands, lists:flatten(AcOpts),
|
||||
fun(A) when is_atom(A) ->
|
||||
A;
|
||||
(L) when is_list(L) ->
|
||||
true = lists:all(
|
||||
fun is_atom/1,
|
||||
L),
|
||||
L
|
||||
end, all),
|
||||
%% CommOpts = gen_mod:get_opt(
|
||||
%% options, AcOpts,
|
||||
%% fun(L) when is_list(L) -> L end,
|
||||
%% []),
|
||||
[{<<"ejabberd_xmlrpc compatibility shim">>, {[?MODULE], [{access, Ac}], Commands}}];
|
||||
(Wrong) ->
|
||||
?WARNING_MSG("wrong options format for ~p: ~p",
|
||||
[?MODULE, Wrong]),
|
||||
[]
|
||||
end, lists:flatten(AccessCommandsOpts))
|
||||
end,
|
||||
AccessCommands = proplists:get_value(access_commands, Opts),
|
||||
GetAuth = true,
|
||||
State = #state{access_commands = AccessCommands, get_auth = GetAuth, ip = IP},
|
||||
case fxml_stream:parse_element(Data) of
|
||||
@@ -590,3 +561,25 @@ transform_listen_option({access_commands, ACOpts}, Opts) ->
|
||||
[{access_commands, NewACOpts}|Opts];
|
||||
transform_listen_option(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
|
||||
listen_opt_type(access_commands) ->
|
||||
fun(Opts) ->
|
||||
lists:map(
|
||||
fun({Ac, AcOpts}) ->
|
||||
Commands = case proplists:get_value(
|
||||
commands, lists:flatten(AcOpts), all) of
|
||||
Cmd when is_atom(Cmd) -> Cmd;
|
||||
Cmds when is_list(Cmds) ->
|
||||
true = lists:all(fun is_atom/1, Cmds),
|
||||
Cmds
|
||||
end,
|
||||
{<<"ejabberd_xmlrpc compatibility shim">>,
|
||||
{[?MODULE], [{access, Ac}], Commands}}
|
||||
end, lists:flatten(Opts))
|
||||
end;
|
||||
listen_opt_type(maxsessions) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(timeout) ->
|
||||
fun(I) when is_integer(I), I>0 -> I end;
|
||||
listen_opt_type(_) ->
|
||||
[access_commands, maxsessions, timeout].
|
||||
|
||||
+5
-4
@@ -53,14 +53,15 @@ modules() ->
|
||||
mod_caps,
|
||||
mod_irc,
|
||||
mod_last,
|
||||
mod_mam,
|
||||
mod_muc,
|
||||
mod_offline,
|
||||
mod_privacy,
|
||||
mod_private,
|
||||
mod_pubsub,
|
||||
mod_roster,
|
||||
mod_shared_roster,
|
||||
mod_vcard,
|
||||
mod_vcard_xupdate].
|
||||
mod_vcard].
|
||||
|
||||
export(Server, Output) ->
|
||||
LServer = jid:nameprep(iolist_to_binary(Server)),
|
||||
@@ -80,8 +81,8 @@ export(Server, Output, Module) ->
|
||||
case export(LServer, Table, IO, ConvertFun) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, Reason} ->
|
||||
?ERROR_MSG("Failed export for module ~p: ~p",
|
||||
[Module, Reason])
|
||||
?ERROR_MSG("Failed export for module ~p and table ~p: ~p",
|
||||
[Module, Table, Reason])
|
||||
end
|
||||
end, Module:export(Server)),
|
||||
close_output(Output, IO).
|
||||
|
||||
+32
-45
@@ -63,7 +63,7 @@
|
||||
%%% active_bind - sent bind() request and waiting for response
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-behaviour(gen_fsm).
|
||||
-behaviour(p1_fsm).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -130,7 +130,8 @@
|
||||
port = 389 :: inet:port_number(),
|
||||
sockmod = gen_tcp :: ssl | gen_tcp,
|
||||
tls = none :: none | tls,
|
||||
tls_options = [] :: [{cacertfile, string()} |
|
||||
tls_options = [] :: [{certfile, string()} |
|
||||
{cacertfile, string()} |
|
||||
{depth, non_neg_integer()} |
|
||||
{verify, non_neg_integer()}],
|
||||
fd :: gen_tcp:socket() | undefined,
|
||||
@@ -147,7 +148,7 @@
|
||||
start_link(Name) ->
|
||||
Reg_name = misc:binary_to_atom(<<"eldap_",
|
||||
Name/binary>>),
|
||||
gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
|
||||
p1_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
|
||||
|
||||
-spec start_link(binary(), [binary()], inet:port_number(), binary(),
|
||||
binary(), tlsopts()) -> any().
|
||||
@@ -155,7 +156,7 @@ start_link(Name) ->
|
||||
start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
|
||||
Reg_name = misc:binary_to_atom(<<"eldap_",
|
||||
Name/binary>>),
|
||||
gen_fsm:start_link({local, Reg_name}, ?MODULE,
|
||||
p1_fsm:start_link({local, Reg_name}, ?MODULE,
|
||||
[Hosts, Port, Rootdn, Passwd, Opts], []).
|
||||
|
||||
-spec get_status(handle()) -> any().
|
||||
@@ -165,7 +166,7 @@ start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
|
||||
%%% --------------------------------------------------------------------
|
||||
get_status(Handle) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_all_state_event(Handle1, get_status).
|
||||
p1_fsm:sync_send_all_state_event(Handle1, get_status).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
%%% Shutdown connection (and process) asynchronous.
|
||||
@@ -174,7 +175,7 @@ get_status(Handle) ->
|
||||
|
||||
close(Handle) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:send_all_state_event(Handle1, close).
|
||||
p1_fsm:send_all_state_event(Handle1, close).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
%%% Add an entry. The entry field MUST NOT exist for the AddRequest
|
||||
@@ -191,7 +192,7 @@ close(Handle) ->
|
||||
%%% --------------------------------------------------------------------
|
||||
add(Handle, Entry, Attributes) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1,
|
||||
p1_fsm:sync_send_event(Handle1,
|
||||
{add, Entry, add_attrs(Attributes)}, ?CALL_TIMEOUT).
|
||||
|
||||
%%% Do sanity check !
|
||||
@@ -215,7 +216,7 @@ add_attrs(Attrs) ->
|
||||
%%% --------------------------------------------------------------------
|
||||
delete(Handle, Entry) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {delete, Entry},
|
||||
p1_fsm:sync_send_event(Handle1, {delete, Entry},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
@@ -233,7 +234,7 @@ delete(Handle, Entry) ->
|
||||
|
||||
modify(Handle, Object, Mods) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {modify, Object, Mods},
|
||||
p1_fsm:sync_send_event(Handle1, {modify, Object, Mods},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
%%%
|
||||
@@ -273,7 +274,7 @@ m(Operation, Type, Values) ->
|
||||
|
||||
modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1,
|
||||
p1_fsm:sync_send_event(Handle1,
|
||||
{modify_dn, Entry, NewRDN, bool_p(DelOldRDN),
|
||||
optional(NewSup)},
|
||||
?CALL_TIMEOUT).
|
||||
@@ -282,7 +283,7 @@ modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
|
||||
|
||||
modify_passwd(Handle, DN, Passwd) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1,
|
||||
p1_fsm:sync_send_event(Handle1,
|
||||
{modify_passwd, DN, Passwd}, ?CALL_TIMEOUT).
|
||||
|
||||
%%% --------------------------------------------------------------------
|
||||
@@ -297,7 +298,7 @@ modify_passwd(Handle, DN, Passwd) ->
|
||||
|
||||
bind(Handle, RootDN, Passwd) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
|
||||
p1_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
%%% Sanity checks !
|
||||
@@ -355,7 +356,7 @@ search(Handle, L) when is_list(L) ->
|
||||
|
||||
call_search(Handle, A) ->
|
||||
Handle1 = get_handle(Handle),
|
||||
gen_fsm:sync_send_event(Handle1, {search, A},
|
||||
p1_fsm:sync_send_event(Handle1, {search, A},
|
||||
?CALL_TIMEOUT).
|
||||
|
||||
-spec parse_search_args(search_args()) -> eldap_search().
|
||||
@@ -565,11 +566,7 @@ get_handle(Name) when is_binary(Name) ->
|
||||
%% process.
|
||||
%%----------------------------------------------------------------------
|
||||
init([Hosts, Port, Rootdn, Passwd, Opts]) ->
|
||||
Encrypt = case gen_mod:get_opt(encrypt, Opts,
|
||||
fun(tls) -> tls;
|
||||
(starttls) -> starttls;
|
||||
(none) -> none
|
||||
end) of
|
||||
Encrypt = case proplists:get_value(encrypt, Opts) of
|
||||
tls -> tls;
|
||||
_ -> none
|
||||
end,
|
||||
@@ -581,46 +578,36 @@ init([Hosts, Port, Rootdn, Passwd, Opts]) ->
|
||||
end;
|
||||
PT -> PT
|
||||
end,
|
||||
CacertOpts = case gen_mod:get_opt(
|
||||
tls_cacertfile, Opts,
|
||||
fun(S) when is_binary(S) ->
|
||||
binary_to_list(S);
|
||||
(undefined) ->
|
||||
undefined
|
||||
end) of
|
||||
CertOpts = case proplists:get_value(tls_certfile, Opts) of
|
||||
undefined ->
|
||||
[];
|
||||
Path1 ->
|
||||
[{certfile, Path1}]
|
||||
end,
|
||||
CacertOpts = case proplists:get_value(tls_cacertfile, Opts) of
|
||||
undefined ->
|
||||
[];
|
||||
Path ->
|
||||
[{cacertfile, Path}]
|
||||
Path2 ->
|
||||
[{cacertfile, Path2}]
|
||||
end,
|
||||
DepthOpts = case gen_mod:get_opt(
|
||||
tls_depth, Opts,
|
||||
fun(I) when is_integer(I), I>=0 ->
|
||||
I;
|
||||
(undefined) ->
|
||||
undefined
|
||||
end) of
|
||||
DepthOpts = case proplists:get_value(tls_depth, Opts) of
|
||||
undefined ->
|
||||
[];
|
||||
Depth ->
|
||||
[{depth, Depth}]
|
||||
end,
|
||||
Verify = gen_mod:get_opt(tls_verify, Opts,
|
||||
fun(hard) -> hard;
|
||||
(soft) -> soft;
|
||||
(false) -> false
|
||||
end, false),
|
||||
Verify = proplists:get_value(tls_verify, Opts, false),
|
||||
TLSOpts = if (Verify == hard orelse Verify == soft)
|
||||
andalso CacertOpts == [] ->
|
||||
?WARNING_MSG("TLS verification is enabled but no CA "
|
||||
"certfiles configured, so verification "
|
||||
"is disabled.",
|
||||
[]),
|
||||
[];
|
||||
CertOpts;
|
||||
Verify == soft ->
|
||||
[{verify, 1}] ++ CacertOpts ++ DepthOpts;
|
||||
[{verify, 1}] ++ CertOpts ++ CacertOpts ++ DepthOpts;
|
||||
Verify == hard ->
|
||||
[{verify, 2}] ++ CacertOpts ++ DepthOpts;
|
||||
[{verify, 2}] ++ CertOpts ++ CacertOpts ++ DepthOpts;
|
||||
true -> []
|
||||
end,
|
||||
{ok, connecting,
|
||||
@@ -650,7 +637,7 @@ active(Event, From, S) ->
|
||||
|
||||
%%----------------------------------------------------------------------
|
||||
%% Func: handle_event/3
|
||||
%% Called when gen_fsm:send_all_state_event/2 is invoked.
|
||||
%% Called when p1_fsm:send_all_state_event/2 is invoked.
|
||||
%% Returns: {next_state, NextStateName, NextStateData} |
|
||||
%% {next_state, NextStateName, NextStateData, Timeout} |
|
||||
%% {stop, Reason, NewStateData}
|
||||
@@ -693,7 +680,7 @@ handle_info({Tag, _Socket, Data}, StateName, S)
|
||||
case catch recvd_packet(Data, S) of
|
||||
{response, Response, RequestType} ->
|
||||
NewS = case Response of
|
||||
{reply, Reply, To, S1} -> gen_fsm:reply(To, Reply), S1;
|
||||
{reply, Reply, To, S1} -> p1_fsm:reply(To, Reply), S1;
|
||||
{ok, S1} -> S1
|
||||
end,
|
||||
if StateName == active_bind andalso
|
||||
@@ -722,7 +709,7 @@ handle_info({timeout, Timer, {cmd_timeout, Id}},
|
||||
StateName, S) ->
|
||||
case cmd_timeout(Timer, Id, S) of
|
||||
{reply, To, Reason, NewS} ->
|
||||
gen_fsm:reply(To, Reason),
|
||||
p1_fsm:reply(To, Reason),
|
||||
{next_state, StateName, NewS};
|
||||
{error, _Reason} -> {next_state, StateName, S}
|
||||
end;
|
||||
|
||||
+74
-60
@@ -28,7 +28,7 @@
|
||||
-behaviour(ejabberd_config).
|
||||
-author('mremond@process-one.net').
|
||||
|
||||
-export([generate_subfilter/1, find_ldap_attrs/2,
|
||||
-export([generate_subfilter/1, find_ldap_attrs/2, check_filter/1,
|
||||
get_ldap_attr/2, get_user_part/2, make_filter/2,
|
||||
get_state/2, case_insensitive_match/2, get_config/2,
|
||||
decode_octet_string/3, uids_domain_subst/2, opt_type/1]).
|
||||
@@ -137,6 +137,11 @@ make_filter(Data, UIDs) ->
|
||||
eldap:'and'(Filter)
|
||||
end.
|
||||
|
||||
check_filter(F) ->
|
||||
NewF = iolist_to_binary(F),
|
||||
{ok, _} = eldap_filter:parse(NewF),
|
||||
NewF.
|
||||
|
||||
-spec case_insensitive_match(binary(), binary()) -> boolean().
|
||||
|
||||
case_insensitive_match(X, Y) ->
|
||||
@@ -168,58 +173,26 @@ uids_domain_subst(Host, UIDs) ->
|
||||
-spec get_config(binary(), list()) -> eldap_config().
|
||||
|
||||
get_config(Host, Opts) ->
|
||||
Servers = gen_mod:get_opt({ldap_servers, Host}, Opts,
|
||||
fun(L) ->
|
||||
[iolist_to_binary(H) || H <- L]
|
||||
end, [<<"localhost">>]),
|
||||
Backups = gen_mod:get_opt({ldap_backups, Host}, Opts,
|
||||
fun(L) ->
|
||||
[iolist_to_binary(H) || H <- L]
|
||||
end, []),
|
||||
Encrypt = gen_mod:get_opt({ldap_encrypt, Host}, Opts,
|
||||
fun(tls) -> tls;
|
||||
(starttls) -> starttls;
|
||||
(none) -> none
|
||||
end, none),
|
||||
TLSVerify = gen_mod:get_opt({ldap_tls_verify, Host}, Opts,
|
||||
fun(hard) -> hard;
|
||||
(soft) -> soft;
|
||||
(false) -> false
|
||||
end, false),
|
||||
TLSCAFile = gen_mod:get_opt({ldap_tls_cacertfile, Host}, Opts,
|
||||
fun iolist_to_binary/1),
|
||||
TLSDepth = gen_mod:get_opt({ldap_tls_depth, Host}, Opts,
|
||||
fun(I) when is_integer(I), I>=0 -> I end),
|
||||
Port = gen_mod:get_opt({ldap_port, Host}, Opts,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
case Encrypt of
|
||||
tls -> ?LDAPS_PORT;
|
||||
starttls -> ?LDAP_PORT;
|
||||
_ -> ?LDAP_PORT
|
||||
end),
|
||||
RootDN = gen_mod:get_opt({ldap_rootdn, Host}, Opts,
|
||||
fun iolist_to_binary/1,
|
||||
<<"">>),
|
||||
Password = gen_mod:get_opt({ldap_password, Host}, Opts,
|
||||
fun iolist_to_binary/1,
|
||||
<<"">>),
|
||||
Base = gen_mod:get_opt({ldap_base, Host}, Opts,
|
||||
fun iolist_to_binary/1,
|
||||
<<"">>),
|
||||
OldDerefAliases = gen_mod:get_opt({deref_aliases, Host}, Opts,
|
||||
fun(never) -> never;
|
||||
(searching) -> searching;
|
||||
(finding) -> finding;
|
||||
(always) -> always
|
||||
end, unspecified),
|
||||
Servers = get_opt(ldap_servers, Host, Opts, [<<"localhost">>]),
|
||||
Backups = get_opt(ldap_backups, Host, Opts, []),
|
||||
Encrypt = get_opt(ldap_encrypt, Host, Opts, none),
|
||||
TLSVerify = get_opt(ldap_tls_verify, Host, Opts, false),
|
||||
TLSCertFile = get_opt(ldap_tls_certfile, Host, Opts),
|
||||
TLSCAFile = get_opt(ldap_tls_cacertfile, Host, Opts),
|
||||
TLSDepth = get_opt(ldap_tls_depth, Host, Opts),
|
||||
Port = get_opt(ldap_port, Host, Opts,
|
||||
case Encrypt of
|
||||
tls -> ?LDAPS_PORT;
|
||||
starttls -> ?LDAP_PORT;
|
||||
_ -> ?LDAP_PORT
|
||||
end),
|
||||
RootDN = get_opt(ldap_rootdn, Host, Opts, <<"">>),
|
||||
Password = get_opt(ldap_password, Host, Opts, <<"">>),
|
||||
Base = get_opt(ldap_base, Host, Opts, <<"">>),
|
||||
OldDerefAliases = get_opt(deref_aliases, Host, Opts, unspecified),
|
||||
DerefAliases =
|
||||
if OldDerefAliases == unspecified ->
|
||||
gen_mod:get_opt({ldap_deref_aliases, Host}, Opts,
|
||||
fun(never) -> never;
|
||||
(searching) -> searching;
|
||||
(finding) -> finding;
|
||||
(always) -> always
|
||||
end, never);
|
||||
get_opt(ldap_deref_aliases, Host, Opts, never);
|
||||
true ->
|
||||
?WARNING_MSG("Option 'deref_aliases' is deprecated. "
|
||||
"The option is still supported "
|
||||
@@ -231,6 +204,7 @@ get_config(Host, Opts) ->
|
||||
backups = Backups,
|
||||
tls_options = [{encrypt, Encrypt},
|
||||
{tls_verify, TLSVerify},
|
||||
{tls_certfile, TLSCertFile},
|
||||
{tls_cacertfile, TLSCAFile},
|
||||
{tls_depth, TLSDepth}],
|
||||
port = Port,
|
||||
@@ -239,6 +213,15 @@ get_config(Host, Opts) ->
|
||||
base = Base,
|
||||
deref_aliases = DerefAliases}.
|
||||
|
||||
get_opt(Opt, Host, Opts) ->
|
||||
get_opt(Opt, Host, Opts, undefined).
|
||||
|
||||
get_opt(Opt, Host, Opts, Default) ->
|
||||
case proplists:get_value(Opt, Opts) of
|
||||
undefined -> ejabberd_config:get_option({Opt, Host}, Default);
|
||||
Value -> Value
|
||||
end.
|
||||
|
||||
%%----------------------------------------
|
||||
%% Borrowed from asn1rt_ber_bin_v2.erl
|
||||
%%----------------------------------------
|
||||
@@ -346,12 +329,26 @@ collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) ->
|
||||
collect_parts_bit([],Acc,Uacc) ->
|
||||
list_to_binary([Uacc|lists:reverse(Acc)]).
|
||||
|
||||
-type deref_aliases() :: never | searching | finding | always.
|
||||
-type uids() :: binary() | {binary()} | {binary(), binary()}.
|
||||
-spec opt_type(deref_aliases) -> fun((deref_aliases()) -> deref_aliases());
|
||||
(ldap_backups) -> fun(([binary()]) -> [binary()]);
|
||||
(ldap_base) -> fun((binary()) -> binary());
|
||||
(ldap_deref_aliases) -> fun((deref_aliases()) -> deref_aliases());
|
||||
(ldap_encrypt) -> fun((tls | starttls | none) -> tls | starttls | none);
|
||||
(ldap_password) -> fun((binary()) -> binary());
|
||||
(ldap_port) -> fun((0..65535) -> 0..65535);
|
||||
(ldap_rootdn) -> fun((binary()) -> binary());
|
||||
(ldap_servers) -> fun(([binary()]) -> [binary()]);
|
||||
(ldap_tls_certfile) -> fun((binary()) -> string());
|
||||
(ldap_tls_cacertfile) -> fun((binary()) -> string());
|
||||
(ldap_tls_depth) -> fun((non_neg_integer()) -> non_neg_integer());
|
||||
(ldap_tls_verify) -> fun((hard | soft | false) -> hard | soft | false);
|
||||
(ldap_filter) -> fun((binary()) -> binary());
|
||||
(ldap_uids) -> fun((uids()) -> uids());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(deref_aliases) ->
|
||||
fun (never) -> never;
|
||||
(searching) -> searching;
|
||||
(finding) -> finding;
|
||||
(always) -> always
|
||||
end;
|
||||
opt_type(ldap_deref_aliases);
|
||||
opt_type(ldap_backups) ->
|
||||
fun (L) -> [iolist_to_binary(H) || H <- L] end;
|
||||
opt_type(ldap_base) -> fun iolist_to_binary/1;
|
||||
@@ -372,7 +369,12 @@ opt_type(ldap_port) ->
|
||||
opt_type(ldap_rootdn) -> fun iolist_to_binary/1;
|
||||
opt_type(ldap_servers) ->
|
||||
fun (L) -> [iolist_to_binary(H) || H <- L] end;
|
||||
opt_type(ldap_tls_cacertfile) -> fun iolist_to_binary/1;
|
||||
opt_type(ldap_tls_certfile) ->
|
||||
fun(S) ->
|
||||
binary_to_list(ejabberd_pkix:try_certfile(S))
|
||||
end;
|
||||
opt_type(ldap_tls_cacertfile) ->
|
||||
fun(S) -> binary_to_list(misc:try_read_file(S)) end;
|
||||
opt_type(ldap_tls_depth) ->
|
||||
fun (I) when is_integer(I), I >= 0 -> I end;
|
||||
opt_type(ldap_tls_verify) ->
|
||||
@@ -380,8 +382,20 @@ opt_type(ldap_tls_verify) ->
|
||||
(soft) -> soft;
|
||||
(false) -> false
|
||||
end;
|
||||
opt_type(ldap_filter) ->
|
||||
fun check_filter/1;
|
||||
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(_) ->
|
||||
[deref_aliases, ldap_backups, ldap_base,
|
||||
[deref_aliases, ldap_backups, ldap_base, ldap_uids,
|
||||
ldap_deref_aliases, ldap_encrypt, ldap_password,
|
||||
ldap_port, ldap_rootdn, ldap_servers,
|
||||
ldap_tls_cacertfile, ldap_tls_depth, ldap_tls_verify].
|
||||
ldap_port, ldap_rootdn, ldap_servers, ldap_filter,
|
||||
ldap_tls_certfile, ldap_tls_cacertfile, ldap_tls_depth,
|
||||
ldap_tls_verify].
|
||||
|
||||
+56
-30
@@ -80,16 +80,18 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
get_commands_spec() ->
|
||||
[#ejabberd_commands{name = modules_update_specs,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Update the module source code from Git",
|
||||
longdesc = "A connection to Internet is required",
|
||||
module = ?MODULE, function = update,
|
||||
args = [],
|
||||
result = {res, integer}},
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = modules_available,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "List the contributed modules available to install",
|
||||
module = ?MODULE, function = available_command,
|
||||
result_desc = "List of tuples with module name and description",
|
||||
result_example = [{mod_cron, "Execute scheduled commands"},
|
||||
{mod_rest, "ReST frontend"}],
|
||||
args = [],
|
||||
result = {modules, {list,
|
||||
{module, {tuple,
|
||||
@@ -97,9 +99,11 @@ get_commands_spec() ->
|
||||
{summary, string}]}}}}},
|
||||
#ejabberd_commands{name = modules_installed,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "List the contributed modules already installed",
|
||||
module = ?MODULE, function = installed_command,
|
||||
result_desc = "List of tuples with module name and description",
|
||||
result_example = [{mod_cron, "Execute scheduled commands"},
|
||||
{mod_rest, "ReST frontend"}],
|
||||
args = [],
|
||||
result = {modules, {list,
|
||||
{module, {tuple,
|
||||
@@ -107,46 +111,64 @@ get_commands_spec() ->
|
||||
{summary, string}]}}}}},
|
||||
#ejabberd_commands{name = module_install,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Compile, install and start an available contributed module",
|
||||
module = ?MODULE, function = install,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, integer}},
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = module_uninstall,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Uninstall a contributed module",
|
||||
module = ?MODULE, function = uninstall,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, integer}},
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = module_upgrade,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Upgrade the running code of an installed module",
|
||||
longdesc = "In practice, this uninstalls and installs the module",
|
||||
module = ?MODULE, function = upgrade,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, integer}},
|
||||
result = {res, rescode}},
|
||||
#ejabberd_commands{name = module_check,
|
||||
tags = [admin,modules],
|
||||
desc = "",
|
||||
longdesc = "",
|
||||
desc = "Check the contributed module repository compliance",
|
||||
module = ?MODULE, function = check,
|
||||
args_desc = ["Module name"],
|
||||
args_example = [<<"mod_rest">>],
|
||||
args = [{module, binary}],
|
||||
result = {res, integer}}
|
||||
result = {res, rescode}}
|
||||
].
|
||||
%% -- public modules functions
|
||||
|
||||
update() ->
|
||||
add_sources(?REPOS),
|
||||
Contrib = maps:put(?REPOS, [], maps:new()),
|
||||
Jungles = lists:foldl(fun({Package, Spec}, Acc) ->
|
||||
Repo = proplists:get_value(url, Spec, ""),
|
||||
Mods = maps:get(Repo, Acc, []),
|
||||
maps:put(Repo, [Package|Mods], Acc)
|
||||
end, Contrib, modules_spec(sources_dir(), "*/*")),
|
||||
Repos = maps:fold(fun(Repo, _Mods, Acc) ->
|
||||
Update = add_sources(Repo),
|
||||
?INFO_MSG("Update packages from repo ~s: ~p", [Repo, Update]),
|
||||
case Update of
|
||||
ok -> Acc;
|
||||
Error -> [{repository, Repo, Error}|Acc]
|
||||
end
|
||||
end, [], Jungles),
|
||||
Res = lists:foldl(fun({Package, Spec}, Acc) ->
|
||||
Path = proplists:get_value(url, Spec, ""),
|
||||
Update = add_sources(Package, Path),
|
||||
Repo = proplists:get_value(url, Spec, ""),
|
||||
Update = add_sources(Package, Repo),
|
||||
?INFO_MSG("Update package ~s: ~p", [Package, Update]),
|
||||
case Update of
|
||||
ok -> Acc;
|
||||
Error -> [Error|Acc]
|
||||
Error -> [{Package, Repo, Error}|Acc]
|
||||
end
|
||||
end, [], modules_spec(sources_dir(), "*")),
|
||||
end, Repos, modules_spec(sources_dir(), "*")),
|
||||
case Res of
|
||||
[] -> ok;
|
||||
[Error|_] -> Error
|
||||
@@ -439,11 +461,7 @@ short_spec({Module, Attrs}) when is_atom(Module), is_list(Attrs) ->
|
||||
{Module, proplists:get_value(summary, Attrs, "")}.
|
||||
|
||||
is_contrib_allowed() ->
|
||||
ejabberd_config:get_option(allow_contrib_modules,
|
||||
fun(false) -> false;
|
||||
(no) -> false;
|
||||
(_) -> true
|
||||
end, true).
|
||||
ejabberd_config:get_option(allow_contrib_modules, true).
|
||||
|
||||
%% -- build functions
|
||||
|
||||
@@ -542,7 +560,9 @@ compile_result(Results) ->
|
||||
compile_options() ->
|
||||
[verbose, report_errors, report_warnings]
|
||||
++ [{i, filename:join(app_dir(App), "include")}
|
||||
|| App <- [fast_xml, xmpp, p1_utils, ejabberd]].
|
||||
|| App <- [fast_xml, xmpp, p1_utils, ejabberd]]
|
||||
++ [{i, filename:join(mod_dir(Mod), "include")}
|
||||
|| Mod <- installed()].
|
||||
|
||||
app_dir(App) ->
|
||||
case code:lib_dir(App) of
|
||||
@@ -557,6 +577,10 @@ app_dir(App) ->
|
||||
Dir
|
||||
end.
|
||||
|
||||
mod_dir({Package, Spec}) ->
|
||||
Default = filename:join(modules_dir(), Package),
|
||||
proplists:get_value(path, Spec, Default).
|
||||
|
||||
compile_erlang_file(Dest, File) ->
|
||||
compile_erlang_file(Dest, File, compile_options()).
|
||||
|
||||
@@ -656,6 +680,8 @@ format({Key, Val}) when is_binary(Val) ->
|
||||
format({Key, Val}) -> % TODO: improve Yaml parsing
|
||||
{Key, Val}.
|
||||
|
||||
-spec opt_type(allow_contrib_modules) -> fun((boolean()) -> boolean());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(allow_contrib_modules) ->
|
||||
fun (false) -> false;
|
||||
(no) -> false;
|
||||
|
||||
+6
-8
@@ -31,7 +31,7 @@
|
||||
|
||||
-export([start/2, stop/1, init/2, check_password/3,
|
||||
set_password/3, try_register/3, remove_user/2,
|
||||
remove_user/3, is_user_exists/2, opt_type/1]).
|
||||
remove_user/3, user_exists/2, opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -73,7 +73,7 @@ get_process_name(Host, Integer) ->
|
||||
check_password(User, Server, Password) ->
|
||||
call_port(Server, [<<"auth">>, User, Server, Password]).
|
||||
|
||||
is_user_exists(User, Server) ->
|
||||
user_exists(User, Server) ->
|
||||
call_port(Server, [<<"isuser">>, User, Server]).
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
@@ -83,7 +83,7 @@ try_register(User, Server, Password) ->
|
||||
case call_port(Server,
|
||||
[<<"tryregister">>, User, Server, Password])
|
||||
of
|
||||
true -> {atomic, ok};
|
||||
true -> ok;
|
||||
false -> {error, not_allowed}
|
||||
end.
|
||||
|
||||
@@ -105,11 +105,7 @@ random_instance(MaxNum) ->
|
||||
randoms:uniform(MaxNum) - 1.
|
||||
|
||||
get_instances(Server) ->
|
||||
ejabberd_config:get_option(
|
||||
{extauth_instances, Server},
|
||||
fun(V) when is_integer(V), V > 0 ->
|
||||
V
|
||||
end, 1).
|
||||
ejabberd_config:get_option({extauth_instances, Server}, 1).
|
||||
|
||||
loop(Port, Timeout, ProcessName, ExtPrg) ->
|
||||
receive
|
||||
@@ -158,6 +154,8 @@ encode(L) -> str:join(L, <<":">>).
|
||||
decode([0, 0]) -> false;
|
||||
decode([0, 1]) -> true.
|
||||
|
||||
-spec opt_type(extauth_instances) -> fun((pos_integer()) -> pos_integer());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(extauth_instances) ->
|
||||
fun (V) when is_integer(V), V > 0 -> V end;
|
||||
opt_type(_) -> [extauth_instances].
|
||||
|
||||
+11
-1
@@ -31,11 +31,13 @@
|
||||
-define(GEN_SERVER, gen_server).
|
||||
-endif.
|
||||
-behaviour(?GEN_SERVER).
|
||||
-behaviour(ejabberd_config).
|
||||
|
||||
%% API
|
||||
-export([start_link/3, add_iq_handler/6,
|
||||
remove_iq_handler/3, stop_iq_handler/3, handle/5,
|
||||
process_iq/4, check_type/1, transform_module_options/1]).
|
||||
process_iq/4, check_type/1, transform_module_options/1,
|
||||
opt_type/1, iqdisc/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
@@ -183,6 +185,9 @@ check_type(one_queue) -> one_queue;
|
||||
check_type(N) when is_integer(N), N>0 -> N;
|
||||
check_type(parallel) -> parallel.
|
||||
|
||||
iqdisc(Host) ->
|
||||
ejabberd_config:get_option({iqdisc, Host}, no_queue).
|
||||
|
||||
-spec transform_module_options([{atom(), any()}]) -> [{atom(), any()}].
|
||||
|
||||
transform_module_options(Opts) ->
|
||||
@@ -193,6 +198,11 @@ transform_module_options(Opts) ->
|
||||
Opt
|
||||
end, Opts).
|
||||
|
||||
-spec opt_type(iqdisc) -> fun((type()) -> type());
|
||||
(atom()) -> [atom()].
|
||||
opt_type(iqdisc) -> fun check_type/1;
|
||||
opt_type(_) -> [iqdisc].
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
+202
-158
@@ -33,16 +33,19 @@
|
||||
-export([init/1, start_link/0, start_child/3, start_child/4,
|
||||
stop_child/1, stop_child/2, config_reloaded/0]).
|
||||
-export([start_module/2, start_module/3,
|
||||
stop_module/2, stop_module_keep_config/2, get_opt/3,
|
||||
get_opt/4, get_opt_host/3, opt_type/1, is_equal_opt/5,
|
||||
get_module_opt/4, get_module_opt/5, get_module_opt_host/3,
|
||||
stop_module/2, stop_module_keep_config/2,
|
||||
get_opt/2, get_opt/3, get_opt_host/3,
|
||||
get_opt_hosts/3, opt_type/1, is_equal_opt/4,
|
||||
get_module_opt/3, get_module_opt/4, get_module_opt_host/3,
|
||||
loaded_modules/1, loaded_modules_with_opts/1,
|
||||
get_hosts/2, get_module_proc/2, is_loaded/2, is_loaded_elsewhere/2,
|
||||
start_modules/0, start_modules/1, stop_modules/0, stop_modules/1,
|
||||
db_mod/2, db_mod/3, ram_db_mod/2, ram_db_mod/3,
|
||||
db_type/2, db_type/3, ram_db_type/2, ram_db_type/3]).
|
||||
|
||||
%%-export([behaviour_info/1]).
|
||||
%% Deprecated functions
|
||||
-export([get_opt/4, get_module_opt/5]).
|
||||
-deprecated([{get_opt, 4}, {get_module_opt, 5}]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -73,7 +76,7 @@
|
||||
start_link() ->
|
||||
case supervisor:start_link({local, ejabberd_gen_mod_sup}, ?MODULE, []) of
|
||||
{ok, Pid} ->
|
||||
gen_mod:start_modules(),
|
||||
start_modules(),
|
||||
{ok, Pid};
|
||||
Err ->
|
||||
Err
|
||||
@@ -97,7 +100,7 @@ start_child(Mod, Host, Opts) ->
|
||||
start_child(Mod, Host, Opts, Proc) ->
|
||||
Spec = {Proc, {?GEN_SERVER, start_link,
|
||||
[{local, Proc}, Mod, [Host, Opts], []]},
|
||||
transient, 2000, worker, [Mod]},
|
||||
transient, timer:minutes(1), worker, [Mod]},
|
||||
supervisor:start_child(ejabberd_gen_mod_sup, Spec).
|
||||
|
||||
-spec stop_child(module(), binary() | global) -> ok | {error, any()}.
|
||||
@@ -119,14 +122,7 @@ start_modules() ->
|
||||
end, ?MYHOSTS).
|
||||
|
||||
get_modules_options(Host) ->
|
||||
ejabberd_config:get_option(
|
||||
{modules, Host},
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
fun({M, A}) when is_atom(M), is_list(A) ->
|
||||
{M, A}
|
||||
end, Mods)
|
||||
end, []).
|
||||
ejabberd_config:get_option({modules, Host}, []).
|
||||
|
||||
sort_modules(Host, ModOpts) ->
|
||||
G = digraph:new([acyclic]),
|
||||
@@ -164,7 +160,9 @@ sort_modules(Host, ModOpts) ->
|
||||
end
|
||||
end, Deps)
|
||||
end, ModOpts),
|
||||
[digraph:vertex(G, V) || V <- digraph_utils:topsort(G)].
|
||||
Result = [digraph:vertex(G, V) || V <- digraph_utils:topsort(G)],
|
||||
digraph:delete(G),
|
||||
Result.
|
||||
|
||||
-spec start_modules(binary()) -> ok.
|
||||
|
||||
@@ -187,10 +185,17 @@ start_module(Host, Module) ->
|
||||
end.
|
||||
|
||||
-spec start_module(binary(), atom(), opts()) -> ok | {ok, pid()}.
|
||||
start_module(Host, Module, Opts) ->
|
||||
start_module(Host, Module, Opts, true).
|
||||
|
||||
start_module(Host, Module, Opts0) ->
|
||||
-spec start_module(binary(), atom(), opts(), boolean()) -> ok | {ok, pid()}.
|
||||
start_module(Host, Module, Opts0, NeedValidation) ->
|
||||
?DEBUG("loading ~s at ~s", [Module, Host]),
|
||||
Opts = validate_opts(Module, Opts0),
|
||||
Opts = if NeedValidation ->
|
||||
validate_opts(Host, Module, Opts0);
|
||||
true ->
|
||||
Opts0
|
||||
end,
|
||||
store_options(Host, Module, Opts),
|
||||
try case Module:start(Host, Opts) of
|
||||
ok -> ok;
|
||||
@@ -211,8 +216,7 @@ start_module(Host, Module, Opts0) ->
|
||||
|
||||
-spec reload_modules(binary()) -> ok.
|
||||
reload_modules(Host) ->
|
||||
NewMods = ejabberd_config:get_option(
|
||||
{modules, Host}, opt_type(modules), []),
|
||||
NewMods = ejabberd_config:get_option({modules, Host}, []),
|
||||
OldMods = ets:select(
|
||||
ejabberd_modules,
|
||||
ets:fun2ms(
|
||||
@@ -240,19 +244,23 @@ reload_modules(Host) ->
|
||||
lists:foreach(
|
||||
fun({Mod, OldOpts}) ->
|
||||
case lists:keyfind(Mod, 1, NewMods) of
|
||||
{_, NewOpts} when NewOpts /= OldOpts ->
|
||||
reload_module(Host, Mod, NewOpts, OldOpts);
|
||||
{_, NewOpts0} ->
|
||||
case validate_opts(Host, Mod, NewOpts0) of
|
||||
OldOpts ->
|
||||
ok;
|
||||
NewOpts ->
|
||||
reload_module(Host, Mod, NewOpts, OldOpts)
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end
|
||||
end, OldMods).
|
||||
|
||||
-spec reload_module(binary(), module(), opts(), opts()) -> ok | {ok, pid()}.
|
||||
reload_module(Host, Module, NewOpts0, OldOpts) ->
|
||||
reload_module(Host, Module, NewOpts, OldOpts) ->
|
||||
case erlang:function_exported(Module, reload, 3) of
|
||||
true ->
|
||||
?DEBUG("reloading ~s at ~s", [Module, Host]),
|
||||
NewOpts = validate_opts(Module, NewOpts0),
|
||||
store_options(Host, Module, NewOpts),
|
||||
try case Module:reload(Host, NewOpts, OldOpts) of
|
||||
ok -> ok;
|
||||
@@ -271,7 +279,7 @@ reload_module(Host, Module, NewOpts0, OldOpts) ->
|
||||
?WARNING_MSG("module ~s doesn't support reloading "
|
||||
"and will be restarted", [Module]),
|
||||
stop_module(Host, Module),
|
||||
start_module(Host, Module, NewOpts0)
|
||||
start_module(Host, Module, NewOpts, false)
|
||||
end.
|
||||
|
||||
-spec store_options(binary(), module(), opts()) -> true.
|
||||
@@ -311,7 +319,7 @@ stop_modules(Host) ->
|
||||
Modules = get_modules_options(Host),
|
||||
lists:foreach(
|
||||
fun({Module, _Args}) ->
|
||||
gen_mod:stop_module_keep_config(Host, Module)
|
||||
stop_module_keep_config(Host, Module)
|
||||
end, Modules).
|
||||
|
||||
-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
|
||||
@@ -359,40 +367,47 @@ wait_for_stop1(MonitorReference) ->
|
||||
|
||||
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
|
||||
|
||||
-spec get_opt(atom() | {atom(), binary()|global}, opts(), check_fun()) -> any().
|
||||
-spec get_opt(atom() | {atom(), binary() | global}, opts()) -> any().
|
||||
get_opt(Opt, Opts) ->
|
||||
get_opt(Opt, Opts, undefined).
|
||||
|
||||
get_opt(Opt, Opts, F) ->
|
||||
get_opt(Opt, Opts, F, undefined).
|
||||
-spec get_opt(atom() | {atom(), binary()|global}, opts(), check_fun() | any()) -> any().
|
||||
|
||||
-spec get_opt(atom() | {atom(), binary()|global}, opts(), check_fun(), any()) -> any().
|
||||
|
||||
get_opt({Opt, Host}, Opts, F, Default) ->
|
||||
case lists:keysearch(Opt, 1, Opts) of
|
||||
get_opt(Opt, Opts, F) when is_function(F) ->
|
||||
get_opt(Opt, Opts, undefined);
|
||||
get_opt({Opt, Host}, Opts, Default) ->
|
||||
case lists:keyfind(Opt, 1, Opts) of
|
||||
false ->
|
||||
ejabberd_config:get_option({Opt, Host}, F, Default);
|
||||
{value, {_, Val}} ->
|
||||
ejabberd_config:prepare_opt_val(Opt, Val, F, Default)
|
||||
ejabberd_config:get_option({Opt, Host}, Default);
|
||||
{_, Val} ->
|
||||
Val
|
||||
end;
|
||||
get_opt(Opt, Opts, F, Default) ->
|
||||
case lists:keysearch(Opt, 1, Opts) of
|
||||
get_opt(Opt, Opts, Default) ->
|
||||
case lists:keyfind(Opt, 1, Opts) of
|
||||
false ->
|
||||
Default;
|
||||
{value, {_, Val}} ->
|
||||
ejabberd_config:prepare_opt_val(Opt, Val, F, Default)
|
||||
{_, Val} ->
|
||||
Val
|
||||
end.
|
||||
|
||||
-spec get_module_opt(global | binary(), atom(), atom(), check_fun()) -> any().
|
||||
-spec get_opt(atom() | {atom(), binary()}, opts(), check_fun(), any()) -> any().
|
||||
get_opt(Opt, Opts, _, Default) ->
|
||||
get_opt(Opt, Opts, Default).
|
||||
|
||||
get_module_opt(Host, Module, Opt, F) ->
|
||||
get_module_opt(Host, Module, Opt, F, undefined).
|
||||
-spec get_module_opt(global | binary(), atom(), atom()) -> any().
|
||||
|
||||
-spec get_module_opt(global | binary(), atom(), atom(), check_fun(), any()) -> any().
|
||||
get_module_opt(Host, Module, Opt) ->
|
||||
get_module_opt(Host, Module, Opt, undefined).
|
||||
|
||||
get_module_opt(global, Module, Opt, F, Default) ->
|
||||
-spec get_module_opt(global | binary(), atom(), atom(), any()) -> any().
|
||||
|
||||
get_module_opt(Host, Module, Opt, F) when is_function(F) ->
|
||||
get_module_opt(Host, Module, Opt, undefined);
|
||||
get_module_opt(global, Module, Opt, Default) ->
|
||||
Hosts = (?MYHOSTS),
|
||||
[Value | Values] = lists:map(fun (Host) ->
|
||||
get_module_opt(Host, Module, Opt,
|
||||
F, Default)
|
||||
Default)
|
||||
end,
|
||||
Hosts),
|
||||
Same_all = lists:all(fun (Other_value) ->
|
||||
@@ -403,102 +418,143 @@ get_module_opt(global, Module, Opt, F, Default) ->
|
||||
true -> Value;
|
||||
false -> Default
|
||||
end;
|
||||
get_module_opt(Host, Module, Opt, F, Default) ->
|
||||
get_module_opt(Host, Module, Opt, Default) ->
|
||||
OptsList = ets:lookup(ejabberd_modules, {Module, Host}),
|
||||
case OptsList of
|
||||
[] -> Default;
|
||||
[#ejabberd_module{opts = Opts} | _] ->
|
||||
get_opt(Opt, Opts, F, Default)
|
||||
get_opt(Opt, Opts, Default)
|
||||
end.
|
||||
|
||||
-spec get_module_opt(global | binary(), atom(), atom(), check_fun(), any()) -> any().
|
||||
get_module_opt(Host, Module, Opt, _, Default) ->
|
||||
get_module_opt(Host, Module, Opt, Default).
|
||||
|
||||
-spec get_module_opt_host(global | binary(), atom(), binary()) -> binary().
|
||||
|
||||
get_module_opt_host(Host, Module, Default) ->
|
||||
Val = get_module_opt(Host, Module, host,
|
||||
fun iolist_to_binary/1,
|
||||
Default),
|
||||
Val = get_module_opt(Host, Module, host, Default),
|
||||
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
|
||||
|
||||
-spec get_opt_host(binary(), opts(), binary()) -> binary().
|
||||
|
||||
get_opt_host(Host, Opts, Default) ->
|
||||
Val = get_opt(host, Opts, fun iolist_to_binary/1, Default),
|
||||
Val = get_opt(host, Opts, Default),
|
||||
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
|
||||
|
||||
-spec get_opt_hosts(binary(), opts(), binary()) -> [binary()].
|
||||
|
||||
get_module_mod_opt_type_fun(Module) ->
|
||||
DBSubMods = ejabberd_config:v_dbs_mods(Module),
|
||||
fun(Opt) ->
|
||||
Res = lists:foldl(fun(Mod, {Funs, ArgsList, _} = Acc) ->
|
||||
case catch Mod:mod_opt_type(Opt) of
|
||||
Fun when is_function(Fun) ->
|
||||
{[Fun | Funs], ArgsList, true};
|
||||
L when is_list(L) ->
|
||||
{Funs, L ++ ArgsList, true};
|
||||
_ ->
|
||||
Acc
|
||||
end
|
||||
end, {[], [], false}, [Module | DBSubMods]),
|
||||
case Res of
|
||||
{[], [], false} ->
|
||||
throw({'EXIT', {undef, mod_opt_type}});
|
||||
{[], Args, _} -> Args;
|
||||
{Funs, _, _} ->
|
||||
fun(Val) ->
|
||||
lists:any(fun(F) ->
|
||||
try F(Val) of
|
||||
_ ->
|
||||
true
|
||||
catch {replace_with, _NewVal} = E ->
|
||||
throw(E);
|
||||
{invalid_syntax, _Error} = E2 ->
|
||||
throw(E2);
|
||||
_:_ ->
|
||||
false
|
||||
end
|
||||
end, Funs)
|
||||
end
|
||||
end
|
||||
get_opt_hosts(Host, Opts, Default) ->
|
||||
Vals = case get_opt(host, Opts, undefined) of
|
||||
undefined ->
|
||||
case get_opt(hosts, Opts, []) of
|
||||
[] -> [Default];
|
||||
L -> L
|
||||
end;
|
||||
Val ->
|
||||
[Val]
|
||||
end,
|
||||
[ejabberd_regexp:greplace(V, <<"@HOST@">>, Host) || V <- Vals].
|
||||
|
||||
-spec get_validators(binary(), module(), opts()) -> dict:dict() | undef.
|
||||
get_validators(Host, Module, Opts) ->
|
||||
try Module:mod_opt_type('') of
|
||||
L ->
|
||||
SubMods1 = case lists:member(db_type, L) of
|
||||
true -> [db_mod(Host, Opts, Module)];
|
||||
false -> []
|
||||
end,
|
||||
SubMods2 = case lists:member(ram_db_type, L) of
|
||||
true -> [ram_db_mod(Host, Opts, Module)];
|
||||
false -> []
|
||||
end,
|
||||
lists:foldl(
|
||||
fun(Mod, D) ->
|
||||
try Mod:mod_opt_type('') of
|
||||
Os ->
|
||||
lists:foldl(
|
||||
fun({Opt, SubOpt} = O, Acc) ->
|
||||
SubF = Mod:mod_opt_type(O),
|
||||
F = case Mod:mod_opt_type(Opt) of
|
||||
F1 when is_function(F1) ->
|
||||
F1;
|
||||
_ ->
|
||||
fun(X) -> X end
|
||||
end,
|
||||
dict:append_list(
|
||||
Opt, [F, {SubOpt, [SubF]}], Acc);
|
||||
(O, Acc) ->
|
||||
F = Mod:mod_opt_type(O),
|
||||
dict:store(O, [F], Acc)
|
||||
end, D, Os)
|
||||
catch _:undef ->
|
||||
D
|
||||
end
|
||||
end, dict:new(), [Module|SubMods1 ++ SubMods2])
|
||||
catch _:undef ->
|
||||
?WARNING_MSG("module '~s' doesn't export mod_opt_type/1",
|
||||
[Module]),
|
||||
undef
|
||||
end.
|
||||
|
||||
validate_opts(Module, Opts) ->
|
||||
ModOptFun = get_module_mod_opt_type_fun(Module),
|
||||
lists:filtermap(
|
||||
fun({Opt, Val}) ->
|
||||
case catch ModOptFun(Opt) of
|
||||
VFun when is_function(VFun) ->
|
||||
try VFun(Val) of
|
||||
_ ->
|
||||
true
|
||||
catch {replace_with, NewVal} ->
|
||||
{true, {Opt, NewVal}};
|
||||
{invalid_syntax, Error} ->
|
||||
?ERROR_MSG("ignoring invalid value '~p' for "
|
||||
"option '~s' of module '~s': ~s",
|
||||
[Val, Opt, Module, Error]),
|
||||
false;
|
||||
_:_ ->
|
||||
?ERROR_MSG("ignoring invalid value '~p' for "
|
||||
"option '~s' of module '~s'",
|
||||
[Val, Opt, Module]),
|
||||
false
|
||||
-spec validate_opts(binary(), module(), opts()) -> opts().
|
||||
validate_opts(Host, Module, Opts) ->
|
||||
case get_validators(Host, Module, Opts) of
|
||||
undef ->
|
||||
Opts;
|
||||
Validators ->
|
||||
validate_opts(Host, Module, Opts, dict:to_list(Validators))
|
||||
end.
|
||||
|
||||
validate_opts(Host, Module, Opts, Validators) when is_list(Opts) ->
|
||||
lists:flatmap(
|
||||
fun({Opt, Val}) when is_atom(Opt) ->
|
||||
case lists:keyfind(Opt, 1, Validators) of
|
||||
{_, L} ->
|
||||
case lists:partition(fun is_function/1, L) of
|
||||
{[VFun|_], []} ->
|
||||
validate_opt(Module, Opt, Val, VFun);
|
||||
{[VFun|_], SubValidators} ->
|
||||
try validate_opts(Host, Module, Val, SubValidators) of
|
||||
SubOpts ->
|
||||
validate_opt(Module, Opt, SubOpts, VFun)
|
||||
catch _:bad_option ->
|
||||
?ERROR_MSG("ignoring invalid value '~p' for "
|
||||
"option '~s' of module '~s'",
|
||||
[Val, Opt, Module]),
|
||||
[]
|
||||
end
|
||||
end;
|
||||
L when is_list(L) ->
|
||||
SOpts = str:join([[$', atom_to_list(A), $'] || A <- L], <<", ">>),
|
||||
false ->
|
||||
?ERROR_MSG("unknown option '~s' for module '~s' will be"
|
||||
" likely ignored, available options are: ~s",
|
||||
[Opt, Module, SOpts]),
|
||||
true;
|
||||
{'EXIT', {undef, _}} ->
|
||||
?WARNING_MSG("module '~s' doesn't export mod_opt_type/1",
|
||||
[Module]),
|
||||
true
|
||||
[Opt, Module,
|
||||
misc:join_atoms([K || {K, _} <- Validators],
|
||||
<<", ">>)]),
|
||||
[{Opt, Val}]
|
||||
end;
|
||||
(Junk) ->
|
||||
?ERROR_MSG("failed to understand option ~p for module '~s'",
|
||||
[Junk, Module]),
|
||||
false
|
||||
end, Opts).
|
||||
(_) ->
|
||||
erlang:error(bad_option)
|
||||
end, Opts);
|
||||
validate_opts(_, _, _, _) ->
|
||||
erlang:error(bad_option).
|
||||
|
||||
-spec validate_opt(module(), atom(), any(),
|
||||
[{atom(), check_fun(), any()}]) -> [{atom(), any()}].
|
||||
validate_opt(Module, Opt, Val, VFun) ->
|
||||
try VFun(Val) of
|
||||
NewVal -> [{Opt, NewVal}]
|
||||
catch {invalid_syntax, Error} ->
|
||||
?ERROR_MSG("ignoring invalid value '~p' for "
|
||||
"option '~s' of module '~s': ~s",
|
||||
[Val, Opt, Module, Error]),
|
||||
[];
|
||||
_:_ ->
|
||||
?ERROR_MSG("ignoring invalid value '~p' for "
|
||||
"option '~s' of module '~s'",
|
||||
[Val, Opt, Module]),
|
||||
[]
|
||||
end.
|
||||
|
||||
-spec db_type(binary() | global, module()) -> db_type();
|
||||
(opts(), module()) -> db_type().
|
||||
@@ -506,27 +562,21 @@ validate_opts(Module, Opts) ->
|
||||
db_type(Opts, Module) when is_list(Opts) ->
|
||||
db_type(global, Opts, Module);
|
||||
db_type(Host, Module) when is_atom(Module) ->
|
||||
case catch Module:mod_opt_type(db_type) of
|
||||
F when is_function(F) ->
|
||||
case get_module_opt(Host, Module, db_type, F) of
|
||||
undefined -> ejabberd_config:default_db(Host, Module);
|
||||
Type -> Type
|
||||
end;
|
||||
_ ->
|
||||
undefined
|
||||
case get_module_opt(Host, Module, db_type) of
|
||||
undefined ->
|
||||
ejabberd_config:default_db(Host, Module);
|
||||
Type ->
|
||||
Type
|
||||
end.
|
||||
|
||||
-spec db_type(binary() | global, opts(), module()) -> db_type().
|
||||
|
||||
db_type(Host, Opts, Module) ->
|
||||
case catch Module:mod_opt_type(db_type) of
|
||||
F when is_function(F) ->
|
||||
case get_opt(db_type, Opts, F) of
|
||||
undefined -> ejabberd_config:default_db(Host, Module);
|
||||
Type -> Type
|
||||
end;
|
||||
_ ->
|
||||
undefined
|
||||
case get_opt(db_type, Opts) of
|
||||
undefined ->
|
||||
ejabberd_config:default_db(Host, Module);
|
||||
Type ->
|
||||
Type
|
||||
end.
|
||||
|
||||
-spec db_mod(binary() | global | db_type(), module()) -> module().
|
||||
@@ -546,26 +596,20 @@ db_mod(Host, Opts, Module) when is_list(Opts) ->
|
||||
ram_db_type(Opts, Module) when is_list(Opts) ->
|
||||
ram_db_type(global, Opts, Module);
|
||||
ram_db_type(Host, Module) when is_atom(Module) ->
|
||||
case catch Module:mod_opt_type(ram_db_type) of
|
||||
F when is_function(F) ->
|
||||
case get_module_opt(Host, Module, ram_db_type, F) of
|
||||
undefined -> ejabberd_config:default_ram_db(Host, Module);
|
||||
Type -> Type
|
||||
end;
|
||||
_ ->
|
||||
undefined
|
||||
case get_module_opt(Host, Module, ram_db_type) of
|
||||
undefined ->
|
||||
ejabberd_config:default_ram_db(Host, Module);
|
||||
Type ->
|
||||
Type
|
||||
end.
|
||||
|
||||
-spec ram_db_type(binary() | global, opts(), module()) -> db_type().
|
||||
ram_db_type(Host, Opts, Module) ->
|
||||
case catch Module:mod_opt_type(ram_db_type) of
|
||||
F when is_function(F) ->
|
||||
case get_opt(ram_db_type, Opts, F) of
|
||||
undefined -> ejabberd_config:default_ram_db(Host, Module);
|
||||
Type -> Type
|
||||
end;
|
||||
_ ->
|
||||
undefined
|
||||
case get_opt(ram_db_type, Opts) of
|
||||
undefined ->
|
||||
ejabberd_config:default_ram_db(Host, Module);
|
||||
Type ->
|
||||
Type
|
||||
end.
|
||||
|
||||
-spec ram_db_mod(binary() | global | db_type(), module()) -> module().
|
||||
@@ -596,11 +640,9 @@ loaded_modules_with_opts(Host) ->
|
||||
-spec get_hosts(opts(), binary()) -> [binary()].
|
||||
|
||||
get_hosts(Opts, Prefix) ->
|
||||
case get_opt(hosts, Opts,
|
||||
fun(Hs) -> [iolist_to_binary(H) || H <- Hs] end) of
|
||||
case get_opt(hosts, Opts) of
|
||||
undefined ->
|
||||
case get_opt(host, Opts,
|
||||
fun iolist_to_binary/1) of
|
||||
case get_opt(host, Opts) of
|
||||
undefined ->
|
||||
[<<Prefix/binary, Host/binary>> || Host <- ?MYHOSTS];
|
||||
Host ->
|
||||
@@ -639,17 +681,19 @@ config_reloaded() ->
|
||||
reload_modules(Host)
|
||||
end, ?MYHOSTS).
|
||||
|
||||
-spec is_equal_opt(atom(), opts(), opts(), check_fun(), any()) ->
|
||||
-spec is_equal_opt(atom(), opts(), opts(), any()) ->
|
||||
true | {false, any(), any()}.
|
||||
is_equal_opt(Opt, NewOpts, OldOpts, VFun, Default) ->
|
||||
NewVal = get_opt(Opt, NewOpts, VFun, Default),
|
||||
OldVal = get_opt(Opt, OldOpts, VFun, Default),
|
||||
is_equal_opt(Opt, NewOpts, OldOpts, Default) ->
|
||||
NewVal = get_opt(Opt, NewOpts, Default),
|
||||
OldVal = get_opt(Opt, OldOpts, Default),
|
||||
if NewVal /= OldVal ->
|
||||
{false, NewVal, OldVal};
|
||||
true ->
|
||||
true
|
||||
end.
|
||||
|
||||
-spec opt_type(modules) -> fun(([{atom(), list()}]) -> [{atom(), list()}]);
|
||||
(atom()) -> [atom()].
|
||||
opt_type(modules) ->
|
||||
fun(Mods) ->
|
||||
lists:map(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user