Compare commits
343 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5935b4e104 | |||
| d1f09a29b9 | |||
| df88d9f2e5 | |||
| 466278fde1 | |||
| 0a19dac4fd | |||
| 7e6d310fe4 | |||
| ffe3ea8917 | |||
| 455039ae69 | |||
| a78a0a65fe | |||
| ba8f38e2eb | |||
| 9899935e42 | |||
| 865509757c | |||
| 2cb16bc509 | |||
| 00dfcc1e10 | |||
| 4163626844 | |||
| f60c721f84 | |||
| e97e56d776 | |||
| 6b916e7a04 | |||
| 6279c3fd8d | |||
| 6900a41e7d | |||
| a456482e2f | |||
| 30687c40ef | |||
| 16311b73c8 | |||
| b85357d280 | |||
| 946b64e166 | |||
| 46d035c142 | |||
| 982215d644 | |||
| 5afa1f6ade | |||
| c566b1d01e | |||
| 84c227e6ae | |||
| ab12270837 | |||
| 3b96525550 | |||
| 62ccf1cf0e | |||
| d5ecd32cec | |||
| e770d3174d | |||
| 2446b66016 | |||
| f69d1ca282 | |||
| 830fdccd21 | |||
| 5cc30c3977 | |||
| 8efae1f05b | |||
| de3e1c3508 | |||
| 8184326eb9 | |||
| f47a59de2f | |||
| ee0ecd2419 | |||
| 7138cc5633 | |||
| f95f22aea0 | |||
| 25e5253f33 | |||
| 41dc1efde4 | |||
| 1d2efcc168 | |||
| dfb21e802e | |||
| 9a0b951855 | |||
| 7819986ec0 | |||
| 295681283a | |||
| 5b0d8b7776 | |||
| 1d2ef85b33 | |||
| b550f247e7 | |||
| 565f064b15 | |||
| 7db4587eeb | |||
| fad0d867fc | |||
| 56dab7ddbe | |||
| 74b67fa0dc | |||
| 067958d705 | |||
| dec1e1f67f | |||
| 76b9098a25 | |||
| 2399aba67d | |||
| 94cdcd7b34 | |||
| bf33f74ef8 | |||
| 8cf43cf750 | |||
| 2d748115ee | |||
| 0b22277b11 | |||
| c7d9b46b6f | |||
| d2edcf1288 | |||
| 160c9d7698 | |||
| ecd35f7ba8 | |||
| 0c24e18b5e | |||
| 96d6aacede | |||
| adaa067333 | |||
| 724a31fa13 | |||
| 1ccc0d8bcb | |||
| 3f3f64c217 | |||
| 97fa57c360 | |||
| 7bdc1151b1 | |||
| 4bbf16b21a | |||
| d87ca9fb7b | |||
| 7b3209cc7f | |||
| 1d782db84f | |||
| e109f352e3 | |||
| 6e63ee480e | |||
| 90fb19797d | |||
| 415936146b | |||
| 277e1dc3ff | |||
| 56175fef1b | |||
| ef89497d3f | |||
| 7aec0337e1 | |||
| e49cf604e9 | |||
| 61c8836740 | |||
| 57dec40007 | |||
| 29a841d8c7 | |||
| c18413c52b | |||
| 0a9212583d | |||
| 19446967fa | |||
| 8d9a9228d9 | |||
| 72fd353988 | |||
| c90786527e | |||
| 1a320baad8 | |||
| b8c98232b8 | |||
| f723c00762 | |||
| 4d59f677a9 | |||
| 7a48e30523 | |||
| f0887e45b8 | |||
| 2ca563e328 | |||
| 2e169167d4 | |||
| 11b2921971 | |||
| 646b445515 | |||
| 50d7046517 | |||
| c3eaa29f70 | |||
| ac2ba399a9 | |||
| fda73c3d16 | |||
| a1ce33ebf8 | |||
| 9be9949dab | |||
| 0f1d95a074 | |||
| 2430e6691b | |||
| bfd028beea | |||
| 2cb0f92fe6 | |||
| 2ae7d0a122 | |||
| f1ad6f017b | |||
| c658984531 | |||
| 191eeed7c9 | |||
| 01a3c1c2e1 | |||
| 8e3a49d369 | |||
| c48b7f272b | |||
| 4a9417c501 | |||
| 72049e5323 | |||
| 33e0bf1c19 | |||
| 5ed7f10153 | |||
| 2802b6cee2 | |||
| 44828c54fe | |||
| ae0d31a8c9 | |||
| 7274dafe10 | |||
| bc2e26fecd | |||
| 2d4c39cd54 | |||
| 9484b11383 | |||
| 848e1497d1 | |||
| 2daf95e93f | |||
| 1b1d9b5a73 | |||
| 5836eb5bc2 | |||
| 5c88f6423a | |||
| 56d61c2784 | |||
| 0917209711 | |||
| 8c22b154c9 | |||
| 436f0832c1 | |||
| 5d0de39127 | |||
| 92f89e3d45 | |||
| f91caf7108 | |||
| 38c016a041 | |||
| 4f63cb21c2 | |||
| 2e70c59471 | |||
| f00725dffb | |||
| 4205108f30 | |||
| 651de2ca8e | |||
| e79290fb56 | |||
| db3c469d4d | |||
| 7d93463d31 | |||
| 5d79dff4f3 | |||
| 58fd56e6a2 | |||
| f1e6365ee1 | |||
| 4a02df8b6d | |||
| bee9ffd91e | |||
| 3e232952ea | |||
| c0001184fd | |||
| abeaac1c11 | |||
| 6427d9398a | |||
| 677b358a9a | |||
| b997c4325a | |||
| 9c279f2e06 | |||
| 46f01b962a | |||
| 9db39a5e4c | |||
| 43000d9ce4 | |||
| 33368b7e5c | |||
| a087af7060 | |||
| 3d3a4f7543 | |||
| 9ff3ce8bd1 | |||
| 4efca05149 | |||
| f19e19e2b6 | |||
| 25676b43ed | |||
| 870d822f08 | |||
| 320abee110 | |||
| 0579fc80ec | |||
| 08ff969896 | |||
| 40ef406ec7 | |||
| 790201afc0 | |||
| f2003943db | |||
| 014d61955c | |||
| c068712373 | |||
| 467ccdffbd | |||
| 48d7ec1a92 | |||
| 105b421418 | |||
| 277fe5ab25 | |||
| 744018425b | |||
| 1f4e0c8aea | |||
| e0c9242dcf | |||
| 0456b78d87 | |||
| 568068c79f | |||
| b5c4fe6626 | |||
| 64205426bf | |||
| 89025eea39 | |||
| 4a918c5b18 | |||
| 9a7c26eaa8 | |||
| eb803832b7 | |||
| 4ef0dd6997 | |||
| b5f1b17926 | |||
| fd298521e2 | |||
| 99c28ab4d6 | |||
| 2d6a838905 | |||
| 792b5a24df | |||
| 19cc687928 | |||
| 86a6667122 | |||
| 07501f8085 | |||
| dd77236d75 | |||
| 926c9193e7 | |||
| a5987633e0 | |||
| aa8dce9804 | |||
| edfb5fc2f8 | |||
| 91fcdf9f6a | |||
| 54cfd5091f | |||
| 2fe8e0dea5 | |||
| 9d62d13492 | |||
| f40d5e4a89 | |||
| c559c9425a | |||
| 6a73b96459 | |||
| 538d4ffbd0 | |||
| c15dc01cff | |||
| f1d0b05db5 | |||
| a60dd672b7 | |||
| e82219185b | |||
| 0490c2f139 | |||
| a4b02c38db | |||
| 47763c10e3 | |||
| fc692ea512 | |||
| 28479321bb | |||
| 2b8c4acd57 | |||
| ee40c0e9a7 | |||
| 9a55ffba7a | |||
| 50a73d1188 | |||
| 76ebebf2a0 | |||
| aba7150af1 | |||
| 26a4d91297 | |||
| 9265720f92 | |||
| 273631c242 | |||
| bb8a0f71e6 | |||
| ffdb39d269 | |||
| 8fae4748a1 | |||
| 643a31dcea | |||
| 31440a586c | |||
| 1ef2dd45f3 | |||
| b29615561c | |||
| 7892b72bcb | |||
| 4b82a38cf7 | |||
| c20acbf4d8 | |||
| e66899e68e | |||
| 87f8c2ecd8 | |||
| 62be3bc111 | |||
| c485aea48b | |||
| 6f4b4ad087 | |||
| 3e8a0af6d1 | |||
| 12ab5a749f | |||
| ddfbca5830 | |||
| 9e72529544 | |||
| 11aa51373a | |||
| 5992582bc5 | |||
| e0e74a9d5e | |||
| cc228db337 | |||
| c546ce2439 | |||
| fdda4d506f | |||
| 5de16493d1 | |||
| 2381a8d609 | |||
| 39f1005006 | |||
| 6441c284e0 | |||
| 0aea9c74bd | |||
| 9a0d77571d | |||
| f446e7fc0b | |||
| b75b5ebeb2 | |||
| d19903877d | |||
| f271ea6eef | |||
| c76201b6b4 | |||
| 86f2af6fdc | |||
| da22da23cd | |||
| f45654a16a | |||
| bb952f9ecc | |||
| ad2d3964ef | |||
| 2cd17c7988 | |||
| 32998f7e18 | |||
| 7261cb29ac | |||
| 8fb1bb1f5f | |||
| 5a29d56d94 | |||
| 0cb9ea3643 | |||
| 7d54fdea51 | |||
| 99ca8281fa | |||
| 3a27b1dd0c | |||
| f9c5e349fb | |||
| 50a4c5a6ab | |||
| ed0c89f876 | |||
| 702cddd4ff | |||
| 4d1332c30f | |||
| 402fb9665d | |||
| b2e84405c1 | |||
| 52221127cc | |||
| dceab3689d | |||
| 123b01aaa0 | |||
| 572938aa49 | |||
| 48600ae71d | |||
| 59f6efeaf7 | |||
| 737b0ae5dc | |||
| 8925975c86 | |||
| ab9667f917 | |||
| 6baf3a24de | |||
| 735bd95659 | |||
| 69abb48c90 | |||
| 419a98d45a | |||
| 0dc8429d16 | |||
| b9210d491a | |||
| 5d855f3723 | |||
| 6b996061a2 | |||
| fca640f50f | |||
| 5010cea1a4 | |||
| 5726636053 | |||
| b7a542e074 | |||
| 9c37450fe4 | |||
| c39ce133de | |||
| dd543af2f6 | |||
| 6d06f22f64 | |||
| 862166511c | |||
| 4e54c53abb | |||
| a6ddab1e9d | |||
| 3c045ba8aa | |||
| 6e8dd5bdff | |||
| f6da708b02 | |||
| 23fdf0e889 | |||
| 9121ca14de | |||
| f4a3dbea70 | |||
| 47efe4e6a9 | |||
| 03fd88e4ec | |||
| 9b16d09261 |
@@ -7,6 +7,7 @@
|
||||
/Makefile
|
||||
/config.log
|
||||
/config.status
|
||||
/configure
|
||||
/aclocal.m4
|
||||
/contrib/extract_translations/extract_translations.beam
|
||||
/*.cache
|
||||
@@ -28,6 +29,7 @@
|
||||
/ejabberd.init
|
||||
/ejabberdctl.example
|
||||
/include/XmppAddr.hrl
|
||||
/rel/ejabberd/
|
||||
/src/XmppAddr.asn1db
|
||||
/src/XmppAddr.erl
|
||||
/src/ejabberd.app.src
|
||||
|
||||
+7
-4
@@ -1,9 +1,12 @@
|
||||
language: erlang
|
||||
|
||||
otp_release:
|
||||
- 17.0
|
||||
- R16B03
|
||||
- R15B01
|
||||
|
||||
services:
|
||||
- riak
|
||||
|
||||
before_install:
|
||||
- sudo apt-get -qq update
|
||||
|
||||
@@ -20,9 +23,9 @@ before_script:
|
||||
|
||||
script:
|
||||
- ./autogen.sh
|
||||
- ./configure --enable-transient_supervisors --enable-all --disable-http --disable-odbc
|
||||
- make
|
||||
- make test
|
||||
- ./configure --enable-all --disable-odbc
|
||||
- make xref
|
||||
- ERL_LIBS=$PWD make test
|
||||
- grep -q 'TEST COMPLETE, \([[:digit:]]*\) ok, .* of \1 ' logs/raw.log
|
||||
|
||||
after_script:
|
||||
|
||||
+14
-4
@@ -88,6 +88,10 @@ update:
|
||||
rm -rf deps/.built
|
||||
$(REBAR) update-deps && :> deps/.got
|
||||
|
||||
xref: all
|
||||
$(REBAR) skip_deps=true xref
|
||||
|
||||
|
||||
translations:
|
||||
contrib/extract_translations/prepare-translation.sh -updateall
|
||||
|
||||
@@ -151,10 +155,13 @@ install: all
|
||||
# Binary C programs
|
||||
$(INSTALL) -d $(PBINDIR)
|
||||
$(INSTALL) -m 750 $(O_USER) tools/captcha.sh $(PBINDIR)
|
||||
-[ -f deps/p1_pam/priv/bin/epam ] \
|
||||
&& $(INSTALL) -m 750 $(O_USER) deps/p1_pam/priv/bin/epam $(PBINDIR)
|
||||
#
|
||||
# Binary system libraries
|
||||
$(INSTALL) -d $(SODIR)
|
||||
$(INSTALL) -m 644 $(DLLs) $(SODIR)
|
||||
-[ -f $(SODIR)/jiffy.so ] && (cd $(PRIVDIR); ln -s lib/jiffy.so; true)
|
||||
#
|
||||
# Translated strings
|
||||
$(INSTALL) -d $(MSGSDIR)
|
||||
@@ -178,13 +185,16 @@ install: all
|
||||
#
|
||||
# Documentation
|
||||
$(INSTALL) -d $(DOCDIR)
|
||||
$(INSTALL) -m 644 doc/dev.html $(DOCDIR)
|
||||
$(INSTALL) -m 644 doc/guide.html $(DOCDIR)
|
||||
$(INSTALL) -m 644 doc/*.png $(DOCDIR)
|
||||
$(INSTALL) -m 644 doc/*.txt $(DOCDIR)
|
||||
[ -f doc/dev.html ] \
|
||||
&& $(INSTALL) -m 644 doc/dev.html $(DOCDIR) \
|
||||
|| echo "No doc/dev.html was built"
|
||||
[ -f doc/guide.html ] \
|
||||
&& $(INSTALL) -m 644 doc/guide.html $(DOCDIR) \
|
||||
|| echo "No doc/guide.html was built"
|
||||
[ -f doc/guide.pdf ] \
|
||||
&& $(INSTALL) -m 644 doc/guide.pdf $(DOCDIR) \
|
||||
|| echo "No doc/guide.pdf was built"
|
||||
$(INSTALL) -m 644 doc/*.png $(DOCDIR)
|
||||
$(INSTALL) -m 644 COPYING $(DOCDIR)
|
||||
|
||||
uninstall: uninstall-binary
|
||||
|
||||
@@ -1,8 +1,92 @@
|
||||
ejabberd - High-Performance Enterprise Instant Messaging Server
|
||||
---------------------------------------------------------------
|
||||
Ejabberd Community Edition, by ProcessOne
|
||||
-----------------------------------------
|
||||
|
||||
Quickstart guide
|
||||
================
|
||||
|
||||
ejabberd is a distributed, fault-tolerant technology that allows the creation
|
||||
of large-scale instant messaging applications.
|
||||
The server can reliably support thousands of simultaneous users on a single
|
||||
node and has been designed to provide exceptional standards of fault
|
||||
tolerance.
|
||||
As an open source technology, based on industry-standards, ejabberd can be
|
||||
used to build bespoke solutions very cost effectively.
|
||||
|
||||
|
||||
Key Features:
|
||||
=============
|
||||
|
||||
|
||||
- Cross-platform: ejabberd runs under Microsoft Windows and Unix derived
|
||||
systems such as Linux, FreeBSD and NetBSD.
|
||||
- Distributed: You can run ejabberd on a cluster of machines and all of them
|
||||
will serve the same Jabber domain(s). When you need more capacity you can
|
||||
simply add a new cheap node to your cluster. Accordingly, you do not need to
|
||||
buy an expensive high-end machine to support tens of thousands concurrent
|
||||
users.
|
||||
- Fault-tolerant: You can deploy an ejabberd cluster so that all the
|
||||
information required for a properly working service will be replicated
|
||||
permanently on all nodes. This means that if one of the nodes crashes, the
|
||||
others will continue working without disruption. In addition, nodes also can
|
||||
be added or replaced ‘on the fly’.
|
||||
- Administrator Friendly: ejabberd is built on top of the Open Source
|
||||
Erlang. As a result you do not need to install an external database, an
|
||||
external web server, amongst others because everything is already included,
|
||||
and ready to run out of the box. Other administrator benefits include:
|
||||
Comprehensive documentation.
|
||||
Straightforward installers for Linux, Mac OS X.
|
||||
Web Administration.
|
||||
Shared Roster Groups.
|
||||
Command line administration tool.
|
||||
Can integrate with existing authentication mechanisms.
|
||||
Capability to send announce messages._
|
||||
- Internationalized: ejabberd leads in internationalization. Hence it is
|
||||
very well suited in a globalized world. Related features are:
|
||||
Translated to 25 languages.
|
||||
Support for IDNA._
|
||||
- Open Standards: ejabberd is the first Open Source Jabber server claiming
|
||||
to fully comply to the XMPP standard.
|
||||
Fully XMPP compliant.
|
||||
XML-based protocol.
|
||||
Many protocols supported._
|
||||
|
||||
|
||||
Additional Features:
|
||||
====================
|
||||
|
||||
|
||||
Moreover, ejabberd comes with a wide range of other state-of-the-art features:
|
||||
|
||||
- Modular
|
||||
Load only the modules you want.
|
||||
Extend ejabberd with your own custom modules.
|
||||
- Security
|
||||
SASL and STARTTLS for c2s and s2s connections.
|
||||
STARTTLS and Dialback s2s connections.
|
||||
Web Admin accessible via HTTPS secure access.
|
||||
- Databases
|
||||
Internal database for fast deployment (Mnesia).
|
||||
Native MySQL support.
|
||||
Native PostgreSQL support.
|
||||
ODBC data storage support.
|
||||
Microsoft SQL Server support.
|
||||
- Authentication
|
||||
Internal Authentication.
|
||||
PAM, LDAP and ODBC.
|
||||
External Authentication script.
|
||||
- Others
|
||||
Support for virtual hosting.
|
||||
Compressing XML streams with Stream Compression (XEP-0138).
|
||||
Statistics via Statistics Gathering (XEP-0039).
|
||||
IPv6 support both for c2s and s2s connections.
|
||||
Multi-User Chat module with support for clustering and HTML logging.
|
||||
Users Directory based on users vCards.
|
||||
Publish-Subscribe component with support for Personal Eventing.
|
||||
Support for web clients: HTTP Polling and HTTP Binding (BOSH).
|
||||
IRC transport.
|
||||
Component support: interface with networks such as AIM, ICQ and MSN
|
||||
|
||||
|
||||
Quickstart guide:
|
||||
=================
|
||||
|
||||
|
||||
0. Requirements
|
||||
@@ -13,7 +97,7 @@ To compile ejabberd you need:
|
||||
- GNU Make
|
||||
- GCC
|
||||
- Libexpat 1.95 or higher
|
||||
- Libyaml 1.4 or higher
|
||||
- Libyaml 0.1.4 or higher
|
||||
- Erlang/OTP R15B or higher.
|
||||
- OpenSSL 0.9.8 or higher, for STARTTLS, SASL and SSL encryption.
|
||||
- Zlib 1.2.3 or higher, for Stream Compression support
|
||||
@@ -56,7 +140,16 @@ start and stop ejabberd. For example:
|
||||
ejabberdctl start
|
||||
|
||||
|
||||
For detailed information please refer to the [ejabberd Installation and
|
||||
Operation Guide][1].
|
||||
For detailed information please refer to the ejabberd Installation and
|
||||
Operation Guide available online and in the doc directory of sources tarball.
|
||||
|
||||
|
||||
Links:
|
||||
======
|
||||
|
||||
|
||||
- Guide: http://www.process-one.net/docs/ejabberd/guide_en.html
|
||||
- Official site: https://www.process-one.net/en/ejabberd
|
||||
- Community site: http://www.ejabberd.im
|
||||
- Forum: http://www.process-one.net/en/forum
|
||||
|
||||
[1]: http://www.process-one.net/docs/ejabberd/guide_en.html
|
||||
|
||||
+11
-45
@@ -2,7 +2,7 @@
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ(2.53)
|
||||
AC_INIT(ejabberd, community m4_esyscmd([git describe --tags | tr -d '\n']), [ejabberd@process-one.net], [ejabberd])
|
||||
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="5.9.1 (Erlang/OTP R15B01)"
|
||||
REQUIRE_ERLANG_MAX="9.0.0 (No Max)"
|
||||
|
||||
@@ -106,10 +106,10 @@ AC_ARG_ENABLE(mssql,
|
||||
esac],[db_type=generic])
|
||||
|
||||
AC_ARG_ENABLE(all,
|
||||
[AC_HELP_STRING([--enable-all], [same as --enable-nif --enable-odbc --enable-mysql --enable-pgsql --enable-pam --enable-zlib --enable-stun --enable-riak --enable-json --enable-iconv --enable-debug --enable-http --enable-lager --enable-tools (useful for Dialyzer checks, default: no)])],
|
||||
[AC_HELP_STRING([--enable-all], [same as --enable-nif --enable-odbc --enable-mysql --enable-pgsql --enable-pam --enable-zlib --enable-riak --enable-json --enable-iconv --enable-debug --enable-lager --enable-tools (useful for Dialyzer checks, default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true stun=true riak=true json=true iconv=true debug=true http=true lager=true tools=true ;;
|
||||
no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false stun=false riak=false json=false iconv=false debug=false http=false lager=false tools=false ;;
|
||||
yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true riak=true json=true iconv=true debug=true lager=true tools=true ;;
|
||||
no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false riak=false json=false iconv=false debug=false lager=false tools=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
|
||||
esac],[])
|
||||
|
||||
@@ -169,13 +169,13 @@ AC_ARG_ENABLE(zlib,
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-zlib) ;;
|
||||
esac],[if test "x$zlib" = "x"; then zlib=true; fi])
|
||||
|
||||
AC_ARG_ENABLE(stun,
|
||||
[AC_HELP_STRING([--enable-stun], [enable STUN support (default: no)])],
|
||||
AC_ARG_ENABLE(riak,
|
||||
[AC_HELP_STRING([--enable-riak], [enable Riak 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])
|
||||
yes) riak=true ;;
|
||||
no) riak=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-riak) ;;
|
||||
esac],[if test "x$riak" = "x"; then riak=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(json,
|
||||
[AC_HELP_STRING([--enable-json], [enable JSON support for mod_bosh (default: no)])],
|
||||
@@ -201,14 +201,6 @@ AC_ARG_ENABLE(debug,
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;;
|
||||
esac],[if test "x$debug" = "x"; then debug=true; fi])
|
||||
|
||||
AC_ARG_ENABLE(http,
|
||||
[AC_HELP_STRING([--enable-http], [build external HTTP libraries ('ibrowse' and 'lhttpc', default: no)])],
|
||||
[case "${enableval}" in
|
||||
yes) http=true ;;
|
||||
no) http=false ;;
|
||||
*) AC_MSG_ERROR(bad value ${enableval} for --enable-http) ;;
|
||||
esac],[if test "x$http" = "x"; then http=false; fi])
|
||||
|
||||
AC_ARG_ENABLE(lager,
|
||||
[AC_HELP_STRING([--enable-lager], [enable lager support (default: yes)])],
|
||||
[case "${enableval}" in
|
||||
@@ -235,31 +227,6 @@ if test "$ENABLEUSER" != ""; then
|
||||
AC_SUBST([INSTALLUSER], [$ENABLEUSER])
|
||||
fi
|
||||
|
||||
AC_ERLANG_CHECK_LIB([sasl], [],
|
||||
[AC_MSG_ERROR([Erlang application 'sasl' was not found])])
|
||||
AC_ERLANG_CHECK_LIB([crypto], [],
|
||||
[AC_MSG_ERROR([Erlang application 'crypto' was not found])])
|
||||
AC_ERLANG_CHECK_LIB([public_key], [],
|
||||
[AC_MSG_ERROR([Erlang application 'public_key' was not found])])
|
||||
AC_ERLANG_CHECK_LIB([ssl], [],
|
||||
[AC_MSG_ERROR([Erlang application 'ssl' was not found])])
|
||||
AC_ERLANG_CHECK_LIB([mnesia], [],
|
||||
[AC_MSG_ERROR([Erlang application 'mnesia' was not found])])
|
||||
AC_ERLANG_CHECK_LIB([inets], [],
|
||||
[AC_MSG_ERROR([Erlang application 'inets' was not found])])
|
||||
AC_ERLANG_CHECK_LIB([compiler], [],
|
||||
[AC_MSG_ERROR([Erlang application 'compiler' was not found])])
|
||||
if test "x$odbc" = "xtrue"; then
|
||||
AC_ERLANG_CHECK_LIB([odbc], [],
|
||||
[AC_MSG_ERROR([Erlang application 'odbc' was not found])])
|
||||
fi
|
||||
if test "x$tools" = "xtrue"; then
|
||||
AC_ERLANG_CHECK_LIB([tools], [],
|
||||
[AC_MSG_ERROR([Erlang application 'tools' was not found])])
|
||||
AC_ERLANG_CHECK_LIB([runtime_tools], [],
|
||||
[AC_MSG_ERROR([Erlang application 'runtime_tools' was not found])])
|
||||
fi
|
||||
|
||||
AC_SUBST(hipe)
|
||||
AC_SUBST(roster_gateway_workaround)
|
||||
AC_SUBST(transient_supervisors)
|
||||
@@ -271,11 +238,10 @@ AC_SUBST(mysql)
|
||||
AC_SUBST(pgsql)
|
||||
AC_SUBST(pam)
|
||||
AC_SUBST(zlib)
|
||||
AC_SUBST(stun)
|
||||
AC_SUBST(riak)
|
||||
AC_SUBST(json)
|
||||
AC_SUBST(iconv)
|
||||
AC_SUBST(debug)
|
||||
AC_SUBST(http)
|
||||
AC_SUBST(lager)
|
||||
AC_SUBST(tools)
|
||||
|
||||
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
# $Id$
|
||||
|
||||
SHELL = /bin/bash
|
||||
SHELL = /bin/sh
|
||||
|
||||
CONTRIBUTED_MODULES = ""
|
||||
#ifeq ($(shell ls mod_http_bind.tex),mod_http_bind.tex)
|
||||
@@ -11,16 +11,16 @@ CONTRIBUTED_MODULES = ""
|
||||
all: release pdf html
|
||||
|
||||
release:
|
||||
@echo "Notes for the releaser:"
|
||||
@echo "* Do not forget to add a link to the release notes in guide.tex"
|
||||
@echo "* Do not forget to update the version number in ebin/ejabberd.app!"
|
||||
@echo "* Do not forget to update the features in introduction.tex (including \new{} and \improved{} tags)."
|
||||
@echo "Press any key to continue"
|
||||
@printf '%s\n' "Notes for the releaser:"
|
||||
@printf '%s\n' "* Do not forget to add a link to the release notes in guide.tex"
|
||||
@printf '%s\n' "* Do not forget to update the version number in ebin/ejabberd.app!"
|
||||
@printf '%s\n' "* Do not forget to update the features in introduction.tex (including \new{} and \improved{} tags)."
|
||||
@printf '%s\n' "Press any key to continue"
|
||||
##@read foo
|
||||
@echo "% ejabberd version (automatically generated)." > version.tex
|
||||
@echo "\newcommand{\version}{"`sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../ebin/ejabberd.app`"}" >> version.tex
|
||||
@echo -n "% Contributed modules (automatically generated)." > contributed_modules.tex
|
||||
@echo -e "$(CONTRIBUTED_MODULES)" >> contributed_modules.tex
|
||||
@printf '%s\n' "% ejabberd version (automatically generated)." > version.tex
|
||||
@printf '%s\n' "\newcommand{\version}{"`sed '/vsn/!d;s/\(.*\)"\(.*\)"\(.*\)/\2/' ../ebin/ejabberd.app`"}" >> version.tex
|
||||
@printf '%s' "% Contributed modules (automatically generated)." > contributed_modules.tex
|
||||
@printf '%b\n' "$(CONTRIBUTED_MODULES)" >> contributed_modules.tex
|
||||
|
||||
html: guide.html dev.html features.html
|
||||
|
||||
|
||||
+236
-212
@@ -1,40 +1,45 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.12 Developers Guide
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
<META name="GENERATOR" content="hevea 1.10">
|
||||
<STYLE type="text/css">
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
<meta name="generator" content="hevea 2.09">
|
||||
<style type="text/css">
|
||||
.li-itemize{margin:1ex 0ex;}
|
||||
.li-enumerate{margin:1ex 0ex;}
|
||||
.dd-description{margin:0ex 0ex 1ex 4ex;}
|
||||
.dt-description{margin:0ex;}
|
||||
.toc{list-style:none;}
|
||||
.footnotetext{margin:0ex; padding:0ex;}
|
||||
div.footnotetext P{margin:0px; text-indent:1em;}
|
||||
.thefootnotes{text-align:left;margin:0ex;}
|
||||
.dt-thefootnotes{margin:0em;}
|
||||
.dd-thefootnotes{margin:0em 0em 0em 2em;}
|
||||
.footnoterule{margin:1em auto 1em 0px;width:50%;}
|
||||
.caption{padding-left:2ex; padding-right:2ex; margin-left:auto; margin-right:auto}
|
||||
.title{margin:2ex auto;text-align:center}
|
||||
.titlemain{margin:1ex 2ex 2ex 1ex;}
|
||||
.titlerest{margin:0ex 2ex;}
|
||||
.center{text-align:center;margin-left:auto;margin-right:auto;}
|
||||
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
|
||||
DIV TABLE{margin-left:inherit;margin-right:inherit;}
|
||||
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
|
||||
TD P{margin:0px;}
|
||||
div table{margin-left:inherit;margin-right:inherit;margin-bottom:2px;margin-top:2px}
|
||||
td table{margin:auto;}
|
||||
table{border-collapse:collapse;}
|
||||
td{padding:0;}
|
||||
.cellpadding0 tr td{padding:0;}
|
||||
.cellpadding1 tr td{padding:1px;}
|
||||
pre{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
blockquote{margin-left:4ex;margin-right:4ex;text-align:left;}
|
||||
td p{margin:0px;}
|
||||
.boxed{border:1px solid black}
|
||||
.textboxed{border:1px solid black}
|
||||
.vbar{border:none;width:2px;background-color:black;}
|
||||
.hbar{border:none;height:2px;width:100%;background-color:black;}
|
||||
.hfill{border:none;height:1px;width:200%;background-color:black;}
|
||||
.vdisplay{border-collapse:separate;border-spacing:2px;width:auto; empty-cells:show; border:2px solid red;}
|
||||
.vdcell{white-space:nowrap;padding:0px;width:auto; border:2px solid green;}
|
||||
.vdcell{white-space:nowrap;padding:0px; border:2px solid green;}
|
||||
.display{border-collapse:separate;border-spacing:2px;width:auto; border:none;}
|
||||
.dcell{white-space:nowrap;padding:0px;width:auto; border:none;}
|
||||
.dcell{white-space:nowrap;padding:0px; border:none;}
|
||||
.dcenter{margin:0ex auto;}
|
||||
.vdcenter{border:solid #FF8000 2px; margin:0ex auto;}
|
||||
.minipage{text-align:left; margin-left:0em; margin-right:auto;}
|
||||
@@ -43,169 +48,182 @@ TD P{margin:0px;}
|
||||
.marginparright{float:right; margin-left:1ex; margin-right:0ex;}
|
||||
.theorem{text-align:left;margin:1ex auto 1ex 0ex;}
|
||||
.part{margin:2ex auto;text-align:center}
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
<BODY >
|
||||
</style>
|
||||
<title>Ejabberd community 14.05-120-gedfb5fc Developers Guide
|
||||
</title>
|
||||
</head>
|
||||
<body >
|
||||
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic dev.tex -->
|
||||
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
|
||||
<!--CUT STYLE article--><!--CUT DEF section 1 --><p><a id="titlepage"></a>
|
||||
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.12 Developers Guide</H1><H3 CLASS="titlerest">Alexey Shchepin<BR>
|
||||
<A HREF="mailto:alexey@sevcom.net"><TT>mailto:alexey@sevcom.net</TT></A><BR>
|
||||
<A HREF="xmpp:aleksey@jabber.ru"><TT>xmpp:aleksey@jabber.ru</TT></A></H3></TD></TR>
|
||||
</TABLE><DIV CLASS="center">
|
||||
</p><table class="title"><tr><td style="padding:1ex"><h1 class="titlemain">Ejabberd community 14.05-120-gedfb5fc Developers Guide</h1><h3 class="titlerest">Alexey Shchepin <br>
|
||||
<a href="mailto:alexey@sevcom.net"><span style="font-family:monospace">mailto:alexey@sevcom.net</span></a> <br>
|
||||
<a href="xmpp:aleksey@jabber.ru"><span style="font-family:monospace">xmpp:aleksey@jabber.ru</span></a></h3></td></tr>
|
||||
</table><div class="center">
|
||||
|
||||
<IMG SRC="logo.png" ALT="logo.png">
|
||||
<img src="logo.png" alt="logo.png">
|
||||
|
||||
|
||||
</DIV><BLOCKQUOTE CLASS="quotation"><I>I can thoroughly recommend ejabberd for ease of setup –
|
||||
Kevin Smith, Current maintainer of the Psi project</I></BLOCKQUOTE><!--TOC section Contents-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR -->Contents</H2><!--SEC END --><UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc1">1  Key Features</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc2">2  Additional Features</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc3">3  How it Works</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc4">3.1  Router</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc5">3.2  Local Router</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc6">3.3  Session Manager</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc7">3.4  S2S Manager</A>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc8">4  Authentication</A>
|
||||
<UL CLASS="toc">
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc9">4.0.1  External</A>
|
||||
</LI></UL>
|
||||
</UL>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc10">5  XML Representation</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc11">6  Module <TT>xml</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc12">7  Module <TT>xml_stream</TT></A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc13">8  Modules</A>
|
||||
<UL CLASS="toc"><LI CLASS="li-toc">
|
||||
<A HREF="#htoc14">8.1  Module gen_iq_handler</A>
|
||||
</LI><LI CLASS="li-toc"><A HREF="#htoc15">8.2  Services</A>
|
||||
</LI></UL>
|
||||
</LI></UL><P>Introduction
|
||||
<A NAME="intro"></A></P><P><TT>ejabberd</TT> is a free and open source instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a rock-solid and feature rich XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1">1</A>  Key Features</H2><!--SEC END --><P>
|
||||
<A NAME="keyfeatures"></A>
|
||||
</P><P><TT>ejabberd</TT> is:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Cross-platform: <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</LI><LI CLASS="li-itemize">Distributed: You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</LI><LI CLASS="li-itemize">Fault-tolerant: You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced ‘on the fly’.</LI><LI CLASS="li-itemize">Administrator Friendly: <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</div><blockquote class="quotation"><span style="font-style:italic">I can thoroughly recommend ejabberd for ease of setup –
|
||||
Kevin Smith, Current maintainer of the Psi project</span></blockquote><!--TOC section id="intro" Contents-->
|
||||
<h2 id="intro" class="section">Contents</h2><!--SEC END --><ul class="toc"><li class="li-toc">
|
||||
<a href="#sec2">1  Key Features</a>
|
||||
</li><li class="li-toc"><a href="#sec3">2  Additional Features</a>
|
||||
</li><li class="li-toc"><a href="#sec4">3  How it Works</a>
|
||||
<ul class="toc"><li class="li-toc">
|
||||
<a href="#sec5">3.1  Router</a>
|
||||
</li><li class="li-toc"><a href="#sec6">3.2  Local Router</a>
|
||||
</li><li class="li-toc"><a href="#sec7">3.3  Session Manager</a>
|
||||
</li><li class="li-toc"><a href="#sec8">3.4  S2S Manager</a>
|
||||
</li></ul>
|
||||
</li><li class="li-toc"><a href="#sec9">4  Authentication</a>
|
||||
<ul class="toc">
|
||||
<ul class="toc"><li class="li-toc">
|
||||
<a href="#sec10">4.0.1  External</a>
|
||||
</li></ul>
|
||||
</ul>
|
||||
</li><li class="li-toc"><a href="#sec11">5  XML Representation</a>
|
||||
</li><li class="li-toc"><a href="#sec12">6  Module <span style="font-family:monospace">xml</span></a>
|
||||
</li><li class="li-toc"><a href="#sec13">7  Module <span style="font-family:monospace">xml_stream</span></a>
|
||||
</li><li class="li-toc"><a href="#sec14">8  Modules</a>
|
||||
<ul class="toc"><li class="li-toc">
|
||||
<a href="#sec15">8.1  Module gen_iq_handler</a>
|
||||
</li><li class="li-toc"><a href="#sec16">8.2  Services</a>
|
||||
</li></ul>
|
||||
</li></ul><p>Introduction
|
||||
</p><p><span style="font-family:monospace">ejabberd</span> is a free and open source instant messaging server written in <a href="http://www.erlang.org/">Erlang/OTP</a>.</p><p><span style="font-family:monospace">ejabberd</span> is cross-platform, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</p><p><span style="font-family:monospace">ejabberd</span> is designed to be a rock-solid and feature rich XMPP server.</p><p><span style="font-family:monospace">ejabberd</span> is suitable for small deployments, whether they need to be scalable or not, as well as extremely big deployments.</p>
|
||||
<!--TOC section id="sec2" Key Features-->
|
||||
<h2 id="sec2" class="section">1  Key Features</h2><!--SEC END --><p>
|
||||
<a id="keyfeatures"></a>
|
||||
</p><p><span style="font-family:monospace">ejabberd</span> is:
|
||||
</p><ul class="itemize"><li class="li-itemize">
|
||||
Cross-platform: <span style="font-family:monospace">ejabberd</span> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</li><li class="li-itemize">Distributed: You can run <span style="font-family:monospace">ejabberd</span> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</li><li class="li-itemize">Fault-tolerant: You can deploy an <span style="font-family:monospace">ejabberd</span> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced ‘on the fly’.</li><li class="li-itemize">Administrator Friendly: <span style="font-family:monospace">ejabberd</span> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Comprehensive documentation.
|
||||
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web Administration.
|
||||
</LI><LI CLASS="li-itemize">Shared Roster Groups.
|
||||
</LI><LI CLASS="li-itemize">Command line administration tool. </LI><LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
|
||||
</LI><LI CLASS="li-itemize">Capability to send announce messages.
|
||||
</LI></UL></LI><LI CLASS="li-itemize">Internationalized: <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
|
||||
</LI></UL></LI><LI CLASS="li-itemize">Open Standards: <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </li><li class="li-itemize">Web Administration.
|
||||
</li><li class="li-itemize">Shared Roster Groups.
|
||||
</li><li class="li-itemize">Command line administration tool. </li><li class="li-itemize">Can integrate with existing authentication mechanisms.
|
||||
</li><li class="li-itemize">Capability to send announce messages.
|
||||
</li></ul></li><li class="li-itemize">Internationalized: <span style="font-family:monospace">ejabberd</span> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Translated to 25 languages. </li><li class="li-itemize">Support for <a href="http://www.ietf.org/rfc/rfc3490.txt">IDNA</a>.
|
||||
</li></ul></li><li class="li-itemize">Open Standards: <span style="font-family:monospace">ejabberd</span> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Fully XMPP compliant.
|
||||
</LI><LI CLASS="li-itemize">XML-based protocol.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.ejabberd.im/protocols">Many protocols supported</A>.
|
||||
</LI></UL></LI></UL><!--TOC section Additional Features-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc2">2</A>  Additional Features</H2><!--SEC END --><P>
|
||||
<A NAME="addfeatures"></A>
|
||||
</P><P>Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">XML-based protocol.
|
||||
</li><li class="li-itemize"><a href="http://www.ejabberd.im/protocols">Many protocols supported</a>.
|
||||
</li></ul></li></ul>
|
||||
<!--TOC section id="sec3" Additional Features-->
|
||||
<h2 id="sec3" class="section">2  Additional Features</h2><!--SEC END --><p>
|
||||
<a id="addfeatures"></a>
|
||||
</p><p>Moreover, <span style="font-family:monospace">ejabberd</span> comes with a wide range of other state-of-the-art features:
|
||||
</p><ul class="itemize"><li class="li-itemize">
|
||||
Modular
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Load only the modules you want.
|
||||
</LI><LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Security
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">Extend <span style="font-family:monospace">ejabberd</span> with your own custom modules.
|
||||
</li></ul>
|
||||
</li><li class="li-itemize">Security
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
SASL and STARTTLS for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
|
||||
</LI><LI CLASS="li-itemize">Web Admin accessible via HTTPS secure access.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Databases
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">STARTTLS and Dialback s2s connections.
|
||||
</li><li class="li-itemize">Web Admin accessible via HTTPS secure access.
|
||||
</li></ul>
|
||||
</li><li class="li-itemize">Databases
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Internal database for fast deployment (Mnesia).
|
||||
</LI><LI CLASS="li-itemize">Native MySQL support.
|
||||
</LI><LI CLASS="li-itemize">Native PostgreSQL support.
|
||||
</LI><LI CLASS="li-itemize">ODBC data storage support.
|
||||
</LI><LI CLASS="li-itemize">Microsoft SQL Server support. </LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Authentication
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">Native MySQL support.
|
||||
</li><li class="li-itemize">Native PostgreSQL support.
|
||||
</li><li class="li-itemize">ODBC data storage support.
|
||||
</li><li class="li-itemize">Microsoft SQL Server support. </li><li class="li-itemize">Riak NoSQL database support.
|
||||
</li></ul>
|
||||
</li><li class="li-itemize">Authentication
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Internal Authentication.
|
||||
</LI><LI CLASS="li-itemize">PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Others
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">PAM, LDAP, ODBC and Riak. </li><li class="li-itemize">External Authentication script.
|
||||
</li></ul>
|
||||
</li><li class="li-itemize">Others
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Support for virtual hosting.
|
||||
</LI><LI CLASS="li-itemize">Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
|
||||
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
|
||||
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
|
||||
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
|
||||
</LI><LI CLASS="li-itemize">IRC transport.
|
||||
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
||||
</LI></UL>
|
||||
</LI></UL><!--TOC section How it Works-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc3">3</A>  How it Works</H2><!--SEC END --><P>
|
||||
<A NAME="howitworks"></A></P><P>A XMPP domain is served by one or more <TT>ejabberd</TT> nodes. These nodes can
|
||||
</li><li class="li-itemize">Compressing XML streams with Stream Compression (<a href="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</a>).
|
||||
</li><li class="li-itemize">Statistics via Statistics Gathering (<a href="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</a>).
|
||||
</li><li class="li-itemize">IPv6 support both for c2s and s2s connections.
|
||||
</li><li class="li-itemize"><a href="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</a> module with support for clustering and HTML logging. </li><li class="li-itemize">Users Directory based on users vCards.
|
||||
</li><li class="li-itemize"><a href="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</a> component with support for <a href="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</a>.
|
||||
</li><li class="li-itemize">Support for web clients: <a href="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</a> and <a href="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</a> services.
|
||||
</li><li class="li-itemize">IRC transport.
|
||||
</li><li class="li-itemize">SIP support.
|
||||
</li><li class="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
||||
</li></ul>
|
||||
</li></ul>
|
||||
<!--TOC section id="sec4" How it Works-->
|
||||
<h2 id="sec4" class="section">3  How it Works</h2><!--SEC END --><p>
|
||||
<a id="howitworks"></a></p><p>A XMPP domain is served by one or more <span style="font-family:monospace">ejabberd</span> nodes. These nodes can
|
||||
be run on different machines that are connected via a network. They all must
|
||||
have the ability to connect to port 4369 of all another nodes, and must have
|
||||
the same magic cookie (see Erlang/OTP documentation, in other words the file
|
||||
<TT>~ejabberd/.erlang.cookie</TT> must be the same on all nodes). This is
|
||||
<span style="font-family:monospace">~ejabberd/.erlang.cookie</span> must be the same on all nodes). This is
|
||||
needed because all nodes exchange information about connected users, S2S
|
||||
connections, registered services, etc…</P><P>Each <TT>ejabberd</TT> node have following modules:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
connections, registered services, etc…</p><p>Each <span style="font-family:monospace">ejabberd</span> node have following modules:
|
||||
</p><ul class="itemize"><li class="li-itemize">
|
||||
router;
|
||||
</LI><LI CLASS="li-itemize">local router.
|
||||
</LI><LI CLASS="li-itemize">session manager;
|
||||
</LI><LI CLASS="li-itemize">S2S manager;
|
||||
</LI></UL><!--TOC subsection Router-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc4">3.1</A>  Router</H3><!--SEC END --><P>This module is the main router of XMPP packets on each node. It routes
|
||||
</li><li class="li-itemize">local router.
|
||||
</li><li class="li-itemize">session manager;
|
||||
</li><li class="li-itemize">S2S manager;
|
||||
</li></ul>
|
||||
<!--TOC subsection id="sec5" Router-->
|
||||
<h3 id="sec5" class="subsection">3.1  Router</h3><!--SEC END --><p>This module is the main router of XMPP packets on each node. It routes
|
||||
them based on their destinations domains. It has two tables: local and global
|
||||
routes. First, domain of packet destination searched in local table, and if it
|
||||
found, then the packet is routed to appropriate process. If no, then it
|
||||
searches in global table, and is routed to the appropriate <TT>ejabberd</TT> node or
|
||||
searches in global table, and is routed to the appropriate <span style="font-family:monospace">ejabberd</span> node or
|
||||
process. If it does not exists in either tables, then it sent to the S2S
|
||||
manager.</P><!--TOC subsection Local Router-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc5">3.2</A>  Local Router</H3><!--SEC END --><P>This module routes packets which have a destination domain equal to this server
|
||||
manager.</p>
|
||||
<!--TOC subsection id="sec6" Local Router-->
|
||||
<h3 id="sec6" class="subsection">3.2  Local Router</h3><!--SEC END --><p>This module routes packets which have a destination domain equal to this server
|
||||
name. If destination JID has a non-empty user part, then it routed to the
|
||||
session manager, else it is processed depending on it’s content.</P><!--TOC subsection Session Manager-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc6">3.3</A>  Session Manager</H3><!--SEC END --><P>This module routes packets to local users. It searches for what user resource
|
||||
session manager, else it is processed depending on it’s content.</p>
|
||||
<!--TOC subsection id="sec7" Session Manager-->
|
||||
<h3 id="sec7" class="subsection">3.3  Session Manager</h3><!--SEC END --><p>This module routes packets to local users. It searches for what user resource
|
||||
packet must be sent via presence table. If this resource is connected to
|
||||
this node, it is routed to C2S process, if it connected via another node, then
|
||||
the packet is sent to session manager on that node.</P><!--TOC subsection S2S Manager-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc7">3.4</A>  S2S Manager</H3><!--SEC END --><P>This module routes packets to other XMPP servers. First, it checks if an
|
||||
the packet is sent to session manager on that node.</p>
|
||||
<!--TOC subsection id="sec8" S2S Manager-->
|
||||
<h3 id="sec8" class="subsection">3.4  S2S Manager</h3><!--SEC END --><p>This module routes packets to other XMPP servers. First, it checks if an
|
||||
open S2S connection from the domain of the packet source to the domain of
|
||||
packet destination already exists. If it is open on another node, then it
|
||||
routes the packet to S2S manager on that node, if it is open on this node, then
|
||||
it is routed to the process that serves this connection, and if a connection
|
||||
does not exist, then it is opened and registered.</P><!--TOC section Authentication-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc8">4</A>  Authentication</H2><!--SEC END --><!--TOC subsubsection External-->
|
||||
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A NAME="htoc9">4.0.1</A>  External</H4><!--SEC END --><P>
|
||||
<A NAME="externalauth"></A>
|
||||
</P><P>The external authentication script follows
|
||||
<A HREF="http://www.erlang.org/doc/tutorial/c_portdriver.html">the erlang port driver API</A>.</P><P>That script is supposed to do theses actions, in an infinite loop:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
does not exist, then it is opened and registered.</p>
|
||||
<!--TOC section id="sec9" Authentication-->
|
||||
<h2 id="sec9" class="section">4  Authentication</h2><!--SEC END -->
|
||||
<!--TOC subsubsection id="sec10" External-->
|
||||
<h4 id="sec10" class="subsubsection">4.0.1  External</h4><!--SEC END --><p>
|
||||
<a id="externalauth"></a>
|
||||
</p><p>The external authentication script follows
|
||||
<a href="http://www.erlang.org/doc/tutorial/c_portdriver.html">the erlang port driver API</a>.</p><p>That script is supposed to do theses actions, in an infinite loop:
|
||||
</p><ul class="itemize"><li class="li-itemize">
|
||||
read from stdin: AABBBBBBBBB.....
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
A: 2 bytes of length data (a short in network byte order)
|
||||
</LI><LI CLASS="li-itemize">B: a string of length found in A that contains operation in plain text
|
||||
</li><li class="li-itemize">B: a string of length found in A that contains operation in plain text
|
||||
operation are as follows:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
auth:User:Server:Password (check if a username/password pair is correct)
|
||||
</LI><LI CLASS="li-itemize">isuser:User:Server (check if it’s a valid user)
|
||||
</LI><LI CLASS="li-itemize">setpass:User:Server:Password (set user’s password)
|
||||
</LI><LI CLASS="li-itemize">tryregister:User:Server:Password (try to register an account)
|
||||
</LI><LI CLASS="li-itemize">removeuser:User:Server (remove this account)
|
||||
</LI><LI CLASS="li-itemize">removeuser3:User:Server:Password (remove this account if the password is correct)
|
||||
</LI></UL>
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">write to stdout: AABB
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">isuser:User:Server (check if it’s a valid user)
|
||||
</li><li class="li-itemize">setpass:User:Server:Password (set user’s password)
|
||||
</li><li class="li-itemize">tryregister:User:Server:Password (try to register an account)
|
||||
</li><li class="li-itemize">removeuser:User:Server (remove this account)
|
||||
</li><li class="li-itemize">removeuser3:User:Server:Password (remove this account if the password is correct)
|
||||
</li></ul>
|
||||
</li></ul>
|
||||
</li><li class="li-itemize">write to stdout: AABB
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
A: the number 2 (coded as a short, which is bytes length of following result)
|
||||
</LI><LI CLASS="li-itemize">B: the result code (coded as a short), should be 1 for success/valid, or 0 for failure/invalid
|
||||
</LI></UL>
|
||||
</LI></UL><P>Example python script
|
||||
</P><PRE CLASS="verbatim">#!/usr/bin/python
|
||||
</li><li class="li-itemize">B: the result code (coded as a short), should be 1 for success/valid, or 0 for failure/invalid
|
||||
</li></ul>
|
||||
</li></ul><p>Example python script
|
||||
</p><pre class="verbatim">#!/usr/bin/python
|
||||
|
||||
import sys
|
||||
from struct import *
|
||||
@@ -242,10 +260,11 @@ while True:
|
||||
elif data[0] == "setpass":
|
||||
success = setpass(data[1], data[2], data[3])
|
||||
to_ejabberd(success)
|
||||
</PRE><!--TOC section XML Representation-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc10">5</A>  XML Representation</H2><!--SEC END --><P>
|
||||
<A NAME="xmlrepr"></A></P><P>Each XML stanza is represented as the following tuple:
|
||||
</P><PRE CLASS="verbatim">XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
|
||||
</pre>
|
||||
<!--TOC section id="sec11" XML Representation-->
|
||||
<h2 id="sec11" class="section">5  XML Representation</h2><!--SEC END --><p>
|
||||
<a id="xmlrepr"></a></p><p>Each XML stanza is represented as the following tuple:
|
||||
</p><pre class="verbatim">XMLElement = {xmlelement, Name, Attrs, [ElementOrCDATA]}
|
||||
Name = string()
|
||||
Attrs = [Attr]
|
||||
Attr = {Key, Val}
|
||||
@@ -253,30 +272,31 @@ while True:
|
||||
Val = string()
|
||||
ElementOrCDATA = XMLElement | CDATA
|
||||
CDATA = {xmlcdata, string()}
|
||||
</PRE><P>E. g. this stanza:
|
||||
</P><PRE CLASS="verbatim"><message to='test@conference.example.org' type='groupchat'>
|
||||
</pre><p>E. g. this stanza:
|
||||
</p><pre class="verbatim"><message to='test@conference.example.org' type='groupchat'>
|
||||
<body>test</body>
|
||||
</message>
|
||||
</PRE><P>is represented as the following structure:
|
||||
</P><PRE CLASS="verbatim">{xmlelement, "message",
|
||||
</pre><p>is represented as the following structure:
|
||||
</p><pre class="verbatim">{xmlelement, "message",
|
||||
[{"to", "test@conference.example.org"},
|
||||
{"type", "groupchat"}],
|
||||
[{xmlelement, "body",
|
||||
[],
|
||||
[{xmlcdata, "test"}]}]}}
|
||||
</PRE><!--TOC section Module <TT>xml</TT>-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc11">6</A>  Module <TT>xml</TT></H2><!--SEC END --><P>
|
||||
<A NAME="xmlmod"></A></P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>element_to_string(El) -> string()</CODE>
|
||||
<PRE CLASS="verbatim">El = XMLElement
|
||||
</PRE>Returns string representation of XML stanza <TT>El</TT>.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>crypt(S) -> string()</CODE>
|
||||
<PRE CLASS="verbatim">S = string()
|
||||
</PRE>Returns string which correspond to <TT>S</TT> with encoded XML special
|
||||
characters.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>remove_cdata(ECList) -> EList</CODE>
|
||||
<PRE CLASS="verbatim">ECList = [ElementOrCDATA]
|
||||
</pre>
|
||||
<!--TOC section id="sec12" Module <span style="font-family:monospace">xml</span>-->
|
||||
<h2 id="sec12" class="section">6  Module <span style="font-family:monospace">xml</span></h2><!--SEC END --><p>
|
||||
<a id="xmlmod"></a></p><dl class="description"><dt class="dt-description">
|
||||
</dt><dd class="dd-description"><code>element_to_string(El) -> string()</code>
|
||||
<pre class="verbatim">El = XMLElement
|
||||
</pre>Returns string representation of XML stanza <span style="font-family:monospace">El</span>.</dd><dt class="dt-description"></dt><dd class="dd-description"><code>crypt(S) -> string()</code>
|
||||
<pre class="verbatim">S = string()
|
||||
</pre>Returns string which correspond to <span style="font-family:monospace">S</span> with encoded XML special
|
||||
characters.</dd><dt class="dt-description"></dt><dd class="dd-description"><code>remove_cdata(ECList) -> EList</code>
|
||||
<pre class="verbatim">ECList = [ElementOrCDATA]
|
||||
EList = [XMLElement]
|
||||
</PRE><TT>EList</TT> is a list of all non-CDATA elements of ECList.</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>get_path_s(El, Path) -> Res</CODE>
|
||||
<PRE CLASS="verbatim">El = XMLElement
|
||||
</pre><span style="font-family:monospace">EList</span> is a list of all non-CDATA elements of ECList.</dd><dt class="dt-description"></dt><dd class="dd-description"><code>get_path_s(El, Path) -> Res</code>
|
||||
<pre class="verbatim">El = XMLElement
|
||||
Path = [PathItem]
|
||||
PathItem = PathElem | PathAttr | PathCDATA
|
||||
PathElem = {elem, Name}
|
||||
@@ -284,57 +304,60 @@ PathAttr = {attr, Name}
|
||||
PathCDATA = cdata
|
||||
Name = string()
|
||||
Res = string() | XMLElement
|
||||
</PRE>If <TT>Path</TT> is empty, then returns <TT>El</TT>. Else sequentially
|
||||
consider elements of <TT>Path</TT>. Each element is one of:
|
||||
<DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>{elem, Name}</CODE> <TT>Name</TT> is name of subelement of
|
||||
<TT>El</TT>, if such element exists, then this element considered in
|
||||
</pre>If <span style="font-family:monospace">Path</span> is empty, then returns <span style="font-family:monospace">El</span>. Else sequentially
|
||||
consider elements of <span style="font-family:monospace">Path</span>. Each element is one of:
|
||||
<dl class="description"><dt class="dt-description">
|
||||
</dt><dd class="dd-description"><code>{elem, Name}</code> <span style="font-family:monospace">Name</span> is name of subelement of
|
||||
<span style="font-family:monospace">El</span>, if such element exists, then this element considered in
|
||||
following steps, else returns empty string.
|
||||
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>{attr, Name}</CODE> If <TT>El</TT> have attribute <TT>Name</TT>, then
|
||||
</dd><dt class="dt-description"></dt><dd class="dd-description"><code>{attr, Name}</code> If <span style="font-family:monospace">El</span> have attribute <span style="font-family:monospace">Name</span>, then
|
||||
returns value of this attribute, else returns empty string.
|
||||
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>cdata</CODE> Returns CDATA of <TT>El</TT>.
|
||||
</DD></DL></DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description">TODO:
|
||||
<PRE CLASS="verbatim"> get_cdata/1, get_tag_cdata/1
|
||||
</dd><dt class="dt-description"></dt><dd class="dd-description"><code>cdata</code> Returns CDATA of <span style="font-family:monospace">El</span>.
|
||||
</dd></dl></dd><dt class="dt-description"></dt><dd class="dd-description">TODO:
|
||||
<pre class="verbatim"> get_cdata/1, get_tag_cdata/1
|
||||
get_attr/2, get_attr_s/2
|
||||
get_tag_attr/2, get_tag_attr_s/2
|
||||
get_subtag/2
|
||||
</PRE></DD></DL><!--TOC section Module <TT>xml_stream</TT>-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc12">7</A>  Module <TT>xml_stream</TT></H2><!--SEC END --><P>
|
||||
<A NAME="xmlstreammod"></A></P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>parse_element(Str) -> XMLElement | {error, Err}</CODE>
|
||||
<PRE CLASS="verbatim">Str = string()
|
||||
</pre></dd></dl>
|
||||
<!--TOC section id="sec13" Module <span style="font-family:monospace">xml_stream</span>-->
|
||||
<h2 id="sec13" class="section">7  Module <span style="font-family:monospace">xml_stream</span></h2><!--SEC END --><p>
|
||||
<a id="xmlstreammod"></a></p><dl class="description"><dt class="dt-description">
|
||||
</dt><dd class="dd-description"><code>parse_element(Str) -> XMLElement | {error, Err}</code>
|
||||
<pre class="verbatim">Str = string()
|
||||
Err = term()
|
||||
</PRE>Parses <TT>Str</TT> using XML parser, returns either parsed element or error
|
||||
</pre>Parses <span style="font-family:monospace">Str</span> using XML parser, returns either parsed element or error
|
||||
tuple.
|
||||
</DD></DL><!--TOC section Modules-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc13">8</A>  Modules</H2><!--SEC END --><P>
|
||||
<A NAME="emods"></A></P><!--TOC subsection Module gen_iq_handler-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc14">8.1</A>  Module gen_iq_handler</H3><!--SEC END --><P>
|
||||
<A NAME="geniqhandl"></A></P><P>The module <CODE>gen_iq_handler</CODE> allows to easily write handlers for IQ packets
|
||||
of particular XML namespaces that addressed to server or to users bare JIDs.</P><P>In this module the following functions are defined:
|
||||
</P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>add_iq_handler(Component, Host, NS, Module, Function, Type)</CODE>
|
||||
<PRE CLASS="verbatim">Component = Module = Function = atom()
|
||||
</dd></dl>
|
||||
<!--TOC section id="sec14" Modules-->
|
||||
<h2 id="sec14" class="section">8  Modules</h2><!--SEC END --><p>
|
||||
<a id="emods"></a></p>
|
||||
<!--TOC subsection id="sec15" Module gen_iq_handler-->
|
||||
<h3 id="sec15" class="subsection">8.1  Module gen_iq_handler</h3><!--SEC END --><p>
|
||||
<a id="geniqhandl"></a></p><p>The module <code>gen_iq_handler</code> allows to easily write handlers for IQ packets
|
||||
of particular XML namespaces that addressed to server or to users bare JIDs.</p><p>In this module the following functions are defined:
|
||||
</p><dl class="description"><dt class="dt-description">
|
||||
</dt><dd class="dd-description"><code>add_iq_handler(Component, Host, NS, Module, Function, Type)</code>
|
||||
<pre class="verbatim">Component = Module = Function = atom()
|
||||
Host = NS = string()
|
||||
Type = no_queue | one_queue | parallel
|
||||
</PRE>Registers function <CODE>Module:Function</CODE> as handler for IQ packets on
|
||||
virtual host <CODE>Host</CODE> that contain child of namespace <CODE>NS</CODE> in
|
||||
<CODE>Component</CODE>. Queueing discipline is <CODE>Type</CODE>. There are at least
|
||||
</pre>Registers function <code>Module:Function</code> as handler for IQ packets on
|
||||
virtual host <code>Host</code> that contain child of namespace <code>NS</code> in
|
||||
<code>Component</code>. Queueing discipline is <code>Type</code>. There are at least
|
||||
two components defined:
|
||||
<DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>ejabberd_local</CODE> Handles packets that addressed to server JID;
|
||||
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>ejabberd_sm</CODE> Handles packets that addressed to users bare JIDs.
|
||||
</DD></DL>
|
||||
</DD><DT CLASS="dt-description"></DT><DD CLASS="dd-description"><CODE>remove_iq_handler(Component, Host, NS)</CODE>
|
||||
<PRE CLASS="verbatim">Component = atom()
|
||||
<dl class="description"><dt class="dt-description">
|
||||
</dt><dd class="dd-description"><code>ejabberd_local</code> Handles packets that addressed to server JID;
|
||||
</dd><dt class="dt-description"></dt><dd class="dd-description"><code>ejabberd_sm</code> Handles packets that addressed to users bare JIDs.
|
||||
</dd></dl>
|
||||
</dd><dt class="dt-description"></dt><dd class="dd-description"><code>remove_iq_handler(Component, Host, NS)</code>
|
||||
<pre class="verbatim">Component = atom()
|
||||
Host = NS = string()
|
||||
</PRE>Removes IQ handler on virtual host <CODE>Host</CODE> for namespace <CODE>NS</CODE> from
|
||||
<CODE>Component</CODE>.
|
||||
</DD></DL><P>Handler function must have the following type:
|
||||
</P><DL CLASS="description"><DT CLASS="dt-description">
|
||||
</DT><DD CLASS="dd-description"><CODE>Module:Function(From, To, IQ)</CODE>
|
||||
<PRE CLASS="verbatim">From = To = jid()
|
||||
</PRE></DD></DL><PRE CLASS="verbatim">-module(mod_cputime).
|
||||
</pre>Removes IQ handler on virtual host <code>Host</code> for namespace <code>NS</code> from
|
||||
<code>Component</code>.
|
||||
</dd></dl><p>Handler function must have the following type:
|
||||
</p><dl class="description"><dt class="dt-description">
|
||||
</dt><dd class="dd-description"><code>Module:Function(From, To, IQ)</code>
|
||||
<pre class="verbatim">From = To = jid()
|
||||
</pre></dd></dl><pre class="verbatim">-module(mod_cputime).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
@@ -368,9 +391,10 @@ process_local_iq(From, To, {iq, ID, Type, XMLNS, SubEl}) ->
|
||||
[{"xmlns", ?NS_CPUTIME}],
|
||||
[{xmlelement, "cputime", [], [{xmlcdata, SCPUTime}]}]}]}
|
||||
end.
|
||||
</PRE><!--TOC subsection Services-->
|
||||
<H3 CLASS="subsection"><!--SEC ANCHOR --><A NAME="htoc15">8.2</A>  Services</H3><!--SEC END --><P>
|
||||
<A NAME="services"></A></P><PRE CLASS="verbatim">-module(mod_echo).
|
||||
</pre>
|
||||
<!--TOC subsection id="sec16" Services-->
|
||||
<h3 id="sec16" class="subsection">8.2  Services</h3><!--SEC END --><p>
|
||||
<a id="services"></a></p><pre class="verbatim">-module(mod_echo).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
@@ -404,10 +428,10 @@ stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
Proc ! stop,
|
||||
{wait, Proc}.
|
||||
</PRE><!--CUT END -->
|
||||
</pre><!--CUT END -->
|
||||
<!--HTMLFOOT-->
|
||||
<!--ENDHTML-->
|
||||
<!--FOOTER-->
|
||||
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
|
||||
</EM><A HREF="http://hevea.inria.fr/index.html"><EM>H</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>V</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
|
||||
</HTML>
|
||||
<hr style="height:2"><blockquote class="quote"><em>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
|
||||
</em><a href="http://hevea.inria.fr/index.html"><em>H</em><em><span style="font-size:small"><sup>E</sup></span></em><em>V</em><em><span style="font-size:small"><sup>E</sup></span></em><em>A</em></a><em>.</em></blockquote></body>
|
||||
</html>
|
||||
|
||||
+97
-86
@@ -1,40 +1,45 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Ejabberd 2.1.12 Feature Sheet
|
||||
</TITLE>
|
||||
|
||||
<META http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
<META name="GENERATOR" content="hevea 1.10">
|
||||
<STYLE type="text/css">
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
|
||||
<meta name="generator" content="hevea 2.09">
|
||||
<style type="text/css">
|
||||
.li-itemize{margin:1ex 0ex;}
|
||||
.li-enumerate{margin:1ex 0ex;}
|
||||
.dd-description{margin:0ex 0ex 1ex 4ex;}
|
||||
.dt-description{margin:0ex;}
|
||||
.toc{list-style:none;}
|
||||
.footnotetext{margin:0ex; padding:0ex;}
|
||||
div.footnotetext P{margin:0px; text-indent:1em;}
|
||||
.thefootnotes{text-align:left;margin:0ex;}
|
||||
.dt-thefootnotes{margin:0em;}
|
||||
.dd-thefootnotes{margin:0em 0em 0em 2em;}
|
||||
.footnoterule{margin:1em auto 1em 0px;width:50%;}
|
||||
.caption{padding-left:2ex; padding-right:2ex; margin-left:auto; margin-right:auto}
|
||||
.title{margin:2ex auto;text-align:center}
|
||||
.titlemain{margin:1ex 2ex 2ex 1ex;}
|
||||
.titlerest{margin:0ex 2ex;}
|
||||
.center{text-align:center;margin-left:auto;margin-right:auto;}
|
||||
.flushleft{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
.flushright{text-align:right;margin-left:auto;margin-right:0ex;}
|
||||
DIV TABLE{margin-left:inherit;margin-right:inherit;}
|
||||
PRE{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
BLOCKQUOTE{margin-left:4ex;margin-right:4ex;text-align:left;}
|
||||
TD P{margin:0px;}
|
||||
div table{margin-left:inherit;margin-right:inherit;margin-bottom:2px;margin-top:2px}
|
||||
td table{margin:auto;}
|
||||
table{border-collapse:collapse;}
|
||||
td{padding:0;}
|
||||
.cellpadding0 tr td{padding:0;}
|
||||
.cellpadding1 tr td{padding:1px;}
|
||||
pre{text-align:left;margin-left:0ex;margin-right:auto;}
|
||||
blockquote{margin-left:4ex;margin-right:4ex;text-align:left;}
|
||||
td p{margin:0px;}
|
||||
.boxed{border:1px solid black}
|
||||
.textboxed{border:1px solid black}
|
||||
.vbar{border:none;width:2px;background-color:black;}
|
||||
.hbar{border:none;height:2px;width:100%;background-color:black;}
|
||||
.hfill{border:none;height:1px;width:200%;background-color:black;}
|
||||
.vdisplay{border-collapse:separate;border-spacing:2px;width:auto; empty-cells:show; border:2px solid red;}
|
||||
.vdcell{white-space:nowrap;padding:0px;width:auto; border:2px solid green;}
|
||||
.vdcell{white-space:nowrap;padding:0px; border:2px solid green;}
|
||||
.display{border-collapse:separate;border-spacing:2px;width:auto; border:none;}
|
||||
.dcell{white-space:nowrap;padding:0px;width:auto; border:none;}
|
||||
.dcell{white-space:nowrap;padding:0px; border:none;}
|
||||
.dcenter{margin:0ex auto;}
|
||||
.vdcenter{border:solid #FF8000 2px; margin:0ex auto;}
|
||||
.minipage{text-align:left; margin-left:0em; margin-right:auto;}
|
||||
@@ -44,89 +49,95 @@ TD P{margin:0px;}
|
||||
.theorem{text-align:left;margin:1ex auto 1ex 0ex;}
|
||||
.part{margin:2ex auto;text-align:center}
|
||||
SPAN{width:20%; float:right; text-align:left; margin-left:auto;}
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
<BODY >
|
||||
</style>
|
||||
<title>Ejabberd community 14.05-120-gedfb5fc Feature Sheet
|
||||
</title>
|
||||
</head>
|
||||
<body >
|
||||
<!--HEVEA command line is: /usr/bin/hevea -fix -pedantic features.tex -->
|
||||
<!--CUT DEF section 1 --><P><A NAME="titlepage"></A>
|
||||
<!--CUT STYLE article--><!--CUT DEF section 1 --><p><a id="titlepage"></a>
|
||||
|
||||
</P><TABLE CLASS="title"><TR><TD><H1 CLASS="titlemain">Ejabberd 2.1.12 Feature Sheet</H1><H3 CLASS="titlerest">Sander Devrieze<BR>
|
||||
<A HREF="mailto:s.devrieze@pandora.be"><TT>mailto:s.devrieze@pandora.be</TT></A><BR>
|
||||
<A HREF="xmpp:sander@devrieze.dyndns.org"><TT>xmpp:sander@devrieze.dyndns.org</TT></A></H3></TD></TR>
|
||||
</TABLE><DIV CLASS="center">
|
||||
</p><table class="title"><tr><td style="padding:1ex"><h1 class="titlemain">Ejabberd community 14.05-120-gedfb5fc Feature Sheet</h1><h3 class="titlerest">Sander Devrieze <br>
|
||||
<a href="mailto:s.devrieze@pandora.be"><span style="font-family:monospace">mailto:s.devrieze@pandora.be</span></a> <br>
|
||||
<a href="xmpp:sander@devrieze.dyndns.org"><span style="font-family:monospace">xmpp:sander@devrieze.dyndns.org</span></a></h3></td></tr>
|
||||
</table><div class="center">
|
||||
|
||||
<IMG SRC="logo.png" ALT="logo.png">
|
||||
<img src="logo.png" alt="logo.png">
|
||||
|
||||
|
||||
</DIV><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I can thoroughly recommend ejabberd for ease of setup –
|
||||
Kevin Smith, Current maintainer of the Psi project</I></FONT></BLOCKQUOTE><P>Introduction
|
||||
<A NAME="intro"></A></P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. —
|
||||
Joeri</I></FONT></BLOCKQUOTE><P><TT>ejabberd</TT> is a <B><FONT SIZE=4><FONT COLOR="#001376">free and open source</FONT></FONT></B> instant messaging server written in <A HREF="http://www.erlang.org/">Erlang/OTP</A>.</P><P><TT>ejabberd</TT> is <B><FONT SIZE=4><FONT COLOR="#001376">cross-platform</FONT></FONT></B>, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</P><P><TT>ejabberd</TT> is designed to be a <B><FONT SIZE=4><FONT COLOR="#001376">rock-solid and feature rich</FONT></FONT></B> XMPP server.</P><P><TT>ejabberd</TT> is suitable for small deployments, whether they need to be <B><FONT SIZE=4><FONT COLOR="#001376">scalable</FONT></FONT></B> or not, as well as extremely big deployments.</P><!--TOC section Key Features-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc1"></A>Key Features</H2><!--SEC END --><P>
|
||||
<A NAME="keyfeatures"></A>
|
||||
</P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>Erlang seems to be tailor-made for writing stable, robust servers. —
|
||||
Peter Saint-André, Executive Director of the Jabber Software Foundation</I></FONT></BLOCKQUOTE><P><TT>ejabberd</TT> is:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
<B><FONT SIZE=4><FONT COLOR="#001376">Cross-platform:</FONT></FONT></B> <TT>ejabberd</TT> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Distributed:</FONT></FONT></B> You can run <TT>ejabberd</TT> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Fault-tolerant:</FONT></FONT></B> You can deploy an <TT>ejabberd</TT> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced ‘on the fly’.</LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Administrator Friendly:</FONT></FONT></B> <TT>ejabberd</TT> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</div><blockquote class="quotation"><span style="color:#921700"><span style="font-style:italic">I can thoroughly recommend ejabberd for ease of setup –
|
||||
Kevin Smith, Current maintainer of the Psi project</span></span></blockquote><p>Introduction
|
||||
<a id="intro"></a></p><blockquote class="quotation"><span style="color:#921700"><span style="font-style:italic">I just tried out ejabberd and was impressed both by ejabberd itself and the language it is written in, Erlang. —
|
||||
Joeri</span></span></blockquote><p><span style="font-family:monospace">ejabberd</span> is a <span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">free and open source</span></span></span> instant messaging server written in <a href="http://www.erlang.org/">Erlang/OTP</a>.</p><p><span style="font-family:monospace">ejabberd</span> is <span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">cross-platform</span></span></span>, distributed, fault-tolerant, and based on open standards to achieve real-time communication.</p><p><span style="font-family:monospace">ejabberd</span> is designed to be a <span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">rock-solid and feature rich</span></span></span> XMPP server.</p><p><span style="font-family:monospace">ejabberd</span> is suitable for small deployments, whether they need to be <span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">scalable</span></span></span> or not, as well as extremely big deployments.</p>
|
||||
<!--TOC section id="sec1" Key Features-->
|
||||
<h2 id="sec1" class="section">Key Features</h2><!--SEC END --><p>
|
||||
<a id="keyfeatures"></a>
|
||||
</p><blockquote class="quotation"><span style="color:#921700"><span style="font-style:italic">Erlang seems to be tailor-made for writing stable, robust servers. —
|
||||
Peter Saint-André, Executive Director of the Jabber Software Foundation</span></span></blockquote><p><span style="font-family:monospace">ejabberd</span> is:
|
||||
</p><ul class="itemize"><li class="li-itemize">
|
||||
<span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Cross-platform:</span></span></span> <span style="font-family:monospace">ejabberd</span> runs under Microsoft Windows and Unix derived systems such as Linux, FreeBSD and NetBSD.</li><li class="li-itemize"><span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Distributed:</span></span></span> You can run <span style="font-family:monospace">ejabberd</span> on a cluster of machines and all of them will serve the same Jabber domain(s). When you need more capacity you can simply add a new cheap node to your cluster. Accordingly, you do not need to buy an expensive high-end machine to support tens of thousands concurrent users.</li><li class="li-itemize"><span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Fault-tolerant:</span></span></span> You can deploy an <span style="font-family:monospace">ejabberd</span> cluster so that all the information required for a properly working service will be replicated permanently on all nodes. This means that if one of the nodes crashes, the others will continue working without disruption. In addition, nodes also can be added or replaced ‘on the fly’.</li><li class="li-itemize"><span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Administrator Friendly:</span></span></span> <span style="font-family:monospace">ejabberd</span> is built on top of the Open Source Erlang. As a result you do not need to install an external database, an external web server, amongst others because everything is already included, and ready to run out of the box. Other administrator benefits include:
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Comprehensive documentation.
|
||||
</LI><LI CLASS="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </LI><LI CLASS="li-itemize">Web Administration.
|
||||
</LI><LI CLASS="li-itemize">Shared Roster Groups.
|
||||
</LI><LI CLASS="li-itemize">Command line administration tool. </LI><LI CLASS="li-itemize">Can integrate with existing authentication mechanisms.
|
||||
</LI><LI CLASS="li-itemize">Capability to send announce messages.
|
||||
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Internationalized:</FONT></FONT></B> <TT>ejabberd</TT> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
Translated to 25 languages. </LI><LI CLASS="li-itemize">Support for <A HREF="http://www.ietf.org/rfc/rfc3490.txt">IDNA</A>.
|
||||
</LI></UL></LI><LI CLASS="li-itemize"><B><FONT SIZE=4><FONT COLOR="#001376">Open Standards:</FONT></FONT></B> <TT>ejabberd</TT> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">Straightforward installers for Linux, Mac OS X, and Windows. </li><li class="li-itemize">Web Administration.
|
||||
</li><li class="li-itemize">Shared Roster Groups.
|
||||
</li><li class="li-itemize">Command line administration tool. </li><li class="li-itemize">Can integrate with existing authentication mechanisms.
|
||||
</li><li class="li-itemize">Capability to send announce messages.
|
||||
</li></ul></li><li class="li-itemize"><span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Internationalized:</span></span></span> <span style="font-family:monospace">ejabberd</span> leads in internationalization. Hence it is very well suited in a globalized world. Related features are:
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Translated to 25 languages. </li><li class="li-itemize">Support for <a href="http://www.ietf.org/rfc/rfc3490.txt">IDNA</a>.
|
||||
</li></ul></li><li class="li-itemize"><span style="font-weight:bold"><span style="font-size:large"><span style="color:#001376">Open Standards:</span></span></span> <span style="font-family:monospace">ejabberd</span> is the first Open Source Jabber server claiming to fully comply to the XMPP standard.
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Fully XMPP compliant.
|
||||
</LI><LI CLASS="li-itemize">XML-based protocol.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.ejabberd.im/protocols">Many protocols supported</A>.
|
||||
</LI></UL></LI></UL><!--TOC section Additional Features-->
|
||||
<H2 CLASS="section"><!--SEC ANCHOR --><A NAME="htoc2"></A>Additional Features</H2><!--SEC END --><P>
|
||||
<A NAME="addfeatures"></A>
|
||||
</P><BLOCKQUOTE CLASS="quotation"><FONT COLOR="#921700"><I>ejabberd is making inroads to solving the "buggy incomplete server" problem —
|
||||
Justin Karneges, Founder of the Psi and the Delta projects</I></FONT></BLOCKQUOTE><P>Moreover, <TT>ejabberd</TT> comes with a wide range of other state-of-the-art features:
|
||||
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">XML-based protocol.
|
||||
</li><li class="li-itemize"><a href="http://www.ejabberd.im/protocols">Many protocols supported</a>.
|
||||
</li></ul></li></ul>
|
||||
<!--TOC section id="sec2" Additional Features-->
|
||||
<h2 id="sec2" class="section">Additional Features</h2><!--SEC END --><p>
|
||||
<a id="addfeatures"></a>
|
||||
</p><blockquote class="quotation"><span style="color:#921700"><span style="font-style:italic">ejabberd is making inroads to solving the "buggy incomplete server" problem —
|
||||
Justin Karneges, Founder of the Psi and the Delta projects</span></span></blockquote><p>Moreover, <span style="font-family:monospace">ejabberd</span> comes with a wide range of other state-of-the-art features:
|
||||
</p><ul class="itemize"><li class="li-itemize">
|
||||
Modular
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Load only the modules you want.
|
||||
</LI><LI CLASS="li-itemize">Extend <TT>ejabberd</TT> with your own custom modules.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Security
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">Extend <span style="font-family:monospace">ejabberd</span> with your own custom modules.
|
||||
</li></ul>
|
||||
</li><li class="li-itemize">Security
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
SASL and STARTTLS for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize">STARTTLS and Dialback s2s connections.
|
||||
</LI><LI CLASS="li-itemize">Web Admin accessible via HTTPS secure access.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Databases
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">STARTTLS and Dialback s2s connections.
|
||||
</li><li class="li-itemize">Web Admin accessible via HTTPS secure access.
|
||||
</li></ul>
|
||||
</li><li class="li-itemize">Databases
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Internal database for fast deployment (Mnesia).
|
||||
</LI><LI CLASS="li-itemize">Native MySQL support.
|
||||
</LI><LI CLASS="li-itemize">Native PostgreSQL support.
|
||||
</LI><LI CLASS="li-itemize">ODBC data storage support.
|
||||
</LI><LI CLASS="li-itemize">Microsoft SQL Server support. </LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Authentication
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">Native MySQL support.
|
||||
</li><li class="li-itemize">Native PostgreSQL support.
|
||||
</li><li class="li-itemize">ODBC data storage support.
|
||||
</li><li class="li-itemize">Microsoft SQL Server support. </li><li class="li-itemize">Riak NoSQL database support.
|
||||
</li></ul>
|
||||
</li><li class="li-itemize">Authentication
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Internal Authentication.
|
||||
</LI><LI CLASS="li-itemize">PAM, LDAP and ODBC. </LI><LI CLASS="li-itemize">External Authentication script.
|
||||
</LI></UL>
|
||||
</LI><LI CLASS="li-itemize">Others
|
||||
<UL CLASS="itemize"><LI CLASS="li-itemize">
|
||||
</li><li class="li-itemize">PAM, LDAP, ODBC and Riak. </li><li class="li-itemize">External Authentication script.
|
||||
</li></ul>
|
||||
</li><li class="li-itemize">Others
|
||||
<ul class="itemize"><li class="li-itemize">
|
||||
Support for virtual hosting.
|
||||
</LI><LI CLASS="li-itemize">Compressing XML streams with Stream Compression (<A HREF="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</A>).
|
||||
</LI><LI CLASS="li-itemize">Statistics via Statistics Gathering (<A HREF="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</A>).
|
||||
</LI><LI CLASS="li-itemize">IPv6 support both for c2s and s2s connections.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</A> module with support for clustering and HTML logging. </LI><LI CLASS="li-itemize">Users Directory based on users vCards.
|
||||
</LI><LI CLASS="li-itemize"><A HREF="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</A> component with support for <A HREF="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</A>.
|
||||
</LI><LI CLASS="li-itemize">Support for web clients: <A HREF="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</A> and <A HREF="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</A> services.
|
||||
</LI><LI CLASS="li-itemize">IRC transport.
|
||||
</LI><LI CLASS="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
||||
</LI></UL>
|
||||
</LI></UL><!--CUT END -->
|
||||
</li><li class="li-itemize">Compressing XML streams with Stream Compression (<a href="http://www.xmpp.org/extensions/xep-0138.html">XEP-0138</a>).
|
||||
</li><li class="li-itemize">Statistics via Statistics Gathering (<a href="http://www.xmpp.org/extensions/xep-0039.html">XEP-0039</a>).
|
||||
</li><li class="li-itemize">IPv6 support both for c2s and s2s connections.
|
||||
</li><li class="li-itemize"><a href="http://www.xmpp.org/extensions/xep-0045.html">Multi-User Chat</a> module with support for clustering and HTML logging. </li><li class="li-itemize">Users Directory based on users vCards.
|
||||
</li><li class="li-itemize"><a href="http://www.xmpp.org/extensions/xep-0060.html">Publish-Subscribe</a> component with support for <a href="http://www.xmpp.org/extensions/xep-0163.html">Personal Eventing via Pubsub</a>.
|
||||
</li><li class="li-itemize">Support for web clients: <a href="http://www.xmpp.org/extensions/xep-0025.html">HTTP Polling</a> and <a href="http://www.xmpp.org/extensions/xep-0206.html">HTTP Binding (BOSH)</a> services.
|
||||
</li><li class="li-itemize">IRC transport.
|
||||
</li><li class="li-itemize">SIP support.
|
||||
</li><li class="li-itemize">Component support: interface with networks such as AIM, ICQ and MSN installing special tranports.
|
||||
</li></ul>
|
||||
</li></ul><!--CUT END -->
|
||||
<!--HTMLFOOT-->
|
||||
<!--ENDHTML-->
|
||||
<!--FOOTER-->
|
||||
<HR SIZE=2><BLOCKQUOTE CLASS="quote"><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
|
||||
</EM><A HREF="http://hevea.inria.fr/index.html"><EM>H</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>V</EM><EM><FONT SIZE=2><sup>E</sup></FONT></EM><EM>A</EM></A><EM>.</EM></BLOCKQUOTE></BODY>
|
||||
</HTML>
|
||||
<hr style="height:2"><blockquote class="quote"><em>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
|
||||
</em><a href="http://hevea.inria.fr/index.html"><em>H</em><em><span style="font-size:small"><sup>E</sup></span></em><em>V</em><em><span style="font-size:small"><sup>E</sup></span></em><em>A</em></a><em>.</em></blockquote></body>
|
||||
</html>
|
||||
|
||||
-5036
File diff suppressed because it is too large
Load Diff
+239
-33
@@ -66,12 +66,14 @@
|
||||
\newcommand{\module}[1]{\texttt{#1}}
|
||||
\newcommand{\modadhoc}{\module{mod\_adhoc}}
|
||||
\newcommand{\modannounce}{\module{mod\_announce}}
|
||||
\newcommand{\modclientstate}{\module{mod\_client\_state}}
|
||||
\newcommand{\modblocking}{\module{mod\_blocking}}
|
||||
\newcommand{\modcaps}{\module{mod\_caps}}
|
||||
\newcommand{\modcarboncopy}{\module{mod\_carboncopy}}
|
||||
\newcommand{\modconfigure}{\module{mod\_configure}}
|
||||
\newcommand{\moddisco}{\module{mod\_disco}}
|
||||
\newcommand{\modecho}{\module{mod\_echo}}
|
||||
\newcommand{\modfailban}{\module{mod\_fail2ban}}
|
||||
\newcommand{\modhttpbind}{\module{mod\_http\_bind}}
|
||||
\newcommand{\modhttpfileserver}{\module{mod\_http\_fileserver}}
|
||||
\newcommand{\modirc}{\module{mod\_irc}}
|
||||
@@ -322,13 +324,12 @@ To compile \ejabberd{} on a `Unix-like' operating system, you need:
|
||||
\item GCC
|
||||
\item Libexpat 1.95 or higher
|
||||
\item Erlang/OTP R15B or higher.
|
||||
\item Libyaml 1.4 or higher
|
||||
\item Libyaml 0.1.4 or higher
|
||||
\item OpenSSL 0.9.8 or higher, for STARTTLS, SASL and SSL encryption.
|
||||
\item Zlib 1.2.3 or higher, for Stream Compression support (\xepref{0138}). Optional.
|
||||
\item PAM library. Optional. For Pluggable Authentication Modules (PAM). See section \ref{pam}.
|
||||
\item GNU Iconv 1.8 or higher, for the IRC Transport (mod\_irc). Optional. Not needed on systems with GNU Libc. See section \ref{modirc}.
|
||||
\item ImageMagick's Convert program. Optional. For CAPTCHA challenges. See section \ref{captcha}.
|
||||
\item exmpp 0.9.6 or higher. Optional. For import/export user data with \xepref{0227} XML files.
|
||||
\end{itemize}
|
||||
|
||||
\makesubsection{download}{Download Source Code}
|
||||
@@ -397,9 +398,6 @@ Some options that you may be interested in modifying:
|
||||
\titem{--enable-zlib}
|
||||
Enable Stream Compression (XEP-0138) using zlib.
|
||||
|
||||
\titem{--enable-stun}
|
||||
Enable STUN/TURN support (see section \ref{stun}).
|
||||
|
||||
\titem{--enable-iconv}
|
||||
Enable iconv support. This is needed for \term{mod\_irc} (see seciont \ref{modirc}).
|
||||
|
||||
@@ -907,7 +905,7 @@ The available modules, their purpose and the options allowed by each one are:
|
||||
Options: \texttt{access\_commands}, \texttt{maxsessions}, \texttt{timeout}.\\
|
||||
You can find option explanations, example configuration in old and new format,
|
||||
and example calls in several languages in the old
|
||||
\footahref{https://raw.github.com/processone/ejabberd-contrib/master/ejabberd\_xmlrpc/README.txt}{ejabberd\_xmlrpc README.txt}
|
||||
\footahref{http://www.ejabberd.im/ejabberd\_xmlrpc}{ejabberd\_xmlrpc documentation}.
|
||||
\end{description}
|
||||
|
||||
|
||||
@@ -930,8 +928,10 @@ This is a detailed description of each option allowed by the listening modules:
|
||||
\titem{ciphers: Ciphers} OpenSSL ciphers list in the same format accepted by
|
||||
`\verb|openssl ciphers|' command.
|
||||
\titem{protocol\_options: ProtocolOpts} \ind{options!protocol\_options}
|
||||
List of general options relating to SSL/TLS. These map to \verb|<a href="https://www.openssl.org/docs/ssl/SSL_CTX_set_options.html">OpenSSL's set_options()</a>|.
|
||||
For a full list of options available in ejabberd, \verb|<a href="https://github.com/processone/tls/blob/master/c_src/options.h">see the source</a>|.
|
||||
List of general options relating to SSL/TLS. These map to
|
||||
\footahref{https://www.openssl.org/docs/ssl/SSL\_CTX\_set\_options.html}{OpenSSL's set\_options()}.
|
||||
For a full list of options available in ejabberd,
|
||||
\footahref{https://github.com/processone/tls/blob/master/c\_src/options.h}{see the source}.
|
||||
The default entry is: \verb|"no_sslv2"|
|
||||
\titem{default\_host: undefined|HostName\}}
|
||||
If the HTTP request received by ejabberd contains the HTTP header \term{Host}
|
||||
@@ -981,10 +981,10 @@ This is a detailed description of each option allowed by the listening modules:
|
||||
\titem{max\_ack\_queue: Size}
|
||||
This option specifies the maximum number of unacknowledged stanzas
|
||||
queued for possible retransmission if \term{stream\_management} is
|
||||
enabled. When the limit is reached, the first stanza is dropped from
|
||||
the queue before adding the next one. This option can be specified
|
||||
for \term{ejabberd\_c2s} listeners. The allowed values are positive
|
||||
integers and \term{infinity}. Default value: \term{500}.
|
||||
enabled. When the limit is exceeded, the client session is
|
||||
terminated. This option can be specified for \term{ejabberd\_c2s}
|
||||
listeners. The allowed values are positive integers and
|
||||
\term{infinity}. Default value: \term{500}.
|
||||
\titem{max\_fsm\_queue: Size}
|
||||
This option specifies the maximum number of elements in the queue of the FSM
|
||||
(Finite State Machine).
|
||||
@@ -1022,7 +1022,7 @@ request_handlers:
|
||||
/"a"/"b": mod_foo
|
||||
/"http-bind": mod_http_bind
|
||||
\end{verbatim}
|
||||
\titem{resend\_on\_timeout: true|false}
|
||||
\titem{resend\_on\_timeout: true|false|if\_offline}
|
||||
If \term{stream\_management} is enabled and this option is set to
|
||||
\term{true}, any stanzas that weren't acknowledged by the client
|
||||
will be resent on session timeout. This behavior might often be
|
||||
@@ -1030,8 +1030,12 @@ request_handlers:
|
||||
circumstances. For example, a message that was sent to two resources
|
||||
might get resent to one of them if the other one timed out.
|
||||
Therefore, the default value for this option is \term{false}, which
|
||||
tells ejabberd to generate an error message instead. The option can
|
||||
be specified for \term{ejabberd\_c2s} listeners.
|
||||
tells ejabberd to generate an error message instead. As an
|
||||
alternative, the option may be set to \term{if\_offline}. In this
|
||||
case, unacknowledged stanzas are resent only if no other resource is
|
||||
online when the session times out. Otherwise, error messages are
|
||||
generated. The option can be specified for \term{ejabberd\_c2s}
|
||||
listeners.
|
||||
\titem{resume\_timeout: Seconds}
|
||||
This option configures the number of seconds until a session times
|
||||
out if the connection is lost. During this period of time, a client
|
||||
@@ -1064,7 +1068,7 @@ request_handlers:
|
||||
You can define a certificate file for a specific domain using the global option \option{domain\_certfile}.
|
||||
\titem{stream\_management: true|false}
|
||||
Setting this option to \term{false} disables ejabberd's support for
|
||||
\ind{protocols!XEP-0198: Stream Management}. It can be specified for
|
||||
Stream Management (\xepref{0198}). It can be specified for
|
||||
\term{ejabberd\_c2s} listeners. The default value is \term{true}.
|
||||
\titem{timeout: Integer} \ind{options!timeout}
|
||||
Timeout of the connections, expressed in milliseconds.
|
||||
@@ -1110,8 +1114,10 @@ There are some additional global options that can be specified in the ejabberd c
|
||||
\titem{s2s\_ciphers: Ciphers} \ind{options!s2s\_ciphers} OpenSSL ciphers list
|
||||
in the same format accepted by `\verb|openssl ciphers|' command.
|
||||
\titem{s2s\_protocol\_options: ProtocolOpts} \ind{options!s2s\_protocol\_options}
|
||||
List of general options relating to SSL/TLS. These map to \verb|<a href="https://www.openssl.org/docs/ssl/SSL_CTX_set_options.html">OpenSSL's set_options()</a>|.
|
||||
For a full list of options available in ejabberd, \verb|<a href="https://github.com/processone/tls/blob/protocol_options/c_src/options.h">see the source</a>|.
|
||||
List of general options relating to SSL/TLS. These map to
|
||||
\footahref{https://www.openssl.org/docs/ssl/SSL\_CTX\_set\_options.html}{OpenSSL's set\_options()}.
|
||||
For a full list of options available in ejabberd,
|
||||
\footahref{https://github.com/processone/tls/blob/master/c\_src/options.h}{see the source}.
|
||||
The default entry is: \verb|"no_sslv2"|
|
||||
\titem{outgoing\_s2s\_families: [Family, ...]} \ind{options!outgoing\_s2s\_families}
|
||||
Specify which address families to try, in what order.
|
||||
@@ -1288,11 +1294,11 @@ access:
|
||||
all: normal
|
||||
xmlrpc_access:
|
||||
xmlrpc_bot: allow
|
||||
s2s_access:
|
||||
s2s:
|
||||
trusted_servers: allow
|
||||
all: deny
|
||||
s2s_certfile: "/path/to/ssl.pem"
|
||||
s2s_policy: s2s_access
|
||||
s2s_access: s2s
|
||||
s2s_use_starttls: required_trusted
|
||||
listen:
|
||||
-
|
||||
@@ -1447,6 +1453,11 @@ The FQDN is used to authenticate some clients that use the DIGEST-MD5 SASL mecha
|
||||
The option syntax is:
|
||||
\esyntax{fqdn: undefined|FqdnString|[FqdnString]}
|
||||
|
||||
The option \option{disable\_sasl\_mechanisms} specifies a list of SASL
|
||||
mechanisms that should \emph{not} be offered to the client. The mechanisms can
|
||||
be listed as lowercase or uppercase strings. The option syntax is:
|
||||
\esyntax{disable\_sasl\_mechanisms: [Mechanism, ...]}
|
||||
|
||||
\makesubsubsection{internalauth}{Internal}
|
||||
\ind{internal authentication}\ind{Mnesia}
|
||||
|
||||
@@ -2023,10 +2034,10 @@ The specific configurable options are:
|
||||
\titem{turn\_max\_port: Integer}
|
||||
Together with \option{turn\_min\_port} forms port range to allocate from.
|
||||
The default is 65535. Implies \term{use\_turn}.
|
||||
\titem{turn\_max\_allocations: Integer|unlimited}
|
||||
\titem{turn\_max\_allocations: Integer|infinity}
|
||||
Maximum number of TURN allocations available from the particular IP address.
|
||||
The default value is 10. Implies \term{use\_turn}.
|
||||
\titem{turn\_max\_permissions: Integer|unlimited}
|
||||
\titem{turn\_max\_permissions: Integer|infinity}
|
||||
Maximum number of TURN permissions available from the particular IP address.
|
||||
The default value is 10. Implies \term{use\_turn}.
|
||||
\titem{auth\_type: user|anonymous}
|
||||
@@ -2294,7 +2305,7 @@ listen:
|
||||
%TODO: this whole section is not yet 100% optimized
|
||||
|
||||
\ejabberd{} uses its internal Mnesia database by default. However, it is
|
||||
possible to use a relational database or an LDAP server to store persistent,
|
||||
possible to use a relational database, key-value storage or an LDAP server to store persistent,
|
||||
long-living data. \ejabberd{} is very flexible: you can configure different
|
||||
authentication methods for different virtual hosts, you can configure different
|
||||
authentication mechanisms for the same virtual host (fallback), you can set
|
||||
@@ -2307,6 +2318,7 @@ The following databases are supported by \ejabberd{}:
|
||||
\item \footahref{http://www.mysql.com/}{MySQL}
|
||||
\item \footahref{http://en.wikipedia.org/wiki/Open\_Database\_Connectivity}{Any ODBC compatible database}
|
||||
\item \footahref{http://www.postgresql.org/}{PostgreSQL}
|
||||
\item \footahref{http://basho.com/riak/}{Riak}
|
||||
\end{itemize}
|
||||
|
||||
The following LDAP servers are tested with \ejabberd{}:
|
||||
@@ -2661,6 +2673,79 @@ modules:
|
||||
...
|
||||
\end{verbatim}
|
||||
|
||||
\makesubsection{riak}{Riak}
|
||||
\ind{databases!Riak}
|
||||
|
||||
\footahref{http://basho.com/riak/}{Riak} is a distributed NoSQL key-value data store.
|
||||
The actual database access is defined in the options with \term{riak\_} prefix.
|
||||
|
||||
\makesubsubsection{riakconnection}{Connection}
|
||||
\ind{riak!connection}
|
||||
|
||||
The following paramaters are available:
|
||||
\begin{description}
|
||||
\titem{riak\_server: String} A hostname of the Riak server. The default is
|
||||
\term{``localhost''}.
|
||||
\titem{riak\_port: Port} The port where the Riak server is accepting connections.
|
||||
The defalt is 8087.
|
||||
\titem{riak\_pool\_size: N} By default \ejabberd{} opens 10 connections to
|
||||
the Riak server. You can change this number by using this option.
|
||||
\titem{riak\_start\_interval: N} If the connection to the Riak server fails,
|
||||
\ejabberd{} waits 30 seconds before retrying.
|
||||
You can modify this interval with this option.
|
||||
\end{description}
|
||||
|
||||
Example configuration:
|
||||
\begin{verbatim}
|
||||
riak_server: "riak.server.com"
|
||||
riak_port: 9097
|
||||
\end{verbatim}
|
||||
|
||||
\makesubsubsection{riakstorage}{Storage}
|
||||
\ind{riak!storage}
|
||||
|
||||
Several \ejabberd{} modules can be used to store information in Riak database.
|
||||
Refer to the corresponding module documentation to see if it supports such
|
||||
ability. To enable storage to Riak database, just make
|
||||
sure that your database is running well (see the next section), and add the
|
||||
module option \term{db\_type: riak}.
|
||||
|
||||
\makesubsubsection{riakconfiguration}{Riak Configuration}
|
||||
\ind{riak!configuration}
|
||||
|
||||
First, you need to configure Riak to use
|
||||
\footahref{http://en.wikipedia.org/wiki/LevelDB}{LevelDB} as a database backend.
|
||||
|
||||
If you are using Riak 2.x and higher, configure \term{storage\_backend} option
|
||||
of \term{/etc/riak/riak.conf} as follows:
|
||||
\begin{verbatim}
|
||||
...
|
||||
storage_backend = leveldb
|
||||
...
|
||||
\end{verbatim}
|
||||
|
||||
If you are using Riak 1.4.x and older, configure \term{storage\_backend} option
|
||||
of \term{/etc/riak/app.config} in the section \term{riak\_kv} as follows:
|
||||
\begin{verbatim}
|
||||
...
|
||||
{riak_kv, [
|
||||
...
|
||||
{storage_backend, riak_kv_eleveldb_backend},
|
||||
...
|
||||
\end{verbatim}
|
||||
|
||||
Second, Riak should be pointed to \ejabberd{} Erlang binary files (*.beam).
|
||||
As described in \ref{install}, by default those are located
|
||||
in \term{/lib/ejabberd/ebin} directory. So you
|
||||
should add the following to \term{/etc/riak/vm.args}:
|
||||
\begin{verbatim}
|
||||
...
|
||||
## Path to ejabberd beams in order to make map/reduce
|
||||
-pz /lib/ejabberd/ebin
|
||||
...
|
||||
\end{verbatim}
|
||||
Important notice: make sure Riak has at least read access to that directory.
|
||||
Otherwise its startup will likely fail.
|
||||
|
||||
\makesection{modules}{Modules Configuration}
|
||||
\ind{modules}
|
||||
@@ -2706,9 +2791,11 @@ The following table lists all modules included in \ejabberd{}.
|
||||
\hline \modblocking{} & Simple Communications Blocking (\xepref{0191}) & \modprivacy{} \\
|
||||
\hline \modcaps{} & Entity Capabilities (\xepref{0115}) & \\
|
||||
\hline \modcarboncopy{} & Message Carbons (\xepref{0280}) & \\
|
||||
\hline \ahrefloc{modclientstate}{\modclientstate{}} & Filter stanzas for inactive clients & \\
|
||||
\hline \modconfigure{} & Server configuration using Ad-Hoc & \modadhoc{} \\
|
||||
\hline \ahrefloc{moddisco}{\moddisco{}} & Service Discovery (\xepref{0030}) & \\
|
||||
\hline \ahrefloc{modecho}{\modecho{}} & Echoes XMPP stanzas & \\
|
||||
\hline \ahrefloc{modfail2ban}{\modfailban{}} & Bans IPs that show the malicious signs & \\
|
||||
\hline \ahrefloc{modhttpbind}{\modhttpbind{}} & XMPP over Bosh service (HTTP Binding) & \\
|
||||
\hline \ahrefloc{modhttpfileserver}{\modhttpfileserver{}} & Small HTTP file server & \\
|
||||
\hline \ahrefloc{modirc}{\modirc{}} & IRC transport & \\
|
||||
@@ -2748,7 +2835,7 @@ The following table lists all modules included in \ejabberd{}.
|
||||
You can see which database backend each module needs by looking at the suffix:
|
||||
\begin{itemize}
|
||||
\item No suffix, this means that the module uses Erlang's built-in database
|
||||
Mnesia as backend, or a ODBC database in some cases (see~\ref{database}).
|
||||
Mnesia as backend, Riak key-value store or ODBC database (see~\ref{database}).
|
||||
\item `\_ldap', this means that the module needs an LDAP server as backend.
|
||||
\end{itemize}
|
||||
|
||||
@@ -2925,6 +3012,38 @@ Note that \modannounce{} can be resource intensive on large
|
||||
deployments as it can broadcast lot of messages. This module should be
|
||||
disabled for instances of \ejabberd{} with hundreds of thousands users.
|
||||
|
||||
\makesubsection{modclientstate}{\modclientstate{}}
|
||||
\ind{modules!\modclientstate{}}\ind{Client State Indication}
|
||||
\ind{protocols!XEP-0352: Client State Indication}
|
||||
|
||||
This module allows for queueing or dropping certain types of stanzas
|
||||
when a client indicates that the user is not actively using the client
|
||||
at the moment (see \xepref{0352}). This can save bandwidth and
|
||||
resources.
|
||||
|
||||
Options:
|
||||
\begin{description}
|
||||
\titem{drop\_chat\_states: true|false} \ind{options!drop\_chat\_states}
|
||||
Drop most "standalone" Chat State Notifications (as defined in
|
||||
\xepref{0085}) while a client indicates inactivity. The default value
|
||||
is \term{false}.
|
||||
\titem{queue\_presence: true|false} \ind{options!queue\_presence}
|
||||
While a client is inactive, queue presence stanzas that indicate
|
||||
(un)availability. The latest queued stanza of each contact is
|
||||
delivered as soon as the client becomes active again. The default
|
||||
value is \term{false}.
|
||||
\end{description}
|
||||
|
||||
Example:
|
||||
\begin{verbatim}
|
||||
modules:
|
||||
...
|
||||
mod_client_state:
|
||||
drop_chat_states: true
|
||||
queue_presence: true
|
||||
...
|
||||
\end{verbatim}
|
||||
|
||||
\makesubsection{moddisco}{\moddisco{}}
|
||||
\ind{modules!\moddisco{}}
|
||||
\ind{protocols!XEP-0030: Service Discovery}
|
||||
@@ -3043,6 +3162,30 @@ modules:
|
||||
...
|
||||
\end{verbatim}
|
||||
|
||||
\makesubsection{modfail2ban}{\modfailban{}}
|
||||
\ind{modules!\modfailban{}}\ind{modfail2ban}
|
||||
|
||||
The module bans IPs that show the malicious signs. Currently only C2S authentication
|
||||
failures are detected.
|
||||
|
||||
Available options:
|
||||
\begin{description}
|
||||
\titem{c2s\_auth\_ban\_lifetime: Seconds} The lifetime of the IP ban caused by too
|
||||
many C2S authentication failures. The default is 3600, i.e. one hour.
|
||||
\titem{c2s\_max\_auth\_failures: Integer} The number of C2S authentication failures to
|
||||
trigger the IP ban. The default is 20.
|
||||
\end{description}
|
||||
|
||||
Example:
|
||||
\begin{verbatim}
|
||||
modules:
|
||||
...
|
||||
mod_fail2ban:
|
||||
c2s_auth_block_lifetime: 7200
|
||||
c2s_max_auth_failures: 50
|
||||
...
|
||||
\end{verbatim}
|
||||
|
||||
\makesubsection{modhttpbind}{\modhttpbind{}}
|
||||
\ind{modules!\modhttpbind{}}\ind{modhttpbind}
|
||||
|
||||
@@ -3341,15 +3484,15 @@ Module options:
|
||||
\titem{max\_room\_id: Number} \ind{options!max\_room\_id}
|
||||
This option defines the maximum number of characters that Room ID
|
||||
can have when creating a new room.
|
||||
The default value is to not limit: infinite.
|
||||
The default value is to not limit: \term{infinity}.
|
||||
\titem{max\_room\_name: Number} \ind{options!max\_room\_name}
|
||||
This option defines the maximum number of characters that Room Name
|
||||
can have when configuring the room.
|
||||
The default value is to not limit: infinite.
|
||||
The default value is to not limit: \term{infinity}.
|
||||
\titem{max\_room\_desc: Number} \ind{options!max\_room\_desc}
|
||||
This option defines the maximum number of characters that Room Description
|
||||
can have when configuring the room.
|
||||
The default value is to not limit: infinite.
|
||||
The default value is to not limit: \term{infinity}.
|
||||
\titem{min\_message\_interval: Number} \ind{options!min\_message\_interval}
|
||||
This option defines the minimum interval between two messages send
|
||||
by an occupant in seconds. This option is global and valid for all
|
||||
@@ -3681,6 +3824,8 @@ online again. Thus it is very similar to how email works. Note that
|
||||
The default value is \term{max\_user\_offline\_messages}.
|
||||
Then you can define an access rule with a syntax similar to
|
||||
\term{max\_user\_sessions} (see \ref{configmaxsessions}).
|
||||
\titem{store\_empty\_body: true|false}\ind{options!store\_empty\_body} Whether or not
|
||||
to store messages with empty \term{<body/>} element. The default value is \term{true}.
|
||||
\end{description}
|
||||
|
||||
This example allows power users to have as much as 5000 offline messages,
|
||||
@@ -4787,16 +4932,40 @@ modules:
|
||||
|
||||
Options:
|
||||
\begin{description}
|
||||
\titem{record\_route: SIP\_URI}\ind{options!record\_route}When the option
|
||||
\term{always\_record\_route} is set or when SIP outbound
|
||||
is utilized \footahref{http://tools.ietf.org/html/rfc5626}{RFC 5626},
|
||||
\ejabberd{} inserts \term{Record-Route} header field with this \term{SIP\_URI}
|
||||
into a SIP message. The default is SIP URI constructed from the virtual host.
|
||||
\titem{always\_record\_route: true|false}\ind{options!always\_record\_route}
|
||||
Always insert \term{Record-Route} header into SIP messages. This approach allows
|
||||
to bypass NATs/firewalls a bit more easily. The default is \term{true}.
|
||||
\titem{routes: [SIP\_URI]}\ind{options!routes}You can set a list of SIP URIs of routes
|
||||
pointing to this proxy server. The default is a list of a SIP URI constructed
|
||||
from the virtual host.
|
||||
\titem{flow\_timeout\_udp: Seconds}For SIP outbound UDP connections set a keep-alive
|
||||
timer to \term{Seconds}. The default is 29.
|
||||
\titem{flow\_timeout\_tcp: Seconds}For SIP outbound TCP connections set a keep-alive
|
||||
timer to \term{Seconds}. The default is 120.
|
||||
\titem{via: [\{type: Type, host: Host, port: Port\}]}\ind{options!via}With
|
||||
this option for every \term{Type} you can specify \term{Host} and \term{Port}
|
||||
to set in \term{Via} header of outgoing SIP messages, where \term{Type} can be
|
||||
\term{udp}, \term{tcp} or \term{tls}. \term{Host} is a string and \term{Port} is
|
||||
a non negative integer. This is useful if you're running your server in a non-standard
|
||||
network topology. Example configuration:
|
||||
network topology.
|
||||
\end{description}
|
||||
Example complex configuration:
|
||||
\begin{verbatim}
|
||||
modules:
|
||||
...
|
||||
mod_sip:
|
||||
always_record_route: false
|
||||
record_route: sip:example.com;lr
|
||||
routes:
|
||||
- sip:example.com;lr
|
||||
- sip:sip.example.com;lr
|
||||
flow_timeout_udp: 30
|
||||
flow_timeout_tcp: 130
|
||||
via:
|
||||
-
|
||||
type: tls
|
||||
@@ -4812,7 +4981,6 @@ modules:
|
||||
port: 5060
|
||||
...
|
||||
\end{verbatim}
|
||||
\end{description}
|
||||
|
||||
\makesubsection{modstats}{\modstats{}}
|
||||
\ind{modules!\modstats{}}\ind{protocols!XEP-0039: Statistics Gathering}\ind{statistics}
|
||||
@@ -6015,10 +6183,11 @@ The syntax is:
|
||||
|
||||
\makesection{logfiles}{Log Files}
|
||||
|
||||
An \ejabberd{} node writes two log files:
|
||||
An \ejabberd{} node writes three log files:
|
||||
\begin{description}
|
||||
\titem{ejabberd.log} is the ejabberd service log, with the messages reported by \ejabberd{} code
|
||||
\titem{erlang.log} is the Erlang/OTP system log, with the messages reported by Erlang/OTP using SASL (System Architecture Support Libraries)
|
||||
\titem{error.log} is the file accumulating error messages from \term{ejabberd.log}
|
||||
\titem{crash.log} is the Erlang/OTP log, with the crash messages reported by Erlang/OTP using SASL (System Architecture Support Libraries)
|
||||
\end{description}
|
||||
|
||||
The option \term{loglevel} modifies the verbosity of the file ejabberd.log. The syntax:
|
||||
@@ -6040,13 +6209,50 @@ For example, the default configuration is:
|
||||
loglevel: 4
|
||||
\end{verbatim}
|
||||
|
||||
The log files grow continually, so it is recommended to rotate them periodically.
|
||||
To rotate the log files, rename the files and then reopen them.
|
||||
Option \term{log\_rate\_limit} is useful if you want to protect the logging
|
||||
mechanism from being overloaded by excessive amount of log messages.
|
||||
The syntax is:
|
||||
\begin{description}
|
||||
\titem{log\_rate\_limit: N} Where N is a maximum number of log messages per second.
|
||||
The default value is 100.
|
||||
\end{description}
|
||||
When the limit is reached the similar warning message is logged:
|
||||
\begin{verbatim}
|
||||
lager_error_logger_h dropped 800 messages in the last second that exceeded the limit of 100 messages/sec
|
||||
\end{verbatim}
|
||||
|
||||
By default \ejabberd{} rotates the log files when they get grown above a certain size.
|
||||
The exact value is controlled by \term{log\_rotate\_size} option.
|
||||
The syntax is:
|
||||
\begin{description}
|
||||
\titem{log\_rotate\_size: N} Where N is the maximum size of a log file in bytes.
|
||||
The default value is 10485760 (10Mb).
|
||||
\end{description}
|
||||
|
||||
\ejabberd{} can also rotates the log files at given date interval.
|
||||
The exact value is controlled by \term{log\_rotate\_date} option.
|
||||
The syntax is:
|
||||
\begin{description}
|
||||
\titem{log\_rotate\_date: D} Where D is a string with syntax is taken from the syntax newsyslog uses in newsyslog.conf.
|
||||
The default value is \term{""} (no rotation triggered by date).
|
||||
\end{description}
|
||||
|
||||
However, you can rotate the log files manually.
|
||||
For doing this, set \term{log\_rotate\_size} option to 0 and \term{log\_rotate\_date}
|
||||
to empty list, then, when you need to rotate the files, rename and then reopen them.
|
||||
The ejabberdctl command \term{reopen-log}
|
||||
(please refer to section \ref{ectl-commands})
|
||||
reopens the log files,
|
||||
and also renames the old ones if you didn't rename them.
|
||||
|
||||
The option \term{log\_rotate\_count} defines the number of rotated files to keep
|
||||
by \term{reopen-log} command.
|
||||
Every such file has a numeric suffix. The exact format is:
|
||||
\begin{description}
|
||||
\titem{log\_rotate\_count: N} The default value is 1,
|
||||
which means only \term{ejabberd.log.0}, \term{error.log.0}
|
||||
and \term{crash.log.0} will be kept.
|
||||
\end{description}
|
||||
|
||||
\makesection{debugconsole}{Debug Console}
|
||||
|
||||
|
||||
@@ -110,11 +110,12 @@ Moreover, \ejabberd{} comes with a wide range of other state-of-the-art features
|
||||
\item Native PostgreSQL support.
|
||||
\item ODBC data storage support.
|
||||
\item Microsoft SQL Server support. %%\new{}
|
||||
\item Riak NoSQL database support.
|
||||
\end{itemize}
|
||||
\item Authentication
|
||||
\begin{itemize}
|
||||
\item Internal Authentication.
|
||||
\item PAM, LDAP and ODBC. %%\improved{}
|
||||
\item PAM, LDAP, ODBC and Riak. %%\improved{}
|
||||
\item External Authentication script.
|
||||
\end{itemize}
|
||||
\item Others
|
||||
|
||||
@@ -24,7 +24,7 @@ test -x "$CTL" || {
|
||||
echo "ERROR: ejabberd not found: $DIR"
|
||||
exit 1
|
||||
}
|
||||
grep ^"$USER": /etc/passwd >/dev/null || {
|
||||
getent passwd "$USER" >/dev/null || {
|
||||
echo "ERROR: System user not found: $USER"
|
||||
exit 2
|
||||
}
|
||||
|
||||
+40
-6
@@ -24,8 +24,8 @@
|
||||
### > Art thou not Romeo,
|
||||
### and a Montague?
|
||||
|
||||
### =========
|
||||
### DEBUGGING
|
||||
### =======
|
||||
### LOGGING
|
||||
|
||||
##
|
||||
## loglevel: Verbosity of log files generated by ejabberd.
|
||||
@@ -38,6 +38,32 @@
|
||||
##
|
||||
loglevel: 4
|
||||
|
||||
##
|
||||
## rotation: Describe how to rotate logs. Either size and/or date can trigger
|
||||
## log rotation. Setting count to N keeps N rotated logs. Setting count to 0
|
||||
## does not disable rotation, it instead rotates the file and keeps no previous
|
||||
## versions around. Setting size to X rotate log when it reaches X bytes.
|
||||
## To disable rotation set the size to 0 and the date to ""
|
||||
## Date syntax is taken from the syntax newsyslog uses in newsyslog.conf.
|
||||
## Some examples:
|
||||
## $D0 rotate every night at midnight
|
||||
## $D23 rotate every day at 23:00 hr
|
||||
## $W0D23 rotate every week on Sunday at 23:00 hr
|
||||
## $W5D16 rotate every week on Friday at 16:00 hr
|
||||
## $M1D0 rotate on the first day of every month at midnight
|
||||
## $M5D6 rotate on every 5th day of the month at 6:00 hr
|
||||
##
|
||||
log_rotate_size: 10485760
|
||||
log_rotate_date: ""
|
||||
log_rotate_count: 1
|
||||
|
||||
##
|
||||
## overload protection: If you want to limit the number of messages per second
|
||||
## allowed from error_logger, which is a good idea if you want to avoid a flood
|
||||
## of messages when system is overloaded, you can set a limit.
|
||||
## 100 is ejabberd's default.
|
||||
log_rate_limit: 100
|
||||
|
||||
##
|
||||
## watchdog_admins: Only useful for developers: if an ejabberd process
|
||||
## consumes a lot of memory, send live notifications to these XMPP
|
||||
@@ -82,11 +108,16 @@ listen:
|
||||
##
|
||||
## If TLS is compiled in and you installed a SSL
|
||||
## certificate, specify the full path to the
|
||||
## file and uncomment this line:
|
||||
## file and uncomment these lines:
|
||||
##
|
||||
## certfile: "/path/to/ssl.pem"
|
||||
## starttls: true
|
||||
##
|
||||
## To enforce TLS encryption for client connections,
|
||||
## use this instead of the "starttls" option:
|
||||
##
|
||||
## starttls_required: true
|
||||
##
|
||||
## Custom OpenSSL options
|
||||
##
|
||||
## protocol_options:
|
||||
@@ -170,7 +201,7 @@ listen:
|
||||
##
|
||||
## Default s2s policy for undefined hosts.
|
||||
##
|
||||
## s2s_policy: s2s_access
|
||||
## s2s_access: s2s
|
||||
|
||||
##
|
||||
## Outgoing S2S options
|
||||
@@ -461,7 +492,7 @@ access:
|
||||
trusted_network:
|
||||
loopback: allow
|
||||
## Do not establish S2S connections with bad servers
|
||||
## s2s_access:
|
||||
## s2s:
|
||||
## bad_servers: deny
|
||||
## all: allow
|
||||
|
||||
@@ -527,6 +558,9 @@ modules:
|
||||
mod_blocking: {} # requires mod_privacy
|
||||
mod_caps: {}
|
||||
mod_carboncopy: {}
|
||||
mod_client_state:
|
||||
drop_chat_states: true
|
||||
queue_presence: false
|
||||
mod_configure: {} # requires mod_adhoc
|
||||
mod_disco: {}
|
||||
## mod_echo: {}
|
||||
@@ -612,7 +646,7 @@ modules:
|
||||
##
|
||||
## Enable modules with custom options in a specific virtual host
|
||||
##
|
||||
## append_host_config:
|
||||
## host_config:
|
||||
## "localhost":
|
||||
## modules:
|
||||
## mod_echo:
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
# This communication is used by ejabberdctl command line tool,
|
||||
# and in a cluster of several ejabberd nodes.
|
||||
#
|
||||
# Default: 127.0.0.1
|
||||
# Default: 0.0.0.0
|
||||
#
|
||||
#INET_DIST_INTERFACE=127.0.0.1
|
||||
|
||||
|
||||
+77
-59
@@ -7,7 +7,6 @@ ERL_MAX_PORTS=32000
|
||||
ERL_PROCESSES=250000
|
||||
ERL_MAX_ETS_TABLES=1400
|
||||
FIREWALL_WINDOW=""
|
||||
INET_DIST_INTERFACE="127.0.0.1"
|
||||
ERLANG_NODE=ejabberd@localhost
|
||||
|
||||
# define default environment variables
|
||||
@@ -23,7 +22,12 @@ if [ "$INSTALLUSER" != "" ] ; then
|
||||
EXEC_CMD="false"
|
||||
for GID in `id -G`; do
|
||||
if [ $GID -eq 0 ] ; then
|
||||
EXEC_CMD="su $INSTALLUSER -p -c"
|
||||
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"
|
||||
fi
|
||||
done
|
||||
if [ `id -g` -eq `id -g $INSTALLUSER` ] ; then
|
||||
@@ -45,33 +49,33 @@ while [ $# -ne 0 ] ; do
|
||||
case $PARAM in
|
||||
--) break ;;
|
||||
--node) ERLANG_NODE_ARG=$1 ; shift ;;
|
||||
--config-dir) ETCDIR=$1 ; shift ;;
|
||||
--config) EJABBERD_CONFIG_PATH=$1 ; shift ;;
|
||||
--ctl-config) EJABBERDCTL_CONFIG_PATH=$1 ; shift ;;
|
||||
--logs) LOGS_DIR=$1 ; shift ;;
|
||||
--spool) SPOOLDIR=$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" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Define ejabberd variable if they have not been defined from the command line
|
||||
if [ "$ETCDIR" = "" ] ; then
|
||||
ETCDIR={{sysconfdir}}/ejabberd
|
||||
if [ "$ETC_DIR" = "" ] ; then
|
||||
ETC_DIR={{sysconfdir}}/ejabberd
|
||||
fi
|
||||
if [ "$EJABBERDCTL_CONFIG_PATH" = "" ] ; then
|
||||
EJABBERDCTL_CONFIG_PATH=$ETCDIR/ejabberdctl.cfg
|
||||
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=$ETCDIR/ejabberd.yml
|
||||
EJABBERD_CONFIG_PATH=$ETC_DIR/ejabberd.yml
|
||||
fi
|
||||
if [ "$LOGS_DIR" = "" ] ; then
|
||||
LOGS_DIR={{localstatedir}}/log/ejabberd
|
||||
fi
|
||||
if [ "$SPOOLDIR" = "" ] ; then
|
||||
SPOOLDIR={{localstatedir}}/lib/ejabberd
|
||||
if [ "$SPOOL_DIR" = "" ] ; then
|
||||
SPOOL_DIR={{localstatedir}}/lib/ejabberd
|
||||
fi
|
||||
if [ "$EJABBERD_DOC_PATH" = "" ] ; then
|
||||
EJABBERD_DOC_PATH={{docdir}}
|
||||
@@ -104,8 +108,7 @@ EJABBERD_LOG_PATH=$LOGS_DIR/ejabberd.log
|
||||
SASL_LOG_PATH=$LOGS_DIR/erlang.log
|
||||
DATETIME=`date "+%Y%m%d-%H%M%S"`
|
||||
ERL_CRASH_DUMP=$LOGS_DIR/erl_crash_$DATETIME.dump
|
||||
ERL_INETRC=$ETCDIR/inetrc
|
||||
HOME=$SPOOLDIR
|
||||
ERL_INETRC=$ETC_DIR/inetrc
|
||||
|
||||
# define erl parameters
|
||||
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
|
||||
@@ -126,11 +129,26 @@ else
|
||||
NAME="-name"
|
||||
fi
|
||||
|
||||
# create the ejabberd home dir with the proper user if doesn't exist
|
||||
# then change to that directory readable by INSTALLUSER to
|
||||
# prevent "File operation error: eacces." messages
|
||||
[ -d $HOME ] || $EXEC_CMD "mkdir -p $HOME"
|
||||
cd $HOME
|
||||
# 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
|
||||
|
||||
# export global variables
|
||||
export EJABBERD_CONFIG_PATH
|
||||
@@ -145,8 +163,6 @@ export ERL_EPMD_ADDRESS
|
||||
export ERL_INETRC
|
||||
export ERL_MAX_PORTS
|
||||
export ERL_MAX_ETS_TABLES
|
||||
export HOME
|
||||
export EXEC_CMD
|
||||
|
||||
# start server
|
||||
start()
|
||||
@@ -156,8 +172,9 @@ start()
|
||||
$NAME $ERLANG_NODE \
|
||||
-noinput -detached \
|
||||
-pa $EJABBERD_EBIN_PATH \
|
||||
-mnesia dir \"\\\"$SPOOLDIR\\\"\" \
|
||||
-mnesia dir \"\\\"$SPOOL_DIR\\\"\" \
|
||||
$KERNEL_OPTS \
|
||||
$EJABBERD_OPTS \
|
||||
-s ejabberd \
|
||||
-sasl sasl_error_logger \\{file,\\\"$SASL_LOG_PATH\\\"\\} \
|
||||
$ERLANG_OPTS $ARGS \"$@\""
|
||||
@@ -166,26 +183,26 @@ start()
|
||||
# attach to server
|
||||
debug()
|
||||
{
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo ""
|
||||
echo "IMPORTANT: we will attempt to attach an INTERACTIVE shell"
|
||||
echo "to an already running ejabberd node."
|
||||
echo "If an ERROR is printed, it means the connection was not successful."
|
||||
echo "You can interact with the ejabberd node if you know how to use it."
|
||||
echo "Please be extremely cautious with your actions,"
|
||||
echo "and exit immediately if you are not completely sure."
|
||||
echo ""
|
||||
echo "To detach this shell from ejabberd, press:"
|
||||
echo " control+c, control+c"
|
||||
echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press any key to continue"
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo ""
|
||||
echo "IMPORTANT: we will attempt to attach an INTERACTIVE shell"
|
||||
echo "to an already running ejabberd node."
|
||||
echo "If an ERROR is printed, it means the connection was not successful."
|
||||
echo "You can interact with the ejabberd node if you know how to use it."
|
||||
echo "Please be extremely cautious with your actions,"
|
||||
echo "and exit immediately if you are not completely sure."
|
||||
echo ""
|
||||
echo "To detach this shell from ejabberd, press:"
|
||||
echo " control+c, control+c"
|
||||
echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press any key to continue"
|
||||
read foo
|
||||
echo ""
|
||||
fi
|
||||
echo ""
|
||||
TTY=`tty | sed -e 's/.*\///g'`
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME debug-${TTY}-${ERLANG_NODE} \
|
||||
@@ -199,30 +216,31 @@ debug()
|
||||
live()
|
||||
{
|
||||
check_start
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo ""
|
||||
echo "IMPORTANT: ejabberd is going to start in LIVE (interactive) mode."
|
||||
echo "All log messages will be shown in the command shell."
|
||||
echo "You can interact with the ejabberd node if you know how to use it."
|
||||
echo "Please be extremely cautious with your actions,"
|
||||
echo "and exit immediately if you are not completely sure."
|
||||
echo ""
|
||||
echo "To exit this LIVE mode and stop ejabberd, press:"
|
||||
echo " q(). and press the Enter key"
|
||||
echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press any key to continue"
|
||||
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo ""
|
||||
echo "IMPORTANT: ejabberd is going to start in LIVE (interactive) mode."
|
||||
echo "All log messages will be shown in the command shell."
|
||||
echo "You can interact with the ejabberd node if you know how to use it."
|
||||
echo "Please be extremely cautious with your actions,"
|
||||
echo "and exit immediately if you are not completely sure."
|
||||
echo ""
|
||||
echo "To exit this LIVE mode and stop ejabberd, press:"
|
||||
echo " q(). and press the Enter key"
|
||||
echo ""
|
||||
echo "--------------------------------------------------------------------"
|
||||
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
|
||||
echo " EJABBERD_BYPASS_WARNINGS=true"
|
||||
echo "Press any key to continue"
|
||||
read foo
|
||||
echo ""
|
||||
fi
|
||||
echo ""
|
||||
$EXEC_CMD "$ERL \
|
||||
$NAME $ERLANG_NODE \
|
||||
-pa $EJABBERD_EBIN_PATH \
|
||||
-mnesia dir \"\\\"$SPOOLDIR\\\"\" \
|
||||
-mnesia dir \"\\\"$SPOOL_DIR\\\"\" \
|
||||
$KERNEL_OPTS \
|
||||
$EJABBERD_OPTS \
|
||||
-s ejabberd \
|
||||
$ERLANG_OPTS $ARGS \"$@\""
|
||||
}
|
||||
@@ -243,11 +261,11 @@ help()
|
||||
echo " live Start an ejabberd node in live (interactive) mode"
|
||||
echo ""
|
||||
echo "Optional parameters when starting an ejabberd node:"
|
||||
echo " --config-dir dir Config ejabberd: $ETCDIR"
|
||||
echo " --config-dir dir Config ejabberd: $ETC_DIR"
|
||||
echo " --config file Config ejabberd: $EJABBERD_CONFIG_PATH"
|
||||
echo " --ctl-config file Config ejabberdctl: $EJABBERDCTL_CONFIG_PATH"
|
||||
echo " --logs dir Directory for logs: $LOGS_DIR"
|
||||
echo " --spool dir Database spool dir: $SPOOLDIR"
|
||||
echo " --spool dir Database spool dir: $SPOOL_DIR"
|
||||
echo " --node nodename ejabberd node name: $ERLANG_NODE"
|
||||
echo ""
|
||||
}
|
||||
|
||||
@@ -31,5 +31,6 @@
|
||||
host = <<"">> :: binary(),
|
||||
port = 5280 :: inet:port_number(),
|
||||
tp = http, % :: protocol(),
|
||||
opts = [] :: list(),
|
||||
headers = [] :: [{atom() | binary(), binary()}]}).
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-include("ns.hrl").
|
||||
-include("xml.hrl").
|
||||
-include_lib("p1_xml/include/xml.hrl").
|
||||
|
||||
-define(STANZA_ERROR(Code, Type, Condition),
|
||||
#xmlel{name = <<"error">>,
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
-define(NS_IQDATA, <<"jabber:iq:data">>).
|
||||
-define(NS_DELAY91, <<"jabber:x:delay">>).
|
||||
-define(NS_DELAY, <<"urn:xmpp:delay">>).
|
||||
-define(NS_HINTS, <<"urn:xmpp:hints">>).
|
||||
-define(NS_EXPIRE, <<"jabber:x:expire">>).
|
||||
-define(NS_EVENT, <<"jabber:x:event">>).
|
||||
-define(NS_CHATSTATES,
|
||||
@@ -143,5 +144,9 @@
|
||||
-define(NS_MEDIA, <<"urn:xmpp:media-element">>).
|
||||
-define(NS_BOB, <<"urn:xmpp:bob">>).
|
||||
-define(NS_PING, <<"urn:xmpp:ping">>).
|
||||
-define(NS_CARBONS_2, <<"urn:xmpp:carbons:2">>).
|
||||
-define(NS_CARBONS_1, <<"urn:xmpp:carbons:1">>).
|
||||
-define(NS_FORWARD, <<"urn:xmpp:forward:0">>).
|
||||
-define(NS_CLIENT_STATE, <<"urn:xmpp:csi:0">>).
|
||||
-define(NS_STREAM_MGMT_2, <<"urn:xmpp:sm:2">>).
|
||||
-define(NS_STREAM_MGMT_3, <<"urn:xmpp:sm:3">>).
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@
|
||||
{"has been kicked because of a system shutdown","telah dikick karena sistem shutdown"}.
|
||||
{"has been kicked because the room has been changed to members-only","telah dikick karena ruangan telah diubah menjadi hanya untuk member"}.
|
||||
{"has been kicked","telah dikick"}.
|
||||
{" has set the subject to: ","telah menetapkan topik yaitu:"}.
|
||||
{" has set the subject to: "," telah menetapkan topik yaitu: "}.
|
||||
{"Host","Host"}.
|
||||
{"If you don't see the CAPTCHA image here, visit the web page.","Jika Anda tidak melihat gambar CAPTCHA disini, silahkan kunjungi halaman web."}.
|
||||
{"If you want to specify different ports, passwords, encodings for IRC servers, fill this list with values in format '{\"irc server\", \"encoding\", port, \"password\"}'. By default this service use \"~s\" encoding, port ~p, empty password.","Jika Anda ingin menentukan port yang berbeda, sandi, pengkodean untuk layanan IRC, isi daftar ini dengan nilai-nilai dalam format '{\"server irc \", \"encoding \", port, \"sandi \"}'. Secara default ini menggunakan layanan \"~s \" pengkodean, port ~p, kata sandi kosong."}.
|
||||
|
||||
+41
-13
@@ -42,10 +42,6 @@ HiPE = case lists:keysearch(hipe, 1, Cfg) of
|
||||
[]
|
||||
end,
|
||||
|
||||
Includes = [{i, "include"},
|
||||
{i, filename:join(["deps", "esip", "include"])},
|
||||
{i, filename:join(["deps", "p1_xml", "include"])}],
|
||||
|
||||
SrcDirs = lists:foldl(
|
||||
fun({tools, true}, Acc) ->
|
||||
[tools|Acc];
|
||||
@@ -58,8 +54,10 @@ Deps = [{p1_cache_tab, ".*", {git, "git://github.com/processone/cache_tab"}},
|
||||
{p1_stringprep, ".*", {git, "git://github.com/processone/stringprep"}},
|
||||
{p1_xml, ".*", {git, "git://github.com/processone/xml"}},
|
||||
{esip, ".*", {git, "git://github.com/processone/p1_sip"}},
|
||||
{p1_stun, ".*", {git, "git://github.com/processone/stun"}},
|
||||
{p1_yaml, ".*", {git, "git://github.com/processone/p1_yaml"}},
|
||||
{xmlrpc, ".*", {git, "git://github.com/rds13/xmlrpc"}}],
|
||||
{ehyperloglog, ".*", {git, "https://github.com/vaxelfel/eHyperLogLog.git"}},
|
||||
{p1_utils, ".*", {git, "git://github.com/processone/p1_utils"}}],
|
||||
|
||||
ConfigureCmd = fun(Pkg, Flags) ->
|
||||
{'get-deps',
|
||||
@@ -91,15 +89,14 @@ CfgDeps = lists:flatmap(
|
||||
[{p1_pam, ".*", {git, "git://github.com/processone/epam"}}];
|
||||
({zlib, true}) ->
|
||||
[{p1_zlib, ".*", {git, "git://github.com/processone/zlib"}}];
|
||||
({stun, true}) ->
|
||||
[{p1_stun, ".*", {git, "git://github.com/processone/stun"}}];
|
||||
({riak, true}) ->
|
||||
[{riakc, ".*",
|
||||
{git, "git://github.com/basho/riak-erlang-client",
|
||||
{tag, "1.4.2"}}}];
|
||||
({json, true}) ->
|
||||
[{jiffy, ".*", {git, "git://github.com/davisp/jiffy"}}];
|
||||
({iconv, true}) ->
|
||||
[{p1_iconv, ".*", {git, "git://github.com/processone/eiconv"}}];
|
||||
({http, true}) ->
|
||||
[{ibrowse, ".*", {git, "git://github.com/cmullaparthi/ibrowse"}},
|
||||
{lhttpc, ".*", {git, "git://github.com/esl/lhttpc"}}];
|
||||
({lager, true}) ->
|
||||
[{lager, ".*", {git, "git://github.com/basho/lager"}}];
|
||||
({lager, false}) ->
|
||||
@@ -119,15 +116,46 @@ CfgPostHooks = lists:flatmap(
|
||||
[]
|
||||
end, Cfg),
|
||||
|
||||
CfgXrefs = lists:flatmap(
|
||||
fun({mysql, false}) ->
|
||||
["(\".*mysql.*\":_/_)"];
|
||||
({pgsql, false}) ->
|
||||
["(\".*pgsql.*\":_/_)"];
|
||||
({pam, false}) ->
|
||||
["(\"epam\":_/_)"];
|
||||
({riak, false}) ->
|
||||
["(\"riak.*\":_/_)"];
|
||||
({riak, true}) ->
|
||||
% used in map-reduce function called from riak vm
|
||||
["(\"riak_object\":_/_)"];
|
||||
({json, false}) ->
|
||||
["(\"jiffy\":_/_)"];
|
||||
({zlib, false}) ->
|
||||
["(\"ezlib\":_/_)"];
|
||||
({http, false}) ->
|
||||
["(\"lhttpc\":_/_)"];
|
||||
({iconv, false}) ->
|
||||
["(\"iconv\":_/_)"];
|
||||
({odbc, false}) ->
|
||||
["(\"odbc\":_/_)"];
|
||||
(_) ->
|
||||
[]
|
||||
end, Cfg),
|
||||
|
||||
|
||||
{ok, Cwd} = file:get_cwd(),
|
||||
|
||||
Config = [{erl_opts, Includes ++ Macros ++ HiPE ++ DebugInfo ++
|
||||
Config = [{erl_opts, Macros ++ HiPE ++ DebugInfo ++
|
||||
[{src_dirs, [asn1, src | SrcDirs]}]},
|
||||
{sub_dirs, ["rel"]},
|
||||
{keep_build_info, true},
|
||||
{ct_extra_params, "-include "
|
||||
++ filename:join([Cwd, "tools"]) ++ " "
|
||||
++ filename:join([Cwd, "deps", "p1_xml", "include"])},
|
||||
++ filename:join([Cwd, "tools"])},
|
||||
{xref_warnings, false},
|
||||
{xref_checks, []},
|
||||
{xref_queries,
|
||||
[{"(XC - UC) || (XU - X - B - "
|
||||
++ string:join(CfgXrefs, " - ") ++ ")", []}]},
|
||||
{post_hooks, PostHooks ++ CfgPostHooks},
|
||||
{deps, Deps ++ CfgDeps}],
|
||||
%%io:format("ejabberd configuration:~n ~p~n", [Config]),
|
||||
|
||||
@@ -28,7 +28,7 @@ ConfiguredOTPApps = lists:flatmap(
|
||||
|
||||
OTPApps = RequiredOTPApps ++ ConfiguredOTPApps,
|
||||
|
||||
DepRequiredApps = [p1_cache_tab, p1_tls, p1_stringprep, p1_xml, p1_yaml, xmlrpc],
|
||||
DepRequiredApps = [p1_cache_tab, p1_tls, p1_stringprep, p1_xml, p1_yaml, p1_utils],
|
||||
|
||||
DepConfiguredApps = lists:flatmap(
|
||||
fun({mysql, true}) -> [p1_mysql];
|
||||
@@ -38,7 +38,6 @@ DepConfiguredApps = lists:flatmap(
|
||||
({stun, true}) -> [p1_stun];
|
||||
({json, true}) -> [jiffy];
|
||||
({iconv, true}) -> [p1_iconv];
|
||||
({http, true}) -> [ibrowse, lhttpc];
|
||||
({lager, true}) -> [lager, goldrush];
|
||||
({lager, false}) -> [p1_logger];
|
||||
(_) -> []
|
||||
|
||||
+25
-3
@@ -93,9 +93,15 @@ start() ->
|
||||
).
|
||||
|
||||
register_mechanism(Mechanism, Module, PasswordType) ->
|
||||
ets:insert(sasl_mechanism,
|
||||
#sasl_mechanism{mechanism = Mechanism, module = Module,
|
||||
password_type = PasswordType}).
|
||||
case is_disabled(Mechanism) of
|
||||
false ->
|
||||
ets:insert(sasl_mechanism,
|
||||
#sasl_mechanism{mechanism = Mechanism, module = Module,
|
||||
password_type = PasswordType});
|
||||
true ->
|
||||
?DEBUG("SASL mechanism ~p is disabled", [Mechanism]),
|
||||
true
|
||||
end.
|
||||
|
||||
%%% TODO: use callbacks
|
||||
%%-include("ejabberd.hrl").
|
||||
@@ -215,3 +221,19 @@ filter_anonymous(Host, Mechs) ->
|
||||
true -> Mechs;
|
||||
false -> Mechs -- [<<"ANONYMOUS">>]
|
||||
end.
|
||||
|
||||
-spec(is_disabled/1 ::
|
||||
(
|
||||
Mechanism :: mechanism())
|
||||
-> boolean()
|
||||
).
|
||||
|
||||
is_disabled(Mechanism) ->
|
||||
Disabled = ejabberd_config:get_option(
|
||||
disable_sasl_mechanisms,
|
||||
fun(V) when is_list(V) ->
|
||||
lists:map(fun(M) -> str:to_upper(M) end, V);
|
||||
(V) ->
|
||||
[str:to_upper(V)]
|
||||
end, []),
|
||||
lists:member(Mechanism, Disabled).
|
||||
|
||||
@@ -57,6 +57,7 @@ start(normal, _Args) ->
|
||||
connect_nodes(),
|
||||
Sup = ejabberd_sup:start_link(),
|
||||
ejabberd_rdbms:start(),
|
||||
ejabberd_riak_sup:start(),
|
||||
ejabberd_auth:start(),
|
||||
cyrsasl:start(),
|
||||
% Profiling
|
||||
@@ -107,6 +108,18 @@ loop() ->
|
||||
end.
|
||||
|
||||
db_init() ->
|
||||
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()]);
|
||||
|
||||
@@ -445,5 +445,7 @@ import(Server) ->
|
||||
|
||||
import(Server, mnesia, Passwd) ->
|
||||
ejabberd_auth_internal:import(Server, mnesia, Passwd);
|
||||
import(Server, riak, Passwd) ->
|
||||
ejabberd_auth_riak:import(Server, riak, Passwd);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
@@ -186,6 +186,8 @@ check_password_extauth(User, Server, Password) ->
|
||||
try_register_extauth(User, Server, Password) ->
|
||||
extauth:try_register(User, Server, Password).
|
||||
|
||||
check_password_cache(User, Server, Password, 0) ->
|
||||
check_password_external_cache(User, Server, Password);
|
||||
check_password_cache(User, Server, Password,
|
||||
CacheTime) ->
|
||||
case get_last_access(User, Server) of
|
||||
|
||||
@@ -387,7 +387,7 @@ parse_options(Host) ->
|
||||
[{<<"%u">>, <<"*">>}]),
|
||||
{DNFilter, DNFilterAttrs} =
|
||||
eldap_utils:get_opt({ldap_dn_filter, Host}, [],
|
||||
fun({DNF, DNFA}) ->
|
||||
fun([{DNF, DNFA}]) ->
|
||||
NewDNFA = case DNFA of
|
||||
undefined ->
|
||||
[];
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_auth_riak.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Purpose : Authentification via Riak
|
||||
%%% Created : 12 Nov 2012 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2012 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., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_auth_riak).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-behaviour(ejabberd_auth).
|
||||
|
||||
%% External exports
|
||||
-export([start/1, set_password/3, check_password/3,
|
||||
check_password/5, 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/3,
|
||||
plain_password_required/0]).
|
||||
-export([passwd_schema/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
|
||||
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
|
||||
password = <<"">> :: binary() | scram() | '_'}).
|
||||
|
||||
-define(SALT_LENGTH, 16).
|
||||
|
||||
start(_Host) ->
|
||||
ok.
|
||||
|
||||
plain_password_required() ->
|
||||
case is_scrammed() of
|
||||
false -> false;
|
||||
true -> true
|
||||
end.
|
||||
|
||||
store_type() ->
|
||||
case is_scrammed() of
|
||||
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
|
||||
true -> scram %% allows: PLAIN SCRAM
|
||||
end.
|
||||
|
||||
passwd_schema() ->
|
||||
{record_info(fields, passwd), #passwd{}}.
|
||||
|
||||
check_password(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib: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.
|
||||
|
||||
check_password(User, Server, Password, Digest,
|
||||
DigestGen) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib: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 = jlib: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.
|
||||
|
||||
set_password(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
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}]}])
|
||||
end.
|
||||
|
||||
try_register(User, Server, PasswordList) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
Password = iolist_to_binary(PasswordList),
|
||||
US = {LUser, LServer},
|
||||
if (LUser == error) or (LServer == error) ->
|
||||
{error, invalid_jid};
|
||||
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 = jlib:nameprep(Server),
|
||||
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, LServer) of
|
||||
{ok, Users} ->
|
||||
Users;
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
get_vh_registered_users(Server, _) ->
|
||||
get_vh_registered_users(Server).
|
||||
|
||||
get_vh_registered_users_number(Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
case ejabberd_riak:count_by_index(passwd, <<"host">>, LServer) of
|
||||
{ok, N} ->
|
||||
N;
|
||||
_ ->
|
||||
0
|
||||
end.
|
||||
|
||||
get_vh_registered_users_number(Server, _) ->
|
||||
get_vh_registered_users_number(Server).
|
||||
|
||||
get_password(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib: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) ->
|
||||
{jlib:decode_base64(Scram#scram.storedkey),
|
||||
jlib:decode_base64(Scram#scram.serverkey),
|
||||
jlib:decode_base64(Scram#scram.salt),
|
||||
Scram#scram.iterationcount};
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
get_password_s(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib: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 = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
|
||||
{error, notfound} -> false;
|
||||
{ok, _} -> true;
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
ejabberd_riak:delete(passwd, {LUser, LServer}),
|
||||
ok.
|
||||
|
||||
remove_user(User, Server, Password) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib: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_local_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 = crypto:rand_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 = jlib:encode_base64(StoredKey),
|
||||
serverkey = jlib:encode_base64(ServerKey),
|
||||
salt = jlib:encode_base64(Salt),
|
||||
iterationcount = IterationCount}.
|
||||
|
||||
is_password_scram_valid(Password, Scram) ->
|
||||
IterationCount = Scram#scram.iterationcount,
|
||||
Salt = jlib:decode_base64(Scram#scram.salt),
|
||||
SaltedPassword = scram:salted_password(Password, Salt,
|
||||
IterationCount),
|
||||
StoredKey =
|
||||
scram:stored_key(scram:client_key(SaltedPassword)),
|
||||
jlib:decode_base64(Scram#scram.storedkey) == StoredKey.
|
||||
|
||||
export(_Server) ->
|
||||
[{passwd,
|
||||
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
Pass = ejabberd_odbc:escape(Password),
|
||||
[[<<"delete from users where username='">>, Username, <<"';">>],
|
||||
[<<"insert into users(username, password) "
|
||||
"values ('">>, Username, <<"', '">>, Pass, <<"');">>]];
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import(LServer, riak, #passwd{} = Passwd) ->
|
||||
ejabberd_riak:put(Passwd, passwd_schema(), [{'2i', [{<<"host">>, LServer}]}]);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
+427
-289
File diff suppressed because it is too large
Load Diff
+14
-6
@@ -68,8 +68,15 @@ start() ->
|
||||
%% This start time is used by mod_last:
|
||||
{MegaSecs, Secs, _} = now(),
|
||||
UnixTime = MegaSecs*1000000 + Secs,
|
||||
SharedKey = case erlang:get_cookie() of
|
||||
nocookie ->
|
||||
p1_sha:sha(randoms:get_string());
|
||||
Cookie ->
|
||||
p1_sha:sha(jlib:atom_to_binary(Cookie))
|
||||
end,
|
||||
State1 = set_option({node_start, global}, UnixTime, State),
|
||||
set_opts(State1).
|
||||
State2 = set_option({shared_key, global}, SharedKey, State1),
|
||||
set_opts(State2).
|
||||
|
||||
%% @doc Get the filename of the ejabberd configuration file.
|
||||
%% The filename can be specified with: erl -config "/path/to/ejabberd.yml".
|
||||
@@ -179,7 +186,9 @@ consult(File) ->
|
||||
{ok, [Document|_]} ->
|
||||
{ok, Document};
|
||||
{error, Err} ->
|
||||
{error, p1_yaml:format_error(Err)}
|
||||
Msg1 = "Cannot load " ++ File ++ ": ",
|
||||
Msg2 = p1_yaml:format_error(Err),
|
||||
{error, Msg1 ++ Msg2}
|
||||
end;
|
||||
_ ->
|
||||
case file:consult(File) of
|
||||
@@ -201,9 +210,8 @@ get_absolute_path(File) ->
|
||||
absolute ->
|
||||
File;
|
||||
relative ->
|
||||
Config_path = get_ejabberd_config_path(),
|
||||
Config_dir = filename:dirname(Config_path),
|
||||
filename:absname_join(Config_dir, File)
|
||||
{ok, Dir} = file:get_cwd(),
|
||||
filename:absname_join(Dir, File)
|
||||
end.
|
||||
|
||||
|
||||
@@ -980,7 +988,7 @@ report_and_stop(Tab, Err) ->
|
||||
halt(string:substr(ErrTxt, 1, 199)).
|
||||
|
||||
emit_deprecation_warning(Module, NewModule, DBType) ->
|
||||
?WARNING_MSG("Module ~s is deprecated, use {~s, [{db_type, ~s}, ...]}"
|
||||
?WARNING_MSG("Module ~s is deprecated, use ~s with 'db_type: ~s'"
|
||||
" instead", [Module, NewModule, DBType]).
|
||||
|
||||
emit_deprecation_warning(Module, NewModule) ->
|
||||
|
||||
@@ -151,7 +151,7 @@ run(Hook, Host, Args) ->
|
||||
%% The arguments passed to the function are: [Val | Args].
|
||||
%% The result of a call is used as Val for the next call.
|
||||
%% If a call returns 'stop', no more calls are performed and 'stopped' is returned.
|
||||
%% If a call returns {stopped, NewVal}, no more calls are performed and NewVal is returned.
|
||||
%% If a call returns {stop, NewVal}, no more calls are performed and NewVal is returned.
|
||||
run_fold(Hook, Val, Args) ->
|
||||
run_fold(Hook, global, Val, Args).
|
||||
|
||||
|
||||
+11
-3
@@ -65,6 +65,7 @@
|
||||
request_tp,
|
||||
request_headers = [],
|
||||
end_of_request = false,
|
||||
options = [],
|
||||
default_host,
|
||||
trail = <<>>
|
||||
}).
|
||||
@@ -133,6 +134,10 @@ init({SockMod, Socket}, Opts) ->
|
||||
true -> [{[<<"http-poll">>], ejabberd_http_poll}];
|
||||
false -> []
|
||||
end,
|
||||
XMLRPC = case proplists:get_bool(xmlrpc, Opts) of
|
||||
true -> [{[], ejabberd_xmlrpc}];
|
||||
false -> []
|
||||
end,
|
||||
DefinedHandlers = gen_mod:get_opt(
|
||||
request_handlers, Opts,
|
||||
fun(Hs) ->
|
||||
@@ -141,7 +146,7 @@ init({SockMod, Socket}, Opts) ->
|
||||
Mod} || {Path, Mod} <- Hs]
|
||||
end, []),
|
||||
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
|
||||
Admin ++ Bind ++ Poll,
|
||||
Admin ++ Bind ++ Poll ++ XMLRPC,
|
||||
?DEBUG("S: ~p~n", [RequestHandlers]),
|
||||
|
||||
DefaultHost = gen_mod:get_opt(default_host, Opts, fun(A) -> A end, undefined),
|
||||
@@ -150,6 +155,7 @@ init({SockMod, Socket}, Opts) ->
|
||||
State = #state{sockmod = SockMod1,
|
||||
socket = Socket1,
|
||||
default_host = DefaultHost,
|
||||
options = Opts,
|
||||
request_handlers = RequestHandlers},
|
||||
receive_headers(State).
|
||||
|
||||
@@ -359,7 +365,7 @@ process(Handlers, Request) ->
|
||||
false -> process(HandlersLeft, Request)
|
||||
end.
|
||||
|
||||
process_request(#state{request_method = Method,
|
||||
process_request(#state{request_method = Method, options = Options,
|
||||
request_path = {abs_path, Path}, request_auth = Auth,
|
||||
request_lang = Lang, request_handlers = RequestHandlers,
|
||||
request_host = Host, request_port = Port,
|
||||
@@ -389,6 +395,7 @@ process_request(#state{request_method = Method,
|
||||
IP = analyze_ip_xff(IPHere, XFF, Host),
|
||||
Request = #request{method = Method,
|
||||
path = LPath,
|
||||
opts = Options,
|
||||
q = LQuery,
|
||||
auth = Auth,
|
||||
lang = Lang,
|
||||
@@ -413,7 +420,7 @@ process_request(#state{request_method = Method,
|
||||
make_text_output(State, Status, Headers, Output)
|
||||
end
|
||||
end;
|
||||
process_request(#state{request_method = Method,
|
||||
process_request(#state{request_method = Method, options = Options,
|
||||
request_path = {abs_path, Path}, request_auth = Auth,
|
||||
request_content_length = Len, request_lang = Lang,
|
||||
sockmod = SockMod, socket = Socket, request_host = Host,
|
||||
@@ -450,6 +457,7 @@ process_request(#state{request_method = Method,
|
||||
Request = #request{method = Method,
|
||||
path = LPath,
|
||||
q = LQuery,
|
||||
opts = Options,
|
||||
auth = Auth,
|
||||
data = Data,
|
||||
lang = Lang,
|
||||
|
||||
@@ -201,11 +201,7 @@ listen_tcp(PortIP, Module, SockOpts, Port, IPS) ->
|
||||
catch
|
||||
_:_ -> []
|
||||
end,
|
||||
DeliverAs = case Module of
|
||||
ejabberd_xmlrpc -> list;
|
||||
_ -> binary
|
||||
end,
|
||||
Res = gen_tcp:listen(Port, [DeliverAs,
|
||||
Res = gen_tcp:listen(Port, [binary,
|
||||
{packet, 0},
|
||||
{active, false},
|
||||
{reuseaddr, true},
|
||||
@@ -595,7 +591,7 @@ transform_option({{Port, IP, Transport}, Mod, Opts}) ->
|
||||
try
|
||||
Mod:transform_listen_option(Opt, Acc)
|
||||
catch error:undef ->
|
||||
Acc
|
||||
[Opt|Acc]
|
||||
end
|
||||
end, [], Opts1),
|
||||
TransportOpt = if Transport == tcp -> [];
|
||||
|
||||
+43
-6
@@ -61,29 +61,66 @@ get_log_path() ->
|
||||
|
||||
-ifdef(LAGER).
|
||||
|
||||
get_pos_integer_env(Name, Default) ->
|
||||
case application:get_env(ejabberd, Name) of
|
||||
{ok, I} when is_integer(I), I>0 ->
|
||||
I;
|
||||
undefined ->
|
||||
Default;
|
||||
{ok, Junk} ->
|
||||
error_logger:error_msg("wrong value for ~s: ~p; "
|
||||
"using ~p as a fallback~n",
|
||||
[Name, Junk, Default]),
|
||||
Default
|
||||
end.
|
||||
get_pos_string_env(Name, Default) ->
|
||||
case application:get_env(ejabberd, Name) of
|
||||
{ok, L} when is_list(L) ->
|
||||
L;
|
||||
undefined ->
|
||||
Default;
|
||||
{ok, Junk} ->
|
||||
error_logger:error_msg("wrong value for ~s: ~p; "
|
||||
"using ~p as a fallback~n",
|
||||
[Name, Junk, Default]),
|
||||
Default
|
||||
end.
|
||||
|
||||
start() ->
|
||||
application:load(sasl),
|
||||
application:set_env(sasl, sasl_error_logger, false),
|
||||
application:load(lager),
|
||||
ConsoleLog = get_log_path(),
|
||||
Dir = filename:dirname(ConsoleLog),
|
||||
ErrorLog = filename:join([Dir, "error.log"]),
|
||||
CrashLog = filename:join([Dir, "crash.log"]),
|
||||
LogRotateDate = get_pos_string_env(log_rotate_date, ""),
|
||||
LogRotateSize = get_pos_integer_env(log_rotate_size, 10*1024*1024),
|
||||
LogRotateCount = get_pos_integer_env(log_rotate_count, 1),
|
||||
LogRateLimit = get_pos_integer_env(log_rate_limit, 100),
|
||||
application:set_env(lager, error_logger_hwm, LogRateLimit),
|
||||
application:set_env(
|
||||
lager, handlers,
|
||||
[{lager_console_backend, info},
|
||||
{lager_file_backend, [{file, ConsoleLog}, {level, info}, {count, 1}]},
|
||||
{lager_file_backend, [{file, ErrorLog}, {level, error}, {count, 1}]}]),
|
||||
{lager_file_backend, [{file, ConsoleLog}, {level, info}, {date, LogRotateDate},
|
||||
{count, LogRotateCount}, {size, LogRotateSize}]},
|
||||
{lager_file_backend, [{file, ErrorLog}, {level, error}, {date, LogRotateDate},
|
||||
{count, LogRotateCount}, {size, LogRotateSize}]}]),
|
||||
application:set_env(lager, crash_log, CrashLog),
|
||||
application:set_env(lager, crash_log_date, LogRotateDate),
|
||||
application:set_env(lager, crash_log_size, LogRotateSize),
|
||||
application:set_env(lager, crash_log_count, LogRotateCount),
|
||||
ejabberd:start_app(lager),
|
||||
ok.
|
||||
|
||||
reopen_log() ->
|
||||
lager_crash_log ! rotate,
|
||||
lists:foreach(
|
||||
fun({lager_file_backend, File}) ->
|
||||
whereis(lager_event) ! {rotate, File};
|
||||
(_) ->
|
||||
ok
|
||||
end, gen_event:which_handlers(lager_event)),
|
||||
reopen_sasl_log().
|
||||
end, gen_event:which_handlers(lager_event)).
|
||||
|
||||
get() ->
|
||||
case lager:get_loglevel(lager_console_backend) of
|
||||
@@ -145,8 +182,6 @@ get() ->
|
||||
set(LogLevel) ->
|
||||
p1_loglevel:set(LogLevel).
|
||||
|
||||
-endif.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
@@ -179,3 +214,5 @@ get_sasl_error_logger_type () ->
|
||||
{ok, Bad} -> exit ({bad_config, {sasl, {errlog_type, Bad}}});
|
||||
_ -> all
|
||||
end.
|
||||
|
||||
-endif.
|
||||
|
||||
+11
-5
@@ -204,7 +204,7 @@ decode_term(Bin) ->
|
||||
%%%----------------------------------------------------------------------
|
||||
init([Host, StartInterval]) ->
|
||||
case ejabberd_config:get_option(
|
||||
{keepalive_interval, Host},
|
||||
{odbc_keepalive_interval, Host},
|
||||
fun(I) when is_integer(I), I>0 -> I end) of
|
||||
undefined ->
|
||||
ok;
|
||||
@@ -450,7 +450,7 @@ sql_query_internal(Query) ->
|
||||
?DEBUG("MySQL, Send query~n~p~n", [Query]),
|
||||
%%squery to be able to specify result_type = binary
|
||||
%%[Query] because p1_mysql_conn expect query to be a list (elements can be binaries, or iolist)
|
||||
%% but doesn't accept just a binary
|
||||
%% but doesn't accept just a binary
|
||||
R = mysql_to_odbc(p1_mysql_conn:squery(State#state.db_ref,
|
||||
[Query], self(),
|
||||
[{timeout, (?TRANSACTION_TIMEOUT) - 1000},
|
||||
@@ -553,10 +553,16 @@ mysql_to_odbc({data, MySQLRes}) ->
|
||||
mysql_item_to_odbc(p1_mysql:get_result_field_info(MySQLRes),
|
||||
p1_mysql:get_result_rows(MySQLRes));
|
||||
mysql_to_odbc({error, MySQLRes})
|
||||
when is_binary(MySQLRes) ->
|
||||
when is_binary(MySQLRes) ->
|
||||
{error, MySQLRes};
|
||||
mysql_to_odbc({error, MySQLRes})
|
||||
when is_list(MySQLRes) ->
|
||||
{error, list_to_binary(MySQLRes)};
|
||||
mysql_to_odbc({error, MySQLRes}) ->
|
||||
{error, p1_mysql:get_result_reason(MySQLRes)}.
|
||||
{error, p1_mysql:get_result_reason(MySQLRes)};
|
||||
mysql_to_odbc(ok) ->
|
||||
ok.
|
||||
|
||||
|
||||
%% When tabular data is returned, convert it to the ODBC formalism
|
||||
mysql_item_to_odbc(Columns, Recs) ->
|
||||
@@ -588,7 +594,7 @@ db_opts(Host) ->
|
||||
[odbc, Server];
|
||||
_ ->
|
||||
Port = ejabberd_config:get_option(
|
||||
{port, Host},
|
||||
{odbc_port, Host},
|
||||
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
|
||||
case Type of
|
||||
mysql -> ?MYSQL_PORT;
|
||||
|
||||
@@ -0,0 +1,554 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Alexey Shchepin <alexey@process-one.net>
|
||||
%%% @doc
|
||||
%%% Interface for Riak database
|
||||
%%% @end
|
||||
%%% Created : 29 Dec 2011 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%% @copyright (C) 2002-2014 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., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(ejabberd_riak).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/4, get_proc/1, make_bucket/1, put/2, put/3,
|
||||
get/2, get/3, get_by_index/4, delete/1, delete/2,
|
||||
count_by_index/3, get_by_index_range/5,
|
||||
get_keys/1, get_keys_by_index/3, is_connected/0,
|
||||
count/1, delete_by_index/3]).
|
||||
%% For debugging
|
||||
-export([get_tables/0]).
|
||||
%% map/reduce exports
|
||||
-export([map_key/3]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-record(state, {pid = self() :: pid()}).
|
||||
|
||||
-type index() :: {binary(), any()}.
|
||||
|
||||
-type index_info() :: [{i, any()} | {'2i', [index()]}].
|
||||
|
||||
%% The `record_schema()' is just a tuple:
|
||||
%% {record_info(fields, some_record), #some_record{}}
|
||||
|
||||
-type record_schema() :: {[atom()], tuple()}.
|
||||
|
||||
%% The `index_info()' is used in put/delete functions:
|
||||
%% `i' defines a primary index, `` '2i' '' defines secondary indexes.
|
||||
%% There must be only one primary index. If `i' is not specified,
|
||||
%% the first element of the record is assumed as a primary index,
|
||||
%% i.e. `i' = element(2, Record).
|
||||
|
||||
-export_types([index_info/0]).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
%% @private
|
||||
start_link(Num, Server, Port, _StartInterval) ->
|
||||
gen_server:start_link({local, get_proc(Num)}, ?MODULE, [Server, Port], []).
|
||||
|
||||
%% @private
|
||||
is_connected() ->
|
||||
catch riakc_pb_socket:is_connected(get_random_pid()).
|
||||
|
||||
%% @private
|
||||
get_proc(I) ->
|
||||
jlib:binary_to_atom(
|
||||
iolist_to_binary(
|
||||
[atom_to_list(?MODULE), $_, integer_to_list(I)])).
|
||||
|
||||
-spec make_bucket(atom()) -> binary().
|
||||
%% @doc Makes a bucket from a table name
|
||||
%% @private
|
||||
make_bucket(Table) ->
|
||||
erlang:atom_to_binary(Table, utf8).
|
||||
|
||||
-spec put(tuple(), record_schema()) -> ok | {error, any()}.
|
||||
%% @equiv put(Record, [])
|
||||
put(Record, RecFields) ->
|
||||
?MODULE:put(Record, RecFields, []).
|
||||
|
||||
-spec put(tuple(), record_schema(), index_info()) -> ok | {error, any()}.
|
||||
%% @doc Stores a record `Rec' with indexes described in ``IndexInfo''
|
||||
put(Rec, RecSchema, IndexInfo) ->
|
||||
Key = encode_key(proplists:get_value(i, IndexInfo, element(2, Rec))),
|
||||
SecIdxs = [encode_index_key(K, V) ||
|
||||
{K, V} <- proplists:get_value('2i', IndexInfo, [])],
|
||||
Table = element(1, Rec),
|
||||
Value = encode_record(Rec, RecSchema),
|
||||
case put_raw(Table, Key, Value, SecIdxs) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, _} = Error ->
|
||||
log_error(Error, put, [{record, Rec},
|
||||
{index_info, IndexInfo}]),
|
||||
Error
|
||||
end.
|
||||
|
||||
put_raw(Table, Key, Value, Indexes) ->
|
||||
Bucket = make_bucket(Table),
|
||||
Obj = riakc_obj:new(Bucket, Key, Value, "application/x-erlang-term"),
|
||||
Obj1 = if Indexes /= [] ->
|
||||
MetaData = dict:store(<<"index">>, Indexes, dict:new()),
|
||||
riakc_obj:update_metadata(Obj, MetaData);
|
||||
true ->
|
||||
Obj
|
||||
end,
|
||||
catch riakc_pb_socket:put(get_random_pid(), Obj1).
|
||||
|
||||
get_object_raw(Table, Key) ->
|
||||
Bucket = make_bucket(Table),
|
||||
catch riakc_pb_socket:get(get_random_pid(), Bucket, Key).
|
||||
|
||||
-spec get(atom(), record_schema()) -> {ok, [any()]} | {error, any()}.
|
||||
%% @doc Returns all objects from table `Table'
|
||||
get(Table, RecSchema) ->
|
||||
Bucket = make_bucket(Table),
|
||||
case catch riakc_pb_socket:mapred(
|
||||
get_random_pid(),
|
||||
Bucket,
|
||||
[{map, {modfun, riak_kv_mapreduce, map_object_value},
|
||||
none, true}]) of
|
||||
{ok, [{_, Objs}]} ->
|
||||
{ok, lists:flatmap(
|
||||
fun(Obj) ->
|
||||
case catch decode_record(Obj, RecSchema) of
|
||||
{'EXIT', _} ->
|
||||
Error = {error, make_invalid_object(Obj)},
|
||||
log_error(Error, get,
|
||||
[{table, Table}]),
|
||||
[];
|
||||
Term ->
|
||||
[Term]
|
||||
end
|
||||
end, Objs)};
|
||||
{ok, []} ->
|
||||
{ok, []};
|
||||
{error, notfound} ->
|
||||
{ok, []};
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
-spec get(atom(), record_schema(), any()) -> {ok, any()} | {error, any()}.
|
||||
%% @doc Reads record by `Key' from table `Table'
|
||||
get(Table, RecSchema, Key) ->
|
||||
case get_raw(Table, encode_key(Key)) of
|
||||
{ok, Val} ->
|
||||
case catch decode_record(Val, RecSchema) of
|
||||
{'EXIT', _} ->
|
||||
Error = {error, make_invalid_object(Val)},
|
||||
log_error(Error, get, [{table, Table}, {key, Key}]),
|
||||
{error, notfound};
|
||||
Term ->
|
||||
{ok, Term}
|
||||
end;
|
||||
{error, _} = Error ->
|
||||
log_error(Error, get, [{table, Table},
|
||||
{key, Key}]),
|
||||
Error
|
||||
end.
|
||||
|
||||
-spec get_by_index(atom(), record_schema(), binary(), any()) ->
|
||||
{ok, [any()]} | {error, any()}.
|
||||
%% @doc Reads records by `Index' and value `Key' from `Table'
|
||||
get_by_index(Table, RecSchema, Index, Key) ->
|
||||
{NewIndex, NewKey} = encode_index_key(Index, Key),
|
||||
case get_by_index_raw(Table, NewIndex, NewKey) of
|
||||
{ok, Vals} ->
|
||||
{ok, lists:flatmap(
|
||||
fun(Val) ->
|
||||
case catch decode_record(Val, RecSchema) of
|
||||
{'EXIT', _} ->
|
||||
Error = {error, make_invalid_object(Val)},
|
||||
log_error(Error, get_by_index,
|
||||
[{table, Table},
|
||||
{index, Index},
|
||||
{key, Key}]),
|
||||
[];
|
||||
Term ->
|
||||
[Term]
|
||||
end
|
||||
end, Vals)};
|
||||
{error, notfound} ->
|
||||
{ok, []};
|
||||
{error, _} = Error ->
|
||||
log_error(Error, get_by_index,
|
||||
[{table, Table},
|
||||
{index, Index},
|
||||
{key, Key}]),
|
||||
Error
|
||||
end.
|
||||
|
||||
-spec get_by_index_range(atom(), record_schema(), binary(), any(), any()) ->
|
||||
{ok, [any()]} | {error, any()}.
|
||||
%% @doc Reads records by `Index' in the range `FromKey'..`ToKey' from `Table'
|
||||
get_by_index_range(Table, RecSchema, Index, FromKey, ToKey) ->
|
||||
{NewIndex, NewFromKey} = encode_index_key(Index, FromKey),
|
||||
{NewIndex, NewToKey} = encode_index_key(Index, ToKey),
|
||||
case get_by_index_range_raw(Table, NewIndex, NewFromKey, NewToKey) of
|
||||
{ok, Vals} ->
|
||||
{ok, lists:flatmap(
|
||||
fun(Val) ->
|
||||
case catch decode_record(Val, RecSchema) of
|
||||
{'EXIT', _} ->
|
||||
Error = {error, make_invalid_object(Val)},
|
||||
log_error(Error, get_by_index_range,
|
||||
[{table, Table},
|
||||
{index, Index},
|
||||
{start_key, FromKey},
|
||||
{end_key, ToKey}]),
|
||||
[];
|
||||
Term ->
|
||||
[Term]
|
||||
end
|
||||
end, Vals)};
|
||||
{error, notfound} ->
|
||||
{ok, []};
|
||||
{error, _} = Error ->
|
||||
log_error(Error, get_by_index_range,
|
||||
[{table, Table}, {index, Index},
|
||||
{start_key, FromKey}, {end_key, ToKey}]),
|
||||
Error
|
||||
end.
|
||||
|
||||
get_raw(Table, Key) ->
|
||||
case get_object_raw(Table, Key) of
|
||||
{ok, Obj} ->
|
||||
{ok, riakc_obj:get_value(Obj)};
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
-spec get_keys(atom()) -> {ok, [any()]} | {error, any()}.
|
||||
%% @doc Returns a list of index values
|
||||
get_keys(Table) ->
|
||||
Bucket = make_bucket(Table),
|
||||
case catch riakc_pb_socket:mapred(
|
||||
get_random_pid(),
|
||||
Bucket,
|
||||
[{map, {modfun, ?MODULE, map_key}, none, true}]) of
|
||||
{ok, [{_, Keys}]} ->
|
||||
{ok, Keys};
|
||||
{ok, []} ->
|
||||
{ok, []};
|
||||
{error, _} = Error ->
|
||||
log_error(Error, get_keys, [{table, Table}]),
|
||||
Error
|
||||
end.
|
||||
|
||||
-spec get_keys_by_index(atom(), binary(),
|
||||
any()) -> {ok, [any()]} | {error, any()}.
|
||||
%% @doc Returns a list of primary keys of objects indexed by `Key'.
|
||||
get_keys_by_index(Table, Index, Key) ->
|
||||
{NewIndex, NewKey} = encode_index_key(Index, Key),
|
||||
Bucket = make_bucket(Table),
|
||||
case catch riakc_pb_socket:mapred(
|
||||
get_random_pid(),
|
||||
{index, Bucket, NewIndex, NewKey},
|
||||
[{map, {modfun, ?MODULE, map_key}, none, true}]) of
|
||||
{ok, [{_, Keys}]} ->
|
||||
{ok, Keys};
|
||||
{ok, []} ->
|
||||
{ok, []};
|
||||
{error, _} = Error ->
|
||||
log_error(Error, get_keys_by_index, [{table, Table},
|
||||
{index, Index},
|
||||
{key, Key}]),
|
||||
Error
|
||||
end.
|
||||
|
||||
%% @hidden
|
||||
get_tables() ->
|
||||
catch riakc_pb_socket:list_buckets(get_random_pid()).
|
||||
|
||||
get_by_index_raw(Table, Index, Key) ->
|
||||
Bucket = make_bucket(Table),
|
||||
case riakc_pb_socket:mapred(
|
||||
get_random_pid(),
|
||||
{index, Bucket, Index, Key},
|
||||
[{map, {modfun, riak_kv_mapreduce, map_object_value},
|
||||
none, true}]) of
|
||||
{ok, [{_, Objs}]} ->
|
||||
{ok, Objs};
|
||||
{ok, []} ->
|
||||
{ok, []};
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
get_by_index_range_raw(Table, Index, FromKey, ToKey) ->
|
||||
Bucket = make_bucket(Table),
|
||||
case catch riakc_pb_socket:mapred(
|
||||
get_random_pid(),
|
||||
{index, Bucket, Index, FromKey, ToKey},
|
||||
[{map, {modfun, riak_kv_mapreduce, map_object_value},
|
||||
none, true}]) of
|
||||
{ok, [{_, Objs}]} ->
|
||||
{ok, Objs};
|
||||
{ok, []} ->
|
||||
{ok, []};
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
-spec count(atom()) -> {ok, non_neg_integer()} | {error, any()}.
|
||||
%% @doc Returns the number of objects in the `Table'
|
||||
count(Table) ->
|
||||
Bucket = make_bucket(Table),
|
||||
case catch riakc_pb_socket:mapred(
|
||||
get_random_pid(),
|
||||
Bucket,
|
||||
[{reduce, {modfun, riak_kv_mapreduce, reduce_count_inputs},
|
||||
none, true}]) of
|
||||
{ok, [{_, [Cnt]}]} ->
|
||||
{ok, Cnt};
|
||||
{error, _} = Error ->
|
||||
log_error(Error, count, [{table, Table}]),
|
||||
Error
|
||||
end.
|
||||
|
||||
-spec count_by_index(atom(), binary(), any()) ->
|
||||
{ok, non_neg_integer()} | {error, any()}.
|
||||
%% @doc Returns the number of objects in the `Table' by index
|
||||
count_by_index(Tab, Index, Key) ->
|
||||
{NewIndex, NewKey} = encode_index_key(Index, Key),
|
||||
case count_by_index_raw(Tab, NewIndex, NewKey) of
|
||||
{ok, Cnt} ->
|
||||
{ok, Cnt};
|
||||
{error, notfound} ->
|
||||
{ok, 0};
|
||||
{error, _} = Error ->
|
||||
log_error(Error, count_by_index,
|
||||
[{table, Tab},
|
||||
{index, Index},
|
||||
{key, Key}]),
|
||||
Error
|
||||
end.
|
||||
|
||||
count_by_index_raw(Table, Index, Key) ->
|
||||
Bucket = make_bucket(Table),
|
||||
case catch riakc_pb_socket:mapred(
|
||||
get_random_pid(),
|
||||
{index, Bucket, Index, Key},
|
||||
[{reduce, {modfun, riak_kv_mapreduce, reduce_count_inputs},
|
||||
none, true}]) of
|
||||
{ok, [{_, [Cnt]}]} ->
|
||||
{ok, Cnt};
|
||||
{error, _} = Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
-spec delete(tuple() | atom()) -> ok | {error, any()}.
|
||||
%% @doc Same as delete(T, []) when T is record.
|
||||
%% Or deletes all elements from table if T is atom.
|
||||
delete(Rec) when is_tuple(Rec) ->
|
||||
delete(Rec, []);
|
||||
delete(Table) when is_atom(Table) ->
|
||||
try
|
||||
{ok, Keys} = ?MODULE:get_keys(Table),
|
||||
lists:foreach(
|
||||
fun(K) ->
|
||||
ok = delete(Table, K)
|
||||
end, Keys)
|
||||
catch _:{badmatch, Err} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec delete(tuple() | atom(), index_info() | any()) -> ok | {error, any()}.
|
||||
%% @doc Delete an object
|
||||
delete(Rec, Opts) when is_tuple(Rec) ->
|
||||
Table = element(1, Rec),
|
||||
Key = proplists:get_value(i, Opts, element(2, Rec)),
|
||||
delete(Table, Key);
|
||||
delete(Table, Key) when is_atom(Table) ->
|
||||
case delete_raw(Table, encode_key(Key)) of
|
||||
ok ->
|
||||
ok;
|
||||
Err ->
|
||||
log_error(Err, delete, [{table, Table}, {key, Key}]),
|
||||
Err
|
||||
end.
|
||||
|
||||
delete_raw(Table, Key) ->
|
||||
Bucket = make_bucket(Table),
|
||||
catch riakc_pb_socket:delete(get_random_pid(), Bucket, Key).
|
||||
|
||||
-spec delete_by_index(atom(), binary(), any()) -> ok | {error, any()}.
|
||||
%% @doc Deletes objects by index
|
||||
delete_by_index(Table, Index, Key) ->
|
||||
try
|
||||
{ok, Keys} = get_keys_by_index(Table, Index, Key),
|
||||
lists:foreach(
|
||||
fun(K) ->
|
||||
ok = delete(Table, K)
|
||||
end, Keys)
|
||||
catch _:{badmatch, Err} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% map/reduce functions
|
||||
%%%===================================================================
|
||||
%% @private
|
||||
map_key(Obj, _, _) ->
|
||||
[case riak_object:key(Obj) of
|
||||
<<"b_", B/binary>> ->
|
||||
B;
|
||||
<<"i_", B/binary>> ->
|
||||
list_to_integer(binary_to_list(B));
|
||||
B ->
|
||||
erlang:binary_to_term(B)
|
||||
end].
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server API
|
||||
%%%===================================================================
|
||||
%% @private
|
||||
init([Server, Port]) ->
|
||||
case riakc_pb_socket:start(
|
||||
Server, Port,
|
||||
[auto_reconnect]) of
|
||||
{ok, Pid} ->
|
||||
erlang:monitor(process, Pid),
|
||||
{ok, #state{pid = Pid}};
|
||||
Err ->
|
||||
{stop, Err}
|
||||
end.
|
||||
|
||||
%% @private
|
||||
handle_call(get_pid, _From, #state{pid = Pid} = State) ->
|
||||
{reply, {ok, Pid}, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
%% @private
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
%% @private
|
||||
handle_info({'DOWN', _MonitorRef, _Type, _Object, _Info}, State) ->
|
||||
{stop, normal, State};
|
||||
handle_info(_Info, State) ->
|
||||
?ERROR_MSG("unexpected info: ~p", [_Info]),
|
||||
{noreply, State}.
|
||||
|
||||
%% @private
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
%% @private
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
encode_index_key(Idx, Key) when is_integer(Key) ->
|
||||
{<<Idx/binary, "_int">>, Key};
|
||||
encode_index_key(Idx, Key) ->
|
||||
{<<Idx/binary, "_bin">>, encode_key(Key)}.
|
||||
|
||||
encode_key(Bin) when is_binary(Bin) ->
|
||||
<<"b_", Bin/binary>>;
|
||||
encode_key(Int) when is_integer(Int) ->
|
||||
<<"i_", (list_to_binary(integer_to_list(Int)))/binary>>;
|
||||
encode_key(Term) ->
|
||||
erlang:term_to_binary(Term).
|
||||
|
||||
log_error({error, notfound}, _, _) ->
|
||||
ok;
|
||||
log_error({error, Why} = Err, Function, Opts) ->
|
||||
Txt = lists:map(
|
||||
fun({table, Table}) ->
|
||||
io_lib:fwrite("** Table: ~p~n", [Table]);
|
||||
({key, Key}) ->
|
||||
io_lib:fwrite("** Key: ~p~n", [Key]);
|
||||
({index, Index}) ->
|
||||
io_lib:fwrite("** Index = ~p~n", [Index]);
|
||||
({start_key, Key}) ->
|
||||
io_lib:fwrite("** Start Key: ~p~n", [Key]);
|
||||
({end_key, Key}) ->
|
||||
io_lib:fwrite("** End Key: ~p~n", [Key]);
|
||||
({record, Rec}) ->
|
||||
io_lib:fwrite("** Record = ~p~n", [Rec]);
|
||||
({index_info, IdxInfo}) ->
|
||||
io_lib:fwrite("** Index info = ~p~n", [IdxInfo]);
|
||||
(_) ->
|
||||
""
|
||||
end, Opts),
|
||||
ErrTxt = if is_binary(Why) ->
|
||||
io_lib:fwrite("** Error: ~s", [Why]);
|
||||
true ->
|
||||
io_lib:fwrite("** Error: ~p", [Err])
|
||||
end,
|
||||
?ERROR_MSG("database error:~n** Function: ~p~n~s~s",
|
||||
[Function, Txt, ErrTxt]);
|
||||
log_error(_, _, _) ->
|
||||
ok.
|
||||
|
||||
make_invalid_object(Val) ->
|
||||
list_to_binary(io_lib:fwrite("Invalid object: ~p", [Val])).
|
||||
|
||||
get_random_pid() ->
|
||||
PoolPid = ejabberd_riak_sup:get_random_pid(),
|
||||
case catch gen_server:call(PoolPid, get_pid) of
|
||||
{ok, Pid} ->
|
||||
Pid;
|
||||
{'EXIT', {timeout, _}} ->
|
||||
throw({error, timeout});
|
||||
{'EXIT', Err} ->
|
||||
throw({error, Err})
|
||||
end.
|
||||
|
||||
encode_record(Rec, {Fields, DefRec}) ->
|
||||
term_to_binary(encode_record(Rec, Fields, DefRec, 2)).
|
||||
|
||||
encode_record(Rec, [FieldName|Fields], DefRec, Pos) ->
|
||||
Value = element(Pos, Rec),
|
||||
DefValue = element(Pos, DefRec),
|
||||
if Value == DefValue ->
|
||||
encode_record(Rec, Fields, DefRec, Pos+1);
|
||||
true ->
|
||||
[{FieldName, Value}|encode_record(Rec, Fields, DefRec, Pos+1)]
|
||||
end;
|
||||
encode_record(_, [], _, _) ->
|
||||
[].
|
||||
|
||||
decode_record(Bin, {Fields, DefRec}) ->
|
||||
decode_record(binary_to_term(Bin), Fields, DefRec, 2).
|
||||
|
||||
decode_record(KeyVals, [FieldName|Fields], Rec, Pos) ->
|
||||
case lists:keyfind(FieldName, 1, KeyVals) of
|
||||
{_, Value} ->
|
||||
NewRec = setelement(Pos, Rec, Value),
|
||||
decode_record(KeyVals, Fields, NewRec, Pos+1);
|
||||
false ->
|
||||
decode_record(KeyVals, Fields, Rec, Pos+1)
|
||||
end;
|
||||
decode_record(_, [], Rec, _) ->
|
||||
Rec.
|
||||
@@ -0,0 +1,161 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : ejabberd_riak_sup.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Riak connections supervisor
|
||||
%%% Created : 29 Dec 2011 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2011 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., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(ejabberd_riak_sup).
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
%% API
|
||||
-export([start/0,
|
||||
start_link/0,
|
||||
init/1,
|
||||
get_pids/0,
|
||||
transform_options/1,
|
||||
get_random_pid/0,
|
||||
get_random_pid/1
|
||||
]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-define(DEFAULT_POOL_SIZE, 10).
|
||||
-define(DEFAULT_RIAK_START_INTERVAL, 30). % 30 seconds
|
||||
-define(DEFAULT_RIAK_HOST, "127.0.0.1").
|
||||
-define(DEFAULT_RIAK_PORT, 8087).
|
||||
|
||||
% time to wait for the supervisor to start its child before returning
|
||||
% a timeout error to the request
|
||||
-define(CONNECT_TIMEOUT, 500). % milliseconds
|
||||
|
||||
start() ->
|
||||
case lists:any(
|
||||
fun(Host) ->
|
||||
is_riak_configured(Host)
|
||||
end, ?MYHOSTS) of
|
||||
true ->
|
||||
ejabberd:start_app(riakc),
|
||||
do_start();
|
||||
false ->
|
||||
ok
|
||||
end.
|
||||
|
||||
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),
|
||||
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(Opts) == riak
|
||||
end, Modules),
|
||||
ServerConfigured or PortConfigured
|
||||
or AuthConfigured or ModuleWithRiakDBConfigured.
|
||||
|
||||
do_start() ->
|
||||
SupervisorName = ?MODULE,
|
||||
ChildSpec =
|
||||
{SupervisorName,
|
||||
{?MODULE, start_link, []},
|
||||
transient,
|
||||
infinity,
|
||||
supervisor,
|
||||
[?MODULE]},
|
||||
case supervisor:start_child(ejabberd_sup, ChildSpec) of
|
||||
{ok, _PID} ->
|
||||
ok;
|
||||
_Error ->
|
||||
?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying...~n",
|
||||
[SupervisorName, _Error]),
|
||||
timer:sleep(5000),
|
||||
start()
|
||||
end.
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
init([]) ->
|
||||
PoolSize = get_pool_size(),
|
||||
StartInterval = get_start_interval(),
|
||||
Server = get_riak_server(),
|
||||
Port = get_riak_port(),
|
||||
{ok, {{one_for_one, PoolSize*10, 1},
|
||||
lists:map(
|
||||
fun(I) ->
|
||||
{ejabberd_riak:get_proc(I),
|
||||
{ejabberd_riak, start_link,
|
||||
[I, Server, Port, StartInterval*1000]},
|
||||
transient, 2000, worker, [?MODULE]}
|
||||
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).
|
||||
|
||||
get_pool_size() ->
|
||||
ejabberd_config:get_option(
|
||||
riak_pool_size,
|
||||
fun(N) when is_integer(N), N >= 1 -> N end,
|
||||
?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).
|
||||
|
||||
get_riak_port() ->
|
||||
ejabberd_config:get_option(
|
||||
riak_port,
|
||||
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
|
||||
?DEFAULT_RIAK_PORT).
|
||||
|
||||
get_pids() ->
|
||||
[ejabberd_riak:get_proc(I) || I <- lists:seq(1, get_pool_size())].
|
||||
|
||||
get_random_pid() ->
|
||||
get_random_pid(now()).
|
||||
|
||||
get_random_pid(Term) ->
|
||||
I = erlang:phash2(Term, get_pool_size()) + 1,
|
||||
ejabberd_riak:get_proc(I).
|
||||
|
||||
transform_options(Opts) ->
|
||||
lists:foldl(fun transform_options/2, [], Opts).
|
||||
|
||||
transform_options({riak_server, {S, P}}, Opts) ->
|
||||
[{riak_server, S}, {riak_port, P}|Opts];
|
||||
transform_options(Opt, Opts) ->
|
||||
[Opt|Opts].
|
||||
@@ -374,8 +374,8 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||
#xmlel{name = <<"success">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||
children = []}),
|
||||
?DEBUG("(~w) Accepted s2s authentication for ~s",
|
||||
[StateData#state.socket, AuthDomain]),
|
||||
?INFO_MSG("Accepted s2s EXTERNAL authentication for ~s (TLS=~p)",
|
||||
[AuthDomain, StateData#state.tls_enabled]),
|
||||
change_shaper(StateData, <<"">>,
|
||||
jlib:make_jid(<<"">>, AuthDomain, <<"">>)),
|
||||
{next_state, wait_for_stream,
|
||||
@@ -515,6 +515,8 @@ stream_established({valid, From, To}, StateData) ->
|
||||
[{<<"from">>, To}, {<<"to">>, From},
|
||||
{<<"type">>, <<"valid">>}],
|
||||
children = []}),
|
||||
?INFO_MSG("Accepted s2s dialback authentication for ~s (TLS=~p)",
|
||||
[From, StateData#state.tls_enabled]),
|
||||
LFrom = jlib:nameprep(From),
|
||||
LTo = jlib:nameprep(To),
|
||||
NSD = StateData#state{connections =
|
||||
|
||||
+5
-5
@@ -54,7 +54,7 @@
|
||||
connected_users/0,
|
||||
connected_users_number/0,
|
||||
user_resources/2,
|
||||
disconnect_user/2,
|
||||
kick_user/2,
|
||||
get_session_pid/3,
|
||||
get_user_info/3,
|
||||
get_user_ip/3,
|
||||
@@ -822,10 +822,10 @@ commands() ->
|
||||
module = ?MODULE, function = user_resources,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
result = {resources, {list, {resource, string}}}},
|
||||
#ejabberd_commands{name = disconnect_user,
|
||||
#ejabberd_commands{name = kick_user,
|
||||
tags = [session],
|
||||
desc = "Disconnect user's active sessions",
|
||||
module = ?MODULE, function = disconnect_user,
|
||||
module = ?MODULE, function = kick_user,
|
||||
args = [{user, binary}, {host, binary}],
|
||||
result = {num_resources, integer}}].
|
||||
|
||||
@@ -844,12 +844,12 @@ user_resources(User, Server) ->
|
||||
Resources = get_user_resources(User, Server),
|
||||
lists:sort(Resources).
|
||||
|
||||
disconnect_user(User, Server) ->
|
||||
kick_user(User, Server) ->
|
||||
Resources = get_user_resources(User, Server),
|
||||
lists:foreach(
|
||||
fun(Resource) ->
|
||||
PID = get_session_pid(User, Server, Resource),
|
||||
PID ! disconnect
|
||||
PID ! kick
|
||||
end, Resources),
|
||||
length(Resources).
|
||||
|
||||
|
||||
+46
-22
@@ -17,11 +17,12 @@
|
||||
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-export([start/2, handler/2, socket_type/0, transform_listen_option/2]).
|
||||
-export([start/2, handler/2, process/2, socket_type/0,
|
||||
transform_listen_option/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-include("ejabberd_http.hrl").
|
||||
-include("mod_roster.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
@@ -170,12 +171,14 @@
|
||||
%% -----------------------------
|
||||
|
||||
start({gen_tcp = _SockMod, Socket}, Opts) ->
|
||||
%MaxSessions = gen_mod:get_opt(maxsessions, Opts,
|
||||
% fun(I) when is_integer(I), I>0 -> I end,
|
||||
% 10),
|
||||
Timeout = gen_mod:get_opt(timeout, Opts,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
5000),
|
||||
ejabberd_http:start({gen_tcp, Socket}, [{xmlrpc, true}|Opts]).
|
||||
|
||||
socket_type() -> raw.
|
||||
|
||||
%% -----------------------------
|
||||
%% HTTP interface
|
||||
%% -----------------------------
|
||||
process(_, #request{method = 'POST', data = Data, opts = Opts}) ->
|
||||
AccessCommandsOpts = gen_mod:get_opt(access_commands, Opts,
|
||||
fun(L) when is_list(L) -> L end,
|
||||
[]),
|
||||
@@ -201,19 +204,36 @@ start({gen_tcp = _SockMod, Socket}, Opts) ->
|
||||
[?MODULE, Wrong]),
|
||||
[]
|
||||
end, AccessCommandsOpts),
|
||||
GetAuth = case [ACom
|
||||
|| {Ac, _, _} = ACom <- AccessCommands, Ac /= all]
|
||||
of
|
||||
[] -> false;
|
||||
_ -> true
|
||||
GetAuth = case [ACom || {Ac, _, _} = ACom <- AccessCommands, Ac /= all] of
|
||||
[] -> false;
|
||||
_ -> true
|
||||
end,
|
||||
Handler = {?MODULE, handler},
|
||||
State = #state{access_commands = AccessCommands,
|
||||
get_auth = GetAuth},
|
||||
Pid = proc_lib:spawn(xmlrpc_http, handler, [Socket, Timeout, Handler, State]),
|
||||
{ok, Pid}.
|
||||
|
||||
socket_type() -> raw.
|
||||
State = #state{access_commands = AccessCommands, get_auth = GetAuth},
|
||||
case xml_stream:parse_element(Data) of
|
||||
{error, _} ->
|
||||
{400, [],
|
||||
#xmlel{name = <<"h1">>, attrs = [],
|
||||
children = [{xmlcdata, <<"Malformed XML">>}]}};
|
||||
El ->
|
||||
case p1_xmlrpc:decode(El) of
|
||||
{error, _} = Err ->
|
||||
?ERROR_MSG("XML-RPC request ~s failed with reason: ~p",
|
||||
[Data, Err]),
|
||||
{400, [],
|
||||
#xmlel{name = <<"h1">>, attrs = [],
|
||||
children = [{xmlcdata, <<"Malformed Request">>}]}};
|
||||
{ok, RPC} ->
|
||||
?DEBUG("got XML-RPC request: ~p", [RPC]),
|
||||
{false, Result} = handler(State, RPC),
|
||||
XML = xml:element_to_binary(p1_xmlrpc:encode(Result)),
|
||||
{200, [{<<"Content-Type">>, <<"text/xml">>}],
|
||||
<<"<?xml version=\"1.0\"?>", XML/binary>>}
|
||||
end
|
||||
end;
|
||||
process(_, _) ->
|
||||
{400, [],
|
||||
#xmlel{name = <<"h1">>, attrs = [],
|
||||
children = [{xmlcdata, <<"400 Bad Request">>}]}}.
|
||||
|
||||
%% -----------------------------
|
||||
%% Access verification
|
||||
@@ -428,8 +448,8 @@ format_arg({array, Elements}, {list, ElementsDef})
|
||||
format_arg(Arg, integer) when is_integer(Arg) -> Arg;
|
||||
format_arg(Arg, binary) when is_list(Arg) -> list_to_binary(Arg);
|
||||
format_arg(Arg, binary) when is_binary(Arg) -> Arg;
|
||||
format_arg(Arg, string) when is_list(Arg) -> list_to_binary(Arg);
|
||||
format_arg(Arg, string) when is_binary(Arg) -> Arg;
|
||||
format_arg(Arg, string) when is_list(Arg) -> Arg;
|
||||
format_arg(Arg, string) when is_binary(Arg) -> binary_to_list(Arg);
|
||||
format_arg(Arg, Format) ->
|
||||
?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
|
||||
throw({error_formatting_argument, Arg, Format}).
|
||||
@@ -450,6 +470,10 @@ format_result(String, {Name, string}) when is_list(String) ->
|
||||
{struct, [{Name, lists:flatten(String)}]};
|
||||
format_result(Binary, {Name, string}) when is_binary(Binary) ->
|
||||
{struct, [{Name, binary_to_list(Binary)}]};
|
||||
format_result(String, {Name, binary}) when is_list(String) ->
|
||||
{struct, [{Name, lists:flatten(String)}]};
|
||||
format_result(Binary, {Name, binary}) when is_binary(Binary) ->
|
||||
{struct, [{Name, binary_to_list(Binary)}]};
|
||||
format_result(Code, {Name, rescode}) ->
|
||||
{struct, [{Name, make_status(Code)}]};
|
||||
format_result({Code, Text}, {Name, restuple}) ->
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
modules() ->
|
||||
[ejabberd_auth,
|
||||
mod_announce,
|
||||
mod_caps,
|
||||
mod_irc,
|
||||
mod_last,
|
||||
mod_muc,
|
||||
|
||||
@@ -38,9 +38,9 @@ any -> '$empty': [].
|
||||
initial -> value: initial('$1').
|
||||
final -> value: final('$1').
|
||||
|
||||
extensible -> xattr ':dn' ':' matchingrule ':=' value: extensible('$6', ['$1', '$4']).
|
||||
extensible -> xattr ':dn' ':' matchingrule ':=' value: extensible('$6', ['$1', '$4', {dnAttributes, true}]).
|
||||
extensible -> xattr ':' matchingrule ':=' value: extensible('$5', ['$1', '$3']).
|
||||
extensible -> xattr ':dn' ':=' value: extensible('$4', ['$1']).
|
||||
extensible -> xattr ':dn' ':=' value: extensible('$4', ['$1', {dnAttributes, true}]).
|
||||
extensible -> xattr ':=' value: extensible('$3', ['$1']).
|
||||
extensible -> ':dn' ':' matchingrule ':=' value: extensible('$5', ['$3']).
|
||||
extensible -> ':' matchingrule ':=' value: extensible('$4', ['$2']).
|
||||
|
||||
+22
-7
@@ -228,13 +228,28 @@ get_config(Host, Opts) ->
|
||||
Base = get_opt({ldap_base, Host}, Opts,
|
||||
fun iolist_to_binary/1,
|
||||
<<"">>),
|
||||
DerefAliases = get_opt({deref_aliases, Host}, Opts,
|
||||
fun(never) -> never;
|
||||
(searching) -> searching;
|
||||
(finding) -> finding;
|
||||
(always) -> always
|
||||
end, never),
|
||||
#eldap_config{servers = Servers,
|
||||
OldDerefAliases = get_opt({deref_aliases, Host}, Opts,
|
||||
fun(never) -> never;
|
||||
(searching) -> searching;
|
||||
(finding) -> finding;
|
||||
(always) -> always
|
||||
end, unspecified),
|
||||
DerefAliases =
|
||||
if OldDerefAliases == unspecified ->
|
||||
get_opt({ldap_deref_aliases, Host}, Opts,
|
||||
fun(never) -> never;
|
||||
(searching) -> searching;
|
||||
(finding) -> finding;
|
||||
(always) -> always
|
||||
end, never);
|
||||
true ->
|
||||
?WARNING_MSG("Option 'deref_aliases' is deprecated. "
|
||||
"The option is still supported "
|
||||
"but it is better to fix your config: "
|
||||
"use 'ldap_deref_aliases' instead.", []),
|
||||
OldDerefAliases
|
||||
end,
|
||||
#eldap_config{servers = Servers,
|
||||
backups = Backups,
|
||||
tls_options = [{encrypt, Encrypt},
|
||||
{tls_verify, TLSVerify},
|
||||
|
||||
+22
-5
@@ -28,7 +28,7 @@
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
|
||||
-export([start/0, start_module/3, stop_module/2,
|
||||
-export([start/0, start_module/2, start_module/3, stop_module/2,
|
||||
stop_module_keep_config/2, get_opt/3, get_opt/4,
|
||||
get_opt_host/3, db_type/1, db_type/2, get_module_opt/5,
|
||||
get_module_opt_host/3, loaded_modules/1,
|
||||
@@ -60,6 +60,19 @@ start() ->
|
||||
{keypos, #ejabberd_module.module_host}]),
|
||||
ok.
|
||||
|
||||
-spec start_module(binary(), atom()) -> any().
|
||||
|
||||
start_module(Host, Module) ->
|
||||
Modules = ejabberd_config:get_option(
|
||||
{modules, Host},
|
||||
fun(L) when is_list(L) -> L end, []),
|
||||
case lists:keyfind(Module, 1, Modules) of
|
||||
{_, Opts} ->
|
||||
start_module(Host, Module, Opts);
|
||||
false ->
|
||||
{error, not_found_in_config}
|
||||
end.
|
||||
|
||||
-spec start_module(binary(), atom(), opts()) -> any().
|
||||
|
||||
start_module(Host, Module, Opts) ->
|
||||
@@ -196,22 +209,26 @@ get_opt_host(Host, Opts, Default) ->
|
||||
Val = get_opt(host, Opts, fun iolist_to_binary/1, Default),
|
||||
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
|
||||
|
||||
-spec db_type(opts()) -> odbc | mnesia.
|
||||
-spec db_type(opts()) -> odbc | mnesia | riak.
|
||||
|
||||
db_type(Opts) ->
|
||||
get_opt(db_type, Opts,
|
||||
fun(odbc) -> odbc;
|
||||
(internal) -> mnesia;
|
||||
(mnesia) -> mnesia end,
|
||||
(mnesia) -> mnesia;
|
||||
(riak) -> riak
|
||||
end,
|
||||
mnesia).
|
||||
|
||||
-spec db_type(binary(), atom()) -> odbc | mnesia.
|
||||
-spec db_type(binary(), atom()) -> odbc | mnesia | riak.
|
||||
|
||||
db_type(Host, Module) ->
|
||||
get_module_opt(Host, Module, db_type,
|
||||
fun(odbc) -> odbc;
|
||||
(internal) -> mnesia;
|
||||
(mnesia) -> mnesia end,
|
||||
(mnesia) -> mnesia;
|
||||
(riak) -> riak
|
||||
end,
|
||||
mnesia).
|
||||
|
||||
-spec loaded_modules(binary()) -> [atom()].
|
||||
|
||||
+97
-41
@@ -41,10 +41,11 @@
|
||||
jid_remove_resource/1, jid_replace_resource/2,
|
||||
get_iq_namespace/1, iq_query_info/1,
|
||||
iq_query_or_response_info/1, is_iq_request_type/1,
|
||||
iq_to_xml/1, parse_xdata_submit/1, timestamp_to_iso/1,
|
||||
timestamp_to_iso/2, timestamp_to_xml/4,
|
||||
timestamp_to_xml/1, now_to_utc_string/1,
|
||||
now_to_local_string/1, datetime_string_to_timestamp/1,
|
||||
iq_to_xml/1, parse_xdata_submit/1,
|
||||
add_delay_info/3, add_delay_info/4,
|
||||
timestamp_to_iso/1, timestamp_to_iso/2,
|
||||
now_to_utc_string/1, now_to_local_string/1,
|
||||
datetime_string_to_timestamp/1,
|
||||
term_to_base64/1, base64_to_term/1,
|
||||
decode_base64/1, encode_base64/1, ip_to_list/1,
|
||||
rsm_encode/1, rsm_encode/2, rsm_decode/1,
|
||||
@@ -600,6 +601,77 @@ rsm_encode_count(Count, Arr) ->
|
||||
children = [{xmlcdata, i2l(Count)}]}
|
||||
| Arr].
|
||||
|
||||
-spec add_delay_info(xmlel(), erlang:timestamp(), binary()) -> xmlel().
|
||||
|
||||
add_delay_info(El, From, Time) ->
|
||||
add_delay_info(El, From, Time, <<"">>).
|
||||
|
||||
-spec add_delay_info(xmlel(), erlang:timestamp(), binary(),
|
||||
binary()) -> xmlel().
|
||||
|
||||
add_delay_info(El, From, Time, Desc) ->
|
||||
%% TODO: Remove support for <x/>, XEP-0091 is obsolete.
|
||||
El1 = add_delay_info(El, From, Time, Desc, <<"delay">>, ?NS_DELAY),
|
||||
El2 = add_delay_info(El1, From, Time, Desc, <<"x">>, ?NS_DELAY91),
|
||||
El2.
|
||||
|
||||
-spec add_delay_info(xmlel(), erlang:timestamp(), binary(), binary(), binary(),
|
||||
binary()) -> xmlel().
|
||||
|
||||
add_delay_info(El, From, Time, Desc, Name, XMLNS) ->
|
||||
case xml:get_subtag_with_xmlns(El, Name, XMLNS) of
|
||||
false ->
|
||||
%% Add new tag
|
||||
DelayTag = create_delay_tag(Time, From, Desc, XMLNS),
|
||||
xml:append_subtags(El, [DelayTag]);
|
||||
DelayTag ->
|
||||
%% Update existing tag
|
||||
NewDelayTag =
|
||||
case {xml:get_tag_cdata(DelayTag), Desc} of
|
||||
{<<"">>, <<"">>} ->
|
||||
DelayTag;
|
||||
{OldDesc, <<"">>} ->
|
||||
DelayTag#xmlel{children = [{xmlcdata, OldDesc}]};
|
||||
{<<"">>, NewDesc} ->
|
||||
DelayTag#xmlel{children = [{xmlcdata, NewDesc}]};
|
||||
{OldDesc, NewDesc} ->
|
||||
case binary:match(OldDesc, NewDesc) of
|
||||
nomatch ->
|
||||
FinalDesc = <<OldDesc/binary, ", ", NewDesc/binary>>,
|
||||
DelayTag#xmlel{children = [{xmlcdata, FinalDesc}]};
|
||||
_ ->
|
||||
DelayTag#xmlel{children = [{xmlcdata, OldDesc}]}
|
||||
end
|
||||
end,
|
||||
NewEl = xml:remove_subtags(El, Name, {<<"xmlns">>, XMLNS}),
|
||||
xml:append_subtags(NewEl, [NewDelayTag])
|
||||
end.
|
||||
|
||||
-spec create_delay_tag(erlang:timestamp(), jid() | binary(), binary(),
|
||||
binary()) -> xmlel() | error.
|
||||
|
||||
create_delay_tag(TimeStamp, FromJID, Desc, XMLNS) when is_tuple(FromJID) ->
|
||||
From = jlib:jid_to_string(FromJID),
|
||||
{Name, Stamp} = case XMLNS of
|
||||
?NS_DELAY ->
|
||||
{<<"delay">>, now_to_utc_string(TimeStamp, 3)};
|
||||
?NS_DELAY91 ->
|
||||
DateTime = calendar:now_to_universal_time(TimeStamp),
|
||||
{<<"x">>, timestamp_to_iso(DateTime)}
|
||||
end,
|
||||
Children = case Desc of
|
||||
<<"">> -> [];
|
||||
_ -> [{xmlcdata, Desc}]
|
||||
end,
|
||||
#xmlel{name = Name,
|
||||
attrs =
|
||||
[{<<"xmlns">>, XMLNS}, {<<"from">>, From},
|
||||
{<<"stamp">>, Stamp}],
|
||||
children = Children};
|
||||
create_delay_tag(DateTime, Host, Desc, XMLNS) when is_binary(Host) ->
|
||||
FromJID = jlib:make_jid(<<"">>, Host, <<"">>),
|
||||
create_delay_tag(DateTime, FromJID, Desc, XMLNS).
|
||||
|
||||
-type tz() :: {binary(), {integer(), integer()}} | {integer(), integer()} | utc.
|
||||
|
||||
%% Timezone = utc | {Sign::string(), {Hours, Minutes}} | {Hours, Minutes}
|
||||
@@ -611,18 +683,18 @@ timestamp_to_iso({{Year, Month, Day},
|
||||
{Hour, Minute, Second}},
|
||||
Timezone) ->
|
||||
Timestamp_string =
|
||||
lists:flatten(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w",
|
||||
lists:flatten(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B",
|
||||
[Year, Month, Day, Hour, Minute, Second])),
|
||||
Timezone_string = case Timezone of
|
||||
utc -> "Z";
|
||||
{Sign, {TZh, TZm}} ->
|
||||
io_lib:format("~s~2..0w:~2..0w", [Sign, TZh, TZm]);
|
||||
io_lib:format("~s~2..0B:~2..0B", [Sign, TZh, TZm]);
|
||||
{TZh, TZm} ->
|
||||
Sign = case TZh >= 0 of
|
||||
true -> "+";
|
||||
false -> "-"
|
||||
end,
|
||||
io_lib:format("~s~2..0w:~2..0w",
|
||||
io_lib:format("~s~2..0B:~2..0B",
|
||||
[Sign, abs(TZh), TZm])
|
||||
end,
|
||||
{iolist_to_binary(Timestamp_string), iolist_to_binary(Timezone_string)}.
|
||||
@@ -631,46 +703,25 @@ timestamp_to_iso({{Year, Month, Day},
|
||||
|
||||
timestamp_to_iso({{Year, Month, Day},
|
||||
{Hour, Minute, Second}}) ->
|
||||
iolist_to_binary(io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
|
||||
iolist_to_binary(io_lib:format("~4..0B~2..0B~2..0BT~2..0B:~2..0B:~2..0B",
|
||||
[Year, Month, Day, Hour, Minute, Second])).
|
||||
|
||||
-spec timestamp_to_xml(calendar:datetime(), tz(), jid(), binary()) -> xmlel().
|
||||
|
||||
timestamp_to_xml(DateTime, Timezone, FromJID, Desc) ->
|
||||
{T_string, Tz_string} = timestamp_to_iso(DateTime,
|
||||
Timezone),
|
||||
Text = [{xmlcdata, Desc}],
|
||||
From = jlib:jid_to_string(FromJID),
|
||||
%% TODO: Remove this function once XEP-0091 is Obsolete
|
||||
#xmlel{name = <<"delay">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DELAY}, {<<"from">>, From},
|
||||
{<<"stamp">>, <<T_string/binary, Tz_string/binary>>}],
|
||||
children = Text}.
|
||||
|
||||
-spec timestamp_to_xml(calendar:datetime()) -> xmlel().
|
||||
|
||||
timestamp_to_xml({{Year, Month, Day},
|
||||
{Hour, Minute, Second}}) ->
|
||||
#xmlel{name = <<"x">>,
|
||||
attrs =
|
||||
[{<<"xmlns">>, ?NS_DELAY91},
|
||||
{<<"stamp">>,
|
||||
iolist_to_binary(io_lib:format("~4..0w~2..0w~2..0wT~2..0w:~2..0w:~2..0w",
|
||||
[Year, Month, Day, Hour, Minute,
|
||||
Second]))}],
|
||||
children = []}.
|
||||
|
||||
-spec now_to_utc_string(erlang:timestamp()) -> binary().
|
||||
|
||||
now_to_utc_string({MegaSecs, Secs, MicroSecs}) ->
|
||||
now_to_utc_string({MegaSecs, Secs, MicroSecs}, 6).
|
||||
|
||||
-spec now_to_utc_string(erlang:timestamp(), 1..6) -> binary().
|
||||
|
||||
now_to_utc_string({MegaSecs, Secs, MicroSecs}, Precision) ->
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} =
|
||||
calendar:now_to_universal_time({MegaSecs, Secs,
|
||||
MicroSecs}),
|
||||
list_to_binary(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6."
|
||||
".0wZ",
|
||||
FracOfSec = round(MicroSecs / math:pow(10, 6 - Precision)),
|
||||
list_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B.~*."
|
||||
".0BZ",
|
||||
[Year, Month, Day, Hour, Minute, Second,
|
||||
MicroSecs])).
|
||||
Precision, FracOfSec])).
|
||||
|
||||
-spec now_to_local_string(erlang:timestamp()) -> binary().
|
||||
|
||||
@@ -688,8 +739,8 @@ now_to_local_string({MegaSecs, Secs, MicroSecs}) ->
|
||||
end,
|
||||
{{Year, Month, Day}, {Hour, Minute, Second}} =
|
||||
LocalTime,
|
||||
list_to_binary(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~6."
|
||||
".0w~s~2..0w:~2..0w",
|
||||
list_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B.~6."
|
||||
".0B~s~2..0B:~2..0B",
|
||||
[Year, Month, Day, Hour, Minute, Second,
|
||||
MicroSecs, Sign, H, M])).
|
||||
|
||||
@@ -798,7 +849,12 @@ base64_to_term(Base64) ->
|
||||
-spec decode_base64(binary()) -> binary().
|
||||
|
||||
decode_base64(S) ->
|
||||
decode_base64_bin(S, <<>>).
|
||||
case catch binary:last(S) of
|
||||
C when C == $\n; C == $\s ->
|
||||
decode_base64(binary:part(S, 0, byte_size(S) - 1));
|
||||
_ ->
|
||||
decode_base64_bin(S, <<>>)
|
||||
end.
|
||||
|
||||
take_without_spaces(Bin, Count) ->
|
||||
take_without_spaces(Bin, Count, <<>>).
|
||||
|
||||
@@ -792,6 +792,18 @@ announce_motd(Host, Packet) ->
|
||||
end, Sessions)
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
riak ->
|
||||
try
|
||||
lists:foreach(
|
||||
fun({U, S, _R}) ->
|
||||
ok = ejabberd_riak:put(#motd_users{us = {U, S}},
|
||||
motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, S}]}])
|
||||
end, Sessions),
|
||||
{atomic, ok}
|
||||
catch _:{badmatch, Err} ->
|
||||
{atomic, Err}
|
||||
end;
|
||||
odbc ->
|
||||
F = fun() ->
|
||||
lists:foreach(
|
||||
@@ -837,6 +849,10 @@ announce_motd_update(LServer, Packet) ->
|
||||
mnesia:write(#motd{server = LServer, packet = Packet})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
riak ->
|
||||
{atomic, ejabberd_riak:put(#motd{server = LServer,
|
||||
packet = Packet},
|
||||
motd_schema())};
|
||||
odbc ->
|
||||
XML = ejabberd_odbc:escape(xml:element_to_binary(Packet)),
|
||||
F = fun() ->
|
||||
@@ -887,6 +903,16 @@ announce_motd_delete(LServer) ->
|
||||
end, Users)
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
riak ->
|
||||
try
|
||||
ok = ejabberd_riak:delete(motd, LServer),
|
||||
ok = ejabberd_riak:delete_by_index(motd_users,
|
||||
<<"server">>,
|
||||
LServer),
|
||||
{atomic, ok}
|
||||
catch _:{badmatch, Err} ->
|
||||
{atomic, Err}
|
||||
end;
|
||||
odbc ->
|
||||
F = fun() ->
|
||||
ejabberd_odbc:sql_query_t([<<"delete from motd;">>])
|
||||
@@ -915,6 +941,23 @@ send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) ->
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
send_motd(#jid{luser = LUser, lserver = LServer} = JID, riak) ->
|
||||
case catch ejabberd_riak:get(motd, motd_schema(), LServer) of
|
||||
{ok, #motd{packet = Packet}} ->
|
||||
US = {LUser, LServer},
|
||||
case ejabberd_riak:get(motd_users, motd_users_schema(), US) of
|
||||
{ok, #motd_users{}} ->
|
||||
ok;
|
||||
_ ->
|
||||
Local = jlib:make_jid(<<>>, LServer, <<>>),
|
||||
ejabberd_router:route(Local, JID, Packet),
|
||||
{atomic, ejabberd_riak:put(
|
||||
#motd_users{us = US}, motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, LServer}]}])}
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>> ->
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, [<<"select xml from motd where username='';">>]) of
|
||||
@@ -965,6 +1008,13 @@ get_stored_motd_packet(LServer, mnesia) ->
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
get_stored_motd_packet(LServer, riak) ->
|
||||
case ejabberd_riak:get(motd, motd_schema(), LServer) of
|
||||
{ok, #motd{packet = Packet}} ->
|
||||
{ok, Packet};
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
get_stored_motd_packet(LServer, odbc) ->
|
||||
case catch ejabberd_odbc:sql_query(
|
||||
LServer, [<<"select xml from motd where username='';">>]) of
|
||||
@@ -1052,6 +1102,12 @@ update_motd_users_table() ->
|
||||
mnesia:transform_table(motd_users, ignore, Fields)
|
||||
end.
|
||||
|
||||
motd_schema() ->
|
||||
{record_info(fields, motd), #motd{}}.
|
||||
|
||||
motd_users_schema() ->
|
||||
{record_info(fields, motd_users), #motd_users{}}.
|
||||
|
||||
export(_Server) ->
|
||||
[{motd,
|
||||
fun(Host, #motd{server = LServer, packet = El})
|
||||
@@ -1089,5 +1145,10 @@ import(_LServer, mnesia, #motd{} = Motd) ->
|
||||
mnesia:dirty_write(Motd);
|
||||
import(_LServer, mnesia, #motd_users{} = Users) ->
|
||||
mnesia:dirty_write(Users);
|
||||
import(_LServer, riak, #motd{} = Motd) ->
|
||||
ejabberd_riak:put(Motd, motd_schema());
|
||||
import(_LServer, riak, #motd_users{us = {_, S}} = Users) ->
|
||||
ejabberd_riak:put(Users, motd_users_schema(),
|
||||
[{'2i', [{<<"server">>, S}]}]);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
@@ -181,6 +181,39 @@ process_blocklist_block(LUser, LServer, Filter,
|
||||
{ok, NewDefault, NewList}
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_blocklist_block(LUser, LServer, Filter,
|
||||
riak) ->
|
||||
{atomic,
|
||||
begin
|
||||
case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewDefault = Default,
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists);
|
||||
false ->
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = Lists,
|
||||
List = []
|
||||
end;
|
||||
{error, _} ->
|
||||
P = #privacy{us = {LUser, LServer}},
|
||||
NewDefault = <<"Blocked contacts">>,
|
||||
NewLists1 = [],
|
||||
List = []
|
||||
end,
|
||||
NewList = Filter(List),
|
||||
NewLists = [{NewDefault, NewList} | NewLists1],
|
||||
case ejabberd_riak:put(P#privacy{default = NewDefault,
|
||||
lists = NewLists},
|
||||
mod_privacy:privacy_schema()) of
|
||||
ok ->
|
||||
{ok, NewDefault, NewList};
|
||||
Err ->
|
||||
Err
|
||||
end
|
||||
end};
|
||||
process_blocklist_block(LUser, LServer, Filter, odbc) ->
|
||||
F = fun () ->
|
||||
Default = case
|
||||
@@ -256,6 +289,31 @@ process_blocklist_unblock_all(LUser, LServer, Filter,
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_blocklist_unblock_all(LUser, LServer, Filter,
|
||||
riak) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, {LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
case ejabberd_riak:put(P#privacy{lists = NewLists},
|
||||
mod_privacy:privacy_schema()) of
|
||||
ok ->
|
||||
{ok, Default, NewList};
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
false ->
|
||||
%% No default list, nothing to unblock
|
||||
ok
|
||||
end;
|
||||
{error, _} ->
|
||||
%% No lists, nothing to unblock
|
||||
ok
|
||||
end};
|
||||
process_blocklist_unblock_all(LUser, LServer, Filter,
|
||||
odbc) ->
|
||||
F = fun () ->
|
||||
@@ -331,6 +389,32 @@ process_blocklist_unblock(LUser, LServer, Filter,
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_blocklist_unblock(LUser, LServer, Filter,
|
||||
riak) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{error, _} ->
|
||||
%% No lists, nothing to unblock
|
||||
ok;
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} ->
|
||||
NewList = Filter(List),
|
||||
NewLists1 = lists:keydelete(Default, 1, Lists),
|
||||
NewLists = [{Default, NewList} | NewLists1],
|
||||
case ejabberd_riak:put(P#privacy{lists = NewLists},
|
||||
mod_privacy:privacy_schema()) of
|
||||
ok ->
|
||||
{ok, Default, NewList};
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
false ->
|
||||
%% No default list, nothing to unblock
|
||||
ok
|
||||
end
|
||||
end};
|
||||
process_blocklist_unblock(LUser, LServer, Filter,
|
||||
odbc) ->
|
||||
F = fun () ->
|
||||
@@ -409,6 +493,19 @@ process_blocklist_get(LUser, LServer, mnesia) ->
|
||||
_ -> []
|
||||
end
|
||||
end;
|
||||
process_blocklist_get(LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists}} ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
_ -> []
|
||||
end;
|
||||
{error, notfound} ->
|
||||
[];
|
||||
{error, _} ->
|
||||
error
|
||||
end;
|
||||
process_blocklist_get(LUser, LServer, odbc) ->
|
||||
case catch
|
||||
mod_privacy:sql_get_default_privacy_list(LUser, LServer)
|
||||
|
||||
+261
-93
@@ -17,9 +17,10 @@
|
||||
%%% 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.
|
||||
%%% 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., 59 Temple Place, Suite 330, Boston, MA
|
||||
%%% 02111-1307 USA
|
||||
%%%
|
||||
%%% 2009, improvements from ProcessOne to support correct PEP handling
|
||||
%%% through s2s, use less memory, and speedup global caps handling
|
||||
@@ -35,7 +36,8 @@
|
||||
|
||||
-export([read_caps/1, caps_stream_features/2,
|
||||
disco_features/5, disco_identity/5, disco_info/5,
|
||||
get_features/1]).
|
||||
get_features/2, export/1, import_info/0, import/5,
|
||||
import_start/2, import_stop/2]).
|
||||
|
||||
%% gen_mod callbacks
|
||||
-export([start/2, start_link/2, stop/1]).
|
||||
@@ -45,10 +47,9 @@
|
||||
handle_cast/2, terminate/2, code_change/3]).
|
||||
|
||||
%% hook handlers
|
||||
-export([user_send_packet/3,
|
||||
user_receive_packet/4,
|
||||
c2s_presence_in/2,
|
||||
c2s_broadcast_recipients/5]).
|
||||
-export([user_send_packet/3, user_receive_packet/4,
|
||||
c2s_presence_in/2, c2s_filter_packet/6,
|
||||
c2s_broadcast_recipients/6]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -79,9 +80,6 @@
|
||||
|
||||
-record(state, {host = <<"">> :: binary()}).
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
gen_server:start_link({local, Proc}, ?MODULE,
|
||||
@@ -99,20 +97,14 @@ stop(Host) ->
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
%% get_features returns a list of features implied by the given caps
|
||||
%% record (as extracted by read_caps) or 'unknown' if features are
|
||||
%% not completely collected at the moment.
|
||||
get_features(nothing) -> [];
|
||||
get_features(#caps{node = Node, version = Version, exts = Exts}) ->
|
||||
get_features(_Host, nothing) -> [];
|
||||
get_features(Host, #caps{node = Node, version = Version,
|
||||
exts = Exts}) ->
|
||||
SubNodes = [Version | Exts],
|
||||
%% read_caps takes a list of XML elements (the child elements of a
|
||||
%% <presence/> stanza) and returns an opaque value representing the
|
||||
%% Entity Capabilities contained therein, or the atom nothing if no
|
||||
%% capabilities are advertised.
|
||||
lists:foldl(fun (SubNode, Acc) ->
|
||||
NodePair = {Node, SubNode},
|
||||
case cache_tab:lookup(caps_features, NodePair,
|
||||
caps_read_fun(NodePair))
|
||||
caps_read_fun(Host, NodePair))
|
||||
of
|
||||
{ok, Features} when is_list(Features) ->
|
||||
Features ++ Acc;
|
||||
@@ -121,6 +113,8 @@ get_features(#caps{node = Node, version = Version, exts = Exts}) ->
|
||||
end,
|
||||
[], SubNodes).
|
||||
|
||||
-spec read_caps([xmlel()]) -> nothing | caps().
|
||||
|
||||
read_caps(Els) -> read_caps(Els, nothing).
|
||||
|
||||
read_caps([#xmlel{name = <<"c">>, attrs = Attrs}
|
||||
@@ -149,13 +143,11 @@ read_caps([_ | Tail], Result) ->
|
||||
read_caps(Tail, Result);
|
||||
read_caps([], Result) -> Result.
|
||||
|
||||
%%====================================================================
|
||||
%% Hooks
|
||||
%%====================================================================
|
||||
user_send_packet(
|
||||
#jid{luser = User, lserver = Server} = From,
|
||||
#jid{luser = User, lserver = Server, lresource = <<"">>},
|
||||
#xmlel{name = <<"presence">>, attrs = Attrs, children = Els}) ->
|
||||
user_send_packet(#jid{luser = User, lserver = Server} = From,
|
||||
#jid{luser = User, lserver = Server,
|
||||
lresource = <<"">>},
|
||||
#xmlel{name = <<"presence">>, attrs = Attrs,
|
||||
children = Els} = Pkt) ->
|
||||
Type = xml:get_attr_s(<<"type">>, Attrs),
|
||||
if Type == <<"">>; Type == <<"available">> ->
|
||||
case read_caps(Els) of
|
||||
@@ -164,12 +156,15 @@ user_send_packet(
|
||||
feature_request(Server, From, Caps, [Version | Exts])
|
||||
end;
|
||||
true -> ok
|
||||
end;
|
||||
user_send_packet(_From, _To, _Packet) -> ok.
|
||||
end,
|
||||
Pkt;
|
||||
user_send_packet( _From, _To, Pkt) ->
|
||||
Pkt.
|
||||
|
||||
user_receive_packet(#jid{lserver = Server}, From, _To,
|
||||
user_receive_packet(#jid{lserver = Server},
|
||||
From, _To,
|
||||
#xmlel{name = <<"presence">>, attrs = Attrs,
|
||||
children = Els}) ->
|
||||
children = Els} = Pkt) ->
|
||||
Type = xml:get_attr_s(<<"type">>, Attrs),
|
||||
IsRemote = not lists:member(From#jid.lserver, ?MYHOSTS),
|
||||
if IsRemote and
|
||||
@@ -180,9 +175,12 @@ user_receive_packet(#jid{lserver = Server}, From, _To,
|
||||
feature_request(Server, From, Caps, [Version | Exts])
|
||||
end;
|
||||
true -> ok
|
||||
end;
|
||||
user_receive_packet(_JID, _From, _To, _Packet) ->
|
||||
ok.
|
||||
end,
|
||||
Pkt;
|
||||
user_receive_packet( _JID, _From, _To, Pkt) ->
|
||||
Pkt.
|
||||
|
||||
-spec caps_stream_features([xmlel()], binary()) -> [xmlel()].
|
||||
|
||||
caps_stream_features(Acc, MyHost) ->
|
||||
case make_my_disco_hash(MyHost) of
|
||||
@@ -260,7 +258,8 @@ c2s_presence_in(C2SState,
|
||||
end,
|
||||
if CapsUpdated ->
|
||||
ejabberd_hooks:run(caps_update, To#jid.lserver,
|
||||
[From, To, get_features(Caps)]);
|
||||
[From, To,
|
||||
get_features(To#jid.lserver, Caps)]);
|
||||
true -> ok
|
||||
end,
|
||||
ejabberd_c2s:set_aux_field(caps_resources, NewRs,
|
||||
@@ -268,63 +267,90 @@ c2s_presence_in(C2SState,
|
||||
true -> C2SState
|
||||
end.
|
||||
|
||||
c2s_broadcast_recipients(InAcc, C2SState, {pep_message, Feature},
|
||||
_From, _Packet) ->
|
||||
c2s_filter_packet(InAcc, Host, C2SState, {pep_message, Feature}, To, _Packet) ->
|
||||
case ejabberd_c2s:get_aux_field(caps_resources, C2SState) of
|
||||
{ok, Rs} ->
|
||||
gb_trees_fold(
|
||||
fun(USR, Caps, Acc) ->
|
||||
case lists:member(Feature, get_features(Caps)) of
|
||||
true ->
|
||||
[USR|Acc];
|
||||
false ->
|
||||
Acc
|
||||
end
|
||||
end, InAcc, Rs);
|
||||
_ ->
|
||||
InAcc
|
||||
{ok, Rs} ->
|
||||
LTo = jlib:jid_tolower(To),
|
||||
case gb_trees:lookup(LTo, Rs) of
|
||||
{value, Caps} ->
|
||||
Drop = not lists:member(Feature, get_features(Host, Caps)),
|
||||
{stop, Drop};
|
||||
none ->
|
||||
{stop, true}
|
||||
end;
|
||||
_ -> InAcc
|
||||
end;
|
||||
c2s_broadcast_recipients(Acc, _, _, _, _) ->
|
||||
Acc.
|
||||
c2s_filter_packet(Acc, _, _, _, _, _) -> Acc.
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
init([Host, Opts]) ->
|
||||
c2s_broadcast_recipients(InAcc, Host, C2SState,
|
||||
{pep_message, Feature}, _From, _Packet) ->
|
||||
case ejabberd_c2s:get_aux_field(caps_resources,
|
||||
C2SState)
|
||||
of
|
||||
{ok, Rs} ->
|
||||
gb_trees_fold(fun (USR, Caps, Acc) ->
|
||||
case lists:member(Feature,
|
||||
get_features(Host, Caps))
|
||||
of
|
||||
true -> [USR | Acc];
|
||||
false -> Acc
|
||||
end
|
||||
end,
|
||||
InAcc, Rs);
|
||||
_ -> InAcc
|
||||
end;
|
||||
c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc.
|
||||
|
||||
init_db(mnesia, _Host) ->
|
||||
case catch mnesia:table_info(caps_features, storage_type) of
|
||||
{'EXIT', _} ->
|
||||
ok;
|
||||
disc_only_copies ->
|
||||
ok;
|
||||
_ ->
|
||||
mnesia:delete_table(caps_features)
|
||||
{'EXIT', _} ->
|
||||
ok;
|
||||
disc_only_copies ->
|
||||
ok;
|
||||
_ ->
|
||||
mnesia:delete_table(caps_features)
|
||||
end,
|
||||
mnesia:create_table(caps_features,
|
||||
[{disc_only_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes, record_info(fields, caps_features)}]),
|
||||
mnesia:add_table_copy(caps_features, node(), disc_only_copies),
|
||||
MaxSize = gen_mod:get_opt(cache_size, Opts, fun(CS) when is_integer(CS) -> CS end, 1000),
|
||||
LifeTime = gen_mod:get_opt(cache_life_time, Opts, fun(CL) when is_integer(CL) -> CL end, timer:hours(24) div 1000),
|
||||
cache_tab:new(caps_features, [{max_size, MaxSize}, {life_time, LifeTime}]),
|
||||
ejabberd_hooks:add(c2s_presence_in, Host,
|
||||
?MODULE, c2s_presence_in, 75),
|
||||
[{disc_only_copies, [node()]},
|
||||
{local_content, true},
|
||||
{attributes,
|
||||
record_info(fields, caps_features)}]),
|
||||
update_table(),
|
||||
mnesia:add_table_copy(caps_features, node(),
|
||||
disc_only_copies);
|
||||
init_db(_, _) ->
|
||||
ok.
|
||||
|
||||
init([Host, Opts]) ->
|
||||
init_db(gen_mod:db_type(Opts), Host),
|
||||
MaxSize = gen_mod:get_opt(cache_size, Opts,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
1000),
|
||||
LifeTime = gen_mod:get_opt(cache_life_time, Opts,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
timer:hours(24) div 1000),
|
||||
cache_tab:new(caps_features,
|
||||
[{max_size, MaxSize}, {life_time, LifeTime}]),
|
||||
ejabberd_hooks:add(c2s_presence_in, Host, ?MODULE,
|
||||
c2s_presence_in, 75),
|
||||
ejabberd_hooks:add(c2s_filter_packet, Host, ?MODULE,
|
||||
c2s_filter_packet, 75),
|
||||
ejabberd_hooks:add(c2s_broadcast_recipients, Host,
|
||||
?MODULE, c2s_broadcast_recipients, 75),
|
||||
ejabberd_hooks:add(user_send_packet, Host,
|
||||
?MODULE, user_send_packet, 75),
|
||||
ejabberd_hooks:add(user_receive_packet, Host,
|
||||
?MODULE, user_receive_packet, 75),
|
||||
ejabberd_hooks:add(c2s_stream_features, Host,
|
||||
?MODULE, caps_stream_features, 75),
|
||||
ejabberd_hooks:add(s2s_stream_features, Host,
|
||||
?MODULE, caps_stream_features, 75),
|
||||
ejabberd_hooks:add(disco_local_features, Host,
|
||||
?MODULE, disco_features, 75),
|
||||
ejabberd_hooks:add(disco_local_identity, Host,
|
||||
?MODULE, disco_identity, 75),
|
||||
ejabberd_hooks:add(disco_info, Host,
|
||||
?MODULE, disco_info, 75),
|
||||
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
||||
user_send_packet, 75),
|
||||
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
|
||||
user_receive_packet, 75),
|
||||
ejabberd_hooks:add(c2s_stream_features, Host, ?MODULE,
|
||||
caps_stream_features, 75),
|
||||
ejabberd_hooks:add(s2s_stream_features, Host, ?MODULE,
|
||||
caps_stream_features, 75),
|
||||
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
||||
disco_features, 75),
|
||||
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
|
||||
disco_identity, 75),
|
||||
ejabberd_hooks:add(disco_info, Host, ?MODULE,
|
||||
disco_info, 75),
|
||||
{ok, #state{host = Host}}.
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
@@ -340,6 +366,8 @@ terminate(_Reason, State) ->
|
||||
Host = State#state.host,
|
||||
ejabberd_hooks:delete(c2s_presence_in, Host, ?MODULE,
|
||||
c2s_presence_in, 75),
|
||||
ejabberd_hooks:delete(c2s_filter_packet, Host, ?MODULE,
|
||||
c2s_filter_packet, 75),
|
||||
ejabberd_hooks:delete(c2s_broadcast_recipients, Host,
|
||||
?MODULE, c2s_broadcast_recipients, 75),
|
||||
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
||||
@@ -360,15 +388,12 @@ terminate(_Reason, State) ->
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
%%====================================================================
|
||||
%% Aux functions
|
||||
%%====================================================================
|
||||
feature_request(Host, From, Caps,
|
||||
[SubNode | Tail] = SubNodes) ->
|
||||
Node = Caps#caps.node,
|
||||
NodePair = {Node, SubNode},
|
||||
case cache_tab:lookup(caps_features, NodePair,
|
||||
caps_read_fun(NodePair))
|
||||
caps_read_fun(Host, NodePair))
|
||||
of
|
||||
{ok, Fs} when is_list(Fs) ->
|
||||
feature_request(Host, From, Caps, Tail);
|
||||
@@ -388,7 +413,7 @@ feature_request(Host, From, Caps,
|
||||
SubNode/binary>>}],
|
||||
children = []}]},
|
||||
cache_tab:insert(caps_features, NodePair, now_ts(),
|
||||
caps_write_fun(NodePair, now_ts())),
|
||||
caps_write_fun(Host, NodePair, now_ts())),
|
||||
F = fun (IQReply) ->
|
||||
feature_response(IQReply, Host, From, Caps,
|
||||
SubNodes)
|
||||
@@ -416,7 +441,7 @@ feature_response(#iq{type = result,
|
||||
Els),
|
||||
cache_tab:insert(caps_features, NodePair,
|
||||
Features,
|
||||
caps_write_fun(NodePair, Features));
|
||||
caps_write_fun(Host, NodePair, Features));
|
||||
false -> ok
|
||||
end,
|
||||
feature_request(Host, From, Caps, SubNodes);
|
||||
@@ -424,18 +449,66 @@ feature_response(_IQResult, Host, From, Caps,
|
||||
[_SubNode | SubNodes]) ->
|
||||
feature_request(Host, From, Caps, SubNodes).
|
||||
|
||||
caps_read_fun(Node) ->
|
||||
caps_read_fun(Host, Node) ->
|
||||
LServer = jlib:nameprep(Host),
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
caps_read_fun(LServer, Node, DBType).
|
||||
|
||||
caps_read_fun(_LServer, Node, mnesia) ->
|
||||
fun () ->
|
||||
case mnesia:dirty_read({caps_features, Node}) of
|
||||
[#caps_features{features = Features}] -> {ok, Features};
|
||||
_ -> error
|
||||
end
|
||||
end;
|
||||
caps_read_fun(_LServer, Node, riak) ->
|
||||
fun() ->
|
||||
case ejabberd_riak:get(caps_features, caps_features_schema(), Node) of
|
||||
{ok, #caps_features{features = Features}} -> {ok, Features};
|
||||
_ -> error
|
||||
end
|
||||
end;
|
||||
caps_read_fun(LServer, {Node, SubNode}, odbc) ->
|
||||
fun() ->
|
||||
SNode = ejabberd_odbc:escape(Node),
|
||||
SSubNode = ejabberd_odbc:escape(SubNode),
|
||||
case ejabberd_odbc:sql_query(
|
||||
LServer, [<<"select feature from caps_features where ">>,
|
||||
<<"node='">>, SNode, <<"' and subnode='">>,
|
||||
SSubNode, <<"';">>]) of
|
||||
{selected, [<<"feature">>], [[H]|_] = Fs} ->
|
||||
case catch jlib:binary_to_integer(H) of
|
||||
Int when is_integer(Int), Int>=0 ->
|
||||
{ok, Int};
|
||||
_ ->
|
||||
{ok, lists:flatten(Fs)}
|
||||
end;
|
||||
_ ->
|
||||
error
|
||||
end
|
||||
end.
|
||||
|
||||
caps_write_fun(Node, Features) ->
|
||||
caps_write_fun(Host, Node, Features) ->
|
||||
LServer = jlib:nameprep(Host),
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
caps_write_fun(LServer, Node, Features, DBType).
|
||||
|
||||
caps_write_fun(_LServer, Node, Features, mnesia) ->
|
||||
fun () ->
|
||||
mnesia:dirty_write(#caps_features{node_pair = Node,
|
||||
features = Features})
|
||||
end;
|
||||
caps_write_fun(_LServer, Node, Features, riak) ->
|
||||
fun () ->
|
||||
ejabberd_riak:put(#caps_features{node_pair = Node,
|
||||
features = Features},
|
||||
caps_features_schema())
|
||||
end;
|
||||
caps_write_fun(LServer, NodePair, Features, odbc) ->
|
||||
fun () ->
|
||||
ejabberd_odbc:sql_transaction(
|
||||
LServer,
|
||||
sql_write_features_t(NodePair, Features))
|
||||
end.
|
||||
|
||||
make_my_disco_hash(Host) ->
|
||||
@@ -585,3 +658,98 @@ is_valid_node(Node) ->
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, caps_features),
|
||||
case mnesia:table_info(caps_features, attributes) of
|
||||
Fields ->
|
||||
ejabberd_config:convert_table_to_binary(
|
||||
caps_features, Fields, set,
|
||||
fun(#caps_features{node_pair = {N, _}}) -> N end,
|
||||
fun(#caps_features{node_pair = {N, P},
|
||||
features = Fs} = R) ->
|
||||
NewFs = if is_integer(Fs) ->
|
||||
Fs;
|
||||
true ->
|
||||
[iolist_to_binary(F) || F <- Fs]
|
||||
end,
|
||||
R#caps_features{node_pair = {iolist_to_binary(N),
|
||||
iolist_to_binary(P)},
|
||||
features = NewFs}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating caps_features table", []),
|
||||
mnesia:transform_table(caps_features, ignore, Fields)
|
||||
end.
|
||||
|
||||
sql_write_features_t({Node, SubNode}, Features) ->
|
||||
SNode = ejabberd_odbc:escape(Node),
|
||||
SSubNode = ejabberd_odbc:escape(SubNode),
|
||||
NewFeatures = if is_integer(Features) ->
|
||||
[jlib:integer_to_binary(Features)];
|
||||
true ->
|
||||
Features
|
||||
end,
|
||||
[[<<"delete from caps_features where node='">>,
|
||||
SNode, <<"' and subnode='">>, SSubNode, <<"';">>]|
|
||||
[[<<"insert into caps_features(node, subnode, feature) ">>,
|
||||
<<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>,
|
||||
ejabberd_odbc:escape(F), <<"');">>] || F <- NewFeatures]].
|
||||
|
||||
caps_features_schema() ->
|
||||
{record_info(fields, caps_features), #caps_features{}}.
|
||||
|
||||
export(_Server) ->
|
||||
[{caps_features,
|
||||
fun(_Host, #caps_features{node_pair = NodePair,
|
||||
features = Features}) ->
|
||||
sql_write_features_t(NodePair, Features);
|
||||
(_Host, _R) ->
|
||||
[]
|
||||
end}].
|
||||
|
||||
import_info() ->
|
||||
[{<<"caps_features">>, 4}].
|
||||
|
||||
import_start(LServer, DBType) ->
|
||||
ets:new(caps_features_tmp, [private, named_table, bag]),
|
||||
init_db(DBType, LServer),
|
||||
ok.
|
||||
|
||||
import(_LServer, {odbc, _}, _DBType, <<"caps_features">>,
|
||||
[Node, SubNode, Feature, _TimeStamp]) ->
|
||||
Feature1 = case catch jlib:binary_to_integer(Feature) of
|
||||
I when is_integer(I), I>0 -> I;
|
||||
_ -> Feature
|
||||
end,
|
||||
ets:insert(caps_features_tmp, {{Node, SubNode}, Feature1}),
|
||||
ok.
|
||||
|
||||
import_stop(LServer, DBType) ->
|
||||
import_next(LServer, DBType, ets:first(caps_features_tmp)),
|
||||
ets:delete(caps_features_tmp),
|
||||
ok.
|
||||
|
||||
import_next(_LServer, _DBType, '$end_of_table') ->
|
||||
ok;
|
||||
import_next(LServer, DBType, NodePair) ->
|
||||
Features = [F || {_, F} <- ets:lookup(caps_features_tmp, NodePair)],
|
||||
case Features of
|
||||
[I] when is_integer(I), DBType == mnesia ->
|
||||
mnesia:dirty_write(
|
||||
#caps_features{node_pair = NodePair, features = I});
|
||||
[I] when is_integer(I), DBType == riak ->
|
||||
ejabberd_riak:put(
|
||||
#caps_features{node_pair = NodePair, features = I},
|
||||
caps_features_schema());
|
||||
_ when DBType == mnesia ->
|
||||
mnesia:dirty_write(
|
||||
#caps_features{node_pair = NodePair, features = Features});
|
||||
_ when DBType == riak ->
|
||||
ejabberd_riak:put(
|
||||
#caps_features{node_pair = NodePair, features = Features},
|
||||
caps_features_schema());
|
||||
_ when DBType == odbc ->
|
||||
ok
|
||||
end,
|
||||
import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)).
|
||||
|
||||
+74
-66
@@ -41,10 +41,6 @@
|
||||
remove_connection/4,
|
||||
is_carbon_copy/1]).
|
||||
|
||||
-define(NS_CC_2, <<"urn:xmpp:carbons:2">>).
|
||||
-define(NS_CC_1, <<"urn:xmpp:carbons:1">>).
|
||||
-define(NS_FORWARD, <<"urn:xmpp:forward:0">>).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
@@ -57,20 +53,24 @@
|
||||
version :: binary() | matchspec_atom()}).
|
||||
|
||||
is_carbon_copy(Packet) ->
|
||||
case xml:get_subtag(Packet, <<"sent">>) of
|
||||
#xmlel{name= <<"sent">>, attrs = AAttrs} ->
|
||||
case xml:get_attr_s(<<"xmlns">>, AAttrs) of
|
||||
?NS_CC_2 -> true;
|
||||
?NS_CC_1 -> true;
|
||||
_ -> false
|
||||
end;
|
||||
is_carbon_copy(Packet, <<"sent">>) orelse
|
||||
is_carbon_copy(Packet, <<"received">>).
|
||||
|
||||
is_carbon_copy(Packet, Direction) ->
|
||||
case xml:get_subtag(Packet, Direction) of
|
||||
#xmlel{name = Direction, attrs = Attrs} ->
|
||||
case xml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_CARBONS_2 -> true;
|
||||
?NS_CARBONS_1 -> true;
|
||||
_ -> false
|
||||
end.
|
||||
end;
|
||||
_ -> false
|
||||
end.
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts,fun gen_iq_handler:check_type/1, one_queue),
|
||||
mod_disco:register_feature(Host, ?NS_CC_1),
|
||||
mod_disco:register_feature(Host, ?NS_CC_2),
|
||||
mod_disco:register_feature(Host, ?NS_CARBONS_1),
|
||||
mod_disco:register_feature(Host, ?NS_CARBONS_2),
|
||||
Fields = record_info(fields, ?TABLE),
|
||||
try mnesia:table_info(?TABLE, attributes) of
|
||||
Fields -> ok;
|
||||
@@ -86,26 +86,26 @@ start(Host, Opts) ->
|
||||
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
|
||||
ejabberd_hooks:add(user_send_packet,Host, ?MODULE, user_send_packet, 89),
|
||||
ejabberd_hooks:add(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CC_2, ?MODULE, iq_handler2, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CC_1, ?MODULE, iq_handler1, IQDisc).
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2, ?MODULE, iq_handler2, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_1, ?MODULE, iq_handler1, IQDisc).
|
||||
|
||||
stop(Host) ->
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CC_1),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CC_2),
|
||||
mod_disco:unregister_feature(Host, ?NS_CC_2),
|
||||
mod_disco:unregister_feature(Host, ?NS_CC_1),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_1),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CARBONS_2),
|
||||
mod_disco:unregister_feature(Host, ?NS_CARBONS_2),
|
||||
mod_disco:unregister_feature(Host, ?NS_CARBONS_1),
|
||||
%% why priority 89: to define clearly that we must run BEFORE mod_logdb hook (90)
|
||||
ejabberd_hooks:delete(user_send_packet,Host, ?MODULE, user_send_packet, 89),
|
||||
ejabberd_hooks:delete(user_receive_packet,Host, ?MODULE, user_receive_packet, 89),
|
||||
ejabberd_hooks:delete(unset_presence_hook,Host, ?MODULE, remove_connection, 10).
|
||||
|
||||
iq_handler2(From, To, IQ) ->
|
||||
iq_handler(From, To, IQ, ?NS_CC_2).
|
||||
iq_handler(From, To, IQ, ?NS_CARBONS_2).
|
||||
iq_handler1(From, To, IQ) ->
|
||||
iq_handler(From, To, IQ, ?NS_CC_1).
|
||||
iq_handler(From, To, IQ, ?NS_CARBONS_1).
|
||||
|
||||
iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children = []}} = IQ, CC)->
|
||||
?INFO_MSG("carbons IQ received: ~p", [IQ]),
|
||||
?DEBUG("carbons IQ received: ~p", [IQ]),
|
||||
{U, S, R} = jlib:jid_tolower(From),
|
||||
Result = case Operation of
|
||||
<<"enable">>->
|
||||
@@ -117,10 +117,10 @@ iq_handler(From, _To, #iq{type=set, sub_el = #xmlel{name = Operation, children
|
||||
end,
|
||||
case Result of
|
||||
ok ->
|
||||
?INFO_MSG("carbons IQ result: ok", []),
|
||||
?DEBUG("carbons IQ result: ok", []),
|
||||
IQ#iq{type=result, sub_el=[]};
|
||||
{error,_Error} ->
|
||||
?INFO_MSG("Error enabling / disabling carbons: ~p", [Result]),
|
||||
?WARNING_MSG("Error enabling / disabling carbons: ~p", [Result]),
|
||||
IQ#iq{type=error,sub_el = [?ERR_BAD_REQUEST]}
|
||||
end;
|
||||
|
||||
@@ -138,35 +138,21 @@ user_receive_packet(JID, _From, To, Packet) ->
|
||||
% - registered to the user_send_packet hook, to be called only once even for multicast
|
||||
% - do not support "private" message mode, and do not modify the original packet in any way
|
||||
% - we also replicate "read" notifications
|
||||
check_and_forward(JID, To, #xmlel{name = <<"message">>, attrs = Attrs} = Packet, Direction)->
|
||||
case xml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"chat">> ->
|
||||
case xml:get_subtag(Packet, <<"private">>) of
|
||||
false ->
|
||||
case xml:get_subtag(Packet,<<"received">>) of
|
||||
false ->
|
||||
%% We must check if a packet contains "<sent><forwarded></sent></forwarded>" tags in order to avoid
|
||||
%% receiving message back to original sender.
|
||||
SubTag = xml:get_subtag(Packet,<<"sent">>),
|
||||
if SubTag == false ->
|
||||
send_copies(JID, To, Packet, Direction);
|
||||
true ->
|
||||
case xml:get_subtag(SubTag,<<"forwarded">>) of
|
||||
false->
|
||||
send_copies(JID, To, Packet, Direction);
|
||||
_ ->
|
||||
stop
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
%% stop the hook chain, we don't want mod_logdb to register this message (duplicate)
|
||||
stop
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
check_and_forward(JID, To, Packet, Direction)->
|
||||
case is_chat_or_normal_message(Packet) andalso
|
||||
xml:get_subtag(Packet, <<"private">>) == false andalso
|
||||
xml:get_subtag(Packet, <<"no-copy">>) == false of
|
||||
true ->
|
||||
case is_carbon_copy(Packet) of
|
||||
false ->
|
||||
send_copies(JID, To, Packet, Direction);
|
||||
true ->
|
||||
%% stop the hook chain, we don't want mod_logdb to register
|
||||
%% this message (duplicate)
|
||||
stop
|
||||
end;
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
|
||||
check_and_forward(_JID, _To, _Packet, _)-> ok.
|
||||
@@ -181,6 +167,10 @@ remove_connection(User, Server, Resource, _Status)->
|
||||
send_copies(JID, To, Packet, Direction)->
|
||||
{U, S, R} = jlib:jid_tolower(JID),
|
||||
PrioRes = ejabberd_sm:get_user_present_resources(U, S),
|
||||
{MaxPrio, MaxRes} = case catch lists:max(PrioRes) of
|
||||
{Prio, Res} -> {Prio, Res};
|
||||
_ -> {0, undefined}
|
||||
end,
|
||||
|
||||
IsBareTo = case {Direction, To} of
|
||||
{received, #jid{lresource = <<>>}} -> true;
|
||||
@@ -194,15 +184,19 @@ send_copies(JID, To, Packet, Direction)->
|
||||
end,
|
||||
%% list of JIDs that should receive a carbon copy of this message (excluding the
|
||||
%% receiver(s) of the original message
|
||||
TargetJIDs = if IsBareTo ->
|
||||
MaxPrio = case catch lists:max(PrioRes) of
|
||||
{Prio, _Res} -> Prio;
|
||||
_ -> 0
|
||||
end,
|
||||
TargetJIDs = case {IsBareTo, R} of
|
||||
{true, MaxRes} ->
|
||||
OrigTo = fun(Res) -> lists:member({MaxPrio, Res}, PrioRes) end,
|
||||
[ {jlib:make_jid({U, S, CCRes}), CC_Version}
|
||||
|| {CCRes, CC_Version} <- list(U, S), not OrigTo(CCRes) ];
|
||||
true ->
|
||||
{true, _} ->
|
||||
%% The message was sent to our bare JID, and we currently have
|
||||
%% multiple resources with the same highest priority, so the session
|
||||
%% manager routes the message to each of them. We create carbon
|
||||
%% copies only from one of those resources (the one where R equals
|
||||
%% MaxRes) in order to avoid duplicates.
|
||||
[];
|
||||
{false, _} ->
|
||||
[ {jlib:make_jid({U, S, CCRes}), CC_Version}
|
||||
|| {CCRes, CC_Version} <- list(U, S), CCRes /= R ]
|
||||
%TargetJIDs = lists:delete(JID, [ jlib:make_jid({U, S, CCRes}) || CCRes <- list(U, S) ]),
|
||||
@@ -218,15 +212,15 @@ send_copies(JID, To, Packet, Direction)->
|
||||
end, TargetJIDs),
|
||||
ok.
|
||||
|
||||
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CC_2) ->
|
||||
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_2) ->
|
||||
#xmlel{name = <<"message">>,
|
||||
attrs = [{<<"xmlns">>, <<"jabber:client">>},
|
||||
{<<"type">>, <<"chat">>},
|
||||
{<<"type">>, message_type(Packet)},
|
||||
{<<"from">>, jlib:jid_to_string(Sender)},
|
||||
{<<"to">>, jlib:jid_to_string(Dest)}],
|
||||
children = [
|
||||
#xmlel{name = list_to_binary(atom_to_list(Direction)),
|
||||
attrs = [{<<"xmlns">>, ?NS_CC_2}],
|
||||
attrs = [{<<"xmlns">>, ?NS_CARBONS_2}],
|
||||
children = [
|
||||
#xmlel{name = <<"forwarded">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
|
||||
@@ -234,15 +228,15 @@ build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CC_2) ->
|
||||
complete_packet(JID, Packet, Direction)]}
|
||||
]}
|
||||
]};
|
||||
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CC_1) ->
|
||||
build_forward_packet(JID, Packet, Sender, Dest, Direction, ?NS_CARBONS_1) ->
|
||||
#xmlel{name = <<"message">>,
|
||||
attrs = [{<<"xmlns">>, <<"jabber:client">>},
|
||||
{<<"type">>, <<"chat">>},
|
||||
{<<"type">>, message_type(Packet)},
|
||||
{<<"from">>, jlib:jid_to_string(Sender)},
|
||||
{<<"to">>, jlib:jid_to_string(Dest)}],
|
||||
children = [
|
||||
#xmlel{name = list_to_binary(atom_to_list(Direction)),
|
||||
attrs = [{<<"xmlns">>, ?NS_CC_1}]},
|
||||
attrs = [{<<"xmlns">>, ?NS_CARBONS_1}]},
|
||||
#xmlel{name = <<"forwarded">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_FORWARD}],
|
||||
children = [complete_packet(JID, Packet, Direction)]}
|
||||
@@ -278,6 +272,20 @@ complete_packet(_From, #xmlel{name = <<"message">>, attrs=OrigAttrs} = Packet, r
|
||||
Attrs = lists:keystore(<<"xmlns">>, 1, OrigAttrs, {<<"xmlns">>, <<"jabber:client">>}),
|
||||
Packet#xmlel{attrs = Attrs}.
|
||||
|
||||
message_type(#xmlel{attrs = Attrs}) ->
|
||||
case xml:get_attr(<<"type">>, Attrs) of
|
||||
{value, Type} -> Type;
|
||||
false -> <<"normal">>
|
||||
end.
|
||||
|
||||
is_chat_or_normal_message(#xmlel{name = <<"message">>} = Packet) ->
|
||||
case message_type(Packet) of
|
||||
<<"chat">> -> true;
|
||||
<<"normal">> -> true;
|
||||
_ -> false
|
||||
end;
|
||||
is_chat_or_normal_message(_Packet) -> false.
|
||||
|
||||
%% list {resource, cc_version} with carbons enabled for given user and host
|
||||
list(User, Server)->
|
||||
mnesia:dirty_select(?TABLE, [{#carboncopy{us = {User, Server}, resource = '$2', version = '$3'}, [], [{{'$2','$3'}}]}]).
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_client_state.erl
|
||||
%%% Author : Holger Weiss
|
||||
%%% Purpose : Filter stanzas sent to inactive clients (XEP-0352)
|
||||
%%% Created : 11 Sep 2014 by Holger Weiss
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2014 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_client_state).
|
||||
-author('holger@zedat.fu-berlin.de').
|
||||
|
||||
-behavior(gen_mod).
|
||||
|
||||
-export([start/2, stop/1, add_stream_feature/2, filter_presence/2,
|
||||
filter_chat_states/2]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("jlib.hrl").
|
||||
|
||||
start(Host, Opts) ->
|
||||
QueuePresence = gen_mod:get_opt(queue_presence, Opts,
|
||||
fun(true) -> true;
|
||||
(false) -> false
|
||||
end, false),
|
||||
DropChatStates = gen_mod:get_opt(drop_chat_states, Opts,
|
||||
fun(true) -> true;
|
||||
(false) -> false
|
||||
end, false),
|
||||
if QueuePresence; DropChatStates ->
|
||||
ejabberd_hooks:add(c2s_post_auth_features, Host, ?MODULE,
|
||||
add_stream_feature, 50),
|
||||
if QueuePresence ->
|
||||
ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_presence, 50);
|
||||
true -> ok
|
||||
end,
|
||||
if DropChatStates ->
|
||||
ejabberd_hooks:add(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_chat_states, 50);
|
||||
true -> ok
|
||||
end;
|
||||
true -> ok
|
||||
end,
|
||||
ok.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_presence, 50),
|
||||
ejabberd_hooks:delete(csi_filter_stanza, Host, ?MODULE,
|
||||
filter_chat_states, 50),
|
||||
ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE,
|
||||
add_stream_feature, 50),
|
||||
ok.
|
||||
|
||||
add_stream_feature(Features, _Host) ->
|
||||
Feature = #xmlel{name = <<"csi">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_CLIENT_STATE}],
|
||||
children = []},
|
||||
[Feature | Features].
|
||||
|
||||
filter_presence(_Action, #xmlel{name = <<"presence">>, attrs = Attrs}) ->
|
||||
case xml:get_attr(<<"type">>, Attrs) of
|
||||
{value, Type} when Type /= <<"unavailable">> ->
|
||||
?DEBUG("Got important presence stanza", []),
|
||||
{stop, send};
|
||||
_ ->
|
||||
?DEBUG("Got availability presence stanza", []),
|
||||
{stop, queue}
|
||||
end;
|
||||
filter_presence(Action, _Stanza) -> Action.
|
||||
|
||||
filter_chat_states(_Action, #xmlel{name = <<"message">>} = Stanza) ->
|
||||
%% All XEP-0085 chat states except for <gone/>:
|
||||
ChatStates = [<<"active">>, <<"inactive">>, <<"composing">>, <<"paused">>],
|
||||
Stripped =
|
||||
lists:foldl(fun(ChatState, AccStanza) ->
|
||||
xml:remove_subtags(AccStanza, ChatState,
|
||||
{<<"xmlns">>, ?NS_CHATSTATES})
|
||||
end, Stanza, ChatStates),
|
||||
case Stripped of
|
||||
#xmlel{children = [#xmlel{name = <<"thread">>}]} ->
|
||||
?DEBUG("Got standalone chat state notification", []),
|
||||
{stop, drop};
|
||||
#xmlel{children = []} ->
|
||||
?DEBUG("Got standalone chat state notification", []),
|
||||
{stop, drop};
|
||||
_ ->
|
||||
?DEBUG("Got message with chat state notification", []),
|
||||
{stop, send}
|
||||
end;
|
||||
filter_chat_states(Action, _Stanza) -> Action.
|
||||
@@ -0,0 +1,161 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% @copyright (C) 2014, Evgeny Khramtsov
|
||||
%%% @doc
|
||||
%%%
|
||||
%%% @end
|
||||
%%% Created : 15 Aug 2014 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(mod_fail2ban).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%% API
|
||||
-export([start_link/2, start/2, stop/1, c2s_auth_result/4, check_bl_c2s/3]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
-define(C2S_AUTH_BAN_LIFETIME, 3600). %% 1 hour
|
||||
-define(C2S_MAX_AUTH_FAILURES, 20).
|
||||
-define(CLEAN_INTERVAL, timer:minutes(10)).
|
||||
|
||||
-record(state, {host = <<"">> :: binary()}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
%%%===================================================================
|
||||
start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
|
||||
|
||||
c2s_auth_result(false, _User, LServer, {Addr, _Port}) ->
|
||||
BanLifetime = gen_mod:get_module_opt(
|
||||
LServer, ?MODULE, c2s_auth_ban_lifetime,
|
||||
fun(T) when is_integer(T), T > 0 -> T end,
|
||||
?C2S_AUTH_BAN_LIFETIME),
|
||||
MaxFailures = gen_mod:get_module_opt(
|
||||
LServer, ?MODULE, c2s_max_auth_failures,
|
||||
fun(I) when is_integer(I), I > 0 -> I end,
|
||||
?C2S_MAX_AUTH_FAILURES),
|
||||
UnbanTS = unban_timestamp(BanLifetime),
|
||||
case ets:lookup(failed_auth, Addr) of
|
||||
[{Addr, N, _, _}] ->
|
||||
ets:insert(failed_auth, {Addr, N+1, UnbanTS, MaxFailures});
|
||||
[] ->
|
||||
ets:insert(failed_auth, {Addr, 1, UnbanTS, MaxFailures})
|
||||
end;
|
||||
c2s_auth_result(true, _User, _Server, _AddrPort) ->
|
||||
ok.
|
||||
|
||||
check_bl_c2s(_Acc, Addr, Lang) ->
|
||||
case ets:lookup(failed_auth, Addr) of
|
||||
[{Addr, N, TS, MaxFailures}] when N >= MaxFailures ->
|
||||
case TS > now() of
|
||||
true ->
|
||||
IP = jlib:ip_to_list(Addr),
|
||||
UnbanDate = format_date(
|
||||
calendar:now_to_universal_time(TS)),
|
||||
LogReason = io_lib:fwrite(
|
||||
"Too many (~p) failed authentications "
|
||||
"from this IP address (~s). The address "
|
||||
"will be unblocked at ~s UTC",
|
||||
[N, IP, UnbanDate]),
|
||||
ReasonT = io_lib:fwrite(
|
||||
translate:translate(
|
||||
Lang,
|
||||
<<"Too many (~p) failed authentications "
|
||||
"from this IP address (~s). The address "
|
||||
"will be unblocked at ~s UTC">>),
|
||||
[N, IP, UnbanDate]),
|
||||
{stop, {true, LogReason, ReasonT}};
|
||||
false ->
|
||||
ets:delete(failed_auth, Addr),
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% gen_mod callbacks
|
||||
%%====================================================================
|
||||
start(Host, Opts) ->
|
||||
catch ets:new(failed_auth, [named_table, public]),
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
transient, 1000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||
supervisor:delete_child(ejabberd_sup, Proc).
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
init([Host, _Opts]) ->
|
||||
ejabberd_hooks:add(c2s_auth_result, Host, ?MODULE, c2s_auth_result, 100),
|
||||
ejabberd_hooks:add(check_bl_c2s, ?MODULE, check_bl_c2s, 100),
|
||||
erlang:send_after(?CLEAN_INTERVAL, self(), clean),
|
||||
{ok, #state{host = Host}}.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
{reply, Reply, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
?ERROR_MSG("got unexpected cast = ~p", [_Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(clean, State) ->
|
||||
?DEBUG("cleaning ~p ETS table", [failed_auth]),
|
||||
Now = now(),
|
||||
ets:select_delete(
|
||||
failed_auth,
|
||||
ets:fun2ms(fun({_, _, UnbanTS, _}) -> UnbanTS =< Now end)),
|
||||
erlang:send_after(?CLEAN_INTERVAL, self(), clean),
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
?ERROR_MSG("got unexpected info = ~p", [_Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, #state{host = Host}) ->
|
||||
ejabberd_hooks:delete(c2s_auth_result, Host, ?MODULE, c2s_auth_result, 100),
|
||||
case is_loaded_at_other_hosts(Host) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
ejabberd_hooks:delete(check_bl_c2s, ?MODULE, check_bl_c2s, 100),
|
||||
ets:delete(failed_auth)
|
||||
end.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
unban_timestamp(BanLifetime) ->
|
||||
{MegaSecs, MSecs, USecs} = now(),
|
||||
UnbanSecs = MegaSecs * 1000000 + MSecs + BanLifetime,
|
||||
{UnbanSecs div 1000000, UnbanSecs rem 1000000, USecs}.
|
||||
|
||||
is_loaded_at_other_hosts(Host) ->
|
||||
lists:any(
|
||||
fun(VHost) when VHost == Host ->
|
||||
false;
|
||||
(VHost) ->
|
||||
gen_mod:is_loaded(VHost, ?MODULE)
|
||||
end, ?MYHOSTS).
|
||||
|
||||
format_date({{Year, Month, Day}, {Hour, Minute, Second}}) ->
|
||||
io_lib:format("~2..0w:~2..0w:~2..0w ~2..0w.~2..0w.~4..0w",
|
||||
[Hour, Minute, Second, Day, Month, Year]).
|
||||
@@ -48,17 +48,12 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("ejabberd_http.hrl").
|
||||
|
||||
-include("jlib.hrl").
|
||||
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
%%-include("ejabberd_http.hrl").
|
||||
%% TODO: When ejabberd-modules SVN gets the new ejabberd_http.hrl, delete this code:
|
||||
-record(request,
|
||||
{method, path, q = [], us, auth, lang = <<"">>,
|
||||
data = <<"">>, ip, host, port, tp, headers}).
|
||||
|
||||
-record(state,
|
||||
{host, docroot, accesslog, accesslogfd,
|
||||
directory_indices, custom_headers, default_content_type,
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
-export([update_bl_c2s/0]).
|
||||
|
||||
%% Hooks:
|
||||
-export([is_ip_in_c2s_blacklist/2]).
|
||||
-export([is_ip_in_c2s_blacklist/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -107,14 +107,23 @@ update_bl_c2s() ->
|
||||
%% Return: false: IP not blacklisted
|
||||
%% true: IP is blacklisted
|
||||
%% IPV4 IP tuple:
|
||||
is_ip_in_c2s_blacklist(_Val, IP) when is_tuple(IP) ->
|
||||
is_ip_in_c2s_blacklist(_Val, IP, Lang) when is_tuple(IP) ->
|
||||
BinaryIP = jlib:ip_to_list(IP),
|
||||
case ets:lookup(bl_c2s, BinaryIP) of
|
||||
[] -> %% Not in blacklist
|
||||
false;
|
||||
[_] -> {stop, true}
|
||||
[_] ->
|
||||
LogReason = io_lib:fwrite(
|
||||
"This IP address is blacklisted in ~s",
|
||||
[?BLC2S]),
|
||||
ReasonT = io_lib:fwrite(
|
||||
translate:translate(
|
||||
Lang,
|
||||
<<"This IP address is blacklisted in ~s">>),
|
||||
[?BLC2S]),
|
||||
{stop, {true, LogReason, ReasonT}}
|
||||
end;
|
||||
is_ip_in_c2s_blacklist(_Val, _IP) -> false.
|
||||
is_ip_in_c2s_blacklist(_Val, _IP, _Lang) -> false.
|
||||
|
||||
%% TODO:
|
||||
%% - For now, we do not kick user already logged on a given IP after
|
||||
|
||||
+60
-16
@@ -56,7 +56,8 @@
|
||||
|
||||
-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
|
||||
{binary(), binary(), inet:port_number()} |
|
||||
{binary(), binary()}.
|
||||
{binary(), binary()} |
|
||||
{binary()}.
|
||||
|
||||
-record(irc_connection,
|
||||
{jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
|
||||
@@ -590,6 +591,17 @@ get_data(_LServer, Host, From, mnesia) ->
|
||||
[] -> empty;
|
||||
[#irc_custom{data = Data}] -> Data
|
||||
end;
|
||||
get_data(LServer, Host, From, riak) ->
|
||||
#jid{luser = LUser, lserver = LServer} = From,
|
||||
US = {LUser, LServer},
|
||||
case ejabberd_riak:get(irc_custom, irc_custom_schema(), {US, Host}) of
|
||||
{ok, #irc_custom{data = Data}} ->
|
||||
Data;
|
||||
{error, notfound} ->
|
||||
empty;
|
||||
_Err ->
|
||||
error
|
||||
end;
|
||||
get_data(LServer, Host, From, odbc) ->
|
||||
SJID =
|
||||
ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
|
||||
@@ -600,7 +612,7 @@ get_data(LServer, Host, From, odbc) ->
|
||||
<<"';">>])
|
||||
of
|
||||
{selected, [<<"data">>], [[SData]]} ->
|
||||
data_to_binary(ejabberd_odbc:decode_term(SData));
|
||||
data_to_binary(From, ejabberd_odbc:decode_term(SData));
|
||||
{'EXIT', _} -> error;
|
||||
{selected, _, _} -> empty
|
||||
end.
|
||||
@@ -711,7 +723,7 @@ get_form(_ServerHost, _Host, _, _, _Lang) ->
|
||||
|
||||
set_data(ServerHost, Host, From, Data) ->
|
||||
LServer = jlib:nameprep(ServerHost),
|
||||
set_data(LServer, Host, From, data_to_binary(Data),
|
||||
set_data(LServer, Host, From, data_to_binary(From, Data),
|
||||
gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
set_data(_LServer, Host, From, Data, mnesia) ->
|
||||
@@ -722,6 +734,12 @@ set_data(_LServer, Host, From, Data, mnesia) ->
|
||||
data = Data})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
set_data(LServer, Host, From, Data, riak) ->
|
||||
{LUser, LServer, _} = jlib:jid_tolower(From),
|
||||
US = {LUser, LServer},
|
||||
{atomic, ejabberd_riak:put(#irc_custom{us_host = {US, Host},
|
||||
data = Data},
|
||||
irc_custom_schema())};
|
||||
set_data(LServer, Host, From, Data, odbc) ->
|
||||
SJID =
|
||||
ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
|
||||
@@ -1217,28 +1235,48 @@ get_username_and_connection_params(Data) ->
|
||||
end,
|
||||
{Username, ConnParams}.
|
||||
|
||||
data_to_binary(Data) ->
|
||||
data_to_binary(JID, Data) ->
|
||||
lists:map(
|
||||
fun({username, U}) ->
|
||||
{username, iolist_to_binary(U)};
|
||||
({connections_params, Params}) ->
|
||||
{connections_params,
|
||||
lists:map(
|
||||
fun({S, E}) ->
|
||||
{iolist_to_binary(S), iolist_to_binary(E)};
|
||||
({S, E, Port}) ->
|
||||
{iolist_to_binary(S), iolist_to_binary(E), Port};
|
||||
({S, E, Port, P}) ->
|
||||
{iolist_to_binary(S), iolist_to_binary(E),
|
||||
Port, iolist_to_binary(P)}
|
||||
end, Params)};
|
||||
{connections_params,
|
||||
lists:flatmap(
|
||||
fun(Param) ->
|
||||
try
|
||||
[conn_param_to_binary(Param)]
|
||||
catch _:_ ->
|
||||
if JID /= error ->
|
||||
?ERROR_MSG("failed to convert "
|
||||
"parameter ~p for user ~s",
|
||||
[Param,
|
||||
jlib:jid_to_string(JID)]);
|
||||
true ->
|
||||
?ERROR_MSG("failed to convert "
|
||||
"parameter ~p",
|
||||
[Param])
|
||||
end,
|
||||
[]
|
||||
end
|
||||
end, Params)};
|
||||
(Opt) ->
|
||||
Opt
|
||||
end, Data).
|
||||
|
||||
conn_param_to_binary({S}) ->
|
||||
{iolist_to_binary(S)};
|
||||
conn_param_to_binary({S, E}) ->
|
||||
{iolist_to_binary(S), iolist_to_binary(E)};
|
||||
conn_param_to_binary({S, E, Port}) when is_integer(Port) ->
|
||||
{iolist_to_binary(S), iolist_to_binary(E), Port};
|
||||
conn_param_to_binary({S, E, Port, P}) when is_integer(Port) ->
|
||||
{iolist_to_binary(S), iolist_to_binary(E), Port, iolist_to_binary(P)}.
|
||||
|
||||
conn_params_to_list(Params) ->
|
||||
lists:map(
|
||||
fun({S, E}) ->
|
||||
fun({S}) ->
|
||||
{binary_to_list(S)};
|
||||
({S, E}) ->
|
||||
{binary_to_list(S), binary_to_list(E)};
|
||||
({S, E, Port}) ->
|
||||
{binary_to_list(S), binary_to_list(E), Port};
|
||||
@@ -1247,6 +1285,9 @@ conn_params_to_list(Params) ->
|
||||
Port, binary_to_list(P)}
|
||||
end, Params).
|
||||
|
||||
irc_custom_schema() ->
|
||||
{record_info(fields, irc_custom), #irc_custom{}}.
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, irc_custom),
|
||||
case mnesia:table_info(irc_custom, attributes) of
|
||||
@@ -1256,10 +1297,11 @@ update_table() ->
|
||||
fun(#irc_custom{us_host = {_, H}}) -> H end,
|
||||
fun(#irc_custom{us_host = {{U, S}, H},
|
||||
data = Data} = R) ->
|
||||
JID = jlib:make_jid(U, S, <<"">>),
|
||||
R#irc_custom{us_host = {{iolist_to_binary(U),
|
||||
iolist_to_binary(S)},
|
||||
iolist_to_binary(H)},
|
||||
data = data_to_binary(Data)}
|
||||
data = data_to_binary(JID, Data)}
|
||||
end);
|
||||
_ ->
|
||||
?INFO_MSG("Recreating irc_custom table", []),
|
||||
@@ -1299,5 +1341,7 @@ import(_LServer) ->
|
||||
|
||||
import(_LServer, mnesia, #irc_custom{} = R) ->
|
||||
mnesia:dirty_write(R);
|
||||
import(_LServer, riak, #irc_custom{} = R) ->
|
||||
ejabberd_riak:put(R, irc_custom_schema());
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+26
-1
@@ -168,6 +168,17 @@ get_last(LUser, LServer, mnesia) ->
|
||||
status = Status}] ->
|
||||
{ok, TimeStamp, Status}
|
||||
end;
|
||||
get_last(LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get(last_activity, last_activity_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #last_activity{timestamp = TimeStamp,
|
||||
status = Status}} ->
|
||||
{ok, TimeStamp, Status};
|
||||
{error, notfound} ->
|
||||
not_found;
|
||||
Err ->
|
||||
Err
|
||||
end;
|
||||
get_last(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:get_last(LServer, Username) of
|
||||
@@ -235,6 +246,13 @@ store_last_info(LUser, LServer, TimeStamp, Status,
|
||||
status = Status})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
store_last_info(LUser, LServer, TimeStamp, Status,
|
||||
riak) ->
|
||||
US = {LUser, LServer},
|
||||
{atomic, ejabberd_riak:put(#last_activity{us = US,
|
||||
timestamp = TimeStamp,
|
||||
status = Status},
|
||||
last_activity_schema())};
|
||||
store_last_info(LUser, LServer, TimeStamp, Status,
|
||||
odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
@@ -264,7 +282,9 @@ remove_user(LUser, LServer, mnesia) ->
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_last(LServer, Username).
|
||||
odbc_queries:del_last(LServer, Username);
|
||||
remove_user(LUser, LServer, riak) ->
|
||||
{atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}.
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, last_activity),
|
||||
@@ -283,6 +303,9 @@ update_table() ->
|
||||
mnesia:transform_table(last_activity, ignore, Fields)
|
||||
end.
|
||||
|
||||
last_activity_schema() ->
|
||||
{record_info(fields, last_activity), #last_activity{}}.
|
||||
|
||||
export(_Server) ->
|
||||
[{last_activity,
|
||||
fun(Host, #last_activity{us = {LUser, LServer},
|
||||
@@ -312,6 +335,8 @@ import(LServer) ->
|
||||
|
||||
import(_LServer, mnesia, #last_activity{} = LA) ->
|
||||
mnesia:dirty_write(LA);
|
||||
import(_LServer, riak, #last_activity{} = LA) ->
|
||||
ejabberd_riak:put(LA, last_activity_schema());
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
|
||||
+86
-1
@@ -147,6 +147,10 @@ store_room(_LServer, Host, Name, Opts, mnesia) ->
|
||||
opts = Opts})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
store_room(_LServer, Host, Name, Opts, riak) ->
|
||||
{atomic, ejabberd_riak:put(#muc_room{name_host = {Name, Host},
|
||||
opts = Opts},
|
||||
muc_room_schema())};
|
||||
store_room(LServer, Host, Name, Opts, odbc) ->
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
@@ -170,6 +174,11 @@ restore_room(_LServer, Host, Name, mnesia) ->
|
||||
[#muc_room{opts = Opts}] -> Opts;
|
||||
_ -> error
|
||||
end;
|
||||
restore_room(_LServer, Host, Name, riak) ->
|
||||
case ejabberd_riak:get(muc_room, muc_room_schema(), {Name, Host}) of
|
||||
{ok, #muc_room{opts = Opts}} -> Opts;
|
||||
_ -> error
|
||||
end;
|
||||
restore_room(LServer, Host, Name, odbc) ->
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
@@ -192,6 +201,8 @@ forget_room(_LServer, Host, Name, mnesia) ->
|
||||
F = fun () -> mnesia:delete({muc_room, {Name, Host}})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
forget_room(_LServer, Host, Name, riak) ->
|
||||
{atomic, ejabberd_riak:delete(muc_room, {Name, Host})};
|
||||
forget_room(LServer, Host, Name, odbc) ->
|
||||
SName = ejabberd_odbc:escape(Name),
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
@@ -231,6 +242,19 @@ can_use_nick(_LServer, Host, JID, Nick, mnesia) ->
|
||||
[] -> true;
|
||||
[#muc_registered{us_host = {U, _Host}}] -> U == LUS
|
||||
end;
|
||||
can_use_nick(LServer, Host, JID, Nick, riak) ->
|
||||
{LUser, LServer, _} = jlib:jid_tolower(JID),
|
||||
LUS = {LUser, LServer},
|
||||
case ejabberd_riak:get_by_index(muc_registered,
|
||||
muc_registered_schema(),
|
||||
<<"nick_host">>, {Nick, Host}) of
|
||||
{ok, []} ->
|
||||
true;
|
||||
{ok, [#muc_registered{us_host = {U, _Host}}]} ->
|
||||
U == LUS;
|
||||
{error, _} ->
|
||||
true
|
||||
end;
|
||||
can_use_nick(LServer, Host, JID, Nick, odbc) ->
|
||||
SJID =
|
||||
jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(JID))),
|
||||
@@ -617,6 +641,16 @@ get_rooms(_LServer, Host, mnesia) ->
|
||||
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
|
||||
Rs -> Rs
|
||||
end;
|
||||
get_rooms(_LServer, Host, riak) ->
|
||||
case ejabberd_riak:get(muc_room, muc_room_schema()) of
|
||||
{ok, Rs} ->
|
||||
lists:filter(
|
||||
fun(#muc_room{name_host = {_, H}}) ->
|
||||
Host == H
|
||||
end, Rs);
|
||||
_Err ->
|
||||
[]
|
||||
end;
|
||||
get_rooms(LServer, Host, odbc) ->
|
||||
SHost = ejabberd_odbc:escape(Host),
|
||||
case catch ejabberd_odbc:sql_query(LServer,
|
||||
@@ -839,6 +873,15 @@ get_nick(_LServer, Host, From, mnesia) ->
|
||||
[] -> error;
|
||||
[#muc_registered{nick = Nick}] -> Nick
|
||||
end;
|
||||
get_nick(LServer, Host, From, riak) ->
|
||||
{LUser, LServer, _} = jlib:jid_tolower(From),
|
||||
US = {LUser, LServer},
|
||||
case ejabberd_riak:get(muc_registered,
|
||||
muc_registered_schema(),
|
||||
{US, Host}) of
|
||||
{ok, #muc_registered{nick = Nick}} -> Nick;
|
||||
{error, _} -> error
|
||||
end;
|
||||
get_nick(LServer, Host, From, odbc) ->
|
||||
SJID =
|
||||
ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
|
||||
@@ -871,7 +914,8 @@ iq_get_register_info(ServerHost, Host, From, Lang) ->
|
||||
<<"You need a client that supports x:data "
|
||||
"to register the nickname">>)}]},
|
||||
#xmlel{name = <<"x">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_XDATA}],
|
||||
attrs = [{<<"xmlns">>, ?NS_XDATA},
|
||||
{<<"type">>, <<"form">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"title">>, attrs = [],
|
||||
children =
|
||||
@@ -922,6 +966,35 @@ set_nick(_LServer, Host, From, Nick, mnesia) ->
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
set_nick(LServer, Host, From, Nick, riak) ->
|
||||
{LUser, LServer, _} = jlib:jid_tolower(From),
|
||||
LUS = {LUser, LServer},
|
||||
{atomic,
|
||||
case Nick of
|
||||
<<"">> ->
|
||||
ejabberd_riak:delete(muc_registered, {LUS, Host});
|
||||
_ ->
|
||||
Allow = case ejabberd_riak:get_by_index(
|
||||
muc_registered,
|
||||
muc_registered_schema(),
|
||||
<<"nick_host">>, {Nick, Host}) of
|
||||
{ok, []} ->
|
||||
true;
|
||||
{ok, [#muc_registered{us_host = {U, _Host}}]} ->
|
||||
U == LUS;
|
||||
{error, _} ->
|
||||
false
|
||||
end,
|
||||
if Allow ->
|
||||
ejabberd_riak:put(#muc_registered{us_host = {LUS, Host},
|
||||
nick = Nick},
|
||||
muc_registered_schema(),
|
||||
[{'2i', [{<<"nick_host">>,
|
||||
{Nick, Host}}]}]);
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end};
|
||||
set_nick(LServer, Host, From, Nick, odbc) ->
|
||||
JID =
|
||||
jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From))),
|
||||
@@ -1107,6 +1180,12 @@ update_tables(Host) ->
|
||||
update_muc_room_table(Host),
|
||||
update_muc_registered_table(Host).
|
||||
|
||||
muc_room_schema() ->
|
||||
{record_info(fields, muc_room), #muc_room{}}.
|
||||
|
||||
muc_registered_schema() ->
|
||||
{record_info(fields, muc_registered), #muc_registered{}}.
|
||||
|
||||
update_muc_room_table(_Host) ->
|
||||
Fields = record_info(fields, muc_room),
|
||||
case mnesia:table_info(muc_room, attributes) of
|
||||
@@ -1202,5 +1281,11 @@ import(_LServer, mnesia, #muc_room{} = R) ->
|
||||
mnesia:dirty_write(R);
|
||||
import(_LServer, mnesia, #muc_registered{} = R) ->
|
||||
mnesia:dirty_write(R);
|
||||
import(_LServer, riak, #muc_room{} = R) ->
|
||||
ejabberd_riak:put(R, muc_room_schema());
|
||||
import(_LServer, riak,
|
||||
#muc_registered{us_host = {_, Host}, nick = Nick} = R) ->
|
||||
ejabberd_riak:put(R, muc_registered_schema(),
|
||||
[{'2i', [{<<"nick_host">>, {Nick, Host}}]}]);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+28
-22
@@ -146,7 +146,13 @@ init([Host, Opts]) ->
|
||||
(plaintext) -> plaintext
|
||||
end, html),
|
||||
FilePermissions = gen_mod:get_opt(file_permissions, Opts,
|
||||
fun({A, B}) -> {A, B}
|
||||
fun(SubOpts) ->
|
||||
F = fun({mode, Mode}, {_M, G}) ->
|
||||
{Mode, G};
|
||||
({group, Group}, {M, _G}) ->
|
||||
{M, Group}
|
||||
end,
|
||||
lists:foldl(F, {644, 33}, SubOpts)
|
||||
end, {644, 33}),
|
||||
CSSFile = gen_mod:get_opt(cssfile, Opts,
|
||||
fun iolist_to_binary/1,
|
||||
@@ -233,16 +239,22 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
%%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
add_to_log2(text, {Nick, Packet}, Room, Opts, State) ->
|
||||
case {xml:get_subtag(Packet, <<"subject">>),
|
||||
xml:get_subtag(Packet, <<"body">>)}
|
||||
case {xml:get_subtag(Packet, <<"no-store">>),
|
||||
xml:get_subtag(Packet, <<"no-permanent-store">>)}
|
||||
of
|
||||
{false, false} -> ok;
|
||||
{false, SubEl} ->
|
||||
Message = {body, xml:get_tag_cdata(SubEl)},
|
||||
add_message_to_log(Nick, Message, Room, Opts, State);
|
||||
{SubEl, _} ->
|
||||
Message = {subject, xml:get_tag_cdata(SubEl)},
|
||||
add_message_to_log(Nick, Message, Room, Opts, State)
|
||||
{false, false} ->
|
||||
case {xml:get_subtag(Packet, <<"subject">>),
|
||||
xml:get_subtag(Packet, <<"body">>)}
|
||||
of
|
||||
{false, false} -> ok;
|
||||
{false, SubEl} ->
|
||||
Message = {body, xml:get_tag_cdata(SubEl)},
|
||||
add_message_to_log(Nick, Message, Room, Opts, State);
|
||||
{SubEl, _} ->
|
||||
Message = {subject, xml:get_tag_cdata(SubEl)},
|
||||
add_message_to_log(Nick, Message, Room, Opts, State)
|
||||
end;
|
||||
{_, _} -> ok
|
||||
end;
|
||||
add_to_log2(roomconfig_change, _Occupants, Room, Opts,
|
||||
State) ->
|
||||
@@ -565,16 +577,7 @@ get_dateweek(Date, Lang) ->
|
||||
end).
|
||||
|
||||
make_dir_rec(Dir) ->
|
||||
DirS = binary_to_list(Dir),
|
||||
case file:read_file_info(DirS) of
|
||||
{ok, _} -> ok;
|
||||
{error, enoent} ->
|
||||
DirL = [list_to_binary(F) || F <- filename:split(DirS)],
|
||||
DirR = lists:sublist(DirL, length(DirL) - 1),
|
||||
make_dir_rec(fjoin(DirR)),
|
||||
file:make_dir(DirS),
|
||||
file:change_mode(DirS, 8#00755) % -rwxr-xr-x
|
||||
end.
|
||||
filelib:ensure_dir(<<Dir/binary, $/>>).
|
||||
|
||||
%% {ok, F1}=file:open("valid-xhtml10.png", [read]).
|
||||
%% {ok, F1b}=file:read(F1, 1000000).
|
||||
@@ -779,7 +782,7 @@ fw(F, S, O, FileFormat) ->
|
||||
S1y = ejabberd_regexp:greplace(S1x, ?PLAINTEXT_IN, <<"<">>),
|
||||
ejabberd_regexp:greplace(S1y, ?PLAINTEXT_OUT, <<">">>)
|
||||
end,
|
||||
io:format(F, S2, []).
|
||||
file:write(F, S2).
|
||||
|
||||
put_header(_, _, _, _, _, _, _, _, _, plaintext) -> ok;
|
||||
put_header(F, Room, Date, CSSFile, Lang, Hour_offset,
|
||||
@@ -1016,7 +1019,9 @@ htmlize2(S1, NoFollow) ->
|
||||
<<"\\ \\ ">>),
|
||||
S7 = ejabberd_regexp:greplace(S6, <<"\\t">>,
|
||||
<<"\\ \\ \\ \\ ">>),
|
||||
ejabberd_regexp:greplace(S7, <<226, 128, 174>>,
|
||||
S8 = ejabberd_regexp:greplace(S7, <<"~">>,
|
||||
<<"~~">>),
|
||||
ejabberd_regexp:greplace(S8, <<226, 128, 174>>,
|
||||
<<"[RLO]">>).
|
||||
|
||||
link_regexp(false) -> <<"<a href=\"&\">&</a>">>;
|
||||
@@ -1240,5 +1245,6 @@ calc_hour_offset(TimeHere) ->
|
||||
3600,
|
||||
TimeHereHour - TimeZeroHour.
|
||||
|
||||
fjoin([]) -> <<"/">>;
|
||||
fjoin(FileList) ->
|
||||
list_to_binary(filename:join([binary_to_list(File) || File <- FileList])).
|
||||
|
||||
+23
-17
@@ -127,6 +127,13 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, _Nick, D
|
||||
just_created = true,
|
||||
room_shaper = Shaper}),
|
||||
State1 = set_opts(DefRoomOpts, State),
|
||||
if (State1#state.config)#config.persistent ->
|
||||
mod_muc:store_room(State1#state.server_host,
|
||||
State1#state.host,
|
||||
State1#state.room,
|
||||
make_opts(State1));
|
||||
true -> ok
|
||||
end,
|
||||
?INFO_MSG("Created MUC room ~s@~s by ~s",
|
||||
[Room, Host, jlib:jid_to_string(Creator)]),
|
||||
add_to_log(room_existence, created, State1),
|
||||
@@ -167,7 +174,7 @@ normal_state({route, From, <<"">>,
|
||||
Now = now_to_usec(now()),
|
||||
MinMessageInterval =
|
||||
trunc(gen_mod:get_module_opt(StateData#state.server_host,
|
||||
mod_muc, min_message_interval, fun(MMI) when is_integer(MMI) -> MMI end, 0)
|
||||
mod_muc, min_message_interval, fun(MMI) when is_number(MMI) -> MMI end, 0)
|
||||
* 1000000),
|
||||
Size = element_size(Packet),
|
||||
{MessageShaper, MessageShaperInterval} =
|
||||
@@ -1510,15 +1517,17 @@ get_user_activity(JID, StateData) ->
|
||||
|
||||
store_user_activity(JID, UserActivity, StateData) ->
|
||||
MinMessageInterval =
|
||||
gen_mod:get_module_opt(StateData#state.server_host,
|
||||
mod_muc, min_message_interval,
|
||||
fun(I) when is_integer(I), I>=0 -> I end,
|
||||
0),
|
||||
trunc(gen_mod:get_module_opt(StateData#state.server_host,
|
||||
mod_muc, min_message_interval,
|
||||
fun(I) when is_number(I), I>=0 -> I end,
|
||||
0)
|
||||
* 1000),
|
||||
MinPresenceInterval =
|
||||
gen_mod:get_module_opt(StateData#state.server_host,
|
||||
mod_muc, min_presence_interval,
|
||||
fun(I) when is_integer(I), I>=0 -> I end,
|
||||
0),
|
||||
trunc(gen_mod:get_module_opt(StateData#state.server_host,
|
||||
mod_muc, min_presence_interval,
|
||||
fun(I) when is_number(I), I>=0 -> I end,
|
||||
0)
|
||||
* 1000),
|
||||
Key = jlib:jid_tolower(JID),
|
||||
Now = now_to_usec(now()),
|
||||
Activity1 = clean_treap(StateData#state.activity,
|
||||
@@ -1549,8 +1558,8 @@ store_user_activity(JID, UserActivity, StateData) ->
|
||||
100000),
|
||||
Delay = lists:max([MessageShaperInterval,
|
||||
PresenceShaperInterval,
|
||||
MinMessageInterval * 1000,
|
||||
MinPresenceInterval * 1000])
|
||||
MinMessageInterval,
|
||||
MinPresenceInterval])
|
||||
* 1000,
|
||||
Priority = {1, -(Now + Delay)},
|
||||
StateData#state{activity =
|
||||
@@ -2429,24 +2438,21 @@ add_message_to_history(FromNick, FromJID, Packet, StateData) ->
|
||||
false -> false;
|
||||
_ -> true
|
||||
end,
|
||||
TimeStamp = calendar:now_to_universal_time(now()),
|
||||
TimeStamp = now(),
|
||||
SenderJid = case
|
||||
(StateData#state.config)#config.anonymous
|
||||
of
|
||||
true -> StateData#state.jid;
|
||||
false -> FromJID
|
||||
end,
|
||||
TSPacket = xml:append_subtags(Packet,
|
||||
[jlib:timestamp_to_xml(TimeStamp, utc,
|
||||
SenderJid, <<"">>),
|
||||
jlib:timestamp_to_xml(TimeStamp)]),
|
||||
TSPacket = jlib:add_delay_info(Packet, SenderJid, TimeStamp),
|
||||
SPacket =
|
||||
jlib:replace_from_to(jlib:jid_replace_resource(StateData#state.jid,
|
||||
FromNick),
|
||||
StateData#state.jid, TSPacket),
|
||||
Size = element_size(SPacket),
|
||||
Q1 = lqueue_in({FromNick, TSPacket, HaveSubject,
|
||||
TimeStamp, Size},
|
||||
calendar:now_to_universal_time(TimeStamp), Size},
|
||||
StateData#state.history),
|
||||
add_to_log(text, {FromNick, Packet}, StateData),
|
||||
StateData#state{history = Q1}.
|
||||
|
||||
+322
-129
@@ -26,13 +26,15 @@
|
||||
-module(mod_offline).
|
||||
|
||||
-author('alexey@process-one.net').
|
||||
-define(GEN_SERVER, p1_server).
|
||||
-behaviour(?GEN_SERVER).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-export([count_offline_messages/2]).
|
||||
|
||||
-export([start/2,
|
||||
loop/2,
|
||||
start_link/2,
|
||||
stop/1,
|
||||
store_packet/3,
|
||||
resend_offline_messages/2,
|
||||
@@ -50,6 +52,10 @@
|
||||
webadmin_user/4,
|
||||
webadmin_user_parse_query/5]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2,
|
||||
handle_info/2, terminate/2, code_change/3]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
||||
@@ -67,6 +73,10 @@
|
||||
to = #jid{} :: jid() | '_',
|
||||
packet = #xmlel{} :: xmlel() | '_'}).
|
||||
|
||||
-record(state,
|
||||
{host = <<"">> :: binary(),
|
||||
access_max_offline_messages}).
|
||||
|
||||
-define(PROCNAME, ejabberd_offline).
|
||||
|
||||
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
|
||||
@@ -74,7 +84,29 @@
|
||||
%% default value for the maximum number of user messages
|
||||
-define(MAX_USER_MESSAGES, infinity).
|
||||
|
||||
start_link(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
?GEN_SERVER:start_link({local, Proc}, ?MODULE,
|
||||
[Host, Opts], []).
|
||||
|
||||
start(Host, Opts) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
||||
temporary, 1000, worker, [?MODULE]},
|
||||
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||
|
||||
stop(Host) ->
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
?GEN_SERVER:call(Proc, stop),
|
||||
supervisor:delete_child(ejabberd_sup, Proc),
|
||||
ok.
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% gen_server callbacks
|
||||
%%====================================================================
|
||||
|
||||
init([Host, Opts]) ->
|
||||
case gen_mod:db_type(Opts) of
|
||||
mnesia ->
|
||||
mnesia:create_table(offline_msg,
|
||||
@@ -102,31 +134,63 @@ start(Host, Opts) ->
|
||||
ejabberd_hooks:add(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts, fun(A) -> A end, max_user_offline_messages),
|
||||
register(gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
spawn(?MODULE, loop, [Host, AccessMaxOfflineMsgs])).
|
||||
{ok,
|
||||
#state{host = Host,
|
||||
access_max_offline_messages = AccessMaxOfflineMsgs}}.
|
||||
|
||||
|
||||
handle_call(stop, _From, State) ->
|
||||
{stop, normal, ok, State}.
|
||||
|
||||
|
||||
handle_cast(_Msg, State) -> {noreply, State}.
|
||||
|
||||
|
||||
handle_info(#offline_msg{us = UserServer} = Msg, State) ->
|
||||
#state{host = Host,
|
||||
access_max_offline_messages = AccessMaxOfflineMsgs} = State,
|
||||
DBType = gen_mod:db_type(Host, ?MODULE),
|
||||
Msgs = receive_all(UserServer, [Msg], DBType),
|
||||
Len = length(Msgs),
|
||||
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
|
||||
UserServer, Host),
|
||||
store_offline_msg(Host, UserServer, Msgs, Len, MaxOfflineMsgs, DBType),
|
||||
{noreply, State};
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
?ERROR_MSG("got unexpected info: ~p", [_Info]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
terminate(_Reason, State) ->
|
||||
Host = State#state.host,
|
||||
ejabberd_hooks:delete(offline_message_hook, Host,
|
||||
?MODULE, store_packet, 50),
|
||||
ejabberd_hooks:delete(resend_offline_messages_hook,
|
||||
Host, ?MODULE, pop_offline_messages, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:delete(anonymous_purge_hook, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
|
||||
ejabberd_hooks:delete(webadmin_page_host, Host,
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:delete(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
ok.
|
||||
|
||||
|
||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||
|
||||
loop(Host, AccessMaxOfflineMsgs) ->
|
||||
receive
|
||||
#offline_msg{us = UserServer} = Msg ->
|
||||
DBType = gen_mod:db_type(Host, ?MODULE),
|
||||
Msgs = receive_all(UserServer, [Msg], DBType),
|
||||
Len = length(Msgs),
|
||||
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
|
||||
UserServer, Host),
|
||||
store_offline_msg(Host, UserServer, Msgs, Len, MaxOfflineMsgs, DBType),
|
||||
loop(Host, AccessMaxOfflineMsgs);
|
||||
_ ->
|
||||
loop(Host, AccessMaxOfflineMsgs)
|
||||
end.
|
||||
|
||||
store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs,
|
||||
mnesia) ->
|
||||
F = fun () ->
|
||||
Count = if MaxOfflineMsgs =/= infinity ->
|
||||
Len +
|
||||
p1_mnesia:count_records(offline_msg,
|
||||
#offline_msg{us = US,
|
||||
_ = '_'});
|
||||
Len + count_mnesia_records(US);
|
||||
true -> 0
|
||||
end,
|
||||
if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs);
|
||||
@@ -151,30 +215,36 @@ store_offline_msg(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs, odbc) ->
|
||||
ejabberd_odbc:escape((M#offline_msg.to)#jid.luser),
|
||||
From = M#offline_msg.from,
|
||||
To = M#offline_msg.to,
|
||||
#xmlel{name = Name, attrs = Attrs,
|
||||
children = Els} =
|
||||
M#offline_msg.packet,
|
||||
Attrs2 =
|
||||
jlib:replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
Packet = #xmlel{name = Name,
|
||||
attrs = Attrs2,
|
||||
children =
|
||||
Els ++
|
||||
[jlib:timestamp_to_xml(calendar:now_to_universal_time(M#offline_msg.timestamp),
|
||||
utc,
|
||||
jlib:make_jid(<<"">>,
|
||||
Host,
|
||||
<<"">>),
|
||||
<<"Offline Storage">>),
|
||||
jlib:timestamp_to_xml(calendar:now_to_universal_time(M#offline_msg.timestamp))]},
|
||||
Packet =
|
||||
jlib:replace_from_to(From, To,
|
||||
M#offline_msg.packet),
|
||||
NewPacket =
|
||||
jlib:add_delay_info(Packet, Host,
|
||||
M#offline_msg.timestamp,
|
||||
<<"Offline Storage">>),
|
||||
XML =
|
||||
ejabberd_odbc:escape(xml:element_to_binary(Packet)),
|
||||
ejabberd_odbc:escape(xml:element_to_binary(NewPacket)),
|
||||
odbc_queries:add_spool_sql(Username, XML)
|
||||
end,
|
||||
Msgs),
|
||||
odbc_queries:add_spool(Host, Query)
|
||||
end;
|
||||
store_offline_msg(Host, {User, _}, Msgs, Len, MaxOfflineMsgs,
|
||||
riak) ->
|
||||
Count = if MaxOfflineMsgs =/= infinity ->
|
||||
Len + count_offline_messages(User, Host);
|
||||
true -> 0
|
||||
end,
|
||||
if
|
||||
Count > MaxOfflineMsgs ->
|
||||
discard_warn_sender(Msgs);
|
||||
true ->
|
||||
lists:foreach(
|
||||
fun(#offline_msg{us = US,
|
||||
timestamp = TS} = M) ->
|
||||
ejabberd_riak:put(M, offline_msg_schema(),
|
||||
[{i, TS}, {'2i', [{<<"us">>, US}]}])
|
||||
end, Msgs)
|
||||
end.
|
||||
|
||||
%% Function copied from ejabberd_sm.erl:
|
||||
@@ -193,32 +263,12 @@ receive_all(US, Msgs, DBType) ->
|
||||
after 0 ->
|
||||
case DBType of
|
||||
mnesia -> Msgs;
|
||||
odbc -> lists:reverse(Msgs)
|
||||
odbc -> lists:reverse(Msgs);
|
||||
riak -> Msgs
|
||||
end
|
||||
end.
|
||||
|
||||
stop(Host) ->
|
||||
ejabberd_hooks:delete(offline_message_hook, Host,
|
||||
?MODULE, store_packet, 50),
|
||||
ejabberd_hooks:delete(resend_offline_messages_hook,
|
||||
Host, ?MODULE, pop_offline_messages, 50),
|
||||
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
||||
remove_user, 50),
|
||||
ejabberd_hooks:delete(anonymous_purge_hook, Host,
|
||||
?MODULE, remove_user, 50),
|
||||
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
|
||||
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_sm_features, 50),
|
||||
ejabberd_hooks:delete(webadmin_page_host, Host,
|
||||
?MODULE, webadmin_page, 50),
|
||||
ejabberd_hooks:delete(webadmin_user, Host,
|
||||
?MODULE, webadmin_user, 50),
|
||||
ejabberd_hooks:delete(webadmin_user_parse_query, Host,
|
||||
?MODULE, webadmin_user_parse_query, 50),
|
||||
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
||||
exit(whereis(Proc), stop),
|
||||
{wait, Proc}.
|
||||
|
||||
get_sm_features(Acc, _From, _To, "", _Lang) ->
|
||||
get_sm_features(Acc, _From, _To, <<"">>, _Lang) ->
|
||||
Feats = case Acc of
|
||||
{result, I} -> I;
|
||||
_ -> []
|
||||
@@ -232,25 +282,57 @@ get_sm_features(_Acc, _From, _To, ?NS_FEATURE_MSGOFFLINE, _Lang) ->
|
||||
get_sm_features(Acc, _From, _To, _Node, _Lang) ->
|
||||
Acc.
|
||||
|
||||
|
||||
store_packet(From, To, Packet) ->
|
||||
need_to_store(LServer, Packet) ->
|
||||
Type = xml:get_tag_attr_s(<<"type">>, Packet),
|
||||
if (Type /= <<"error">>) and (Type /= <<"groupchat">>)
|
||||
and (Type /= <<"headline">>) ->
|
||||
case check_event(From, To, Packet) of
|
||||
true ->
|
||||
#jid{luser = LUser, lserver = LServer} = To,
|
||||
TimeStamp = now(),
|
||||
#xmlel{children = Els} = Packet,
|
||||
Expire = find_x_expire(TimeStamp, Els),
|
||||
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
|
||||
#offline_msg{us = {LUser, LServer},
|
||||
timestamp = TimeStamp, expire = Expire,
|
||||
from = From, to = To, packet = Packet},
|
||||
stop;
|
||||
and (Type /= <<"headline">>) ->
|
||||
case gen_mod:get_module_opt(
|
||||
LServer, ?MODULE, store_empty_body,
|
||||
fun(V) when is_boolean(V) -> V end,
|
||||
true) of
|
||||
false ->
|
||||
xml:get_subtag(Packet, <<"body">>) /= false;
|
||||
true ->
|
||||
true
|
||||
end;
|
||||
true ->
|
||||
false
|
||||
end.
|
||||
|
||||
store_packet(From, To, Packet) ->
|
||||
case need_to_store(To#jid.lserver, Packet) of
|
||||
true ->
|
||||
case has_no_storage_hint(Packet) of
|
||||
false ->
|
||||
case check_event(From, To, Packet) of
|
||||
true ->
|
||||
#jid{luser = LUser, lserver = LServer} = To,
|
||||
TimeStamp = now(),
|
||||
#xmlel{children = Els} = Packet,
|
||||
Expire = find_x_expire(TimeStamp, Els),
|
||||
gen_mod:get_module_proc(To#jid.lserver, ?PROCNAME) !
|
||||
#offline_msg{us = {LUser, LServer},
|
||||
timestamp = TimeStamp, expire = Expire,
|
||||
from = From, to = To, packet = Packet},
|
||||
stop;
|
||||
_ -> ok
|
||||
end;
|
||||
_ -> ok
|
||||
end;
|
||||
true -> ok
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
has_no_storage_hint(Packet) ->
|
||||
case xml:get_subtag(Packet, <<"no-store">>) of
|
||||
#xmlel{attrs = Attrs} ->
|
||||
case xml:get_attr_s(<<"xmlns">>, Attrs) of
|
||||
?NS_HINTS ->
|
||||
true;
|
||||
_ ->
|
||||
false
|
||||
end;
|
||||
_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
%% Check if the packet has any content about XEP-0022 or XEP-0085
|
||||
@@ -339,15 +421,12 @@ resend_offline_messages(User, Server) ->
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Rs} ->
|
||||
lists:foreach(fun (R) ->
|
||||
#xmlel{name = Name, attrs = Attrs,
|
||||
children = Els} =
|
||||
R#offline_msg.packet,
|
||||
ejabberd_sm !
|
||||
{route, R#offline_msg.from, R#offline_msg.to,
|
||||
#xmlel{name = Name, attrs = Attrs,
|
||||
children =
|
||||
Els ++
|
||||
[jlib:timestamp_to_xml(calendar:now_to_universal_time(R#offline_msg.timestamp))]}}
|
||||
jlib:add_delay_info(R#offline_msg.packet,
|
||||
LServer,
|
||||
R#offline_msg.timestamp,
|
||||
<<"Offline Storage">>)}
|
||||
end,
|
||||
lists:keysort(#offline_msg.timestamp, Rs));
|
||||
_ -> ok
|
||||
@@ -404,6 +483,34 @@ pop_offline_messages(Ls, LUser, LServer, odbc) ->
|
||||
end,
|
||||
Rs);
|
||||
_ -> Ls
|
||||
end;
|
||||
pop_offline_messages(Ls, LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get_by_index(offline_msg, offline_msg_schema(),
|
||||
<<"us">>, {LUser, LServer}) of
|
||||
{ok, Rs} ->
|
||||
try
|
||||
lists:foreach(
|
||||
fun(#offline_msg{timestamp = T}) ->
|
||||
ok = ejabberd_riak:delete(offline_msg, T)
|
||||
end, Rs),
|
||||
TS = now(),
|
||||
Ls ++ lists:map(
|
||||
fun (R) ->
|
||||
offline_msg_to_route(LServer, R)
|
||||
end,
|
||||
lists:filter(
|
||||
fun(R) ->
|
||||
case R#offline_msg.expire of
|
||||
never -> true;
|
||||
TimeStamp -> TS < TimeStamp
|
||||
end
|
||||
end,
|
||||
lists:keysort(#offline_msg.timestamp, Rs)))
|
||||
catch _:{badmatch, _} ->
|
||||
Ls
|
||||
end;
|
||||
_ ->
|
||||
Ls
|
||||
end.
|
||||
|
||||
remove_expired_messages(Server) ->
|
||||
@@ -428,7 +535,8 @@ remove_expired_messages(_LServer, mnesia) ->
|
||||
ok, offline_msg)
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
remove_expired_messages(_LServer, odbc) -> {atomic, ok}.
|
||||
remove_expired_messages(_LServer, odbc) -> {atomic, ok};
|
||||
remove_expired_messages(_LServer, riak) -> {atomic, ok}.
|
||||
|
||||
remove_old_messages(Days, Server) ->
|
||||
LServer = jlib:nameprep(Server),
|
||||
@@ -453,6 +561,8 @@ remove_old_messages(Days, _LServer, mnesia) ->
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
remove_old_messages(_Days, _LServer, odbc) ->
|
||||
{atomic, ok};
|
||||
remove_old_messages(_Days, _LServer, riak) ->
|
||||
{atomic, ok}.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
@@ -467,7 +577,10 @@ remove_user(LUser, LServer, mnesia) ->
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_spool_msg(LServer, Username).
|
||||
odbc_queries:del_spool_msg(LServer, Username);
|
||||
remove_user(LUser, LServer, riak) ->
|
||||
{atomic, ejabberd_riak:delete_by_index(offline_msg,
|
||||
<<"us">>, {LUser, LServer})}.
|
||||
|
||||
jid_to_binary(#jid{user = U, server = S, resource = R,
|
||||
luser = LU, lserver = LS, lresource = LR}) ->
|
||||
@@ -526,8 +639,9 @@ webadmin_page(Acc, _, _) -> Acc.
|
||||
get_offline_els(LUser, LServer) ->
|
||||
get_offline_els(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
get_offline_els(LUser, LServer, mnesia) ->
|
||||
Msgs = read_all_msgs(LUser, LServer, mnesia),
|
||||
get_offline_els(LUser, LServer, DBType)
|
||||
when DBType == mnesia; DBType == riak ->
|
||||
Msgs = read_all_msgs(LUser, LServer, DBType),
|
||||
lists:map(
|
||||
fun(Msg) ->
|
||||
{route, From, To, Packet} = offline_msg_to_route(LServer, Msg),
|
||||
@@ -536,8 +650,8 @@ get_offline_els(LUser, LServer, mnesia) ->
|
||||
get_offline_els(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch ejabberd_odbc:sql_query(LServer,
|
||||
[<<"select xml from spool where username='">>,
|
||||
Username, <<"' order by seq;">>]) of
|
||||
[<<"select xml from spool where username='">>,
|
||||
Username, <<"' order by seq;">>]) of
|
||||
{selected, [<<"xml">>], Rs} ->
|
||||
lists:flatmap(
|
||||
fun([XML]) ->
|
||||
@@ -558,19 +672,9 @@ get_offline_els(LUser, LServer, odbc) ->
|
||||
end.
|
||||
|
||||
offline_msg_to_route(LServer, #offline_msg{} = R) ->
|
||||
El = #xmlel{children = Els} = R#offline_msg.packet,
|
||||
{route, R#offline_msg.from, R#offline_msg.to,
|
||||
El#xmlel{children =
|
||||
Els ++
|
||||
[jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(
|
||||
R#offline_msg.timestamp),
|
||||
utc,
|
||||
jlib:make_jid(<<"">>, LServer, <<"">>),
|
||||
<<"Offline Storage">>),
|
||||
jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(
|
||||
R#offline_msg.timestamp))]}};
|
||||
jlib:add_delay_info(R#offline_msg.packet, LServer, R#offline_msg.timestamp,
|
||||
<<"Offline Storage">>)};
|
||||
offline_msg_to_route(_LServer, #xmlel{} = El) ->
|
||||
To = jlib:string_to_jid(xml:get_tag_attr_s(<<"to">>, El)),
|
||||
From = jlib:string_to_jid(xml:get_tag_attr_s(<<"from">>, El)),
|
||||
@@ -584,6 +688,15 @@ read_all_msgs(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
lists:keysort(#offline_msg.timestamp,
|
||||
mnesia:dirty_read({offline_msg, US}));
|
||||
read_all_msgs(LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get_by_index(
|
||||
offline_msg, offline_msg_schema(),
|
||||
<<"us">>, {LUser, LServer}) of
|
||||
{ok, Rs} ->
|
||||
lists:keysort(#offline_msg.timestamp, Rs);
|
||||
_Err ->
|
||||
[]
|
||||
end;
|
||||
read_all_msgs(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch ejabberd_odbc:sql_query(LServer,
|
||||
@@ -601,7 +714,7 @@ read_all_msgs(LUser, LServer, odbc) ->
|
||||
_ -> []
|
||||
end.
|
||||
|
||||
format_user_queue(Msgs, mnesia) ->
|
||||
format_user_queue(Msgs, DBType) when DBType == mnesia; DBType == riak ->
|
||||
lists:map(fun (#offline_msg{timestamp = TimeStamp,
|
||||
from = From, to = To,
|
||||
packet =
|
||||
@@ -709,6 +822,26 @@ user_queue_parse_query(LUser, LServer, Query, mnesia) ->
|
||||
ok;
|
||||
false -> nothing
|
||||
end;
|
||||
user_queue_parse_query(LUser, LServer, Query, riak) ->
|
||||
case lists:keysearch(<<"delete">>, 1, Query) of
|
||||
{value, _} ->
|
||||
Msgs = read_all_msgs(LUser, LServer, riak),
|
||||
lists:foreach(
|
||||
fun (Msg) ->
|
||||
ID = jlib:encode_base64((term_to_binary(Msg))),
|
||||
case lists:member({<<"selected">>, ID}, Query) of
|
||||
true ->
|
||||
ejabberd_riak:delete(offline_msg,
|
||||
Msg#offline_msg.timestamp);
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end,
|
||||
Msgs),
|
||||
ok;
|
||||
false ->
|
||||
nothing
|
||||
end;
|
||||
user_queue_parse_query(LUser, LServer, Query, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case lists:keysearch(<<"delete">>, 1, Query) of
|
||||
@@ -767,6 +900,14 @@ get_queue_length(LUser, LServer) ->
|
||||
get_queue_length(LUser, LServer, mnesia) ->
|
||||
length(mnesia:dirty_read({offline_msg,
|
||||
{LUser, LServer}}));
|
||||
get_queue_length(LUser, LServer, riak) ->
|
||||
case ejabberd_riak:count_by_index(offline_msg,
|
||||
<<"us">>, {LUser, LServer}) of
|
||||
{ok, N} ->
|
||||
N;
|
||||
_ ->
|
||||
0
|
||||
end;
|
||||
get_queue_length(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch ejabberd_odbc:sql_query(LServer,
|
||||
@@ -795,7 +936,8 @@ get_messages_subset(User, Host, MsgsAll, DBType) ->
|
||||
get_messages_subset2(Max, Length, MsgsAll, _DBType)
|
||||
when Length =< Max * 2 ->
|
||||
MsgsAll;
|
||||
get_messages_subset2(Max, Length, MsgsAll, mnesia) ->
|
||||
get_messages_subset2(Max, Length, MsgsAll, DBType)
|
||||
when DBType == mnesia; DBType == riak ->
|
||||
FirstN = Max,
|
||||
{MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll),
|
||||
MsgsLastN = lists:nthtail(Length - FirstN - FirstN,
|
||||
@@ -843,6 +985,10 @@ delete_all_msgs(LUser, LServer, mnesia) ->
|
||||
mnesia:dirty_read({offline_msg, US}))
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
delete_all_msgs(LUser, LServer, riak) ->
|
||||
Res = ejabberd_riak:delete_by_index(offline_msg,
|
||||
<<"us">>, {LUser, LServer}),
|
||||
{atomic, Res};
|
||||
delete_all_msgs(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_spool_msg(LServer, Username),
|
||||
@@ -865,17 +1011,73 @@ webadmin_user_parse_query(Acc, _Action, _User, _Server,
|
||||
Acc.
|
||||
|
||||
%% Returns as integer the number of offline messages for a given user
|
||||
count_offline_messages(LUser, LServer) ->
|
||||
count_offline_messages(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
count_offline_messages(LUser, LServer, DBType).
|
||||
|
||||
count_offline_messages(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
F = fun () ->
|
||||
count_mnesia_records(US)
|
||||
end,
|
||||
case catch mnesia:async_dirty(F) of
|
||||
I when is_integer(I) -> I;
|
||||
_ -> 0
|
||||
end;
|
||||
count_offline_messages(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:count_records_where(
|
||||
LServer, "spool",
|
||||
<<"where username='", Username/binary, "'">>) of
|
||||
{selected, [_], [[Res]]} ->
|
||||
jlib:binary_to_integer(Res);
|
||||
case catch odbc_queries:count_records_where(LServer,
|
||||
<<"spool">>,
|
||||
<<"where username='",
|
||||
Username/binary, "'">>)
|
||||
of
|
||||
{selected, [_], [[Res]]} ->
|
||||
jlib:binary_to_integer(Res);
|
||||
_ -> 0
|
||||
end;
|
||||
count_offline_messages(LUser, LServer, riak) ->
|
||||
case ejabberd_riak:count_by_index(
|
||||
offline_msg, <<"us">>, {LUser, LServer}) of
|
||||
{ok, Res} ->
|
||||
Res;
|
||||
_ ->
|
||||
0
|
||||
end;
|
||||
count_offline_messages(_Acc, User, Server) ->
|
||||
N = count_offline_messages(User, Server),
|
||||
{stop, N}.
|
||||
|
||||
%% Return the number of records matching a given match expression.
|
||||
%% This function is intended to be used inside a Mnesia transaction.
|
||||
%% The count has been written to use the fewest possible memory by
|
||||
%% getting the record by small increment and by using continuation.
|
||||
-define(BATCHSIZE, 100).
|
||||
|
||||
count_mnesia_records(US) ->
|
||||
MatchExpression = #offline_msg{us = US, _ = '_'},
|
||||
case mnesia:select(offline_msg, [{MatchExpression, [], [[]]}],
|
||||
?BATCHSIZE, read) of
|
||||
{Result, Cont} ->
|
||||
Count = length(Result),
|
||||
count_records_cont(Cont, Count);
|
||||
'$end_of_table' ->
|
||||
0
|
||||
end.
|
||||
|
||||
count_records_cont(Cont, Count) ->
|
||||
case mnesia:select(Cont) of
|
||||
{Result, Cont} ->
|
||||
NewCount = Count + length(Result),
|
||||
count_records_cont(Cont, NewCount);
|
||||
'$end_of_table' ->
|
||||
Count
|
||||
end.
|
||||
|
||||
offline_msg_schema() ->
|
||||
{record_info(fields, offline_msg), #offline_msg{}}.
|
||||
|
||||
export(_Server) ->
|
||||
[{offline_msg,
|
||||
fun(Host, #offline_msg{us = {LUser, LServer},
|
||||
@@ -883,26 +1085,14 @@ export(_Server) ->
|
||||
packet = Packet})
|
||||
when LServer == Host ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
#xmlel{name = Name, attrs = Attrs, children = Els} =
|
||||
Packet,
|
||||
Attrs2 =
|
||||
jlib:replace_from_to_attrs(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To),
|
||||
Attrs),
|
||||
NewPacket = #xmlel{name = Name, attrs = Attrs2,
|
||||
children =
|
||||
Els ++
|
||||
[jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(TimeStamp),
|
||||
utc,
|
||||
jlib:make_jid(<<"">>,
|
||||
LServer,
|
||||
<<"">>),
|
||||
<<"Offline Storage">>),
|
||||
jlib:timestamp_to_xml(
|
||||
calendar:now_to_universal_time(TimeStamp))]},
|
||||
Packet1 =
|
||||
jlib:replace_from_to(jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To), Packet),
|
||||
Packet2 =
|
||||
jlib:add_delay_info(Packet1, LServer, TimeStamp,
|
||||
<<"Offline Storage">>),
|
||||
XML =
|
||||
ejabberd_odbc:escape(xml:element_to_binary(NewPacket)),
|
||||
ejabberd_odbc:escape(xml:element_to_binary(Packet2)),
|
||||
[[<<"delete from spool where username='">>, Username, <<"';">>],
|
||||
[<<"insert into spool(username, xml) values ('">>,
|
||||
Username, <<"', '">>, XML, <<"');">>]];
|
||||
@@ -934,5 +1124,8 @@ import(LServer) ->
|
||||
|
||||
import(_LServer, mnesia, #offline_msg{} = Msg) ->
|
||||
mnesia:dirty_write(Msg);
|
||||
import(_LServer, riak, #offline_msg{us = US, timestamp = TS} = M) ->
|
||||
ejabberd_riak:put(M, offline_msg_schema(),
|
||||
[{i, TS}, {'2i', [{<<"us">>, US}]}]);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+116
-1
@@ -43,7 +43,7 @@
|
||||
sql_get_privacy_list_data_by_id_t/1,
|
||||
sql_get_privacy_list_id_t/2,
|
||||
sql_set_default_privacy_list/2,
|
||||
sql_set_privacy_list/2]).
|
||||
sql_set_privacy_list/2, privacy_schema/0]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
@@ -52,6 +52,9 @@
|
||||
|
||||
-include("mod_privacy.hrl").
|
||||
|
||||
privacy_schema() ->
|
||||
{record_info(fields, privacy), #privacy{}}.
|
||||
|
||||
start(Host, Opts) ->
|
||||
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
|
||||
one_queue),
|
||||
@@ -160,6 +163,21 @@ process_lists_get(LUser, LServer, _Active, mnesia) ->
|
||||
Lists),
|
||||
{Default, LItems}
|
||||
end;
|
||||
process_lists_get(LUser, LServer, _Active, riak) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists}} ->
|
||||
LItems = lists:map(fun ({N, _}) ->
|
||||
#xmlel{name = <<"list">>,
|
||||
attrs = [{<<"name">>, N}],
|
||||
children = []}
|
||||
end,
|
||||
Lists),
|
||||
{Default, LItems};
|
||||
{error, notfound} ->
|
||||
{none, []};
|
||||
{error, _} ->
|
||||
error
|
||||
end;
|
||||
process_lists_get(LUser, LServer, _Active, odbc) ->
|
||||
Default = case catch sql_get_default_privacy_list(LUser,
|
||||
LServer)
|
||||
@@ -209,6 +227,18 @@ process_list_get(LUser, LServer, Name, mnesia) ->
|
||||
_ -> not_found
|
||||
end
|
||||
end;
|
||||
process_list_get(LUser, LServer, Name, riak) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{lists = Lists}} ->
|
||||
case lists:keysearch(Name, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
_ -> not_found
|
||||
end;
|
||||
{error, notfound} ->
|
||||
not_found;
|
||||
{error, _} ->
|
||||
error
|
||||
end;
|
||||
process_list_get(LUser, LServer, Name, odbc) ->
|
||||
case catch sql_get_privacy_list_id(LUser, LServer, Name)
|
||||
of
|
||||
@@ -354,6 +384,21 @@ process_default_set(LUser, LServer, {value, Name},
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_default_set(LUser, LServer, {value, Name}, riak) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{lists = Lists} = P} ->
|
||||
case lists:keymember(Name, 1, Lists) of
|
||||
true ->
|
||||
ejabberd_riak:put(P#privacy{default = Name,
|
||||
lists = Lists},
|
||||
privacy_schema());
|
||||
false ->
|
||||
not_found
|
||||
end;
|
||||
{error, _} ->
|
||||
not_found
|
||||
end};
|
||||
process_default_set(LUser, LServer, {value, Name},
|
||||
odbc) ->
|
||||
F = fun () ->
|
||||
@@ -375,6 +420,14 @@ process_default_set(LUser, LServer, false, mnesia) ->
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
process_default_set(LUser, LServer, false, riak) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, R} ->
|
||||
ejabberd_riak:put(R#privacy{default = none}, privacy_schema());
|
||||
{error, _} ->
|
||||
ok
|
||||
end};
|
||||
process_default_set(LUser, LServer, false, odbc) ->
|
||||
case catch sql_unset_default_privacy_list(LUser,
|
||||
LServer)
|
||||
@@ -407,6 +460,16 @@ process_active_set(LUser, LServer, Name, mnesia) ->
|
||||
false -> error
|
||||
end
|
||||
end;
|
||||
process_active_set(LUser, LServer, Name, riak) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{lists = Lists}} ->
|
||||
case lists:keysearch(Name, 1, Lists) of
|
||||
{value, {_, List}} -> List;
|
||||
false -> error
|
||||
end;
|
||||
{error, _} ->
|
||||
error
|
||||
end;
|
||||
process_active_set(LUser, LServer, Name, odbc) ->
|
||||
case catch sql_get_privacy_list_id(LUser, LServer, Name)
|
||||
of
|
||||
@@ -438,6 +501,20 @@ remove_privacy_list(LUser, LServer, Name, mnesia) ->
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
remove_privacy_list(LUser, LServer, Name, riak) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists} = P} ->
|
||||
if Name == Default ->
|
||||
conflict;
|
||||
true ->
|
||||
NewLists = lists:keydelete(Name, 1, Lists),
|
||||
ejabberd_riak:put(P#privacy{lists = NewLists},
|
||||
privacy_schema())
|
||||
end;
|
||||
{error, _} ->
|
||||
ok
|
||||
end};
|
||||
remove_privacy_list(LUser, LServer, Name, odbc) ->
|
||||
F = fun () ->
|
||||
case sql_get_default_privacy_list_t(LUser) of
|
||||
@@ -465,6 +542,19 @@ set_privacy_list(LUser, LServer, Name, List, mnesia) ->
|
||||
end
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
set_privacy_list(LUser, LServer, Name, List, riak) ->
|
||||
{atomic,
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{lists = Lists} = P} ->
|
||||
NewLists1 = lists:keydelete(Name, 1, Lists),
|
||||
NewLists = [{Name, List} | NewLists1],
|
||||
ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema());
|
||||
{error, _} ->
|
||||
NewLists = [{Name, List}],
|
||||
ejabberd_riak:put(#privacy{us = {LUser, LServer},
|
||||
lists = NewLists},
|
||||
privacy_schema())
|
||||
end};
|
||||
set_privacy_list(LUser, LServer, Name, List, odbc) ->
|
||||
RItems = lists:map(fun item_to_raw/1, List),
|
||||
F = fun () ->
|
||||
@@ -649,6 +739,20 @@ get_user_list(_, LUser, LServer, mnesia) ->
|
||||
end;
|
||||
_ -> {none, []}
|
||||
end;
|
||||
get_user_list(_, LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{default = Default, lists = Lists}} ->
|
||||
case Default of
|
||||
none -> {none, []};
|
||||
_ ->
|
||||
case lists:keysearch(Default, 1, Lists) of
|
||||
{value, {_, List}} -> {Default, List};
|
||||
_ -> {none, []}
|
||||
end
|
||||
end;
|
||||
{error, _} ->
|
||||
{none, []}
|
||||
end;
|
||||
get_user_list(_, LUser, LServer, odbc) ->
|
||||
case catch sql_get_default_privacy_list(LUser, LServer)
|
||||
of
|
||||
@@ -680,6 +784,13 @@ get_user_lists(LUser, LServer, mnesia) ->
|
||||
_ ->
|
||||
error
|
||||
end;
|
||||
get_user_lists(LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of
|
||||
{ok, #privacy{} = P} ->
|
||||
{ok, P};
|
||||
{error, _} ->
|
||||
error
|
||||
end;
|
||||
get_user_lists(LUser, LServer, odbc) ->
|
||||
Default = case catch sql_get_default_privacy_list(LUser, LServer) of
|
||||
{selected, [<<"name">>], []} ->
|
||||
@@ -843,6 +954,8 @@ remove_user(LUser, LServer, mnesia) ->
|
||||
F = fun () -> mnesia:delete({privacy, {LUser, LServer}})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, riak) ->
|
||||
{atomic, ejabberd_riak:delete(privacy, {LUser, LServer})};
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
sql_del_privacy_lists(LUser, LServer).
|
||||
|
||||
@@ -1134,5 +1247,7 @@ import(LServer) ->
|
||||
|
||||
import(_LServer, mnesia, #privacy{} = P) ->
|
||||
mnesia:dirty_write(P);
|
||||
import(_LServer, riak, #privacy{} = P) ->
|
||||
ejabberd_riak:put(P, privacy_schema());
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+37
-7
@@ -89,7 +89,8 @@ process_sm_iq(#jid{luser = LUser, lserver = LServer},
|
||||
end,
|
||||
case DBType of
|
||||
odbc -> ejabberd_odbc:sql_transaction(LServer, F);
|
||||
mnesia -> mnesia:transaction(F)
|
||||
mnesia -> mnesia:transaction(F);
|
||||
riak -> F()
|
||||
end,
|
||||
IQ#iq{type = result, sub_el = []}
|
||||
end;
|
||||
@@ -149,7 +150,12 @@ set_data(LUser, LServer, {XMLNS, El}, odbc) ->
|
||||
LXMLNS = ejabberd_odbc:escape(XMLNS),
|
||||
SData = ejabberd_odbc:escape(xml:element_to_binary(El)),
|
||||
odbc_queries:set_private_data(LServer, Username, LXMLNS,
|
||||
SData).
|
||||
SData);
|
||||
set_data(LUser, LServer, {XMLNS, El}, riak) ->
|
||||
ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS},
|
||||
xml = El},
|
||||
private_storage_schema(),
|
||||
[{'2i', [{<<"us">>, {LUser, LServer}}]}]).
|
||||
|
||||
get_data(LUser, LServer, Data) ->
|
||||
get_data(LUser, LServer,
|
||||
@@ -182,13 +188,18 @@ get_data(LUser, LServer, odbc, [{XMLNS, El} | Els],
|
||||
Data when is_record(Data, xmlel) ->
|
||||
get_data(LUser, LServer, odbc, Els, [Data | Res])
|
||||
end;
|
||||
%% MREMOND: I wonder when the query could return a vcard ?
|
||||
{selected, [<<"vcard">>], []} ->
|
||||
get_data(LUser, LServer, odbc, Els, [El | Res]);
|
||||
_ -> get_data(LUser, LServer, odbc, Els, [El | Res])
|
||||
end;
|
||||
get_data(LUser, LServer, riak, [{XMLNS, El} | Els],
|
||||
Res) ->
|
||||
case ejabberd_riak:get(private_storage, private_storage_schema(),
|
||||
{LUser, LServer, XMLNS}) of
|
||||
{ok, #private_storage{xml = NewEl}} ->
|
||||
get_data(LUser, LServer, riak, Els, [NewEl|Res]);
|
||||
_ ->
|
||||
get_data(LUser, LServer, riak, Els, [El|Res])
|
||||
end.
|
||||
|
||||
|
||||
get_data(LUser, LServer) ->
|
||||
get_all_data(LUser, LServer,
|
||||
gen_mod:db_type(LServer, ?MODULE)).
|
||||
@@ -214,8 +225,20 @@ get_all_data(LUser, LServer, odbc) ->
|
||||
end, Res);
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
get_all_data(LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get_by_index(
|
||||
private_storage, private_storage_schema(),
|
||||
<<"us">>, {LUser, LServer}) of
|
||||
{ok, Res} ->
|
||||
[El || #private_storage{xml = El} <- Res];
|
||||
_ ->
|
||||
[]
|
||||
end.
|
||||
|
||||
private_storage_schema() ->
|
||||
{record_info(fields, private_storage), #private_storage{}}.
|
||||
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
@@ -242,7 +265,10 @@ remove_user(LUser, LServer, mnesia) ->
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
odbc_queries:del_user_private_storage(LServer,
|
||||
Username).
|
||||
Username);
|
||||
remove_user(LUser, LServer, riak) ->
|
||||
{atomic, ejabberd_riak:delete_by_index(private_storage,
|
||||
<<"us">>, {LUser, LServer})}.
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, private_storage),
|
||||
@@ -287,5 +313,9 @@ import(LServer) ->
|
||||
|
||||
import(_LServer, mnesia, #private_storage{} = PS) ->
|
||||
mnesia:dirty_write(PS);
|
||||
|
||||
import(_LServer, riak, #private_storage{usns = {LUser, LServer, _}} = PS) ->
|
||||
ejabberd_riak:put(PS, private_storage_schema(),
|
||||
[{'2i', [{<<"us">>, {LUser, LServer}}]}]);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+82
-51
@@ -74,7 +74,8 @@
|
||||
on_user_offline/3, remove_user/2,
|
||||
disco_local_identity/5, disco_local_features/5,
|
||||
disco_local_items/5, disco_sm_identity/5,
|
||||
disco_sm_features/5, disco_sm_items/5]).
|
||||
disco_sm_features/5, disco_sm_items/5,
|
||||
drop_pep_error/4]).
|
||||
|
||||
%% exported iq handlers
|
||||
-export([iq_sm/3]).
|
||||
@@ -344,6 +345,8 @@ init([ServerHost, Opts]) ->
|
||||
?MODULE, disco_sm_features, 75),
|
||||
ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE,
|
||||
disco_sm_items, 75),
|
||||
ejabberd_hooks:add(c2s_filter_packet_in, ServerHost, ?MODULE,
|
||||
drop_pep_error, 75),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
|
||||
?NS_PUBSUB, ?MODULE, iq_sm, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
|
||||
@@ -387,7 +390,7 @@ init_send_loop(ServerHost, State) ->
|
||||
init_plugins(Host, ServerHost, Opts) ->
|
||||
TreePlugin =
|
||||
jlib:binary_to_atom(<<(?TREE_PREFIX)/binary,
|
||||
(gen_mod:get_opt(nodetree, Opts, fun(A) when is_list(A) -> A end,
|
||||
(gen_mod:get_opt(nodetree, Opts, fun(A) when is_binary(A) -> A end,
|
||||
?STDTREE))/binary>>),
|
||||
?DEBUG("** tree plugin is ~p", [TreePlugin]),
|
||||
TreePlugin:init(Host, ServerHost, Opts),
|
||||
@@ -690,9 +693,9 @@ update_node_database(Host, ServerHost) ->
|
||||
end,
|
||||
mnesia:transaction(fun () ->
|
||||
case catch mnesia:first(pubsub_node) of
|
||||
{_, L} when is_binary(L) ->
|
||||
{_, L} when is_list(L) ->
|
||||
lists:foreach(fun ({H, N})
|
||||
when is_binary(N) ->
|
||||
when is_list(N) ->
|
||||
[Node] =
|
||||
mnesia:read({pubsub_node,
|
||||
{H,
|
||||
@@ -1179,8 +1182,12 @@ presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid)
|
||||
%% ignore presence_probe from my other ressources
|
||||
%% to not get duplicated last items
|
||||
ok;
|
||||
presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Pid) ->
|
||||
presence(Host, {presence, U, S, [R], JID}).
|
||||
presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = S} = JID, _Pid) ->
|
||||
presence(S, {presence, U, S, [R], JID});
|
||||
presence_probe(_Host, _JID, _Pid) ->
|
||||
%% ignore presence_probe from remote contacts,
|
||||
%% those are handled via caps_update
|
||||
ok.
|
||||
|
||||
presence(ServerHost, Presence) ->
|
||||
SendLoop = case
|
||||
@@ -1278,6 +1285,33 @@ unsubscribe_user(Entity, Owner) ->
|
||||
plugins(Host))
|
||||
end).
|
||||
|
||||
%% -------
|
||||
%% packet receive hook handling function
|
||||
%%
|
||||
|
||||
drop_pep_error(#xmlel{name = <<"message">>, attrs = Attrs} = Packet, _JID, From,
|
||||
#jid{lresource = <<"">>} = To) ->
|
||||
case xml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"error">> ->
|
||||
case xml:get_subtag(Packet, <<"event">>) of
|
||||
#xmlel{attrs = EventAttrs} ->
|
||||
case xml:get_attr_s(<<"xmlns">>, EventAttrs) of
|
||||
?NS_PUBSUB_EVENT ->
|
||||
?DEBUG("Dropping PEP error message from ~s to ~s",
|
||||
[jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To)]),
|
||||
drop;
|
||||
_ ->
|
||||
Packet
|
||||
end;
|
||||
false ->
|
||||
Packet
|
||||
end;
|
||||
_ ->
|
||||
Packet
|
||||
end;
|
||||
drop_pep_error(Acc, _JID, _From, _To) -> Acc.
|
||||
|
||||
%% -------
|
||||
%% user remove hook handling function
|
||||
%%
|
||||
@@ -1418,6 +1452,8 @@ terminate(_Reason,
|
||||
?MODULE, disco_sm_features, 75),
|
||||
ejabberd_hooks:delete(disco_sm_items, ServerHost,
|
||||
?MODULE, disco_sm_items, 75),
|
||||
ejabberd_hooks:delete(c2s_filter_packet_in, ServerHost,
|
||||
?MODULE, drop_pep_error, 75),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm,
|
||||
ServerHost, ?NS_PUBSUB),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm,
|
||||
@@ -3312,7 +3348,7 @@ get_allowed_items_call(Host, NodeIdx, From, Type, Options, Owners) ->
|
||||
%% Number = last | integer()
|
||||
%% @doc <p>Resend the items of a node to the user.</p>
|
||||
%% @todo use cache-last-item feature
|
||||
send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, last) ->
|
||||
send_items(Host, Node, NodeId, Type, LJID, last) ->
|
||||
case get_cached_item(Host, NodeId) of
|
||||
undefined ->
|
||||
send_items(Host, Node, NodeId, Type, LJID, 1);
|
||||
@@ -3325,24 +3361,9 @@ send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, last) ->
|
||||
children =
|
||||
itemsEls([LastItem])}],
|
||||
ModifNow, ModifUSR),
|
||||
case is_tuple(Host) of
|
||||
false ->
|
||||
ejabberd_router:route(service_jid(Host),
|
||||
jlib:make_jid(LJID), Stanza);
|
||||
true ->
|
||||
case ejabberd_sm:get_session_pid(U, S, R) of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
ejabberd_c2s:broadcast(C2SPid,
|
||||
{pep_message,
|
||||
<<((Node))/binary, "+notify">>},
|
||||
_Sender = service_jid(Host),
|
||||
Stanza);
|
||||
_ -> ok
|
||||
end
|
||||
end
|
||||
dispatch_items(Host, LJID, Node, Stanza)
|
||||
end;
|
||||
send_items(Host, Node, NodeId, Type, {U, S, R} = LJID,
|
||||
Number) ->
|
||||
send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
ToSend = case node_action(Host, Type, get_items,
|
||||
[NodeId, LJID])
|
||||
of
|
||||
@@ -3370,22 +3391,38 @@ send_items(Host, Node, NodeId, Type, {U, S, R} = LJID,
|
||||
attrs = nodeAttr(Node),
|
||||
children = itemsEls(ToSend)}])
|
||||
end,
|
||||
case {is_tuple(Host), Stanza} of
|
||||
{_, undefined} ->
|
||||
ok;
|
||||
{false, _} ->
|
||||
ejabberd_router:route(service_jid(Host),
|
||||
jlib:make_jid(LJID), Stanza);
|
||||
{true, _} ->
|
||||
case ejabberd_sm:get_session_pid(U, S, R) of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
ejabberd_c2s:broadcast(C2SPid,
|
||||
{pep_message,
|
||||
<<((Node))/binary, "+notify">>},
|
||||
_Sender = service_jid(Host), Stanza);
|
||||
_ -> ok
|
||||
end
|
||||
end.
|
||||
dispatch_items(Host, LJID, Node, Stanza).
|
||||
|
||||
-spec(dispatch_items/4 ::
|
||||
(
|
||||
From :: mod_pubsub:host(),
|
||||
To :: jid(),
|
||||
Node :: mod_pubsub:nodeId(),
|
||||
Stanza :: xmlel() | undefined)
|
||||
-> any()
|
||||
).
|
||||
|
||||
dispatch_items(_From, _To, _Node, _Stanza = undefined) -> ok;
|
||||
dispatch_items({FromU, FromS, FromR} = From, {ToU, ToS, ToR} = To, Node,
|
||||
Stanza) ->
|
||||
C2SPid = case ejabberd_sm:get_session_pid(ToU, ToS, ToR) of
|
||||
ToPid when is_pid(ToPid) -> ToPid;
|
||||
_ ->
|
||||
R = user_resource(FromU, FromS, FromR),
|
||||
case ejabberd_sm:get_session_pid(FromU, FromS, R) of
|
||||
FromPid when is_pid(FromPid) -> FromPid;
|
||||
_ -> undefined
|
||||
end
|
||||
end,
|
||||
if C2SPid == undefined -> ok;
|
||||
true ->
|
||||
ejabberd_c2s:send_filtered(C2SPid,
|
||||
{pep_message, <<Node/binary, "+notify">>},
|
||||
service_jid(From), jlib:make_jid(To),
|
||||
Stanza)
|
||||
end;
|
||||
dispatch_items(From, To, _Node, Stanza) ->
|
||||
ejabberd_router:route(service_jid(From), jlib:make_jid(To), Stanza).
|
||||
|
||||
%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response}
|
||||
%% Host = host()
|
||||
@@ -4238,21 +4275,15 @@ payload_xmlelements([_ | Tail], Count) ->
|
||||
%% @spec (Els) -> stanza()
|
||||
%% Els = [xmlelement()]
|
||||
%% @doc <p>Build pubsub event stanza</p>
|
||||
event_stanza(Els) -> event_stanza_withmoreels(Els, []).
|
||||
|
||||
event_stanza_with_delay(Els, ModifNow, ModifUSR) ->
|
||||
DateTime = calendar:now_to_datetime(ModifNow),
|
||||
MoreEls = [jlib:timestamp_to_xml(DateTime, utc,
|
||||
ModifUSR, <<"">>)],
|
||||
event_stanza_withmoreels(Els, MoreEls).
|
||||
|
||||
event_stanza_withmoreels(Els, MoreEls) ->
|
||||
event_stanza(Els) ->
|
||||
#xmlel{name = <<"message">>, attrs = [],
|
||||
children =
|
||||
[#xmlel{name = <<"event">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}],
|
||||
children = Els}
|
||||
| MoreEls]}.
|
||||
children = Els}]}.
|
||||
|
||||
event_stanza_with_delay(Els, ModifNow, ModifUSR) ->
|
||||
jlib:add_delay_info(event_stanza(Els), ModifUSR, ModifNow).
|
||||
|
||||
%%%%%% broadcast functions
|
||||
|
||||
|
||||
+80
-34
@@ -74,7 +74,8 @@
|
||||
on_user_offline/3, remove_user/2,
|
||||
disco_local_identity/5, disco_local_features/5,
|
||||
disco_local_items/5, disco_sm_identity/5,
|
||||
disco_sm_features/5, disco_sm_items/5]).
|
||||
disco_sm_features/5, disco_sm_items/5,
|
||||
drop_pep_error/4]).
|
||||
|
||||
%% exported iq handlers
|
||||
-export([iq_sm/3]).
|
||||
@@ -344,6 +345,8 @@ init([ServerHost, Opts]) ->
|
||||
?MODULE, disco_sm_features, 75),
|
||||
ejabberd_hooks:add(disco_sm_items, ServerHost, ?MODULE,
|
||||
disco_sm_items, 75),
|
||||
ejabberd_hooks:add(c2s_filter_packet_in, ServerHost, ?MODULE,
|
||||
drop_pep_error, 75),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
|
||||
?NS_PUBSUB, ?MODULE, iq_sm, IQDisc),
|
||||
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost,
|
||||
@@ -385,7 +388,7 @@ init_send_loop(ServerHost, State) ->
|
||||
init_plugins(Host, ServerHost, Opts) ->
|
||||
TreePlugin =
|
||||
jlib:binary_to_atom(<<(?TREE_PREFIX)/binary,
|
||||
(gen_mod:get_opt(nodetree, Opts, fun(A) when is_list(A) -> A end,
|
||||
(gen_mod:get_opt(nodetree, Opts, fun(A) when is_binary(A) -> A end,
|
||||
?STDTREE))/binary,
|
||||
(?ODBC_SUFFIX)/binary>>),
|
||||
?DEBUG("** tree plugin is ~p", [TreePlugin]),
|
||||
@@ -830,8 +833,12 @@ presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid)
|
||||
%% ignore presence_probe from my other ressources
|
||||
%% to not get duplicated last items
|
||||
ok;
|
||||
presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Pid) ->
|
||||
presence(Host, {presence, U, S, [R], JID}).
|
||||
presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = S} = JID, _Pid) ->
|
||||
presence(S, {presence, U, S, [R], JID});
|
||||
presence_probe(_Host, _JID, _Pid) ->
|
||||
%% ignore presence_probe from remote contacts,
|
||||
%% those are handled via caps_update
|
||||
ok.
|
||||
|
||||
presence(ServerHost, Presence) ->
|
||||
SendLoop = case
|
||||
@@ -929,6 +936,33 @@ unsubscribe_user(Entity, Owner) ->
|
||||
plugins(Host))
|
||||
end).
|
||||
|
||||
%% -------
|
||||
%% packet receive hook handling function
|
||||
%%
|
||||
|
||||
drop_pep_error(#xmlel{name = <<"message">>, attrs = Attrs} = Packet, _JID, From,
|
||||
#jid{lresource = <<"">>} = To) ->
|
||||
case xml:get_attr_s(<<"type">>, Attrs) of
|
||||
<<"error">> ->
|
||||
case xml:get_subtag(Packet, <<"event">>) of
|
||||
#xmlel{attrs = EventAttrs} ->
|
||||
case xml:get_attr_s(<<"xmlns">>, EventAttrs) of
|
||||
?NS_PUBSUB_EVENT ->
|
||||
?DEBUG("Dropping PEP error message from ~s to ~s",
|
||||
[jlib:jid_to_string(From),
|
||||
jlib:jid_to_string(To)]),
|
||||
drop;
|
||||
_ ->
|
||||
Packet
|
||||
end;
|
||||
false ->
|
||||
Packet
|
||||
end;
|
||||
_ ->
|
||||
Packet
|
||||
end;
|
||||
drop_pep_error(Acc, _JID, _From, _To) -> Acc.
|
||||
|
||||
%% -------
|
||||
%% user remove hook handling function
|
||||
%%
|
||||
@@ -1069,6 +1103,8 @@ terminate(_Reason,
|
||||
?MODULE, disco_sm_features, 75),
|
||||
ejabberd_hooks:delete(disco_sm_items, ServerHost,
|
||||
?MODULE, disco_sm_items, 75),
|
||||
ejabberd_hooks:delete(c2s_filter_packet_in, ServerHost,
|
||||
?MODULE, drop_pep_error, 75),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm,
|
||||
ServerHost, ?NS_PUBSUB),
|
||||
gen_iq_handler:remove_iq_handler(ejabberd_sm,
|
||||
@@ -2315,7 +2351,7 @@ create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) ->
|
||||
{result, Reply};
|
||||
{result, {NodeId, _SubsByDepth, Result}} ->
|
||||
ejabberd_hooks:run(pubsub_create_node, ServerHost, [ServerHost, Host, Node, NodeId, NodeOptions]),
|
||||
{result, Result};
|
||||
{result, Reply};
|
||||
Error ->
|
||||
%% in case we change transaction to sync_dirty...
|
||||
%% node_call(Type, delete_node, [Host, Node]),
|
||||
@@ -3011,8 +3047,8 @@ send_items(Host, Node, NodeId, Type, LJID, last) ->
|
||||
itemsEls([LastItem])}],
|
||||
ModifNow, ModifUSR)
|
||||
end,
|
||||
ejabberd_router:route(service_jid(Host), jlib:make_jid(LJID), Stanza);
|
||||
send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, Number) ->
|
||||
dispatch_items(Host, LJID, Node, Stanza);
|
||||
send_items(Host, Node, NodeId, Type, LJID, Number) ->
|
||||
ToSend = case node_action(Host, Type, get_items,
|
||||
[NodeId, LJID])
|
||||
of
|
||||
@@ -3040,22 +3076,38 @@ send_items(Host, Node, NodeId, Type, {U, S, R} = LJID, Number) ->
|
||||
attrs = nodeAttr(Node),
|
||||
children = itemsEls(ToSend)}])
|
||||
end,
|
||||
case {is_tuple(Host), Stanza} of
|
||||
{_, undefined} ->
|
||||
ok;
|
||||
{false, _} ->
|
||||
ejabberd_router:route(service_jid(Host),
|
||||
jlib:make_jid(LJID), Stanza);
|
||||
{true, _} ->
|
||||
case ejabberd_sm:get_session_pid(U, S, R) of
|
||||
C2SPid when is_pid(C2SPid) ->
|
||||
ejabberd_c2s:broadcast(C2SPid,
|
||||
{pep_message,
|
||||
<<((Node))/binary, "+notify">>},
|
||||
_Sender = service_jid(Host), Stanza);
|
||||
_ -> ok
|
||||
end
|
||||
end.
|
||||
dispatch_items(Host, LJID, Node, Stanza).
|
||||
|
||||
-spec(dispatch_items/4 ::
|
||||
(
|
||||
From :: mod_pubsub:host(),
|
||||
To :: jid(),
|
||||
Node :: mod_pubsub:nodeId(),
|
||||
Stanza :: xmlel() | undefined)
|
||||
-> any()
|
||||
).
|
||||
|
||||
dispatch_items(_From, _To, _Node, _Stanza = undefined) -> ok;
|
||||
dispatch_items({FromU, FromS, FromR} = From, {ToU, ToS, ToR} = To, Node,
|
||||
Stanza) ->
|
||||
C2SPid = case ejabberd_sm:get_session_pid(ToU, ToS, ToR) of
|
||||
ToPid when is_pid(ToPid) -> ToPid;
|
||||
_ ->
|
||||
R = user_resource(FromU, FromS, FromR),
|
||||
case ejabberd_sm:get_session_pid(FromU, FromS, R) of
|
||||
FromPid when is_pid(FromPid) -> FromPid;
|
||||
_ -> undefined
|
||||
end
|
||||
end,
|
||||
if C2SPid == undefined -> ok;
|
||||
true ->
|
||||
ejabberd_c2s:send_filtered(C2SPid,
|
||||
{pep_message, <<Node/binary, "+notify">>},
|
||||
service_jid(From), jlib:make_jid(To),
|
||||
Stanza)
|
||||
end;
|
||||
dispatch_items(From, To, _Node, Stanza) ->
|
||||
ejabberd_router:route(service_jid(From), jlib:make_jid(To), Stanza).
|
||||
|
||||
%% @spec (Host, JID, Plugins) -> {error, Reason} | {result, Response}
|
||||
%% Host = host()
|
||||
@@ -3873,21 +3925,15 @@ payload_xmlelements([_ | Tail], Count) ->
|
||||
%% @spec (Els) -> stanza()
|
||||
%% Els = [xmlelement()]
|
||||
%% @doc <p>Build pubsub event stanza</p>
|
||||
event_stanza(Els) -> event_stanza_withmoreels(Els, []).
|
||||
|
||||
event_stanza_with_delay(Els, ModifNow, ModifUSR) ->
|
||||
DateTime = calendar:now_to_datetime(ModifNow),
|
||||
MoreEls = [jlib:timestamp_to_xml(DateTime, utc,
|
||||
ModifUSR, <<"">>)],
|
||||
event_stanza_withmoreels(Els, MoreEls).
|
||||
|
||||
event_stanza_withmoreels(Els, MoreEls) ->
|
||||
event_stanza(Els) ->
|
||||
#xmlel{name = <<"message">>, attrs = [],
|
||||
children =
|
||||
[#xmlel{name = <<"event">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}],
|
||||
children = Els}
|
||||
| MoreEls]}.
|
||||
children = Els}]}.
|
||||
|
||||
event_stanza_with_delay(Els, ModifNow, ModifUSR) ->
|
||||
jlib:add_delay_info(event_stanza(Els), ModifUSR, ModifNow).
|
||||
|
||||
%%%%%% broadcast functions
|
||||
|
||||
|
||||
+114
-44
@@ -204,6 +204,12 @@ read_roster_version(LUser, LServer, odbc) ->
|
||||
of
|
||||
{selected, [<<"version">>], [[Version]]} -> Version;
|
||||
{selected, [<<"version">>], []} -> error
|
||||
end;
|
||||
read_roster_version(LServer, LUser, riak) ->
|
||||
case ejabberd_riak:get(roster_version, roster_version_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #roster_version{version = V}} -> V;
|
||||
_Err -> error
|
||||
end.
|
||||
|
||||
write_roster_version(LUser, LServer) ->
|
||||
@@ -239,7 +245,12 @@ write_roster_version(LUser, LServer, InTransaction, Ver,
|
||||
odbc_queries:set_roster_version(Username,
|
||||
EVer)
|
||||
end)
|
||||
end.
|
||||
end;
|
||||
write_roster_version(LUser, LServer, _InTransaction, Ver,
|
||||
riak) ->
|
||||
US = {LUser, LServer},
|
||||
ejabberd_riak:put(#roster_version{us = US, version = Ver},
|
||||
roster_version_schema()).
|
||||
|
||||
%% Load roster from DB only if neccesary.
|
||||
%% It is neccesary if
|
||||
@@ -347,6 +358,12 @@ get_roster(LUser, LServer, mnesia) ->
|
||||
Items when is_list(Items)-> Items;
|
||||
_ -> []
|
||||
end;
|
||||
get_roster(LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get_by_index(roster, roster_schema(),
|
||||
<<"us">>, {LUser, LServer}) of
|
||||
{ok, Items} -> Items;
|
||||
_Err -> []
|
||||
end;
|
||||
get_roster(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case catch odbc_queries:get_roster(LServer, Username) of
|
||||
@@ -455,6 +472,17 @@ get_roster_by_jid_t(LUser, LServer, LJID, odbc) ->
|
||||
R#roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer}, jid = LJID, name = <<"">>}
|
||||
end
|
||||
end;
|
||||
get_roster_by_jid_t(LUser, LServer, LJID, riak) ->
|
||||
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
|
||||
{ok, I} ->
|
||||
I#roster{jid = LJID, name = <<"">>, groups = [],
|
||||
xs = []};
|
||||
{error, notfound} ->
|
||||
#roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer}, jid = LJID};
|
||||
Err ->
|
||||
exit(Err)
|
||||
end.
|
||||
|
||||
try_process_iq_set(From, To, #iq{sub_el = SubEl} = IQ) ->
|
||||
@@ -631,8 +659,14 @@ get_subscription_lists(_, LUser, LServer, odbc) ->
|
||||
<<"server">>, <<"subscribe">>, <<"type">>],
|
||||
Items}
|
||||
when is_list(Items) ->
|
||||
Items;
|
||||
lists:map(fun(I) -> raw_to_record(LServer, I) end, Items);
|
||||
_ -> []
|
||||
end;
|
||||
get_subscription_lists(_, LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get_by_index(roster, roster_schema(),
|
||||
<<"us">>, {LUser, LServer}) of
|
||||
{ok, Items} -> Items;
|
||||
_Err -> []
|
||||
end.
|
||||
|
||||
fill_subscription_lists(LServer, [#roster{} = I | Is],
|
||||
@@ -671,12 +705,16 @@ roster_subscribe_t(LUser, LServer, LJID, Item, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
odbc_queries:roster_subscribe(LServer, Username, SJID,
|
||||
ItemVals).
|
||||
ItemVals);
|
||||
roster_subscribe_t(LUser, LServer, _LJID, Item, riak) ->
|
||||
ejabberd_riak:put(Item, roster_schema(),
|
||||
[{'2i', [{<<"us">>, {LUser, LServer}}]}]).
|
||||
|
||||
transaction(LServer, F) ->
|
||||
case gen_mod:db_type(LServer, ?MODULE) of
|
||||
mnesia -> mnesia:transaction(F);
|
||||
odbc -> ejabberd_odbc:sql_transaction(LServer, F)
|
||||
odbc -> ejabberd_odbc:sql_transaction(LServer, F);
|
||||
riak -> {atomic, F()}
|
||||
end.
|
||||
|
||||
in_subscription(_, User, Server, JID, Type, Reason) ->
|
||||
@@ -727,6 +765,16 @@ get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
|
||||
[]} ->
|
||||
#roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer}, jid = LJID}
|
||||
end;
|
||||
get_roster_by_jid_with_groups_t(LUser, LServer, LJID, riak) ->
|
||||
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
|
||||
{ok, I} ->
|
||||
I;
|
||||
{error, notfound} ->
|
||||
#roster{usj = {LUser, LServer, LJID},
|
||||
us = {LUser, LServer}, jid = LJID};
|
||||
Err ->
|
||||
exit(Err)
|
||||
end.
|
||||
|
||||
process_subscription(Direction, User, Server, JID1,
|
||||
@@ -924,12 +972,12 @@ in_auto_reply(_, _, _) -> none.
|
||||
remove_user(User, Server) ->
|
||||
LUser = jlib:nodeprep(User),
|
||||
LServer = jlib:nameprep(Server),
|
||||
send_unsubscription_to_rosteritems(LUser, LServer),
|
||||
remove_user(LUser, LServer,
|
||||
gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
remove_user(LUser, LServer, mnesia) ->
|
||||
US = {LUser, LServer},
|
||||
send_unsubscription_to_rosteritems(LUser, LServer),
|
||||
F = fun () ->
|
||||
lists:foreach(fun (R) -> mnesia:delete_object(R) end,
|
||||
mnesia:index_read(roster, US, #roster.us))
|
||||
@@ -937,9 +985,10 @@ remove_user(LUser, LServer, mnesia) ->
|
||||
mnesia:transaction(F);
|
||||
remove_user(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
send_unsubscription_to_rosteritems(LUser, LServer),
|
||||
odbc_queries:del_user_roster_t(LServer, Username),
|
||||
ok.
|
||||
ok;
|
||||
remove_user(LUser, LServer, riak) ->
|
||||
{atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}.
|
||||
|
||||
%% For each contact with Subscription:
|
||||
%% Both or From, send a "unsubscribed" presence stanza;
|
||||
@@ -1009,7 +1058,11 @@ update_roster_t(LUser, LServer, LJID, Item, odbc) ->
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
ItemVals = record_to_string(Item),
|
||||
ItemGroups = groups_to_string(Item),
|
||||
odbc_queries:update_roster(LServer, Username, SJID, ItemVals, ItemGroups).
|
||||
odbc_queries:update_roster(LServer, Username, SJID, ItemVals,
|
||||
ItemGroups);
|
||||
update_roster_t(LUser, LServer, _LJID, Item, riak) ->
|
||||
ejabberd_riak:put(Item, roster_schema(),
|
||||
[{'2i', [{<<"us">>, {LUser, LServer}}]}]).
|
||||
|
||||
del_roster_t(LUser, LServer, LJID) ->
|
||||
DBType = gen_mod:db_type(LServer, ?MODULE),
|
||||
@@ -1020,7 +1073,9 @@ del_roster_t(LUser, LServer, LJID, mnesia) ->
|
||||
del_roster_t(LUser, LServer, LJID, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SJID = ejabberd_odbc:escape(jlib:jid_to_string(LJID)),
|
||||
odbc_queries:del_roster(LServer, Username, SJID).
|
||||
odbc_queries:del_roster(LServer, Username, SJID);
|
||||
del_roster_t(LUser, LServer, LJID, riak) ->
|
||||
ejabberd_riak:delete(roster, {LUser, LServer, LJID}).
|
||||
|
||||
process_item_set_t(LUser, LServer,
|
||||
#xmlel{attrs = Attrs, children = Els}) ->
|
||||
@@ -1086,40 +1141,35 @@ get_in_pending_subscriptions(Ls, User, Server) ->
|
||||
get_in_pending_subscriptions(Ls, User, Server,
|
||||
gen_mod:db_type(LServer, ?MODULE)).
|
||||
|
||||
get_in_pending_subscriptions(Ls, User, Server,
|
||||
mnesia) ->
|
||||
get_in_pending_subscriptions(Ls, User, Server, DBType)
|
||||
when DBType == mnesia; DBType == riak ->
|
||||
JID = jlib:make_jid(User, Server, <<"">>),
|
||||
US = {JID#jid.luser, JID#jid.lserver},
|
||||
case mnesia:dirty_index_read(roster, US, #roster.us) of
|
||||
Result when is_list(Result) ->
|
||||
Ls ++
|
||||
lists:map(fun (R) ->
|
||||
Message = R#roster.askmessage,
|
||||
Status = if is_binary(Message) -> (Message);
|
||||
true -> <<"">>
|
||||
end,
|
||||
#xmlel{name = <<"presence">>,
|
||||
attrs =
|
||||
[{<<"from">>,
|
||||
jlib:jid_to_string(R#roster.jid)},
|
||||
{<<"to">>, jlib:jid_to_string(JID)},
|
||||
{<<"type">>, <<"subscribe">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"status">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[{xmlcdata, Status}]}]}
|
||||
end,
|
||||
lists:filter(fun (R) ->
|
||||
case R#roster.ask of
|
||||
in -> true;
|
||||
both -> true;
|
||||
_ -> false
|
||||
end
|
||||
end,
|
||||
Result));
|
||||
_ -> Ls
|
||||
end;
|
||||
Result = get_roster(JID#jid.luser, JID#jid.lserver, DBType),
|
||||
Ls ++ lists:map(fun (R) ->
|
||||
Message = R#roster.askmessage,
|
||||
Status = if is_binary(Message) -> (Message);
|
||||
true -> <<"">>
|
||||
end,
|
||||
#xmlel{name = <<"presence">>,
|
||||
attrs =
|
||||
[{<<"from">>,
|
||||
jlib:jid_to_string(R#roster.jid)},
|
||||
{<<"to">>, jlib:jid_to_string(JID)},
|
||||
{<<"type">>, <<"subscribe">>}],
|
||||
children =
|
||||
[#xmlel{name = <<"status">>,
|
||||
attrs = [],
|
||||
children =
|
||||
[{xmlcdata, Status}]}]}
|
||||
end,
|
||||
lists:filter(fun (R) ->
|
||||
case R#roster.ask of
|
||||
in -> true;
|
||||
both -> true;
|
||||
_ -> false
|
||||
end
|
||||
end,
|
||||
Result));
|
||||
get_in_pending_subscriptions(Ls, User, Server, odbc) ->
|
||||
JID = jlib:make_jid(User, Server, <<"">>),
|
||||
LUser = JID#jid.luser,
|
||||
@@ -1188,7 +1238,7 @@ read_subscription_and_groups(LUser, LServer, LJID,
|
||||
case catch odbc_queries:get_subscription(LServer,
|
||||
Username, SJID)
|
||||
of
|
||||
{selected, [<<"subscription">>], [{SSubscription}]} ->
|
||||
{selected, [<<"subscription">>], [[SSubscription]]} ->
|
||||
Subscription = case SSubscription of
|
||||
<<"B">> -> both;
|
||||
<<"T">> -> to;
|
||||
@@ -1205,6 +1255,15 @@ read_subscription_and_groups(LUser, LServer, LJID,
|
||||
end,
|
||||
{Subscription, Groups};
|
||||
_ -> error
|
||||
end;
|
||||
read_subscription_and_groups(LUser, LServer, LJID,
|
||||
riak) ->
|
||||
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
|
||||
{ok, #roster{subscription = Subscription,
|
||||
groups = Groups}} ->
|
||||
{Subscription, Groups};
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
get_jid_info(_, User, Server, JID) ->
|
||||
@@ -1319,7 +1378,8 @@ update_roster_table() ->
|
||||
iolist_to_binary(R2)},
|
||||
name = iolist_to_binary(Name),
|
||||
groups = [iolist_to_binary(G) || G <- Gs],
|
||||
askmessage = iolist_to_binary(Ask),
|
||||
askmessage = try iolist_to_binary(Ask)
|
||||
catch _:_ -> <<"">> end,
|
||||
xs = [xml:to_xmlel(X) || X <- Xs]}
|
||||
end);
|
||||
_ ->
|
||||
@@ -1642,6 +1702,11 @@ is_managed_from_id(<<"roster-remotely-managed">>) ->
|
||||
is_managed_from_id(_Id) ->
|
||||
false.
|
||||
|
||||
roster_schema() ->
|
||||
{record_info(fields, roster), #roster{}}.
|
||||
|
||||
roster_version_schema() ->
|
||||
{record_info(fields, roster_version), #roster_version{}}.
|
||||
|
||||
export(_Server) ->
|
||||
[{roster,
|
||||
@@ -1692,5 +1757,10 @@ import(_LServer, mnesia, #roster{} = R) ->
|
||||
mnesia:dirty_write(R);
|
||||
import(_LServer, mnesia, #roster_version{} = RV) ->
|
||||
mnesia:dirty_write(RV);
|
||||
import(_LServer, riak, #roster{us = {LUser, LServer}} = R) ->
|
||||
ejabberd_riak:put(R, roster_schema(),
|
||||
[{'2i', [{<<"us">>, {LUser, LServer}}]}]);
|
||||
import(_LServer, riak, #roster_version{} = RV) ->
|
||||
ejabberd_riak:put(RV, roster_version_schema());
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+106
-1
@@ -400,6 +400,13 @@ list_groups(Host, mnesia) ->
|
||||
mnesia:dirty_select(sr_group,
|
||||
[{#sr_group{group_host = {'$1', '$2'}, _ = '_'},
|
||||
[{'==', '$2', Host}], ['$1']}]);
|
||||
list_groups(Host, riak) ->
|
||||
case ejabberd_riak:get_keys_by_index(sr_group, <<"host">>, Host) of
|
||||
{ok, Gs} ->
|
||||
[G || {G, _} <- Gs];
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
list_groups(Host, odbc) ->
|
||||
case ejabberd_odbc:sql_query(Host,
|
||||
[<<"select name from sr_group;">>])
|
||||
@@ -417,6 +424,14 @@ groups_with_opts(Host, mnesia) ->
|
||||
_ = '_'},
|
||||
[], [['$1', '$2']]}]),
|
||||
lists:map(fun ([G, O]) -> {G, O} end, Gs);
|
||||
groups_with_opts(Host, riak) ->
|
||||
case ejabberd_riak:get_by_index(sr_group, sr_group_schema(),
|
||||
<<"host">>, Host) of
|
||||
{ok, Rs} ->
|
||||
[{G, O} || #sr_group{group_host = {G, _}, opts = O} <- Rs];
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
groups_with_opts(Host, odbc) ->
|
||||
case ejabberd_odbc:sql_query(Host,
|
||||
[<<"select name, opts from sr_group;">>])
|
||||
@@ -438,6 +453,11 @@ create_group(Host, Group, Opts, mnesia) ->
|
||||
R = #sr_group{group_host = {Group, Host}, opts = Opts},
|
||||
F = fun () -> mnesia:write(R) end,
|
||||
mnesia:transaction(F);
|
||||
create_group(Host, Group, Opts, riak) ->
|
||||
{atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
|
||||
opts = Opts},
|
||||
sr_group_schema(),
|
||||
[{'2i', [{<<"host">>, Host}]}])};
|
||||
create_group(Host, Group, Opts, odbc) ->
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
SOpts = ejabberd_odbc:encode_term(Opts),
|
||||
@@ -464,6 +484,15 @@ delete_group(Host, Group, mnesia) ->
|
||||
Users)
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
delete_group(Host, Group, riak) ->
|
||||
try
|
||||
ok = ejabberd_riak:delete(sr_group, {Group, Host}),
|
||||
ok = ejabberd_riak:delete_by_index(sr_user, <<"group_host">>,
|
||||
{Group, Host}),
|
||||
{atomic, ok}
|
||||
catch _:{badmatch, Err} ->
|
||||
{atomic, Err}
|
||||
end;
|
||||
delete_group(Host, Group, odbc) ->
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
F = fun () ->
|
||||
@@ -472,7 +501,10 @@ delete_group(Host, Group, odbc) ->
|
||||
ejabberd_odbc:sql_query_t([<<"delete from sr_user where grp='">>,
|
||||
SGroup, <<"';">>])
|
||||
end,
|
||||
ejabberd_odbc:sql_transaction(Host, F).
|
||||
case ejabberd_odbc:sql_transaction(Host, F) of
|
||||
{atomic,{updated,_}} -> {atomic, ok};
|
||||
Res -> Res
|
||||
end.
|
||||
|
||||
get_group_opts(Host, Group) ->
|
||||
get_group_opts(Host, Group,
|
||||
@@ -483,6 +515,11 @@ get_group_opts(Host, Group, mnesia) ->
|
||||
[#sr_group{opts = Opts}] -> Opts;
|
||||
_ -> error
|
||||
end;
|
||||
get_group_opts(Host, Group, riak) ->
|
||||
case ejabberd_riak:get(sr_group, sr_group_schema(), {Group, Host}) of
|
||||
{ok, #sr_group{opts = Opts}} -> Opts;
|
||||
_ -> error
|
||||
end;
|
||||
get_group_opts(Host, Group, odbc) ->
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
case catch ejabberd_odbc:sql_query(Host,
|
||||
@@ -502,6 +539,11 @@ set_group_opts(Host, Group, Opts, mnesia) ->
|
||||
R = #sr_group{group_host = {Group, Host}, opts = Opts},
|
||||
F = fun () -> mnesia:write(R) end,
|
||||
mnesia:transaction(F);
|
||||
set_group_opts(Host, Group, Opts, riak) ->
|
||||
{atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
|
||||
opts = Opts},
|
||||
sr_group_schema(),
|
||||
[{'2i', [{<<"host">>, Host}]}])};
|
||||
set_group_opts(Host, Group, Opts, odbc) ->
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
SOpts = ejabberd_odbc:encode_term(Opts),
|
||||
@@ -525,6 +567,13 @@ get_user_groups(US, Host, mnesia) ->
|
||||
|| #sr_user{group_host = {Group, H}} <- Rs, H == Host];
|
||||
_ -> []
|
||||
end;
|
||||
get_user_groups(US, Host, riak) ->
|
||||
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
|
||||
{ok, Rs} ->
|
||||
[Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
get_user_groups(US, Host, odbc) ->
|
||||
SJID = make_jid_s(US),
|
||||
case catch ejabberd_odbc:sql_query(Host,
|
||||
@@ -595,6 +644,14 @@ get_group_explicit_users(Host, Group, mnesia) ->
|
||||
Rs when is_list(Rs) -> [R#sr_user.us || R <- Rs];
|
||||
_ -> []
|
||||
end;
|
||||
get_group_explicit_users(Host, Group, riak) ->
|
||||
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
|
||||
<<"group_host">>, {Group, Host}) of
|
||||
{ok, Rs} ->
|
||||
[R#sr_user.us || R <- Rs];
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
get_group_explicit_users(Host, Group, odbc) ->
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
case catch ejabberd_odbc:sql_query(Host,
|
||||
@@ -680,6 +737,16 @@ get_user_displayed_groups(LUser, LServer, GroupsOpts,
|
||||
H == LServer];
|
||||
_ -> []
|
||||
end;
|
||||
get_user_displayed_groups(LUser, LServer, GroupsOpts,
|
||||
riak) ->
|
||||
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
|
||||
<<"us">>, {LUser, LServer}) of
|
||||
{ok, Rs} ->
|
||||
[{Group, proplists:get_value(Group, GroupsOpts, [])}
|
||||
|| #sr_user{group_host = {Group, _}} <- Rs];
|
||||
_ ->
|
||||
[]
|
||||
end;
|
||||
get_user_displayed_groups(LUser, LServer, GroupsOpts,
|
||||
odbc) ->
|
||||
SJID = make_jid_s(LUser, LServer),
|
||||
@@ -726,6 +793,21 @@ is_user_in_group(US, Group, Host, mnesia) ->
|
||||
[] -> lists:member(US, get_group_users(Host, Group));
|
||||
_ -> true
|
||||
end;
|
||||
is_user_in_group(US, Group, Host, riak) ->
|
||||
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
|
||||
{ok, Rs} ->
|
||||
case lists:any(
|
||||
fun(#sr_user{group_host = {G, H}}) ->
|
||||
(Group == G) and (Host == H)
|
||||
end, Rs) of
|
||||
false ->
|
||||
lists:member(US, get_group_users(Host, Group));
|
||||
true ->
|
||||
true
|
||||
end;
|
||||
_Err ->
|
||||
false
|
||||
end;
|
||||
is_user_in_group(US, Group, Host, odbc) ->
|
||||
SJID = make_jid_s(US),
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
@@ -765,6 +847,13 @@ add_user_to_group(Host, US, Group, mnesia) ->
|
||||
R = #sr_user{us = US, group_host = {Group, Host}},
|
||||
F = fun () -> mnesia:write(R) end,
|
||||
mnesia:transaction(F);
|
||||
add_user_to_group(Host, US, Group, riak) ->
|
||||
{atomic, ejabberd_riak:put(
|
||||
#sr_user{us = US, group_host = {Group, Host}},
|
||||
sr_user_schema(),
|
||||
[{i, {US, {Group, Host}}},
|
||||
{'2i', [{<<"us">>, US},
|
||||
{<<"group_host">>, {Group, Host}}]}])};
|
||||
add_user_to_group(Host, US, Group, odbc) ->
|
||||
SJID = make_jid_s(US),
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
@@ -816,6 +905,8 @@ remove_user_from_group(Host, US, Group, mnesia) ->
|
||||
R = #sr_user{us = US, group_host = {Group, Host}},
|
||||
F = fun () -> mnesia:delete_object(R) end,
|
||||
mnesia:transaction(F);
|
||||
remove_user_from_group(Host, US, Group, riak) ->
|
||||
{atomic, ejabberd_riak:delete(sr_group, {US, {Group, Host}})};
|
||||
remove_user_from_group(Host, US, Group, odbc) ->
|
||||
SJID = make_jid_s(US),
|
||||
SGroup = ejabberd_odbc:escape(Group),
|
||||
@@ -1274,6 +1365,12 @@ opts_to_binary(Opts) ->
|
||||
Opt
|
||||
end, Opts).
|
||||
|
||||
sr_group_schema() ->
|
||||
{record_info(fields, sr_group), #sr_group{}}.
|
||||
|
||||
sr_user_schema() ->
|
||||
{record_info(fields, sr_user), #sr_user{}}.
|
||||
|
||||
update_tables() ->
|
||||
update_sr_group_table(),
|
||||
update_sr_user_table().
|
||||
@@ -1355,7 +1452,15 @@ import(LServer) ->
|
||||
|
||||
import(_LServer, mnesia, #sr_group{} = G) ->
|
||||
mnesia:dirty_write(G);
|
||||
|
||||
import(_LServer, mnesia, #sr_user{} = U) ->
|
||||
mnesia:dirty_write(U);
|
||||
import(_LServer, riak, #sr_group{group_host = {_, Host}} = G) ->
|
||||
ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, Host}]}]);
|
||||
import(_LServer, riak, #sr_user{us = US, group_host = {Group, Host}} = User) ->
|
||||
ejabberd_riak:put(User, sr_user_schema(),
|
||||
[{i, {US, {Group, Host}}},
|
||||
{'2i', [{<<"us">>, US},
|
||||
{<<"group_host">>, {Group, Host}}]}]);
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
+31
-27
@@ -12,7 +12,7 @@
|
||||
-behaviour(esip).
|
||||
|
||||
%% API
|
||||
-export([start/2, stop/1, prepare_request/1, make_response/2, at_my_host/1]).
|
||||
-export([start/2, stop/1, make_response/2, is_my_host/1, at_my_host/1]).
|
||||
|
||||
%% esip_callbacks
|
||||
-export([data_in/2, data_out/2, message_in/2, message_out/2,
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("esip.hrl").
|
||||
-include_lib("esip/include/esip.hrl").
|
||||
|
||||
%%%===================================================================
|
||||
%%% API
|
||||
@@ -68,6 +68,8 @@ message_in(#sip{type = request, method = M} = Req, SIPSock)
|
||||
Action ->
|
||||
request(Req, SIPSock, undefined, Action)
|
||||
end;
|
||||
message_in(ping, SIPSock) ->
|
||||
mod_sip_registrar:ping(SIPSock);
|
||||
message_in(_, _) ->
|
||||
ok.
|
||||
|
||||
@@ -77,8 +79,17 @@ message_out(_, _) ->
|
||||
response(_Resp, _SIPSock) ->
|
||||
ok.
|
||||
|
||||
request(#sip{method = <<"ACK">>} = Req, SIPSock) ->
|
||||
case action(Req, SIPSock) of
|
||||
{relay, LServer} ->
|
||||
mod_sip_proxy:route(Req, LServer, [{authenticated, true}]);
|
||||
{proxy_auth, LServer} ->
|
||||
mod_sip_proxy:route(Req, LServer, [{authenticated, false}]);
|
||||
_ ->
|
||||
ok
|
||||
end;
|
||||
request(_Req, _SIPSock) ->
|
||||
error.
|
||||
ok.
|
||||
|
||||
request(Req, SIPSock, TrID) ->
|
||||
request(Req, SIPSock, TrID, action(Req, SIPSock)).
|
||||
@@ -105,20 +116,20 @@ request(Req, SIPSock, TrID, Action) ->
|
||||
?INFO_MSG("failed to proxy request ~p: ~p", [Req, Err]),
|
||||
Err
|
||||
end;
|
||||
{proxy_auth, Host} ->
|
||||
{proxy_auth, LServer} ->
|
||||
make_response(
|
||||
Req,
|
||||
#sip{status = 407,
|
||||
type = response,
|
||||
hdrs = [{'proxy-authenticate',
|
||||
make_auth_hdr(Host)}]});
|
||||
{auth, Host} ->
|
||||
make_auth_hdr(LServer)}]});
|
||||
{auth, LServer} ->
|
||||
make_response(
|
||||
Req,
|
||||
#sip{status = 401,
|
||||
type = response,
|
||||
hdrs = [{'www-authenticate',
|
||||
make_auth_hdr(Host)}]});
|
||||
make_auth_hdr(LServer)}]});
|
||||
deny ->
|
||||
make_response(Req, #sip{status = 403,
|
||||
type = response});
|
||||
@@ -151,8 +162,9 @@ action(#sip{method = <<"REGISTER">>, type = request, hdrs = Hdrs,
|
||||
uri = #uri{user = <<"">>} = URI} = Req, SIPSock) ->
|
||||
case at_my_host(URI) of
|
||||
true ->
|
||||
case esip:get_hdrs('require', Hdrs) of
|
||||
[_|_] = Require ->
|
||||
Require = esip:get_hdrs('require', Hdrs) -- supported(),
|
||||
case Require of
|
||||
[_|_] ->
|
||||
{unsupported, Require};
|
||||
_ ->
|
||||
{_, ToURI, _} = esip:get_hdr('to', Hdrs),
|
||||
@@ -162,7 +174,7 @@ action(#sip{method = <<"REGISTER">>, type = request, hdrs = Hdrs,
|
||||
true ->
|
||||
register;
|
||||
false ->
|
||||
{auth, ToURI#uri.host}
|
||||
{auth, jlib:nameprep(ToURI#uri.host)}
|
||||
end;
|
||||
false ->
|
||||
deny
|
||||
@@ -178,8 +190,9 @@ action(#sip{method = Method, hdrs = Hdrs, type = request} = Req, SIPSock) ->
|
||||
0 ->
|
||||
loop;
|
||||
_ ->
|
||||
case esip:get_hdrs('proxy-require', Hdrs) of
|
||||
[_|_] = Require ->
|
||||
Require = esip:get_hdrs('proxy-require', Hdrs) -- supported(),
|
||||
case Require of
|
||||
[_|_] ->
|
||||
{unsupported, Require};
|
||||
_ ->
|
||||
{_, ToURI, _} = esip:get_hdr('to', Hdrs),
|
||||
@@ -242,30 +255,21 @@ check_auth(#sip{method = Method, hdrs = Hdrs, body = Body}, AuthHdr, _SIPSock) -
|
||||
allow() ->
|
||||
[<<"OPTIONS">>, <<"REGISTER">>].
|
||||
|
||||
supported() ->
|
||||
[<<"path">>, <<"outbound">>].
|
||||
|
||||
process(#sip{method = <<"OPTIONS">>} = Req, _) ->
|
||||
make_response(Req, #sip{type = response, status = 200,
|
||||
hdrs = [{'allow', allow()}]});
|
||||
hdrs = [{'allow', allow()},
|
||||
{'supported', supported()}]});
|
||||
process(#sip{method = <<"REGISTER">>} = Req, _) ->
|
||||
make_response(Req, #sip{type = response, status = 400});
|
||||
process(Req, _) ->
|
||||
make_response(Req, #sip{type = response, status = 405,
|
||||
hdrs = [{'allow', allow()}]}).
|
||||
|
||||
prepare_request(#sip{hdrs = Hdrs1} = Req) ->
|
||||
MF = esip:get_hdr('max-forwards', Hdrs1),
|
||||
Hdrs2 = esip:set_hdr('max-forwards', MF-1, Hdrs1),
|
||||
Hdrs3 = lists:filter(
|
||||
fun({'proxy-authorization', {_, Params}}) ->
|
||||
Realm = esip:unquote(esip:get_param(<<"realm">>, Params)),
|
||||
not is_my_host(jlib:nameprep(Realm));
|
||||
(_) ->
|
||||
true
|
||||
end, Hdrs2),
|
||||
Req#sip{hdrs = Hdrs3}.
|
||||
|
||||
make_auth_hdr(LServer) ->
|
||||
Realm = jlib:nameprep(LServer),
|
||||
{<<"Digest">>, [{<<"realm">>, esip:quote(Realm)},
|
||||
{<<"Digest">>, [{<<"realm">>, esip:quote(LServer)},
|
||||
{<<"qop">>, esip:quote(<<"auth">>)},
|
||||
{<<"nonce">>, esip:quote(esip:make_hexstr(20))}]}.
|
||||
|
||||
|
||||
+177
-13
@@ -12,7 +12,7 @@
|
||||
-behaviour(?GEN_FSM).
|
||||
|
||||
%% API
|
||||
-export([start/2, start_link/2, route/4]).
|
||||
-export([start/2, start_link/2, route/3, route/4]).
|
||||
|
||||
%% gen_fsm callbacks
|
||||
-export([init/1, wait_for_request/2, wait_for_response/2,
|
||||
@@ -21,7 +21,9 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("esip.hrl").
|
||||
-include_lib("esip/include/esip.hrl").
|
||||
|
||||
-define(SIGN_LIFETIME, 300). %% in seconds.
|
||||
|
||||
-record(state, {host = <<"">> :: binary(),
|
||||
opts = [] :: [{certfile, binary()}],
|
||||
@@ -42,6 +44,43 @@ start_link(LServer, Opts) ->
|
||||
route(SIPMsg, _SIPSock, TrID, Pid) ->
|
||||
?GEN_FSM:send_event(Pid, {SIPMsg, TrID}).
|
||||
|
||||
route(#sip{hdrs = Hdrs} = Req, LServer, Opts) ->
|
||||
case proplists:get_bool(authenticated, Opts) of
|
||||
true ->
|
||||
route_statelessly(Req, LServer, Opts);
|
||||
false ->
|
||||
ConfiguredRRoute = get_configured_record_route(LServer),
|
||||
case esip:get_hdrs('route', Hdrs) of
|
||||
[{_, URI, _}|_] ->
|
||||
case cmp_uri(URI, ConfiguredRRoute) of
|
||||
true ->
|
||||
case is_signed_by_me(URI#uri.user, Hdrs) of
|
||||
true ->
|
||||
route_statelessly(Req, LServer, Opts);
|
||||
false ->
|
||||
error
|
||||
end;
|
||||
false ->
|
||||
error
|
||||
end;
|
||||
[] ->
|
||||
error
|
||||
end
|
||||
end.
|
||||
|
||||
route_statelessly(Req, LServer, Opts) ->
|
||||
Req1 = prepare_request(LServer, Req),
|
||||
case connect(Req1, add_certfile(LServer, Opts)) of
|
||||
{ok, SIPSocketsWithURIs} ->
|
||||
lists:foreach(
|
||||
fun({SIPSocket, _URI}) ->
|
||||
Req2 = add_via(SIPSocket, LServer, Req1),
|
||||
esip:send(SIPSocket, Req2)
|
||||
end, SIPSocketsWithURIs);
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_fsm callbacks
|
||||
%%%===================================================================
|
||||
@@ -51,16 +90,18 @@ init([Host, Opts]) ->
|
||||
|
||||
wait_for_request({#sip{type = request} = Req, TrID}, State) ->
|
||||
Opts = State#state.opts,
|
||||
Req1 = mod_sip:prepare_request(Req),
|
||||
Req1 = prepare_request(State#state.host, Req),
|
||||
case connect(Req1, Opts) of
|
||||
{ok, SIPSockets} ->
|
||||
{ok, SIPSocketsWithURIs} ->
|
||||
NewState =
|
||||
lists:foldl(
|
||||
fun(_SIPSocket, {error, _} = Err) ->
|
||||
fun(_SIPSocketWithURI, {error, _} = Err) ->
|
||||
Err;
|
||||
(SIPSocket, #state{tr_ids = TrIDs} = AccState) ->
|
||||
Req2 = add_via(SIPSocket, State#state.host, Req1),
|
||||
case esip:request(SIPSocket, Req2,
|
||||
({SIPSocket, URI}, #state{tr_ids = TrIDs} = AccState) ->
|
||||
Req2 = add_record_route_and_set_uri(
|
||||
URI, State#state.host, Req1),
|
||||
Req3 = add_via(SIPSocket, State#state.host, Req2),
|
||||
case esip:request(SIPSocket, Req3,
|
||||
{?MODULE, route, [self()]}) of
|
||||
{ok, ClientTrID} ->
|
||||
NewTrIDs = [ClientTrID|TrIDs],
|
||||
@@ -69,7 +110,7 @@ wait_for_request({#sip{type = request} = Req, TrID}, State) ->
|
||||
cancel_pending_transactions(AccState),
|
||||
Err
|
||||
end
|
||||
end, State, SIPSockets),
|
||||
end, State, SIPSocketsWithURIs),
|
||||
case NewState of
|
||||
{error, _} = Err ->
|
||||
{Status, Reason} = esip:error_status(Err),
|
||||
@@ -200,7 +241,7 @@ connect(#sip{hdrs = Hdrs} = Req, Opts) ->
|
||||
false ->
|
||||
case esip:connect(Req, Opts) of
|
||||
{ok, SIPSock} ->
|
||||
{ok, [SIPSock]};
|
||||
{ok, [{SIPSock, Req#sip.uri}]};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end
|
||||
@@ -230,13 +271,68 @@ add_via(#sip_socket{type = Transport}, LServer, #sip{hdrs = Hdrs} = Req) ->
|
||||
Via = #via{transport = ViaTransport,
|
||||
host = ViaHost,
|
||||
port = ViaPort,
|
||||
params = [{<<"branch">>, esip:make_branch()},
|
||||
{<<"rport">>, <<"">>}]},
|
||||
params = [{<<"branch">>, esip:make_branch()}]},
|
||||
Req#sip{hdrs = [{'via', [Via]}|Hdrs]}.
|
||||
|
||||
add_record_route_and_set_uri(URI, LServer, #sip{hdrs = Hdrs} = Req) ->
|
||||
case is_request_within_dialog(Req) of
|
||||
false ->
|
||||
case need_record_route(LServer) of
|
||||
true ->
|
||||
RR_URI = get_configured_record_route(LServer),
|
||||
{MSecs, Secs, _} = now(),
|
||||
TS = list_to_binary(integer_to_list(MSecs*1000000 + Secs)),
|
||||
Sign = make_sign(TS, Hdrs),
|
||||
User = <<TS/binary, $-, Sign/binary>>,
|
||||
NewRR_URI = RR_URI#uri{user = User},
|
||||
Hdrs1 = [{'record-route', [{<<>>, NewRR_URI, []}]}|Hdrs],
|
||||
Req#sip{uri = URI, hdrs = Hdrs1};
|
||||
false ->
|
||||
Req
|
||||
end;
|
||||
true ->
|
||||
Req
|
||||
end.
|
||||
|
||||
is_request_within_dialog(#sip{hdrs = Hdrs}) ->
|
||||
{_, _, Params} = esip:get_hdr('to', Hdrs),
|
||||
esip:has_param(<<"tag">>, Params).
|
||||
|
||||
need_record_route(LServer) ->
|
||||
gen_mod:get_module_opt(
|
||||
LServer, mod_sip, always_record_route,
|
||||
fun(true) -> true;
|
||||
(false) -> false
|
||||
end, true).
|
||||
|
||||
make_sign(TS, Hdrs) ->
|
||||
{_, #uri{user = FUser, host = FServer}, FParams} = esip:get_hdr('from', Hdrs),
|
||||
{_, #uri{user = TUser, host = TServer}, _} = esip:get_hdr('to', Hdrs),
|
||||
LFUser = safe_nodeprep(FUser),
|
||||
LTUser = safe_nodeprep(TUser),
|
||||
LFServer = safe_nameprep(FServer),
|
||||
LTServer = safe_nameprep(TServer),
|
||||
FromTag = esip:get_param(<<"tag">>, FParams),
|
||||
CallID = esip:get_hdr('call-id', Hdrs),
|
||||
SharedKey = ejabberd_config:get_option(shared_key, fun(V) -> V end),
|
||||
p1_sha:sha([SharedKey, LFUser, LFServer, LTUser, LTServer,
|
||||
FromTag, CallID, TS]).
|
||||
|
||||
is_signed_by_me(TS_Sign, Hdrs) ->
|
||||
try
|
||||
[TSBin, Sign] = str:tokens(TS_Sign, <<"-">>),
|
||||
TS = list_to_integer(binary_to_list(TSBin)),
|
||||
{MSecs, Secs, _} = now(),
|
||||
NowTS = MSecs*1000000 + Secs,
|
||||
true = (NowTS - TS) =< ?SIGN_LIFETIME,
|
||||
Sign == make_sign(TSBin, Hdrs)
|
||||
catch _:_ ->
|
||||
false
|
||||
end.
|
||||
|
||||
get_configured_vias(LServer) ->
|
||||
gen_mod:get_module_opt(
|
||||
LServer, ?MODULE, via,
|
||||
LServer, mod_sip, via,
|
||||
fun(L) ->
|
||||
lists:map(
|
||||
fun(Opts) ->
|
||||
@@ -252,6 +348,25 @@ get_configured_vias(LServer) ->
|
||||
end, L)
|
||||
end, []).
|
||||
|
||||
get_configured_record_route(LServer) ->
|
||||
gen_mod:get_module_opt(
|
||||
LServer, mod_sip, record_route,
|
||||
fun(IOList) ->
|
||||
S = iolist_to_binary(IOList),
|
||||
#uri{} = esip:decode_uri(S)
|
||||
end, #uri{host = LServer, params = [{<<"lr">>, <<"">>}]}).
|
||||
|
||||
get_configured_routes(LServer) ->
|
||||
gen_mod:get_module_opt(
|
||||
LServer, mod_sip, routes,
|
||||
fun(L) ->
|
||||
lists:map(
|
||||
fun(IOList) ->
|
||||
S = iolist_to_binary(IOList),
|
||||
#uri{} = esip:decode_uri(S)
|
||||
end, L)
|
||||
end, [#uri{host = LServer, params = [{<<"lr">>, <<"">>}]}]).
|
||||
|
||||
mark_transaction_as_complete(TrID, State) ->
|
||||
NewTrIDs = lists:delete(TrID, State#state.tr_ids),
|
||||
State#state{tr_ids = NewTrIDs}.
|
||||
@@ -275,3 +390,52 @@ choose_best_response(#state{responses = Responses} = State) ->
|
||||
ok
|
||||
end
|
||||
end.
|
||||
|
||||
%% Just compare host part only.
|
||||
cmp_uri(#uri{host = H1}, #uri{host = H2}) ->
|
||||
jlib:nameprep(H1) == jlib:nameprep(H2).
|
||||
|
||||
is_my_route(URI, URIs) ->
|
||||
lists:any(fun(U) -> cmp_uri(URI, U) end, URIs).
|
||||
|
||||
prepare_request(LServer, #sip{hdrs = Hdrs} = Req) ->
|
||||
ConfiguredRRoute = get_configured_record_route(LServer),
|
||||
ConfiguredRoutes = get_configured_routes(LServer),
|
||||
Hdrs1 = lists:flatmap(
|
||||
fun({Hdr, HdrList}) when Hdr == 'route';
|
||||
Hdr == 'record-route' ->
|
||||
case lists:filter(
|
||||
fun({_, URI, _}) ->
|
||||
not cmp_uri(URI, ConfiguredRRoute)
|
||||
and not is_my_route(URI, ConfiguredRoutes)
|
||||
end, HdrList) of
|
||||
[] ->
|
||||
[];
|
||||
HdrList1 ->
|
||||
[{Hdr, HdrList1}]
|
||||
end;
|
||||
(Hdr) ->
|
||||
[Hdr]
|
||||
end, Hdrs),
|
||||
MF = esip:get_hdr('max-forwards', Hdrs1),
|
||||
Hdrs2 = esip:set_hdr('max-forwards', MF-1, Hdrs1),
|
||||
Hdrs3 = lists:filter(
|
||||
fun({'proxy-authorization', {_, Params}}) ->
|
||||
Realm = esip:unquote(esip:get_param(<<"realm">>, Params)),
|
||||
not mod_sip:is_my_host(jlib:nameprep(Realm));
|
||||
(_) ->
|
||||
true
|
||||
end, Hdrs2),
|
||||
Req#sip{hdrs = Hdrs3}.
|
||||
|
||||
safe_nodeprep(S) ->
|
||||
case jlib:nodeprep(S) of
|
||||
error -> S;
|
||||
S1 -> S1
|
||||
end.
|
||||
|
||||
safe_nameprep(S) ->
|
||||
case jlib:nameprep(S) of
|
||||
error -> S;
|
||||
S1 -> S1
|
||||
end.
|
||||
|
||||
+392
-164
@@ -12,7 +12,7 @@
|
||||
-behaviour(?GEN_SERVER).
|
||||
|
||||
%% API
|
||||
-export([start_link/0, request/2, find_sockets/2]).
|
||||
-export([start_link/0, request/2, find_sockets/2, ping/1]).
|
||||
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
@@ -20,19 +20,23 @@
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("esip.hrl").
|
||||
-include_lib("esip/include/esip.hrl").
|
||||
|
||||
-define(CALL_TIMEOUT, timer:seconds(30)).
|
||||
|
||||
-record(binding, {socket = #sip_socket{},
|
||||
call_id = <<"">> :: binary(),
|
||||
cseq = 0 :: non_neg_integer(),
|
||||
timestamp = now() :: erlang:timestamp(),
|
||||
tref = make_ref() :: reference(),
|
||||
expires = 0 :: non_neg_integer()}).
|
||||
-define(DEFAULT_EXPIRES, 3600).
|
||||
-define(FLOW_TIMEOUT_UDP, 29).
|
||||
-define(FLOW_TIMEOUT_TCP, 120).
|
||||
|
||||
-record(sip_session, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
bindings = [] :: [#binding{}]}).
|
||||
socket = #sip_socket{} :: #sip_socket{},
|
||||
call_id = <<"">> :: binary(),
|
||||
cseq = 0 :: non_neg_integer(),
|
||||
timestamp = now() :: erlang:timestamp(),
|
||||
contact :: {binary(), #uri{}, [{binary(), binary()}]},
|
||||
flow_tref :: reference(),
|
||||
reg_tref = make_ref() :: reference(),
|
||||
conn_mref = make_ref() :: reference(),
|
||||
expires = 0 :: non_neg_integer()}).
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
@@ -50,15 +54,21 @@ request(#sip{hdrs = Hdrs} = Req, SIPSock) ->
|
||||
US = {LUser, LServer},
|
||||
CallID = esip:get_hdr('call-id', Hdrs),
|
||||
CSeq = esip:get_hdr('cseq', Hdrs),
|
||||
Expires = esip:get_hdr('expires', Hdrs, 0),
|
||||
Expires = esip:get_hdr('expires', Hdrs, ?DEFAULT_EXPIRES),
|
||||
Supported = esip:get_hdrs('supported', Hdrs),
|
||||
IsOutboundSupported = lists:member(<<"outbound">>, Supported),
|
||||
case esip:get_hdrs('contact', Hdrs) of
|
||||
[<<"*">>] when Expires == 0 ->
|
||||
case unregister_session(US, SIPSock, CallID, CSeq) of
|
||||
ok ->
|
||||
case unregister_session(US, CallID, CSeq) of
|
||||
{ok, ContactsWithExpires} ->
|
||||
?INFO_MSG("unregister SIP session for user ~s@~s from ~s",
|
||||
[LUser, LServer, inet_parse:ntoa(PeerIP)]),
|
||||
Cs = prepare_contacts_to_send(ContactsWithExpires),
|
||||
mod_sip:make_response(
|
||||
Req, #sip{type = response, status = 200});
|
||||
Req,
|
||||
#sip{type = response,
|
||||
status = 200,
|
||||
hdrs = [{'contact', Cs}]});
|
||||
{error, Why} ->
|
||||
{Status, Reason} = make_status(Why),
|
||||
mod_sip:make_response(
|
||||
@@ -67,51 +77,40 @@ request(#sip{hdrs = Hdrs} = Req, SIPSock) ->
|
||||
reason = Reason})
|
||||
end;
|
||||
[{_, _URI, _Params}|_] = Contacts ->
|
||||
ExpiresList = lists:map(
|
||||
fun({_, _, Params}) ->
|
||||
case to_integer(
|
||||
esip:get_param(
|
||||
<<"expires">>, Params),
|
||||
0, (1 bsl 32)-1) of
|
||||
{ok, E} -> E;
|
||||
_ -> Expires
|
||||
end
|
||||
end, Contacts),
|
||||
Expires1 = lists:max(ExpiresList),
|
||||
Contact = {<<"">>, #uri{user = LUser, host = LServer},
|
||||
[{<<"expires">>, jlib:integer_to_binary(Expires1)}]},
|
||||
ContactsWithExpires = make_contacts_with_expires(Contacts, Expires),
|
||||
ContactsHaveManyRegID = contacts_have_many_reg_id(Contacts),
|
||||
Expires1 = lists:max([E || {_, E} <- ContactsWithExpires]),
|
||||
MinExpires = min_expires(),
|
||||
if Expires1 >= MinExpires ->
|
||||
case register_session(US, SIPSock, CallID, CSeq, Expires1) of
|
||||
ok ->
|
||||
?INFO_MSG("register SIP session for user ~s@~s from ~s",
|
||||
[LUser, LServer, inet_parse:ntoa(PeerIP)]),
|
||||
if Expires1 > 0, Expires1 < MinExpires ->
|
||||
mod_sip:make_response(
|
||||
Req, #sip{type = response,
|
||||
status = 423,
|
||||
hdrs = [{'min-expires', MinExpires}]});
|
||||
ContactsHaveManyRegID ->
|
||||
mod_sip:make_response(
|
||||
Req, #sip{type = response, status = 400,
|
||||
reason = <<"Multiple 'reg-id' parameter">>});
|
||||
true ->
|
||||
case register_session(US, SIPSock, CallID, CSeq,
|
||||
IsOutboundSupported,
|
||||
ContactsWithExpires) of
|
||||
{ok, Res} ->
|
||||
?INFO_MSG("~s SIP session for user ~s@~s from ~s",
|
||||
[Res, LUser, LServer,
|
||||
inet_parse:ntoa(PeerIP)]),
|
||||
Cs = prepare_contacts_to_send(ContactsWithExpires),
|
||||
Require = case need_ob_hdrs(
|
||||
Contacts, IsOutboundSupported) of
|
||||
true -> [{'require', [<<"outbound">>]},
|
||||
{'flow-timer',
|
||||
get_flow_timeout(LServer, SIPSock)}];
|
||||
false -> []
|
||||
end,
|
||||
mod_sip:make_response(
|
||||
Req,
|
||||
#sip{type = response,
|
||||
status = 200,
|
||||
hdrs = [{'contact', [Contact]}]});
|
||||
{error, Why} ->
|
||||
{Status, Reason} = make_status(Why),
|
||||
mod_sip:make_response(
|
||||
Req, #sip{type = response,
|
||||
status = Status,
|
||||
reason = Reason})
|
||||
end;
|
||||
Expires1 > 0, Expires1 < MinExpires ->
|
||||
mod_sip:make_response(
|
||||
Req, #sip{type = response,
|
||||
status = 423,
|
||||
hdrs = [{'min-expires', MinExpires}]});
|
||||
true ->
|
||||
case unregister_session(US, SIPSock, CallID, CSeq) of
|
||||
ok ->
|
||||
?INFO_MSG("unregister SIP session for user ~s@~s from ~s",
|
||||
[LUser, LServer, inet_parse:ntoa(PeerIP)]),
|
||||
mod_sip:make_response(
|
||||
Req,
|
||||
#sip{type = response, status = 200,
|
||||
hdrs = [{'contact', [Contact]}]});
|
||||
hdrs = [{'contact', Cs}|Require]});
|
||||
{error, Why} ->
|
||||
{Status, Reason} = make_status(Why),
|
||||
mod_sip:make_response(
|
||||
@@ -122,23 +121,16 @@ request(#sip{hdrs = Hdrs} = Req, SIPSock) ->
|
||||
end;
|
||||
[] ->
|
||||
case mnesia:dirty_read(sip_session, US) of
|
||||
[#sip_session{bindings = Bindings}] ->
|
||||
case pop_previous_binding(SIPSock, Bindings) of
|
||||
{ok, #binding{expires = Expires1}, _} ->
|
||||
Contact = {<<"">>,
|
||||
#uri{user = LUser, host = LServer},
|
||||
[{<<"expires">>,
|
||||
jlib:integer_to_binary(Expires1)}]},
|
||||
mod_sip:make_response(
|
||||
Req, #sip{type = response, status = 200,
|
||||
hdrs = [{'contact', [Contact]}]});
|
||||
{error, notfound} ->
|
||||
{Status, Reason} = make_status(notfound),
|
||||
mod_sip:make_response(
|
||||
Req, #sip{type = response,
|
||||
status = Status,
|
||||
reason = Reason})
|
||||
end;
|
||||
[_|_] = Sessions ->
|
||||
ContactsWithExpires =
|
||||
lists:map(
|
||||
fun(#sip_session{contact = Contact, expires = Es}) ->
|
||||
{Contact, Es}
|
||||
end, Sessions),
|
||||
Cs = prepare_contacts_to_send(ContactsWithExpires),
|
||||
mod_sip:make_response(
|
||||
Req, #sip{type = response, status = 200,
|
||||
hdrs = [{'contact', Cs}]});
|
||||
[] ->
|
||||
{Status, Reason} = make_status(notfound),
|
||||
mod_sip:make_response(
|
||||
@@ -152,27 +144,41 @@ request(#sip{hdrs = Hdrs} = Req, SIPSock) ->
|
||||
|
||||
find_sockets(U, S) ->
|
||||
case mnesia:dirty_read(sip_session, {U, S}) of
|
||||
[#sip_session{bindings = Bindings}] ->
|
||||
[Binding#binding.socket || Binding <- Bindings];
|
||||
[_|_] = Sessions ->
|
||||
lists:map(
|
||||
fun(#sip_session{contact = {_, URI, _},
|
||||
socket = Socket}) ->
|
||||
{Socket, URI}
|
||||
end, Sessions);
|
||||
[] ->
|
||||
[]
|
||||
end.
|
||||
|
||||
ping(SIPSocket) ->
|
||||
call({ping, SIPSocket}).
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
update_table(),
|
||||
mnesia:create_table(sip_session,
|
||||
[{ram_copies, [node()]},
|
||||
{type, bag},
|
||||
{attributes, record_info(fields, sip_session)}]),
|
||||
mnesia:add_table_index(sip_session, conn_mref),
|
||||
mnesia:add_table_index(sip_session, socket),
|
||||
mnesia:add_table_copy(sip_session, node(), ram_copies),
|
||||
{ok, #state{}}.
|
||||
|
||||
handle_call({write, Session}, _From, State) ->
|
||||
Res = write_session(Session),
|
||||
handle_call({write, Sessions, Supported}, _From, State) ->
|
||||
Res = write_session(Sessions, Supported),
|
||||
{reply, Res, State};
|
||||
handle_call({delete, US, SIPSocket, CallID, CSeq}, _From, State) ->
|
||||
Res = delete_session(US, SIPSocket, CallID, CSeq),
|
||||
handle_call({delete, US, CallID, CSeq}, _From, State) ->
|
||||
Res = delete_session(US, CallID, CSeq),
|
||||
{reply, Res, State};
|
||||
handle_call({ping, SIPSocket}, _From, State) ->
|
||||
Res = process_ping(SIPSocket),
|
||||
{reply, Res, State};
|
||||
handle_call(_Request, _From, State) ->
|
||||
Reply = ok,
|
||||
@@ -181,15 +187,23 @@ handle_call(_Request, _From, State) ->
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
handle_info({write, Session}, State) ->
|
||||
write_session(Session),
|
||||
handle_info({write, Sessions, Supported}, State) ->
|
||||
write_session(Sessions, Supported),
|
||||
{noreply, State};
|
||||
handle_info({delete, US, SIPSocket, CallID, CSeq}, State) ->
|
||||
delete_session(US, SIPSocket, CallID, CSeq),
|
||||
handle_info({delete, US, CallID, CSeq}, State) ->
|
||||
delete_session(US, CallID, CSeq),
|
||||
{noreply, State};
|
||||
handle_info({timeout, TRef, US}, State) ->
|
||||
delete_expired_session(US, TRef),
|
||||
{noreply, State};
|
||||
handle_info({'DOWN', MRef, process, _Pid, _Reason}, State) ->
|
||||
case mnesia:dirty_index_read(sip_session, MRef, #sip_session.conn_mref) of
|
||||
[Session] ->
|
||||
mnesia:dirty_delete_object(Session);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
{noreply, State};
|
||||
handle_info(_Info, State) ->
|
||||
?ERROR_MSG("got unexpected info: ~p", [_Info]),
|
||||
{noreply, State}.
|
||||
@@ -203,70 +217,98 @@ code_change(_OldVsn, State, _Extra) ->
|
||||
%%%===================================================================
|
||||
%%% Internal functions
|
||||
%%%===================================================================
|
||||
register_session(US, SIPSocket, CallID, CSeq, Expires) ->
|
||||
Session = #sip_session{us = US,
|
||||
bindings = [#binding{socket = SIPSocket,
|
||||
call_id = CallID,
|
||||
cseq = CSeq,
|
||||
timestamp = now(),
|
||||
expires = Expires}]},
|
||||
call({write, Session}).
|
||||
|
||||
unregister_session(US, SIPSocket, CallID, CSeq) ->
|
||||
Msg = {delete, US, SIPSocket, CallID, CSeq},
|
||||
register_session(US, SIPSocket, CallID, CSeq, IsOutboundSupported,
|
||||
ContactsWithExpires) ->
|
||||
Sessions = lists:map(
|
||||
fun({Contact, Expires}) ->
|
||||
#sip_session{us = US,
|
||||
socket = SIPSocket,
|
||||
call_id = CallID,
|
||||
cseq = CSeq,
|
||||
timestamp = now(),
|
||||
contact = Contact,
|
||||
expires = Expires}
|
||||
end, ContactsWithExpires),
|
||||
Msg = {write, Sessions, IsOutboundSupported},
|
||||
call(Msg).
|
||||
|
||||
write_session(#sip_session{us = {U, S} = US,
|
||||
bindings = [#binding{socket = SIPSocket,
|
||||
call_id = CallID,
|
||||
expires = Expires,
|
||||
cseq = CSeq} = Binding]}) ->
|
||||
case mnesia:dirty_read(sip_session, US) of
|
||||
[#sip_session{bindings = Bindings}] ->
|
||||
case pop_previous_binding(SIPSocket, Bindings) of
|
||||
{ok, #binding{call_id = CallID, cseq = PrevCSeq}, _}
|
||||
when PrevCSeq > CSeq ->
|
||||
{error, cseq_out_of_order};
|
||||
{ok, #binding{tref = Tref}, Bindings1} ->
|
||||
erlang:cancel_timer(Tref),
|
||||
NewTRef = erlang:start_timer(Expires * 1000, self(), US),
|
||||
NewBindings = [Binding#binding{tref = NewTRef}|Bindings1],
|
||||
mnesia:dirty_write(
|
||||
#sip_session{us = US, bindings = NewBindings});
|
||||
{error, notfound} ->
|
||||
MaxSessions = ejabberd_sm:get_max_user_sessions(U, S),
|
||||
if length(Bindings) < MaxSessions ->
|
||||
NewTRef = erlang:start_timer(Expires * 1000, self(), US),
|
||||
NewBindings = [Binding#binding{tref = NewTRef}|Bindings],
|
||||
mnesia:dirty_write(
|
||||
#sip_session{us = US, bindings = NewBindings});
|
||||
true ->
|
||||
{error, too_many_sessions}
|
||||
unregister_session(US, CallID, CSeq) ->
|
||||
Msg = {delete, US, CallID, CSeq},
|
||||
call(Msg).
|
||||
|
||||
write_session([#sip_session{us = {U, S} = US}|_] = NewSessions,
|
||||
IsOutboundSupported) ->
|
||||
PrevSessions = mnesia:dirty_read(sip_session, US),
|
||||
Res = lists:foldl(
|
||||
fun(_, {error, _} = Err) ->
|
||||
Err;
|
||||
(#sip_session{call_id = CallID,
|
||||
expires = Expires,
|
||||
cseq = CSeq} = Session, {Add, Del}) ->
|
||||
case find_session(Session, PrevSessions,
|
||||
IsOutboundSupported) of
|
||||
{ok, normal, #sip_session{call_id = CallID,
|
||||
cseq = PrevCSeq}}
|
||||
when PrevCSeq > CSeq ->
|
||||
{error, cseq_out_of_order};
|
||||
{ok, _Type, PrevSession} when Expires == 0 ->
|
||||
{Add, [PrevSession|Del]};
|
||||
{ok, _Type, PrevSession} ->
|
||||
{[Session|Add], [PrevSession|Del]};
|
||||
{error, notfound} when Expires == 0 ->
|
||||
{error, notfound};
|
||||
{error, notfound} ->
|
||||
{[Session|Add], Del}
|
||||
end
|
||||
end;
|
||||
[] ->
|
||||
NewTRef = erlang:start_timer(Expires * 1000, self(), US),
|
||||
NewBindings = [Binding#binding{tref = NewTRef}],
|
||||
mnesia:dirty_write(#sip_session{us = US, bindings = NewBindings})
|
||||
end, {[], []}, NewSessions),
|
||||
MaxSessions = ejabberd_sm:get_max_user_sessions(U, S),
|
||||
case Res of
|
||||
{error, Why} ->
|
||||
{error, Why};
|
||||
{AddSessions, DelSessions} ->
|
||||
MaxSessions = ejabberd_sm:get_max_user_sessions(U, S),
|
||||
AllSessions = AddSessions ++ PrevSessions -- DelSessions,
|
||||
if length(AllSessions) > MaxSessions ->
|
||||
{error, too_many_sessions};
|
||||
true ->
|
||||
lists:foreach(fun delete_session/1, DelSessions),
|
||||
lists:foreach(
|
||||
fun(Session) ->
|
||||
NewSession = set_monitor_and_timer(
|
||||
Session, IsOutboundSupported),
|
||||
mnesia:dirty_write(NewSession)
|
||||
end, AddSessions),
|
||||
case {AllSessions, AddSessions} of
|
||||
{[], _} ->
|
||||
{ok, unregister};
|
||||
{_, []} ->
|
||||
{ok, unregister};
|
||||
_ ->
|
||||
{ok, register}
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
delete_session(US, SIPSocket, CallID, CSeq) ->
|
||||
delete_session(US, CallID, CSeq) ->
|
||||
case mnesia:dirty_read(sip_session, US) of
|
||||
[#sip_session{bindings = Bindings}] ->
|
||||
case pop_previous_binding(SIPSocket, Bindings) of
|
||||
{ok, #binding{call_id = CallID, cseq = PrevCSeq}, _}
|
||||
when PrevCSeq > CSeq ->
|
||||
{error, cseq_out_of_order};
|
||||
{ok, #binding{tref = TRef}, []} ->
|
||||
erlang:cancel_timer(TRef),
|
||||
mnesia:dirty_delete(sip_session, US);
|
||||
{ok, #binding{tref = TRef}, NewBindings} ->
|
||||
erlang:cancel_timer(TRef),
|
||||
mnesia:dirty_write(sip_session,
|
||||
#sip_session{us = US,
|
||||
bindings = NewBindings});
|
||||
{error, notfound} ->
|
||||
{error, notfound}
|
||||
[_|_] = Sessions ->
|
||||
case lists:all(
|
||||
fun(S) when S#sip_session.call_id == CallID,
|
||||
S#sip_session.cseq > CSeq ->
|
||||
false;
|
||||
(_) ->
|
||||
true
|
||||
end, Sessions) of
|
||||
true ->
|
||||
ContactsWithExpires =
|
||||
lists:map(
|
||||
fun(#sip_session{contact = Contact} = Session) ->
|
||||
delete_session(Session),
|
||||
{Contact, 0}
|
||||
end, Sessions),
|
||||
{ok, ContactsWithExpires};
|
||||
false ->
|
||||
{error, cseq_out_of_order}
|
||||
end;
|
||||
[] ->
|
||||
{error, notfound}
|
||||
@@ -274,20 +316,20 @@ delete_session(US, SIPSocket, CallID, CSeq) ->
|
||||
|
||||
delete_expired_session(US, TRef) ->
|
||||
case mnesia:dirty_read(sip_session, US) of
|
||||
[#sip_session{bindings = Bindings}] ->
|
||||
case lists:filter(
|
||||
fun(#binding{tref = TRef1}) when TRef1 == TRef ->
|
||||
false;
|
||||
(_) ->
|
||||
true
|
||||
end, Bindings) of
|
||||
[] ->
|
||||
mnesia:dirty_delete(sip_session, US);
|
||||
NewBindings ->
|
||||
mnesia:dirty_write(sip_session,
|
||||
#sip_session{us = US,
|
||||
bindings = NewBindings})
|
||||
end;
|
||||
[_|_] = Sessions ->
|
||||
lists:foreach(
|
||||
fun(#sip_session{reg_tref = T1,
|
||||
flow_tref = T2} = Session)
|
||||
when T1 == TRef; T2 == TRef ->
|
||||
if T2 /= undefined ->
|
||||
close_socket(Session);
|
||||
true ->
|
||||
ok
|
||||
end,
|
||||
delete_session(Session);
|
||||
(_) ->
|
||||
ok
|
||||
end, Sessions);
|
||||
[] ->
|
||||
ok
|
||||
end.
|
||||
@@ -303,17 +345,6 @@ to_integer(Bin, Min, Max) ->
|
||||
error
|
||||
end.
|
||||
|
||||
pop_previous_binding(#sip_socket{peer = Peer}, Bindings) ->
|
||||
case lists:partition(
|
||||
fun(#binding{socket = #sip_socket{peer = Peer1}}) ->
|
||||
Peer1 == Peer
|
||||
end, Bindings) of
|
||||
{[Binding], RestBindings} ->
|
||||
{ok, Binding, RestBindings};
|
||||
_ ->
|
||||
{error, notfound}
|
||||
end.
|
||||
|
||||
call(Msg) ->
|
||||
case catch ?GEN_SERVER:call(?MODULE, Msg, ?CALL_TIMEOUT) of
|
||||
{'EXIT', {timeout, _}} ->
|
||||
@@ -324,6 +355,87 @@ call(Msg) ->
|
||||
Reply
|
||||
end.
|
||||
|
||||
make_contacts_with_expires(Contacts, Expires) ->
|
||||
lists:map(
|
||||
fun({Name, URI, Params}) ->
|
||||
E1 = case to_integer(esip:get_param(<<"expires">>, Params),
|
||||
0, (1 bsl 32)-1) of
|
||||
{ok, E} -> E;
|
||||
_ -> Expires
|
||||
end,
|
||||
Params1 = lists:keydelete(<<"expires">>, 1, Params),
|
||||
{{Name, URI, Params1}, E1}
|
||||
end, Contacts).
|
||||
|
||||
prepare_contacts_to_send(ContactsWithExpires) ->
|
||||
lists:map(
|
||||
fun({{Name, URI, Params}, Expires}) ->
|
||||
Params1 = esip:set_param(<<"expires">>,
|
||||
list_to_binary(
|
||||
integer_to_list(Expires)),
|
||||
Params),
|
||||
{Name, URI, Params1}
|
||||
end, ContactsWithExpires).
|
||||
|
||||
contacts_have_many_reg_id(Contacts) ->
|
||||
Sum = lists:foldl(
|
||||
fun({_Name, _URI, Params}, Acc) ->
|
||||
case get_ob_params(Params) of
|
||||
error ->
|
||||
Acc;
|
||||
{_, _} ->
|
||||
Acc + 1
|
||||
end
|
||||
end, 0, Contacts),
|
||||
if Sum > 1 ->
|
||||
true;
|
||||
true ->
|
||||
false
|
||||
end.
|
||||
|
||||
find_session(#sip_session{contact = {_, URI, Params}}, Sessions,
|
||||
IsOutboundSupported) ->
|
||||
if IsOutboundSupported ->
|
||||
case get_ob_params(Params) of
|
||||
{InstanceID, RegID} ->
|
||||
find_session_by_ob({InstanceID, RegID}, Sessions);
|
||||
error ->
|
||||
find_session_by_uri(URI, Sessions)
|
||||
end;
|
||||
true ->
|
||||
find_session_by_uri(URI, Sessions)
|
||||
end.
|
||||
|
||||
find_session_by_ob({InstanceID, RegID},
|
||||
[#sip_session{contact = {_, _, Params}} = Session|Sessions]) ->
|
||||
case get_ob_params(Params) of
|
||||
{InstanceID, RegID} ->
|
||||
{ok, flow, Session};
|
||||
_ ->
|
||||
find_session_by_ob({InstanceID, RegID}, Sessions)
|
||||
end;
|
||||
find_session_by_ob(_, []) ->
|
||||
{error, notfound}.
|
||||
|
||||
find_session_by_uri(URI1,
|
||||
[#sip_session{contact = {_, URI2, _}} = Session|Sessions]) ->
|
||||
case cmp_uri(URI1, URI2) of
|
||||
true ->
|
||||
{ok, normal, Session};
|
||||
false ->
|
||||
find_session_by_uri(URI1, Sessions)
|
||||
end;
|
||||
find_session_by_uri(_, []) ->
|
||||
{error, notfound}.
|
||||
|
||||
%% TODO: this is *totally* wrong.
|
||||
%% Rewrite this using URI comparison rules
|
||||
cmp_uri(#uri{user = U, host = H, port = P},
|
||||
#uri{user = U, host = H, port = P}) ->
|
||||
true;
|
||||
cmp_uri(_, _) ->
|
||||
false.
|
||||
|
||||
make_status(notfound) ->
|
||||
{404, esip:reason(404)};
|
||||
make_status(cseq_out_of_order) ->
|
||||
@@ -334,3 +446,119 @@ make_status(too_many_sessions) ->
|
||||
{503, <<"Too Many Registered Sessions">>};
|
||||
make_status(_) ->
|
||||
{500, esip:reason(500)}.
|
||||
|
||||
get_ob_params(Params) ->
|
||||
case esip:get_param(<<"+sip.instance">>, Params) of
|
||||
<<>> ->
|
||||
error;
|
||||
InstanceID ->
|
||||
case to_integer(esip:get_param(<<"reg-id">>, Params),
|
||||
0, (1 bsl 32)-1) of
|
||||
{ok, RegID} ->
|
||||
{InstanceID, RegID};
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end.
|
||||
|
||||
need_ob_hdrs(_Contacts, _IsOutboundSupported = false) ->
|
||||
false;
|
||||
need_ob_hdrs(Contacts, _IsOutboundSupported = true) ->
|
||||
lists:any(
|
||||
fun({_Name, _URI, Params}) ->
|
||||
case get_ob_params(Params) of
|
||||
error -> false;
|
||||
{_, _} -> true
|
||||
end
|
||||
end, Contacts).
|
||||
|
||||
get_flow_timeout(LServer, #sip_socket{type = Type}) ->
|
||||
{Option, Default} =
|
||||
case Type of
|
||||
udp -> {flow_timeout_udp, ?FLOW_TIMEOUT_UDP};
|
||||
_ -> {flow_timeout_tcp, ?FLOW_TIMEOUT_TCP}
|
||||
end,
|
||||
gen_mod:get_module_opt(
|
||||
LServer, mod_sip, Option,
|
||||
fun(I) when is_integer(I), I>0 -> I end,
|
||||
Default).
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, sip_session),
|
||||
case catch mnesia:table_info(sip_session, attributes) of
|
||||
Fields ->
|
||||
ok;
|
||||
[_|_] ->
|
||||
mnesia:delete_table(sip_session);
|
||||
{'EXIT', _} ->
|
||||
ok
|
||||
end.
|
||||
|
||||
set_monitor_and_timer(#sip_session{socket = #sip_socket{type = Type,
|
||||
pid = Pid} = SIPSock,
|
||||
conn_mref = MRef,
|
||||
expires = Expires,
|
||||
us = {_, LServer},
|
||||
contact = {_, _, Params}} = Session,
|
||||
IsOutboundSupported) ->
|
||||
RegTRef = set_timer(Session, Expires),
|
||||
Session1 = Session#sip_session{reg_tref = RegTRef},
|
||||
if IsOutboundSupported ->
|
||||
case get_ob_params(Params) of
|
||||
error ->
|
||||
Session1;
|
||||
{_, _} ->
|
||||
FlowTimeout = get_flow_timeout(LServer, SIPSock),
|
||||
FlowTRef = set_timer(Session1, FlowTimeout),
|
||||
NewMRef = if Type == udp -> MRef;
|
||||
true -> erlang:monitor(process, Pid)
|
||||
end,
|
||||
Session1#sip_session{conn_mref = NewMRef,
|
||||
flow_tref = FlowTRef}
|
||||
end;
|
||||
true ->
|
||||
Session1
|
||||
end.
|
||||
|
||||
set_timer(#sip_session{us = US}, Timeout) ->
|
||||
erlang:start_timer(Timeout * 1000, self(), US).
|
||||
|
||||
close_socket(#sip_session{socket = SIPSocket}) ->
|
||||
if SIPSocket#sip_socket.type /= udp ->
|
||||
esip_socket:close(SIPSocket);
|
||||
true ->
|
||||
ok
|
||||
end.
|
||||
|
||||
delete_session(#sip_session{reg_tref = RegTRef,
|
||||
flow_tref = FlowTRef,
|
||||
conn_mref = MRef} = Session) ->
|
||||
erlang:cancel_timer(RegTRef),
|
||||
catch erlang:cancel_timer(FlowTRef),
|
||||
catch erlang:demonitor(MRef, [flush]),
|
||||
mnesia:dirty_delete_object(Session).
|
||||
|
||||
process_ping(SIPSocket) ->
|
||||
ErrResponse = if SIPSocket#sip_socket.type == udp -> pang;
|
||||
true -> drop
|
||||
end,
|
||||
Sessions = mnesia:dirty_index_read(
|
||||
sip_session, SIPSocket, #sip_session.socket),
|
||||
lists:foldl(
|
||||
fun(#sip_session{flow_tref = TRef,
|
||||
us = {_, LServer}} = Session, _)
|
||||
when TRef /= undefined ->
|
||||
erlang:cancel_timer(TRef),
|
||||
mnesia:dirty_delete_object(Session),
|
||||
Timeout = get_flow_timeout(LServer, SIPSocket),
|
||||
NewTRef = set_timer(Session, Timeout),
|
||||
case mnesia:dirty_write(
|
||||
Session#sip_session{flow_tref = NewTRef}) of
|
||||
ok ->
|
||||
pong;
|
||||
_Err ->
|
||||
pang
|
||||
end;
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, ErrResponse, Sessions).
|
||||
|
||||
+122
-4
@@ -46,7 +46,7 @@
|
||||
lbday, ctry, lctry, locality, llocality, email, lemail,
|
||||
orgname, lorgname, orgunit, lorgunit}).
|
||||
|
||||
-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()},
|
||||
-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(),
|
||||
vcard = #xmlel{} :: xmlel()}).
|
||||
|
||||
-define(PROCNAME, ejabberd_mod_vcard).
|
||||
@@ -186,6 +186,11 @@ process_sm_iq(From, To,
|
||||
error ->
|
||||
IQ#iq{type = error,
|
||||
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
|
||||
[] ->
|
||||
IQ#iq{type = result,
|
||||
sub_el = [#xmlel{name = <<"vCard">>,
|
||||
attrs = [{<<"xmlns">>, ?NS_VCARD}],
|
||||
children = []}]};
|
||||
Els -> IQ#iq{type = result, sub_el = Els}
|
||||
end
|
||||
end.
|
||||
@@ -212,6 +217,15 @@ get_vcard(LUser, LServer, odbc) ->
|
||||
end;
|
||||
{selected, [<<"vcard">>], []} -> [];
|
||||
_ -> error
|
||||
end;
|
||||
get_vcard(LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of
|
||||
{ok, R} ->
|
||||
[R#vcard.vcard];
|
||||
{error, notfound} ->
|
||||
[];
|
||||
_ ->
|
||||
error
|
||||
end.
|
||||
|
||||
set_vcard(User, LServer, VCARD) ->
|
||||
@@ -289,6 +303,34 @@ set_vcard(User, LServer, VCARD) ->
|
||||
lorgunit = LOrgUnit})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
riak ->
|
||||
US = {LUser, LServer},
|
||||
ejabberd_riak:put(#vcard{us = US, vcard = VCARD},
|
||||
vcard_schema(),
|
||||
[{'2i', [{<<"user">>, User},
|
||||
{<<"luser">>, LUser},
|
||||
{<<"fn">>, FN},
|
||||
{<<"lfn">>, LFN},
|
||||
{<<"family">>, Family},
|
||||
{<<"lfamily">>, LFamily},
|
||||
{<<"given">>, Given},
|
||||
{<<"lgiven">>, LGiven},
|
||||
{<<"middle">>, Middle},
|
||||
{<<"lmiddle">>, LMiddle},
|
||||
{<<"nickname">>, Nickname},
|
||||
{<<"lnickname">>, LNickname},
|
||||
{<<"bday">>, BDay},
|
||||
{<<"lbday">>, LBDay},
|
||||
{<<"ctry">>, CTRY},
|
||||
{<<"lctry">>, LCTRY},
|
||||
{<<"locality">>, Locality},
|
||||
{<<"llocality">>, LLocality},
|
||||
{<<"email">>, EMail},
|
||||
{<<"lemail">>, LEMail},
|
||||
{<<"orgname">>, OrgName},
|
||||
{<<"lorgname">>, LOrgName},
|
||||
{<<"orgunit">>, OrgUnit},
|
||||
{<<"lorgunit">>, LOrgUnit}]}]);
|
||||
odbc ->
|
||||
Username = ejabberd_odbc:escape(User),
|
||||
LUsername = ejabberd_odbc:escape(LUser),
|
||||
@@ -687,14 +729,18 @@ search(LServer, MatchSpec, AllowReturnAll, odbc) ->
|
||||
Rs;
|
||||
Error -> ?ERROR_MSG("~p", [Error]), []
|
||||
end
|
||||
end.
|
||||
end;
|
||||
search(_LServer, _MatchSpec, _AllowReturnAll, riak) ->
|
||||
[].
|
||||
|
||||
make_matchspec(LServer, Data, mnesia) ->
|
||||
GlobMatch = #vcard_search{_ = '_'},
|
||||
Match = filter_fields(Data, GlobMatch, LServer, mnesia),
|
||||
Match;
|
||||
make_matchspec(LServer, Data, odbc) ->
|
||||
filter_fields(Data, <<"">>, LServer, odbc).
|
||||
filter_fields(Data, <<"">>, LServer, odbc);
|
||||
make_matchspec(_LServer, _Data, riak) ->
|
||||
[].
|
||||
|
||||
filter_fields([], Match, _LServer, mnesia) -> Match;
|
||||
filter_fields([], Match, _LServer, odbc) ->
|
||||
@@ -884,7 +930,9 @@ remove_user(LUser, LServer, odbc) ->
|
||||
[[<<"delete from vcard where username='">>,
|
||||
Username, <<"';">>],
|
||||
[<<"delete from vcard_search where lusername='">>,
|
||||
Username, <<"';">>]]).
|
||||
Username, <<"';">>]]);
|
||||
remove_user(LUser, LServer, riak) ->
|
||||
{atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}.
|
||||
|
||||
update_tables() ->
|
||||
update_vcard_table(),
|
||||
@@ -930,6 +978,9 @@ update_vcard_search_table() ->
|
||||
mnesia:transform_table(vcard_search, ignore, Fields)
|
||||
end.
|
||||
|
||||
vcard_schema() ->
|
||||
{record_info(fields, vcard), #vcard{}}.
|
||||
|
||||
export(_Server) ->
|
||||
[{vcard,
|
||||
fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD})
|
||||
@@ -1039,5 +1090,72 @@ import(_LServer, mnesia, #vcard{} = VCard) ->
|
||||
mnesia:dirty_write(VCard);
|
||||
import(_LServer, mnesia, #vcard_search{} = S) ->
|
||||
mnesia:dirty_write(S);
|
||||
import(_LServer, riak, #vcard{us = {LUser, _}, vcard = El} = VCard) ->
|
||||
FN = xml:get_path_s(El, [{elem, <<"FN">>}, cdata]),
|
||||
Family = xml:get_path_s(El,
|
||||
[{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
|
||||
Given = xml:get_path_s(El,
|
||||
[{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
|
||||
Middle = xml:get_path_s(El,
|
||||
[{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
|
||||
Nickname = xml:get_path_s(El,
|
||||
[{elem, <<"NICKNAME">>}, cdata]),
|
||||
BDay = xml:get_path_s(El,
|
||||
[{elem, <<"BDAY">>}, cdata]),
|
||||
CTRY = xml:get_path_s(El,
|
||||
[{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
|
||||
Locality = xml:get_path_s(El,
|
||||
[{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
|
||||
cdata]),
|
||||
EMail1 = xml:get_path_s(El,
|
||||
[{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]),
|
||||
EMail2 = xml:get_path_s(El,
|
||||
[{elem, <<"EMAIL">>}, cdata]),
|
||||
OrgName = xml:get_path_s(El,
|
||||
[{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
|
||||
OrgUnit = xml:get_path_s(El,
|
||||
[{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
|
||||
EMail = case EMail1 of
|
||||
<<"">> -> EMail2;
|
||||
_ -> EMail1
|
||||
end,
|
||||
LFN = string2lower(FN),
|
||||
LFamily = string2lower(Family),
|
||||
LGiven = string2lower(Given),
|
||||
LMiddle = string2lower(Middle),
|
||||
LNickname = string2lower(Nickname),
|
||||
LBDay = string2lower(BDay),
|
||||
LCTRY = string2lower(CTRY),
|
||||
LLocality = string2lower(Locality),
|
||||
LEMail = string2lower(EMail),
|
||||
LOrgName = string2lower(OrgName),
|
||||
LOrgUnit = string2lower(OrgUnit),
|
||||
ejabberd_riak:put(VCard, vcard_schema(),
|
||||
[{'2i', [{<<"user">>, LUser},
|
||||
{<<"luser">>, LUser},
|
||||
{<<"fn">>, FN},
|
||||
{<<"lfn">>, LFN},
|
||||
{<<"family">>, Family},
|
||||
{<<"lfamily">>, LFamily},
|
||||
{<<"given">>, Given},
|
||||
{<<"lgiven">>, LGiven},
|
||||
{<<"middle">>, Middle},
|
||||
{<<"lmiddle">>, LMiddle},
|
||||
{<<"nickname">>, Nickname},
|
||||
{<<"lnickname">>, LNickname},
|
||||
{<<"bday">>, BDay},
|
||||
{<<"lbday">>, LBDay},
|
||||
{<<"ctry">>, CTRY},
|
||||
{<<"lctry">>, LCTRY},
|
||||
{<<"locality">>, Locality},
|
||||
{<<"llocality">>, LLocality},
|
||||
{<<"email">>, EMail},
|
||||
{<<"lemail">>, LEMail},
|
||||
{<<"orgname">>, OrgName},
|
||||
{<<"lorgname">>, LOrgName},
|
||||
{<<"orgunit">>, OrgUnit},
|
||||
{<<"lorgunit">>, LOrgUnit}]}]);
|
||||
import(_LServer, riak, #vcard_search{}) ->
|
||||
ok;
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
@@ -88,6 +88,10 @@ add_xupdate(LUser, LServer, Hash, mnesia) ->
|
||||
hash = Hash})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
add_xupdate(LUser, LServer, Hash, riak) ->
|
||||
{atomic, ejabberd_riak:put(#vcard_xupdate{us = {LUser, LServer},
|
||||
hash = Hash},
|
||||
vcard_xupdate_schema())};
|
||||
add_xupdate(LUser, LServer, Hash, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
SHash = ejabberd_odbc:escape(Hash),
|
||||
@@ -109,6 +113,12 @@ get_xupdate(LUser, LServer, mnesia) ->
|
||||
[#vcard_xupdate{hash = Hash}] -> Hash;
|
||||
_ -> undefined
|
||||
end;
|
||||
get_xupdate(LUser, LServer, riak) ->
|
||||
case ejabberd_riak:get(vcard_xupdate, vcard_xupdate_schema(),
|
||||
{LUser, LServer}) of
|
||||
{ok, #vcard_xupdate{hash = Hash}} -> Hash;
|
||||
_ -> undefined
|
||||
end;
|
||||
get_xupdate(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
case ejabberd_odbc:sql_query(LServer,
|
||||
@@ -129,6 +139,8 @@ remove_xupdate(LUser, LServer, mnesia) ->
|
||||
mnesia:delete({vcard_xupdate, {LUser, LServer}})
|
||||
end,
|
||||
mnesia:transaction(F);
|
||||
remove_xupdate(LUser, LServer, riak) ->
|
||||
{atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})};
|
||||
remove_xupdate(LUser, LServer, odbc) ->
|
||||
Username = ejabberd_odbc:escape(LUser),
|
||||
F = fun () ->
|
||||
@@ -172,6 +184,9 @@ build_xphotoel(User, Host) ->
|
||||
attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}],
|
||||
children = PhotoEl}.
|
||||
|
||||
vcard_xupdate_schema() ->
|
||||
{record_info(fields, vcard_xupdate), #vcard_xupdate{}}.
|
||||
|
||||
update_table() ->
|
||||
Fields = record_info(fields, vcard_xupdate),
|
||||
case mnesia:table_info(vcard_xupdate, attributes) of
|
||||
@@ -212,5 +227,7 @@ import(LServer) ->
|
||||
|
||||
import(_LServer, mnesia, #vcard_xupdate{} = R) ->
|
||||
mnesia:dirty_write(R);
|
||||
import(_LServer, riak, #vcard_xupdate{} = R) ->
|
||||
ejabberd_riak:put(R, vcard_xupdate_schema());
|
||||
import(_, _, _) ->
|
||||
pass.
|
||||
|
||||
@@ -1317,6 +1317,7 @@ get_items(NodeId, _From,
|
||||
first = <<"modification@", F/binary>>,
|
||||
last = <<"modification@", (jlib:i2l(L))/binary>>},
|
||||
{result, {[raw_to_item(NodeId, RItem) || RItem <- RItems], RsmOut}};
|
||||
[] -> {result, {[], #rsm_out{count = Count}}};
|
||||
0 -> {result, {[], #rsm_out{count = Count}}}
|
||||
end;
|
||||
_ -> {result, {[], none}}
|
||||
|
||||
+12
-4
@@ -97,10 +97,14 @@ update_t(Table, Fields, Vals, Where) ->
|
||||
of
|
||||
{updated, 1} -> ok;
|
||||
_ ->
|
||||
ejabberd_odbc:sql_query_t([<<"insert into ">>, Table,
|
||||
Res = ejabberd_odbc:sql_query_t([<<"insert into ">>, Table,
|
||||
<<"(">>, join(Fields, <<", ">>),
|
||||
<<") values ('">>, join(Vals, <<"', '">>),
|
||||
<<"');">>])
|
||||
<<"');">>]),
|
||||
case Res of
|
||||
{updated,1} -> ok;
|
||||
_ -> Res
|
||||
end
|
||||
end.
|
||||
|
||||
update(LServer, Table, Fields, Vals, Where) ->
|
||||
@@ -115,10 +119,14 @@ update(LServer, Table, Fields, Vals, Where) ->
|
||||
of
|
||||
{updated, 1} -> ok;
|
||||
_ ->
|
||||
ejabberd_odbc:sql_query(LServer,
|
||||
Res = ejabberd_odbc:sql_query(LServer,
|
||||
[<<"insert into ">>, Table, <<"(">>,
|
||||
join(Fields, <<", ">>), <<") values ('">>,
|
||||
join(Vals, <<"', '">>), <<"');">>])
|
||||
join(Vals, <<"', '">>), <<"');">>]),
|
||||
case Res of
|
||||
{updated,1} -> ok;
|
||||
_ -> Res
|
||||
end
|
||||
end.
|
||||
|
||||
%% F can be either a fun or a list of queries
|
||||
|
||||
-848
@@ -1,848 +0,0 @@
|
||||
%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%% compliance with the License. You should have received a copy of the
|
||||
%% Erlang Public License along with this software. If not, it can be
|
||||
%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%
|
||||
%% Software distributed under the License is distributed on an "AS IS"
|
||||
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%% the License for the specific language governing rights and limitations
|
||||
%% under the License.
|
||||
%%
|
||||
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
|
||||
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
|
||||
%% AB. All Rights Reserved.''
|
||||
%%
|
||||
%% The code has been modified and improved by ProcessOne.
|
||||
%% Copyright 2007-2014, ProcessOne
|
||||
%%
|
||||
%% The change adds the following features:
|
||||
%% - You can send exit(priority_shutdown) to the p1_fsm process to
|
||||
%% terminate immediatetly. If the fsm trap_exit process flag has been
|
||||
%% set to true, the FSM terminate function will called.
|
||||
%% - You can pass the gen_fsm options to control resource usage.
|
||||
%% {max_queue, N} will exit the process with priority_shutdown
|
||||
%% - You can limit the time processing a message (TODO): If the
|
||||
%% message processing does not return in a given period of time, the
|
||||
%% process will be terminated.
|
||||
%% - You might customize the State data before sending it to error_logger
|
||||
%% in case of a crash (just export the function print_state/1)
|
||||
%% $Id$
|
||||
%%
|
||||
-module(p1_fsm).
|
||||
|
||||
%%%-----------------------------------------------------------------
|
||||
%%%
|
||||
%%% This state machine is somewhat more pure than state_lib. It is
|
||||
%%% still based on State dispatching (one function per state), but
|
||||
%%% allows a function handle_event to take care of events in all states.
|
||||
%%% It's not that pure anymore :( We also allow synchronized event sending.
|
||||
%%%
|
||||
%%% If the Parent process terminates the Module:terminate/2
|
||||
%%% function is called.
|
||||
%%%
|
||||
%%% The user module should export:
|
||||
%%%
|
||||
%%% init(Args)
|
||||
%%% ==> {ok, StateName, StateData}
|
||||
%%% {ok, StateName, StateData, Timeout}
|
||||
%%% ignore
|
||||
%%% {stop, Reason}
|
||||
%%%
|
||||
%%% StateName(Msg, StateData)
|
||||
%%%
|
||||
%%% ==> {next_state, NewStateName, NewStateData}
|
||||
%%% {next_state, NewStateName, NewStateData, Timeout}
|
||||
%%% {stop, Reason, NewStateData}
|
||||
%%% Reason = normal | shutdown | Term terminate(State) is called
|
||||
%%%
|
||||
%%% StateName(Msg, From, StateData)
|
||||
%%%
|
||||
%%% ==> {next_state, NewStateName, NewStateData}
|
||||
%%% {next_state, NewStateName, NewStateData, Timeout}
|
||||
%%% {reply, Reply, NewStateName, NewStateData}
|
||||
%%% {reply, Reply, NewStateName, NewStateData, Timeout}
|
||||
%%% {stop, Reason, NewStateData}
|
||||
%%% Reason = normal | shutdown | Term terminate(State) is called
|
||||
%%%
|
||||
%%% handle_event(Msg, StateName, StateData)
|
||||
%%%
|
||||
%%% ==> {next_state, NewStateName, NewStateData}
|
||||
%%% {next_state, NewStateName, NewStateData, Timeout}
|
||||
%%% {stop, Reason, Reply, NewStateData}
|
||||
%%% {stop, Reason, NewStateData}
|
||||
%%% Reason = normal | shutdown | Term terminate(State) is called
|
||||
%%%
|
||||
%%% handle_sync_event(Msg, From, StateName, StateData)
|
||||
%%%
|
||||
%%% ==> {next_state, NewStateName, NewStateData}
|
||||
%%% {next_state, NewStateName, NewStateData, Timeout}
|
||||
%%% {reply, Reply, NewStateName, NewStateData}
|
||||
%%% {reply, Reply, NewStateName, NewStateData, Timeout}
|
||||
%%% {stop, Reason, Reply, NewStateData}
|
||||
%%% {stop, Reason, NewStateData}
|
||||
%%% Reason = normal | shutdown | Term terminate(State) is called
|
||||
%%%
|
||||
%%% handle_info(Info, StateName) (e.g. {'EXIT', P, R}, {nodedown, N}, ...
|
||||
%%%
|
||||
%%% ==> {next_state, NewStateName, NewStateData}
|
||||
%%% {next_state, NewStateName, NewStateData, Timeout}
|
||||
%%% {stop, Reason, NewStateData}
|
||||
%%% Reason = normal | shutdown | Term terminate(State) is called
|
||||
%%%
|
||||
%%% terminate(Reason, StateName, StateData) Let the user module clean up
|
||||
%%% always called when server terminates
|
||||
%%%
|
||||
%%% ==> the return value is ignored
|
||||
%%%
|
||||
%%%
|
||||
%%% The work flow (of the fsm) can be described as follows:
|
||||
%%%
|
||||
%%% User module fsm
|
||||
%%% ----------- -------
|
||||
%%% start -----> start
|
||||
%%% init <----- .
|
||||
%%%
|
||||
%%% loop
|
||||
%%% StateName <----- .
|
||||
%%%
|
||||
%%% handle_event <----- .
|
||||
%%%
|
||||
%%% handle__sunc_event <----- .
|
||||
%%%
|
||||
%%% handle_info <----- .
|
||||
%%%
|
||||
%%% terminate <----- .
|
||||
%%%
|
||||
%%%
|
||||
%%% ---------------------------------------------------
|
||||
|
||||
-export([start/3, start/4,
|
||||
start_link/3, start_link/4,
|
||||
send_event/2, sync_send_event/2, sync_send_event/3,
|
||||
send_all_state_event/2,
|
||||
sync_send_all_state_event/2, sync_send_all_state_event/3,
|
||||
reply/2,
|
||||
start_timer/2,send_event_after/2,cancel_timer/1,
|
||||
enter_loop/4, enter_loop/5, enter_loop/6, wake_hib/7]).
|
||||
|
||||
%% Internal exports
|
||||
-export([init_it/6, print_event/3,
|
||||
system_continue/3,
|
||||
system_terminate/4,
|
||||
system_code_change/4,
|
||||
format_status/2]).
|
||||
|
||||
-import(error_logger , [format/2]).
|
||||
|
||||
%%% Internal gen_fsm state
|
||||
%%% This state is used to defined resource control values:
|
||||
-record(limits, {max_queue :: non_neg_integer()}).
|
||||
|
||||
%%% ---------------------------------------------------
|
||||
%%% Interface functions.
|
||||
%%% ---------------------------------------------------
|
||||
|
||||
-callback init(Args :: term()) ->
|
||||
{ok, StateName :: atom(), StateData :: term()} |
|
||||
{ok, StateName :: atom(), StateData :: term(), timeout() | hibernate} |
|
||||
{stop, Reason :: term()} | ignore.
|
||||
-callback handle_event(Event :: term(), StateName :: atom(),
|
||||
StateData :: term()) ->
|
||||
{next_state, NextStateName :: atom(), NewStateData :: term()} |
|
||||
{next_state, NextStateName :: atom(), NewStateData :: term(),
|
||||
timeout() | hibernate} |
|
||||
{migrate, NewStateData :: term(),
|
||||
{Node :: atom(), M :: atom(), F :: atom(), A :: list()},
|
||||
Timeout :: timeout()} |
|
||||
{stop, Reason :: term(), NewStateData :: term()}.
|
||||
-callback handle_sync_event(Event :: term(), From :: {pid(), Tag :: term()},
|
||||
StateName :: atom(), StateData :: term()) ->
|
||||
{reply, Reply :: term(), NextStateName :: atom(), NewStateData :: term()} |
|
||||
{reply, Reply :: term(), NextStateName :: atom(), NewStateData :: term(),
|
||||
timeout() | hibernate} |
|
||||
{next_state, NextStateName :: atom(), NewStateData :: term()} |
|
||||
{next_state, NextStateName :: atom(), NewStateData :: term(),
|
||||
timeout() | hibernate} |
|
||||
{migrate, NewStateData :: term(),
|
||||
{Node :: atom(), M :: atom(), F :: atom(), A :: list()},
|
||||
Timeout :: timeout()} |
|
||||
{stop, Reason :: term(), Reply :: term(), NewStateData :: term()} |
|
||||
{stop, Reason :: term(), NewStateData :: term()}.
|
||||
-callback handle_info(Info :: term(), StateName :: atom(),
|
||||
StateData :: term()) ->
|
||||
{next_state, NextStateName :: atom(), NewStateData :: term()} |
|
||||
{next_state, NextStateName :: atom(), NewStateData :: term(),
|
||||
timeout() | hibernate} |
|
||||
{migrate, NewStateData :: term(),
|
||||
{Node :: atom(), M :: atom(), F :: atom(), A :: list()},
|
||||
Timeout :: timeout()} |
|
||||
{stop, Reason :: normal | term(), NewStateData :: term()}.
|
||||
-callback terminate(Reason :: normal | shutdown | {shutdown, term()}
|
||||
| term(), StateName :: atom(), StateData :: term()) ->
|
||||
term().
|
||||
-callback code_change(OldVsn :: term() | {down, term()}, StateName :: atom(),
|
||||
StateData :: term(), Extra :: term()) ->
|
||||
{ok, NextStateName :: atom(), NewStateData :: term()}.
|
||||
|
||||
%%% ---------------------------------------------------
|
||||
%%% Starts a generic state machine.
|
||||
%%% start(Mod, Args, Options)
|
||||
%%% start(Name, Mod, Args, Options)
|
||||
%%% start_link(Mod, Args, Options)
|
||||
%%% start_link(Name, Mod, Args, Options) where:
|
||||
%%% Name ::= {local, atom()} | {global, atom()}
|
||||
%%% Mod ::= atom(), callback module implementing the 'real' fsm
|
||||
%%% Args ::= term(), init arguments (to Mod:init/1)
|
||||
%%% Options ::= [{debug, [Flag]}]
|
||||
%%% Flag ::= trace | log | {logfile, File} | statistics | debug
|
||||
%%% (debug == log && statistics)
|
||||
%%% Returns: {ok, Pid} |
|
||||
%%% {error, {already_started, Pid}} |
|
||||
%%% {error, Reason}
|
||||
%%% ---------------------------------------------------
|
||||
start(Mod, Args, Options) ->
|
||||
gen:start(?MODULE, nolink, Mod, Args, Options).
|
||||
|
||||
start(Name, Mod, Args, Options) ->
|
||||
gen:start(?MODULE, nolink, Name, Mod, Args, Options).
|
||||
|
||||
start_link(Mod, Args, Options) ->
|
||||
gen:start(?MODULE, link, Mod, Args, Options).
|
||||
|
||||
start_link(Name, Mod, Args, Options) ->
|
||||
gen:start(?MODULE, link, Name, Mod, Args, Options).
|
||||
|
||||
|
||||
send_event({global, Name}, Event) ->
|
||||
catch global:send(Name, {'$gen_event', Event}),
|
||||
ok;
|
||||
send_event(Name, Event) ->
|
||||
Name ! {'$gen_event', Event},
|
||||
ok.
|
||||
|
||||
sync_send_event(Name, Event) ->
|
||||
case catch gen:call(Name, '$gen_sync_event', Event) of
|
||||
{ok,Res} ->
|
||||
Res;
|
||||
{'EXIT',Reason} ->
|
||||
exit({Reason, {?MODULE, sync_send_event, [Name, Event]}})
|
||||
end.
|
||||
|
||||
sync_send_event(Name, Event, Timeout) ->
|
||||
case catch gen:call(Name, '$gen_sync_event', Event, Timeout) of
|
||||
{ok,Res} ->
|
||||
Res;
|
||||
{'EXIT',Reason} ->
|
||||
exit({Reason, {?MODULE, sync_send_event, [Name, Event, Timeout]}})
|
||||
end.
|
||||
|
||||
send_all_state_event({global, Name}, Event) ->
|
||||
catch global:send(Name, {'$gen_all_state_event', Event}),
|
||||
ok;
|
||||
send_all_state_event(Name, Event) ->
|
||||
Name ! {'$gen_all_state_event', Event},
|
||||
ok.
|
||||
|
||||
sync_send_all_state_event(Name, Event) ->
|
||||
case catch gen:call(Name, '$gen_sync_all_state_event', Event) of
|
||||
{ok,Res} ->
|
||||
Res;
|
||||
{'EXIT',Reason} ->
|
||||
exit({Reason, {?MODULE, sync_send_all_state_event, [Name, Event]}})
|
||||
end.
|
||||
|
||||
sync_send_all_state_event(Name, Event, Timeout) ->
|
||||
case catch gen:call(Name, '$gen_sync_all_state_event', Event, Timeout) of
|
||||
{ok,Res} ->
|
||||
Res;
|
||||
{'EXIT',Reason} ->
|
||||
exit({Reason, {?MODULE, sync_send_all_state_event,
|
||||
[Name, Event, Timeout]}})
|
||||
end.
|
||||
|
||||
%% Designed to be only callable within one of the callbacks
|
||||
%% hence using the self() of this instance of the process.
|
||||
%% This is to ensure that timers don't go astray in global
|
||||
%% e.g. when straddling a failover, or turn up in a restarted
|
||||
%% instance of the process.
|
||||
|
||||
%% Returns Ref, sends event {timeout,Ref,Msg} after Time
|
||||
%% to the (then) current state.
|
||||
start_timer(Time, Msg) ->
|
||||
erlang:start_timer(Time, self(), {'$gen_timer', Msg}).
|
||||
|
||||
%% Returns Ref, sends Event after Time to the (then) current state.
|
||||
send_event_after(Time, Event) ->
|
||||
erlang:start_timer(Time, self(), {'$gen_event', Event}).
|
||||
|
||||
%% Returns the remaing time for the timer if Ref referred to
|
||||
%% an active timer/send_event_after, false otherwise.
|
||||
cancel_timer(Ref) ->
|
||||
case erlang:cancel_timer(Ref) of
|
||||
false ->
|
||||
receive {timeout, Ref, _} -> 0
|
||||
after 0 -> false
|
||||
end;
|
||||
RemainingTime ->
|
||||
RemainingTime
|
||||
end.
|
||||
|
||||
%% enter_loop/4,5,6
|
||||
%% Makes an existing process into a gen_fsm.
|
||||
%% The calling process will enter the gen_fsm receive loop and become a
|
||||
%% gen_fsm process.
|
||||
%% The process *must* have been started using one of the start functions
|
||||
%% in proc_lib, see proc_lib(3).
|
||||
%% The user is responsible for any initialization of the process,
|
||||
%% including registering a name for it.
|
||||
enter_loop(Mod, Options, StateName, StateData) ->
|
||||
enter_loop(Mod, Options, StateName, StateData, self(), infinity).
|
||||
|
||||
enter_loop(Mod, Options, StateName, StateData, ServerName = {_,_}) ->
|
||||
enter_loop(Mod, Options, StateName, StateData, ServerName,infinity);
|
||||
enter_loop(Mod, Options, StateName, StateData, Timeout) ->
|
||||
enter_loop(Mod, Options, StateName, StateData, self(), Timeout).
|
||||
|
||||
enter_loop(Mod, Options, StateName, StateData, ServerName, Timeout) ->
|
||||
Name = get_proc_name(ServerName),
|
||||
Parent = get_parent(),
|
||||
Debug = gen:debug_options(Options),
|
||||
Limits = limit_options(Options),
|
||||
Queue = queue:new(),
|
||||
QueueLen = 0,
|
||||
loop(Parent, Name, StateName, StateData, Mod, Timeout, Debug,
|
||||
Limits, Queue, QueueLen).
|
||||
|
||||
get_proc_name(Pid) when is_pid(Pid) ->
|
||||
Pid;
|
||||
get_proc_name({local, Name}) ->
|
||||
case process_info(self(), registered_name) of
|
||||
{registered_name, Name} ->
|
||||
Name;
|
||||
{registered_name, _Name} ->
|
||||
exit(process_not_registered);
|
||||
[] ->
|
||||
exit(process_not_registered)
|
||||
end;
|
||||
get_proc_name({global, Name}) ->
|
||||
case global:whereis_name(Name) of
|
||||
undefined ->
|
||||
exit(process_not_registered_globally);
|
||||
Pid when Pid==self() ->
|
||||
Name;
|
||||
_Pid ->
|
||||
exit(process_not_registered_globally)
|
||||
end.
|
||||
|
||||
get_parent() ->
|
||||
case get('$ancestors') of
|
||||
[Parent | _] when is_pid(Parent) ->
|
||||
Parent;
|
||||
[Parent | _] when is_atom(Parent) ->
|
||||
name_to_pid(Parent);
|
||||
_ ->
|
||||
exit(process_was_not_started_by_proc_lib)
|
||||
end.
|
||||
|
||||
name_to_pid(Name) ->
|
||||
case whereis(Name) of
|
||||
undefined ->
|
||||
case global:whereis_name(Name) of
|
||||
undefined ->
|
||||
exit(could_not_find_registerd_name);
|
||||
Pid ->
|
||||
Pid
|
||||
end;
|
||||
Pid ->
|
||||
Pid
|
||||
end.
|
||||
|
||||
%%% ---------------------------------------------------
|
||||
%%% Initiate the new process.
|
||||
%%% Register the name using the Rfunc function
|
||||
%%% Calls the Mod:init/Args function.
|
||||
%%% Finally an acknowledge is sent to Parent and the main
|
||||
%%% loop is entered.
|
||||
%%% ---------------------------------------------------
|
||||
init_it(Starter, self, Name, Mod, Args, Options) ->
|
||||
init_it(Starter, self(), Name, Mod, Args, Options);
|
||||
init_it(Starter, Parent, Name0, Mod, Args, Options) ->
|
||||
Name = name(Name0),
|
||||
Debug = gen:debug_options(Options),
|
||||
Limits = limit_options(Options),
|
||||
Queue = queue:new(),
|
||||
QueueLen = 0,
|
||||
case catch Mod:init(Args) of
|
||||
{ok, StateName, StateData} ->
|
||||
proc_lib:init_ack(Starter, {ok, self()}),
|
||||
loop(Parent, Name, StateName, StateData, Mod, infinity, Debug, Limits, Queue, QueueLen);
|
||||
{ok, StateName, StateData, Timeout} ->
|
||||
proc_lib:init_ack(Starter, {ok, self()}),
|
||||
loop(Parent, Name, StateName, StateData, Mod, Timeout, Debug, Limits, Queue, QueueLen);
|
||||
{stop, Reason} ->
|
||||
proc_lib:init_ack(Starter, {error, Reason}),
|
||||
exit(Reason);
|
||||
ignore ->
|
||||
proc_lib:init_ack(Starter, ignore),
|
||||
exit(normal);
|
||||
{'EXIT', Reason} ->
|
||||
proc_lib:init_ack(Starter, {error, Reason}),
|
||||
exit(Reason);
|
||||
Else ->
|
||||
Error = {bad_return_value, Else},
|
||||
proc_lib:init_ack(Starter, {error, Error}),
|
||||
exit(Error)
|
||||
end.
|
||||
|
||||
name({local,Name}) -> Name;
|
||||
name({global,Name}) -> Name;
|
||||
name(Pid) when is_pid(Pid) -> Pid.
|
||||
|
||||
%%-----------------------------------------------------------------
|
||||
%% The MAIN loop
|
||||
%%-----------------------------------------------------------------
|
||||
loop(Parent, Name, StateName, StateData, Mod, hibernate, Debug,
|
||||
Limits, Queue, QueueLen)
|
||||
when QueueLen > 0 ->
|
||||
case queue:out(Queue) of
|
||||
{{value, Msg}, Queue1} ->
|
||||
decode_msg(Msg, Parent, Name, StateName, StateData, Mod, hibernate,
|
||||
Debug, Limits, Queue1, QueueLen - 1, false);
|
||||
{empty, _} ->
|
||||
Reason = internal_queue_error,
|
||||
error_info(Mod, Reason, Name, hibernate, StateName, StateData, Debug),
|
||||
exit(Reason)
|
||||
end;
|
||||
loop(Parent, Name, StateName, StateData, Mod, hibernate, Debug,
|
||||
Limits, _Queue, _QueueLen) ->
|
||||
proc_lib:hibernate(?MODULE,wake_hib,
|
||||
[Parent, Name, StateName, StateData, Mod,
|
||||
Debug, Limits]);
|
||||
%% First we test if we have reach a defined limit ...
|
||||
loop(Parent, Name, StateName, StateData, Mod, Time, Debug,
|
||||
Limits, Queue, QueueLen) ->
|
||||
try
|
||||
message_queue_len(Limits, QueueLen)
|
||||
%% TODO: We can add more limit checking here...
|
||||
catch
|
||||
{process_limit, Limit} ->
|
||||
Reason = {process_limit, Limit},
|
||||
Msg = {'EXIT', Parent, {error, {process_limit, Limit}}},
|
||||
terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug)
|
||||
end,
|
||||
process_message(Parent, Name, StateName, StateData,
|
||||
Mod, Time, Debug, Limits, Queue, QueueLen).
|
||||
%% ... then we can process a new message:
|
||||
process_message(Parent, Name, StateName, StateData, Mod, Time, Debug,
|
||||
Limits, Queue, QueueLen) ->
|
||||
{Msg, Queue1, QueueLen1} = collect_messages(Queue, QueueLen, Time),
|
||||
decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time,
|
||||
Debug, Limits, Queue1, QueueLen1, false).
|
||||
|
||||
collect_messages(Queue, QueueLen, Time) ->
|
||||
receive
|
||||
Input ->
|
||||
case Input of
|
||||
{'EXIT', _Parent, priority_shutdown} ->
|
||||
{Input, Queue, QueueLen};
|
||||
_ ->
|
||||
collect_messages(
|
||||
queue:in(Input, Queue), QueueLen + 1, Time)
|
||||
end
|
||||
after 0 ->
|
||||
case queue:out(Queue) of
|
||||
{{value, Msg}, Queue1} ->
|
||||
{Msg, Queue1, QueueLen - 1};
|
||||
{empty, _} ->
|
||||
receive
|
||||
Input ->
|
||||
{Input, Queue, QueueLen}
|
||||
after Time ->
|
||||
{{'$gen_event', timeout}, Queue, QueueLen}
|
||||
end
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
wake_hib(Parent, Name, StateName, StateData, Mod, Debug,
|
||||
Limits) ->
|
||||
Msg = receive
|
||||
Input ->
|
||||
Input
|
||||
end,
|
||||
Queue = queue:new(),
|
||||
QueueLen = 0,
|
||||
decode_msg(Msg, Parent, Name, StateName, StateData, Mod, hibernate,
|
||||
Debug, Limits, Queue, QueueLen, true).
|
||||
|
||||
decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, Debug,
|
||||
Limits, Queue, QueueLen, Hib) ->
|
||||
put('$internal_queue_len', QueueLen),
|
||||
case Msg of
|
||||
{system, From, Req} ->
|
||||
sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
|
||||
[Name, StateName, StateData,
|
||||
Mod, Time, Limits, Queue, QueueLen], Hib);
|
||||
{'EXIT', Parent, Reason} ->
|
||||
terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug);
|
||||
_Msg when Debug == [] ->
|
||||
handle_msg(Msg, Parent, Name, StateName, StateData,
|
||||
Mod, Time, Limits, Queue, QueueLen);
|
||||
_Msg ->
|
||||
Debug1 = sys:handle_debug(Debug, fun print_event/3,
|
||||
{Name, StateName}, {in, Msg}),
|
||||
handle_msg(Msg, Parent, Name, StateName, StateData,
|
||||
Mod, Time, Debug1, Limits, Queue, QueueLen)
|
||||
end.
|
||||
|
||||
%%-----------------------------------------------------------------
|
||||
%% Callback functions for system messages handling.
|
||||
%%-----------------------------------------------------------------
|
||||
system_continue(Parent, Debug, [Name, StateName, StateData,
|
||||
Mod, Time, Limits, Queue, QueueLen]) ->
|
||||
loop(Parent, Name, StateName, StateData, Mod, Time, Debug,
|
||||
Limits, Queue, QueueLen).
|
||||
|
||||
-spec system_terminate(term(), _, _, [term(),...]) -> no_return().
|
||||
|
||||
system_terminate(Reason, _Parent, Debug,
|
||||
[Name, StateName, StateData, Mod, _Time, _Limits]) ->
|
||||
terminate(Reason, Name, [], Mod, StateName, StateData, Debug).
|
||||
|
||||
system_code_change([Name, StateName, StateData, Mod, Time,
|
||||
Limits, Queue, QueueLen],
|
||||
_Module, OldVsn, Extra) ->
|
||||
case catch Mod:code_change(OldVsn, StateName, StateData, Extra) of
|
||||
{ok, NewStateName, NewStateData} ->
|
||||
{ok, [Name, NewStateName, NewStateData, Mod, Time,
|
||||
Limits, Queue, QueueLen]};
|
||||
Else -> Else
|
||||
end.
|
||||
|
||||
%%-----------------------------------------------------------------
|
||||
%% Format debug messages. Print them as the call-back module sees
|
||||
%% them, not as the real erlang messages. Use trace for that.
|
||||
%%-----------------------------------------------------------------
|
||||
print_event(Dev, {in, Msg}, {Name, StateName}) ->
|
||||
case Msg of
|
||||
{'$gen_event', Event} ->
|
||||
io:format(Dev, "*DBG* ~p got event ~p in state ~w~n",
|
||||
[Name, Event, StateName]);
|
||||
{'$gen_all_state_event', Event} ->
|
||||
io:format(Dev,
|
||||
"*DBG* ~p got all_state_event ~p in state ~w~n",
|
||||
[Name, Event, StateName]);
|
||||
{timeout, Ref, {'$gen_timer', Message}} ->
|
||||
io:format(Dev,
|
||||
"*DBG* ~p got timer ~p in state ~w~n",
|
||||
[Name, {timeout, Ref, Message}, StateName]);
|
||||
{timeout, _Ref, {'$gen_event', Event}} ->
|
||||
io:format(Dev,
|
||||
"*DBG* ~p got timer ~p in state ~w~n",
|
||||
[Name, Event, StateName]);
|
||||
_ ->
|
||||
io:format(Dev, "*DBG* ~p got ~p in state ~w~n",
|
||||
[Name, Msg, StateName])
|
||||
end;
|
||||
print_event(Dev, {out, Msg, To, StateName}, Name) ->
|
||||
io:format(Dev, "*DBG* ~p sent ~p to ~w~n"
|
||||
" and switched to state ~w~n",
|
||||
[Name, Msg, To, StateName]);
|
||||
print_event(Dev, return, {Name, StateName}) ->
|
||||
io:format(Dev, "*DBG* ~p switched to state ~w~n",
|
||||
[Name, StateName]).
|
||||
|
||||
relay_messages(MRef, TRef, Clone, Queue) ->
|
||||
lists:foreach(
|
||||
fun(Msg) -> Clone ! Msg end,
|
||||
queue:to_list(Queue)),
|
||||
relay_messages(MRef, TRef, Clone).
|
||||
|
||||
relay_messages(MRef, TRef, Clone) ->
|
||||
receive
|
||||
{'DOWN', MRef, process, Clone, Reason} ->
|
||||
Reason;
|
||||
{'EXIT', _Parent, _Reason} ->
|
||||
{migrated, Clone};
|
||||
{timeout, TRef, timeout} ->
|
||||
{migrated, Clone};
|
||||
Msg ->
|
||||
Clone ! Msg,
|
||||
relay_messages(MRef, TRef, Clone)
|
||||
end.
|
||||
|
||||
handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time,
|
||||
Limits, Queue, QueueLen) -> %No debug here
|
||||
From = from(Msg),
|
||||
case catch dispatch(Msg, Mod, StateName, StateData) of
|
||||
{next_state, NStateName, NStateData} ->
|
||||
loop(Parent, Name, NStateName, NStateData,
|
||||
Mod, infinity, [], Limits, Queue, QueueLen);
|
||||
{next_state, NStateName, NStateData, Time1} ->
|
||||
loop(Parent, Name, NStateName, NStateData, Mod, Time1, [],
|
||||
Limits, Queue, QueueLen);
|
||||
{reply, Reply, NStateName, NStateData} when From =/= undefined ->
|
||||
reply(From, Reply),
|
||||
loop(Parent, Name, NStateName, NStateData,
|
||||
Mod, infinity, [], Limits, Queue, QueueLen);
|
||||
{reply, Reply, NStateName, NStateData, Time1} when From =/= undefined ->
|
||||
reply(From, Reply),
|
||||
loop(Parent, Name, NStateName, NStateData, Mod, Time1, [],
|
||||
Limits, Queue, QueueLen);
|
||||
{migrate, NStateData, {Node, M, F, A}, Time1} ->
|
||||
Reason = case catch rpc:call(Node, M, F, A, 5000) of
|
||||
{badrpc, _} = Err ->
|
||||
{migration_error, Err};
|
||||
{'EXIT', _} = Err ->
|
||||
{migration_error, Err};
|
||||
{error, _} = Err ->
|
||||
{migration_error, Err};
|
||||
{ok, Clone} ->
|
||||
process_flag(trap_exit, true),
|
||||
MRef = erlang:monitor(process, Clone),
|
||||
TRef = erlang:start_timer(Time1, self(), timeout),
|
||||
relay_messages(MRef, TRef, Clone, Queue);
|
||||
Reply ->
|
||||
{migration_error, {bad_reply, Reply}}
|
||||
end,
|
||||
terminate(Reason, Name, Msg, Mod, StateName, NStateData, []);
|
||||
{stop, Reason, NStateData} ->
|
||||
terminate(Reason, Name, Msg, Mod, StateName, NStateData, []);
|
||||
{stop, Reason, Reply, NStateData} when From =/= undefined ->
|
||||
{'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod,
|
||||
StateName, NStateData, [])),
|
||||
reply(From, Reply),
|
||||
exit(R);
|
||||
{'EXIT', What} ->
|
||||
terminate(What, Name, Msg, Mod, StateName, StateData, []);
|
||||
Reply ->
|
||||
terminate({bad_return_value, Reply},
|
||||
Name, Msg, Mod, StateName, StateData, [])
|
||||
end.
|
||||
|
||||
handle_msg(Msg, Parent, Name, StateName, StateData,
|
||||
Mod, _Time, Debug, Limits, Queue, QueueLen) ->
|
||||
From = from(Msg),
|
||||
case catch dispatch(Msg, Mod, StateName, StateData) of
|
||||
{next_state, NStateName, NStateData} ->
|
||||
Debug1 = sys:handle_debug(Debug, fun print_event/3,
|
||||
{Name, NStateName}, return),
|
||||
loop(Parent, Name, NStateName, NStateData,
|
||||
Mod, infinity, Debug1, Limits, Queue, QueueLen);
|
||||
{next_state, NStateName, NStateData, Time1} ->
|
||||
Debug1 = sys:handle_debug(Debug, fun print_event/3,
|
||||
{Name, NStateName}, return),
|
||||
loop(Parent, Name, NStateName, NStateData,
|
||||
Mod, Time1, Debug1, Limits, Queue, QueueLen);
|
||||
{reply, Reply, NStateName, NStateData} when From =/= undefined ->
|
||||
Debug1 = reply(Name, From, Reply, Debug, NStateName),
|
||||
loop(Parent, Name, NStateName, NStateData,
|
||||
Mod, infinity, Debug1, Limits, Queue, QueueLen);
|
||||
{reply, Reply, NStateName, NStateData, Time1} when From =/= undefined ->
|
||||
Debug1 = reply(Name, From, Reply, Debug, NStateName),
|
||||
loop(Parent, Name, NStateName, NStateData,
|
||||
Mod, Time1, Debug1, Limits, Queue, QueueLen);
|
||||
{migrate, NStateData, {Node, M, F, A}, Time1} ->
|
||||
Reason = case catch rpc:call(Node, M, F, A, Time1) of
|
||||
{badrpc, R} ->
|
||||
{migration_error, R};
|
||||
{'EXIT', R} ->
|
||||
{migration_error, R};
|
||||
{error, R} ->
|
||||
{migration_error, R};
|
||||
{ok, Clone} ->
|
||||
process_flag(trap_exit, true),
|
||||
MRef = erlang:monitor(process, Clone),
|
||||
TRef = erlang:start_timer(Time1, self(), timeout),
|
||||
relay_messages(MRef, TRef, Clone, Queue);
|
||||
Reply ->
|
||||
{migration_error, {bad_reply, Reply}}
|
||||
end,
|
||||
terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug);
|
||||
{stop, Reason, NStateData} ->
|
||||
terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug);
|
||||
{stop, Reason, Reply, NStateData} when From =/= undefined ->
|
||||
{'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod,
|
||||
StateName, NStateData, Debug)),
|
||||
reply(Name, From, Reply, Debug, StateName),
|
||||
exit(R);
|
||||
{'EXIT', What} ->
|
||||
terminate(What, Name, Msg, Mod, StateName, StateData, Debug);
|
||||
Reply ->
|
||||
terminate({bad_return_value, Reply},
|
||||
Name, Msg, Mod, StateName, StateData, Debug)
|
||||
end.
|
||||
|
||||
dispatch({'$gen_event', Event}, Mod, StateName, StateData) ->
|
||||
Mod:StateName(Event, StateData);
|
||||
dispatch({'$gen_all_state_event', Event}, Mod, StateName, StateData) ->
|
||||
Mod:handle_event(Event, StateName, StateData);
|
||||
dispatch({'$gen_sync_event', From, Event}, Mod, StateName, StateData) ->
|
||||
Mod:StateName(Event, From, StateData);
|
||||
dispatch({'$gen_sync_all_state_event', From, Event},
|
||||
Mod, StateName, StateData) ->
|
||||
Mod:handle_sync_event(Event, From, StateName, StateData);
|
||||
dispatch({timeout, Ref, {'$gen_timer', Msg}}, Mod, StateName, StateData) ->
|
||||
Mod:StateName({timeout, Ref, Msg}, StateData);
|
||||
dispatch({timeout, _Ref, {'$gen_event', Event}}, Mod, StateName, StateData) ->
|
||||
Mod:StateName(Event, StateData);
|
||||
dispatch(Info, Mod, StateName, StateData) ->
|
||||
Mod:handle_info(Info, StateName, StateData).
|
||||
|
||||
from({'$gen_sync_event', From, _Event}) -> From;
|
||||
from({'$gen_sync_all_state_event', From, _Event}) -> From;
|
||||
from(_) -> undefined.
|
||||
|
||||
%% Send a reply to the client.
|
||||
reply({To, Tag}, Reply) ->
|
||||
catch To ! {Tag, Reply}.
|
||||
|
||||
reply(Name, {To, Tag}, Reply, Debug, StateName) ->
|
||||
reply({To, Tag}, Reply),
|
||||
sys:handle_debug(Debug, fun print_event/3, Name,
|
||||
{out, Reply, To, StateName}).
|
||||
|
||||
%%% ---------------------------------------------------
|
||||
%%% Terminate the server.
|
||||
%%% ---------------------------------------------------
|
||||
|
||||
-spec terminate(term(), _, _, atom(), _, _, _) -> no_return().
|
||||
|
||||
terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) ->
|
||||
case catch Mod:terminate(Reason, StateName, StateData) of
|
||||
{'EXIT', R} ->
|
||||
error_info(Mod, R, Name, Msg, StateName, StateData, Debug),
|
||||
exit(R);
|
||||
_ ->
|
||||
case Reason of
|
||||
normal ->
|
||||
exit(normal);
|
||||
shutdown ->
|
||||
exit(shutdown);
|
||||
priority_shutdown ->
|
||||
%% Priority shutdown should be considered as
|
||||
%% shutdown by SASL
|
||||
exit(shutdown);
|
||||
{process_limit, _Limit} ->
|
||||
exit(Reason);
|
||||
{migrated, _Clone} ->
|
||||
exit(normal);
|
||||
_ ->
|
||||
error_info(Mod, Reason, Name, Msg, StateName, StateData, Debug),
|
||||
exit(Reason)
|
||||
end
|
||||
end.
|
||||
|
||||
error_info(Mod, Reason, Name, Msg, StateName, StateData, Debug) ->
|
||||
Reason1 =
|
||||
case Reason of
|
||||
{undef,[{M,F,A}|MFAs]} ->
|
||||
case code:is_loaded(M) of
|
||||
false ->
|
||||
{'module could not be loaded',[{M,F,A}|MFAs]};
|
||||
_ ->
|
||||
case erlang:function_exported(M, F, length(A)) of
|
||||
true ->
|
||||
Reason;
|
||||
false ->
|
||||
{'function not exported',[{M,F,A}|MFAs]}
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
Reason
|
||||
end,
|
||||
StateToPrint = case erlang:function_exported(Mod, print_state, 1) of
|
||||
true -> (catch Mod:print_state(StateData));
|
||||
false -> StateData
|
||||
end,
|
||||
Str = "** State machine ~p terminating \n" ++
|
||||
get_msg_str(Msg) ++
|
||||
"** When State == ~p~n"
|
||||
"** Data == ~p~n"
|
||||
"** Reason for termination = ~n** ~p~n",
|
||||
format(Str, [Name, get_msg(Msg), StateName, StateToPrint, Reason1]),
|
||||
sys:print_log(Debug),
|
||||
ok.
|
||||
|
||||
get_msg_str({'$gen_event', _Event}) ->
|
||||
"** Last event in was ~p~n";
|
||||
get_msg_str({'$gen_sync_event', _Event}) ->
|
||||
"** Last sync event in was ~p~n";
|
||||
get_msg_str({'$gen_all_state_event', _Event}) ->
|
||||
"** Last event in was ~p (for all states)~n";
|
||||
get_msg_str({'$gen_sync_all_state_event', _Event}) ->
|
||||
"** Last sync event in was ~p (for all states)~n";
|
||||
get_msg_str({timeout, _Ref, {'$gen_timer', _Msg}}) ->
|
||||
"** Last timer event in was ~p~n";
|
||||
get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}) ->
|
||||
"** Last timer event in was ~p~n";
|
||||
get_msg_str(_Msg) ->
|
||||
"** Last message in was ~p~n".
|
||||
|
||||
get_msg({'$gen_event', Event}) -> Event;
|
||||
get_msg({'$gen_sync_event', Event}) -> Event;
|
||||
get_msg({'$gen_all_state_event', Event}) -> Event;
|
||||
get_msg({'$gen_sync_all_state_event', Event}) -> Event;
|
||||
get_msg({timeout, Ref, {'$gen_timer', Msg}}) -> {timeout, Ref, Msg};
|
||||
get_msg({timeout, _Ref, {'$gen_event', Event}}) -> Event;
|
||||
get_msg(Msg) -> Msg.
|
||||
|
||||
%%-----------------------------------------------------------------
|
||||
%% Status information
|
||||
%%-----------------------------------------------------------------
|
||||
format_status(Opt, StatusData) ->
|
||||
[PDict, SysState, Parent, Debug, [Name, StateName, StateData, Mod, _Time, _Limits, _Queue, _QueueLen]] =
|
||||
StatusData,
|
||||
NameTag = if is_pid(Name) ->
|
||||
pid_to_list(Name);
|
||||
is_atom(Name) ->
|
||||
Name
|
||||
end,
|
||||
Header = lists:concat(["Status for state machine ", NameTag]),
|
||||
Log = sys:get_debug(log, Debug, []),
|
||||
Specfic =
|
||||
case erlang:function_exported(Mod, format_status, 2) of
|
||||
true ->
|
||||
case catch Mod:format_status(Opt,[PDict,StateData]) of
|
||||
{'EXIT', _} -> [{data, [{"StateData", StateData}]}];
|
||||
Else -> Else
|
||||
end;
|
||||
_ ->
|
||||
[{data, [{"StateData", StateData}]}]
|
||||
end,
|
||||
[{header, Header},
|
||||
{data, [{"Status", SysState},
|
||||
{"Parent", Parent},
|
||||
{"Logged events", Log},
|
||||
{"StateName", StateName}]} |
|
||||
Specfic].
|
||||
|
||||
%%-----------------------------------------------------------------
|
||||
%% Resources limit management
|
||||
%%-----------------------------------------------------------------
|
||||
%% Extract know limit options
|
||||
limit_options(Options) ->
|
||||
limit_options(Options, #limits{}).
|
||||
limit_options([], Limits) ->
|
||||
Limits;
|
||||
%% Maximum number of messages allowed in the process message queue
|
||||
limit_options([{max_queue,N}|Options], Limits)
|
||||
when is_integer(N) ->
|
||||
NewLimits = Limits#limits{max_queue=N},
|
||||
limit_options(Options, NewLimits);
|
||||
limit_options([_|Options], Limits) ->
|
||||
limit_options(Options, Limits).
|
||||
|
||||
%% Throw max_queue if we have reach the max queue size
|
||||
%% Returns ok otherwise
|
||||
message_queue_len(#limits{max_queue = undefined}, _QueueLen) ->
|
||||
ok;
|
||||
message_queue_len(#limits{max_queue = MaxQueue}, QueueLen) ->
|
||||
Pid = self(),
|
||||
case process_info(Pid, message_queue_len) of
|
||||
{message_queue_len, N} when N + QueueLen > MaxQueue ->
|
||||
throw({process_limit, {max_queue, N + QueueLen}});
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
@@ -1,49 +0,0 @@
|
||||
%%% ====================================================================
|
||||
%%% ``The contents of this file are subject to the Erlang Public License,
|
||||
%%% Version 1.1, (the "License"); you may not use this file except in
|
||||
%%% compliance with the License. You should have received a copy of the
|
||||
%%% Erlang Public License along with this software. If not, it can be
|
||||
%%% retrieved via the world wide web at http://www.erlang.org/.
|
||||
%%%
|
||||
%%%
|
||||
%%% Software distributed under the License is distributed on an "AS IS"
|
||||
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
|
||||
%%% the License for the specific language governing rights and limitations
|
||||
%%% under the License.
|
||||
%%%
|
||||
%%%
|
||||
%%% The Initial Developer of the Original Code is ProcessOne.
|
||||
%%% Portions created by ProcessOne are Copyright 2006-2014, ProcessOne
|
||||
%%% All Rights Reserved.''
|
||||
%%%
|
||||
%%% This software is copyright 2006-2014, ProcessOne.
|
||||
|
||||
-module(p1_mnesia).
|
||||
|
||||
-author('mickael.remond@process-one.net').
|
||||
|
||||
-export([count_records/2]).
|
||||
|
||||
%% Return the number of records matching a given match expression.
|
||||
%% This function is intended to be used inside a Mnesia transaction.
|
||||
%% The count has been written to use the fewest possible memory by
|
||||
%% getting the record by small increment and by using continuation.
|
||||
-define(BATCHSIZE, 100).
|
||||
|
||||
count_records(Tab, MatchExpression) ->
|
||||
case mnesia:select(Tab, [{MatchExpression, [], [[]]}],
|
||||
?BATCHSIZE, read)
|
||||
of
|
||||
{Result, Cont} ->
|
||||
Count = length(Result),
|
||||
count_records_cont(Cont, Count);
|
||||
'$end_of_table' -> 0
|
||||
end.
|
||||
|
||||
count_records_cont(Cont, Count) ->
|
||||
case mnesia:select(Cont) of
|
||||
{Result, Cont} ->
|
||||
NewCount = Count + length(Result),
|
||||
count_records_cont(Cont, NewCount);
|
||||
'$end_of_table' -> Count
|
||||
end.
|
||||
-166
@@ -1,166 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : treap.erl
|
||||
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
||||
%%% Purpose : Treaps implementation
|
||||
%%% Created : 22 Apr 2008 by Alexey Shchepin <alexey@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2014 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(treap).
|
||||
|
||||
-export([empty/0, insert/4, delete/2, delete_root/1,
|
||||
get_root/1, lookup/2, is_empty/1, fold/3, from_list/1,
|
||||
to_list/1]).
|
||||
|
||||
-type hashkey() :: {non_neg_integer(), any()}.
|
||||
|
||||
-type treap() :: {hashkey(), any(), any(), treap(), treap()} | nil.
|
||||
|
||||
-export_type([treap/0]).
|
||||
|
||||
empty() -> nil.
|
||||
|
||||
insert(Key, Priority, Value, Tree) ->
|
||||
HashKey = {erlang:phash2(Key), Key},
|
||||
insert1(Tree, HashKey, Priority, Value).
|
||||
|
||||
insert1(nil, HashKey, Priority, Value) ->
|
||||
{HashKey, Priority, Value, nil, nil};
|
||||
insert1({HashKey1, Priority1, Value1, Left, Right} =
|
||||
Tree,
|
||||
HashKey, Priority, Value) ->
|
||||
if HashKey < HashKey1 ->
|
||||
heapify({HashKey1, Priority1, Value1,
|
||||
insert1(Left, HashKey, Priority, Value), Right});
|
||||
HashKey > HashKey1 ->
|
||||
heapify({HashKey1, Priority1, Value1, Left,
|
||||
insert1(Right, HashKey, Priority, Value)});
|
||||
Priority == Priority1 ->
|
||||
{HashKey, Priority, Value, Left, Right};
|
||||
true ->
|
||||
insert1(delete_root(Tree), HashKey, Priority, Value)
|
||||
end.
|
||||
|
||||
heapify({_HashKey, _Priority, _Value, nil, nil} =
|
||||
Tree) ->
|
||||
Tree;
|
||||
heapify({HashKey, Priority, Value, nil = Left,
|
||||
{HashKeyR, PriorityR, ValueR, LeftR, RightR}} =
|
||||
Tree) ->
|
||||
if PriorityR > Priority ->
|
||||
{HashKeyR, PriorityR, ValueR,
|
||||
{HashKey, Priority, Value, Left, LeftR}, RightR};
|
||||
true -> Tree
|
||||
end;
|
||||
heapify({HashKey, Priority, Value,
|
||||
{HashKeyL, PriorityL, ValueL, LeftL, RightL},
|
||||
nil = Right} =
|
||||
Tree) ->
|
||||
if PriorityL > Priority ->
|
||||
{HashKeyL, PriorityL, ValueL, LeftL,
|
||||
{HashKey, Priority, Value, RightL, Right}};
|
||||
true -> Tree
|
||||
end;
|
||||
heapify({HashKey, Priority, Value,
|
||||
{HashKeyL, PriorityL, ValueL, LeftL, RightL} = Left,
|
||||
{HashKeyR, PriorityR, ValueR, LeftR, RightR} = Right} =
|
||||
Tree) ->
|
||||
if PriorityR > Priority ->
|
||||
{HashKeyR, PriorityR, ValueR,
|
||||
{HashKey, Priority, Value, Left, LeftR}, RightR};
|
||||
PriorityL > Priority ->
|
||||
{HashKeyL, PriorityL, ValueL, LeftL,
|
||||
{HashKey, Priority, Value, RightL, Right}};
|
||||
true -> Tree
|
||||
end.
|
||||
|
||||
delete(Key, Tree) ->
|
||||
HashKey = {erlang:phash2(Key), Key},
|
||||
delete1(HashKey, Tree).
|
||||
|
||||
delete1(_HashKey, nil) -> nil;
|
||||
delete1(HashKey,
|
||||
{HashKey1, Priority1, Value1, Left, Right} = Tree) ->
|
||||
if HashKey < HashKey1 ->
|
||||
{HashKey1, Priority1, Value1, delete1(HashKey, Left),
|
||||
Right};
|
||||
HashKey > HashKey1 ->
|
||||
{HashKey1, Priority1, Value1, Left,
|
||||
delete1(HashKey, Right)};
|
||||
true -> delete_root(Tree)
|
||||
end.
|
||||
|
||||
delete_root({HashKey, Priority, Value, Left, Right}) ->
|
||||
case {Left, Right} of
|
||||
{nil, nil} -> nil;
|
||||
{_, nil} -> Left;
|
||||
{nil, _} -> Right;
|
||||
{{HashKeyL, PriorityL, ValueL, LeftL, RightL},
|
||||
{HashKeyR, PriorityR, ValueR, LeftR, RightR}} ->
|
||||
if PriorityL > PriorityR ->
|
||||
{HashKeyL, PriorityL, ValueL, LeftL,
|
||||
delete_root({HashKey, Priority, Value, RightL, Right})};
|
||||
true ->
|
||||
{HashKeyR, PriorityR, ValueR,
|
||||
delete_root({HashKey, Priority, Value, Left, LeftR}),
|
||||
RightR}
|
||||
end
|
||||
end.
|
||||
|
||||
is_empty(nil) -> true;
|
||||
is_empty({_HashKey, _Priority, _Value, _Left,
|
||||
_Right}) ->
|
||||
false.
|
||||
|
||||
get_root({{_Hash, Key}, Priority, Value, _Left,
|
||||
_Right}) ->
|
||||
{Key, Priority, Value}.
|
||||
|
||||
lookup(Key, Tree) ->
|
||||
HashKey = {erlang:phash2(Key), Key},
|
||||
lookup1(Tree, HashKey).
|
||||
|
||||
lookup1(nil, _HashKey) -> error;
|
||||
lookup1({HashKey1, Priority1, Value1, Left, Right},
|
||||
HashKey) ->
|
||||
if HashKey < HashKey1 -> lookup1(Left, HashKey);
|
||||
HashKey > HashKey1 -> lookup1(Right, HashKey);
|
||||
true -> {ok, Priority1, Value1}
|
||||
end.
|
||||
|
||||
fold(_F, Acc, nil) -> Acc;
|
||||
fold(F, Acc,
|
||||
{{_Hash, Key}, Priority, Value, Left, Right}) ->
|
||||
Acc1 = F({Key, Priority, Value}, Acc),
|
||||
Acc2 = fold(F, Acc1, Left),
|
||||
fold(F, Acc2, Right).
|
||||
|
||||
to_list(Tree) -> to_list(Tree, []).
|
||||
|
||||
to_list(nil, Acc) -> Acc;
|
||||
to_list(Tree, Acc) ->
|
||||
Root = get_root(Tree),
|
||||
to_list(delete_root(Tree), [Root | Acc]).
|
||||
|
||||
from_list(List) -> from_list(List, nil).
|
||||
|
||||
from_list([{Key, Priority, Value} | Tail], Tree) ->
|
||||
from_list(Tail, insert(Key, Priority, Value, Tree));
|
||||
from_list([], Tree) -> Tree.
|
||||
+35
-2
@@ -1,17 +1,50 @@
|
||||
You need MySQL and PostgreSQL up and running.
|
||||
You need MySQL, PostgreSQL and Riak up and running.
|
||||
MySQL should be accepting TCP connections on localhost:3306.
|
||||
PostgreSQL should be accepting TCP connections on localhost:5432.
|
||||
Both of them should grant full access to user 'ejabberd_test' with
|
||||
Riak should be accepting TCP connections on localhost:8087.
|
||||
MySQL and PostgreSQL should grant full access to user 'ejabberd_test' with
|
||||
password 'ejabberd_test' on database 'ejabberd_test'.
|
||||
Riak should be configured with leveldb as a database backend and -pz
|
||||
should be pointed to the directory with ejabberd BEAM files.
|
||||
|
||||
Here is a quick setup example:
|
||||
|
||||
------------------
|
||||
PostgreSQL
|
||||
------------------
|
||||
$ psql template1
|
||||
template1=# CREATE USER ejabberd_test WITH PASSWORD 'ejabberd_test';
|
||||
template1=# CREATE DATABASE ejabberd_test;
|
||||
template1=# GRANT ALL PRIVILEGES ON DATABASE ejabberd_test TO ejabberd_test;
|
||||
|
||||
-------------------
|
||||
MySQL
|
||||
-------------------
|
||||
$ mysql
|
||||
mysql> CREATE USER 'ejabberd_test'@'localhost' IDENTIFIED BY 'ejabberd_test';
|
||||
mysql> CREATE DATABASE ejabberd_test;
|
||||
mysql> GRANT ALL ON ejabberd_test.* TO 'ejabberd_test'@'localhost';
|
||||
|
||||
-------------------
|
||||
Riak
|
||||
-------------------
|
||||
$ cat /etc/riak/vm.args
|
||||
...
|
||||
## Map/Reduce path
|
||||
-pz /path/to/ejabberd/ebin
|
||||
...
|
||||
|
||||
For version < 2.x:
|
||||
|
||||
$ cat /etc/riak/app.config:
|
||||
...
|
||||
{riak_kv, [
|
||||
{storage_backend, riak_kv_eleveldb_backend},
|
||||
...
|
||||
|
||||
For version >= 2.x:
|
||||
|
||||
$ cat /etc/riak/riak.conf:
|
||||
...
|
||||
storage_backend = leveldb
|
||||
...
|
||||
|
||||
+729
-36
@@ -19,7 +19,8 @@
|
||||
wait_for_master/1, wait_for_slave/1,
|
||||
make_iq_result/1, start_event_relay/0,
|
||||
stop_event_relay/1, put_event/2, get_event/1,
|
||||
bind/1, auth/1, open_session/1, zlib/1, starttls/1]).
|
||||
bind/1, auth/1, open_session/1, zlib/1, starttls/1,
|
||||
close_socket/1]).
|
||||
|
||||
-include("suite.hrl").
|
||||
|
||||
@@ -68,6 +69,15 @@ init_per_group(ldap, Config) ->
|
||||
set_opt(server, ?LDAP_VHOST, Config);
|
||||
init_per_group(extauth, Config) ->
|
||||
set_opt(server, ?EXTAUTH_VHOST, Config);
|
||||
init_per_group(riak, Config) ->
|
||||
case ejabberd_riak:is_connected() of
|
||||
true ->
|
||||
mod_muc:shutdown_rooms(?RIAK_VHOST),
|
||||
NewConfig = set_opt(server, ?RIAK_VHOST, Config),
|
||||
clear_riak_tables(NewConfig);
|
||||
Err ->
|
||||
{skip, {riak_not_available, Err}}
|
||||
end;
|
||||
init_per_group(_GroupName, Config) ->
|
||||
Pid = start_event_relay(),
|
||||
set_opt(event_relay, Pid, Config).
|
||||
@@ -84,6 +94,8 @@ end_per_group(ldap, _Config) ->
|
||||
ok;
|
||||
end_per_group(extauth, _Config) ->
|
||||
ok;
|
||||
end_per_group(riak, _Config) ->
|
||||
ok;
|
||||
end_per_group(_GroupName, Config) ->
|
||||
stop_event_relay(Config),
|
||||
ok.
|
||||
@@ -94,18 +106,34 @@ init_per_testcase(TestCase, OrigConfig) ->
|
||||
subscribe_to_events(OrigConfig),
|
||||
Server = ?config(server, OrigConfig),
|
||||
Resource = ?config(resource, OrigConfig),
|
||||
MasterResource = ?config(master_resource, OrigConfig),
|
||||
SlaveResource = ?config(slave_resource, OrigConfig),
|
||||
Test = atom_to_list(TestCase),
|
||||
IsMaster = lists:suffix("_master", Test),
|
||||
IsSlave = lists:suffix("_slave", Test),
|
||||
User = if IsMaster -> <<"test_master">>;
|
||||
IsCarbons = lists:prefix("carbons_", Test),
|
||||
User = if IsMaster or IsCarbons -> <<"test_master">>;
|
||||
IsSlave -> <<"test_slave">>;
|
||||
true -> <<"test_single">>
|
||||
end,
|
||||
Slave = jlib:make_jid(<<"test_slave">>, Server, Resource),
|
||||
Master = jlib:make_jid(<<"test_master">>, Server, Resource),
|
||||
MyResource = if IsMaster and IsCarbons -> MasterResource;
|
||||
IsSlave and IsCarbons -> SlaveResource;
|
||||
true -> Resource
|
||||
end,
|
||||
Slave = if IsCarbons ->
|
||||
jlib:make_jid(<<"test_master">>, Server, SlaveResource);
|
||||
true ->
|
||||
jlib:make_jid(<<"test_slave">>, Server, Resource)
|
||||
end,
|
||||
Master = if IsCarbons ->
|
||||
jlib:make_jid(<<"test_master">>, Server, MasterResource);
|
||||
true ->
|
||||
jlib:make_jid(<<"test_master">>, Server, Resource)
|
||||
end,
|
||||
Config = set_opt(user, User,
|
||||
set_opt(slave, Slave,
|
||||
set_opt(master, Master, OrigConfig))),
|
||||
set_opt(master, Master,
|
||||
set_opt(resource, MyResource, OrigConfig)))),
|
||||
case TestCase of
|
||||
test_connect ->
|
||||
Config;
|
||||
@@ -123,6 +151,8 @@ init_per_testcase(TestCase, OrigConfig) ->
|
||||
connect(Config);
|
||||
test_bind ->
|
||||
auth(connect(Config));
|
||||
sm_resume ->
|
||||
auth(connect(Config));
|
||||
test_open_session ->
|
||||
bind(auth(connect(Config)));
|
||||
_ when IsMaster or IsSlave ->
|
||||
@@ -149,11 +179,43 @@ no_db_tests() ->
|
||||
version,
|
||||
time,
|
||||
stats,
|
||||
sm,
|
||||
sm_resume,
|
||||
disco]},
|
||||
{test_proxy65, [parallel],
|
||||
[proxy65_master, proxy65_slave]}].
|
||||
|
||||
db_tests() ->
|
||||
db_tests(riak) ->
|
||||
%% No support for mod_pubsub
|
||||
[{single_user, [sequence],
|
||||
[test_register,
|
||||
auth_plain,
|
||||
auth_md5,
|
||||
presence_broadcast,
|
||||
last,
|
||||
roster_get,
|
||||
private,
|
||||
privacy,
|
||||
blocking,
|
||||
vcard,
|
||||
test_unregister]},
|
||||
{test_muc_register, [sequence],
|
||||
[muc_register_master, muc_register_slave]},
|
||||
{test_roster_subscribe, [parallel],
|
||||
[roster_subscribe_master,
|
||||
roster_subscribe_slave]},
|
||||
{test_offline, [sequence],
|
||||
[offline_master, offline_slave]},
|
||||
{test_muc, [parallel],
|
||||
[muc_master, muc_slave]},
|
||||
{test_announce, [sequence],
|
||||
[announce_master, announce_slave]},
|
||||
{test_vcard_xupdate, [parallel],
|
||||
[vcard_xupdate_master, vcard_xupdate_slave]},
|
||||
{test_roster_remove, [parallel],
|
||||
[roster_remove_master,
|
||||
roster_remove_slave]}];
|
||||
db_tests(mnesia) ->
|
||||
[{single_user, [sequence],
|
||||
[test_register,
|
||||
auth_plain,
|
||||
@@ -166,14 +228,57 @@ db_tests() ->
|
||||
privacy,
|
||||
blocking,
|
||||
vcard,
|
||||
muc_single,
|
||||
pubsub,
|
||||
test_unregister]},
|
||||
{test_muc_register, [sequence],
|
||||
[muc_register_master, muc_register_slave]},
|
||||
{test_roster_subscribe, [parallel],
|
||||
[roster_subscribe_master,
|
||||
roster_subscribe_slave]},
|
||||
{test_offline, [sequence],
|
||||
[offline_master, offline_slave]},
|
||||
{test_carbons, [parallel],
|
||||
[carbons_master, carbons_slave]},
|
||||
{test_client_state, [parallel],
|
||||
[client_state_master, client_state_slave]},
|
||||
{test_muc, [parallel],
|
||||
[muc_master, muc_slave]},
|
||||
{test_announce, [sequence],
|
||||
[announce_master, announce_slave]},
|
||||
{test_vcard_xupdate, [parallel],
|
||||
[vcard_xupdate_master, vcard_xupdate_slave]},
|
||||
{test_roster_remove, [parallel],
|
||||
[roster_remove_master,
|
||||
roster_remove_slave]}];
|
||||
db_tests(_) ->
|
||||
%% No support for carboncopy
|
||||
[{single_user, [sequence],
|
||||
[test_register,
|
||||
auth_plain,
|
||||
auth_md5,
|
||||
presence_broadcast,
|
||||
last,
|
||||
roster_get,
|
||||
roster_ver,
|
||||
private,
|
||||
privacy,
|
||||
blocking,
|
||||
vcard,
|
||||
pubsub,
|
||||
test_unregister]},
|
||||
{test_muc_register, [sequence],
|
||||
[muc_register_master, muc_register_slave]},
|
||||
{test_roster_subscribe, [parallel],
|
||||
[roster_subscribe_master,
|
||||
roster_subscribe_slave]},
|
||||
{test_offline, [sequence],
|
||||
[offline_master, offline_slave]},
|
||||
{test_muc, [parallel],
|
||||
[muc_master, muc_slave]},
|
||||
{test_announce, [sequence],
|
||||
[announce_master, announce_slave]},
|
||||
{test_vcard_xupdate, [parallel],
|
||||
[vcard_xupdate_master, vcard_xupdate_slave]},
|
||||
{test_roster_remove, [parallel],
|
||||
[roster_remove_master,
|
||||
roster_remove_slave]}].
|
||||
@@ -192,9 +297,10 @@ groups() ->
|
||||
[{ldap, [sequence], ldap_tests()},
|
||||
{extauth, [sequence], extauth_tests()},
|
||||
{no_db, [sequence], no_db_tests()},
|
||||
{mnesia, [sequence], db_tests()},
|
||||
{mysql, [sequence], db_tests()},
|
||||
{pgsql, [sequence], db_tests()}].
|
||||
{mnesia, [sequence], db_tests(mnesia)},
|
||||
{mysql, [sequence], db_tests(mysql)},
|
||||
{pgsql, [sequence], db_tests(pgsql)},
|
||||
{riak, [sequence], db_tests(riak)}].
|
||||
|
||||
all() ->
|
||||
[{group, ldap},
|
||||
@@ -203,6 +309,7 @@ all() ->
|
||||
{group, mysql},
|
||||
{group, pgsql},
|
||||
{group, extauth},
|
||||
{group, riak},
|
||||
stop_ejabberd].
|
||||
|
||||
stop_ejabberd(Config) ->
|
||||
@@ -339,11 +446,46 @@ presence(Config) ->
|
||||
disconnect(Config).
|
||||
|
||||
presence_broadcast(Config) ->
|
||||
send(Config, #presence{}),
|
||||
Feature = <<"p1:tmp:", (randoms:get_string())/binary>>,
|
||||
Ver = crypto:sha(["client", $/, "bot", $/, "en", $/,
|
||||
"ejabberd_ct", $<, Feature, $<]),
|
||||
B64Ver = base64:encode(Ver),
|
||||
Node = <<(?EJABBERD_CT_URI)/binary, $#, B64Ver/binary>>,
|
||||
Server = ?config(server, Config),
|
||||
ServerJID = server_jid(Config),
|
||||
Info = #disco_info{identities =
|
||||
[#identity{category = <<"client">>,
|
||||
type = <<"bot">>,
|
||||
lang = <<"en">>,
|
||||
name = <<"ejabberd_ct">>}],
|
||||
node = Node, features = [Feature]},
|
||||
Caps = #caps{hash = <<"sha-1">>, node = ?EJABBERD_CT_URI, ver = Ver},
|
||||
send(Config, #presence{sub_els = [Caps]}),
|
||||
JID = my_jid(Config),
|
||||
%% We receive the welcome message and the presence broadcast
|
||||
?recv2(#message{type = normal},
|
||||
#presence{from = JID, to = JID}),
|
||||
%% We receive:
|
||||
%% 1) disco#info iq request for CAPS
|
||||
%% 2) welcome message
|
||||
%% 3) presence broadcast
|
||||
{IQ, _, _} = ?recv3(#iq{type = get,
|
||||
from = ServerJID,
|
||||
sub_els = [#disco_info{node = Node}]},
|
||||
#message{type = normal},
|
||||
#presence{from = JID, to = JID}),
|
||||
send(Config, #iq{type = result, id = IQ#iq.id,
|
||||
to = ServerJID, sub_els = [Info]}),
|
||||
%% We're trying to read our feature from ejabberd database
|
||||
%% with exponential back-off as our IQ response may be delayed.
|
||||
[Feature] =
|
||||
lists:foldl(
|
||||
fun(Time, []) ->
|
||||
timer:sleep(Time),
|
||||
mod_caps:get_features(
|
||||
Server,
|
||||
mod_caps:read_caps(
|
||||
[xmpp_codec:encode(Caps)]));
|
||||
(_, Acc) ->
|
||||
Acc
|
||||
end, [], [0, 100, 200, 2000, 5000, 10000]),
|
||||
disconnect(Config).
|
||||
|
||||
ping(Config) ->
|
||||
@@ -385,6 +527,43 @@ disco(Config) ->
|
||||
end, Items),
|
||||
disconnect(Config).
|
||||
|
||||
sm(Config) ->
|
||||
Server = ?config(server, Config),
|
||||
ServerJID = jlib:make_jid(<<"">>, Server, <<"">>),
|
||||
Msg = #message{to = ServerJID, body = [#text{data = <<"body">>}]},
|
||||
true = ?config(sm, Config),
|
||||
%% Enable the session management with resumption enabled
|
||||
send(Config, #sm_enable{resume = true, xmlns = ?NS_STREAM_MGMT_3}),
|
||||
#sm_enabled{id = ID, resume = true} = recv(),
|
||||
%% Initial request; 'h' should be 0.
|
||||
send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}),
|
||||
#sm_a{h = 0} = recv(),
|
||||
%% sending two messages and requesting again; 'h' should be 3.
|
||||
send(Config, Msg),
|
||||
send(Config, Msg),
|
||||
send(Config, Msg),
|
||||
send(Config, #sm_r{xmlns = ?NS_STREAM_MGMT_3}),
|
||||
#sm_a{h = 3} = recv(),
|
||||
close_socket(Config),
|
||||
{save_config, set_opt(sm_previd, ID, Config)}.
|
||||
|
||||
sm_resume(Config) ->
|
||||
{sm, SMConfig} = ?config(saved_config, Config),
|
||||
ID = ?config(sm_previd, SMConfig),
|
||||
Server = ?config(server, Config),
|
||||
ServerJID = jlib:make_jid(<<"">>, Server, <<"">>),
|
||||
MyJID = my_jid(Config),
|
||||
Txt = #text{data = <<"body">>},
|
||||
Msg = #message{from = ServerJID, to = MyJID, body = [Txt]},
|
||||
%% Route message. The message should be queued by the C2S process.
|
||||
ejabberd_router:route(ServerJID, MyJID, xmpp_codec:encode(Msg)),
|
||||
send(Config, #sm_resume{previd = ID, h = 0, xmlns = ?NS_STREAM_MGMT_3}),
|
||||
#sm_resumed{previd = ID, h = 3} = recv(),
|
||||
#message{from = ServerJID, to = MyJID, body = [Txt]} = recv(),
|
||||
#sm_r{} = recv(),
|
||||
send(Config, #sm_a{h = 1, xmlns = ?NS_STREAM_MGMT_3}),
|
||||
disconnect(Config).
|
||||
|
||||
private(Config) ->
|
||||
Conference = #bookmark_conference{name = <<"Some name">>,
|
||||
autojoin = true,
|
||||
@@ -549,6 +728,42 @@ vcard_get(Config) ->
|
||||
send_recv(Config, #iq{type = get, sub_els = [#vcard{}]}),
|
||||
disconnect(Config).
|
||||
|
||||
vcard_xupdate_master(Config) ->
|
||||
Img = <<137, "PNG\r\n", 26, $\n>>,
|
||||
ImgHash = p1_sha:sha(Img),
|
||||
MyJID = my_jid(Config),
|
||||
Peer = ?config(slave, Config),
|
||||
wait_for_slave(Config),
|
||||
send(Config, #presence{}),
|
||||
?recv2(#presence{from = MyJID, type = undefined},
|
||||
#presence{from = Peer, type = undefined}),
|
||||
VCard = #vcard{photo = #vcard_photo{type = <<"image/png">>, binval = Img}},
|
||||
I1 = send(Config, #iq{type = set, sub_els = [VCard]}),
|
||||
?recv2(#iq{type = result, sub_els = [], id = I1},
|
||||
#presence{from = MyJID, type = undefined,
|
||||
sub_els = [#vcard_xupdate{photo = ImgHash}]}),
|
||||
I2 = send(Config, #iq{type = set, sub_els = [#vcard{}]}),
|
||||
?recv3(#iq{type = result, sub_els = [], id = I2},
|
||||
#presence{from = MyJID, type = undefined,
|
||||
sub_els = [#vcard_xupdate{photo = undefined}]},
|
||||
#presence{from = Peer, type = unavailable}),
|
||||
disconnect(Config).
|
||||
|
||||
vcard_xupdate_slave(Config) ->
|
||||
Img = <<137, "PNG\r\n", 26, $\n>>,
|
||||
ImgHash = p1_sha:sha(Img),
|
||||
MyJID = my_jid(Config),
|
||||
Peer = ?config(master, Config),
|
||||
send(Config, #presence{}),
|
||||
#presence{from = MyJID, type = undefined} = recv(),
|
||||
wait_for_master(Config),
|
||||
#presence{from = Peer, type = undefined} = recv(),
|
||||
#presence{from = Peer, type = undefined,
|
||||
sub_els = [#vcard_xupdate{photo = ImgHash}]} = recv(),
|
||||
#presence{from = Peer, type = undefined,
|
||||
sub_els = [#vcard_xupdate{photo = undefined}]} = recv(),
|
||||
disconnect(Config).
|
||||
|
||||
stats(Config) ->
|
||||
#iq{type = result, sub_els = [#stats{stat = Stats}]} =
|
||||
send_recv(Config, #iq{type = get, sub_els = [#stats{}],
|
||||
@@ -586,7 +801,7 @@ pubsub(Config) ->
|
||||
node = Node,
|
||||
jid = my_jid(Config)}}]}),
|
||||
?recv2(
|
||||
#message{sub_els = [#pubsub_event{}, #delay{}]},
|
||||
#message{sub_els = [#pubsub_event{}, #delay{}, #legacy_delay{}]},
|
||||
#iq{type = result, id = I1}),
|
||||
%% Get subscriptions
|
||||
true = lists:member(?PUBSUB("retrieve-subscriptions"), Features),
|
||||
@@ -820,15 +1035,22 @@ proxy65_slave(Config) ->
|
||||
socks5_recv(Socks5, Data),
|
||||
disconnect(Config).
|
||||
|
||||
muc_single(Config) ->
|
||||
muc_master(Config) ->
|
||||
MyJID = my_jid(Config),
|
||||
PeerJID = ?config(slave, Config),
|
||||
PeerBareJID = jlib:jid_remove_resource(PeerJID),
|
||||
PeerJIDStr = jlib:jid_to_string(PeerJID),
|
||||
MUC = muc_jid(Config),
|
||||
Room = muc_room_jid(Config),
|
||||
Nick = ?config(user, Config),
|
||||
NickJID = jlib:jid_replace_resource(Room, Nick),
|
||||
MyNick = ?config(master_nick, Config),
|
||||
MyNickJID = jlib:jid_replace_resource(Room, MyNick),
|
||||
PeerNick = ?config(slave_nick, Config),
|
||||
PeerNickJID = jlib:jid_replace_resource(Room, PeerNick),
|
||||
Subject = ?config(room_subject, Config),
|
||||
Localhost = jlib:make_jid(<<"">>, <<"localhost">>, <<"">>),
|
||||
true = is_feature_advertised(Config, ?NS_MUC, MUC),
|
||||
%% Joining
|
||||
send(Config, #presence{to = NickJID, sub_els = [#muc{}]}),
|
||||
send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}),
|
||||
%% As per XEP-0045 we MUST receive stanzas in the following order:
|
||||
%% 1. In-room presence from other occupants
|
||||
%% 2. In-room presence from the joining entity itself (so-called "self-presence")
|
||||
@@ -837,16 +1059,16 @@ muc_single(Config) ->
|
||||
%% 5. Live messages, presence updates, new user joins, etc.
|
||||
%% As this is the newly created room, we receive only the 2nd stanza.
|
||||
#presence{
|
||||
from = NickJID,
|
||||
sub_els = [#muc_user{
|
||||
from = MyNickJID,
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
status_codes = Codes,
|
||||
items = [#muc_item{role = moderator,
|
||||
jid = MyJID,
|
||||
affiliation = owner}]}]} = recv(),
|
||||
%% 110 -> Inform user that presence refers to itself
|
||||
%% 201 -> Inform user that a new room has been created
|
||||
true = lists:member(110, Codes),
|
||||
true = lists:member(201, Codes),
|
||||
[110, 201] = lists:sort(Codes),
|
||||
%% Request the configuration
|
||||
#iq{type = result, sub_els = [#muc_owner{config = #xdata{} = RoomCfg}]} =
|
||||
send_recv(Config, #iq{type = get, sub_els = [#muc_owner{}],
|
||||
@@ -863,10 +1085,14 @@ muc_single(Config) ->
|
||||
[<<"Trying to break the server">>];
|
||||
<<"muc#roomconfig_persistentroom">> ->
|
||||
[<<"1">>];
|
||||
<<"muc#roomconfig_changesubject">> ->
|
||||
[<<"0">>];
|
||||
<<"muc#roomconfig_allowinvites">> ->
|
||||
[<<"1">>];
|
||||
<<"members_by_default">> ->
|
||||
[<<"0">>];
|
||||
<<"muc#roomconfig_allowvoicerequests">> ->
|
||||
[<<"1">>];
|
||||
<<"public_list">> ->
|
||||
[<<"1">>];
|
||||
<<"muc#roomconfig_publicroom">> ->
|
||||
[<<"1">>];
|
||||
_ ->
|
||||
[]
|
||||
end,
|
||||
@@ -878,20 +1104,322 @@ muc_single(Config) ->
|
||||
end, RoomCfg#xdata.fields),
|
||||
NewRoomCfg = #xdata{type = submit, fields = NewFields},
|
||||
%% BUG: We should not receive any sub_els!
|
||||
%% TODO: fix this crap in ejabberd.
|
||||
#iq{type = result, sub_els = [_|_]} =
|
||||
send_recv(Config, #iq{type = set, to = Room,
|
||||
sub_els = [#muc_owner{config = NewRoomCfg}]}),
|
||||
%% Set subject
|
||||
send(Config, #message{to = Room, type = groupchat,
|
||||
body = [#text{data = <<"Subject">>}]}),
|
||||
#message{from = NickJID, type = groupchat,
|
||||
body = [#text{data = <<"Subject">>}]} = recv(),
|
||||
%% Leaving
|
||||
send(Config, #presence{type = unavailable, to = NickJID}),
|
||||
#presence{from = NickJID, type = unavailable,
|
||||
sub_els = [#muc_user{status_codes = NewCodes}]} = recv(),
|
||||
true = lists:member(110, NewCodes),
|
||||
body = [#text{data = Subject}]}),
|
||||
#message{from = MyNickJID, type = groupchat,
|
||||
body = [#text{data = Subject}]} = recv(),
|
||||
%% Sending messages (and thus, populating history for our peer)
|
||||
lists:foreach(
|
||||
fun(N) ->
|
||||
Text = #text{data = jlib:integer_to_binary(N)},
|
||||
I = send(Config, #message{to = Room, body = [Text],
|
||||
type = groupchat}),
|
||||
#message{from = MyNickJID, id = I,
|
||||
type = groupchat,
|
||||
body = [Text]} = recv()
|
||||
end, lists:seq(1, 5)),
|
||||
%% Inviting the peer
|
||||
send(Config, #message{to = Room, type = normal,
|
||||
sub_els =
|
||||
[#muc_user{
|
||||
invites =
|
||||
[#muc_invite{to = PeerJID}]}]}),
|
||||
%% Peer is joining
|
||||
#presence{from = PeerNickJID,
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
items = [#muc_item{role = visitor,
|
||||
jid = PeerJID,
|
||||
affiliation = none}]}]} = recv(),
|
||||
%% Receiving a voice request
|
||||
#message{from = Room,
|
||||
sub_els = [#xdata{type = form,
|
||||
instructions = [_],
|
||||
fields = VoiceReqFs}]} = recv(),
|
||||
%% Approving the voice request
|
||||
ReplyVoiceReqFs =
|
||||
lists:map(
|
||||
fun(#xdata_field{var = Var, values = OrigVals}) ->
|
||||
Vals = case {Var, OrigVals} of
|
||||
{<<"FORM_TYPE">>,
|
||||
[<<"http://jabber.org/protocol/muc#request">>]} ->
|
||||
OrigVals;
|
||||
{<<"muc#role">>, [<<"participant">>]} ->
|
||||
[<<"participant">>];
|
||||
{<<"muc#jid">>, [PeerJIDStr]} ->
|
||||
[PeerJIDStr];
|
||||
{<<"muc#roomnick">>, [PeerNick]} ->
|
||||
[PeerNick];
|
||||
{<<"muc#request_allow">>, [<<"0">>]} ->
|
||||
[<<"1">>]
|
||||
end,
|
||||
#xdata_field{values = Vals, var = Var}
|
||||
end, VoiceReqFs),
|
||||
send(Config, #message{to = Room,
|
||||
sub_els = [#xdata{type = submit,
|
||||
fields = ReplyVoiceReqFs}]}),
|
||||
%% Peer is becoming a participant
|
||||
#presence{from = PeerNickJID,
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
items = [#muc_item{role = participant,
|
||||
jid = PeerJID,
|
||||
affiliation = none}]}]} = recv(),
|
||||
%% Receive private message from the peer
|
||||
#message{from = PeerNickJID, body = [#text{data = Subject}]} = recv(),
|
||||
%% Granting membership to the peer and localhost server
|
||||
I1 = send(Config,
|
||||
#iq{type = set, to = Room,
|
||||
sub_els =
|
||||
[#muc_admin{
|
||||
items = [#muc_item{jid = Localhost,
|
||||
affiliation = member},
|
||||
#muc_item{nick = PeerNick,
|
||||
jid = PeerBareJID,
|
||||
affiliation = member}]}]}),
|
||||
%% Peer became a member
|
||||
#presence{from = PeerNickJID,
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
items = [#muc_item{affiliation = member,
|
||||
jid = PeerJID,
|
||||
role = participant}]}]} = recv(),
|
||||
%% BUG: We should not receive any sub_els!
|
||||
#iq{type = result, id = I1, sub_els = [_|_]} = recv(),
|
||||
%% Receive groupchat message from the peer
|
||||
#message{type = groupchat, from = PeerNickJID,
|
||||
body = [#text{data = Subject}]} = recv(),
|
||||
%% Kick the peer
|
||||
I2 = send(Config,
|
||||
#iq{type = set, to = Room,
|
||||
sub_els = [#muc_admin{
|
||||
items = [#muc_item{nick = PeerNick,
|
||||
role = none}]}]}),
|
||||
%% Got notification the peer is kicked
|
||||
%% 307 -> Inform user that he or she has been kicked from the room
|
||||
#presence{from = PeerNickJID, type = unavailable,
|
||||
sub_els = [#muc_user{
|
||||
status_codes = [307],
|
||||
items = [#muc_item{affiliation = member,
|
||||
jid = PeerJID,
|
||||
role = none}]}]} = recv(),
|
||||
%% BUG: We should not receive any sub_els!
|
||||
#iq{type = result, id = I2, sub_els = [_|_]} = recv(),
|
||||
%% Destroying the room
|
||||
I3 = send(Config,
|
||||
#iq{type = set, to = Room,
|
||||
sub_els = [#muc_owner{
|
||||
destroy = #muc_owner_destroy{
|
||||
reason = Subject}}]}),
|
||||
%% Kicked off
|
||||
#presence{from = MyNickJID, type = unavailable,
|
||||
sub_els = [#muc_user{items = [#muc_item{role = none,
|
||||
affiliation = none}],
|
||||
destroy = #muc_user_destroy{
|
||||
reason = Subject}}]} = recv(),
|
||||
%% BUG: We should not receive any sub_els!
|
||||
#iq{type = result, id = I3, sub_els = [_|_]} = recv(),
|
||||
disconnect(Config).
|
||||
|
||||
muc_slave(Config) ->
|
||||
MyJID = my_jid(Config),
|
||||
MyBareJID = jlib:jid_remove_resource(MyJID),
|
||||
PeerJID = ?config(master, Config),
|
||||
MUC = muc_jid(Config),
|
||||
Room = muc_room_jid(Config),
|
||||
MyNick = ?config(slave_nick, Config),
|
||||
MyNickJID = jlib:jid_replace_resource(Room, MyNick),
|
||||
PeerNick = ?config(master_nick, Config),
|
||||
PeerNickJID = jlib:jid_replace_resource(Room, PeerNick),
|
||||
Subject = ?config(room_subject, Config),
|
||||
Localhost = jlib:make_jid(<<"">>, <<"localhost">>, <<"">>),
|
||||
%% Receive an invite from the peer
|
||||
#message{from = Room, type = normal,
|
||||
sub_els =
|
||||
[#muc_user{invites =
|
||||
[#muc_invite{from = PeerJID}]}]} = recv(),
|
||||
%% But before joining we discover the MUC service first
|
||||
%% to check if the room is in the disco list
|
||||
#iq{type = result,
|
||||
sub_els = [#disco_items{items = [#disco_item{jid = Room}]}]} =
|
||||
send_recv(Config, #iq{type = get, to = MUC,
|
||||
sub_els = [#disco_items{}]}),
|
||||
%% Now check if the peer is in the room. We check this via disco#items
|
||||
#iq{type = result,
|
||||
sub_els = [#disco_items{items = [#disco_item{jid = PeerNickJID,
|
||||
name = PeerNick}]}]} =
|
||||
send_recv(Config, #iq{type = get, to = Room,
|
||||
sub_els = [#disco_items{}]}),
|
||||
%% Now joining
|
||||
send(Config, #presence{to = MyNickJID, sub_els = [#muc{}]}),
|
||||
%% First presence is from the participant, i.e. from the peer
|
||||
#presence{
|
||||
from = PeerNickJID,
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
status_codes = [],
|
||||
items = [#muc_item{role = moderator,
|
||||
affiliation = owner}]}]} = recv(),
|
||||
%% The next is the self-presence (code 110 means it)
|
||||
#presence{
|
||||
from = MyNickJID,
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
status_codes = [110],
|
||||
items = [#muc_item{role = visitor,
|
||||
affiliation = none}]}]} = recv(),
|
||||
%% Receive the room subject
|
||||
#message{from = PeerNickJID, type = groupchat,
|
||||
body = [#text{data = Subject}],
|
||||
sub_els = [#delay{}, #legacy_delay{}]} = recv(),
|
||||
%% Receive MUC history
|
||||
lists:foreach(
|
||||
fun(N) ->
|
||||
Text = #text{data = jlib:integer_to_binary(N)},
|
||||
#message{from = PeerNickJID,
|
||||
type = groupchat,
|
||||
body = [Text],
|
||||
sub_els = [#delay{}, #legacy_delay{}]} = recv()
|
||||
end, lists:seq(1, 5)),
|
||||
%% Sending a voice request
|
||||
VoiceReq = #xdata{
|
||||
type = submit,
|
||||
fields =
|
||||
[#xdata_field{
|
||||
var = <<"FORM_TYPE">>,
|
||||
values = [<<"http://jabber.org/protocol/muc#request">>]},
|
||||
#xdata_field{
|
||||
var = <<"muc#role">>,
|
||||
type = 'text-single',
|
||||
values = [<<"participant">>]}]},
|
||||
send(Config, #message{to = Room, sub_els = [VoiceReq]}),
|
||||
%% Becoming a participant
|
||||
#presence{from = MyNickJID,
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
items = [#muc_item{role = participant,
|
||||
affiliation = none}]}]} = recv(),
|
||||
%% Sending private message to the peer
|
||||
send(Config, #message{to = PeerNickJID,
|
||||
body = [#text{data = Subject}]}),
|
||||
%% Becoming a member
|
||||
#presence{from = MyNickJID,
|
||||
sub_els = [#vcard_xupdate{},
|
||||
#muc_user{
|
||||
items = [#muc_item{role = participant,
|
||||
affiliation = member}]}]} = recv(),
|
||||
%% Retrieving a member list
|
||||
#iq{type = result, sub_els = [#muc_admin{items = MemberList}]} =
|
||||
send_recv(Config,
|
||||
#iq{type = get, to = Room,
|
||||
sub_els =
|
||||
[#muc_admin{items = [#muc_item{affiliation = member}]}]}),
|
||||
[#muc_item{affiliation = member,
|
||||
jid = Localhost},
|
||||
#muc_item{affiliation = member,
|
||||
jid = MyBareJID}] = lists:keysort(#muc_item.jid, MemberList),
|
||||
%% Sending groupchat message
|
||||
send(Config, #message{to = Room, type = groupchat,
|
||||
body = [#text{data = Subject}]}),
|
||||
%% Receive this message back
|
||||
#message{type = groupchat, from = MyNickJID,
|
||||
body = [#text{data = Subject}]} = recv(),
|
||||
%% We're kicked off
|
||||
%% 307 -> Inform user that he or she has been kicked from the room
|
||||
#presence{from = MyNickJID, type = unavailable,
|
||||
sub_els = [#muc_user{
|
||||
status_codes = [307],
|
||||
items = [#muc_item{affiliation = member,
|
||||
role = none}]}]} = recv(),
|
||||
disconnect(Config).
|
||||
|
||||
muc_register_nick(Config, MUC, PrevNick, Nick) ->
|
||||
{Registered, PrevNickVals} = if PrevNick /= <<"">> ->
|
||||
{true, [PrevNick]};
|
||||
true ->
|
||||
{false, []}
|
||||
end,
|
||||
%% Request register form
|
||||
#iq{type = result,
|
||||
sub_els = [#register{registered = Registered,
|
||||
xdata = #xdata{type = form,
|
||||
fields = FsWithoutNick}}]} =
|
||||
send_recv(Config, #iq{type = get, to = MUC,
|
||||
sub_els = [#register{}]}),
|
||||
%% Check if 'nick' field presents
|
||||
#xdata_field{type = 'text-single',
|
||||
var = <<"nick">>,
|
||||
values = PrevNickVals} =
|
||||
lists:keyfind(<<"nick">>, #xdata_field.var, FsWithoutNick),
|
||||
X = #xdata{type = submit,
|
||||
fields = [#xdata_field{var = <<"nick">>, values = [Nick]}]},
|
||||
%% Submitting form
|
||||
#iq{type = result, sub_els = [_|_]} =
|
||||
send_recv(Config, #iq{type = set, to = MUC,
|
||||
sub_els = [#register{xdata = X}]}),
|
||||
%% Check if the nick was registered
|
||||
#iq{type = result,
|
||||
sub_els = [#register{registered = true,
|
||||
xdata = #xdata{type = form,
|
||||
fields = FsWithNick}}]} =
|
||||
send_recv(Config, #iq{type = get, to = MUC,
|
||||
sub_els = [#register{}]}),
|
||||
#xdata_field{type = 'text-single', var = <<"nick">>,
|
||||
values = [Nick]} =
|
||||
lists:keyfind(<<"nick">>, #xdata_field.var, FsWithNick).
|
||||
|
||||
muc_register_master(Config) ->
|
||||
MUC = muc_jid(Config),
|
||||
%% Register nick "master1"
|
||||
muc_register_nick(Config, MUC, <<"">>, <<"master1">>),
|
||||
%% Unregister nick "master1" via jabber:register
|
||||
#iq{type = result, sub_els = [_|_]} =
|
||||
send_recv(Config, #iq{type = set, to = MUC,
|
||||
sub_els = [#register{remove = true}]}),
|
||||
%% Register nick "master2"
|
||||
muc_register_nick(Config, MUC, <<"">>, <<"master2">>),
|
||||
%% Now register nick "master"
|
||||
muc_register_nick(Config, MUC, <<"master2">>, <<"master">>),
|
||||
disconnect(Config).
|
||||
|
||||
muc_register_slave(Config) ->
|
||||
MUC = muc_jid(Config),
|
||||
%% Trying to register occupied nick "master"
|
||||
X = #xdata{type = submit,
|
||||
fields = [#xdata_field{var = <<"nick">>,
|
||||
values = [<<"master">>]}]},
|
||||
#iq{type = error} =
|
||||
send_recv(Config, #iq{type = set, to = MUC,
|
||||
sub_els = [#register{xdata = X}]}),
|
||||
disconnect(Config).
|
||||
|
||||
announce_master(Config) ->
|
||||
MyJID = my_jid(Config),
|
||||
ServerJID = server_jid(Config),
|
||||
MotdJID = jlib:jid_replace_resource(ServerJID, <<"announce/motd">>),
|
||||
MotdText = #text{data = <<"motd">>},
|
||||
send(Config, #presence{}),
|
||||
#presence{from = MyJID} = recv(),
|
||||
%% Set message of the day
|
||||
send(Config, #message{to = MotdJID, body = [MotdText]}),
|
||||
%% Receive this message back
|
||||
#message{from = ServerJID, body = [MotdText]} = recv(),
|
||||
disconnect(Config).
|
||||
|
||||
announce_slave(Config) ->
|
||||
MyJID = my_jid(Config),
|
||||
ServerJID = server_jid(Config),
|
||||
MotdDelJID = jlib:jid_replace_resource(ServerJID, <<"announce/motd/delete">>),
|
||||
MotdText = #text{data = <<"motd">>},
|
||||
send(Config, #presence{}),
|
||||
?recv2(#presence{from = MyJID},
|
||||
#message{from = ServerJID, body = [MotdText]}),
|
||||
%% Delete message of the day
|
||||
send(Config, #message{to = MotdDelJID}),
|
||||
disconnect(Config).
|
||||
|
||||
offline_master(Config) ->
|
||||
@@ -914,6 +1442,158 @@ offline_slave(Config) ->
|
||||
true = lists:keymember(legacy_delay, 1, SubEls),
|
||||
disconnect(Config).
|
||||
|
||||
carbons_master(Config) ->
|
||||
MyJID = my_jid(Config),
|
||||
MyBareJID = jlib:jid_remove_resource(MyJID),
|
||||
Peer = ?config(slave, Config),
|
||||
Txt = #text{data = <<"body">>},
|
||||
true = is_feature_advertised(Config, ?NS_CARBONS_2),
|
||||
send(Config, #presence{priority = 10}),
|
||||
#presence{from = MyJID} = recv(),
|
||||
wait_for_slave(Config),
|
||||
#presence{from = Peer} = recv(),
|
||||
%% Enable carbons
|
||||
#iq{type = result, sub_els = []} =
|
||||
send_recv(Config,
|
||||
#iq{type = set,
|
||||
sub_els = [#carbons_enable{}]}),
|
||||
%% Send a message to bare and full JID
|
||||
send(Config, #message{to = MyBareJID, type = chat, body = [Txt]}),
|
||||
send(Config, #message{to = MyJID, type = chat, body = [Txt]}),
|
||||
send(Config, #message{to = MyBareJID, type = chat, body = [Txt],
|
||||
sub_els = [#carbons_private{}]}),
|
||||
send(Config, #message{to = MyJID, type = chat, body = [Txt],
|
||||
sub_els = [#carbons_private{}]}),
|
||||
%% Receive the messages back
|
||||
?recv4(#message{from = MyJID, to = MyBareJID, type = chat,
|
||||
body = [Txt], sub_els = []},
|
||||
#message{from = MyJID, to = MyJID, type = chat,
|
||||
body = [Txt], sub_els = []},
|
||||
#message{from = MyJID, to = MyBareJID, type = chat,
|
||||
body = [Txt], sub_els = [#carbons_private{}]},
|
||||
#message{from = MyJID, to = MyJID, type = chat,
|
||||
body = [Txt], sub_els = [#carbons_private{}]}),
|
||||
%% Disable carbons
|
||||
#iq{type = result, sub_els = []} =
|
||||
send_recv(Config,
|
||||
#iq{type = set,
|
||||
sub_els = [#carbons_disable{}]}),
|
||||
wait_for_slave(Config),
|
||||
%% Repeat the same and leave
|
||||
send(Config, #message{to = MyBareJID, type = chat, body = [Txt]}),
|
||||
send(Config, #message{to = MyJID, type = chat, body = [Txt]}),
|
||||
send(Config, #message{to = MyBareJID, type = chat, body = [Txt],
|
||||
sub_els = [#carbons_private{}]}),
|
||||
send(Config, #message{to = MyJID, type = chat, body = [Txt],
|
||||
sub_els = [#carbons_private{}]}),
|
||||
?recv4(#message{from = MyJID, to = MyBareJID, type = chat,
|
||||
body = [Txt], sub_els = []},
|
||||
#message{from = MyJID, to = MyJID, type = chat,
|
||||
body = [Txt], sub_els = []},
|
||||
#message{from = MyJID, to = MyBareJID, type = chat,
|
||||
body = [Txt], sub_els = [#carbons_private{}]},
|
||||
#message{from = MyJID, to = MyJID, type = chat,
|
||||
body = [Txt], sub_els = [#carbons_private{}]}),
|
||||
disconnect(Config).
|
||||
|
||||
carbons_slave(Config) ->
|
||||
MyJID = my_jid(Config),
|
||||
MyBareJID = jlib:jid_remove_resource(MyJID),
|
||||
Peer = ?config(master, Config),
|
||||
Txt = #text{data = <<"body">>},
|
||||
wait_for_master(Config),
|
||||
send(Config, #presence{priority = 5}),
|
||||
?recv2(#presence{from = MyJID}, #presence{from = Peer}),
|
||||
%% Enable carbons
|
||||
#iq{type = result, sub_els = []} =
|
||||
send_recv(Config,
|
||||
#iq{type = set,
|
||||
sub_els = [#carbons_enable{}]}),
|
||||
%% Receive messages sent by the peer
|
||||
?recv4(
|
||||
#message{from = MyBareJID, to = MyJID, type = chat,
|
||||
sub_els =
|
||||
[#carbons_sent{
|
||||
forwarded = #forwarded{
|
||||
sub_els =
|
||||
[#message{from = Peer,
|
||||
to = MyBareJID,
|
||||
type = chat,
|
||||
body = [Txt]}]}}]},
|
||||
#message{from = MyBareJID, to = MyJID, type = chat,
|
||||
sub_els =
|
||||
[#carbons_sent{
|
||||
forwarded = #forwarded{
|
||||
sub_els =
|
||||
[#message{from = Peer,
|
||||
to = Peer,
|
||||
type = chat,
|
||||
body = [Txt]}]}}]},
|
||||
#message{from = MyBareJID, to = MyJID, type = chat,
|
||||
sub_els =
|
||||
[#carbons_received{
|
||||
forwarded = #forwarded{
|
||||
sub_els =
|
||||
[#message{from = Peer,
|
||||
to = MyBareJID,
|
||||
type = chat,
|
||||
body = [Txt]}]}}]},
|
||||
#message{from = MyBareJID, to = MyJID, type = chat,
|
||||
sub_els =
|
||||
[#carbons_received{
|
||||
forwarded = #forwarded{
|
||||
sub_els =
|
||||
[#message{from = Peer,
|
||||
to = Peer,
|
||||
type = chat,
|
||||
body = [Txt]}]}}]}),
|
||||
%% Disable carbons
|
||||
#iq{type = result, sub_els = []} =
|
||||
send_recv(Config,
|
||||
#iq{type = set,
|
||||
sub_els = [#carbons_disable{}]}),
|
||||
wait_for_master(Config),
|
||||
%% Now we should receive nothing but presence unavailable from the peer
|
||||
#presence{from = Peer, type = unavailable} = recv(),
|
||||
disconnect(Config).
|
||||
|
||||
client_state_master(Config) ->
|
||||
Peer = ?config(slave, Config),
|
||||
Presence = #presence{to = Peer},
|
||||
Message = #message{to = Peer, thread = <<"1">>,
|
||||
sub_els = [#chatstate{type = active}]},
|
||||
wait_for_slave(Config),
|
||||
%% Should be queued (but see below):
|
||||
send(Config, Presence),
|
||||
%% Should be sent immediately, together with the previous presence:
|
||||
send(Config, Message#message{body = [#text{data = <<"body">>}]}),
|
||||
%% Should be dropped:
|
||||
send(Config, Message),
|
||||
%% Should be queued (but see below):
|
||||
send(Config, Presence),
|
||||
%% Should replace the previous presence in the queue:
|
||||
send(Config, Presence#presence{type = unavailable}),
|
||||
wait_for_slave(Config),
|
||||
%% Should be sent immediately, as the client is active again.
|
||||
send(Config, Message),
|
||||
disconnect(Config).
|
||||
|
||||
client_state_slave(Config) ->
|
||||
true = ?config(csi, Config),
|
||||
Peer = ?config(master, Config),
|
||||
send(Config, #csi{type = inactive}),
|
||||
wait_for_master(Config),
|
||||
#presence{from = Peer, sub_els = [#vcard_xupdate{}|_]} = recv(),
|
||||
#message{from = Peer, thread = <<"1">>, sub_els = [#chatstate{type = active}],
|
||||
body = [#text{data = <<"body">>}]} = recv(),
|
||||
wait_for_master(Config),
|
||||
send(Config, #csi{type = active}),
|
||||
?recv2(#presence{from = Peer, type = unavailable,
|
||||
sub_els = [#delay{}, #legacy_delay{}]},
|
||||
#message{from = Peer, thread = <<"1">>,
|
||||
sub_els = [#chatstate{type = active}]}),
|
||||
disconnect(Config).
|
||||
|
||||
%%%===================================================================
|
||||
%%% Aux functions
|
||||
%%%===================================================================
|
||||
@@ -1022,3 +1702,16 @@ split(Data) ->
|
||||
(_) ->
|
||||
true
|
||||
end, re:split(Data, <<"\s">>)).
|
||||
|
||||
clear_riak_tables(Config) ->
|
||||
User = ?config(user, Config),
|
||||
Server = ?config(server, Config),
|
||||
Room = muc_room_jid(Config),
|
||||
{URoom, SRoom, _} = jlib:jid_tolower(Room),
|
||||
ejabberd_auth:remove_user(User, Server),
|
||||
ejabberd_auth:remove_user(<<"test_slave">>, Server),
|
||||
ejabberd_auth:remove_user(<<"test_master">>, Server),
|
||||
mod_muc:forget_room(Server, URoom, SRoom),
|
||||
ejabberd_riak:delete(muc_registered, {{<<"test_slave">>, Server}, SRoom}),
|
||||
ejabberd_riak:delete(muc_registered, {{<<"test_master">>, Server}, SRoom}),
|
||||
Config.
|
||||
|
||||
@@ -11,6 +11,7 @@ host_config:
|
||||
modules:
|
||||
mod_announce:
|
||||
db_type: odbc
|
||||
access: local
|
||||
mod_blocking:
|
||||
db_type: odbc
|
||||
mod_caps:
|
||||
@@ -39,6 +40,8 @@ host_config:
|
||||
db_type: odbc
|
||||
mod_vcard:
|
||||
db_type: odbc
|
||||
mod_vcard_xupdate:
|
||||
db_type: odbc
|
||||
mod_adhoc: []
|
||||
mod_configure: []
|
||||
mod_disco: []
|
||||
@@ -64,6 +67,7 @@ Welcome to this XMPP server."
|
||||
modules:
|
||||
mod_announce:
|
||||
db_type: odbc
|
||||
access: local
|
||||
mod_blocking:
|
||||
db_type: odbc
|
||||
mod_caps:
|
||||
@@ -92,6 +96,8 @@ Welcome to this XMPP server."
|
||||
db_type: odbc
|
||||
mod_vcard:
|
||||
db_type: odbc
|
||||
mod_vcard_xupdate:
|
||||
db_type: odbc
|
||||
mod_adhoc: []
|
||||
mod_configure: []
|
||||
mod_disco: []
|
||||
@@ -110,6 +116,7 @@ Welcome to this XMPP server."
|
||||
modules:
|
||||
mod_announce:
|
||||
db_type: internal
|
||||
access: local
|
||||
mod_blocking:
|
||||
db_type: internal
|
||||
mod_caps:
|
||||
@@ -138,6 +145,54 @@ Welcome to this XMPP server."
|
||||
db_type: internal
|
||||
mod_vcard:
|
||||
db_type: internal
|
||||
mod_vcard_xupdate:
|
||||
db_type: internal
|
||||
mod_carboncopy:
|
||||
db_type: internal
|
||||
mod_client_state:
|
||||
drop_chat_states: true
|
||||
queue_presence: true
|
||||
mod_adhoc: []
|
||||
mod_configure: []
|
||||
mod_disco: []
|
||||
mod_ping: []
|
||||
mod_proxy65: []
|
||||
mod_register:
|
||||
welcome_message:
|
||||
subject: "Welcome!"
|
||||
body: "Hi.
|
||||
Welcome to this XMPP server."
|
||||
mod_stats: []
|
||||
mod_time: []
|
||||
mod_version: []
|
||||
"riak.localhost":
|
||||
auth_method: riak
|
||||
modules:
|
||||
mod_announce:
|
||||
db_type: riak
|
||||
access: local
|
||||
mod_blocking:
|
||||
db_type: riak
|
||||
mod_caps:
|
||||
db_type: riak
|
||||
mod_last:
|
||||
db_type: riak
|
||||
mod_muc:
|
||||
db_type: riak
|
||||
mod_offline:
|
||||
db_type: riak
|
||||
mod_privacy:
|
||||
db_type: riak
|
||||
mod_private:
|
||||
db_type: riak
|
||||
mod_roster:
|
||||
versioning: true
|
||||
store_current_id: true
|
||||
db_type: riak
|
||||
mod_vcard:
|
||||
db_type: riak
|
||||
mod_vcard_xupdate:
|
||||
db_type: riak
|
||||
mod_adhoc: []
|
||||
mod_configure: []
|
||||
mod_disco: []
|
||||
@@ -186,6 +241,7 @@ hosts:
|
||||
- "pgsql.localhost"
|
||||
- "extauth.localhost"
|
||||
- "ldap.localhost"
|
||||
- "riak.localhost"
|
||||
access:
|
||||
announce:
|
||||
admin: allow
|
||||
@@ -258,4 +314,4 @@ Welcome to this XMPP server."
|
||||
registration_timeout: infinity
|
||||
shaper:
|
||||
fast: 50000
|
||||
normal: 1000
|
||||
normal: 1000
|
||||
|
||||
+19
-2
@@ -39,9 +39,14 @@ init_config(Config) ->
|
||||
{server_host, "localhost"},
|
||||
{server, ?COMMON_VHOST},
|
||||
{user, <<"test_single">>},
|
||||
{master_nick, <<"master_nick">>},
|
||||
{slave_nick, <<"slave_nick">>},
|
||||
{room_subject, <<"hello, world!">>},
|
||||
{certfile, CertFile},
|
||||
{base_dir, BaseDir},
|
||||
{resource, <<"resource">>},
|
||||
{master_resource, <<"master_resource">>},
|
||||
{slave_resource, <<"slave_resource">>},
|
||||
{password, <<"password">>}
|
||||
|Config].
|
||||
|
||||
@@ -83,6 +88,11 @@ disconnect(Config) ->
|
||||
ejabberd_socket:close(Socket),
|
||||
Config.
|
||||
|
||||
close_socket(Config) ->
|
||||
Socket = ?config(socket, Config),
|
||||
ejabberd_socket:close(Socket),
|
||||
Config.
|
||||
|
||||
starttls(Config) ->
|
||||
send(Config, #starttls{}),
|
||||
#starttls_proceed{} = recv(),
|
||||
@@ -141,8 +151,15 @@ wait_auth_SASL_result(Config) ->
|
||||
{xmlstreamstart, <<"stream:stream">>, Attrs} = recv(),
|
||||
<<"jabber:client">> = xml:get_attr_s(<<"xmlns">>, Attrs),
|
||||
<<"1.0">> = xml:get_attr_s(<<"version">>, Attrs),
|
||||
#stream_features{} = recv(),
|
||||
Config;
|
||||
#stream_features{sub_els = Fs} = recv(),
|
||||
lists:foldl(
|
||||
fun(#feature_sm{}, ConfigAcc) ->
|
||||
set_opt(sm, true, ConfigAcc);
|
||||
(#feature_csi{}, ConfigAcc) ->
|
||||
set_opt(csi, true, ConfigAcc);
|
||||
(_, ConfigAcc) ->
|
||||
ConfigAcc
|
||||
end, Config, Fs);
|
||||
#sasl_challenge{text = ClientIn} ->
|
||||
{Response, SASL} = (?config(sasl, Config))(ClientIn),
|
||||
send(Config, #sasl_response{text = Response}),
|
||||
|
||||
+4
-1
@@ -1,5 +1,5 @@
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include("xml.hrl").
|
||||
-include_lib("p1_xml/include/xml.hrl").
|
||||
-include("ns.hrl").
|
||||
-include("ejabberd.hrl").
|
||||
-include("mod_proxy65.hrl").
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
-define(PUBSUB(Node), <<(?NS_PUBSUB)/binary, "#", Node>>).
|
||||
|
||||
-define(EJABBERD_CT_URI, <<"http://www.process-one.net/en/ejabberd_ct/">>).
|
||||
|
||||
-define(recv2(P1, P2),
|
||||
(fun() ->
|
||||
case {R1 = recv(), R2 = recv()} of
|
||||
@@ -59,6 +61,7 @@
|
||||
-define(PGSQL_VHOST, <<"pgsql.localhost">>).
|
||||
-define(LDAP_VHOST, <<"ldap.localhost">>).
|
||||
-define(EXTAUTH_VHOST, <<"extauth.localhost">>).
|
||||
-define(RIAK_VHOST, <<"riak.localhost">>).
|
||||
|
||||
insert(Val, N, Tuple) ->
|
||||
L = tuple_to_list(Tuple),
|
||||
|
||||
@@ -1,327 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% File : p1_prof.erl
|
||||
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%% Description : Handy wrapper around eprof and fprof
|
||||
%%%
|
||||
%%% Created : 23 Jan 2010 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2014 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(p1_prof).
|
||||
|
||||
%% API
|
||||
-export([eprof_start/0, eprof_stop/0,
|
||||
fprof_start/0, fprof_start/1,
|
||||
fprof_stop/0, fprof_analyze/0,
|
||||
queue/0, queue/1, memory/0, memory/1,
|
||||
reds/0, reds/1, trace/1, help/0,
|
||||
q/0, m/0, r/0, q/1, m/1, r/1]).
|
||||
|
||||
-define(TRACE_FILE, "/tmp/fprof.trace").
|
||||
-define(ANALYSIS_FILE, "/tmp/fprof.analysis").
|
||||
|
||||
%%====================================================================
|
||||
%% API
|
||||
%%====================================================================
|
||||
eprof_start() ->
|
||||
eprof:start(),
|
||||
case get_procs() of
|
||||
[] ->
|
||||
{error, no_procs_found};
|
||||
Procs ->
|
||||
eprof:start_profiling(Procs)
|
||||
end.
|
||||
|
||||
fprof_start() ->
|
||||
fprof_start(0).
|
||||
|
||||
fprof_start(Duration) ->
|
||||
case get_procs() of
|
||||
[] ->
|
||||
{error, no_procs_found};
|
||||
Procs ->
|
||||
case fprof:trace([start, {procs, Procs}, {file, ?TRACE_FILE}]) of
|
||||
ok ->
|
||||
io:format("Profiling started, writing trace data to ~s~n",
|
||||
[?TRACE_FILE]),
|
||||
if Duration > 0 ->
|
||||
timer:sleep(Duration*1000),
|
||||
fprof:trace([stop]),
|
||||
fprof:stop();
|
||||
true->
|
||||
ok
|
||||
end;
|
||||
Err ->
|
||||
io:format("Couldn't start profiling: ~p~n", [Err]),
|
||||
Err
|
||||
end
|
||||
end.
|
||||
|
||||
fprof_stop() ->
|
||||
fprof:trace([stop]),
|
||||
case fprof:profile([{file, ?TRACE_FILE}]) of
|
||||
ok ->
|
||||
case fprof:analyse([totals, no_details, {sort, own},
|
||||
no_callers, {dest, ?ANALYSIS_FILE}]) of
|
||||
ok ->
|
||||
fprof:stop(),
|
||||
format_fprof_analyze();
|
||||
Err ->
|
||||
io:format("Couldn't analyze: ~p~n", [Err]),
|
||||
Err
|
||||
end;
|
||||
Err ->
|
||||
io:format("Couldn't compile a trace into profile data: ~p~n",
|
||||
[Err]),
|
||||
Err
|
||||
end.
|
||||
|
||||
fprof_analyze() ->
|
||||
fprof_stop().
|
||||
|
||||
eprof_stop() ->
|
||||
eprof:stop_profiling(),
|
||||
case erlang:function_exported(eprof, analyse, 0) of
|
||||
true ->
|
||||
apply(eprof, analyse, []);
|
||||
false ->
|
||||
eprof:analyze()
|
||||
end.
|
||||
|
||||
help() ->
|
||||
M = ?MODULE,
|
||||
io:format("Brief help:~n"
|
||||
"~p:queue(N) - show top N pids sorted by queue length~n"
|
||||
"~p:queue() - shorthand for ~p:queue(10)~n"
|
||||
"~p:memory(N) - show top N pids sorted by memory usage~n"
|
||||
"~p:memory() - shorthand for ~p:memory(10)~n"
|
||||
"~p:reds(N) - show top N pids sorted by reductions~n"
|
||||
"~p:reds() - shorthand for ~p:reds(10)~n"
|
||||
"~p:q(N)|~p:q() - same as ~p:queue(N)|~p:queue()~n"
|
||||
"~p:m(N)|~p:m() - same as ~p:memory(N)|~p:memory()~n"
|
||||
"~p:r(N)|~p:r() - same as ~p:reds(N)|~p:reds()~n"
|
||||
"~p:trace(Pid) - trace Pid; to stop tracing close "
|
||||
"Erlang shell with Ctrl+C~n"
|
||||
"~p:eprof_start() - start eprof on all available pids; "
|
||||
"DO NOT use on production system!~n"
|
||||
"~p:eprof_stop() - stop eprof and print result~n"
|
||||
"~p:fprof_start() - start fprof on all available pids; "
|
||||
"DO NOT use on production system!~n"
|
||||
"~p:fprof_stop() - stop eprof and print formatted result~n"
|
||||
"~p:fprof_start(N) - start and run fprof for N seconds; "
|
||||
"use ~p:fprof_analyze() to analyze collected statistics and "
|
||||
"print formatted result; use on production system with CARE~n"
|
||||
"~p:fprof_analyze() - analyze previously collected statistics "
|
||||
"using ~p:fprof_start(N) and print formatted result~n"
|
||||
"~p:help() - print this help~n",
|
||||
lists:duplicate(31, M)).
|
||||
|
||||
q() ->
|
||||
queue().
|
||||
|
||||
q(N) ->
|
||||
queue(N).
|
||||
|
||||
m() ->
|
||||
memory().
|
||||
|
||||
m(N) ->
|
||||
memory(N).
|
||||
|
||||
r() ->
|
||||
reds().
|
||||
|
||||
r(N) ->
|
||||
reds(N).
|
||||
|
||||
queue() ->
|
||||
queue(10).
|
||||
|
||||
memory() ->
|
||||
memory(10).
|
||||
|
||||
reds() ->
|
||||
reds(10).
|
||||
|
||||
queue(N) ->
|
||||
dump(N, lists:reverse(lists:ukeysort(1, all_pids(queue)))).
|
||||
|
||||
memory(N) ->
|
||||
dump(N, lists:reverse(lists:ukeysort(3, all_pids(memory)))).
|
||||
|
||||
reds(N) ->
|
||||
dump(N, lists:reverse(lists:ukeysort(4, all_pids(reductions)))).
|
||||
|
||||
trace(Pid) ->
|
||||
erlang:trace(Pid, true, [send, 'receive']),
|
||||
trace_loop().
|
||||
|
||||
trace_loop() ->
|
||||
receive
|
||||
M ->
|
||||
io:format("~p~n", [M]),
|
||||
trace_loop()
|
||||
end.
|
||||
|
||||
%%====================================================================
|
||||
%% Internal functions
|
||||
%%====================================================================
|
||||
get_procs() ->
|
||||
processes().
|
||||
|
||||
format_fprof_analyze() ->
|
||||
case file:consult(?ANALYSIS_FILE) of
|
||||
{ok, [_, [{totals, _, _, TotalOWN}] | Rest]} ->
|
||||
OWNs = lists:flatmap(
|
||||
fun({MFA, _, _, OWN}) ->
|
||||
Percent = OWN*100/TotalOWN,
|
||||
case round(Percent) of
|
||||
0 ->
|
||||
[];
|
||||
_ ->
|
||||
[{mfa_to_list(MFA), Percent}]
|
||||
end
|
||||
end, Rest),
|
||||
ACCs = collect_accs(Rest),
|
||||
MaxACC = find_max(ACCs),
|
||||
MaxOWN = find_max(OWNs),
|
||||
io:format("=== Sorted by OWN:~n"),
|
||||
lists:foreach(
|
||||
fun({MFA, Per}) ->
|
||||
L = length(MFA),
|
||||
S = lists:duplicate(MaxOWN - L + 2, $ ),
|
||||
io:format("~s~s~.2f%~n", [MFA, S, Per])
|
||||
end, lists:reverse(lists:keysort(2, OWNs))),
|
||||
io:format("~n=== Sorted by ACC:~n"),
|
||||
lists:foreach(
|
||||
fun({MFA, Per}) ->
|
||||
L = length(MFA),
|
||||
S = lists:duplicate(MaxACC - L + 2, $ ),
|
||||
io:format("~s~s~.2f%~n", [MFA, S, Per])
|
||||
end, lists:reverse(lists:keysort(2, ACCs)));
|
||||
Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
mfa_to_list({M, F, A}) ->
|
||||
atom_to_list(M) ++ ":" ++ atom_to_list(F) ++ "/" ++ integer_to_list(A);
|
||||
mfa_to_list(F) when is_atom(F) ->
|
||||
atom_to_list(F).
|
||||
|
||||
find_max(List) ->
|
||||
find_max(List, 0).
|
||||
|
||||
find_max([{V, _}|Tail], Acc) ->
|
||||
find_max(Tail, lists:max([length(V), Acc]));
|
||||
find_max([], Acc) ->
|
||||
Acc.
|
||||
|
||||
collect_accs(List) ->
|
||||
List1 = lists:filter(
|
||||
fun({MFA, _, _, _}) ->
|
||||
case MFA of
|
||||
{sys, _, _} ->
|
||||
false;
|
||||
suspend ->
|
||||
false;
|
||||
{gen_fsm, _, _} ->
|
||||
false;
|
||||
{p1_fsm, _, _} ->
|
||||
false;
|
||||
{gen, _, _} ->
|
||||
false;
|
||||
{gen_server, _, _} ->
|
||||
false;
|
||||
{proc_lib, _, _} ->
|
||||
false;
|
||||
_ ->
|
||||
true
|
||||
end
|
||||
end, List),
|
||||
TotalACC = lists:sum([A || {_, _, A, _} <- List1]),
|
||||
lists:flatmap(
|
||||
fun({MFA, _, ACC, _}) ->
|
||||
Percent = ACC*100/TotalACC,
|
||||
case round(Percent) of
|
||||
0 ->
|
||||
[];
|
||||
_ ->
|
||||
[{mfa_to_list(MFA), Percent}]
|
||||
end
|
||||
end, List1).
|
||||
|
||||
all_pids(Type) ->
|
||||
lists:foldl(
|
||||
fun(P, Acc) when P == self() ->
|
||||
%% exclude ourself from statistics
|
||||
Acc;
|
||||
(P, Acc) ->
|
||||
case catch process_info(
|
||||
P,
|
||||
[message_queue_len,
|
||||
memory,
|
||||
reductions,
|
||||
dictionary,
|
||||
current_function,
|
||||
registered_name]) of
|
||||
[{_, Len}, {_, Memory}, {_, Reds},
|
||||
{_, Dict}, {_, CurFun}, {_, RegName}] ->
|
||||
IntQLen = case lists:keysearch('$internal_queue_len', 1, Dict) of
|
||||
{value, {_, N}} ->
|
||||
N;
|
||||
_ ->
|
||||
0
|
||||
end,
|
||||
if Type == queue andalso Len == 0 andalso IntQLen == 0 ->
|
||||
Acc;
|
||||
true ->
|
||||
MaxLen = lists:max([Len, IntQLen]),
|
||||
[{MaxLen, Len, Memory, Reds, Dict, CurFun, P, RegName}|Acc]
|
||||
end;
|
||||
_ ->
|
||||
Acc
|
||||
end
|
||||
end, [], processes()).
|
||||
|
||||
dump(N, Rs) ->
|
||||
lists:foreach(
|
||||
fun({_, MsgQLen, Memory, Reds, Dict, CurFun, Pid, RegName}) ->
|
||||
PidStr = pid_to_list(Pid),
|
||||
[_, Maj, Min] = string:tokens(
|
||||
string:substr(
|
||||
PidStr, 2, length(PidStr) - 2), "."),
|
||||
io:format("** pid(0,~s,~s)~n"
|
||||
"** registered name: ~p~n"
|
||||
"** memory: ~p~n"
|
||||
"** reductions: ~p~n"
|
||||
"** message queue len: ~p~n"
|
||||
"** current_function: ~p~n"
|
||||
"** dictionary: ~p~n~n",
|
||||
[Maj, Min, RegName, Memory, Reds, MsgQLen, CurFun, Dict])
|
||||
end, nthhead(N, Rs)).
|
||||
|
||||
nthhead(N, L) ->
|
||||
lists:reverse(nthhead(N, L, [])).
|
||||
|
||||
nthhead(0, _L, Acc) ->
|
||||
Acc;
|
||||
nthhead(N, [H|T], Acc) ->
|
||||
nthhead(N-1, T, [H|Acc]);
|
||||
nthhead(_N, [], Acc) ->
|
||||
Acc.
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
export PATH="/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:$PATH"
|
||||
|
||||
deps_dir='deps'
|
||||
rebar_script='rebar.config.script'
|
||||
temp_file=$(mktemp "$rebar_script.XXXXXX")
|
||||
|
||||
trap 'rm -f $temp_file' EXIT INT TERM
|
||||
|
||||
die()
|
||||
{
|
||||
echo >&2 "FATAL: $@."
|
||||
exit 1
|
||||
}
|
||||
|
||||
get_dep_list()
|
||||
{
|
||||
sed -n \
|
||||
'/.*{ *\([^,]*\),[^,]*, *{git, *"\([^"]*\)".*/ {
|
||||
s//\1,\2/
|
||||
p
|
||||
}' "$rebar_script"
|
||||
}
|
||||
|
||||
get_dep_name()
|
||||
{
|
||||
printf '%s' "${1%%,*}"
|
||||
}
|
||||
|
||||
get_dep_url()
|
||||
{
|
||||
printf '%s' "${1#*,}"
|
||||
}
|
||||
|
||||
get_dep_rev()
|
||||
{
|
||||
dep_name=$(get_dep_name "$1")
|
||||
dep_dir="$deps_dir/$dep_name"
|
||||
|
||||
test -d "$dep_dir" || clone_repo "$dep"
|
||||
cd "$dep_dir"
|
||||
printf '%s' "$(git rev-parse --verify HEAD)"
|
||||
cd "$OLDPWD"
|
||||
}
|
||||
|
||||
clone_repo()
|
||||
{
|
||||
dep_name=$(get_dep_name "$1")
|
||||
dep_url=$(get_dep_url "$1")
|
||||
|
||||
cd "$deps_dir"
|
||||
git clone -q "$dep_url" "$dep_name"
|
||||
cd "$OLDPWD"
|
||||
}
|
||||
|
||||
edit_rebar_script()
|
||||
{
|
||||
dep_name=$(get_dep_name "$1")
|
||||
dep_url=$(get_dep_url "$1")
|
||||
dep_rev=$(get_dep_rev "$1")
|
||||
|
||||
echo "Using revision $dep_rev of $dep_name"
|
||||
sed "s|\"$dep_url\"[^}]*}|\"$dep_url\", \"$dep_rev\"}|" \
|
||||
"$rebar_script" >"$temp_file"
|
||||
mv "$temp_file" "$rebar_script"
|
||||
}
|
||||
|
||||
test -e "$rebar_script" || die 'Please change to ejabberd source directory'
|
||||
test -d "$deps_dir" || mkdir -p "$deps_dir"
|
||||
|
||||
for dep in $(get_dep_list)
|
||||
do
|
||||
edit_rebar_script "$dep"
|
||||
done
|
||||
+11081
-7039
File diff suppressed because it is too large
Load Diff
+88
-34
@@ -1,14 +1,29 @@
|
||||
%% Created automatically by XML generator (xml_gen.erl)
|
||||
%% Source: xmpp_codec.spec
|
||||
|
||||
-record(chatstate, {type :: active | composing | gone | inactive | paused}).
|
||||
|
||||
-record(csi, {type :: active | inactive}).
|
||||
|
||||
-record(feature_register, {}).
|
||||
|
||||
-record(sasl_success, {text :: any()}).
|
||||
|
||||
-record(text, {lang :: binary(),
|
||||
data :: binary()}).
|
||||
|
||||
-record(streamhost, {jid :: any(),
|
||||
host :: binary(),
|
||||
port = 1080 :: non_neg_integer()}).
|
||||
|
||||
-record(sm_resume, {h :: non_neg_integer(),
|
||||
previd :: binary(),
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(carbons_enable, {}).
|
||||
|
||||
-record(carbons_private, {}).
|
||||
|
||||
-record(pubsub_unsubscribe, {node :: binary(),
|
||||
jid :: any(),
|
||||
subid :: binary()}).
|
||||
@@ -30,8 +45,22 @@
|
||||
from :: any(),
|
||||
to :: any()}).
|
||||
|
||||
-record(sm_a, {h :: non_neg_integer(),
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(starttls_proceed, {}).
|
||||
|
||||
-record(sm_resumed, {h :: non_neg_integer(),
|
||||
previd :: binary(),
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(forwarded, {delay :: #delay{},
|
||||
sub_els = [] :: [any()]}).
|
||||
|
||||
-record(sm_enable, {max :: non_neg_integer(),
|
||||
resume = false :: any(),
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(starttls_failure, {}).
|
||||
|
||||
-record(sasl_challenge, {text :: any()}).
|
||||
@@ -42,6 +71,8 @@
|
||||
|
||||
-record(p1_ack, {}).
|
||||
|
||||
-record(feature_sm, {xmlns :: binary()}).
|
||||
|
||||
-record(pubsub_item, {id :: binary(),
|
||||
xml_els = [] :: [any()]}).
|
||||
|
||||
@@ -61,6 +92,8 @@
|
||||
node :: binary(),
|
||||
publisher :: binary()}).
|
||||
|
||||
-record(sm_r, {xmlns :: binary()}).
|
||||
|
||||
-record(muc_actor, {jid :: any(),
|
||||
nick :: binary()}).
|
||||
|
||||
@@ -78,14 +111,20 @@
|
||||
-record(last, {seconds :: non_neg_integer(),
|
||||
text :: binary()}).
|
||||
|
||||
-record(redirect, {uri :: binary()}).
|
||||
|
||||
-record(sm_enabled, {id :: binary(),
|
||||
location :: binary(),
|
||||
max :: non_neg_integer(),
|
||||
resume = false :: any(),
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(pubsub_event_items, {node :: binary(),
|
||||
retract = [] :: [binary()],
|
||||
items = [] :: [#pubsub_event_item{}]}).
|
||||
|
||||
-record(pubsub_event, {items = [] :: [#pubsub_event_items{}]}).
|
||||
|
||||
-record(redirect, {uri :: binary()}).
|
||||
|
||||
-record(sasl_response, {text :: any()}).
|
||||
|
||||
-record(pubsub_subscribe, {node :: binary(),
|
||||
@@ -96,6 +135,8 @@
|
||||
|
||||
-record(p1_push, {}).
|
||||
|
||||
-record(feature_csi, {xmlns :: binary()}).
|
||||
|
||||
-record(legacy_delay, {stamp :: binary(),
|
||||
from :: any()}).
|
||||
|
||||
@@ -126,6 +167,16 @@
|
||||
subid :: binary(),
|
||||
type :: 'none' | 'pending' | 'subscribed' | 'unconfigured'}).
|
||||
|
||||
-record(muc_item, {actor :: #muc_actor{},
|
||||
continue :: binary(),
|
||||
reason :: binary(),
|
||||
affiliation :: 'admin' | 'member' | 'none' | 'outcast' | 'owner',
|
||||
role :: 'moderator' | 'none' | 'participant' | 'visitor',
|
||||
jid :: any(),
|
||||
nick :: binary()}).
|
||||
|
||||
-record(muc_admin, {items = [] :: [#muc_item{}]}).
|
||||
|
||||
-record(shim, {headers = [] :: [{binary(),'undefined' | binary()}]}).
|
||||
|
||||
-record(caps, {hash :: binary(),
|
||||
@@ -144,6 +195,8 @@
|
||||
subid :: binary(),
|
||||
items = [] :: [#pubsub_item{}]}).
|
||||
|
||||
-record(carbons_sent, {forwarded :: #forwarded{}}).
|
||||
|
||||
-record(p1_rebind, {}).
|
||||
|
||||
-record(compress_failure, {reason :: 'processing-failed' | 'setup-failed' | 'unsupported-method'}).
|
||||
@@ -157,13 +210,12 @@
|
||||
x400 = false :: boolean(),
|
||||
userid :: binary()}).
|
||||
|
||||
-record(carbons_received, {forwarded :: #forwarded{}}).
|
||||
|
||||
-record(pubsub_retract, {node :: binary(),
|
||||
notify = false :: any(),
|
||||
items = [] :: [#pubsub_item{}]}).
|
||||
|
||||
-record(text, {lang :: binary(),
|
||||
data :: binary()}).
|
||||
|
||||
-record(vcard_geo, {lat :: binary(),
|
||||
lon :: binary()}).
|
||||
|
||||
@@ -193,14 +245,6 @@
|
||||
-record(bind, {jid :: any(),
|
||||
resource :: any()}).
|
||||
|
||||
-record(muc_item, {actor :: #muc_actor{},
|
||||
continue :: binary(),
|
||||
reason :: binary(),
|
||||
affiliation :: 'admin' | 'member' | 'none' | 'outcast' | 'owner',
|
||||
role :: 'moderator' | 'none' | 'participant' | 'visitor',
|
||||
jid :: any(),
|
||||
nick :: binary()}).
|
||||
|
||||
-record(muc_user, {decline :: #muc_decline{},
|
||||
destroy :: #muc_user_destroy{},
|
||||
invites = [] :: [#muc_invite{}],
|
||||
@@ -208,6 +252,10 @@
|
||||
status_codes = [] :: [pos_integer()],
|
||||
password :: binary()}).
|
||||
|
||||
-record(vcard_xupdate, {photo :: binary()}).
|
||||
|
||||
-record(carbons_disable, {}).
|
||||
|
||||
-record(bytestreams, {hosts = [] :: [#streamhost{}],
|
||||
used :: any(),
|
||||
activate :: any(),
|
||||
@@ -253,27 +301,6 @@
|
||||
nick :: binary(),
|
||||
password :: binary()}).
|
||||
|
||||
-record(register, {registered = false :: boolean(),
|
||||
remove = false :: boolean(),
|
||||
instructions :: binary(),
|
||||
username :: 'none' | binary(),
|
||||
nick :: 'none' | binary(),
|
||||
password :: 'none' | binary(),
|
||||
name :: 'none' | binary(),
|
||||
first :: 'none' | binary(),
|
||||
last :: 'none' | binary(),
|
||||
email :: 'none' | binary(),
|
||||
address :: 'none' | binary(),
|
||||
city :: 'none' | binary(),
|
||||
state :: 'none' | binary(),
|
||||
zip :: 'none' | binary(),
|
||||
phone :: 'none' | binary(),
|
||||
url :: 'none' | binary(),
|
||||
date :: 'none' | binary(),
|
||||
misc :: 'none' | binary(),
|
||||
text :: 'none' | binary(),
|
||||
key :: 'none' | binary()}).
|
||||
|
||||
-record(bookmark_url, {name :: binary(),
|
||||
url :: binary()}).
|
||||
|
||||
@@ -336,6 +363,28 @@
|
||||
items :: #pubsub_items{},
|
||||
retract :: #pubsub_retract{}}).
|
||||
|
||||
-record(register, {registered = false :: boolean(),
|
||||
remove = false :: boolean(),
|
||||
instructions :: binary(),
|
||||
username :: 'none' | binary(),
|
||||
nick :: 'none' | binary(),
|
||||
password :: 'none' | binary(),
|
||||
name :: 'none' | binary(),
|
||||
first :: 'none' | binary(),
|
||||
last :: 'none' | binary(),
|
||||
email :: 'none' | binary(),
|
||||
address :: 'none' | binary(),
|
||||
city :: 'none' | binary(),
|
||||
state :: 'none' | binary(),
|
||||
zip :: 'none' | binary(),
|
||||
phone :: 'none' | binary(),
|
||||
url :: 'none' | binary(),
|
||||
date :: 'none' | binary(),
|
||||
misc :: 'none' | binary(),
|
||||
text :: 'none' | binary(),
|
||||
key :: 'none' | binary(),
|
||||
xdata :: #xdata{}}).
|
||||
|
||||
-record(disco_info, {node :: binary(),
|
||||
identities = [] :: [#identity{}],
|
||||
features = [] :: [binary()],
|
||||
@@ -343,6 +392,9 @@
|
||||
|
||||
-record(sasl_mechanisms, {list = [] :: [binary()]}).
|
||||
|
||||
-record(sm_failed, {reason :: atom() | #gone{} | #redirect{},
|
||||
xmlns :: binary()}).
|
||||
|
||||
-record(error, {type :: 'auth' | 'cancel' | 'continue' | 'modify' | 'wait',
|
||||
by :: binary(),
|
||||
reason :: atom() | #gone{} | #redirect{},
|
||||
@@ -430,3 +482,5 @@
|
||||
|
||||
-record(time, {tzo :: any(),
|
||||
utc :: any()}).
|
||||
|
||||
|
||||
|
||||
+274
-5
@@ -3,7 +3,6 @@
|
||||
xmlns = <<"jabber:iq:last">>,
|
||||
result = {last, '$seconds', '$text'},
|
||||
attrs = [#attr{name = <<"seconds">>,
|
||||
default = undefined,
|
||||
enc = {enc_int, []},
|
||||
dec = {dec_int, [0, infinity]}}],
|
||||
cdata = #cdata{label = '$text'}}).
|
||||
@@ -61,7 +60,6 @@
|
||||
enc = {enc_enum, []},
|
||||
dec = {dec_enum, [[none,to,from,both,remove]]}},
|
||||
#attr{name = <<"ask">>,
|
||||
default = undefined,
|
||||
enc = {enc_enum, []},
|
||||
dec = {dec_enum, [[subscribe]]}}],
|
||||
refs = [#ref{name = roster_group, label = '$groups'}]}).
|
||||
@@ -946,8 +944,10 @@
|
||||
'$username', '$nick', '$password', '$name',
|
||||
'$first', '$last', '$email', '$address',
|
||||
'$city', '$state', '$zip', '$phone', '$url',
|
||||
'$date', '$misc', '$text', '$key'},
|
||||
refs = [#ref{name = register_registered, min = 0, max = 1,
|
||||
'$date', '$misc', '$text', '$key', '$xdata'},
|
||||
refs = [#ref{name = xdata, min = 0, max = 1,
|
||||
label = '$xdata'},
|
||||
#ref{name = register_registered, min = 0, max = 1,
|
||||
default = false, label = '$registered'},
|
||||
#ref{name = register_remove, min = 0, max = 1,
|
||||
default = false, label = '$remove'},
|
||||
@@ -1486,6 +1486,18 @@
|
||||
label = '$categories'},
|
||||
#ref{name = vcard_CLASS, min = 0, max = 1, label = '$class'}]}).
|
||||
|
||||
-xml(vcard_xupdate_photo,
|
||||
#elem{name = <<"photo">>,
|
||||
xmlns = <<"vcard-temp:x:update">>,
|
||||
result = '$cdata'}).
|
||||
|
||||
-xml(vcard_xupdate,
|
||||
#elem{name = <<"x">>,
|
||||
xmlns = <<"vcard-temp:x:update">>,
|
||||
result = {vcard_xupdate, '$photo'},
|
||||
refs = [#ref{name = vcard_xupdate_photo, min = 0, max = 1,
|
||||
label = '$photo'}]}).
|
||||
|
||||
-xml(xdata_field_required,
|
||||
#elem{name = <<"required">>,
|
||||
xmlns = <<"jabber:x:data">>,
|
||||
@@ -1757,6 +1769,33 @@
|
||||
result = {shim, '$headers'},
|
||||
refs = [#ref{name = shim_header, label = '$headers'}]}).
|
||||
|
||||
-record(chatstate, {type :: active | composing | gone | inactive | paused}).
|
||||
|
||||
-xml(chatstate_active,
|
||||
#elem{name = <<"active">>,
|
||||
xmlns = <<"http://jabber.org/protocol/chatstates">>,
|
||||
result = {chatstate, active}}).
|
||||
|
||||
-xml(chatstate_composing,
|
||||
#elem{name = <<"composing">>,
|
||||
xmlns = <<"http://jabber.org/protocol/chatstates">>,
|
||||
result = {chatstate, composing}}).
|
||||
|
||||
-xml(chatstate_gone,
|
||||
#elem{name = <<"gone">>,
|
||||
xmlns = <<"http://jabber.org/protocol/chatstates">>,
|
||||
result = {chatstate, gone}}).
|
||||
|
||||
-xml(chatstate_inactive,
|
||||
#elem{name = <<"inactive">>,
|
||||
xmlns = <<"http://jabber.org/protocol/chatstates">>,
|
||||
result = {chatstate, inactive}}).
|
||||
|
||||
-xml(chatstate_paused,
|
||||
#elem{name = <<"paused">>,
|
||||
xmlns = <<"http://jabber.org/protocol/chatstates">>,
|
||||
result = {chatstate, paused}}).
|
||||
|
||||
-xml(delay,
|
||||
#elem{name = <<"delay">>,
|
||||
xmlns = <<"urn:xmpp:delay">>,
|
||||
@@ -1976,6 +2015,56 @@
|
||||
label = '$destroy'},
|
||||
#ref{name = xdata, min = 0, max = 1, label = '$config'}]}).
|
||||
|
||||
-xml(muc_admin_item,
|
||||
#elem{name = <<"item">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
|
||||
result = {muc_item, '$actor', '$continue', '$reason',
|
||||
'$affiliation', '$role', '$jid', '$nick'},
|
||||
refs = [#ref{name = muc_admin_actor,
|
||||
min = 0, max = 1, label = '$actor'},
|
||||
#ref{name = muc_admin_continue,
|
||||
min = 0, max = 1, label = '$continue'},
|
||||
#ref{name = muc_admin_reason,
|
||||
min = 0, max = 1, label = '$reason'}],
|
||||
attrs = [#attr{name = <<"affiliation">>,
|
||||
dec = {dec_enum, [[admin, member, none,
|
||||
outcast, owner]]},
|
||||
enc = {enc_enum, []}},
|
||||
#attr{name = <<"role">>,
|
||||
dec = {dec_enum, [[moderator, none,
|
||||
participant, visitor]]},
|
||||
enc = {enc_enum, []}},
|
||||
#attr{name = <<"jid">>,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}},
|
||||
#attr{name = <<"nick">>}]}).
|
||||
|
||||
-xml(muc_admin_actor,
|
||||
#elem{name = <<"actor">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
|
||||
result = {muc_actor, '$jid', '$nick'},
|
||||
attrs = [#attr{name = <<"jid">>,
|
||||
dec = {dec_jid, []},
|
||||
enc = {enc_jid, []}},
|
||||
#attr{name = <<"nick">>}]}).
|
||||
|
||||
-xml(muc_admin_continue,
|
||||
#elem{name = <<"continue">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
|
||||
result = '$thread',
|
||||
attrs = [#attr{name = <<"thread">>}]}).
|
||||
|
||||
-xml(muc_admin_reason,
|
||||
#elem{name = <<"reason">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
|
||||
result = '$cdata'}).
|
||||
|
||||
-xml(muc_admin,
|
||||
#elem{name = <<"query">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc#admin">>,
|
||||
result = {muc_admin, '$items'},
|
||||
refs = [#ref{name = muc_admin_item, label = '$items'}]}).
|
||||
|
||||
-xml(muc,
|
||||
#elem{name = <<"x">>,
|
||||
xmlns = <<"http://jabber.org/protocol/muc">>,
|
||||
@@ -1984,6 +2073,184 @@
|
||||
refs = [#ref{name = muc_history, min = 0, max = 1,
|
||||
label = '$history'}]}).
|
||||
|
||||
-xml(forwarded,
|
||||
#elem{name = <<"forwarded">>,
|
||||
xmlns = <<"urn:xmpp:forward:0">>,
|
||||
result = {forwarded, '$delay', '$_els'},
|
||||
refs = [#ref{name = delay, min = 0,
|
||||
max = 1, label = '$delay'}]}).
|
||||
|
||||
-xml(carbons_disable,
|
||||
#elem{name = <<"disable">>,
|
||||
xmlns = <<"urn:xmpp:carbons:2">>,
|
||||
result = {carbons_disable}}).
|
||||
|
||||
-xml(carbons_enable,
|
||||
#elem{name = <<"enable">>,
|
||||
xmlns = <<"urn:xmpp:carbons:2">>,
|
||||
result = {carbons_enable}}).
|
||||
|
||||
-xml(carbons_private,
|
||||
#elem{name = <<"private">>,
|
||||
xmlns = <<"urn:xmpp:carbons:2">>,
|
||||
result = {carbons_private}}).
|
||||
|
||||
-xml(carbons_received,
|
||||
#elem{name = <<"received">>,
|
||||
xmlns = <<"urn:xmpp:carbons:2">>,
|
||||
result = {carbons_received, '$forwarded'},
|
||||
refs = [#ref{name = forwarded, min = 1,
|
||||
max = 1, label = '$forwarded'}]}).
|
||||
|
||||
-xml(carbons_sent,
|
||||
#elem{name = <<"sent">>,
|
||||
xmlns = <<"urn:xmpp:carbons:2">>,
|
||||
result = {carbons_sent, '$forwarded'},
|
||||
refs = [#ref{name = forwarded, min = 1,
|
||||
max = 1, label = '$forwarded'}]}).
|
||||
|
||||
-xml(feature_csi,
|
||||
#elem{name = <<"csi">>,
|
||||
xmlns = <<"urn:xmpp:csi:0">>,
|
||||
result = {feature_csi, '$xmlns'},
|
||||
attrs = [#attr{name = <<"xmlns">>}]}).
|
||||
|
||||
-record(csi, {type :: active | inactive}).
|
||||
|
||||
-xml(csi_active,
|
||||
#elem{name = <<"active">>,
|
||||
xmlns = <<"urn:xmpp:csi:0">>,
|
||||
result = {csi, active}}).
|
||||
|
||||
-xml(csi_inactive,
|
||||
#elem{name = <<"inactive">>,
|
||||
xmlns = <<"urn:xmpp:csi:0">>,
|
||||
result = {csi, inactive}}).
|
||||
|
||||
-xml(feature_sm,
|
||||
#elem{name = <<"sm">>,
|
||||
xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
|
||||
result = {feature_sm, '$xmlns'},
|
||||
attrs = [#attr{name = <<"xmlns">>}]}).
|
||||
|
||||
-xml(sm_enable,
|
||||
#elem{name = <<"enable">>,
|
||||
xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
|
||||
result = {sm_enable, '$max', '$resume', '$xmlns'},
|
||||
attrs = [#attr{name = <<"max">>,
|
||||
dec = {dec_int, [0, infinity]},
|
||||
enc = {enc_int, []}},
|
||||
#attr{name = <<"xmlns">>},
|
||||
#attr{name = <<"resume">>,
|
||||
default = false,
|
||||
dec = {dec_bool, []},
|
||||
enc = {enc_bool, []}}]}).
|
||||
|
||||
-xml(sm_enabled,
|
||||
#elem{name = <<"enabled">>,
|
||||
xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
|
||||
result = {sm_enabled, '$id', '$location', '$max', '$resume', '$xmlns'},
|
||||
attrs = [#attr{name = <<"id">>},
|
||||
#attr{name = <<"location">>},
|
||||
#attr{name = <<"xmlns">>},
|
||||
#attr{name = <<"max">>,
|
||||
dec = {dec_int, [0, infinity]},
|
||||
enc = {enc_int, []}},
|
||||
#attr{name = <<"resume">>,
|
||||
default = false,
|
||||
dec = {dec_bool, []},
|
||||
enc = {enc_bool, []}}]}).
|
||||
|
||||
-xml(sm_resume,
|
||||
#elem{name = <<"resume">>,
|
||||
xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
|
||||
result = {sm_resume, '$h', '$previd', '$xmlns'},
|
||||
attrs = [#attr{name = <<"h">>,
|
||||
required = true,
|
||||
dec = {dec_int, [0, infinity]},
|
||||
enc = {enc_int, []}},
|
||||
#attr{name = <<"xmlns">>},
|
||||
#attr{name = <<"previd">>,
|
||||
required = true}]}).
|
||||
|
||||
-xml(sm_resumed,
|
||||
#elem{name = <<"resumed">>,
|
||||
xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
|
||||
result = {sm_resumed, '$h', '$previd', '$xmlns'},
|
||||
attrs = [#attr{name = <<"h">>,
|
||||
required = true,
|
||||
dec = {dec_int, [0, infinity]},
|
||||
enc = {enc_int, []}},
|
||||
#attr{name = <<"xmlns">>},
|
||||
#attr{name = <<"previd">>,
|
||||
required = true}]}).
|
||||
|
||||
-xml(sm_r,
|
||||
#elem{name = <<"r">>,
|
||||
xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
|
||||
result = {sm_r, '$xmlns'},
|
||||
attrs = [#attr{name = <<"xmlns">>}]}).
|
||||
|
||||
-xml(sm_a,
|
||||
#elem{name = <<"a">>,
|
||||
xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
|
||||
result = {sm_a, '$h', '$xmlns'},
|
||||
attrs = [#attr{name = <<"h">>,
|
||||
required = true,
|
||||
dec = {dec_int, [0, infinity]},
|
||||
enc = {enc_int, []}},
|
||||
#attr{name = <<"xmlns">>}]}).
|
||||
|
||||
-xml(sm_failed,
|
||||
#elem{name = <<"failed">>,
|
||||
xmlns = [<<"urn:xmpp:sm:2">>, <<"urn:xmpp:sm:3">>],
|
||||
result = {sm_failed, '$reason', '$xmlns'},
|
||||
attrs = [#attr{name = <<"xmlns">>}],
|
||||
refs = [#ref{name = error_bad_request,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_conflict,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_feature_not_implemented,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_forbidden,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_gone,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_internal_server_error,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_item_not_found,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_jid_malformed,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_not_acceptable,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_not_allowed,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_not_authorized,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_policy_violation,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_recipient_unavailable,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_redirect,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_registration_required,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_remote_server_not_found,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_remote_server_timeout,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_resource_constraint,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_service_unavailable,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_subscription_required,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_undefined_condition,
|
||||
min = 0, max = 1, label = '$reason'},
|
||||
#ref{name = error_unexpected_request,
|
||||
min = 0, max = 1, label = '$reason'}]}).
|
||||
|
||||
dec_tzo(Val) ->
|
||||
[H1, M1] = str:tokens(Val, <<":">>),
|
||||
H = jlib:binary_to_integer(H1),
|
||||
@@ -2026,7 +2293,9 @@ resourceprep(R) ->
|
||||
end.
|
||||
|
||||
dec_bool(<<"false">>) -> false;
|
||||
dec_bool(<<"true">>) -> true.
|
||||
dec_bool(<<"0">>) -> false;
|
||||
dec_bool(<<"true">>) -> true;
|
||||
dec_bool(<<"1">>) -> true.
|
||||
|
||||
enc_bool(false) -> <<"false">>;
|
||||
enc_bool(true) -> <<"true">>.
|
||||
|
||||
+1
-2
@@ -24,9 +24,8 @@
|
||||
{pgsql, @pgsql@}.
|
||||
{pam, @pam@}.
|
||||
{zlib, @zlib@}.
|
||||
{stun, @stun@}.
|
||||
{riak, @riak@}.
|
||||
{json, @json@}.
|
||||
{http, @http@}.
|
||||
{lager, @lager@}.
|
||||
{iconv, @iconv@}.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user